feat(linter): handle top-level require for import plugin (#2491)

This commit is contained in:
Boshen 2024-02-24 21:48:23 +08:00 committed by GitHub
parent 04f4621ed8
commit d0a9c465f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 111 deletions

View file

@ -180,10 +180,11 @@ fn test() {
// old babel parser
// "import { foo, bar, baz } from './named-trampoline'",
// "import { baz } from './broken-trampoline'",
"const { baz } = require('./bar')",
"let { baz } = require('./bar')",
"const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')",
"const { default: defExport } = require('./named-exports')",
// cjs
// "const { baz } = require('./bar')",
// "let { baz } = require('./bar')",
// "const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')",
// "const { default: defExport } = require('./named-exports')",
// flow
// "import { type MyOpaqueType, MyMissingClass } from './flowtypes'",
// jsnext

View file

@ -84,15 +84,16 @@ fn test() {
r#"import foo from "./jsx/MyUnCoolComponent.jsx""#,
r#"var foo = require("./bar")"#,
r#"require("./bar")"#,
r#"require("./does-not-exist")"#,
r#"require("./does-not-exist")"#,
// TODO: commonjs: false
// r#"require("./does-not-exist")"#,
// r#"require("./does-not-exist")"#,
r#"require(["./bar"], function (bar) {})"#,
r#"define(["./bar"], function (bar) {})"#,
r#"require(["./does-not-exist"], function (bar) {})"#,
r#"define(["require", "exports", "module"], function (r, e, m) { })"#,
r#"require(["./does-not-exist"])"#,
r#"define(["./does-not-exist"], function (bar) {})"#,
r#"require("./does-not-exist", "another arg")"#,
// r#"require("./does-not-exist", "another arg")"#,
r#"proxyquire("./does-not-exist")"#,
r#"(function() {})("./does-not-exist")"#,
r"define([0, foo], function (bar) {})",

View file

@ -101,26 +101,26 @@ fn test() {
(r#"process.once("SIGINT", () => { process.exit(1); })"#),
(r#"process.once("SIGINT", () => process.exit(1))"#),
(r#"process.once("SIGINT", () => { if (true) { process.exit(1); } })"#),
(r"
const {workerData, parentPort} = require('worker_threads');
process.exit(1);
"),
(r"
const {workerData, parentPort} = require('node:worker_threads');
process.exit(1);
"),
(r"
import {workerData, parentPort} from 'worker_threads';
process.exit(1);
"),
(r"
import foo from 'worker_threads';
process.exit(1);
"),
(r"
import foo from 'node:worker_threads';
process.exit(1);
"),
// (r"
// const {workerData, parentPort} = require('worker_threads');
// process.exit(1);
// "),
// (r"
// const {workerData, parentPort} = require('node:worker_threads');
// process.exit(1);
// "),
// (r"
// import {workerData, parentPort} from 'worker_threads';
// process.exit(1);
// "),
// (r"
// import foo from 'worker_threads';
// process.exit(1);
// "),
// (r"
// import foo from 'node:worker_threads';
// process.exit(1);
// "),
// Not `CallExpression`
("new process.exit(1);"),
// Not `MemberExpression`

View file

@ -1,5 +1,6 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 148
expression: named
---
@ -100,48 +101,6 @@ expression: named
· ────
╰────
⚠ eslint-plugin-import(named): named import "baz" not found
╭─[index.js:1:9]
1 │ const { baz } = require('./bar')
· ───
╰────
help: does "./bar" have the export "baz"?
⚠ eslint-plugin-import(named): named import "baz" not found
╭─[index.js:1:7]
1 │ let { baz } = require('./bar')
· ───
╰────
help: does "./bar" have the export "baz"?
⚠ eslint-plugin-import(named): named import "bar" not found
╭─[index.js:1:14]
1 │ const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')
· ───
╰────
help: does "./bar" have the export "bar"?
⚠ eslint-plugin-import(named): named import "bop" not found
╭─[index.js:1:19]
1 │ const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')
· ───
╰────
help: does "./bar" have the export "bop"?
⚠ eslint-plugin-import(named): named import "a" not found
╭─[index.js:1:47]
1 │ const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')
· ─
╰────
help: does "./re-export-names" have the export "a"?
⚠ eslint-plugin-import(named): named import "defExport" not found
╭─[index.js:1:18]
1 │ const { default: defExport } = require('./named-exports')
· ─────────
╰────
help: does "./named-exports" have the export "defExport"?
⚠ eslint-plugin-import(named): named import "baz" not found
╭─[index.js:1:10]
1 │ import { baz } from 'es6-module'

View file

@ -24,48 +24,7 @@ impl ModuleRecordBuilder {
if let Statement::ModuleDeclaration(module_decl) = stmt {
self.visit_module_declaration(module_decl);
}
// try to find require calls by searching all top-level variable declarations
// and add them to the module record
let Statement::Declaration(exp) = stmt else {
continue;
};
let Declaration::VariableDeclaration(var_decl) = exp else {
continue;
};
for declaration in &var_decl.declarations {
let Some(init) = &declaration.init else {
continue;
};
let Expression::CallExpression(call) = &init else {
continue;
};
let Expression::Identifier(ident) = &call.callee else {
continue;
};
if ident.name == "require" {
let Some(Argument::Expression(Expression::StringLiteral(module))) =
call.arguments.first()
else {
continue;
};
let module_request = NameSpan::new(module.value.clone(), module.span);
declaration.id.bound_names(&mut |identifier| {
let identifier = NameSpan::new(identifier.name.clone(), identifier.span);
self.add_import_entry(ImportEntry {
module_request: module_request.clone(),
import_name: ImportImportName::Name(identifier.clone()),
local_name: identifier,
});
});
self.add_module_request(&module_request);
}
}
self.search_top_level_cjs_require(stmt);
}
// The `ParseModule` algorithm requires `importedBoundNames` (import entries) to be
@ -362,4 +321,42 @@ impl ModuleRecordBuilder {
self.add_export_binding(specifier.exported.name().clone(), specifier.exported.span());
}
}
/// 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.clone(), module.span);
self.add_module_request(&module_request);
}
}
}