mirror of https://github.com/rails/rails
Add "Did you mean?" for unrecognized CLI commands
This commit improves the error message that is displayed when a user specifies an unrecognized `bin/rails` command by offering "Did you mean?" suggestions. The suggestions cover all visible commands, and supersede any incomplete (Rake-task-only) suggestions that Rake might display. __Before (example)__ ```console $ bin/rails credentails:edit rails aborted! Don't know how to build task 'credentails:edit' (See the list of available tasks with `rails --tasks`) (See full trace by running task with --trace) $ bin/rails --tasks ... rails cache_digests:dependencies # Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html) rails cache_digests:nested_dependencies # Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html) rails db:create # Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use ... rails db:drop # Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db... ... $ bin/rails credentails -h # no output $ bin/rails credentials -h # no output $ bin/rails versoin rails aborted! Don't know how to build task 'versoin' (See the list of available tasks with `rails --tasks`) Did you mean? db:version (See full trace by running task with --trace) ``` __After (example)__ ```console $ bin/rails credentails:edit Unrecognized command "credentails:edit" (Rails::Command::UnrecognizedCommandError) Did you mean? credentials:edit $ bin/rails credentails -h Unrecognized command "credentails" (Rails::Command::UnrecognizedCommandError) Did you mean? credentials:diff $ bin/rails credentials -h Unrecognized command "credentials" (Rails::Command::UnrecognizedCommandError) Did you mean? credentials:diff $ bin/rails versoin Unrecognized command "versoin" (Rails::Command::UnrecognizedCommandError) Did you mean? version ```
This commit is contained in:
parent
60bc028df8
commit
25301604b4
|
@ -37,6 +37,7 @@ ue
|
|||
unqiue
|
||||
upto
|
||||
varius
|
||||
vershen
|
||||
vew
|
||||
wil
|
||||
wth
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue