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 0965cbd747d5a..03919b8fde7d6 100644 --- a/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_restricted_imports.rs @@ -27,11 +27,13 @@ fn diagnostic_path(span: Span, message: Option, source: &str) -> Oxc fn diagnostic_pattern(span: Span, message: Option, source: &str) -> OxcDiagnostic { if let Some(message) = message { - return OxcDiagnostic::warn(format!("'{source}' import is restricted from being used by a pattern.")) - .with_help(message) - .with_label(span); + return OxcDiagnostic::warn(format!( + "'{source}' import is restricted from being used by a pattern." + )) + .with_help(message) + .with_label(span); } - + OxcDiagnostic::warn(format!("'{source}' import is restricted from being used by a pattern.")) .with_label(span) } @@ -43,13 +45,17 @@ fn diagnostic_pattern_and_import_name( source: &str, ) -> OxcDiagnostic { if let Some(message) = message { - return OxcDiagnostic::warn(format!("'{name}' import from '{source}' is restricted from being used by a pattern.")) - .with_help(message) - .with_label(span); + return OxcDiagnostic::warn(format!( + "'{name}' import from '{source}' is restricted from being used by a pattern." + )) + .with_help(message) + .with_label(span); } - - OxcDiagnostic::warn(format!("'{name}' import from '{source}' is restricted from being used by a pattern.")) - .with_label(span) + + OxcDiagnostic::warn(format!( + "'{name}' import from '{source}' is restricted from being used by a pattern." + )) + .with_label(span) } fn diagnostic_pattern_and_everything( @@ -84,13 +90,18 @@ fn diagnostic_everything( name: &str, source: &str, ) -> OxcDiagnostic { - let msg = message.unwrap_or_else(|| { - CompactStr::new(&format!( + if let Some(message) = message { + return OxcDiagnostic::warn(format!( "* import is invalid because '{name}' from '{source}' is restricted." )) - }); + .with_help(message) + .with_label(span); + } - OxcDiagnostic::warn(msg).with_help("Remove the import statement.").with_label(span) + OxcDiagnostic::warn(format!( + "* import is invalid because '{name}' from '{source}' is restricted." + )) + .with_label(span) } fn diagnostic_import_name( @@ -104,9 +115,8 @@ fn diagnostic_import_name( .with_help(message) .with_label(span); } - - OxcDiagnostic::warn(format!("'{name}' import from '{source}' is restricted.")) - .with_label(span) + + OxcDiagnostic::warn(format!("'{name}' import from '{source}' is restricted.")).with_label(span) } fn diagnostic_allowed_import_name( @@ -121,7 +131,7 @@ fn diagnostic_allowed_import_name( .with_help(message) .with_label(span); } - + OxcDiagnostic::warn(format!("'{name}' import from '{source}' is restricted because only {allowed} import(s) is/are allowed.")) .with_label(span) } @@ -153,7 +163,7 @@ fn diagnostic_allowed_import_name_pattern( .with_help(message) .with_label(span); } - + OxcDiagnostic::warn(format!("'{name}' import from '{source}' is restricted because only imports that match the pattern '{allowed_pattern}' are allowed from '{source}'.")) .with_label(span) } @@ -693,14 +703,29 @@ impl NoRestrictedImports { } match path.is_skip_able_import(&entry.import_name) { - IsSkipAbleResult::GeneralDisallowed(_) - | IsSkipAbleResult::GeneralDisallowedWithoutSpan => { + IsSkipAbleResult::GeneralDisallowed(_) => { ctx.diagnostic(diagnostic_path( entry.module_request.span(), path.message.clone(), source, )); } + IsSkipAbleResult::GeneralDisallowedWithoutSpan => { + if let Some(import_names) = &path.import_names { + ctx.diagnostic(diagnostic_everything( + entry.module_request.span(), + path.message.clone(), + import_names.join(", ").as_str(), + source, + )); + } else { + ctx.diagnostic(diagnostic_path( + entry.module_request.span(), + path.message.clone(), + source, + )); + } + } IsSkipAbleResult::NameDisallowed(name_span) => { if let Some(import_names) = &path.allow_import_names { ctx.diagnostic(diagnostic_allowed_import_name( @@ -898,7 +923,415 @@ fn test() { }] }]); - let pass = vec![]; + let pass = vec![ + (r#"import os from "os";"#, None), + (r#"import os from "os";"#, Some(serde_json::json!(["osx"]))), + (r#"import fs from "fs";"#, Some(serde_json::json!(["crypto"]))), + (r#"import path from "path";"#, Some(serde_json::json!(["crypto", "stream", "os"]))), + (r#"import async from "async";"#, None), + (r#"import "foo""#, Some(serde_json::json!(["crypto"]))), + (r#"import "foo/bar";"#, Some(serde_json::json!(["foo"]))), + ( + 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/c*"] }])), + ), + ("import foo from 'foo';", Some(serde_json::json!(["../foo"]))), + ("import foo from 'foo';", Some(serde_json::json!([{ "paths": ["../foo"] }]))), + ("import foo from 'foo';", Some(serde_json::json!([{ "patterns": ["../foo"] }]))), + ("import foo from 'foo';", Some(serde_json::json!(["/foo"]))), + ("import foo from 'foo';", Some(serde_json::json!([{ "paths": ["/foo"] }]))), + ("import relative from '../foo';", None), + ("import relative from '../foo';", Some(serde_json::json!(["../notFoo"]))), + ( + "import relativeWithPaths from '../foo';", + Some(serde_json::json!([{ "paths": ["../notFoo"] }])), + ), + ( + "import relativeWithPatterns from '../foo';", + Some(serde_json::json!([{ "patterns": ["notFoo"] }])), + ), + ("import absolute from '/foo';", None), + ("import absolute from '/foo';", Some(serde_json::json!(["/notFoo"]))), + ( + "import absoluteWithPaths from '/foo';", + Some(serde_json::json!([{ "paths": ["/notFoo"] }])), + ), + ( + "import absoluteWithPatterns from '/foo';", + Some(serde_json::json!([{ "patterns": ["notFoo"] }])), + ), + ( + r#"import withPatternsAndPaths from "foo/bar";"#, + Some(serde_json::json!([{ "paths": ["foo"], "patterns": ["foo/c*"] }])), + ), + ( + r#"import withGitignores from "foo/bar";"#, + Some(serde_json::json!([{ "patterns": ["foo/*", "!foo/bar"] }])), + ), + ( + r#"import withPatterns from "foo/bar";"#, + Some( + serde_json::json!([{ "patterns": [{ "group": ["foo/*", "!foo/bar"], "message": "foo is forbidden, use bar instead" }] }]), + ), + ), + ( + "import withPatternsCaseSensitive from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["FOO"], + "message": "foo is forbidden, use bar instead", + "caseSensitive": true + }] + }])), + ), + ( + r#"import AllowedObject from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + r#"import DisallowedObject from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + r#"import * as DisallowedObject from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "bar", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), + ), + ( + r#"import { AllowedObject } from "foo";"#, + Some(serde_json::json!(pass_disallowed_object_foo.clone())), + ), + ( + r#"import { 'AllowedObject' as bar } from "foo";"#, + Some(serde_json::json!(pass_disallowed_object_foo.clone())), + ), + ( + r#"import { ' ' as bar } from "foo";"#, + Some(serde_json::json!([{"paths": [{"name": "foo","importNames": [""]}]}])), + ), + ( + r#"import { '' as bar } from "foo";"#, + Some(serde_json::json!([{"paths": [{"name": "foo","importNames": [" "]}]}])), + ), + ( + r#"import { DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "bar", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), + ), + ( + r#"import { AllowedObject as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import { 'AllowedObject' as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import { AllowedObject, AllowedObjectTwo } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import { AllowedObject, AllowedObjectTwo as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import AllowedObjectThree, { AllowedObject as AllowedObjectTwo } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import AllowedObject, { AllowedObjectTwo as DisallowedObject } from "foo";"#, + Some(pass_disallowed_object_foo.clone()), + ), + ( + r#"import AllowedObject, { AllowedObjectTwo as DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject", "DisallowedObjectTwo"], + "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + }] + }])), + ), + ( + r#"import AllowedObject, * as DisallowedObject from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "bar", + "importNames": ["DisallowedObject"], + "message": r#"Please import "DisallowedObject" from /bar/ instead."# + }] + }])), + ), + ( + r#"import "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject", "DisallowedObjectTwo"], + "message": r#"Please import "DisallowedObject" and "DisallowedObjectTwo" from /bar/ instead."# + }] + }])), + ), + // ( + // r#"import { + // AllowedObject, + // DisallowedObject, // eslint-disable-line + // } from "foo";"#, + // Some( + // serde_json::json!([{ "paths": [{ "name": "foo", "importNames": ["DisallowedObject"] }] }]), + // ), + // ), + (r#"export * from "foo";"#, Some(serde_json::json!(["bar"]))), + ( + r#"export * from "foo";"#, + Some(serde_json::json!([{ + "name": "bar", + "importNames": ["DisallowedObject"] + }])), + ), + ( + r#"export { 'AllowedObject' } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + r#"export { 'AllowedObject' as DisallowedObject } from "foo";"#, + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "importNames": ["DisallowedObject"] + }] + }])), + ), + ( + "import { Bar } from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["Foo"] + }] + }])), + ), + ( + "import Foo from '../../my/relative-module';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["**/my/relative-module"], + "importNames": ["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!([{ + "paths": [{ + "name": "foo", + "allowImportNames": ["AllowedObject"], + "message": r#"Please import anything except "AllowedObject" from /bar/ instead."# + }] + }])), + ), + ( + "import { foo } from 'foo';", + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "allowImportNames": ["foo"] + }] + }])), + ), + ( + "import { foo } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNames": ["foo"] + }] + }])), + ), + ( + "export { bar } from 'foo';", + Some(serde_json::json!([{ + "paths": [{ + "name": "foo", + "allowImportNames": ["bar"] + }] + }])), + ), + ( + "export { bar } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNames": ["bar"] + }] + }])), + ), + ( + "import { Foo } from 'foo';", + Some(serde_json::json!([{ + "patterns": [{ + "group": ["foo"], + "allowImportNamePattern": "^Foo" + }] + }])), + ), + ( + 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!([{ + "patterns": [{ + "regex": "FOO", + "message": "foo is forbidden, use bar instead", + "caseSensitive": true + }] + }])), + ), + ( + "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![ (r#"import "fs""#, Some(serde_json::json!(["fs"]))), 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 48c2ba5a93fad..dd75207cbf373 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_restricted_imports.snap @@ -152,7 +152,7 @@ snapshot_kind: text ╰──── help: Please import the default import of "foo" from /bar/ instead. - ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ⚠ eslint(no-restricted-imports): * import is invalid because 'DisallowedObject' from 'foo' is restricted. ╭─[no_restricted_imports.tsx:1:22] 1 │ import * as All from "foo"; · ───── @@ -247,14 +247,14 @@ snapshot_kind: text ╰──── help: Please import "DisallowedObject" from /bar/ instead. - ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ⚠ eslint(no-restricted-imports): * import is invalid because 'DisallowedObject' from 'foo' is restricted. ╭─[no_restricted_imports.tsx:1:50] 1 │ import AllowedObject, * as AllowedObjectTwo from "foo"; · ───── ╰──── help: Please import "DisallowedObject" from /bar/ instead. - ⚠ eslint(no-restricted-imports): 'foo' import is restricted from being used. + ⚠ eslint(no-restricted-imports): * import is invalid because 'DisallowedObject, DisallowedObjectTwo' from 'foo' is restricted. ╭─[no_restricted_imports.tsx:1:50] 1 │ import AllowedObject, * as AllowedObjectTwo from "foo"; · ───── @@ -376,14 +376,14 @@ snapshot_kind: text ╰──── help: Don"t use "foo" or "baz" from "mod". - ⚠ eslint(no-restricted-imports): 'mod' import is restricted from being used. + ⚠ eslint(no-restricted-imports): * import is invalid because 'foo' from 'mod' is restricted. ╭─[no_restricted_imports.tsx:1:22] 1 │ import * as mod from 'mod' · ───── ╰──── help: Import foo from qux instead. - ⚠ eslint(no-restricted-imports): 'mod' import is restricted from being used. + ⚠ eslint(no-restricted-imports): * import is invalid because 'bar' from 'mod' is restricted. ╭─[no_restricted_imports.tsx:1:22] 1 │ import * as mod from 'mod' · ───── @@ -421,7 +421,7 @@ snapshot_kind: text · ───── ╰──── - ⚠ eslint(no-restricted-imports): 'mod' import is restricted from being used. + ⚠ eslint(no-restricted-imports): * import is invalid because 'default' from 'mod' is restricted. ╭─[no_restricted_imports.tsx:1:27] 1 │ import foo, * as bar from 'mod'; · ─────