From a58d9b0b3a5703b2ebd7fffd7f5715aa661da5d9 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Tue, 11 Feb 2025 11:36:36 +0100 Subject: [PATCH] Refactor/fix tests affecting the whole command set (#15073) # Description Pre-cratification of `nu-command` we added tests that covered the whole command set to ensure consistent documentation style choices and that the search terms which are added are not uselessly redundant. These tests are now moved into the suite of the main binary to truly cover all commands. - **Move parser quickcheck "fuzz" to `nu-cmd-lang`** - **Factor out creation of full engine state for tests** - **Move all-command tests to main context creation** - **Fix all descriptions** - **Fix search term duplicate** # User-Facing Changes As a result I had to fix a few command argument descriptions. (Doesn't mean I fully stand behind this choice, but) positionals (rest/required/optional) and top level descriptions should start with a capital letter and end with a period. This is not enforced for flags. # Tests + Formatting Furthermore I moved our poor-peoples-fuzzer that runs in CI with `quicktest` over the parser to `nu-cmd-lang` reducing its command set to just the keywords (similar to https://github.com/nushell/nushell/pull/15036). Thus this should also run slightly faster (maybe a slight parallel build cost due to earlier dependency on quicktest) --- Cargo.lock | 4 +- .../nu-cli/src/commands/commandline/edit.rs | 2 +- .../src/commands/commandline/set_cursor.rs | 2 +- .../src/commands/history/history_import.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/and.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/into.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/or.rs | 2 +- .../src/extra/bits/rotate_left.rs | 2 +- .../src/extra/bits/rotate_right.rs | 2 +- .../nu-cmd-extra/src/extra/bits/shift_left.rs | 2 +- .../src/extra/bits/shift_right.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/xor.rs | 2 +- .../src/extra/filters/each_while.rs | 2 +- .../nu-cmd-extra/src/extra/filters/rotate.rs | 2 +- .../src/extra/filters/update_cells.rs | 2 +- .../src/extra/platform/ansi/gradient.rs | 2 +- .../src/extra/strings/format/bits.rs | 2 +- .../src/extra/strings/format/command.rs | 2 +- .../src/extra/strings/format/number.rs | 2 +- .../src/extra/strings/str_/case/camel_case.rs | 2 +- .../src/extra/strings/str_/case/kebab_case.rs | 2 +- .../extra/strings/str_/case/pascal_case.rs | 2 +- .../strings/str_/case/screaming_snake_case.rs | 2 +- .../src/extra/strings/str_/case/snake_case.rs | 2 +- .../src/extra/strings/str_/case/title_case.rs | 2 +- crates/nu-cmd-lang/Cargo.toml | 6 +- crates/nu-cmd-lang/src/lib.rs | 2 + crates/nu-cmd-lang/src/parse_const_test.rs | 19 ++ .../nu-cmd-plugin/src/commands/plugin/add.rs | 2 +- .../nu-cmd-plugin/src/commands/plugin/rm.rs | 2 +- .../nu-cmd-plugin/src/commands/plugin/stop.rs | 2 +- .../nu-cmd-plugin/src/commands/plugin/use_.rs | 2 +- crates/nu-command/Cargo.toml | 2 - crates/nu-command/tests/main.rs | 244 ------------------ src/command_context.rs | 239 +++++++++++++++++ src/main.rs | 18 +- 36 files changed, 299 insertions(+), 291 deletions(-) create mode 100644 crates/nu-cmd-lang/src/parse_const_test.rs create mode 100644 src/command_context.rs diff --git a/Cargo.lock b/Cargo.lock index afad2c8875..749bf7babf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3570,6 +3570,8 @@ dependencies = [ "nu-parser", "nu-protocol", "nu-utils", + "quickcheck", + "quickcheck_macros", "shadow-rs", ] @@ -3666,8 +3668,6 @@ dependencies = [ "print-positions", "procfs", "quick-xml 0.37.1", - "quickcheck", - "quickcheck_macros", "rand", "rand_chacha", "rayon", diff --git a/crates/nu-cli/src/commands/commandline/edit.rs b/crates/nu-cli/src/commands/commandline/edit.rs index 64f3ae2a08..9afd52d946 100644 --- a/crates/nu-cli/src/commands/commandline/edit.rs +++ b/crates/nu-cli/src/commands/commandline/edit.rs @@ -29,7 +29,7 @@ impl Command for SubCommand { .required( "str", SyntaxShape::String, - "the string to perform the operation with", + "The string to perform the operation with.", ) .category(Category::Core) } diff --git a/crates/nu-cli/src/commands/commandline/set_cursor.rs b/crates/nu-cli/src/commands/commandline/set_cursor.rs index 411d42de57..bf54674b44 100644 --- a/crates/nu-cli/src/commands/commandline/set_cursor.rs +++ b/crates/nu-cli/src/commands/commandline/set_cursor.rs @@ -18,7 +18,7 @@ impl Command for SubCommand { "set the current cursor position to the end of the buffer", Some('e'), ) - .optional("pos", SyntaxShape::Int, "Cursor position to be set") + .optional("pos", SyntaxShape::Int, "Cursor position to be set.") .category(Category::Core) } diff --git a/crates/nu-cli/src/commands/history/history_import.rs b/crates/nu-cli/src/commands/history/history_import.rs index b9764306f8..80237acb87 100644 --- a/crates/nu-cli/src/commands/history/history_import.rs +++ b/crates/nu-cli/src/commands/history/history_import.rs @@ -21,7 +21,7 @@ impl Command for HistoryImport { } fn description(&self) -> &str { - "Import command line history" + "Import command line history." } fn extra_description(&self) -> &str { diff --git a/crates/nu-cmd-extra/src/extra/bits/and.rs b/crates/nu-cmd-extra/src/extra/bits/and.rs index a87508732d..dfac65af72 100644 --- a/crates/nu-cmd-extra/src/extra/bits/and.rs +++ b/crates/nu-cmd-extra/src/extra/bits/and.rs @@ -26,7 +26,7 @@ impl Command for BitsAnd { .required( "target", SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]), - "right-hand side of the operation", + "Right-hand side of the operation.", ) .named( "endian", diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index 631637a44e..d3979f0e47 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -26,7 +26,7 @@ impl Command for BitsInto { .rest( "rest", SyntaxShape::CellPath, - "for a data structure input, convert data at the given cell paths", + "For a data structure input, convert data at the given cell paths.", ) .category(Category::Deprecated) } diff --git a/crates/nu-cmd-extra/src/extra/bits/or.rs b/crates/nu-cmd-extra/src/extra/bits/or.rs index 287b841750..d3859bb90e 100644 --- a/crates/nu-cmd-extra/src/extra/bits/or.rs +++ b/crates/nu-cmd-extra/src/extra/bits/or.rs @@ -27,7 +27,7 @@ impl Command for BitsOr { .required( "target", SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]), - "right-hand side of the operation", + "Right-hand side of the operation.", ) .named( "endian", diff --git a/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs b/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs index 59e772df64..dffd09d4b0 100644 --- a/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs +++ b/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs @@ -37,7 +37,7 @@ impl Command for BitsRol { ), ]) .allow_variants_without_examples(true) - .required("bits", SyntaxShape::Int, "number of bits to rotate left") + .required("bits", SyntaxShape::Int, "Number of bits to rotate left.") .switch( "signed", "always treat input number as a signed number", diff --git a/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs b/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs index 6017d0b5cf..df2bc84547 100644 --- a/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs +++ b/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs @@ -37,7 +37,7 @@ impl Command for BitsRor { ), ]) .allow_variants_without_examples(true) - .required("bits", SyntaxShape::Int, "number of bits to rotate right") + .required("bits", SyntaxShape::Int, "Number of bits to rotate right.") .switch( "signed", "always treat input number as a signed number", diff --git a/crates/nu-cmd-extra/src/extra/bits/shift_left.rs b/crates/nu-cmd-extra/src/extra/bits/shift_left.rs index 155e20a429..0f1f0114e1 100644 --- a/crates/nu-cmd-extra/src/extra/bits/shift_left.rs +++ b/crates/nu-cmd-extra/src/extra/bits/shift_left.rs @@ -40,7 +40,7 @@ impl Command for BitsShl { ), ]) .allow_variants_without_examples(true) - .required("bits", SyntaxShape::Int, "number of bits to shift left") + .required("bits", SyntaxShape::Int, "Number of bits to shift left.") .switch( "signed", "always treat input number as a signed number", diff --git a/crates/nu-cmd-extra/src/extra/bits/shift_right.rs b/crates/nu-cmd-extra/src/extra/bits/shift_right.rs index bc4f165d61..b99a9618f3 100644 --- a/crates/nu-cmd-extra/src/extra/bits/shift_right.rs +++ b/crates/nu-cmd-extra/src/extra/bits/shift_right.rs @@ -37,7 +37,7 @@ impl Command for BitsShr { ), ]) .allow_variants_without_examples(true) - .required("bits", SyntaxShape::Int, "number of bits to shift right") + .required("bits", SyntaxShape::Int, "Number of bits to shift right.") .switch( "signed", "always treat input number as a signed number", diff --git a/crates/nu-cmd-extra/src/extra/bits/xor.rs b/crates/nu-cmd-extra/src/extra/bits/xor.rs index 2474bf27f1..d4e1d96abf 100644 --- a/crates/nu-cmd-extra/src/extra/bits/xor.rs +++ b/crates/nu-cmd-extra/src/extra/bits/xor.rs @@ -27,7 +27,7 @@ impl Command for BitsXor { .required( "target", SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]), - "right-hand side of the operation", + "Right-hand side of the operation.", ) .named( "endian", diff --git a/crates/nu-cmd-extra/src/extra/filters/each_while.rs b/crates/nu-cmd-extra/src/extra/filters/each_while.rs index cc5c066185..58d1eae9e8 100644 --- a/crates/nu-cmd-extra/src/extra/filters/each_while.rs +++ b/crates/nu-cmd-extra/src/extra/filters/each_while.rs @@ -26,7 +26,7 @@ impl Command for EachWhile { .required( "closure", SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), - "the closure to run", + "The closure to run.", ) .category(Category::Filters) } diff --git a/crates/nu-cmd-extra/src/extra/filters/rotate.rs b/crates/nu-cmd-extra/src/extra/filters/rotate.rs index 4c25e82d37..43c83d42b5 100644 --- a/crates/nu-cmd-extra/src/extra/filters/rotate.rs +++ b/crates/nu-cmd-extra/src/extra/filters/rotate.rs @@ -20,7 +20,7 @@ impl Command for Rotate { .rest( "rest", SyntaxShape::String, - "the names to give columns once rotated", + "The names to give columns once rotated.", ) .category(Category::Filters) .allow_variants_without_examples(true) diff --git a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs index 7cfc20de23..a3edf7754a 100644 --- a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs +++ b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs @@ -16,7 +16,7 @@ impl Command for UpdateCells { .required( "closure", SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), - "the closure to run an update for each cell", + "The closure to run an update for each cell.", ) .named( "columns", diff --git a/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs b/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs index 433757e6c7..b3aecef034 100644 --- a/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs +++ b/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs @@ -38,7 +38,7 @@ impl Command for SubCommand { .rest( "cell path", SyntaxShape::CellPath, - "for a data structure input, add a gradient to strings at the given cell paths", + "For a data structure input, add a gradient to strings at the given cell paths.", ) .input_output_types(vec![ (Type::String, Type::String), diff --git a/crates/nu-cmd-extra/src/extra/strings/format/bits.rs b/crates/nu-cmd-extra/src/extra/strings/format/bits.rs index b34d429c6c..1010477139 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/bits.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/bits.rs @@ -40,7 +40,7 @@ impl Command for FormatBits { .rest( "rest", SyntaxShape::CellPath, - "for a data structure input, convert data at the given cell paths", + "For a data structure input, convert data at the given cell paths.", ) .category(Category::Conversions) } diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 522fc6b05d..18206acba6 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -18,7 +18,7 @@ impl Command for FormatPattern { .required( "pattern", SyntaxShape::String, - "the pattern to output. e.g.) \"{foo}: {bar}\"", + "The pattern to output. e.g.) \"{foo}: {bar}\".", ) .allow_variants_without_examples(true) .category(Category::Strings) diff --git a/crates/nu-cmd-extra/src/extra/strings/format/number.rs b/crates/nu-cmd-extra/src/extra/strings/format/number.rs index e99d19606d..9c77a6006f 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/number.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/number.rs @@ -20,7 +20,7 @@ impl Command for FormatNumber { } fn search_terms(&self) -> Vec<&str> { - vec!["display", "render", "format"] + vec!["display", "render", "fmt"] } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/camel_case.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/camel_case.rs index 781848d7d8..279e3a7bb5 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/camel_case.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/camel_case.rs @@ -25,7 +25,7 @@ impl Command for SubCommand { .rest( "rest", SyntaxShape::CellPath, - "For a data structure input, convert strings at the given cell paths", + "For a data structure input, convert strings at the given cell paths.", ) .category(Category::Strings) } diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/kebab_case.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/kebab_case.rs index 74c2d031e5..89a3429d7a 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/kebab_case.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/kebab_case.rs @@ -25,7 +25,7 @@ impl Command for SubCommand { .rest( "rest", SyntaxShape::CellPath, - "For a data structure input, convert strings at the given cell paths", + "For a data structure input, convert strings at the given cell paths.", ) .category(Category::Strings) } diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/pascal_case.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/pascal_case.rs index 1372537ed5..9d081fa314 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/pascal_case.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/pascal_case.rs @@ -25,7 +25,7 @@ impl Command for SubCommand { .rest( "rest", SyntaxShape::CellPath, - "For a data structure input, convert strings at the given cell paths", + "For a data structure input, convert strings at the given cell paths.", ) .category(Category::Strings) } diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/screaming_snake_case.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/screaming_snake_case.rs index c8b918b37f..aa8d55fd48 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/screaming_snake_case.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/screaming_snake_case.rs @@ -25,7 +25,7 @@ impl Command for SubCommand { .rest( "rest", SyntaxShape::CellPath, - "For a data structure input, convert strings at the given cell paths", + "For a data structure input, convert strings at the given cell paths.", ) .category(Category::Strings) } diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/snake_case.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/snake_case.rs index 56a9b8b754..f2d5fb61c7 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/snake_case.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/snake_case.rs @@ -25,7 +25,7 @@ impl Command for SubCommand { .rest( "rest", SyntaxShape::CellPath, - "For a data structure input, convert strings at the given cell paths", + "For a data structure input, convert strings at the given cell paths.", ) .category(Category::Strings) } diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/title_case.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/title_case.rs index 02d7efcb2e..827a3dd594 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/title_case.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/title_case.rs @@ -25,7 +25,7 @@ impl Command for SubCommand { .rest( "rest", SyntaxShape::CellPath, - "For a data structure input, convert strings at the given cell paths", + "For a data structure input, convert strings at the given cell paths.", ) .category(Category::Strings) } diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 0cf277bb82..2cc86bd782 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -26,6 +26,10 @@ shadow-rs = { version = "0.38", default-features = false } [build-dependencies] shadow-rs = { version = "0.38", default-features = false } +[dev-dependencies] +quickcheck = { workspace = true } +quickcheck_macros = { workspace = true } + [features] default = ["os"] os = [ @@ -42,4 +46,4 @@ mimalloc = [] trash-support = [] sqlite = [] static-link-openssl = [] -system-clipboard = [] \ No newline at end of file +system-clipboard = [] diff --git a/crates/nu-cmd-lang/src/lib.rs b/crates/nu-cmd-lang/src/lib.rs index 1a46c0a350..3d6f506015 100644 --- a/crates/nu-cmd-lang/src/lib.rs +++ b/crates/nu-cmd-lang/src/lib.rs @@ -4,6 +4,8 @@ mod core_commands; mod default_context; pub mod example_support; mod example_test; +#[cfg(test)] +mod parse_const_test; pub use core_commands::*; pub use default_context::*; diff --git a/crates/nu-cmd-lang/src/parse_const_test.rs b/crates/nu-cmd-lang/src/parse_const_test.rs new file mode 100644 index 0000000000..7870b37648 --- /dev/null +++ b/crates/nu-cmd-lang/src/parse_const_test.rs @@ -0,0 +1,19 @@ +use nu_protocol::{engine::StateWorkingSet, Span}; +use quickcheck_macros::quickcheck; + +#[quickcheck] +fn quickcheck_parse(data: String) -> bool { + let (tokens, err) = nu_parser::lex(data.as_bytes(), 0, b"", b"", true); + + if err.is_none() { + let context = crate::create_default_context(); + { + let mut working_set = StateWorkingSet::new(&context); + let _ = working_set.add_file("quickcheck".into(), data.as_bytes()); + + let _ = + nu_parser::parse_block(&mut working_set, &tokens, Span::new(0, 0), false, false); + } + } + true +} diff --git a/crates/nu-cmd-plugin/src/commands/plugin/add.rs b/crates/nu-cmd-plugin/src/commands/plugin/add.rs index 301b88fdff..33e00c8eee 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/add.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/add.rs @@ -33,7 +33,7 @@ impl Command for PluginAdd { .required( "filename", SyntaxShape::String, - "Path to the executable for the plugin", + "Path to the executable for the plugin.", ) .category(Category::Plugin) } diff --git a/crates/nu-cmd-plugin/src/commands/plugin/rm.rs b/crates/nu-cmd-plugin/src/commands/plugin/rm.rs index a82b41524a..82b909a5ff 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/rm.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/rm.rs @@ -28,7 +28,7 @@ impl Command for PluginRm { .required( "name", SyntaxShape::String, - "The name, or filename, of the plugin to remove", + "The name, or filename, of the plugin to remove.", ) .category(Category::Plugin) } diff --git a/crates/nu-cmd-plugin/src/commands/plugin/stop.rs b/crates/nu-cmd-plugin/src/commands/plugin/stop.rs index 3c8383db93..343729436b 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/stop.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/stop.rs @@ -16,7 +16,7 @@ impl Command for PluginStop { .required( "name", SyntaxShape::String, - "The name, or filename, of the plugin to stop", + "The name, or filename, of the plugin to stop.", ) .category(Category::Plugin) } diff --git a/crates/nu-cmd-plugin/src/commands/plugin/use_.rs b/crates/nu-cmd-plugin/src/commands/plugin/use_.rs index df6ddb4507..0d66afce9a 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/use_.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/use_.rs @@ -25,7 +25,7 @@ impl Command for PluginUse { .required( "name", SyntaxShape::String, - "The name, or filename, of the plugin to load", + "The name, or filename, of the plugin to load.", ) .category(Category::Plugin) } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 12f197798d..b3106f7be9 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -195,8 +195,6 @@ nu-test-support = { path = "../nu-test-support", version = "0.102.1" } dirs = { workspace = true } mockito = { workspace = true, default-features = false } -quickcheck = { workspace = true } -quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } rstest_reuse = { workspace = true } pretty_assertions = { workspace = true } diff --git a/crates/nu-command/tests/main.rs b/crates/nu-command/tests/main.rs index fa2a7168b5..e5dec74e6e 100644 --- a/crates/nu-command/tests/main.rs +++ b/crates/nu-command/tests/main.rs @@ -1,247 +1,3 @@ -use nu_protocol::{ - engine::{EngineState, StateWorkingSet}, - Category, PositionalArg, Span, -}; -use quickcheck_macros::quickcheck; - mod commands; mod format_conversions; mod sort_utils; - -fn create_default_context() -> EngineState { - nu_command::add_shell_command_context(nu_cmd_lang::create_default_context()) -} - -#[quickcheck] -fn quickcheck_parse(data: String) -> bool { - let (tokens, err) = nu_parser::lex(data.as_bytes(), 0, b"", b"", true); - - if err.is_none() { - let context = create_default_context(); - { - let mut working_set = StateWorkingSet::new(&context); - let _ = working_set.add_file("quickcheck".into(), data.as_bytes()); - - let _ = - nu_parser::parse_block(&mut working_set, &tokens, Span::new(0, 0), false, false); - } - } - true -} - -#[test] -fn arguments_end_period() { - fn ends_period(cmd_name: &str, ty: &str, arg: PositionalArg, failures: &mut Vec) { - let arg_name = arg.name; - let desc = arg.desc; - if !desc.ends_with('.') { - failures.push(format!( - "{cmd_name} {ty} argument \"{arg_name}\": \"{desc}\"" - )); - } - } - - let ctx = crate::create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (name_bytes, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let cmd_name = String::from_utf8_lossy(&name_bytes); - let signature = cmd.signature(); - - for arg in signature.required_positional { - ends_period(&cmd_name, "required", arg, &mut failures); - } - - for arg in signature.optional_positional { - ends_period(&cmd_name, "optional", arg, &mut failures); - } - - if let Some(arg) = signature.rest_positional { - ends_period(&cmd_name, "rest", arg, &mut failures); - } - } - - assert!( - failures.is_empty(), - "Command argument description does not end with a period:\n{}", - failures.join("\n") - ); -} - -#[test] -fn arguments_start_uppercase() { - fn starts_uppercase(cmd_name: &str, ty: &str, arg: PositionalArg, failures: &mut Vec) { - let arg_name = arg.name; - let desc = arg.desc; - - // Check lowercase to allow usage to contain syntax like: - // - // "`as` keyword …" - if desc.starts_with(|u: char| u.is_lowercase()) { - failures.push(format!( - "{cmd_name} {ty} argument \"{arg_name}\": \"{desc}\"" - )); - } - } - - let ctx = crate::create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (name_bytes, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let cmd_name = String::from_utf8_lossy(&name_bytes); - let signature = cmd.signature(); - - for arg in signature.required_positional { - starts_uppercase(&cmd_name, "required", arg, &mut failures); - } - - for arg in signature.optional_positional { - starts_uppercase(&cmd_name, "optional", arg, &mut failures); - } - - if let Some(arg) = signature.rest_positional { - starts_uppercase(&cmd_name, "rest", arg, &mut failures); - } - } - - assert!( - failures.is_empty(), - "Command argument description does not end with a period:\n{}", - failures.join("\n") - ); -} - -#[test] -fn signature_name_matches_command_name() { - let ctx = create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (name_bytes, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let cmd_name = String::from_utf8_lossy(&name_bytes); - let sig_name = cmd.signature().name; - let category = cmd.signature().category; - - if cmd_name != sig_name { - failures.push(format!( - "{cmd_name} ({category:?}): Signature name \"{sig_name}\" is not equal to the command name \"{cmd_name}\"" - )); - } - } - - assert!( - failures.is_empty(), - "Name mismatch:\n{}", - failures.join("\n") - ); -} - -#[test] -fn commands_declare_input_output_types() { - let ctx = create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (_, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let sig_name = cmd.signature().name; - let category = cmd.signature().category; - - if matches!(category, Category::Removed) { - // Deprecated/Removed commands don't have to conform - continue; - } - - if cmd.signature().input_output_types.is_empty() { - failures.push(format!( - "{sig_name} ({category:?}): No pipeline input/output type signatures found" - )); - } - } - - assert!( - failures.is_empty(), - "Command missing type annotations:\n{}", - failures.join("\n") - ); -} - -#[test] -fn no_search_term_duplicates() { - let ctx = crate::create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (name_bytes, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let cmd_name = String::from_utf8_lossy(&name_bytes); - let search_terms = cmd.search_terms(); - let category = cmd.signature().category; - - for search_term in search_terms { - if cmd_name.contains(search_term) { - failures.push(format!("{cmd_name} ({category:?}): Search term \"{search_term}\" is substring of command name \"{cmd_name}\"")); - } - } - } - - assert!( - failures.is_empty(), - "Duplication in search terms:\n{}", - failures.join("\n") - ); -} - -#[test] -fn description_end_period() { - let ctx = crate::create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (name_bytes, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let cmd_name = String::from_utf8_lossy(&name_bytes); - let description = cmd.description(); - - if !description.ends_with('.') { - failures.push(format!("{cmd_name}: \"{description}\"")); - } - } - - assert!( - failures.is_empty(), - "Command description does not end with a period:\n{}", - failures.join("\n") - ); -} - -#[test] -fn description_start_uppercase() { - let ctx = crate::create_default_context(); - let decls = ctx.get_decls_sorted(true); - let mut failures = Vec::new(); - - for (name_bytes, decl_id) in decls { - let cmd = ctx.get_decl(decl_id); - let cmd_name = String::from_utf8_lossy(&name_bytes); - let description = cmd.description(); - - // Check lowercase to allow description to contain syntax like: - // - // "`$env.FOO = ...`" - if description.starts_with(|u: char| u.is_lowercase()) { - failures.push(format!("{cmd_name}: \"{description}\"")); - } - } - - assert!( - failures.is_empty(), - "Command description does not start with an uppercase letter:\n{}", - failures.join("\n") - ); -} diff --git a/src/command_context.rs b/src/command_context.rs new file mode 100644 index 0000000000..28d991d924 --- /dev/null +++ b/src/command_context.rs @@ -0,0 +1,239 @@ +use nu_protocol::engine::EngineState; + +pub(crate) fn get_engine_state() -> EngineState { + let engine_state = nu_cmd_lang::create_default_context(); + #[cfg(feature = "plugin")] + let engine_state = nu_cmd_plugin::add_plugin_command_context(engine_state); + let engine_state = nu_command::add_shell_command_context(engine_state); + let engine_state = nu_cmd_extra::add_extra_command_context(engine_state); + let engine_state = nu_cli::add_cli_context(engine_state); + nu_explore::add_explore_context(engine_state) +} + +#[cfg(test)] +mod tests { + use super::*; + use nu_protocol::{Category, PositionalArg}; + + #[test] + fn arguments_end_period() { + fn ends_period(cmd_name: &str, ty: &str, arg: PositionalArg, failures: &mut Vec) { + let arg_name = arg.name; + let desc = arg.desc; + if !desc.ends_with('.') { + failures.push(format!( + "{cmd_name} {ty} argument \"{arg_name}\": \"{desc}\"" + )); + } + } + + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (name_bytes, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let cmd_name = String::from_utf8_lossy(&name_bytes); + let signature = cmd.signature(); + + for arg in signature.required_positional { + ends_period(&cmd_name, "required", arg, &mut failures); + } + + for arg in signature.optional_positional { + ends_period(&cmd_name, "optional", arg, &mut failures); + } + + if let Some(arg) = signature.rest_positional { + ends_period(&cmd_name, "rest", arg, &mut failures); + } + } + + assert!( + failures.is_empty(), + "Command argument description does not end with a period:\n{}", + failures.join("\n") + ); + } + + #[test] + fn arguments_start_uppercase() { + fn starts_uppercase( + cmd_name: &str, + ty: &str, + arg: PositionalArg, + failures: &mut Vec, + ) { + let arg_name = arg.name; + let desc = arg.desc; + + // Check lowercase to allow usage to contain syntax like: + // + // "`as` keyword …" + if desc.starts_with(|u: char| u.is_lowercase()) { + failures.push(format!( + "{cmd_name} {ty} argument \"{arg_name}\": \"{desc}\"" + )); + } + } + + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (name_bytes, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let cmd_name = String::from_utf8_lossy(&name_bytes); + let signature = cmd.signature(); + + for arg in signature.required_positional { + starts_uppercase(&cmd_name, "required", arg, &mut failures); + } + + for arg in signature.optional_positional { + starts_uppercase(&cmd_name, "optional", arg, &mut failures); + } + + if let Some(arg) = signature.rest_positional { + starts_uppercase(&cmd_name, "rest", arg, &mut failures); + } + } + + assert!( + failures.is_empty(), + "Command argument description does not end with a period:\n{}", + failures.join("\n") + ); + } + + #[test] + fn signature_name_matches_command_name() { + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (name_bytes, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let cmd_name = String::from_utf8_lossy(&name_bytes); + let sig_name = cmd.signature().name; + let category = cmd.signature().category; + + if cmd_name != sig_name { + failures.push(format!( + "{cmd_name} ({category:?}): Signature name \"{sig_name}\" is not equal to the command name \"{cmd_name}\"" + )); + } + } + + assert!( + failures.is_empty(), + "Name mismatch:\n{}", + failures.join("\n") + ); + } + + #[test] + fn commands_declare_input_output_types() { + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (_, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let sig_name = cmd.signature().name; + let category = cmd.signature().category; + + if matches!(category, Category::Removed) { + // Deprecated/Removed commands don't have to conform + continue; + } + + if cmd.signature().input_output_types.is_empty() { + failures.push(format!( + "{sig_name} ({category:?}): No pipeline input/output type signatures found" + )); + } + } + + assert!( + failures.is_empty(), + "Command missing type annotations:\n{}", + failures.join("\n") + ); + } + + #[test] + fn no_search_term_duplicates() { + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (name_bytes, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let cmd_name = String::from_utf8_lossy(&name_bytes); + let search_terms = cmd.search_terms(); + let category = cmd.signature().category; + + for search_term in search_terms { + if cmd_name.contains(search_term) { + failures.push(format!("{cmd_name} ({category:?}): Search term \"{search_term}\" is substring of command name \"{cmd_name}\"")); + } + } + } + + assert!( + failures.is_empty(), + "Duplication in search terms:\n{}", + failures.join("\n") + ); + } + + #[test] + fn description_end_period() { + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (name_bytes, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let cmd_name = String::from_utf8_lossy(&name_bytes); + let description = cmd.description(); + + if !description.ends_with('.') { + failures.push(format!("{cmd_name}: \"{description}\"")); + } + } + + assert!( + failures.is_empty(), + "Command description does not end with a period:\n{}", + failures.join("\n") + ); + } + + #[test] + fn description_start_uppercase() { + let ctx = get_engine_state(); + let decls = ctx.get_decls_sorted(true); + let mut failures = Vec::new(); + + for (name_bytes, decl_id) in decls { + let cmd = ctx.get_decl(decl_id); + let cmd_name = String::from_utf8_lossy(&name_bytes); + let description = cmd.description(); + + // Check lowercase to allow description to contain syntax like: + // + // "`$env.FOO = ...`" + if description.starts_with(|u: char| u.is_lowercase()) { + failures.push(format!("{cmd_name}: \"{description}\"")); + } + } + + assert!( + failures.is_empty(), + "Command description does not start with an uppercase letter:\n{}", + failures.join("\n") + ); + } +} diff --git a/src/main.rs b/src/main.rs index 6beae6de77..0862529039 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod command; +mod command_context; mod config_files; mod ide; mod logger; @@ -25,9 +26,8 @@ use nu_engine::convert_env_values; use nu_lsp::LanguageServer; use nu_path::canonicalize_with; use nu_protocol::{ - engine::{EngineState, Stack}, - record, report_shell_error, ByteStream, Config, IntoValue, PipelineData, ShellError, Span, - Spanned, Type, Value, + engine::Stack, record, report_shell_error, ByteStream, Config, IntoValue, PipelineData, + ShellError, Span, Spanned, Type, Value, }; use nu_std::load_standard_library; use nu_utils::perf; @@ -35,16 +35,6 @@ use run::{run_commands, run_file, run_repl}; use signals::ctrlc_protection; use std::{path::PathBuf, str::FromStr, sync::Arc}; -fn get_engine_state() -> EngineState { - let engine_state = nu_cmd_lang::create_default_context(); - #[cfg(feature = "plugin")] - let engine_state = nu_cmd_plugin::add_plugin_command_context(engine_state); - let engine_state = nu_command::add_shell_command_context(engine_state); - let engine_state = nu_cmd_extra::add_extra_command_context(engine_state); - let engine_state = nu_cli::add_cli_context(engine_state); - nu_explore::add_explore_context(engine_state) -} - /// Get the directory where the Nushell executable is located. fn current_exe_directory() -> PathBuf { let mut path = std::env::current_exe().expect("current_exe() should succeed"); @@ -76,7 +66,7 @@ fn main() -> Result<()> { miette_hook(x); })); - let mut engine_state = get_engine_state(); + let mut engine_state = command_context::get_engine_state(); // Get the current working directory from the environment. let init_cwd = current_dir_from_environment();