From 66ab2a700af8dff51189b4190f33faf00ddfde94 Mon Sep 17 00:00:00 2001 From: Celeo Date: Sun, 19 Apr 2020 12:50:51 -0700 Subject: [PATCH 1/4] Show aliases in suggestions type: added fixes: - https://github.com/casey/just/issues/445 --- src/common.rs | 77 ++++++++++++++++++++++++++++++++++---------- src/config.rs | 2 +- src/justfile.rs | 69 +++++++++++++++++++++++++++++++++------ src/runtime_error.rs | 4 +-- 4 files changed, 123 insertions(+), 29 deletions(-) diff --git a/src/common.rs b/src/common.rs index 7dee2b41d1..8067569ebe 100644 --- a/src/common.rs +++ b/src/common.rs @@ -48,23 +48,66 @@ pub(crate) use crate::{ // structs and enums pub(crate) use crate::{ - alias::Alias, analyzer::Analyzer, assignment::Assignment, - assignment_resolver::AssignmentResolver, binding::Binding, color::Color, - compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind, - compiler::Compiler, config::Config, config_error::ConfigError, count::Count, - dependency::Dependency, enclosure::Enclosure, evaluator::Evaluator, expression::Expression, - fragment::Fragment, function::Function, function_context::FunctionContext, - interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, - justfile::Justfile, lexer::Lexer, line::Line, list::List, load_error::LoadError, module::Module, - name::Name, output_error::OutputError, parameter::Parameter, parser::Parser, platform::Platform, - position::Position, positional::Positional, recipe::Recipe, recipe_context::RecipeContext, - recipe_resolver::RecipeResolver, runtime_error::RuntimeError, scope::Scope, search::Search, - search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting, - settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace, - string_literal::StringLiteral, subcommand::Subcommand, table::Table, thunk::Thunk, token::Token, - token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, - unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables, - verbosity::Verbosity, warning::Warning, + alias::Alias, + analyzer::Analyzer, + assignment::Assignment, + assignment_resolver::AssignmentResolver, + binding::Binding, + color::Color, + compilation_error::CompilationError, + compilation_error_kind::CompilationErrorKind, + compiler::Compiler, + config::Config, + config_error::ConfigError, + count::Count, + dependency::Dependency, + enclosure::Enclosure, + evaluator::Evaluator, + expression::Expression, + fragment::Fragment, + function::Function, + function_context::FunctionContext, + interrupt_guard::InterruptGuard, + interrupt_handler::InterruptHandler, + item::Item, + justfile::{Justfile, Suggestion}, + lexer::Lexer, + line::Line, + list::List, + load_error::LoadError, + module::Module, + name::Name, + output_error::OutputError, + parameter::Parameter, + parser::Parser, + platform::Platform, + position::Position, + positional::Positional, + recipe::Recipe, + recipe_context::RecipeContext, + recipe_resolver::RecipeResolver, + runtime_error::RuntimeError, + scope::Scope, + search::Search, + search_config::SearchConfig, + search_error::SearchError, + set::Set, + setting::Setting, + settings::Settings, + shebang::Shebang, + show_whitespace::ShowWhitespace, + string_literal::StringLiteral, + subcommand::Subcommand, + table::Table, + thunk::Thunk, + token::Token, + token_kind::TokenKind, + unresolved_dependency::UnresolvedDependency, + unresolved_recipe::UnresolvedRecipe, + use_color::UseColor, + variables::Variables, + verbosity::Verbosity, + warning::Warning, }; // type aliases diff --git a/src/config.rs b/src/config.rs index 8e8000af54..52df39589b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -632,7 +632,7 @@ impl Config { } else { eprintln!("Justfile does not contain recipe `{}`.", name); if let Some(suggestion) = justfile.suggest(name) { - eprintln!("Did you mean `{}`?", suggestion); + eprintln!("{}", suggestion); } Err(EXIT_FAILURE) } diff --git a/src/justfile.rs b/src/justfile.rs index e4c9cb32a5..99630917d5 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -28,19 +28,31 @@ impl<'src> Justfile<'src> { self.recipes.len() } - pub(crate) fn suggest(&self, name: &str) -> Option<&'src str> { + pub(crate) fn suggest(&self, name: &str) -> Option { let mut suggestions = self .recipes .keys() - .map(|suggestion| (edit_distance(suggestion, name), suggestion)) - .collect::>(); - suggestions.sort(); - if let Some(&(distance, suggestion)) = suggestions.first() { - if distance < 3 { - return Some(suggestion); - } + .map(|recipe_name| { + (edit_distance(recipe_name, name), Suggestion { + name: recipe_name, + target: None, + }) + }) + .chain(self.aliases.iter().map(|(alias_name, alias)| { + (edit_distance(alias_name, name), Suggestion { + name: alias_name, + target: Some(alias.target.name.lexeme()), + }) + })) + .filter(|(distance, _suggestion)| distance < &3) + .collect::>(); + suggestions.sort_by_key(|(distance, _suggestion)| *distance); + + if let Some((_distance, suggestion)) = suggestions.first() { + Some(*suggestion) + } else { + None } - None } pub(crate) fn run<'run>( @@ -280,6 +292,22 @@ impl<'src> Display for Justfile<'src> { } } +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct Suggestion<'src> { + pub(crate) name: &'src str, + pub(crate) target: Option<&'src str>, +} + +impl<'src> Display for Suggestion<'src> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Did you mean `{}`", self.name)?; + if let Some(target) = self.target { + write!(f, ", an alias for `{}`", target)?; + } + write!(f, "?") + } +} + #[cfg(test)] mod tests { use super::*; @@ -301,6 +329,29 @@ mod tests { } } + run_error! { + name: unknown_recipes_show_alias_suggestion, + src: " + foo: + echo foo + + alias z := foo + ", + args: ["zz"], + error: UnknownRecipes { + recipes, + suggestion, + }, + check: { + assert_eq!(recipes, &["zz"]); + assert_eq!(suggestion, Some(Suggestion { + name: "z", + target: Some("foo"), + } + )); + } + } + // This test exists to make sure that shebang recipes run correctly. Although // this script is still executed by a shell its behavior depends on the value of // a variable and continuing even though a command fails, whereas in plain diff --git a/src/runtime_error.rs b/src/runtime_error.rs index 70d73c3b73..511ae45509 100644 --- a/src/runtime_error.rs +++ b/src/runtime_error.rs @@ -56,7 +56,7 @@ pub(crate) enum RuntimeError<'src> { }, UnknownRecipes { recipes: Vec<&'src str>, - suggestion: Option<&'src str>, + suggestion: Option>, }, Unknown { recipe: &'src str, @@ -117,7 +117,7 @@ impl<'src> Display for RuntimeError<'src> { List::or_ticked(recipes), )?; if let Some(suggestion) = *suggestion { - write!(f, "\nDid you mean `{}`?", suggestion)?; + write!(f, "\n{}", suggestion)?; } }, UnknownOverrides { overrides } => { From b0ee20b8eae2749afd927b0b217cf0c818ffadb5 Mon Sep 17 00:00:00 2001 From: Celeo Date: Mon, 20 Apr 2020 15:36:30 -0700 Subject: [PATCH 2/4] Move `Suggestion` struct to its own module type: changed fixes: - https://github.com/casey/just/issues/445 --- src/common.rs | 77 +++++++++++------------------------------------ src/justfile.rs | 39 +++++++----------------- src/lib.rs | 1 + src/suggestion.rs | 17 +++++++++++ 4 files changed, 46 insertions(+), 88 deletions(-) create mode 100644 src/suggestion.rs diff --git a/src/common.rs b/src/common.rs index 8067569ebe..1ada469f82 100644 --- a/src/common.rs +++ b/src/common.rs @@ -48,66 +48,23 @@ pub(crate) use crate::{ // structs and enums pub(crate) use crate::{ - alias::Alias, - analyzer::Analyzer, - assignment::Assignment, - assignment_resolver::AssignmentResolver, - binding::Binding, - color::Color, - compilation_error::CompilationError, - compilation_error_kind::CompilationErrorKind, - compiler::Compiler, - config::Config, - config_error::ConfigError, - count::Count, - dependency::Dependency, - enclosure::Enclosure, - evaluator::Evaluator, - expression::Expression, - fragment::Fragment, - function::Function, - function_context::FunctionContext, - interrupt_guard::InterruptGuard, - interrupt_handler::InterruptHandler, - item::Item, - justfile::{Justfile, Suggestion}, - lexer::Lexer, - line::Line, - list::List, - load_error::LoadError, - module::Module, - name::Name, - output_error::OutputError, - parameter::Parameter, - parser::Parser, - platform::Platform, - position::Position, - positional::Positional, - recipe::Recipe, - recipe_context::RecipeContext, - recipe_resolver::RecipeResolver, - runtime_error::RuntimeError, - scope::Scope, - search::Search, - search_config::SearchConfig, - search_error::SearchError, - set::Set, - setting::Setting, - settings::Settings, - shebang::Shebang, - show_whitespace::ShowWhitespace, - string_literal::StringLiteral, - subcommand::Subcommand, - table::Table, - thunk::Thunk, - token::Token, - token_kind::TokenKind, - unresolved_dependency::UnresolvedDependency, - unresolved_recipe::UnresolvedRecipe, - use_color::UseColor, - variables::Variables, - verbosity::Verbosity, - warning::Warning, + alias::Alias, analyzer::Analyzer, assignment::Assignment, + assignment_resolver::AssignmentResolver, binding::Binding, color::Color, + compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind, + compiler::Compiler, config::Config, config_error::ConfigError, count::Count, + dependency::Dependency, enclosure::Enclosure, evaluator::Evaluator, expression::Expression, + fragment::Fragment, function::Function, function_context::FunctionContext, + interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, + justfile::Justfile, lexer::Lexer, line::Line, list::List, load_error::LoadError, module::Module, + name::Name, output_error::OutputError, parameter::Parameter, parser::Parser, platform::Platform, + position::Position, positional::Positional, recipe::Recipe, recipe_context::RecipeContext, + recipe_resolver::RecipeResolver, runtime_error::RuntimeError, scope::Scope, search::Search, + search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting, + settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace, + string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table, + thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, + unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables, + verbosity::Verbosity, warning::Warning, }; // type aliases diff --git a/src/justfile.rs b/src/justfile.rs index 99630917d5..a027db40af 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -28,19 +28,19 @@ impl<'src> Justfile<'src> { self.recipes.len() } - pub(crate) fn suggest(&self, name: &str) -> Option { + pub(crate) fn suggest(&self, input: &str) -> Option { let mut suggestions = self .recipes .keys() - .map(|recipe_name| { - (edit_distance(recipe_name, name), Suggestion { - name: recipe_name, + .map(|name| { + (edit_distance(name, input), Suggestion { + name, target: None, }) }) - .chain(self.aliases.iter().map(|(alias_name, alias)| { - (edit_distance(alias_name, name), Suggestion { - name: alias_name, + .chain(self.aliases.iter().map(|(name, alias)| { + (edit_distance(name, input), Suggestion { + name, target: Some(alias.target.name.lexeme()), }) })) @@ -48,11 +48,10 @@ impl<'src> Justfile<'src> { .collect::>(); suggestions.sort_by_key(|(distance, _suggestion)| *distance); - if let Some((_distance, suggestion)) = suggestions.first() { - Some(*suggestion) - } else { - None - } + suggestions + .into_iter() + .map(|(_distance, suggestion)| suggestion) + .next() } pub(crate) fn run<'run>( @@ -292,22 +291,6 @@ impl<'src> Display for Justfile<'src> { } } -#[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct Suggestion<'src> { - pub(crate) name: &'src str, - pub(crate) target: Option<&'src str>, -} - -impl<'src> Display for Suggestion<'src> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Did you mean `{}`", self.name)?; - if let Some(target) = self.target { - write!(f, ", an alias for `{}`", target)?; - } - write!(f, "?") - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 24d17e5621..868c696ff3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,6 +111,7 @@ mod shebang; mod show_whitespace; mod string_literal; mod subcommand; +mod suggestion; mod table; mod thunk; mod token; diff --git a/src/suggestion.rs b/src/suggestion.rs new file mode 100644 index 0000000000..cb5d9d6870 --- /dev/null +++ b/src/suggestion.rs @@ -0,0 +1,17 @@ +use crate::common::*; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct Suggestion<'src> { + pub(crate) name: &'src str, + pub(crate) target: Option<&'src str>, +} + +impl<'src> Display for Suggestion<'src> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Did you mean `{}`", self.name)?; + if let Some(target) = self.target { + write!(f, ", an alias for `{}`", target)?; + } + write!(f, "?") + } +} From b728b2b809b4a1dc95d79cfd239ae78ca112d2fa Mon Sep 17 00:00:00 2001 From: Celeo Date: Sun, 26 Apr 2020 10:04:21 -0700 Subject: [PATCH 3/4] Add integration tests for alias suggestions type: changed fixes: - https://github.com/casey/just/issues/445 --- tests/integration.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 6ed38b86b8..57bca0152d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1224,6 +1224,22 @@ a Z="\t z": status: EXIT_FAILURE, } +test! { + name: show_alias_suggestion, + justfile: r#" +hello a b='B ' c='C': + echo {{a}} {{b}} {{c}} + +alias foo := hello + +a Z="\t z": +"#, + args: ("--show", "fo"), + stdout: "", + stderr: "Justfile does not contain recipe `fo`.\nDid you mean `foo`, an alias for `hello`?\n", + status: EXIT_FAILURE, +} + test! { name: show_no_suggestion, justfile: r#" @@ -1238,6 +1254,22 @@ a Z="\t z": status: EXIT_FAILURE, } +test! { + name: show_no_alias_suggestion, + justfile: r#" +hello a b='B ' c='C': + echo {{a}} {{b}} {{c}} + +alias foo := hello + +a Z="\t z": +"#, + args: ("--show", "fooooooo"), + stdout: "", + stderr: "Justfile does not contain recipe `fooooooo`.\n", + status: EXIT_FAILURE, +} + test! { name: run_suggestion, justfile: r#" From d4f45392254664afc5239cde651c453e46f774b4 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 26 Apr 2020 14:12:03 -0700 Subject: [PATCH 4/4] Placate clippy --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 868c696ff3..398ba509df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,12 @@ clippy::result_expect_used, clippy::shadow_unrelated, clippy::string_add, + clippy::struct_excessive_bools, clippy::too_many_lines, clippy::unreachable, clippy::use_debug, - clippy::wildcard_enum_match_arm + clippy::wildcard_enum_match_arm, + clippy::wildcard_imports )] #[macro_use]