feat(linter): handle cjs `module.exports = {} as default export (#2493)

This commit is contained in:
Boshen 2024-02-25 00:11:48 +08:00 committed by GitHub
parent f64c7e04a3
commit f5aadc767f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 36 additions and 30 deletions

View file

@ -665,11 +665,7 @@ impl<'a> MemberExpression<'a> {
}
}
pub fn through_optional_is_specific_member_access(
&'a self,
object: &str,
property: &str,
) -> bool {
pub fn through_optional_is_specific_member_access(&self, object: &str, property: &str) -> bool {
let object_matches = match self.object().without_parenthesized() {
Expression::ChainExpression(x) => match &x.expression {
ChainElement::CallExpression(_) => false,
@ -686,7 +682,7 @@ impl<'a> MemberExpression<'a> {
}
/// Whether it is a static member access `object.property`
pub fn is_specific_member_access(&'a self, object: &str, property: &str) -> bool {
pub fn is_specific_member_access(&self, object: &str, property: &str) -> bool {
self.object().is_specific_id(object)
&& self.static_property_name().is_some_and(|p| p == property)
}

View file

@ -39,7 +39,9 @@ impl Rule for Default {
fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record();
for import_entry in &module_record.import_entries {
let ImportImportName::Default(_) = import_entry.import_name else { continue };
let ImportImportName::Default(default_span) = import_entry.import_name else {
continue;
};
let specifier = import_entry.module_request.name();
let Some(remote_module_record_ref) = module_record.loaded_modules.get(specifier) else {
@ -49,10 +51,7 @@ impl Rule for Default {
if remote_module_record_ref.export_default.is_none()
&& !remote_module_record_ref.exported_bindings.contains_key("default")
{
ctx.diagnostic(DefaultDiagnostic(
specifier.to_string(),
import_entry.module_request.span(),
));
ctx.diagnostic(DefaultDiagnostic(specifier.to_string(), default_span));
}
}
}
@ -72,9 +71,7 @@ fn test() {
r#"import CoolClass from "./default-class""#,
r#"import bar, { baz } from "./default-export""#,
r#"import crypto from "crypto""#,
// TODO: module.exports
// r#"import common from "./common""#,
r#"import common from "./common""#,
// No longer valid syntax
// r#"export bar from "./bar""#,
// r#"export bar, { foo } from "./bar""#,

View file

@ -3,9 +3,9 @@ source: crates/oxc_linter/src/tester.rs
expression: default
---
⚠ eslint-plugin-import(default): No default export found in imported module "./named-exports"
╭─[index.js:1:17]
╭─[index.js:1:8]
1 │ import baz from "./named-exports"
· ─────────────────
· ───
╰────
help: does "./named-exports" have the default export?
@ -28,15 +28,15 @@ expression: default
╰────
⚠ eslint-plugin-import(default): No default export found in imported module "./re-export"
╭─[index.js:1:24]
╭─[index.js:1:8]
1 │ import barDefault from "./re-export"
· ─────────────
· ──────────
╰────
help: does "./re-export" have the default export?
⚠ eslint-plugin-import(default): No default export found in imported module "./typescript"
╭─[index.js:1:20]
╭─[index.js:1:8]
1 │ import foobar from "./typescript"
· ──────────────
· ──────
╰────
help: does "./typescript" have the default export?

View file

@ -367,21 +367,34 @@ impl ModuleRecordBuilder {
}
}
// Add export binding `foo` of `
// * exports.foo = bar`
// * module.exports.foo = bar`
// 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() {
Expression::Identifier(ident) if ident.name == "exports" => {}
Expression::MemberExpression(member_expr)
if matches!(member_expr.object(), Expression::Identifier(ident) if ident.name == "module")
&& member_expr.static_property_name() == Some("exports") => {}
_ => return,
// 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);
}
_ => {}
}
let Some((span, name)) = member_expr.static_property_info() else { return };
self.add_export_binding(name.into(), span);
}
}