mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 08:23:24 +01:00
make module name parsing more strict
# User-Facing Changes
- Module names must now be valid identifiers, with an exception for
hyphens:
```nushell
module foo/bar {} # × expected valid module name
module foo-bar {} # ✅
```
- Variable access in modules named with hyphens no longer causes
"expected valid variable name" errors:
```nushell
module foo-bar { export const baz = 1 }
use foo-bar
$foo-bar.baz
```
This commit is contained in:
parent
a84d410f11
commit
ee2f246216
@ -191,7 +191,7 @@ fn parse_module(
|
|||||||
let new_span = working_set.get_span_for_file(file_id);
|
let new_span = working_set.get_span_for_file(file_id);
|
||||||
|
|
||||||
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.into_spanned(call_head));
|
||||||
|
|
||||||
check_parse(
|
check_parse(
|
||||||
starting_error_count,
|
starting_error_count,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
exportable::Exportable,
|
exportable::Exportable,
|
||||||
parse_block,
|
parse_block,
|
||||||
parser::{parse_redirection, redirecting_builtin_error},
|
parser::{is_module_name, parse_redirection, redirecting_builtin_error},
|
||||||
type_check::{check_block_input_output, type_compatible},
|
type_check::{check_block_input_output, type_compatible},
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -15,8 +15,8 @@ use nu_protocol::{
|
|||||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||||
eval_const::eval_constant,
|
eval_const::eval_constant,
|
||||||
parser_path::ParserPath,
|
parser_path::ParserPath,
|
||||||
Alias, BlockId, DeclId, Module, ModuleId, ParseError, PositionalArg, ResolvedImportPattern,
|
Alias, BlockId, DeclId, IntoSpanned, Module, ModuleId, ParseError, PositionalArg,
|
||||||
Span, Spanned, SyntaxShape, Type, Value, VarId,
|
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, Value, VarId,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
@ -1715,7 +1715,7 @@ fn collect_first_comments(tokens: &[Token]) -> Vec<Span> {
|
|||||||
pub fn parse_module_block(
|
pub fn parse_module_block(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
span: Span,
|
span: Span,
|
||||||
module_name: &[u8],
|
module_name: &Spanned<String>,
|
||||||
) -> (Block, Module, Vec<Span>) {
|
) -> (Block, Module, Vec<Span>) {
|
||||||
working_set.enter_scope();
|
working_set.enter_scope();
|
||||||
|
|
||||||
@ -1739,6 +1739,11 @@ pub fn parse_module_block(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !is_module_name(module_name.item.as_bytes()) {
|
||||||
|
working_set.error(ParseError::Expected("valid module name", module_name.span));
|
||||||
|
}
|
||||||
|
let module_name = module_name.item.as_bytes();
|
||||||
|
|
||||||
let mut module = Module::from_span(module_name.to_vec(), span);
|
let mut module = Module::from_span(module_name.to_vec(), span);
|
||||||
|
|
||||||
let mut block = Block::new_with_capacity(output.block.len());
|
let mut block = Block::new_with_capacity(output.block.len());
|
||||||
@ -1948,13 +1953,13 @@ fn parse_module_file(
|
|||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
path: ParserPath,
|
path: ParserPath,
|
||||||
path_span: Span,
|
path_span: Span,
|
||||||
name_override: Option<String>,
|
name_override: Option<Spanned<String>>,
|
||||||
) -> Option<ModuleId> {
|
) -> Option<ModuleId> {
|
||||||
// Infer the module name from the stem of the file, unless overridden.
|
// Infer the module name from the stem of the file, unless overridden.
|
||||||
let module_name = if let Some(name) = name_override {
|
let module_name = if let Some(name) = name_override {
|
||||||
name
|
name
|
||||||
} else if let Some(stem) = path.file_stem() {
|
} else if let Some(stem) = path.file_stem() {
|
||||||
stem.to_string_lossy().to_string()
|
stem.to_string_lossy().to_string().into_spanned(path_span)
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::ModuleNotFound(
|
working_set.error(ParseError::ModuleNotFound(
|
||||||
path_span,
|
path_span,
|
||||||
@ -1992,14 +1997,14 @@ fn parse_module_file(
|
|||||||
|
|
||||||
// Parse the module
|
// Parse the module
|
||||||
let (block, mut module, module_comments) =
|
let (block, mut module, module_comments) =
|
||||||
parse_module_block(working_set, new_span, module_name.as_bytes());
|
parse_module_block(working_set, new_span, &module_name);
|
||||||
|
|
||||||
// Remove the file from the stack of files being processed.
|
// Remove the file from the stack of files being processed.
|
||||||
working_set.files.pop();
|
working_set.files.pop();
|
||||||
|
|
||||||
let _ = working_set.add_block(Arc::new(block));
|
let _ = working_set.add_block(Arc::new(block));
|
||||||
module.file = Some((path, file_id));
|
module.file = Some((path, file_id));
|
||||||
let module_id = working_set.add_module(&module_name, module, module_comments);
|
let module_id = working_set.add_module(&module_name.item, module, module_comments);
|
||||||
|
|
||||||
Some(module_id)
|
Some(module_id)
|
||||||
}
|
}
|
||||||
@ -2008,7 +2013,7 @@ pub fn parse_module_file_or_dir(
|
|||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
path: &[u8],
|
path: &[u8],
|
||||||
path_span: Span,
|
path_span: Span,
|
||||||
name_override: Option<String>,
|
name_override: Option<Spanned<String>>,
|
||||||
) -> Option<ModuleId> {
|
) -> Option<ModuleId> {
|
||||||
let (module_path_str, err) = unescape_unquote_string(path, path_span);
|
let (module_path_str, err) = unescape_unquote_string(path, path_span);
|
||||||
if let Some(err) = err {
|
if let Some(err) = err {
|
||||||
@ -2037,7 +2042,7 @@ pub fn parse_module_file_or_dir(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let module_name = if let Some(stem) = module_path.file_stem() {
|
let module_name = if let Some(stem) = module_path.file_stem() {
|
||||||
stem.to_string_lossy().to_string()
|
stem.to_string_lossy().to_string().into_spanned(path_span)
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::ModuleNotFound(
|
working_set.error(ParseError::ModuleNotFound(
|
||||||
path_span,
|
path_span,
|
||||||
@ -2250,8 +2255,11 @@ pub fn parse_module(
|
|||||||
|
|
||||||
let block_span = Span::new(start, end);
|
let block_span = Span::new(start, end);
|
||||||
|
|
||||||
let (block, module, inner_comments) =
|
let (block, module, inner_comments) = parse_module_block(
|
||||||
parse_module_block(working_set, block_span, module_name.as_bytes());
|
working_set,
|
||||||
|
block_span,
|
||||||
|
&module_name.clone().into_spanned(module_name_or_path_span),
|
||||||
|
);
|
||||||
|
|
||||||
let block_id = working_set.add_block(Arc::new(block));
|
let block_id = working_set.add_block(Arc::new(block));
|
||||||
|
|
||||||
@ -2893,7 +2901,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
|||||||
working_set,
|
working_set,
|
||||||
overlay_name.as_bytes(),
|
overlay_name.as_bytes(),
|
||||||
overlay_name_span,
|
overlay_name_span,
|
||||||
new_name.as_ref().map(|spanned| spanned.item.clone()),
|
new_name.clone(),
|
||||||
) {
|
) {
|
||||||
// try file or directory
|
// try file or directory
|
||||||
let new_module = working_set.get_module(module_id).clone();
|
let new_module = working_set.get_module(module_id).clone();
|
||||||
|
@ -126,6 +126,12 @@ pub fn is_variable(bytes: &[u8]) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_module_name(bytes: &[u8]) -> bool {
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.all(|x| is_identifier_byte(*x) && *x != b'\\' || *x == b'-')
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trim_quotes(bytes: &[u8]) -> &[u8] {
|
pub fn trim_quotes(bytes: &[u8]) -> &[u8] {
|
||||||
if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|
if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|
||||||
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
|
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
|
||||||
@ -5559,7 +5565,7 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex
|
|||||||
pub fn parse_variable(working_set: &mut StateWorkingSet, span: Span) -> Option<VarId> {
|
pub fn parse_variable(working_set: &mut StateWorkingSet, span: Span) -> Option<VarId> {
|
||||||
let bytes = working_set.get_span_contents(span);
|
let bytes = working_set.get_span_contents(span);
|
||||||
|
|
||||||
if is_variable(bytes) {
|
if is_variable(bytes) || is_module_name(bytes) {
|
||||||
working_set.find_variable(bytes)
|
working_set.find_variable(bytes)
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::Expected("valid variable name", span));
|
working_set.error(ParseError::Expected("valid variable name", span));
|
||||||
|
@ -145,6 +145,14 @@ fn export_module_which_defined_const() -> TestResult {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_module_with_hyphen_in_name() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"module spam-mod { export const b = 3; }; use spam-mod; $spam-mod.b"#,
|
||||||
|
"3",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cannot_export_private_const() -> TestResult {
|
fn cannot_export_private_const() -> TestResult {
|
||||||
fail_test(
|
fail_test(
|
||||||
|
@ -186,6 +186,13 @@ fn bad_var_name3() -> TestResult {
|
|||||||
fail_test(r#"=foo=4 true"#, "Command `=foo=4` not found")
|
fail_test(r#"=foo=4 true"#, "Command `=foo=4` not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_module_name() -> TestResult {
|
||||||
|
fail_test(r#"module foo/bar {}"#, "valid module name")?;
|
||||||
|
fail_test(r#"module foo\bar {}"#, "valid module name")?;
|
||||||
|
fail_test(r#"module foo=bar {}"#, "valid module name")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assignment_with_no_var() -> TestResult {
|
fn assignment_with_no_var() -> TestResult {
|
||||||
let cases = [
|
let cases = [
|
||||||
|
Loading…
Reference in New Issue
Block a user