From acbff7bb2f84729086076db88b2fae217d5174be Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 23 Dec 2024 16:03:31 +0100 Subject: [PATCH 1/7] fix(linter): rule `no-restricted-imports` support `regex` option inside `patterns` --- Cargo.lock | 13 +- Cargo.toml | 1 + crates/oxc_linter/Cargo.toml | 1 + .../src/rules/eslint/no_restricted_imports.rs | 121 ++++++++++++------ .../eslint_no_restricted_imports.snap | 14 ++ 5 files changed, 109 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1d2be451c726..2967079cd8634 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,7 +1082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1719,6 +1719,7 @@ dependencies = [ "project-root", "rayon", "regex", + "regress", "rust-lapper", "rustc-hash", "schemars", @@ -2406,6 +2407,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "regress" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1541daf4e4ed43a0922b7969bdc2170178bcacc5dabf7e39bc508a9fa3953a7a" +dependencies = [ + "hashbrown 0.14.5", + "memchr", +] + [[package]] name = "ring" version = "0.17.8" diff --git a/Cargo.toml b/Cargo.toml index 8c41dff011293..52281a38f0542 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,6 +178,7 @@ prettyplease = "0.2.25" project-root = "0.2.2" rayon = "1.10.0" regex = "1.11.1" +regress = "0.10.1" ropey = "1.6.1" rust-lapper = "1.1.0" ryu-js = "1.0.1" diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 10105e422f152..9d07acc56393c 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -53,6 +53,7 @@ nonmax = { workspace = true } phf = { workspace = true, features = ["macros"] } rayon = { workspace = true } regex = { workspace = true } +regress = { workspace = true } rust-lapper = { workspace = true } rustc-hash = { workspace = true } schemars = { workspace = true, features = ["indexmap2"] } 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 6b6976c9409d2..1d24caf5eb18b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -2,6 +2,7 @@ use ignore::gitignore::GitignoreBuilder; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, Span}; +use regress::Regex; use rustc_hash::FxHashMap; use serde::Deserialize; use serde_json::Value; @@ -56,7 +57,8 @@ struct RestrictedPath { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] struct RestrictedPattern { - group: Vec, + group: Option>, + regex: Option, import_names: Option>, allow_import_names: Option>, case_sensitive: Option, @@ -148,6 +150,9 @@ fn add_configuration_patterns_from_object( } Value::Object(_) => { if let Ok(path) = serde_json::from_value::(path_value.clone()) { + if path.group.is_some() && path.regex.is_some() { + // ToDo: not allowed + } patterns.push(path); } } @@ -158,7 +163,8 @@ fn add_configuration_patterns_from_object( fn add_configuration_patterns_from_string(paths: &mut Vec, module_name: &str) { paths.push(RestrictedPattern { - group: vec![CompactStr::new(module_name)], + group: Some(vec![CompactStr::new(module_name)]), + regex: None, import_names: None, allow_import_names: None, case_sensitive: None, @@ -245,12 +251,16 @@ impl RestrictedPattern { } } - fn get_gitignore_glob_result(&self, name: &NameSpan) -> GlobResult { + fn get_group_glob_result(&self, name: &NameSpan) -> GlobResult { + let Some(groups) = &self.group else { + return GlobResult::None; + }; + 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 { + for group in groups { // returns always OK let _ = builder.add_line(None, group.as_str()); } @@ -273,6 +283,25 @@ impl RestrictedPattern { GlobResult::Found } + + fn get_regex_result(&self, name: &NameSpan) -> bool { + let Some(regex) = &self.regex else { + return false; + }; + + let flags = match self.case_sensitive { + Some(case_sensitive) if case_sensitive => "u", + _ => "iu", + }; + + let reg_string = format!("{}", regex.as_str()); + + let Ok(reg_exp) = Regex::with_flags(®_string, flags) else { + return false; + }; + + reg_exp.find(name.name()).is_some() + } } impl Rule for NoRestrictedImports { @@ -396,7 +425,7 @@ impl NoRestrictedImports { continue; } - match pattern.get_gitignore_glob_result(&entry.module_request) { + match pattern.get_group_glob_result(&entry.module_request) { GlobResult::Whitelist => { whitelist_found = true; break; @@ -408,6 +437,12 @@ impl NoRestrictedImports { } GlobResult::None => (), }; + + if pattern.get_regex_result(&entry.module_request) { + let span = entry.module_request.span(); + + no_restricted_imports_diagnostic(ctx, span, pattern.message.clone(), source); + } } if !whitelist_found && !found_errors.is_empty() { @@ -449,7 +484,7 @@ impl NoRestrictedImports { continue; }; - match pattern.get_gitignore_glob_result(module_request) { + match pattern.get_group_glob_result(module_request) { GlobResult::Whitelist => { whitelist_found = true; break; @@ -461,6 +496,12 @@ impl NoRestrictedImports { } GlobResult::None => (), }; + + if pattern.get_regex_result(&module_request) { + let span = module_request.span(); + + no_restricted_imports_diagnostic(ctx, span, pattern.message.clone(), source); + } } if !whitelist_found && !found_errors.is_empty() { @@ -873,24 +914,24 @@ fn test() { }] }])), ), - ( - "import Foo from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "regex": "my/relative-module", - "importNamePattern": "^Foo" - }] - }])), - ), - ( - "import { Bar } from '../../my/relative-module';", - Some(serde_json::json!([{ - "patterns": [{ - "regex": "my/relative-module", - "importNamePattern": "^Foo" - }] - }])), - ), + // ( + // "import Foo from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "regex": "my/relative-module", + // "importNamePattern": "^Foo" + // }] + // }])), + // ), + // ( + // "import { Bar } from '../../my/relative-module';", + // Some(serde_json::json!([{ + // "patterns": [{ + // "regex": "my/relative-module", + // "importNamePattern": "^Foo" + // }] + // }])), + // ), ]; let fail = vec![ @@ -1777,22 +1818,22 @@ fn test() { // }] // }])), // ), - // ( - // r#"import withPatterns from "foo/baz";"#, - // Some( - // serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), - // ), - // ), - // ( - // "import withPatternsCaseSensitive from 'FOO';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "regex": "FOO", - // "message": "foo is forbidden, use bar instead", - // "caseSensitive": true - // }] - // }])), - // ), + ( + r#"import withPatterns from "foo/baz";"#, + Some( + serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), + ), + ), + ( + "import withPatternsCaseSensitive from 'FOO';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "FOO", + "message": "foo is forbidden, use bar instead", + "caseSensitive": true + }] + }])), + ), // ( // "import { Foo } from '../../my/relative-module';", // Some(serde_json::json!([{ 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 fc8dde990dd5e..c774cb6d0fd8d 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -653,6 +653,20 @@ snapshot_kind: text ╰──── help: Remove the import statement. + ⚠ eslint(no-restricted-imports): foo is forbidden, use bar instead + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import withPatterns from "foo/baz"; + · ───────── + ╰──── + 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. + ⚠ eslint(no-restricted-imports): foo is forbidden, use bar instead ╭─[no_restricted_imports.tsx:1:39] 1 │ import withPatternsCaseSensitive from 'foo'; From a955782b826759a30f4fd55ebc5dc3dc6fc87575 Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 23 Dec 2024 16:07:35 +0100 Subject: [PATCH 2/7] fix(linter): rule no-restricted-imports support regex option inside patterns --- crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 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 1d24caf5eb18b..ad009403a7b6a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -294,9 +294,7 @@ impl RestrictedPattern { _ => "iu", }; - let reg_string = format!("{}", regex.as_str()); - - let Ok(reg_exp) = Regex::with_flags(®_string, flags) else { + let Ok(reg_exp) = Regex::with_flags(regex.as_str(), flags) else { return false; }; @@ -497,7 +495,7 @@ impl NoRestrictedImports { GlobResult::None => (), }; - if pattern.get_regex_result(&module_request) { + if pattern.get_regex_result(module_request) { let span = module_request.span(); no_restricted_imports_diagnostic(ctx, span, pattern.message.clone(), source); From 0a19f9a8a3dae4b7ccbc020c9261dd6e8ec058ab Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 23 Dec 2024 17:58:23 +0100 Subject: [PATCH 3/7] fix(linter): rule no-restricted-imports support `import_name_pattern` option inside `patterns` --- .../src/rules/eslint/no_restricted_imports.rs | 690 +++++++++--------- .../eslint_no_restricted_imports.snap | 197 +++++ 2 files changed, 554 insertions(+), 333 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 ad009403a7b6a..717baa4163201 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -49,8 +49,8 @@ pub struct NoRestrictedImportsConfig { #[serde(rename_all = "camelCase")] struct RestrictedPath { name: CompactStr, - import_names: Option>, - allow_import_names: Option>, + import_names: Option>, + allow_import_names: Option>, message: Option, } @@ -59,8 +59,9 @@ struct RestrictedPath { struct RestrictedPattern { group: Option>, regex: Option, - import_names: Option>, - allow_import_names: Option>, + import_names: Option>, + import_name_pattern: Option, + allow_import_names: Option>, case_sensitive: Option, message: Option, } @@ -166,6 +167,7 @@ fn add_configuration_patterns_from_string(paths: &mut Vec, mo group: Some(vec![CompactStr::new(module_name)]), regex: None, import_names: None, + import_name_pattern: None, allow_import_names: None, case_sensitive: None, message: None, @@ -198,8 +200,8 @@ fn is_name_span_allowed_in_pattern(name: &CompactStr, pattern: &RestrictedPatter return true; } - // when no importNames option is provided, no import in general is allowed - if pattern.import_names.as_ref().is_none() { + // when no importNames or importNamePattern option is provided, no import in general is allowed + if pattern.import_names.as_ref().is_none() && pattern.import_name_pattern.is_none() { return false; } @@ -208,6 +210,11 @@ fn is_name_span_allowed_in_pattern(name: &CompactStr, pattern: &RestrictedPatter return false; } + // the name is found is the importNamePattern + if pattern.get_import_name_pattern_result(name) { + return false; + } + // we allow it true } @@ -300,6 +307,23 @@ impl RestrictedPattern { reg_exp.find(name.name()).is_some() } + + fn get_import_name_pattern_result(&self, name: &CompactStr) -> bool { + let Some(import_name_pattern) = &self.import_name_pattern else { + return false; + }; + + let flags = match self.case_sensitive { + Some(case_sensitive) if case_sensitive => "u", + _ => "iu", + }; + + let Ok(reg_exp) = Regex::with_flags(import_name_pattern.as_str(), flags) else { + return false; + }; + + reg_exp.find(name).is_some() + } } impl Rule for NoRestrictedImports { @@ -748,99 +772,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!([{ @@ -912,24 +936,24 @@ fn test() { }] }])), ), - // ( - // "import Foo from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "regex": "my/relative-module", - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import { Bar } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "regex": "my/relative-module", - // "importNamePattern": "^Foo" - // }] - // }])), - // ), + ( + "import Foo from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "my/relative-module", + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "my/relative-module", + "importNamePattern": "^Foo" + }] + }])), + ), ]; let fail = vec![ @@ -1513,195 +1537,195 @@ fn test() { }] }])), ), - // ( - // "import { Foo } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import { Foo as Bar } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import Foo, { Bar } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "importNamePattern": "^(Foo|Bar)" - // }] - // }])), - // ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import { FooBar } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import Foo, { Bar } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo|^Bar" - // }] - // }])), - // ), - // ( - // "import { Foo, Bar } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNamePattern": "^(Foo|Bar)" - // }] - // }])), - // ), - // ( - // "import * as Foo from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import * as All from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import * as AllWithCustomMessage from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo", - // "message": "Import from @/utils instead." - // }] - // }])), - // ), - // ( - // "import * as AllWithCustomMessage from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Foo"], - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo", - // "message": "Import from @/utils instead." - // }] - // }])), - // ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Foo"], - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Foo", "Bar"], - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Bar"], - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Foo"], - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Bar" - // }] - // }])), - // ), - // ( - // "import { Foo, Bar } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Foo"], - // "group": ["**/my/relative-module"], - // "importNamePattern": "^Bar" - // }] - // }])), - // ), - // ( - // "export { Foo } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "export { Foo as Bar } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "export { Foo } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "importNames": ["Bar"], - // "group": ["foo"], - // "importNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "export * 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 as Bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import Foo, { Bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^(Foo|Bar)" + }] + }])), + ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { FooBar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import Foo, { Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo|^Bar" + }] + }])), + ), + ( + "import { Foo, Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^(Foo|Bar)" + }] + }])), + ), + ( + "import * as Foo from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import * as All from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import * as AllWithCustomMessage from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo", + "message": "Import from @/utils instead." + }] + }])), + ), + ( + "import * as AllWithCustomMessage from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo", + "message": "Import from @/utils instead." + }] + }])), + ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo", "Bar"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Bar"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Bar" + }] + }])), + ), + ( + "import { Foo, Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Foo"], + "group": ["**/my/relative-module"], + "importNamePattern": "^Bar" + }] + }])), + ), + ( + "export { Foo } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "export { Foo as Bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "export { Foo } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "importNames": ["Bar"], + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), + ( + "export * from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "importNamePattern": "^Foo" + }] + }])), + ), // ( // "export { Bar } from 'foo';", // Some(serde_json::json!([{ @@ -1832,15 +1856,15 @@ fn test() { }] }])), ), - // ( - // "import { Foo } from '../../my/relative-module';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "regex": "my/relative-module", - // "importNamePattern": "^Foo" - // }] - // }])), - // ), + ( + "import { Foo } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "my/relative-module", + "importNamePattern": "^Foo" + }] + }])), + ), ( "import withPatternsCaseSensitive from 'foo';", Some(serde_json::json!([{ @@ -1851,24 +1875,24 @@ fn test() { }] }])), ), - // ( - // " - // // error - // import { Foo_Enum } from '@app/api'; - // import { Bar_Enum } from '@app/api/bar'; - // import { Baz_Enum } from '@app/api/baz'; - // import { B_Enum } from '@app/api/enums/foo'; - // - // // no error - // import { C_Enum } from '@app/api/enums'; - // ", - // Some(serde_json::json!([{ - // "patterns": [{ - // "regex": "@app/(?!(api/enums$)).*", - // "importNamePattern": "_Enum$" - // }] - // }])), - // ), + ( + " + // error + import { Foo_Enum } from '@app/api'; + import { Bar_Enum } from '@app/api/bar'; + import { Baz_Enum } from '@app/api/baz'; + import { B_Enum } from '@app/api/enums/foo'; + + // no error + import { C_Enum } from '@app/api/enums'; + ", + Some(serde_json::json!([{ + "patterns": [{ + "regex": "@app/(?!(api/enums$)).*", + "importNamePattern": "_Enum$" + }] + }])), + ), ]; Tester::new(NoRestrictedImports::NAME, NoRestrictedImports::CATEGORY, pass, fail) 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 c774cb6d0fd8d..88011084391ee 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -597,6 +597,160 @@ snapshot_kind: text ╰──── help: Remove the import statement. + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:21] + 1 │ import { Foo } from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:28] + 1 │ import { Foo as Bar } from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:26] + 1 │ import Foo, { Bar } 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): '../../my/relative-module' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:24] + 1 │ import { FooBar } 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: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: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:26] + 1 │ import { Foo, Bar } from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:22] + 1 │ import * as Foo 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: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): 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): '../../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): '../../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): '../../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): '../../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): '../../my/relative-module' import is restricted from being used. + ╭─[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:26] + 1 │ import { Foo, Bar } from '../../my/relative-module'; + · ────────────────────────── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:21] + 1 │ export { Foo } from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:28] + 1 │ export { Foo as Bar } from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:21] + 1 │ export { Foo } from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:15] + 1 │ export * 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"; @@ -667,9 +821,52 @@ snapshot_kind: text ╰──── 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): foo is forbidden, use bar instead ╭─[no_restricted_imports.tsx:1:39] 1 │ import withPatternsCaseSensitive from 'foo'; · ───── ╰──── help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): '@app/api' import is restricted from being used. + ╭─[no_restricted_imports.tsx:3:43] + 2 │ // error + 3 │ import { Foo_Enum } from '@app/api'; + · ────────── + 4 │ import { Bar_Enum } from '@app/api/bar'; + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): '@app/api/bar' import is restricted from being used. + ╭─[no_restricted_imports.tsx:4:43] + 3 │ import { Foo_Enum } from '@app/api'; + 4 │ import { Bar_Enum } from '@app/api/bar'; + · ────────────── + 5 │ import { Baz_Enum } from '@app/api/baz'; + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): '@app/api/baz' import is restricted from being used. + ╭─[no_restricted_imports.tsx:5:43] + 4 │ import { Bar_Enum } from '@app/api/bar'; + 5 │ import { Baz_Enum } from '@app/api/baz'; + · ────────────── + 6 │ import { B_Enum } from '@app/api/enums/foo'; + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): '@app/api/enums/foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:6:41] + 5 │ import { Baz_Enum } from '@app/api/baz'; + 6 │ import { B_Enum } from '@app/api/enums/foo'; + · ──────────────────── + 7 │ + ╰──── + help: Remove the import statement. From 5296bb4f9f3cbe27a4e82e13d59aacaec3c76433 Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 23 Dec 2024 18:15:51 +0100 Subject: [PATCH 4/7] fix(linter): rule no-restricted-imports support missing options --- .../src/rules/eslint/no_restricted_imports.rs | 142 +++++++++++------- .../eslint_no_restricted_imports.snap | 28 ++++ 2 files changed, 116 insertions(+), 54 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 717baa4163201..34b6d479ac58f 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -62,6 +62,7 @@ struct RestrictedPattern { import_names: Option>, import_name_pattern: Option, allow_import_names: Option>, + allow_import_name_pattern: Option, case_sensitive: Option, message: Option, } @@ -150,11 +151,31 @@ fn add_configuration_patterns_from_object( add_configuration_patterns_from_string(patterns, module_name); } Value::Object(_) => { - if let Ok(path) = serde_json::from_value::(path_value.clone()) { - if path.group.is_some() && path.regex.is_some() { + if let Ok(pattern) = serde_json::from_value::(path_value.clone()) + { + if pattern.group.is_some() && pattern.regex.is_some() { + // ToDo: not allowed + } + + // allowImportNames cannot be used in combination with importNames, importNamePattern or allowImportNamePattern. + if pattern.allow_import_names.is_some() && ( + pattern.import_names.is_some() + || pattern.import_name_pattern.is_some() + || pattern.allow_import_name_pattern.is_some() + ) { + // ToDo: not allowed + } + + // allowImportNamePattern cannot be used in combination with importNames, importNamePattern or allowImportNames. + if pattern.allow_import_name_pattern.is_some() + && (pattern.import_names.is_some() + || pattern.import_name_pattern.is_some() + || pattern.allow_import_names.is_some()) + { // ToDo: not allowed } - patterns.push(path); + + patterns.push(pattern); } } _ => (), @@ -169,6 +190,7 @@ fn add_configuration_patterns_from_string(paths: &mut Vec, mo import_names: None, import_name_pattern: None, allow_import_names: None, + allow_import_name_pattern: None, case_sensitive: None, message: None, }); @@ -200,6 +222,11 @@ fn is_name_span_allowed_in_pattern(name: &CompactStr, pattern: &RestrictedPatter return true; } + // fast check if this name is allowed + if pattern.get_allow_import_name_pattern_result(name) { + return true; + } + // when no importNames or importNamePattern option is provided, no import in general is allowed if pattern.import_names.as_ref().is_none() && pattern.import_name_pattern.is_none() { return false; @@ -313,12 +340,19 @@ impl RestrictedPattern { return false; }; - let flags = match self.case_sensitive { - Some(case_sensitive) if case_sensitive => "u", - _ => "iu", + let Ok(reg_exp) = Regex::with_flags(import_name_pattern.as_str(), "u") else { + return false; }; - let Ok(reg_exp) = Regex::with_flags(import_name_pattern.as_str(), flags) else { + reg_exp.find(name).is_some() + } + + fn get_allow_import_name_pattern_result(&self, name: &CompactStr) -> bool { + let Some(allow_import_names) = &self.allow_import_name_pattern else { + return false; + }; + + let Ok(reg_exp) = Regex::with_flags(allow_import_names.as_str(), "u") else { return false; }; @@ -911,15 +945,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( @@ -1726,25 +1760,25 @@ fn test() { }] }])), ), - // ( - // "export { Bar } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "allowImportNamePattern": "^Foo" - // }] - // }])), - // ), - // ( - // "export { Bar } from 'foo';", - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo"], - // "allowImportNamePattern": "^Foo", - // "message": r#"Only imports that match the pattern "/^Foo/u" are allowed to be imported from "foo"."# - // }] - // }])), - // ), + ( + "export { Bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNamePattern": "^Foo" + }] + }])), + ), + ( + "export { Bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNamePattern": "^Foo", + "message": r#"Only imports that match the pattern "/^Foo/u" are allowed to be imported from "foo"."# + }] + }])), + ), ( r#"import { AllowedObject, DisallowedObject } from "foo";"#, Some(serde_json::json!([{ @@ -1821,25 +1855,25 @@ fn test() { }] }])), ), - // ( - // r#"import * as AllowedObject from "foo/bar";"#, - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo/*"], - // "allowImportNamePattern": "^Allow" - // }] - // }])), - // ), - // ( - // r#"import * as AllowedObject from "foo/bar";"#, - // Some(serde_json::json!([{ - // "patterns": [{ - // "group": ["foo/*"], - // "allowImportNamePattern": "^Allow", - // "message": r#"Only import names starting with "Allow" are allowed to be imported from "foo"."# - // }] - // }])), - // ), + ( + r#"import * as AllowedObject from "foo/bar";"#, + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo/*"], + "allowImportNamePattern": "^Allow" + }] + }])), + ), + ( + r#"import * as AllowedObject from "foo/bar";"#, + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo/*"], + "allowImportNamePattern": "^Allow", + "message": r#"Only import names starting with "Allow" are allowed to be imported from "foo"."# + }] + }])), + ), ( r#"import withPatterns from "foo/baz";"#, Some( 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 88011084391ee..f9b2334ba92e1 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -751,6 +751,20 @@ snapshot_kind: text ╰──── help: Remove the import statement. + ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ╭─[no_restricted_imports.tsx:1:21] + 1 │ export { Bar } from 'foo'; + · ───── + ╰──── + help: Remove the import statement. + + ⚠ eslint(no-restricted-imports): Only imports that match the pattern "/^Foo/u" are allowed to be imported from "foo". + ╭─[no_restricted_imports.tsx:1:21] + 1 │ export { Bar } 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"; @@ -807,6 +821,20 @@ 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 import names starting with "Allow" are 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:26] 1 │ import withPatterns from "foo/baz"; From f088c10b976d7c7ac6e8cfe5f4ff454078167752 Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 23 Dec 2024 18:16:20 +0100 Subject: [PATCH 5/7] fix(linter): rule `no-restricted-imports` support missing options --- .../src/rules/eslint/no_restricted_imports.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 34b6d479ac58f..72bf133b0126a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -158,11 +158,11 @@ fn add_configuration_patterns_from_object( } // allowImportNames cannot be used in combination with importNames, importNamePattern or allowImportNamePattern. - if pattern.allow_import_names.is_some() && ( - pattern.import_names.is_some() - || pattern.import_name_pattern.is_some() - || pattern.allow_import_name_pattern.is_some() - ) { + if pattern.allow_import_names.is_some() + && (pattern.import_names.is_some() + || pattern.import_name_pattern.is_some() + || pattern.allow_import_name_pattern.is_some()) + { // ToDo: not allowed } From 0da9b9d8030f0bdd87e3e3a5ad588638cdaea8c3 Mon Sep 17 00:00:00 2001 From: Sysix Date: Wed, 25 Dec 2024 16:09:52 +0100 Subject: [PATCH 6/7] fix(linter): rule no-restricted-imports support missing options - remove regress --- Cargo.lock | 11 --- Cargo.toml | 1 - crates/oxc_linter/Cargo.toml | 1 - .../src/rules/eslint/no_restricted_imports.rs | 73 +++++++++---------- .../eslint_no_restricted_imports.snap | 43 ----------- 5 files changed, 34 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2967079cd8634..6154e8ce05dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1719,7 +1719,6 @@ dependencies = [ "project-root", "rayon", "regex", - "regress", "rust-lapper", "rustc-hash", "schemars", @@ -2407,16 +2406,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "regress" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1541daf4e4ed43a0922b7969bdc2170178bcacc5dabf7e39bc508a9fa3953a7a" -dependencies = [ - "hashbrown 0.14.5", - "memchr", -] - [[package]] name = "ring" version = "0.17.8" diff --git a/Cargo.toml b/Cargo.toml index 52281a38f0542..8c41dff011293 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,7 +178,6 @@ prettyplease = "0.2.25" project-root = "0.2.2" rayon = "1.10.0" regex = "1.11.1" -regress = "0.10.1" ropey = "1.6.1" rust-lapper = "1.1.0" ryu-js = "1.0.1" diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 9d07acc56393c..10105e422f152 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -53,7 +53,6 @@ nonmax = { workspace = true } phf = { workspace = true, features = ["macros"] } rayon = { workspace = true } regex = { workspace = true } -regress = { workspace = true } rust-lapper = { workspace = true } rustc-hash = { workspace = true } schemars = { workspace = true, features = ["indexmap2"] } 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 72bf133b0126a..0e7b169ee5df1 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -2,7 +2,7 @@ use ignore::gitignore::GitignoreBuilder; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, Span}; -use regress::Regex; +use regex::Regex; use rustc_hash::FxHashMap; use serde::Deserialize; use serde_json::Value; @@ -323,12 +323,7 @@ impl RestrictedPattern { return false; }; - let flags = match self.case_sensitive { - Some(case_sensitive) if case_sensitive => "u", - _ => "iu", - }; - - let Ok(reg_exp) = Regex::with_flags(regex.as_str(), flags) else { + let Ok(reg_exp) = Regex::new(regex.as_str()) else { return false; }; @@ -340,7 +335,7 @@ impl RestrictedPattern { return false; }; - let Ok(reg_exp) = Regex::with_flags(import_name_pattern.as_str(), "u") else { + let Ok(reg_exp) = Regex::new(import_name_pattern.as_str()) else { return false; }; @@ -352,7 +347,7 @@ impl RestrictedPattern { return false; }; - let Ok(reg_exp) = Regex::with_flags(allow_import_names.as_str(), "u") else { + let Ok(reg_exp) = Regex::new(allow_import_names.as_str()) else { return false; }; @@ -954,12 +949,12 @@ fn test() { }] }])), ), - ( - r#"import withPatterns from "foo/bar";"#, - Some( - serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), - ), - ), + // ( + // r#"import withPatterns from "foo/bar";"#, + // Some( + // serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), + // ), + // ), ( "import withPatternsCaseSensitive from 'foo';", Some(serde_json::json!([{ @@ -1874,12 +1869,12 @@ fn test() { }] }])), ), - ( - r#"import withPatterns from "foo/baz";"#, - Some( - serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), - ), - ), + // ( + // r#"import withPatterns from "foo/baz";"#, + // Some( + // serde_json::json!([{ "patterns": [{ "regex": "foo/(?!bar)", "message": "foo is forbidden, use bar instead" }] }]), + // ), + // ), ( "import withPatternsCaseSensitive from 'FOO';", Some(serde_json::json!([{ @@ -1909,24 +1904,24 @@ fn test() { }] }])), ), - ( - " - // error - import { Foo_Enum } from '@app/api'; - import { Bar_Enum } from '@app/api/bar'; - import { Baz_Enum } from '@app/api/baz'; - import { B_Enum } from '@app/api/enums/foo'; - - // no error - import { C_Enum } from '@app/api/enums'; - ", - Some(serde_json::json!([{ - "patterns": [{ - "regex": "@app/(?!(api/enums$)).*", - "importNamePattern": "_Enum$" - }] - }])), - ), + // ( + // " + // // error + // import { Foo_Enum } from '@app/api'; + // import { Bar_Enum } from '@app/api/bar'; + // import { Baz_Enum } from '@app/api/baz'; + // import { B_Enum } from '@app/api/enums/foo'; + // + // // no error + // import { C_Enum } from '@app/api/enums'; + // ", + // Some(serde_json::json!([{ + // "patterns": [{ + // "regex": "@app/(?!(api/enums$)).*", + // "importNamePattern": "_Enum$" + // }] + // }])), + // ), ]; Tester::new(NoRestrictedImports::NAME, NoRestrictedImports::CATEGORY, pass, fail) 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 f9b2334ba92e1..249cdc67f8925 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -835,13 +835,6 @@ snapshot_kind: text ╰──── help: Remove the import statement. - ⚠ eslint(no-restricted-imports): foo is forbidden, use bar instead - ╭─[no_restricted_imports.tsx:1:26] - 1 │ import withPatterns from "foo/baz"; - · ───────── - ╰──── - 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'; @@ -862,39 +855,3 @@ snapshot_kind: text · ───── ╰──── help: Remove the import statement. - - ⚠ eslint(no-restricted-imports): '@app/api' import is restricted from being used. - ╭─[no_restricted_imports.tsx:3:43] - 2 │ // error - 3 │ import { Foo_Enum } from '@app/api'; - · ────────── - 4 │ import { Bar_Enum } from '@app/api/bar'; - ╰──── - help: Remove the import statement. - - ⚠ eslint(no-restricted-imports): '@app/api/bar' import is restricted from being used. - ╭─[no_restricted_imports.tsx:4:43] - 3 │ import { Foo_Enum } from '@app/api'; - 4 │ import { Bar_Enum } from '@app/api/bar'; - · ────────────── - 5 │ import { Baz_Enum } from '@app/api/baz'; - ╰──── - help: Remove the import statement. - - ⚠ eslint(no-restricted-imports): '@app/api/baz' import is restricted from being used. - ╭─[no_restricted_imports.tsx:5:43] - 4 │ import { Bar_Enum } from '@app/api/bar'; - 5 │ import { Baz_Enum } from '@app/api/baz'; - · ────────────── - 6 │ import { B_Enum } from '@app/api/enums/foo'; - ╰──── - help: Remove the import statement. - - ⚠ eslint(no-restricted-imports): '@app/api/enums/foo' import is restricted from being used. - ╭─[no_restricted_imports.tsx:6:41] - 5 │ import { Baz_Enum } from '@app/api/baz'; - 6 │ import { B_Enum } from '@app/api/enums/foo'; - · ──────────────────── - 7 │ - ╰──── - help: Remove the import statement. From 87d8da7f18ecfe3f403ef03a85b6092ee85194ed Mon Sep 17 00:00:00 2001 From: Sysix Date: Thu, 26 Dec 2024 19:59:13 +0100 Subject: [PATCH 7/7] fix(linter): rule no-restricted-imports support missing options - build regex in `from_configuration` --- .../src/rules/eslint/no_restricted_imports.rs | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 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 0e7b169ee5df1..055bb6afb73a6 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -4,8 +4,9 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, Span}; use regex::Regex; use rustc_hash::FxHashMap; -use serde::Deserialize; +use serde::{de::Error, Deserialize, Deserializer}; use serde_json::Value; +use std::borrow::Cow; use crate::{ context::LintContext, @@ -58,15 +59,42 @@ struct RestrictedPath { #[serde(rename_all = "camelCase")] struct RestrictedPattern { group: Option>, - regex: Option, + regex: Option>, import_names: Option>, - import_name_pattern: Option, + import_name_pattern: Option>, allow_import_names: Option>, - allow_import_name_pattern: Option, + allow_import_name_pattern: Option>, case_sensitive: Option, message: Option, } +/// A wrapper type which implements `Serialize` and `Deserialize` for +/// types involving `Regex` +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct SerdeRegexWrapper(pub T); + +impl std::ops::Deref for SerdeRegexWrapper { + type Target = Regex; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'de> Deserialize<'de> for SerdeRegexWrapper { + fn deserialize(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = >::deserialize(d)?; + + match s.parse() { + Ok(regex) => Ok(SerdeRegexWrapper(regex)), + Err(err) => Err(D::Error::custom(err)), + } + } +} + #[derive(Debug)] enum GlobResult { Found, @@ -323,11 +351,7 @@ impl RestrictedPattern { return false; }; - let Ok(reg_exp) = Regex::new(regex.as_str()) else { - return false; - }; - - reg_exp.find(name.name()).is_some() + regex.find(name.name()).is_some() } fn get_import_name_pattern_result(&self, name: &CompactStr) -> bool { @@ -335,11 +359,7 @@ impl RestrictedPattern { return false; }; - let Ok(reg_exp) = Regex::new(import_name_pattern.as_str()) else { - return false; - }; - - reg_exp.find(name).is_some() + import_name_pattern.find(name).is_some() } fn get_allow_import_name_pattern_result(&self, name: &CompactStr) -> bool { @@ -347,11 +367,7 @@ impl RestrictedPattern { return false; }; - let Ok(reg_exp) = Regex::new(allow_import_names.as_str()) else { - return false; - }; - - reg_exp.find(name).is_some() + allow_import_names.find(name).is_some() } }