Allow main command to define top-level module command (#7764)

This commit is contained in:
Jakub Žádník
2023-01-22 21:34:15 +02:00
committed by GitHub
parent 8d5165c449
commit 3552d03f6c
13 changed files with 633 additions and 135 deletions

View File

@ -213,6 +213,26 @@ pub enum ParseError {
#[diagnostic(code(nu::parser::cyclical_module_import), url(docsrs), help("{0}"))]
CyclicalModuleImport(String, #[label = "detected cyclical module import"] Span),
#[error("Can't export {0} named same as the module.")]
#[diagnostic(
code(nu::parser::named_as_module),
url(docsrs),
help("Module {1} can't export {0} named the same as the module. Either change the module name, or export `main` custom command.")
)]
NamedAsModule(
String,
String,
#[label = "can't export from module {1}"] Span,
),
#[error("Can't export alias defined as 'main'.")]
#[diagnostic(
code(nu::parser::export_main_alias_not_allowed),
url(docsrs),
help("Exporting aliases as 'main' is not allowed. Either rename the alias or convert it to a custom command.")
)]
ExportMainAliasNotAllowed(#[label = "can't export from module"] Span),
#[error("Active overlay not found.")]
#[diagnostic(code(nu::parser::active_overlay_not_found), url(docsrs))]
ActiveOverlayNotFound(#[label = "not an active overlay"] Span),
@ -450,6 +470,8 @@ impl ParseError {
ParseError::AliasNotValid(s) => *s,
ParseError::CommandDefNotValid(s) => *s,
ParseError::ModuleNotFound(s) => *s,
ParseError::NamedAsModule(_, _, s) => *s,
ParseError::ExportMainAliasNotAllowed(s) => *s,
ParseError::CyclicalModuleImport(_, s) => *s,
ParseError::ModuleOrOverlayNotFound(s) => *s,
ParseError::ActiveOverlayNotFound(s) => *s,

View File

@ -232,6 +232,7 @@ pub fn parse_for(
pub fn parse_def(
working_set: &mut StateWorkingSet,
lite_command: &LiteCommand,
module_name: Option<&[u8]>,
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let spans = &lite_command.parts[..];
@ -337,9 +338,39 @@ pub fn parse_def(
let mut error = None;
if let (Some(name), Some(mut signature), Some(block_id)) =
(&name_expr.as_string(), sig.as_signature(), block.as_block())
{
let name = if let Some(name) = name_expr.as_string() {
if let Some(mod_name) = module_name {
if name.as_bytes() == mod_name {
let name_expr_span = name_expr.span;
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
custom_completion: None,
}]),
Some(ParseError::NamedAsModule(
"command".to_string(),
name,
name_expr_span,
)),
);
}
}
name
} else {
return (
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"Could not get string from string expression".into(),
name_expr.span,
)),
);
};
if let (Some(mut signature), Some(block_id)) = (sig.as_signature(), block.as_block()) {
if let Some(decl_id) = working_set.find_predecl(name.as_bytes()) {
let declaration = working_set.get_decl_mut(decl_id);
@ -398,17 +429,8 @@ pub fn parse_def(
};
}
if let Some(name) = name_expr.as_string() {
// It's OK if it returns None: The decl was already merged in previous parse pass.
working_set.merge_predecl(name.as_bytes());
} else {
error = error.or_else(|| {
Some(ParseError::UnknownState(
"Could not get string from string expression".into(),
name_expr.span,
))
});
}
// It's OK if it returns None: The decl was already merged in previous parse pass.
working_set.merge_predecl(name.as_bytes());
(
Pipeline::from_vec(vec![Expression {
@ -424,6 +446,7 @@ pub fn parse_def(
pub fn parse_extern(
working_set: &mut StateWorkingSet,
lite_command: &LiteCommand,
module_name: Option<&[u8]>,
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let spans = &lite_command.parts;
@ -495,16 +518,45 @@ pub fn parse_extern(
if let (Some(name_expr), Some(sig)) = (name_expr, sig) {
if let (Some(name), Some(mut signature)) = (&name_expr.as_string(), sig.as_signature()) {
if let Some(mod_name) = module_name {
if name.as_bytes() == mod_name {
let name_expr_span = name_expr.span;
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: call_span,
ty: Type::Any,
custom_completion: None,
}]),
Some(ParseError::NamedAsModule(
"known external".to_string(),
name.clone(),
name_expr_span,
)),
);
}
}
if let Some(decl_id) = working_set.find_predecl(name.as_bytes()) {
let declaration = working_set.get_decl_mut(decl_id);
signature.name = name.clone();
let external_name = if let Some(mod_name) = module_name {
if name.as_bytes() == b"main" {
String::from_utf8_lossy(mod_name).to_string()
} else {
name.clone()
}
} else {
name.clone()
};
signature.name = external_name.clone();
signature.usage = usage.clone();
signature.extra_usage = extra_usage.clone();
signature.allows_unknown_args = true;
let decl = KnownExternal {
name: name.to_string(),
name: external_name,
usage: [usage, extra_usage].join("\n"),
signature,
};
@ -546,6 +598,7 @@ pub fn parse_extern(
pub fn parse_alias(
working_set: &mut StateWorkingSet,
lite_command: &LiteCommand,
module_name: Option<&[u8]>,
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let spans = &lite_command.parts;
@ -644,6 +697,37 @@ pub fn parse_alias(
} else {
alias_name.to_vec()
};
if let Some(mod_name) = module_name {
if alias_name == mod_name {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: output,
custom_completion: None,
}]),
Some(ParseError::NamedAsModule(
"alias".to_string(),
String::from_utf8_lossy(&alias_name).to_string(),
spans[split_id],
)),
);
}
if &alias_name == b"main" {
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: output,
custom_completion: None,
}]),
Some(ParseError::ExportMainAliasNotAllowed(spans[split_id])),
);
}
}
let _equals = working_set.get_span_contents(spans[split_id + 1]);
let replacement = spans[(split_id + 2)..].to_vec();
@ -773,16 +857,16 @@ pub fn parse_export_in_block(
}
match full_name.as_slice() {
b"export alias" => parse_alias(working_set, lite_command, expand_aliases_denylist),
b"export alias" => parse_alias(working_set, lite_command, None, expand_aliases_denylist),
b"export def" | b"export def-env" => {
parse_def(working_set, lite_command, expand_aliases_denylist)
parse_def(working_set, lite_command, None, expand_aliases_denylist)
}
b"export use" => {
let (pipeline, _, err) =
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
(pipeline, err)
}
b"export extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
b"export extern" => parse_extern(working_set, lite_command, None, expand_aliases_denylist),
_ => (
garbage_pipeline(&lite_command.parts),
Some(ParseError::UnexpectedKeyword(
@ -797,6 +881,7 @@ pub fn parse_export_in_block(
pub fn parse_export_in_module(
working_set: &mut StateWorkingSet,
lite_command: &LiteCommand,
module_name: &[u8],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Vec<Exportable>, Option<ParseError>) {
let spans = &lite_command.parts[..];
@ -856,8 +941,12 @@ pub fn parse_export_in_module(
comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(),
};
let (pipeline, err) =
parse_def(working_set, &lite_command, expand_aliases_denylist);
let (pipeline, err) = parse_def(
working_set,
&lite_command,
Some(module_name),
expand_aliases_denylist,
);
error = error.or(err);
let export_def_decl_id =
@ -924,8 +1013,12 @@ pub fn parse_export_in_module(
comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(),
};
let (pipeline, err) =
parse_def(working_set, &lite_command, expand_aliases_denylist);
let (pipeline, err) = parse_def(
working_set,
&lite_command,
Some(module_name),
expand_aliases_denylist,
);
error = error.or(err);
let export_def_decl_id =
@ -993,8 +1086,12 @@ pub fn parse_export_in_module(
comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(),
};
let (pipeline, err) =
parse_extern(working_set, &lite_command, expand_aliases_denylist);
let (pipeline, err) = parse_extern(
working_set,
&lite_command,
Some(module_name),
expand_aliases_denylist,
);
error = error.or(err);
let export_def_decl_id =
@ -1062,8 +1159,12 @@ pub fn parse_export_in_module(
comments: lite_command.comments.clone(),
parts: spans[1..].to_vec(),
};
let (pipeline, err) =
parse_alias(working_set, &lite_command, expand_aliases_denylist);
let (pipeline, err) = parse_alias(
working_set,
&lite_command,
Some(module_name),
expand_aliases_denylist,
);
error = error.or(err);
let export_alias_decl_id =
@ -1349,6 +1450,7 @@ fn collect_first_comments(tokens: &[Token]) -> Vec<Span> {
pub fn parse_module_block(
working_set: &mut StateWorkingSet,
span: Span,
module_name: &[u8],
expand_aliases_denylist: &[usize],
) -> (Block, Module, Vec<Span>, Option<ParseError>) {
let mut error = None;
@ -1373,7 +1475,7 @@ pub fn parse_module_block(
}
}
let mut module = Module::from_span(span);
let mut module = Module::from_span(module_name.to_vec(), span);
let block: Block = output
.block
@ -1386,20 +1488,32 @@ pub fn parse_module_block(
let (pipeline, err) = match name {
b"def" | b"def-env" => {
let (pipeline, err) =
parse_def(working_set, command, expand_aliases_denylist);
let (pipeline, err) = parse_def(
working_set,
command,
None, // using commands named as the module locally is OK
expand_aliases_denylist,
);
(pipeline, err)
}
b"extern" => {
let (pipeline, err) =
parse_extern(working_set, command, expand_aliases_denylist);
let (pipeline, err) = parse_extern(
working_set,
command,
None,
expand_aliases_denylist,
);
(pipeline, err)
}
b"alias" => {
let (pipeline, err) =
parse_alias(working_set, command, expand_aliases_denylist);
let (pipeline, err) = parse_alias(
working_set,
command,
None, // using aliases named as the module locally is OK
expand_aliases_denylist,
);
(pipeline, err)
}
@ -1413,6 +1527,7 @@ pub fn parse_module_block(
let (pipe, exportables, err) = parse_export_in_module(
working_set,
command,
module_name,
expand_aliases_denylist,
);
@ -1420,7 +1535,11 @@ pub fn parse_module_block(
for exportable in exportables {
match exportable {
Exportable::Decl { name, id } => {
module.add_decl(name, id);
if &name == b"main" {
module.main = Some(id);
} else {
module.add_decl(name, id);
}
}
Exportable::Alias { name, id } => {
module.add_alias(name, id);
@ -1519,8 +1638,12 @@ pub fn parse_module(
let block_span = Span::new(start, end);
let (block, module, inner_comments, err) =
parse_module_block(working_set, block_span, expand_aliases_denylist);
let (block, module, inner_comments, err) = parse_module_block(
working_set,
block_span,
module_name.as_bytes(),
expand_aliases_denylist,
);
error = error.or(err);
let block_id = working_set.add_block(block);
@ -1762,6 +1885,7 @@ pub fn parse_use(
let (block, module, module_comments, err) = parse_module_block(
working_set,
Span::new(span_start, span_end),
module_name.as_bytes(),
expand_aliases_denylist,
);
error = error.or(err);
@ -1801,14 +1925,16 @@ pub fn parse_use(
);
}
} else {
error = error.or(Some(ParseError::ModuleNotFound(import_pattern.head.span)));
let old_span = import_pattern.head.span;
let mut import_pattern = ImportPattern::new();
import_pattern.head.span = old_span;
(import_pattern, Module::new())
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: span(spans),
ty: Type::Any,
custom_completion: None,
}]),
vec![],
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
);
}
} else {
return (
@ -1831,12 +1957,18 @@ pub fn parse_use(
let mut decl_output = vec![];
let mut alias_output = vec![];
if let Some(id) = module.get_decl_id(name) {
if name == b"main" {
if let Some(id) = &module.main {
decl_output.push((import_pattern.head.name.clone(), *id));
} else {
error = error.or(Some(ParseError::ExportNotFound(*span)));
}
} else if let Some(id) = module.get_decl_id(name) {
decl_output.push((name.clone(), id));
} else if let Some(id) = module.get_alias_id(name) {
alias_output.push((name.clone(), id));
} else {
error = error.or(Some(ParseError::ExportNotFound(*span)))
error = error.or(Some(ParseError::ExportNotFound(*span)));
}
(decl_output, alias_output)
@ -1846,7 +1978,13 @@ pub fn parse_use(
let mut alias_output = vec![];
for (name, span) in names {
if let Some(id) = module.get_decl_id(name) {
if name == b"main" {
if let Some(id) = &module.main {
decl_output.push((import_pattern.head.name.clone(), *id));
} else {
error = error.or(Some(ParseError::ExportNotFound(*span)));
}
} else if let Some(id) = module.get_decl_id(name) {
decl_output.push((name.clone(), id));
} else if let Some(id) = module.get_alias_id(name) {
alias_output.push((name.clone(), id));
@ -1992,6 +2130,7 @@ pub fn parse_hide(
error = error.or(err);
}
// module used only internally, not saved anywhere
let (is_module, module) = if let Some(module_id) =
working_set.find_module(&import_pattern.head.name)
{
@ -2000,19 +2139,19 @@ pub fn parse_hide(
// The pattern head can be:
if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
// an alias,
let mut module = Module::new();
let mut module = Module::new(b"tmp".to_vec());
module.add_alias(import_pattern.head.name.clone(), id);
(false, module)
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) {
// a custom command,
let mut module = Module::new();
let mut module = Module::new(b"tmp".to_vec());
module.add_decl(import_pattern.head.name.clone(), id);
(false, module)
} else {
// , or it could be an env var (handled by the engine)
(false, Module::new())
(false, Module::new(b"tmp".to_vec()))
}
} else {
return (
@ -2038,7 +2177,14 @@ pub fn parse_hide(
let mut aliases = vec![];
let mut decls = vec![];
if let Some(item) = module.alias_name_with_head(name, &import_pattern.head.name)
if name == b"main" {
if module.main.is_some() {
decls.push(import_pattern.head.name.clone());
} else {
error = error.or(Some(ParseError::ExportNotFound(*span)));
}
} else if let Some(item) =
module.alias_name_with_head(name, &import_pattern.head.name)
{
aliases.push(item);
} else if let Some(item) =
@ -2056,7 +2202,14 @@ pub fn parse_hide(
let mut decls = vec![];
for (name, span) in names {
if let Some(item) =
if name == b"main" {
if module.main.is_some() {
decls.push(import_pattern.head.name.clone());
} else {
error = error.or(Some(ParseError::ExportNotFound(*span)));
break;
}
} else if let Some(item) =
module.alias_name_with_head(name, &import_pattern.head.name)
{
aliases.push(item);
@ -2346,7 +2499,11 @@ pub fn parse_overlay_new(
custom_completion: None,
}]);
let module_id = working_set.add_module(&overlay_name, Module::new(), vec![]);
let module_id = working_set.add_module(
&overlay_name,
Module::new(overlay_name.as_bytes().to_vec()),
vec![],
);
working_set.add_overlay(
overlay_name.as_bytes().to_vec(),
@ -2525,7 +2682,12 @@ pub fn parse_overlay_use(
if let Some(new_module_id) = working_set.find_module(overlay_name.as_bytes()) {
if !do_reload && (module_id == new_module_id) {
(overlay_name, Module::new(), module_id, false)
(
overlay_name,
Module::new(working_set.get_module(module_id).name.clone()),
module_id,
false,
)
} else {
// The origin module of an overlay changed => update it
(
@ -2536,7 +2698,8 @@ pub fn parse_overlay_use(
)
}
} else {
(overlay_name, Module::new(), module_id, true)
let module_name = overlay_name.as_bytes().to_vec();
(overlay_name, Module::new(module_name), module_id, true)
}
} else {
// Create a new overlay from a module
@ -2586,6 +2749,7 @@ pub fn parse_overlay_use(
let (block, module, module_comments, err) = parse_module_block(
working_set,
Span::new(span_start, span_end),
overlay_name.as_bytes(),
expand_aliases_denylist,
);
error = error.or(err);

View File

@ -5291,8 +5291,8 @@ pub fn parse_builtin_commands(
let name = working_set.get_span_contents(lite_command.parts[0]);
match name {
b"def" | b"def-env" => parse_def(working_set, lite_command, expand_aliases_denylist),
b"extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
b"def" | b"def-env" => parse_def(working_set, lite_command, None, expand_aliases_denylist),
b"extern" => parse_extern(working_set, lite_command, None, expand_aliases_denylist),
b"let" | b"const" => {
parse_let_or_const(working_set, &lite_command.parts, expand_aliases_denylist)
}
@ -5301,7 +5301,7 @@ pub fn parse_builtin_commands(
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
(Pipeline::from_vec(vec![expr]), err)
}
b"alias" => parse_alias(working_set, lite_command, expand_aliases_denylist),
b"alias" => parse_alias(working_set, lite_command, None, expand_aliases_denylist),
b"module" => parse_module(working_set, lite_command, expand_aliases_denylist),
b"use" => {
let (pipeline, _, err) =