mirror of
https://github.com/nushell/nushell.git
synced 2025-04-10 14:08:40 +02:00
Refactor nu-check (#12137)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> This PR refactors `nu-check` and makes it possible to check module directories. Also removes the requirement for files to end with .nu: It was too limiting for module directories and there are executable scripts [around](https://github.com/nushell/nu_scripts/tree/main/make_release/release-note) that do not end with .nu, it's a common practice for scripts to omit it. Other changes are: * Removed the `--all` flag and heuristic parse because these are irrelevant now when module syntax is a subset of script syntax (i.e., every module can be parsed as script). * Reduced code duplication and in general tidied up the code * Replaced unspanned errors with spanned ones. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> * `nu-check` doesn't require files to end with .nu * can check module directories * Removed `--all` flag # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
d8f13b36b1
commit
5e937ca1af
@ -1,11 +1,13 @@
|
|||||||
use nu_engine::{find_in_dirs_env, get_dirs_var_from_call, CallExt};
|
use nu_engine::{env::get_config, find_in_dirs_env, get_dirs_var_from_call, CallExt};
|
||||||
use nu_parser::{parse, parse_module_block, unescape_unquote_string};
|
use nu_parser::{parse, parse_module_block, parse_module_file_or_dir, unescape_unquote_string};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NuCheck;
|
pub struct NuCheck;
|
||||||
|
|
||||||
@ -16,14 +18,15 @@ impl Command for NuCheck {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("nu-check")
|
Signature::build("nu-check")
|
||||||
.input_output_types(vec![(Type::String, Type::Bool),
|
.input_output_types(vec![
|
||||||
|
(Type::String, Type::Bool),
|
||||||
(Type::ListStream, Type::Bool),
|
(Type::ListStream, Type::Bool),
|
||||||
(Type::List(Box::new(Type::Any)), Type::Bool)])
|
(Type::List(Box::new(Type::Any)), Type::Bool),
|
||||||
|
])
|
||||||
// type is string to avoid automatically canonicalizing the path
|
// type is string to avoid automatically canonicalizing the path
|
||||||
.optional("path", SyntaxShape::String, "File path to parse.")
|
.optional("path", SyntaxShape::String, "File path to parse.")
|
||||||
.switch("as-module", "Parse content as module", Some('m'))
|
.switch("as-module", "Parse content as module", Some('m'))
|
||||||
.switch("debug", "Show error messages", Some('d'))
|
.switch("debug", "Show error messages", Some('d'))
|
||||||
.switch("all", "Parse content as script first, returns result if success, otherwise, try with module", Some('a'))
|
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,45 +45,30 @@ impl Command for NuCheck {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let path: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
let path_arg: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
||||||
let is_module = call.has_flag(engine_state, stack, "as-module")?;
|
let as_module = call.has_flag(engine_state, stack, "as-module")?;
|
||||||
let is_debug = call.has_flag(engine_state, stack, "debug")?;
|
let is_debug = call.has_flag(engine_state, stack, "debug")?;
|
||||||
let is_all = call.has_flag(engine_state, stack, "all")?;
|
|
||||||
let config = engine_state.get_config();
|
|
||||||
let mut contents = vec![];
|
|
||||||
|
|
||||||
// DO NOT ever try to merge the working_set in this command
|
// DO NOT ever try to merge the working_set in this command
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
if is_all && is_module {
|
let input_span = input.span().unwrap_or(call.head);
|
||||||
return Err(ShellError::GenericError {
|
|
||||||
error: "Detected command flags conflict".into(),
|
|
||||||
msg: "You cannot have both `--all` and `--as-module` on the same command line, please refer to `nu-check --help` for more details".into(),
|
|
||||||
span: Some(call.head),
|
|
||||||
help: None,
|
|
||||||
inner: vec![]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let span = input.span().unwrap_or(call.head);
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::Value(Value::String { val, .. }, ..) => {
|
PipelineData::Value(Value::String { val, .. }, ..) => {
|
||||||
let contents = Vec::from(val);
|
let contents = Vec::from(val);
|
||||||
if is_all {
|
if as_module {
|
||||||
heuristic_parse(&mut working_set, None, &contents, is_debug, call.head)
|
parse_module(&mut working_set, None, &contents, is_debug, input_span)
|
||||||
} else if is_module {
|
|
||||||
parse_module(&mut working_set, None, &contents, is_debug, span)
|
|
||||||
} else {
|
} else {
|
||||||
parse_script(&mut working_set, None, &contents, is_debug, span)
|
parse_script(&mut working_set, None, &contents, is_debug, input_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineData::ListStream(stream, ..) => {
|
PipelineData::ListStream(stream, ..) => {
|
||||||
let list_stream = stream.into_string("\n", config);
|
let config = get_config(engine_state, stack);
|
||||||
|
let list_stream = stream.into_string("\n", &config);
|
||||||
let contents = Vec::from(list_stream);
|
let contents = Vec::from(list_stream);
|
||||||
|
|
||||||
if is_all {
|
if as_module {
|
||||||
heuristic_parse(&mut working_set, None, &contents, is_debug, call.head)
|
|
||||||
} else if is_module {
|
|
||||||
parse_module(&mut working_set, None, &contents, is_debug, call.head)
|
parse_module(&mut working_set, None, &contents, is_debug, call.head)
|
||||||
} else {
|
} else {
|
||||||
parse_script(&mut working_set, None, &contents, is_debug, call.head)
|
parse_script(&mut working_set, None, &contents, is_debug, call.head)
|
||||||
@ -90,6 +78,7 @@ impl Command for NuCheck {
|
|||||||
stdout: Some(stream),
|
stdout: Some(stream),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
let mut contents = vec![];
|
||||||
let raw_stream: Vec<_> = stream.stream.collect();
|
let raw_stream: Vec<_> = stream.stream.collect();
|
||||||
for r in raw_stream {
|
for r in raw_stream {
|
||||||
match r {
|
match r {
|
||||||
@ -98,16 +87,16 @@ impl Command for NuCheck {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_all {
|
if as_module {
|
||||||
heuristic_parse(&mut working_set, None, &contents, is_debug, call.head)
|
|
||||||
} else if is_module {
|
|
||||||
parse_module(&mut working_set, None, &contents, is_debug, call.head)
|
parse_module(&mut working_set, None, &contents, is_debug, call.head)
|
||||||
} else {
|
} else {
|
||||||
parse_script(&mut working_set, None, &contents, is_debug, call.head)
|
parse_script(&mut working_set, None, &contents, is_debug, call.head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(path_str) = path {
|
if let Some(path_str) = path_arg {
|
||||||
|
let path_span = path_str.span;
|
||||||
|
|
||||||
// look up the path as relative to FILE_PWD or inside NU_LIB_DIRS (same process as source-env)
|
// look up the path as relative to FILE_PWD or inside NU_LIB_DIRS (same process as source-env)
|
||||||
let path = match find_in_dirs_env(
|
let path = match find_in_dirs_env(
|
||||||
&path_str.item,
|
&path_str.item,
|
||||||
@ -121,44 +110,32 @@ impl Command for NuCheck {
|
|||||||
} else {
|
} else {
|
||||||
return Err(ShellError::FileNotFound {
|
return Err(ShellError::FileNotFound {
|
||||||
file: path_str.item,
|
file: path_str.item,
|
||||||
span: path_str.span,
|
span: path_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
};
|
};
|
||||||
|
|
||||||
// get the expanded path as a string
|
|
||||||
let path_str = path.to_string_lossy().to_string();
|
|
||||||
|
|
||||||
let ext: Vec<_> = path_str.rsplitn(2, '.').collect();
|
|
||||||
if ext[0] != "nu" {
|
|
||||||
return Err(ShellError::GenericError {
|
|
||||||
error: "Cannot parse input".into(),
|
|
||||||
msg: "File extension must be the type of .nu".into(),
|
|
||||||
span: Some(call.head),
|
|
||||||
help: None,
|
|
||||||
inner: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change currently parsed directory
|
// Change currently parsed directory
|
||||||
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||||
let prev = working_set.currently_parsed_cwd.clone();
|
let prev = working_set.currently_parsed_cwd.clone();
|
||||||
|
|
||||||
working_set.currently_parsed_cwd = Some(parent.into());
|
working_set.currently_parsed_cwd = Some(parent.into());
|
||||||
|
|
||||||
prev
|
prev
|
||||||
} else {
|
} else {
|
||||||
working_set.currently_parsed_cwd.clone()
|
working_set.currently_parsed_cwd.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = if is_all {
|
let result = if as_module || path.is_dir() {
|
||||||
heuristic_parse_file(path_str, &mut working_set, call, is_debug)
|
parse_file_or_dir_module(
|
||||||
} else if is_module {
|
path.to_string_lossy().as_bytes(),
|
||||||
parse_file_module(path_str, &mut working_set, call, is_debug)
|
&mut working_set,
|
||||||
|
is_debug,
|
||||||
|
path_span,
|
||||||
|
call.head,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
parse_file_script(path_str, &mut working_set, call, is_debug)
|
parse_file_script(&path, &mut working_set, is_debug, path_span, call.head)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restore the currently parsed directory back
|
// Restore the currently parsed directory back
|
||||||
@ -168,9 +145,9 @@ impl Command for NuCheck {
|
|||||||
} else {
|
} else {
|
||||||
Err(ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: "Failed to execute command".into(),
|
error: "Failed to execute command".into(),
|
||||||
msg: "Please run 'nu-check --help' for more details".into(),
|
msg: "Requires path argument if ran without pipeline input".into(),
|
||||||
span: Some(call.head),
|
span: Some(call.head),
|
||||||
help: None,
|
help: Some("Please run 'nu-check --help' for more details".into()),
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -224,101 +201,12 @@ impl Command for NuCheck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn heuristic_parse(
|
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
filename: Option<&str>,
|
|
||||||
contents: &[u8],
|
|
||||||
is_debug: bool,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
match parse_script(working_set, filename, contents, is_debug, span) {
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
Err(_) => {
|
|
||||||
match parse_module(
|
|
||||||
working_set,
|
|
||||||
filename.map(|f| f.to_string()),
|
|
||||||
contents,
|
|
||||||
is_debug,
|
|
||||||
span,
|
|
||||||
) {
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
Err(_) => {
|
|
||||||
if is_debug {
|
|
||||||
Err(ShellError::GenericError {
|
|
||||||
error: "Failed to parse content,tried both script and module".into(),
|
|
||||||
msg: "syntax error".into(),
|
|
||||||
span: Some(span),
|
|
||||||
help: Some("Run `nu-check --help` for more details".into()),
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(PipelineData::Value(Value::bool(false, span), None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn heuristic_parse_file(
|
|
||||||
path: String,
|
|
||||||
working_set: &mut StateWorkingSet,
|
|
||||||
call: &Call,
|
|
||||||
is_debug: bool,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let starting_error_count = working_set.parse_errors.len();
|
|
||||||
let bytes = working_set.get_span_contents(call.head);
|
|
||||||
let (filename, err) = unescape_unquote_string(bytes, call.head);
|
|
||||||
if let Some(err) = err {
|
|
||||||
working_set.error(err);
|
|
||||||
}
|
|
||||||
if starting_error_count == working_set.parse_errors.len() {
|
|
||||||
if let Ok(contents) = std::fs::read(path) {
|
|
||||||
match parse_script(
|
|
||||||
working_set,
|
|
||||||
Some(filename.as_str()),
|
|
||||||
&contents,
|
|
||||||
is_debug,
|
|
||||||
call.head,
|
|
||||||
) {
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
Err(_) => {
|
|
||||||
match parse_module(working_set, Some(filename), &contents, is_debug, call.head)
|
|
||||||
{
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
Err(_) => {
|
|
||||||
if is_debug {
|
|
||||||
Err(ShellError::GenericError {
|
|
||||||
error: "Failed to parse content,tried both script and module"
|
|
||||||
.into(),
|
|
||||||
msg: "syntax error".into(),
|
|
||||||
span: Some(call.head),
|
|
||||||
help: Some("Run `nu-check --help` for more details".into()),
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(PipelineData::Value(Value::bool(false, call.head), None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::IOError {
|
|
||||||
msg: "Can not read input".to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ShellError::NotFound { span: call.head })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_module(
|
fn parse_module(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
filename: Option<String>,
|
filename: Option<String>,
|
||||||
contents: &[u8],
|
contents: &[u8],
|
||||||
is_debug: bool,
|
is_debug: bool,
|
||||||
span: Span,
|
call_head: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let filename = filename.unwrap_or_else(|| "empty".to_string());
|
let filename = filename.unwrap_or_else(|| "empty".to_string());
|
||||||
|
|
||||||
@ -328,28 +216,16 @@ fn parse_module(
|
|||||||
let starting_error_count = working_set.parse_errors.len();
|
let starting_error_count = working_set.parse_errors.len();
|
||||||
parse_module_block(working_set, new_span, filename.as_bytes());
|
parse_module_block(working_set, new_span, filename.as_bytes());
|
||||||
|
|
||||||
if starting_error_count != working_set.parse_errors.len() {
|
check_parse(
|
||||||
if is_debug {
|
starting_error_count,
|
||||||
let msg = format!(
|
working_set,
|
||||||
r#"Found : {}"#,
|
is_debug,
|
||||||
working_set
|
Some(
|
||||||
.parse_errors
|
"If the content is intended to be a script, please try to remove `--as-module` flag "
|
||||||
.first()
|
.to_string(),
|
||||||
.expect("Unable to parse content as module")
|
),
|
||||||
);
|
call_head,
|
||||||
Err(ShellError::GenericError {
|
)
|
||||||
error: "Failed to parse content".into(),
|
|
||||||
msg,
|
|
||||||
span: Some(span),
|
|
||||||
help: Some("If the content is intended to be a script, please try to remove `--as-module` flag ".into()),
|
|
||||||
inner: vec![],
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(PipelineData::Value(Value::bool(false, new_span), None))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(PipelineData::Value(Value::bool(true, new_span), None))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_script(
|
fn parse_script(
|
||||||
@ -357,86 +233,116 @@ fn parse_script(
|
|||||||
filename: Option<&str>,
|
filename: Option<&str>,
|
||||||
contents: &[u8],
|
contents: &[u8],
|
||||||
is_debug: bool,
|
is_debug: bool,
|
||||||
span: Span,
|
call_head: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let starting_error_count = working_set.parse_errors.len();
|
let starting_error_count = working_set.parse_errors.len();
|
||||||
parse(working_set, filename, contents, false);
|
parse(working_set, filename, contents, false);
|
||||||
|
check_parse(starting_error_count, working_set, is_debug, None, call_head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_parse(
|
||||||
|
starting_error_count: usize,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
is_debug: bool,
|
||||||
|
help: Option<String>,
|
||||||
|
call_head: Span,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
if starting_error_count != working_set.parse_errors.len() {
|
if starting_error_count != working_set.parse_errors.len() {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
r#"Found : {}"#,
|
r#"Found : {}"#,
|
||||||
working_set
|
working_set
|
||||||
.parse_errors
|
.parse_errors
|
||||||
.first()
|
.first()
|
||||||
.expect("Unable to parse content")
|
.expect("Missing parser error")
|
||||||
);
|
);
|
||||||
|
|
||||||
if is_debug {
|
if is_debug {
|
||||||
Err(ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: "Failed to parse content".into(),
|
error: "Failed to parse content".into(),
|
||||||
msg,
|
msg,
|
||||||
span: Some(span),
|
span: Some(call_head),
|
||||||
help: Some("If the content is intended to be a module, please consider flag of `--as-module` ".into()),
|
help,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(PipelineData::Value(Value::bool(false, span), None))
|
Ok(PipelineData::Value(Value::bool(false, call_head), None))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(PipelineData::Value(Value::bool(true, span), None))
|
Ok(PipelineData::Value(Value::bool(true, call_head), None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_file_script(
|
fn parse_file_script(
|
||||||
path: String,
|
path: &Path,
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
call: &Call,
|
|
||||||
is_debug: bool,
|
is_debug: bool,
|
||||||
|
path_span: Span,
|
||||||
|
call_head: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let starting_error_count = working_set.parse_errors.len();
|
let filename = check_path(working_set, path_span, call_head)?;
|
||||||
let bytes = working_set.get_span_contents(call.head);
|
|
||||||
let (filename, err) = unescape_unquote_string(bytes, call.head);
|
|
||||||
if let Some(err) = err {
|
|
||||||
working_set.error(err)
|
|
||||||
}
|
|
||||||
if starting_error_count == working_set.parse_errors.len() {
|
|
||||||
if let Ok(contents) = std::fs::read(path) {
|
if let Ok(contents) = std::fs::read(path) {
|
||||||
parse_script(
|
parse_script(working_set, Some(&filename), &contents, is_debug, call_head)
|
||||||
working_set,
|
|
||||||
Some(filename.as_str()),
|
|
||||||
&contents,
|
|
||||||
is_debug,
|
|
||||||
call.head,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::IOError {
|
Err(ShellError::IOErrorSpanned {
|
||||||
msg: "Can not read path".to_string(),
|
msg: "Could not read path".to_string(),
|
||||||
|
span: path_span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Err(ShellError::NotFound { span: call.head })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_file_module(
|
fn parse_file_or_dir_module(
|
||||||
path: String,
|
path_bytes: &[u8],
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
call: &Call,
|
|
||||||
is_debug: bool,
|
is_debug: bool,
|
||||||
|
path_span: Span,
|
||||||
|
call_head: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let _ = check_path(working_set, path_span, call_head)?;
|
||||||
|
|
||||||
let starting_error_count = working_set.parse_errors.len();
|
let starting_error_count = working_set.parse_errors.len();
|
||||||
let bytes = working_set.get_span_contents(call.head);
|
let _ = parse_module_file_or_dir(working_set, path_bytes, path_span, None);
|
||||||
let (filename, err) = unescape_unquote_string(bytes, call.head);
|
|
||||||
if let Some(err) = err {
|
if starting_error_count != working_set.parse_errors.len() {
|
||||||
working_set.error(err);
|
if is_debug {
|
||||||
}
|
let msg = format!(
|
||||||
if starting_error_count == working_set.parse_errors.len() {
|
r#"Found : {}"#,
|
||||||
if let Ok(contents) = std::fs::read(path) {
|
working_set
|
||||||
parse_module(working_set, Some(filename), &contents, is_debug, call.head)
|
.parse_errors
|
||||||
} else {
|
.first()
|
||||||
Err(ShellError::IOError {
|
.expect("Missing parser error")
|
||||||
msg: "Can not read path".to_string(),
|
);
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "Failed to parse content".into(),
|
||||||
|
msg,
|
||||||
|
span: Some(path_span),
|
||||||
|
help: Some("If the content is intended to be a script, please try to remove `--as-module` flag ".into()),
|
||||||
|
inner: vec![],
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
Ok(PipelineData::Value(Value::bool(false, call_head), None))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::NotFound { span: call.head })
|
Ok(PipelineData::Value(Value::bool(true, call_head), None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_path(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
path_span: Span,
|
||||||
|
call_head: Span,
|
||||||
|
) -> Result<String, ShellError> {
|
||||||
|
let bytes = working_set.get_span_contents(path_span);
|
||||||
|
let (filename, err) = unescape_unquote_string(bytes, path_span);
|
||||||
|
if let Some(e) = err {
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "Could not escape filename".to_string(),
|
||||||
|
msg: "could not escape filename".to_string(),
|
||||||
|
span: Some(call_head),
|
||||||
|
help: Some(format!("Returned error: {e}")),
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,52 +176,6 @@ fn file_not_exist() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_unsupported_file() {
|
|
||||||
Playground::setup("nu_check_test_8", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
|
||||||
"foo.txt",
|
|
||||||
r#"
|
|
||||||
# foo.nu
|
|
||||||
|
|
||||||
export def hello [name: string {
|
|
||||||
$"hello ($name)!"
|
|
||||||
}
|
|
||||||
|
|
||||||
export def hi [where: string] {
|
|
||||||
$"hi ($where)!"
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
)]);
|
|
||||||
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
"
|
|
||||||
nu-check --as-module foo.txt
|
|
||||||
"
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(actual
|
|
||||||
.err
|
|
||||||
.contains("File extension must be the type of .nu"));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn parse_dir_failure() {
|
|
||||||
Playground::setup("nu_check_test_9", |dirs, _sandbox| {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
"
|
|
||||||
nu-check --as-module ~
|
|
||||||
"
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(actual
|
|
||||||
.err
|
|
||||||
.contains("File extension must be the type of .nu"));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_module_success_2() {
|
fn parse_module_success_2() {
|
||||||
Playground::setup("nu_check_test_10", |dirs, sandbox| {
|
Playground::setup("nu_check_test_10", |dirs, sandbox| {
|
||||||
@ -554,7 +508,7 @@ fn parse_module_success_with_complex_external_stream() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_with_flag_all_success_for_complex_external_stream() {
|
fn parse_with_flag_success_for_complex_external_stream() {
|
||||||
Playground::setup("nu_check_test_20", |dirs, sandbox| {
|
Playground::setup("nu_check_test_20", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
"grep.nu",
|
"grep.nu",
|
||||||
@ -594,7 +548,7 @@ fn parse_with_flag_all_success_for_complex_external_stream() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(), pipeline(
|
cwd: dirs.test(), pipeline(
|
||||||
"
|
"
|
||||||
open grep.nu | nu-check --all --debug
|
open grep.nu | nu-check --debug
|
||||||
"
|
"
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -603,7 +557,7 @@ fn parse_with_flag_all_success_for_complex_external_stream() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_with_flag_all_failure_for_complex_external_stream() {
|
fn parse_with_flag_failure_for_complex_external_stream() {
|
||||||
Playground::setup("nu_check_test_21", |dirs, sandbox| {
|
Playground::setup("nu_check_test_21", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
"grep.nu",
|
"grep.nu",
|
||||||
@ -643,16 +597,16 @@ fn parse_with_flag_all_failure_for_complex_external_stream() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(), pipeline(
|
cwd: dirs.test(), pipeline(
|
||||||
"
|
"
|
||||||
open grep.nu | nu-check --all --debug
|
open grep.nu | nu-check --debug
|
||||||
"
|
"
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(actual.err.contains("syntax error"));
|
assert!(actual.err.contains("Failed to parse content"));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_with_flag_all_failure_for_complex_list_stream() {
|
fn parse_with_flag_failure_for_complex_list_stream() {
|
||||||
Playground::setup("nu_check_test_22", |dirs, sandbox| {
|
Playground::setup("nu_check_test_22", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
"grep.nu",
|
"grep.nu",
|
||||||
@ -692,38 +646,11 @@ fn parse_with_flag_all_failure_for_complex_list_stream() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(), pipeline(
|
cwd: dirs.test(), pipeline(
|
||||||
"
|
"
|
||||||
open grep.nu | lines | nu-check --all --debug
|
open grep.nu | lines | nu-check --debug
|
||||||
"
|
"
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(actual.err.contains("syntax error"));
|
assert!(actual.err.contains("Failed to parse content"));
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_failure_due_conflicted_flags() {
|
|
||||||
Playground::setup("nu_check_test_23", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
|
||||||
"script.nu",
|
|
||||||
r#"
|
|
||||||
greet "world"
|
|
||||||
|
|
||||||
def greet [name] {
|
|
||||||
echo "hello" $name
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
)]);
|
|
||||||
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: dirs.test(), pipeline(
|
|
||||||
"
|
|
||||||
nu-check -a --as-module script.nu
|
|
||||||
"
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(actual
|
|
||||||
.err
|
|
||||||
.contains("You cannot have both `--all` and `--as-module` on the same command line"));
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -793,3 +720,27 @@ fn nu_check_respects_file_pwd() {
|
|||||||
assert_eq!(actual.out, "true");
|
assert_eq!(actual.out, "true");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn nu_check_module_dir() {
|
||||||
|
Playground::setup("nu_check_test_26", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("lol")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/mod.nu",
|
||||||
|
r#"
|
||||||
|
export module foo.nu
|
||||||
|
export def main [] { 'lol' }
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"lol/foo.nu",
|
||||||
|
r#"
|
||||||
|
export def main [] { 'lol foo' }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline( "nu-check lol"));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user