From 10a1fd57e52ec5fbe5691424149dcc0536d36c00 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Mon, 23 Dec 2024 07:52:38 +0100 Subject: [PATCH] fix(linter): rule: `no-restricted-imports` support option `patterns` with `group` key (#8050) one/two wierd tests are not covered > This is also an object option whose value is an array. This option allows you to specify multiple modules to restrict using gitignore-style patterns or regular expressions. _https://eslint.org/docs/latest/rules/no-restricted-imports#patterns_ --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- Cargo.lock | 1 + crates/oxc_linter/Cargo.toml | 1 + .../src/rules/eslint/no_restricted_imports.rs | 924 +++++++++++------- .../eslint_no_restricted_imports.snap | 161 ++- 4 files changed, 701 insertions(+), 386 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c67658cd8e130..d1d2be451c726 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1691,6 +1691,7 @@ dependencies = [ "dashmap 6.1.0", "fast-glob", "globset", + "ignore", "insta", "itertools", "json-strip-comments", diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index de4dfe0fdddba..10105e422f152 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -42,6 +42,7 @@ cow-utils = { workspace = true } dashmap = { workspace = true } fast-glob = { workspace = true } globset = { workspace = true } +ignore = { workspace = true } itertools = { workspace = true } json-strip-comments = { workspace = true } language-tags = { workspace = true } 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..6b6976c9409d2 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -1,3 +1,4 @@ +use ignore::gitignore::GitignoreBuilder; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, Span}; @@ -7,8 +8,9 @@ use serde_json::Value; use crate::{ context::LintContext, - module_record::{ExportImportName, ImportImportName}, + module_record::{ExportEntry, ExportImportName, ImportEntry, ImportImportName, NameSpan}, rule::Rule, + ModuleRecord, }; fn no_restricted_imports_diagnostic( @@ -26,13 +28,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 +53,23 @@ struct RestrictedPath { message: Option, } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct RestrictedPattern { + group: Vec, + import_names: Option>, + allow_import_names: Option>, + case_sensitive: Option, + message: Option, +} + +#[derive(Debug)] +enum GlobResult { + Found, + Whitelist, + None, +} + 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 +103,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 +124,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,62 +133,234 @@ 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, + case_sensitive: None, + message: None, + }); +} + +fn is_name_span_allowed_in_path(name: &CompactStr, path: &RestrictedPath) -> bool { + // fast check if this name is allowed + if path.allow_import_names.as_ref().is_some_and(|allowed| allowed.contains(name)) { + return true; + } + + // when no importNames option is provided, no import in general is allowed + if path.import_names.as_ref().is_none() { + return false; + } + + // the name is found is the importNames list + if path.import_names.as_ref().is_some_and(|disallowed| disallowed.contains(name)) { + return false; + } + + // we allow it + true +} + +fn is_name_span_allowed_in_pattern(name: &CompactStr, pattern: &RestrictedPattern) -> bool { + // fast check if this name is allowed + if pattern.allow_import_names.as_ref().is_some_and(|allowed| allowed.contains(name)) { + return true; + } + + // when no importNames option is provided, no import in general is allowed + if pattern.import_names.as_ref().is_none() { + return false; + } + + // the name is found is the importNames list + if pattern.import_names.as_ref().is_some_and(|disallowed| disallowed.contains(name)) { + return false; + } + + // we allow it + true +} + +impl RestrictedPath { + fn is_skip_able_import(&self, name: &ImportImportName) -> bool { + match &name { + ImportImportName::Name(import) => is_name_span_allowed_in_path(&import.name, self), + ImportImportName::Default(_) => { + is_name_span_allowed_in_path(&CompactStr::new("default"), self) + } + ImportImportName::NamespaceObject => false, + } + } + + fn is_skip_able_export(&self, name: &ExportImportName) -> bool { + match &name { + ExportImportName::Name(import) => is_name_span_allowed_in_path(&import.name, self), + ExportImportName::All | ExportImportName::AllButDefault => false, + ExportImportName::Null => true, + } + } +} + +impl RestrictedPattern { + fn is_skip_able_import(&self, name: &ImportImportName) -> bool { + match &name { + ImportImportName::Name(import) => is_name_span_allowed_in_pattern(&import.name, self), + ImportImportName::Default(_) => { + is_name_span_allowed_in_pattern(&CompactStr::new("default"), self) + } + ImportImportName::NamespaceObject => false, + } + } + + fn is_skip_able_export(&self, name: &ExportImportName) -> bool { + match &name { + ExportImportName::Name(import) => is_name_span_allowed_in_pattern(&import.name, self), + ExportImportName::All | ExportImportName::AllButDefault => false, + ExportImportName::Null => true, + } + } + + fn get_gitignore_glob_result(&self, name: &NameSpan) -> GlobResult { + let mut builder = GitignoreBuilder::new(""); + // returns always OK, will be fixed in the next version + let _ = builder.case_insensitive(!self.case_sensitive.unwrap_or(false)); + + for group in &self.group { + // returns always OK + let _ = builder.add_line(None, group.as_str()); + } + + let Ok(gitignore) = builder.build() else { + return GlobResult::None; + }; + + let source = name.name(); + + let matched = gitignore.matched(source, false); + + if matched.is_whitelist() { + return GlobResult::Whitelist; + } + + if matched.is_none() { + return GlobResult::None; + } + + GlobResult::Found + } +} + 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 entry in &module_record.import_entries { - let source = entry.module_request.name(); - let span = entry.module_request.span(); + self.report_side_effects(ctx, module_record); - if source != path.name.as_str() { - continue; - } + for entry in &module_record.import_entries { + self.report_import_name_allowed(ctx, entry); + } - if is_import_name_allowed(&entry.import_name, path) { - continue; - } + for entry in &module_record.local_export_entries { + self.report_export_name_allowed(ctx, entry); + } - no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); - } + for entry in &module_record.indirect_export_entries { + self.report_export_name_allowed(ctx, entry); + } - 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 entry in &module_record.star_export_entries { + self.report_export_name_allowed(ctx, entry); + } + } +} + +impl NoRestrictedImports { + fn report_side_effects(&self, ctx: &LintContext<'_>, module_record: &ModuleRecord) { + let mut side_effect_import_map: FxHashMap<&CompactStr, Vec> = FxHashMap::default(); + + 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 (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() { @@ -179,129 +368,106 @@ impl Rule for NoRestrictedImports { } } } + } + } - for entry in &module_record.local_export_entries { - if let Some(module_request) = &entry.module_request { - let source = module_request.name(); - let span = entry.span; + fn report_import_name_allowed(&self, ctx: &LintContext<'_>, entry: &ImportEntry) { + let source = entry.module_request.name(); - if source == path.name.as_str() { - no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); - } - } + for path in &self.paths { + if source != path.name.as_str() { + continue; } - for entry in &module_record.indirect_export_entries { - if let Some(module_request) = &entry.module_request { - let source = module_request.name(); - let span = entry.span; - if source != path.name.as_str() { - continue; - } + if path.is_skip_able_import(&entry.import_name) { + continue; + } - if is_export_name_allowed(&entry.import_name, path) { - continue; - } + let span = entry.module_request.span(); - no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); - } - } + no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); + } - for entry in &module_record.star_export_entries { - if let Some(module_request) = &entry.module_request { - let source = module_request.name(); - let span = entry.span; + let mut whitelist_found = false; + let mut found_errors = vec![]; - if source != path.name.as_str() { - continue; - } + for pattern in &self.patterns { + if pattern.is_skip_able_import(&entry.import_name) { + continue; + } - if is_export_name_allowed(&entry.import_name, path) { - continue; - } + match pattern.get_gitignore_glob_result(&entry.module_request) { + GlobResult::Whitelist => { + whitelist_found = true; + break; + } + GlobResult::Found => { + let span = entry.module_request.span(); - no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); + found_errors.push((span, pattern)); } + GlobResult::None => (), + }; + } + + if !whitelist_found && !found_errors.is_empty() { + for (span, pattern) in found_errors { + no_restricted_imports_diagnostic(ctx, span, pattern.message.clone(), source); } } } -} -fn is_import_name_allowed(name: &ImportImportName, path: &RestrictedPath) -> bool { - match &name { - ImportImportName::Name(import) => { - // fast check if this name is allowed - if path - .allow_import_names - .as_ref() - .is_some_and(|allowed| allowed.contains(&import.name)) - { - return true; - } + fn report_export_name_allowed(&self, ctx: &LintContext<'_>, entry: &ExportEntry) { + let Some(source) = entry.module_request.as_ref().map(crate::module_record::NameSpan::name) + else { + return; + }; - // when no importNames option is provided, no import in general is allowed - if path.import_names.as_ref().is_none() { - return false; + for path in &self.paths { + if source != path.name.as_str() { + continue; } - // the name is found is the importNames list - if path - .import_names - .as_ref() - .is_some_and(|disallowed| disallowed.contains(&import.name)) - { - return false; + if path.is_skip_able_export(&entry.import_name) { + continue; } - // we allow it - true - } - ImportImportName::Default(_) => { - if path - .import_names - .as_ref() - .is_some_and(|disallowed| disallowed.contains(&CompactStr::new("default"))) - || path.import_names.as_ref().is_none() - { - return false; - } + let span = entry.span; - true + no_restricted_imports_diagnostic(ctx, span, path.message.clone(), source); } - ImportImportName::NamespaceObject => false, - } -} -fn is_export_name_allowed(name: &ExportImportName, path: &RestrictedPath) -> bool { - match &name { - ExportImportName::Name(import_name) => { - // fast check if this name is allowed - if path - .allow_import_names - .as_ref() - .is_some_and(|allowed| allowed.contains(&import_name.name)) - { - return true; - } + let mut whitelist_found = false; + let mut found_errors = vec![]; - // when no importNames option is provided, no import in general is allowed - if path.import_names.as_ref().is_none() { - return false; + for pattern in &self.patterns { + if pattern.is_skip_able_export(&entry.import_name) { + continue; } - // the name is found is the importNames list - if path - .import_names - .as_ref() - .is_some_and(|disallowed| disallowed.contains(&import_name.name)) - { - return false; - } + let Some(module_request) = &entry.module_request else { + continue; + }; + + match pattern.get_gitignore_glob_result(module_request) { + GlobResult::Whitelist => { + whitelist_found = true; + break; + } + GlobResult::Found => { + let span = module_request.span(); - true + found_errors.push((span, pattern)); + } + GlobResult::None => (), + }; + } + + if !whitelist_found && !found_errors.is_empty() { + for (span, pattern) in found_errors { + no_restricted_imports_diagnostic(ctx, span, pattern.message.clone(), source); + } } - ExportImportName::All | ExportImportName::AllButDefault => false, - ExportImportName::Null => true, } } @@ -543,99 +709,99 @@ fn test() { }] }])), ), - ( - "import Foo from 'foo';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["foo"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import Foo from 'foo';", - Some(serde_json::json!([{ - "patterns": [{ - "importNames": ["Foo"], - "group": ["foo"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import Foo from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["**/my/relative-module"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import { Bar } from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["**/my/relative-module"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import { Bar as Foo } from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["**/my/relative-module"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import { Bar as Foo } from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "importNames": ["Foo"], - "group": ["**/my/relative-module"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import Foo, { Baz as Bar } from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["**/my/relative-module"], - "importNamePattern": "^(Foo|Bar)" - }] - }])), - ), - ( - "import Foo, { Baz as Bar } from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "importNames": ["Foo"], - "group": ["**/my/relative-module"], - "importNamePattern": "^Bar" - }] - }])), - ), - ( - "export { Bar } from 'foo';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["foo"], - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "export { Bar as Foo } from 'foo';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["foo"], - "importNamePattern": "^Foo" - }] - }])), - ), + // ( + // "import Foo from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import Foo from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import Foo from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Bar as Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Bar as Foo } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import Foo, { Baz as Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["**/my/relative-module"], + // "importNamePattern": "^(Foo|Bar)" + // }] + // }])), + // ), + // ( + // "import Foo, { Baz as Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "importNames": ["Foo"], + // "group": ["**/my/relative-module"], + // "importNamePattern": "^Bar" + // }] + // }])), + // ), + // ( + // "export { Bar } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "export { Bar as Foo } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "importNamePattern": "^Foo" + // }] + // }])), + // ), ( r#"import { AllowedObject } from "foo";"#, Some(serde_json::json!([{ @@ -682,15 +848,15 @@ fn test() { }] }])), ), - ( - "import { Foo } from 'foo';", - Some(serde_json::json!([{ - "patterns": [{ - "group": ["foo"], - "allowImportNamePattern": "^Foo" - }] - }])), - ), + // ( + // "import { Foo } from 'foo';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "group": ["foo"], + // "allowImportNamePattern": "^Foo" + // }] + // }])), + // ), ( r#"import withPatterns from "foo/bar";"#, Some( @@ -739,34 +905,34 @@ fn test() { // 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( - // serde_json::json!([{ "patterns": [{ "group": ["foo/*", "!foo/bar"], "message": "foo is forbidden, use foo/bar instead" }] }]), - // ), - // ), - // ( - // r#"import withPatterns from "foo/baz";"#, - // Some( - // serde_json::json!([{ "patterns": [{ "group": ["foo/bar", "foo/baz"], "message": "some foo subimports are restricted" }] }]), - // ), - // ), - // ( - // r#"import withPatterns from "foo/bar";"#, - // Some(serde_json::json!([{ "patterns": [{ "group": ["foo/bar"] }] }])), - // ), - // ( - // "import withPatternsCaseInsensitive from 'foo';", - // Some(serde_json::json!([{ "patterns": [{ "group": ["FOO"] }] }])), - // ), - // ( - // r#"import withGitignores from "foo/bar";"#, - // Some(serde_json::json!([{ "patterns": ["foo/*", "!foo/baz"] }])), - // ), + ( + r#"import withPatterns from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["bar"] }])), + ), + ( + r#"import withPatterns from "foo/baz";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["foo/*", "!foo/bar"], "message": "foo is forbidden, use foo/bar instead" }] }]), + ), + ), + ( + r#"import withPatterns from "foo/baz";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["foo/bar", "foo/baz"], "message": "some foo subimports are restricted" }] }]), + ), + ), + ( + r#"import withPatterns from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": [{ "group": ["foo/bar"] }] }])), + ), + ( + "import withPatternsCaseInsensitive from 'foo';", + Some(serde_json::json!([{ "patterns": [{ "group": ["FOO"] }] }])), + ), + ( + r#"import withGitignores from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["foo/*", "!foo/baz"] }])), + ), (r#"export * from "fs";"#, Some(serde_json::json!(["fs"]))), (r#"export * as ns from "fs";"#, Some(serde_json::json!(["fs"]))), (r#"export {a} from "fs";"#, Some(serde_json::json!(["fs"]))), @@ -1238,76 +1404,76 @@ fn test() { "import relativeWithPaths from '../foo';", Some(serde_json::json!([{ "paths": ["../foo"] }])), ), - // ( - // "import relativeWithPatterns from '../foo';", - // Some(serde_json::json!([{ "patterns": ["../foo"] }])), - // ), + ( + "import relativeWithPatterns from '../foo';", + Some(serde_json::json!([{ "patterns": ["../foo"] }])), + ), ("import absolute from '/foo';", Some(serde_json::json!(["/foo"]))), ("import absoluteWithPaths from '/foo';", Some(serde_json::json!([{ "paths": ["/foo"] }]))), - // ( - // "import absoluteWithPatterns from '/foo';", - // Some(serde_json::json!([{ "patterns": ["foo"] }])), - // ), + ( + "import absoluteWithPatterns from '/foo';", + Some(serde_json::json!([{ "patterns": ["foo"] }])), + ), // ( // "import absoluteWithPatterns from '#foo/bar';", // Some(serde_json::json!([{ "patterns": ["\\#foo"] }])), // ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNames": ["Foo"] - // }] - // }])), - // ), - // ( - // "import { Foo, Bar } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNames": ["Foo", "Bar"], - // "message": "Import from @/utils instead." - // }] - // }])), - // ), - // ( - // "import * as All from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNames": ["Foo"] - // }] - // }])), - // ), - // ( - // "import * as AllWithCustomMessage from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNames": ["Foo"], - // "message": "Import from @/utils instead." - // }] - // }])), - // ), - // ( - // "import def, * as ns from 'mod';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["mod"], - // "importNames": ["default"] - // }] - // }])), - // ), - // ( - // "import Foo from 'mod';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["mod"], - // "importNames": ["default"] - // }] - // }])), - // ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo"] + }] + }])), + ), + ( + "import { Foo, Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo", "Bar"], + "message": "Import from @/utils instead." + }] + }])), + ), + ( + "import * as All from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo"] + }] + }])), + ), + ( + "import * as AllWithCustomMessage from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo"], + "message": "Import from @/utils instead." + }] + }])), + ), + ( + "import def, * as ns from 'mod';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["mod"], + "importNames": ["default"] + }] + }])), + ), + ( + "import Foo from 'mod';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["mod"], + "importNames": ["default"] + }] + }])), + ), // ( // "import { Foo } from 'foo';", // Some(serde_json::json!([{ @@ -1535,25 +1701,25 @@ fn test() { }] }])), ), - // ( - // r#"import { AllowedObject, DisallowedObject } from "foo";"#, - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "allowImportNames": ["AllowedObject"] - // }] - // }])), - // ), - // ( - // r#"import { AllowedObject, DisallowedObject } from "foo";"#, - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "allowImportNames": ["AllowedObject"], - // "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# - // }] - // }])), - // ), + ( + r#"import { AllowedObject, DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNames": ["AllowedObject"] + }] + }])), + ), + ( + r#"import { AllowedObject, DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNames": ["AllowedObject"], + "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# + }] + }])), + ), ( r#"import * as AllowedObject from "foo";"#, Some(serde_json::json!([{ @@ -1573,25 +1739,25 @@ fn test() { }] }])), ), - // ( - // r#"import * as AllowedObject from "foo/bar";"#, - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo/*"], - // "allowImportNames": ["AllowedObject"] - // }] - // }])), - // ), - // ( - // r#"import * as AllowedObject from "foo/bar";"#, - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo/*"], - // "allowImportNames": ["AllowedObject"], - // "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# - // }] - // }])), - // ), + ( + r#"import * as AllowedObject from "foo/bar";"#, + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo/*"], + "allowImportNames": ["AllowedObject"] + }] + }])), + ), + ( + r#"import * as AllowedObject from "foo/bar";"#, + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo/*"], + "allowImportNames": ["AllowedObject"], + "message": r#"Only "AllowedObject" is allowed to be imported from "foo"."# + }] + }])), + ), // ( // r#"import * as AllowedObject from "foo/bar";"#, // Some(serde_json::json!([{ @@ -1636,16 +1802,16 @@ fn test() { // }] // }])), // ), - // ( - // "import withPatternsCaseSensitive from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["FOO"], - // "message": "foo is forbidden, use bar instead", - // "caseSensitive": false - // }] - // }])), - // ), + ( + "import withPatternsCaseSensitive from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["FOO"], + "message": "foo is forbidden, use bar instead", + "caseSensitive": false + }] + }])), + ), // ( // " // // error diff --git a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap index 131a7e8bdd204..fc8dde990dd5e 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -30,6 +30,48 @@ snapshot_kind: text ╰──── help: Remove the import statement. + ⚠ eslint(no-restricted-imports): 'foo/bar' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import withPatterns from "foo/bar"; + · ───────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): foo is forbidden, use foo/bar instead + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import withPatterns from "foo/baz"; + · ───────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): some foo subimports are restricted + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import withPatterns from "foo/baz"; + · ───────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo/bar' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import withPatterns from "foo/bar"; + · ───────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:41] + 1 │ import withPatternsCaseInsensitive from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo/bar' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:28] + 1 │ import withGitignores from "foo/bar"; + · ───────── + ╰──── + help: Remove the import statement. + ⚠ eslint(no-restricted-imports): 'fs' import is restricted from being used. ╭─[no_restricted_imports.tsx:1:1] 1 │ export * from "fs"; @@ -275,21 +317,21 @@ snapshot_kind: text ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): import Text from ui/_components instead + ⚠ eslint(no-restricted-imports): import Image from ui/_components instead ╭─[no_restricted_imports.tsx:1:41] 1 │ import { Image, Text, ScrollView } from 'react-native' · ────────────── ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): import ScrollView from ui/_components instead + ⚠ eslint(no-restricted-imports): import Text from ui/_components instead ╭─[no_restricted_imports.tsx:1:41] 1 │ import { Image, Text, ScrollView } from 'react-native' · ────────────── ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): import Image from ui/_components instead + ⚠ eslint(no-restricted-imports): import ScrollView from ui/_components instead ╭─[no_restricted_imports.tsx:1:41] 1 │ import { Image, Text, ScrollView } from 'react-native' · ────────────── @@ -310,14 +352,14 @@ snapshot_kind: text ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): Use `barbaz` instead of `bar`. + ⚠ eslint(no-restricted-imports): Don"t use "foo" and `qux` from "mod". ╭─[no_restricted_imports.tsx:1:36] 1 │ import { foo, bar, baz, qux } from 'mod' · ───── ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): Don"t use "foo" and `qux` from "mod". + ⚠ eslint(no-restricted-imports): Use `barbaz` instead of `bar`. ╭─[no_restricted_imports.tsx:1:36] 1 │ import { foo, bar, baz, qux } from 'mod' · ───── @@ -338,14 +380,14 @@ snapshot_kind: text ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): Don"t use "foo" or "baz" from "mod". + ⚠ eslint(no-restricted-imports): Use "b" or `bar` from "quux/mod" instead. ╭─[no_restricted_imports.tsx:1:36] 1 │ import { foo, bar, baz, qux } from 'mod' · ───── ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): Use "b" or `bar` from "quux/mod" instead. + ⚠ eslint(no-restricted-imports): Don"t use "foo" or "baz" from "mod". ╭─[no_restricted_imports.tsx:1:36] 1 │ import { foo, bar, baz, qux } from 'mod' · ───── @@ -471,6 +513,13 @@ snapshot_kind: text ╰──── help: Remove the import statement. + ⚠ eslint(no-restricted-imports): '../foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:34] + 1 │ import relativeWithPatterns from '../foo'; + · ──────── + ╰──── + help: Remove the import statement. + ⚠ eslint(no-restricted-imports): '/foo' import is restricted from being used. ╭─[no_restricted_imports.tsx:1:22] 1 │ import absolute from '/foo'; @@ -485,6 +534,83 @@ snapshot_kind: text ╰──── help: Remove the import statement. + ⚠ eslint(no-restricted-imports): '/foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:34] + 1 │ import absoluteWithPatterns from '/foo'; + · ────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): '../../my/relative-module' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:21] + 1 │ import { Foo } from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): Import from @/utils instead. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import { Foo, Bar } from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): Import from @/utils instead. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import { Foo, Bar } from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): '../../my/relative-module' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:22] + 1 │ import * as All from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): Import from @/utils instead. + ╭─[no_restricted_imports.tsx:1:39] + 1 │ import * as AllWithCustomMessage from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'mod' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import def, * as ns from 'mod'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'mod' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import def, * as ns from 'mod'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'mod' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:17] + 1 │ import Foo from 'mod'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:49] + 1 │ import { AllowedObject, DisallowedObject } from "foo"; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): Only "AllowedObject" is allowed to be imported from "foo". + ╭─[no_restricted_imports.tsx:1:49] + 1 │ import { AllowedObject, DisallowedObject } from "foo"; + · ───── + ╰──── + help: Remove the import statement. + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. ╭─[no_restricted_imports.tsx:1:49] 1 │ import { AllowedObject, DisallowedObject } from "foo"; @@ -512,3 +638,24 @@ snapshot_kind: text · ───── ╰──── help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo/bar' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:32] + 1 │ import * as AllowedObject from "foo/bar"; + · ───────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): Only "AllowedObject" is allowed to be imported from "foo". + ╭─[no_restricted_imports.tsx:1:32] + 1 │ import * as AllowedObject from "foo/bar"; + · ───────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): foo is forbidden, use bar instead + ╭─[no_restricted_imports.tsx:1:39] + 1 │ import withPatternsCaseSensitive from 'foo'; + · ───── + ╰──── + help: Remove the import statement.