diff --git a/codespell.txt b/codespell.txt index c267e0db2d1..2ca31a28461 100644 --- a/codespell.txt +++ b/codespell.txt @@ -37,6 +37,7 @@ ue unqiue upto varius +vershen vew wil wth diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index 403cebd987c..ad79715538a 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -14,6 +14,36 @@ module Rails autoload :Behavior autoload :Base + class CorrectableNameError < StandardError # :nodoc: + attr_reader :name + + def initialize(message, name, alternatives) + @name = name + @alternatives = alternatives + super(message) + end + + if !Exception.method_defined?(:detailed_message) + def detailed_message(...) + message + end + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable + + def corrections + @corrections ||= DidYouMean::SpellChecker.new(dictionary: @alternatives).correct(name) + end + end + end + + class UnrecognizedCommandError < CorrectableNameError # :nodoc: + def initialize(name) + super("Unrecognized command #{name.inspect}", name, Command.printing_commands.map(&:first)) + end + end + include Behavior HELP_MAPPINGS = %w(-h -? --help).to_set @@ -46,6 +76,9 @@ module Rails args = ["--describe", full_namespace] if HELP_MAPPINGS.include?(args[0]) find_by_namespace("rake").perform(full_namespace, args, config) end + rescue UnrecognizedCommandError => error + puts error.detailed_message + exit(1) ensure ARGV.replace(original_argv) end diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb index 8fee4c41549..12641337a55 100644 --- a/railties/lib/rails/command/base.rb +++ b/railties/lib/rails/command/base.rb @@ -16,24 +16,6 @@ module Rails class Error < Thor::Error # :nodoc: end - class CorrectableError < Error # :nodoc: - attr_reader :key, :options - - def initialize(message, key, options) - @key = key - @options = options - super(message) - end - - if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) - include DidYouMean::Correctable - - def corrections - @corrections ||= DidYouMean::SpellChecker.new(dictionary: options).correct(key) - end - end - end - include Actions class_attribute :bin, instance_accessor: false, default: "bin/rails" diff --git a/railties/lib/rails/commands/rake/rake_command.rb b/railties/lib/rails/commands/rake/rake_command.rb index da315608276..b8d34ed211c 100644 --- a/railties/lib/rails/commands/rake/rake_command.rb +++ b/railties/lib/rails/commands/rake/rake_command.rb @@ -12,28 +12,24 @@ module Rails formatted_rake_tasks end - def perform(task, args, config, optional: false) + def perform(task, args, config) require_rake Rake.with_application do |rake| rake.init("rails", [task, *args]) rake.load_rakefile + if unrecognized_task = rake.top_level_tasks.find { |task| !rake.lookup(task) } + raise UnrecognizedCommandError.new(unrecognized_task) + end + if Rails.respond_to?(:root) rake.options.suppress_backtrace_pattern = /\A(?!#{Regexp.quote(Rails.root.to_s)})/ end - rake.standard_exception_handling { rake.top_level } unless optional && !task_exists?(rake, task) + rake.standard_exception_handling { rake.top_level } end end private - def task_exists?(rake, task) - name, _args = rake.parse_task_string(task) - rake[name] - true - rescue Exception - false - end - def rake_tasks require_rake diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 6ccbba27fc7..6853c8e40b3 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -271,14 +271,9 @@ module Rails Run `#{executable} --help` for more options. MSG else - error = CorrectableError.new("Could not find server '#{server}'.", server, RACK_SERVERS) - if error.respond_to?(:detailed_message) - formatted_message = error.detailed_message - else - formatted_message = error.message - end + error = CorrectableNameError.new("Could not find server '#{server}'.", server, RACK_SERVERS) <<~MSG - #{formatted_message} + #{error.detailed_message} Run `#{executable} --help` for more options. MSG end diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb index 86e034dd030..b6c871c19d9 100644 --- a/railties/lib/rails/commands/test/test_command.rb +++ b/railties/lib/rails/commands/test/test_command.rb @@ -65,8 +65,10 @@ module Rails private def run_prepare_task(args) if @force_prepare || args.empty? - Rails::Command::RakeCommand.perform("test:prepare", nil, {}, optional: true) + Rails::Command::RakeCommand.perform("test:prepare", [], {}) end + rescue UnrecognizedCommandError => error + raise unless error.name == "test:prepare" end end end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index cb4b58aef18..867436689b2 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -261,16 +261,10 @@ module Rails run_after_generate_callback if config[:behavior] == :invoke else options = sorted_groups.flat_map(&:last) - error = Command::Base::CorrectableError.new("Could not find generator '#{namespace}'.", namespace, options) - - if error.respond_to?(:detailed_message) - formatted_message = error.detailed_message - else - formatted_message = error.message - end + error = Command::CorrectableNameError.new("Could not find generator '#{namespace}'.", namespace, options) puts <<~MSG - #{formatted_message} + #{error.detailed_message} Run `bin/rails generate --help` for more options. MSG end diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb index 2768c2df212..aa2afa4339f 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -1235,7 +1235,7 @@ module ApplicationTests error = assert_raises do rails "db:migrate:animals" ### Task not defined end - assert_includes error.message, "See the list of available tasks" + assert_includes error.message, "Unrecognized command" rails "db:schema:dump" assert_not File.exist?("db/animals_schema.yml") diff --git a/railties/test/command/application_test.rb b/railties/test/command/application_test.rb index c3fb41050b2..fe867ae7b57 100644 --- a/railties/test/command/application_test.rb +++ b/railties/test/command/application_test.rb @@ -16,4 +16,14 @@ class Rails::Command::ApplicationTest < ActiveSupport::TestCase assert output.include?("The `rails new` command creates a new Rails application with a default directory structure and configuration at the path you specify.") end + + test "prints helpful error on unrecognized command" do + output = capture(:stdout) do + Rails::Command.invoke("vershen") + rescue SystemExit + end + + assert_match %(Unrecognized command "vershen"), output + assert_match "Did you mean? version", output + end end