diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 033a21e738..d093cd88ce 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -600,12 +600,90 @@ pub fn parse_hide( let (name_expr, err) = parse_string(working_set, spans[1]); error = error.or(err); - let name_bytes: Vec = working_set.get_span_contents(spans[1]).into(); + let (import_pattern, err) = parse_import_pattern(working_set, spans[1]); + error = error.or(err); - // TODO: Do the import pattern stuff for bulk-hiding + let exported_names: Vec> = + if let Some(block_id) = working_set.find_module(&import_pattern.head) { + working_set + .get_block(block_id) + .exports + .iter() + .map(|(name, _)| name.clone()) + .collect() + } else if import_pattern.members.is_empty() { + // The pattern head can be e.g. a function name, not just a module + vec![import_pattern.head.clone()] + } else { + return ( + garbage_statement(spans), + Some(ParseError::ModuleNotFound(spans[1])), + ); + }; - if working_set.hide_decl(&name_bytes).is_none() { - error = error.or_else(|| Some(ParseError::UnknownCommand(spans[1]))); + // This kind of inverts the import pattern matching found in parse_use() + let names_to_hide = if import_pattern.members.is_empty() { + exported_names + } else { + match &import_pattern.members[0] { + ImportPatternMember::Glob { .. } => exported_names + .into_iter() + .map(|name| { + let mut new_name = import_pattern.head.to_vec(); + new_name.push(b'.'); + new_name.extend(&name); + new_name + }) + .collect(), + ImportPatternMember::Name { name, span } => { + let new_exports: Vec> = exported_names + .into_iter() + .filter(|n| n == name) + .map(|n| { + let mut new_name = import_pattern.head.to_vec(); + new_name.push(b'.'); + new_name.extend(&n); + new_name + }) + .collect(); + + if new_exports.is_empty() { + error = error.or(Some(ParseError::ExportNotFound(*span))) + } + + new_exports + } + ImportPatternMember::List { names } => { + let mut output = vec![]; + + for (name, span) in names { + let mut new_exports: Vec> = exported_names + .iter() + .filter_map(|n| if n == name { Some(n.clone()) } else { None }) + .map(|n| { + let mut new_name = import_pattern.head.to_vec(); + new_name.push(b'.'); + new_name.extend(n); + new_name + }) + .collect(); + + if new_exports.is_empty() { + error = error.or(Some(ParseError::ExportNotFound(*span))) + } else { + output.append(&mut new_exports) + } + } + + output + } + } + }; + + for name in names_to_hide { + if working_set.hide_decl(&name).is_none() { + error = error.or_else(|| Some(ParseError::UnknownCommand(spans[1]))); + } } // Create the Hide command call diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index bbe6bdb1c1..c5cd679860 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -375,7 +375,7 @@ impl<'a> StateWorkingSet<'a> { if let Some(decl_id) = scope.decls.get(name) { if !hiding.contains(decl_id) { - // Do not hide already hidden decl + // Hide decl only if it's not already hidden last_scope_frame.hiding.insert(*decl_id); return Some(*decl_id); } @@ -409,8 +409,6 @@ impl<'a> StateWorkingSet<'a> { } pub fn activate_overlay(&mut self, overlay: Vec<(Vec, DeclId)>) { - // TODO: This will overwrite all existing definitions in a scope. When we add deactivate, - // we need to re-think how make it recoverable. let scope_frame = self .delta .scope diff --git a/src/tests.rs b/src/tests.rs index 7416aecb6f..0efd0b513a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -441,6 +441,46 @@ fn hide_twice_not_allowed() -> TestResult { ) } +#[test] +fn hides_import_1() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.foo; foo"#, + "not found", + ) +} + +#[test] +fn hides_import_2() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.*; foo"#, + "not found", + ) +} + +#[test] +fn hides_import_3() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam.[foo]; foo"#, + "not found", + ) +} + +#[test] +fn hides_import_4() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam.foo; hide foo; foo"#, + "not found", + ) +} + +#[test] +fn hides_import_5() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam.*; hide foo; foo"#, + "not found", + ) +} + #[test] fn def_twice_should_fail() -> TestResult { fail_test( @@ -449,6 +489,15 @@ fn def_twice_should_fail() -> TestResult { ) } +// TODO: This test fails if executed each command on a separate line in REPL +#[test] +fn use_import_after_hide() -> TestResult { + run_test( + r#"module spam { export def foo [] { "foo" } }; use spam.foo; hide foo; use spam.foo; foo"#, + "foo", + ) +} + #[test] fn from_json_1() -> TestResult { run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred")