diff --git a/lib/console1984.rb b/lib/console1984.rb index 6a7dc9c..e00a739 100644 --- a/lib/console1984.rb +++ b/lib/console1984.rb @@ -55,6 +55,28 @@ class << self def running_protected_environment? protected_environments.collect(&:to_sym).include?(Rails.env.to_sym) end + + def require_ruby_parser_dependencies + if RUBY_VERSION >= "3.3" + require 'parser' + require 'prism' + else + Kernel.silence_warnings do + require 'parser/current' + end + end + end + + # Returns the parser class used for parsing console commands. + # Uses Prism on Ruby >= 3.3 for forward-compatibility with Ruby 4.0+. + # Falls back to the parser gem on older Rubies. + def ruby_parser + if RUBY_VERSION >= "3.3" + Prism::Translation::ParserCurrent + else + Parser::CurrentRuby + end + end end end diff --git a/lib/console1984/command_validator/.command_parser.rb b/lib/console1984/command_validator/.command_parser.rb index d0b1d79..8a8f734 100644 --- a/lib/console1984/command_validator/.command_parser.rb +++ b/lib/console1984/command_validator/.command_parser.rb @@ -1,7 +1,7 @@ # Naming class with dot so that it doesn't get loaded eagerly by Zeitwerk. We want to load -# only when a console session is started, when +parser+ is loaded. -# -# See +Console1984::Supervisor#require_dependencies+ +# only when a console session is started. +Console1984.require_ruby_parser_dependencies + class Console1984::CommandValidator::CommandParser < ::Parser::AST::Processor include AST::Processor::Mixin include Console1984::Freezeable diff --git a/lib/console1984/command_validator/parsed_command.rb b/lib/console1984/command_validator/parsed_command.rb index 29d80df..ec8c0bf 100644 --- a/lib/console1984/command_validator/parsed_command.rb +++ b/lib/console1984/command_validator/parsed_command.rb @@ -1,6 +1,4 @@ # Parses a command string and exposes different constructs to be used by validations. -# -# Internally, it uses the {parser}[https://github.com/whitequark/parser] gem to perform the parsing. class Console1984::CommandValidator::ParsedCommand include Console1984::Freezeable @@ -15,7 +13,7 @@ def initialize(raw_command) private def command_parser @command_parser ||= Console1984::CommandValidator::CommandParser.new.tap do |processor| - ast = Parser::CurrentRuby.parse(raw_command) + ast = Console1984.ruby_parser.parse(raw_command) processor.process(ast) rescue Parser::SyntaxError # Fail open with syntax errors diff --git a/lib/console1984/refrigerator.rb b/lib/console1984/refrigerator.rb index f52b925..26b1d35 100644 --- a/lib/console1984/refrigerator.rb +++ b/lib/console1984/refrigerator.rb @@ -27,6 +27,6 @@ def freeze_external_modules_and_classes def external_modules_and_classes_to_freeze # Not using a constant because we want this to run lazily (console-dependant dependencies might not be loaded). - [Parser::CurrentRuby] + [Console1984.ruby_parser] end end diff --git a/lib/console1984/supervisor.rb b/lib/console1984/supervisor.rb index f789d9b..f48e414 100644 --- a/lib/console1984/supervisor.rb +++ b/lib/console1984/supervisor.rb @@ -41,13 +41,7 @@ def current_username private def require_dependencies - Kernel.silence_warnings do - require 'parser/current' - end require 'rainbow' - - # Explicit lazy loading because it depends on +parser+, which we want to only load - # in console sessions. require_relative "./command_validator/.command_parser" # This solves a weird class loading error where ActiveRecord dosn't resolve +Relation+ properly. diff --git a/test/command_validator/forbidden_constant_reference_validation_test.rb b/test/command_validator/forbidden_constant_reference_validation_test.rb index e068e13..b811f2c 100644 --- a/test/command_validator/forbidden_constant_reference_validation_test.rb +++ b/test/command_validator/forbidden_constant_reference_validation_test.rb @@ -45,6 +45,14 @@ class ForbiddenConstantReferenceValidationTest < ActiveSupport::TestCase end end + test "validate assigning a forbidden constant to a new constant" do + assert_raise Console1984::Errors::ForbiddenCommandAttempted do + run_validation <<~RUBY, always: ["SomeClass"] + MyAlias = SomeClass + RUBY + end + end + test "referencing other constants won't raise any error" do run_validation <<~RUBY, always: ["SomeConstant"] SomeNotForbiddenClass.some_method diff --git a/test/command_validator/forbidden_reopening_validation_test.rb b/test/command_validator/forbidden_reopening_validation_test.rb index f0f17d9..bdf20fc 100644 --- a/test/command_validator/forbidden_reopening_validation_test.rb +++ b/test/command_validator/forbidden_reopening_validation_test.rb @@ -37,6 +37,14 @@ module Some::Base::Class end end + test "validate assigning a forbidden class to a new constant" do + assert_raise Console1984::Errors::ForbiddenCommandAttempted do + run_validation <<~RUBY, ["SomeClass"] + MyAlias = SomeClass + RUBY + end + end + test "doesn't prevent reopening classes when the constant is a partial match" do run_validation <<~RUBY, ["SomeClass"] class SomeClass2 diff --git a/test/command_validator/parsed_command_test.rb b/test/command_validator/parsed_command_test.rb index 67dd942..a8fbd13 100644 --- a/test/command_validator/parsed_command_test.rb +++ b/test/command_validator/parsed_command_test.rb @@ -72,6 +72,16 @@ class t::Subtopic end end + if RUBY_VERSION >= "4.0" + test "parse constants from Ruby 4.0 syntax" do + # Logical operators at line beginning is new syntax in Ruby 4.0 + assert_constants ["Foo", "Bar"], <<~RB + result = Foo + || Bar + RB + end + end + test "syntax errors are handled gracefully" do parsed_command = Console1984::CommandValidator::ParsedCommand.new <<~RB def 12'39u```` diff --git a/test/ruby_parser_test.rb b/test/ruby_parser_test.rb new file mode 100644 index 0000000..fb1a095 --- /dev/null +++ b/test/ruby_parser_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class RubyParserTest < ActiveSupport::TestCase + test "ruby_parser returns the appropriate parser for the current Ruby version" do + if RUBY_VERSION >= "3.3" + assert_equal Prism::Translation::ParserCurrent, Console1984.ruby_parser + else + assert_equal Parser::CurrentRuby, Console1984.ruby_parser + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 3f663ac..f7715aa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -8,7 +8,6 @@ require "rails/test_help" require "mocha/minitest" require "minitest/mock" -require "parser/current" require "activeresource" require "ostruct"