From 81a246178409e6a40d058d400471af57f6d57a64 Mon Sep 17 00:00:00 2001 From: Andrew Novoselac Date: Fri, 2 Feb 2024 12:07:21 -0500 Subject: [PATCH] UnrecognizedCommandError can be corrected and retried. When encountering a UnrecognizedCommandError, the developer will be given the option to run of the suggested corrections instead. Co-authored-by: Gannon McGibbon --- railties/CHANGELOG.md | 14 +++++++++++ railties/lib/rails/command.rb | 22 ++++++++++++++++ .../test/command/help_integration_test.rb | 25 +++++++++++++++++-- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 5cff64f07e157..fc472e235e471 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,17 @@ +* When encountering an Unrecognized Command Error, the developer will be given the option to + run of the suggested corrections instead. + + ```txt + bin/rails action_txt:install + Unrecognized command "action_txt:install" + + Did you mean? action_text:install [Yn] Y + Installing JavaScript dependencies + ... + ``` + + *Andrew Novoselac & Gannon McGibbon* + * Allow Actionable Errors encountered when running tests to be retried. ```txt diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index 92dcf6a125486..912eed600034b 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -44,6 +44,21 @@ def initialize(name) end end + class UnrecognizedCommandCorrector < Thor::Shell::Basic + def correct(error) + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + say error.original_message + + error.corrections.detect do |correction| + correction if yes? DidYouMean.formatter.message_for([correction]) + " [Yn]" + end + else + say error.detailed_message + nil + end + end + end + include Behavior HELP_MAPPINGS = %w(-h -? --help).to_set @@ -76,6 +91,9 @@ def invoke(full_namespace, args = [], **config) rescue UnrecognizedCommandError => error if error.name == full_namespace && command && command_name == full_namespace command.perform("help", [], config) + elsif tty? + correction = UnrecognizedCommandCorrector.new.correct(error) + return invoke(correction, args, **config) if correction else puts error.detailed_message end @@ -167,6 +185,10 @@ def lookup_paths # :doc: def file_lookup_paths # :doc: @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_command.rb" ] end + + def tty? + STDOUT.tty? + end end end end diff --git a/railties/test/command/help_integration_test.rb b/railties/test/command/help_integration_test.rb index aad929cd29864..a5ccc78539d60 100644 --- a/railties/test/command/help_integration_test.rb +++ b/railties/test/command/help_integration_test.rb @@ -11,11 +11,32 @@ class Rails::Command::HelpIntegrationTest < ActiveSupport::TestCase assert_match "Invoke default", rails("--trace") end - test "prints helpful error on unrecognized command" do + test "prints helpful error on unrecognized command and allows command to be re-run" do + app_file("config/boot.rb", <<~RUBY, "a+") + require "rails/command" + + module Rails + module Command + class UnrecognizedCommandCorrector + def yes?(statement, color = nil) + raise ArgumentError unless statement == "\nDid you mean? version [Yn]" + true + end + end + + class << self + def tty? + true + end + end + end + end + RUBY + output = rails "vershen", allow_failure: true assert_match %(Unrecognized command "vershen"), output - assert_match "Did you mean? version", output + assert_match Rails::VERSION::STRING, output end test "loads Rake tasks only once on unrecognized command" do