diff --git a/crates/oxc_linter/fixtures/import/oxc_named.js b/crates/oxc_linter/fixtures/import/oxc_named.js deleted file mode 100644 index f800f7d33..000000000 --- a/crates/oxc_linter/fixtures/import/oxc_named.js +++ /dev/null @@ -1,2 +0,0 @@ -module.exports.foo = 1; -exports.bar = 1; diff --git a/crates/oxc_linter/src/rules/import/default.rs b/crates/oxc_linter/src/rules/import/default.rs index 33a76cc5a..1de95ddc3 100644 --- a/crates/oxc_linter/src/rules/import/default.rs +++ b/crates/oxc_linter/src/rules/import/default.rs @@ -47,7 +47,9 @@ impl Rule for Default { let Some(remote_module_record_ref) = module_record.loaded_modules.get(specifier) else { continue; }; - + if remote_module_record_ref.not_esm { + continue; + } if remote_module_record_ref.export_default.is_none() && !remote_module_record_ref.exported_bindings.contains_key("default") { diff --git a/crates/oxc_linter/src/rules/import/named.rs b/crates/oxc_linter/src/rules/import/named.rs index db4c7f898..65ff862ea 100644 --- a/crates/oxc_linter/src/rules/import/named.rs +++ b/crates/oxc_linter/src/rules/import/named.rs @@ -51,6 +51,9 @@ impl Rule for Named { continue; }; let remote_module_record = remote_module_record_ref.value(); + if remote_module_record.not_esm { + continue; + } // Check remote bindings if remote_module_record.exported_bindings.contains_key(import_name.name()) { continue; @@ -164,8 +167,6 @@ fn test() { // "import { foo } from './export-all'", // TypeScript export assignment "import x from './typescript-export-assign-object'", - // oxc - "import { foo, bar } from './oxc_named'", ]; let fail = vec![ @@ -200,8 +201,6 @@ fn test() { // Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead. "import { NotExported } from './typescript-export-assign-object'", "import { FooBar } from './typescript-export-assign-object'", - // oxc - "import { baz, quaz } from './oxc_named'", ]; Tester::new(Named::NAME, pass, fail) diff --git a/crates/oxc_linter/src/rules/import/no_cycle.rs b/crates/oxc_linter/src/rules/import/no_cycle.rs index 1b994cc49..9798b3b13 100644 --- a/crates/oxc_linter/src/rules/import/no_cycle.rs +++ b/crates/oxc_linter/src/rules/import/no_cycle.rs @@ -220,14 +220,14 @@ fn test() { // (r#"import { foo } from "./external-depth-two""#, None), // (r#"import { foo } from "./es6/depth-one""#, None), (r#"import { foo } from "./es6/depth-one""#, Some(json!([{"maxDepth":1}]))), - (r#"const { foo } = require("./es6/depth-one")"#, Some(json!([{"commonjs":true}]))), + // (r#"const { foo } = require("./es6/depth-one")"#, Some(json!([{"commonjs":true}]))), // TODO: amd // (r#"require(["./es6/depth-one"], d1 => {})"#, Some(json!([{"amd":true}]))), // (r#"define(["./es6/depth-one"], d1 => {})"#, Some(json!([{"amd":true}]))), (r#"import { foo } from "./es6/depth-one-reexport""#, None), (r#"import { foo } from "./es6/depth-two""#, None), (r#"import { foo } from "./es6/depth-two""#, Some(json!([{"maxDepth":2}]))), - (r#"const { foo } = require("./es6/depth-two")"#, Some(json!([{"commonjs":true}]))), + // (r#"const { foo } = require("./es6/depth-two")"#, Some(json!([{"commonjs":true}]))), (r#"import { two } from "./es6/depth-three-star""#, None), (r#"import one, { two, three } from "./es6/depth-three-star""#, None), (r#"import { bar } from "./es6/depth-three-indirect""#, None), @@ -242,10 +242,10 @@ fn test() { r#"import { foo } from "./es6/depth-one""#, Some(json!([{"allowUnsafeDynamicCyclicDependency":true,"maxDepth":1}])), ), - ( - r#"const { foo } = require("./es6/depth-one")"#, - Some(json!([{"allowUnsafeDynamicCyclicDependency":true,"commonjs":true}])), - ), + // ( + // r#"const { foo } = require("./es6/depth-one")"#, + // Some(json!([{"allowUnsafeDynamicCyclicDependency":true,"commonjs":true}])), + // ), // TODO: amd // ( // r#"require(["./es6/depth-one"], d1 => {})"#, @@ -267,10 +267,10 @@ fn test() { r#"import { foo } from "./es6/depth-two""#, Some(json!([{"allowUnsafeDynamicCyclicDependency":true,"maxDepth":2}])), ), - ( - r#"const { foo } = require("./es6/depth-two")"#, - Some(json!([{"allowUnsafeDynamicCyclicDependency":true,"commonjs":true}])), - ), + // ( + // r#"const { foo } = require("./es6/depth-two")"#, + // Some(json!([{"allowUnsafeDynamicCyclicDependency":true,"commonjs":true}])), + // ), ( r#"import { two } from "./es6/depth-three-star""#, Some(json!([{"allowUnsafeDynamicCyclicDependency":true}])), diff --git a/crates/oxc_linter/src/rules/import/no_self_import.rs b/crates/oxc_linter/src/rules/import/no_self_import.rs index e7cfc324c..f51c742c3 100644 --- a/crates/oxc_linter/src/rules/import/no_self_import.rs +++ b/crates/oxc_linter/src/rules/import/no_self_import.rs @@ -73,8 +73,8 @@ fn test() { let fail = vec![ "import bar from './no-self-import'", - "var bar = require('./no-self-import')", - "var bar = require('./no-self-import.js')", + // "var bar = require('./no-self-import')", + // "var bar = require('./no-self-import.js')", ]; Tester::new(NoSelfImport::NAME, pass, fail) @@ -83,37 +83,37 @@ fn test() { .test(); } - { - let pass = vec!["var bar = require('./bar')"]; - let fail = vec![]; + // { + // let pass = vec!["var bar = require('./bar')"]; + // let fail = vec![]; - Tester::new(NoSelfImport::NAME, pass, fail) - .with_import_plugin(true) - .change_rule_path("bar/index.js") - .test(); - } + // Tester::new(NoSelfImport::NAME, pass, fail) + // .with_import_plugin(true) + // .change_rule_path("bar/index.js") + // .test(); + // } - { - let pass = vec![]; - let fail = vec![ - "var bar = require('.')", - "var bar = require('./')", - "var bar = require('././././')", - ]; + // { + // let pass = vec![]; + // let fail = vec![ + // "var bar = require('.')", + // "var bar = require('./')", + // "var bar = require('././././')", + // ]; - Tester::new(NoSelfImport::NAME, pass, fail) - .with_import_plugin(true) - .change_rule_path("index.js") - .test(); - } + // Tester::new(NoSelfImport::NAME, pass, fail) + // .with_import_plugin(true) + // .change_rule_path("index.js") + // .test(); + // } - { - let pass = vec![]; - let fail = vec!["var bar = require('../no-self-import-folder')"]; + // { + // let pass = vec![]; + // let fail = vec!["var bar = require('../no-self-import-folder')"]; - Tester::new(NoSelfImport::NAME, pass, fail) - .with_import_plugin(true) - .change_rule_path("no-self-import-folder/index.js") - .test(); - } + // Tester::new(NoSelfImport::NAME, pass, fail) + // .with_import_plugin(true) + // .change_rule_path("no-self-import-folder/index.js") + // .test(); + // } } diff --git a/crates/oxc_linter/src/rules/import/no_unresolved.rs b/crates/oxc_linter/src/rules/import/no_unresolved.rs index ca459c888..ec7362a7a 100644 --- a/crates/oxc_linter/src/rules/import/no_unresolved.rs +++ b/crates/oxc_linter/src/rules/import/no_unresolved.rs @@ -117,7 +117,7 @@ fn test() { // r#"import('in-alternate-root').then(function({DEEP}) {});"#, r#"export * as bar from "./does-not-exist""#, r#"export bar from "./does-not-exist""#, - r#"var bar = require("./baz")"#, + // r#"var bar = require("./baz")"#, // TODO: require expression // r#"require("./baz")"#, // TODO: amd diff --git a/crates/oxc_linter/src/snapshots/named.snap b/crates/oxc_linter/src/snapshots/named.snap index af37ed844..07920b08e 100644 --- a/crates/oxc_linter/src/snapshots/named.snap +++ b/crates/oxc_linter/src/snapshots/named.snap @@ -140,17 +140,3 @@ expression: named · ────── ╰──── help: does "./typescript-export-assign-object" have the export "FooBar"? - - ⚠ eslint-plugin-import(named): named import "baz" not found - ╭─[index.js:1:10] - 1 │ import { baz, quaz } from './oxc_named' - · ─── - ╰──── - help: does "./oxc_named" have the export "baz"? - - ⚠ eslint-plugin-import(named): named import "quaz" not found - ╭─[index.js:1:15] - 1 │ import { baz, quaz } from './oxc_named' - · ──── - ╰──── - help: does "./oxc_named" have the export "quaz"? diff --git a/crates/oxc_linter/src/snapshots/no_cycle.snap b/crates/oxc_linter/src/snapshots/no_cycle.snap index 5958123f0..15ba125dd 100644 --- a/crates/oxc_linter/src/snapshots/no_cycle.snap +++ b/crates/oxc_linter/src/snapshots/no_cycle.snap @@ -11,15 +11,6 @@ expression: no_cycle -> ./es6/depth-one - fixtures/import/cycles/es6/depth-one.js -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected - ╭─[cycles/depth-zero.js:1:25] - 1 │ const { foo } = require("./es6/depth-one") - · ───────────────── - ╰──── - help: These paths form a cycle: - -> ./es6/depth-one - fixtures/import/cycles/es6/depth-one.js - -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected ╭─[cycles/depth-zero.js:1:21] 1 │ import { foo } from "./es6/depth-one-reexport" @@ -49,16 +40,6 @@ expression: no_cycle -> ./depth-one - fixtures/import/cycles/es6/depth-one.js -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected - ╭─[cycles/depth-zero.js:1:25] - 1 │ const { foo } = require("./es6/depth-two") - · ───────────────── - ╰──── - help: These paths form a cycle: - -> ./es6/depth-two - fixtures/import/cycles/es6/depth-two.js - -> ./depth-one - fixtures/import/cycles/es6/depth-one.js - -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected ╭─[cycles/depth-zero.js:1:21] 1 │ import { two } from "./es6/depth-three-star" @@ -141,15 +122,6 @@ expression: no_cycle -> ./es6/depth-one - fixtures/import/cycles/es6/depth-one.js -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected - ╭─[cycles/depth-zero.js:1:25] - 1 │ const { foo } = require("./es6/depth-one") - · ───────────────── - ╰──── - help: These paths form a cycle: - -> ./es6/depth-one - fixtures/import/cycles/es6/depth-one.js - -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected ╭─[cycles/depth-zero.js:1:21] 1 │ import { foo } from "./es6/depth-one-reexport" @@ -179,16 +151,6 @@ expression: no_cycle -> ./depth-one - fixtures/import/cycles/es6/depth-one.js -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected - ╭─[cycles/depth-zero.js:1:25] - 1 │ const { foo } = require("./es6/depth-two") - · ───────────────── - ╰──── - help: These paths form a cycle: - -> ./es6/depth-two - fixtures/import/cycles/es6/depth-two.js - -> ./depth-one - fixtures/import/cycles/es6/depth-one.js - -> ../depth-zero - fixtures/import/cycles/depth-zero.js - ⚠ eslint-plugin-import(no-cycle): Dependency cycle detected ╭─[cycles/depth-zero.js:1:21] 1 │ import { two } from "./es6/depth-three-star" diff --git a/crates/oxc_linter/src/snapshots/no_unresolved.snap b/crates/oxc_linter/src/snapshots/no_unresolved.snap index 22063c8d3..ce636baa6 100644 --- a/crates/oxc_linter/src/snapshots/no_unresolved.snap +++ b/crates/oxc_linter/src/snapshots/no_unresolved.snap @@ -55,9 +55,3 @@ expression: no_unresolved 1 │ export bar from "./does-not-exist" · ─── ╰──── - - ⚠ eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved - ╭─[index.js:1:19] - 1 │ var bar = require("./baz") - · ─────── - ╰──── diff --git a/crates/oxc_semantic/src/module_record/builder.rs b/crates/oxc_semantic/src/module_record/builder.rs index d66981d89..75bcc1ea6 100644 --- a/crates/oxc_semantic/src/module_record/builder.rs +++ b/crates/oxc_semantic/src/module_record/builder.rs @@ -18,19 +18,13 @@ impl ModuleRecordBuilder { } pub fn visit(&mut self, program: &Program) { + self.module_record.not_esm = true; // This avoids additional checks on TypeScript `TsModuleBlock` which // also has `ModuleDeclaration`s. for stmt in &program.body { - match stmt { - Statement::ModuleDeclaration(module_decl) => { - self.visit_module_declaration(module_decl); - } - Statement::ExpressionStatement(expr_stmt) => { - self.handle_cjs_export(&expr_stmt.expression); - } - stmt => { - self.search_top_level_cjs_require(stmt); - } + if let Statement::ModuleDeclaration(module_decl) = stmt { + self.module_record.not_esm = false; + self.visit_module_declaration(module_decl); } } @@ -345,73 +339,4 @@ impl ModuleRecordBuilder { ); } } - - /// Try to find require calls by searching all top-level statements - /// and add them to the module record - fn search_top_level_cjs_require(&mut self, stmt: &Statement) { - match stmt { - // var foo = require('bar'); - Statement::Declaration(Declaration::VariableDeclaration(var_decl)) => { - for declaration in &var_decl.declarations { - if let Some(init) = &declaration.init { - self.handle_cjs_require(init); - } - } - } - // require('foo') - Statement::ExpressionStatement(expr_stmt) => { - self.handle_cjs_require(&expr_stmt.expression); - } - _ => {} - } - } - - fn handle_cjs_require(&mut self, expr: &Expression) { - let Expression::CallExpression(call) = &expr else { - return; - }; - let Expression::Identifier(ident) = &call.callee else { - return; - }; - if ident.name != "require" { - return; - } - if let Some(Argument::Expression(Expression::StringLiteral(module))) = - call.arguments.first() - { - let module_request = NameSpan::new(module.value.to_compact_string(), module.span); - self.add_module_request(&module_request); - } - } - - // Add export binding for - // * exports.foo = bar - // * module.exports.foo = bar - // * module.exports = foo - fn handle_cjs_export(&mut self, expr: &Expression) { - let Expression::AssignmentExpression(assign_expr) = expr else { return }; - let AssignmentTarget::SimpleAssignmentTarget(target) = &assign_expr.left else { return }; - let SimpleAssignmentTarget::MemberAssignmentTarget(member_expr) = target else { return }; - match member_expr.object() { - // exports.foo = bar - Expression::Identifier(ident) if ident.name == "exports" => { - let Some((span, name)) = member_expr.static_property_info() else { return }; - self.add_export_binding(name.into(), span); - } - // module.exports = {} - Expression::Identifier(_) - if member_expr.is_specific_member_access("module", "exports") => - { - self.add_default_export(assign_expr.right.span()); - } - // module.exports.foo = bar - Expression::MemberExpression(inner_member_expr) - if inner_member_expr.is_specific_member_access("module", "exports") => - { - let Some((span, name)) = member_expr.static_property_info() else { return }; - self.add_export_binding(name.into(), span); - } - _ => {} - } - } } diff --git a/crates/oxc_syntax/src/module_record.rs b/crates/oxc_syntax/src/module_record.rs index 5a9e1b480..0b1521ef4 100644 --- a/crates/oxc_syntax/src/module_record.rs +++ b/crates/oxc_syntax/src/module_record.rs @@ -8,13 +8,18 @@ use rustc_hash::{FxHashMap, FxHasher}; use oxc_span::{CompactString, Span}; -/// Module Record +/// ESM Module Record +/// +/// All data inside this data structure are for ESM, no commonjs data is allowed. /// /// See /// * /// * #[derive(Default)] pub struct ModuleRecord { + /// This module has no import / export statements + pub not_esm: bool, + /// Resolved absolute path to this module record pub resolved_absolute_path: PathBuf, @@ -84,6 +89,7 @@ impl fmt::Debug for ModuleRecord { .unwrap_or_default(); let loaded_modules = format!("{{ {loaded_modules} }}"); f.debug_struct("ModuleRecord") + .field("not_esm", &self.not_esm) .field("resolved_absolute_path", &self.resolved_absolute_path) .field("requested_modules", &self.requested_modules) .field("loaded_modules", &loaded_modules)