mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(linter): implement re-exports (#877)
This commit is contained in:
parent
1d03ec32ff
commit
4e5f63a67d
4 changed files with 147 additions and 31 deletions
|
|
@ -4,7 +4,7 @@ use oxc_diagnostics::{
|
||||||
};
|
};
|
||||||
use oxc_macros::declare_oxc_lint;
|
use oxc_macros::declare_oxc_lint;
|
||||||
use oxc_span::{Atom, Span};
|
use oxc_span::{Atom, Span};
|
||||||
use oxc_syntax::module_record::ImportImportName;
|
use oxc_syntax::module_record::{ExportImportName, ImportImportName};
|
||||||
|
|
||||||
use crate::{context::LintContext, rule::Rule};
|
use crate::{context::LintContext, rule::Rule};
|
||||||
|
|
||||||
|
|
@ -61,6 +61,30 @@ impl Rule for Named {
|
||||||
import_name.span(),
|
import_name.span(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for export_entry in &module_record.indirect_export_entries {
|
||||||
|
let Some(module_request) = &export_entry.module_request else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let ExportImportName::Name(import_name) = &export_entry.import_name else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let specifier = module_request.name();
|
||||||
|
// Get remote module record
|
||||||
|
let Some(remote_module_record_ref) = module_record.loaded_modules.get(specifier) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let remote_module_record = remote_module_record_ref.value();
|
||||||
|
// Check remote bindings
|
||||||
|
if remote_module_record.exported_bindings.contains_key(import_name.name()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ctx.diagnostic(NamedDiagnostic(
|
||||||
|
import_name.name().clone(),
|
||||||
|
specifier.clone(),
|
||||||
|
import_name.span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,13 +103,16 @@ fn test() {
|
||||||
"import { destructingAssign } from './named-exports'",
|
"import { destructingAssign } from './named-exports'",
|
||||||
"import { destructingRenamedAssign } from './named-exports'",
|
"import { destructingRenamedAssign } from './named-exports'",
|
||||||
"import { ActionTypes } from './qc'",
|
"import { ActionTypes } from './qc'",
|
||||||
|
// TODO: export *
|
||||||
// "import {a, b, c, d} from './re-export'",
|
// "import {a, b, c, d} from './re-export'",
|
||||||
// "import {a, b, c} from './re-export-common-star'",
|
// "import {a, b, c} from './re-export-common-star'",
|
||||||
// "import {RuleTester} from './re-export-node_modules'",
|
// "import {RuleTester} from './re-export-node_modules'",
|
||||||
// "import { jsxFoo } from './jsx/AnotherComponent'",
|
// "import { jsxFoo } from './jsx/AnotherComponent'",
|
||||||
"import {a, b, d} from './common'; // eslint-disable-line named",
|
"import {a, b, d} from './common'; // eslint-disable-line named",
|
||||||
"import { foo, bar } from './re-export-names'",
|
"import { foo, bar } from './re-export-names'",
|
||||||
|
// TODO: module.exports
|
||||||
// "import { foo, bar } from './common'",
|
// "import { foo, bar } from './common'",
|
||||||
|
// ignore core modules by default
|
||||||
"import { foo } from 'crypto'",
|
"import { foo } from 'crypto'",
|
||||||
// "import { zoob } from 'a'",
|
// "import { zoob } from 'a'",
|
||||||
"import { someThing } from './test-module'",
|
"import { someThing } from './test-module'",
|
||||||
|
|
@ -148,29 +175,28 @@ fn test() {
|
||||||
"import { ActionTypes1 } from './qc'",
|
"import { ActionTypes1 } from './qc'",
|
||||||
"import {a, b, c, d, e} from './re-export'",
|
"import {a, b, c, d, e} from './re-export'",
|
||||||
"import { a } from './re-export-names'",
|
"import { a } from './re-export-names'",
|
||||||
// "export { bar } from './bar'",
|
"export { bar } from './bar'",
|
||||||
// "export bar2, { bar } from './bar'",
|
"export bar2, { bar } from './bar'",
|
||||||
// old babel parser
|
// old babel parser
|
||||||
// "import { foo, bar, baz } from './named-trampoline'",
|
// "import { foo, bar, baz } from './named-trampoline'",
|
||||||
// "import { baz } from './broken-trampoline'",
|
// "import { baz } from './broken-trampoline'",
|
||||||
//
|
"const { baz } = require('./bar')",
|
||||||
// "const { baz } = require('./bar')",
|
"let { baz } = require('./bar')",
|
||||||
// "let { baz } = require('./bar')",
|
"const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')",
|
||||||
// "const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')",
|
"const { default: defExport } = require('./named-exports')",
|
||||||
// "const { default: defExport } = require('./named-exports')",
|
|
||||||
// flow
|
// flow
|
||||||
// "import { type MyOpaqueType, MyMissingClass } from './flowtypes'",
|
// "import { type MyOpaqueType, MyMissingClass } from './flowtypes'",
|
||||||
// jsnext
|
// jsnext
|
||||||
// "/*jsnext*/ import { createSnorlax } from 'redux'",
|
// "/*jsnext*/ import { createSnorlax } from 'redux'",
|
||||||
// "import { baz } from 'es6-module'",
|
"import { baz } from 'es6-module'",
|
||||||
"import { foo, bar, bap } from './re-export-default'",
|
"import { foo, bar, bap } from './re-export-default'",
|
||||||
"import { default as barDefault } from './re-export'",
|
"import { default as barDefault } from './re-export'",
|
||||||
// export all
|
// export all
|
||||||
"import { bar } from './export-all'",
|
"import { bar } from './export-all'",
|
||||||
// TypeScript
|
// TypeScript
|
||||||
// Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead.
|
// 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 { NotExported } from './typescript-export-assign-object'",
|
||||||
// "import { FooBar } from './typescript-export-assign-object'",
|
"import { FooBar } from './typescript-export-assign-object'",
|
||||||
];
|
];
|
||||||
|
|
||||||
Tester::new_without_config(Named::NAME, pass, fail)
|
Tester::new_without_config(Named::NAME, pass, fail)
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,68 @@ expression: named
|
||||||
╰────
|
╰────
|
||||||
help: does "./re-export-names" have the export "a"?
|
help: does "./re-export-names" have the export "a"?
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "bar" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ export { bar } from './bar'
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
help: does "./bar" have the export "bar"?
|
||||||
|
|
||||||
|
× Unexpected token
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ export bar2, { bar } from './bar'
|
||||||
|
· ────
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "baz" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ const { baz } = require('./bar')
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
help: does "./bar" have the export "baz"?
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "baz" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ let { baz } = require('./bar')
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
help: does "./bar" have the export "baz"?
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "bar" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
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
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
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
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
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
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ const { default: defExport } = require('./named-exports')
|
||||||
|
· ─────────
|
||||||
|
╰────
|
||||||
|
help: does "./named-exports" have the export "defExport"?
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "baz" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ import { baz } from 'es6-module'
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
help: does "es6-module" have the export "baz"?
|
||||||
|
|
||||||
⚠ eslint-plugin-import(named): named import "bap" not found
|
⚠ eslint-plugin-import(named): named import "bap" not found
|
||||||
╭─[named.js:1:1]
|
╭─[named.js:1:1]
|
||||||
1 │ import { foo, bar, bap } from './re-export-default'
|
1 │ import { foo, bar, bap } from './re-export-default'
|
||||||
|
|
@ -107,4 +169,18 @@ expression: named
|
||||||
╰────
|
╰────
|
||||||
help: does "./export-all" have the export "bar"?
|
help: does "./export-all" have the export "bar"?
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "NotExported" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ import { NotExported } from './typescript-export-assign-object'
|
||||||
|
· ───────────
|
||||||
|
╰────
|
||||||
|
help: does "./typescript-export-assign-object" have the export "NotExported"?
|
||||||
|
|
||||||
|
⚠ eslint-plugin-import(named): named import "FooBar" not found
|
||||||
|
╭─[named.js:1:1]
|
||||||
|
1 │ import { FooBar } from './typescript-export-assign-object'
|
||||||
|
· ──────
|
||||||
|
╰────
|
||||||
|
help: does "./typescript-export-assign-object" have the export "FooBar"?
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -303,17 +303,16 @@ impl ModuleRecordBuilder {
|
||||||
|
|
||||||
if let Some(decl) = &decl.declaration {
|
if let Some(decl) = &decl.declaration {
|
||||||
decl.bound_names(&mut |ident| {
|
decl.bound_names(&mut |ident| {
|
||||||
|
let export_name =
|
||||||
|
ExportExportName::Name(NameSpan::new(ident.name.clone(), ident.span));
|
||||||
|
let local_name =
|
||||||
|
ExportLocalName::Name(NameSpan::new(ident.name.clone(), ident.span));
|
||||||
let export_entry = ExportEntry {
|
let export_entry = ExportEntry {
|
||||||
|
span: Span::default(),
|
||||||
module_request: module_request.clone(),
|
module_request: module_request.clone(),
|
||||||
export_name: ExportExportName::Name(NameSpan::new(
|
import_name: ExportImportName::Null,
|
||||||
ident.name.clone(),
|
export_name,
|
||||||
ident.span,
|
local_name,
|
||||||
)),
|
|
||||||
local_name: ExportLocalName::Name(NameSpan::new(
|
|
||||||
ident.name.clone(),
|
|
||||||
ident.span,
|
|
||||||
)),
|
|
||||||
..ExportEntry::default()
|
|
||||||
};
|
};
|
||||||
self.add_export_entry(export_entry);
|
self.add_export_entry(export_entry);
|
||||||
self.add_export_binding(ident.name.clone(), ident.span);
|
self.add_export_binding(ident.name.clone(), ident.span);
|
||||||
|
|
@ -321,17 +320,32 @@ impl ModuleRecordBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
for specifier in &decl.specifiers {
|
for specifier in &decl.specifiers {
|
||||||
let export_entry = ExportEntry {
|
let export_name = ExportExportName::Name(NameSpan::new(
|
||||||
module_request: module_request.clone(),
|
specifier.exported.name().clone(),
|
||||||
export_name: ExportExportName::Name(NameSpan::new(
|
specifier.exported.span(),
|
||||||
specifier.exported.name().clone(),
|
));
|
||||||
specifier.exported.span(),
|
let import_name = if module_request.is_some() {
|
||||||
)),
|
ExportImportName::Name(NameSpan::new(
|
||||||
local_name: ExportLocalName::Name(NameSpan::new(
|
|
||||||
specifier.local.name().clone(),
|
specifier.local.name().clone(),
|
||||||
specifier.local.span(),
|
specifier.local.span(),
|
||||||
)),
|
))
|
||||||
..ExportEntry::default()
|
} else {
|
||||||
|
ExportImportName::Null
|
||||||
|
};
|
||||||
|
let local_name = if module_request.is_some() {
|
||||||
|
ExportLocalName::Null
|
||||||
|
} else {
|
||||||
|
ExportLocalName::Name(NameSpan::new(
|
||||||
|
specifier.local.name().clone(),
|
||||||
|
specifier.local.span(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let export_entry = ExportEntry {
|
||||||
|
span: Span::default(),
|
||||||
|
module_request: module_request.clone(),
|
||||||
|
import_name,
|
||||||
|
export_name,
|
||||||
|
local_name,
|
||||||
};
|
};
|
||||||
self.add_export_entry(export_entry);
|
self.add_export_entry(export_entry);
|
||||||
self.add_export_binding(specifier.exported.name().clone(), specifier.exported.span());
|
self.add_export_binding(specifier.exported.name().clone(), specifier.exported.span());
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ mod module_record_tests {
|
||||||
let export_entry = ExportEntry {
|
let export_entry = ExportEntry {
|
||||||
module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))),
|
module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))),
|
||||||
export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(9, 10))),
|
export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(9, 10))),
|
||||||
local_name: ExportLocalName::Name(NameSpan::new("x".into(), Span::new(9, 10))),
|
import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))),
|
||||||
..ExportEntry::default()
|
..ExportEntry::default()
|
||||||
};
|
};
|
||||||
assert_eq!(module_record.indirect_export_entries.len(), 1);
|
assert_eq!(module_record.indirect_export_entries.len(), 1);
|
||||||
|
|
@ -165,7 +165,7 @@ mod module_record_tests {
|
||||||
let export_entry = ExportEntry {
|
let export_entry = ExportEntry {
|
||||||
module_request: Some(NameSpan::new("mod".into(), Span::new(23, 28))),
|
module_request: Some(NameSpan::new("mod".into(), Span::new(23, 28))),
|
||||||
export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(14, 15))),
|
export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(14, 15))),
|
||||||
local_name: ExportLocalName::Name(NameSpan::new("x".into(), Span::new(9, 10))),
|
import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))),
|
||||||
..ExportEntry::default()
|
..ExportEntry::default()
|
||||||
};
|
};
|
||||||
assert_eq!(module_record.indirect_export_entries.len(), 1);
|
assert_eq!(module_record.indirect_export_entries.len(), 1);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue