From 32f905087099abbfec5cbd24bce84001ffe4997b Mon Sep 17 00:00:00 2001 From: Sysix Date: Fri, 20 Dec 2024 16:20:13 +0100 Subject: [PATCH] fix(linter): rule: `no-restricted-imports` support option `patterns` with `group` key --- .../src/rules/eslint/no_restricted_imports.rs | 141 +++++++++++++----- 1 file changed, 101 insertions(+), 40 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs index 4b0c1ffe94b9b..49bd0a46dd5ae 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -26,13 +26,20 @@ fn no_restricted_imports_diagnostic( } #[derive(Debug, Default, Clone)] -pub struct NoRestrictedImports { - paths: Box, +pub struct NoRestrictedImports(Box); + +impl std::ops::Deref for NoRestrictedImports { + type Target = NoRestrictedImportsConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } } #[derive(Debug, Default, Clone)] -struct NoRestrictedImportsConfig { - paths: Box<[RestrictedPath]>, +pub struct NoRestrictedImportsConfig { + paths: Vec, + patterns: Vec, } #[derive(Debug, Clone, Deserialize)] @@ -44,6 +51,15 @@ struct RestrictedPath { message: Option, } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct RestrictedPattern { + group: Vec, + import_names: Option>, + allow_import_names: Option>, + message: Option, +} + declare_oxc_lint!( /// ### What it does /// This rule allows you to specify imports that you don’t want to use in your application. @@ -77,26 +93,17 @@ declare_oxc_lint!( nursery, ); -fn add_configuration_from_object( +fn add_configuration_path_from_object( paths: &mut Vec, - obj: &serde_json::Map, + paths_value: &serde_json::Value, ) { - let Some(paths_value) = obj.get("paths") else { - if let Ok(path) = - serde_json::from_value::(serde_json::Value::Object(obj.clone())) - { - paths.push(path); - } - return; - }; - let Some(paths_array) = paths_value.as_array() else { return; }; for path_value in paths_array { match path_value { - Value::String(module_name) => add_configuration_from_string(paths, module_name), + Value::String(module_name) => add_configuration_path_from_string(paths, module_name), Value::Object(_) => { if let Ok(path) = serde_json::from_value::(path_value.clone()) { paths.push(path); @@ -107,7 +114,7 @@ fn add_configuration_from_object( } } -fn add_configuration_from_string(paths: &mut Vec, module_name: &str) { +fn add_configuration_path_from_string(paths: &mut Vec, module_name: &str) { paths.push(RestrictedPath { name: CompactStr::new(module_name), import_names: None, @@ -116,39 +123,101 @@ fn add_configuration_from_string(paths: &mut Vec, module_name: & }); } +fn add_configuration_patterns_from_object( + patterns: &mut Vec, + patterns_value: &serde_json::Value, +) { + let Some(paths_array) = patterns_value.as_array() else { + return; + }; + + for path_value in paths_array { + match path_value { + Value::String(module_name) => { + add_configuration_patterns_from_string(patterns, module_name); + } + Value::Object(_) => { + if let Ok(path) = serde_json::from_value::(path_value.clone()) { + patterns.push(path); + } + } + _ => (), + } + } +} + +fn add_configuration_patterns_from_string(paths: &mut Vec, module_name: &str) { + paths.push(RestrictedPattern { + group: vec![CompactStr::new(module_name)], + import_names: None, + allow_import_names: None, + message: None, + }); +} + impl Rule for NoRestrictedImports { fn from_configuration(value: serde_json::Value) -> Self { - let mut paths = Vec::new(); + let mut paths: Vec = Vec::new(); + let mut patterns: Vec = Vec::new(); match &value { Value::Array(module_names) => { for module_name in module_names { match module_name { Value::String(module_string) => { - add_configuration_from_string(&mut paths, module_string); + add_configuration_path_from_string(&mut paths, module_string); + } + Value::Object(obj) => { + if let Some(paths_value) = obj.get("paths") { + add_configuration_path_from_object(&mut paths, paths_value); + } else if let Some(patterns_value) = obj.get("patterns") { + add_configuration_patterns_from_object( + &mut patterns, + patterns_value, + ); + } else if let Ok(path) = serde_json::from_value::( + serde_json::Value::Object(obj.clone()), + ) { + paths.push(path); + }; } - Value::Object(obj) => add_configuration_from_object(&mut paths, obj), _ => (), }; } } Value::String(module_name) => { - add_configuration_from_string(&mut paths, module_name); + add_configuration_path_from_string(&mut paths, module_name); } Value::Object(obj) => { - add_configuration_from_object(&mut paths, obj); + if let Some(paths_value) = obj.get("paths") { + add_configuration_path_from_object(&mut paths, paths_value); + } else if let Some(patterns_value) = obj.get("patterns") { + add_configuration_patterns_from_object(&mut patterns, patterns_value); + } else if let Ok(path) = + serde_json::from_value::(serde_json::Value::Object(obj.clone())) + { + paths.push(path); + } } _ => {} } - Self { paths: Box::new(NoRestrictedImportsConfig { paths: paths.into_boxed_slice() }) } + Self(Box::new(NoRestrictedImportsConfig { paths, patterns })) } fn run_once(&self, ctx: &LintContext<'_>) { let module_record = ctx.module_record(); let mut side_effect_import_map: FxHashMap<&CompactStr, Vec> = FxHashMap::default(); - for path in &self.paths.paths { + for (source, requests) in &module_record.requested_modules { + for request in requests { + if request.is_import && module_record.import_entries.is_empty() { + side_effect_import_map.entry(source).or_default().push(request.span); + } + } + } + + for path in &self.paths { for entry in &module_record.import_entries { let source = entry.module_request.name(); let span = entry.module_request.span(); @@ -164,14 +233,6 @@ impl Rule for NoRestrictedImports { no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); } - for (source, requests) in &module_record.requested_modules { - for request in requests { - if request.is_import && module_record.import_entries.is_empty() { - side_effect_import_map.entry(source).or_default().push(request.span); - } - } - } - for (source, spans) in &side_effect_import_map { if source.as_str() == path.name.as_str() && path.import_names.is_none() { if let Some(span) = spans.iter().next() { @@ -735,14 +796,14 @@ fn test() { r#"import withPaths from "foo/bar";"#, Some(serde_json::json!([{ "paths": ["foo/bar"] }])), ), - // ( - // r#"import withPatterns from "foo/bar";"#, - // Some(serde_json::json!([{ "patterns": ["foo"] }])), - // ), - // ( - // r#"import withPatterns from "foo/bar";"#, - // Some(serde_json::json!([{ "patterns": ["bar"] }])), - // ), + ( + r#"import withPatterns from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["foo"] }])), + ), + ( + r#"import withPatterns from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["bar"] }])), + ), // ( // r#"import withPatterns from "foo/baz";"#, // Some(