From 91fbeb225ff3888111ec431cead9de1ae6e57b8b Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Sat, 7 Feb 2026 12:09:34 -0500 Subject: [PATCH 1/2] Backfill tests: constant assignment is caught by validators Both ForbiddenConstantReferenceValidation and ForbiddenReopeningValidation check constant_assignments but neither had test coverage for that path. --- .../forbidden_constant_reference_validation_test.rb | 8 ++++++++ .../forbidden_reopening_validation_test.rb | 8 ++++++++ 2 files changed, 16 insertions(+) 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 From e0aa43b08a43636fee1e8b2cdbf7e9500ccf9ecc Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Sat, 7 Feb 2026 12:28:36 -0500 Subject: [PATCH 2/2] Use Prism for Ruby source parsing on Ruby >= 3.3 The whitequark/parser gem does not support Ruby 3.4+ syntax and will not be updated. On Ruby 4.0 it falls back to its Ruby 3.3 parser, meaning it cannot parse new syntax like logical operators at line beginning. Prism, which is bundled with Ruby 3.3+, provides a translation layer (Prism::Translation::ParserCurrent) that is a drop-in replacement for Parser::CurrentRuby. It produces the same Parser::AST::Node objects, so CommandParser (which inherits from Parser::AST::Processor) continues to work unchanged. Two new methods on Console1984: - require_ruby_parser_dependencies: loads parser+prism on Ruby >= 3.3, or parser/current on older Rubies. Called by .command_parser.rb itself rather than requiring the supervisor to manage load ordering. - ruby_parser: returns the appropriate parser class for the running Ruby version. On older Rubies (< 3.3), everything continues to use the parser gem as before. --- lib/console1984.rb | 22 +++++++++++++++++++ .../command_validator/.command_parser.rb | 6 ++--- .../command_validator/parsed_command.rb | 4 +--- lib/console1984/refrigerator.rb | 2 +- lib/console1984/supervisor.rb | 6 ----- test/command_validator/parsed_command_test.rb | 10 +++++++++ test/ruby_parser_test.rb | 11 ++++++++++ test/test_helper.rb | 1 - 8 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 test/ruby_parser_test.rb 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/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"