mirror of
https://github.com/nushell/nushell.git
synced 2025-04-11 23:08:20 +02:00
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)
This commit is contained in:
parent
2a3d5a9d42
commit
a58d9b0b3a
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<Example> {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = []
|
||||
system-clipboard = []
|
||||
|
@ -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::*;
|
||||
|
19
crates/nu-cmd-lang/src/parse_const_test.rs
Normal file
19
crates/nu-cmd-lang/src/parse_const_test.rs
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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<String>) {
|
||||
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<String>) {
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
239
src/command_context.rs
Normal file
239
src/command_context.rs
Normal file
@ -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<String>) {
|
||||
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<String>,
|
||||
) {
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
18
src/main.rs
18
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();
|
||||
|
Loading…
Reference in New Issue
Block a user