mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 13:18:59 +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_span::{Atom, Span};
|
||||
use oxc_syntax::module_record::ImportImportName;
|
||||
use oxc_syntax::module_record::{ExportImportName, ImportImportName};
|
||||
|
||||
use crate::{context::LintContext, rule::Rule};
|
||||
|
||||
|
|
@ -61,6 +61,30 @@ impl Rule for Named {
|
|||
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 { destructingRenamedAssign } from './named-exports'",
|
||||
"import { ActionTypes } from './qc'",
|
||||
// TODO: export *
|
||||
// "import {a, b, c, d} from './re-export'",
|
||||
// "import {a, b, c} from './re-export-common-star'",
|
||||
// "import {RuleTester} from './re-export-node_modules'",
|
||||
// "import { jsxFoo } from './jsx/AnotherComponent'",
|
||||
"import {a, b, d} from './common'; // eslint-disable-line named",
|
||||
"import { foo, bar } from './re-export-names'",
|
||||
// TODO: module.exports
|
||||
// "import { foo, bar } from './common'",
|
||||
// ignore core modules by default
|
||||
"import { foo } from 'crypto'",
|
||||
// "import { zoob } from 'a'",
|
||||
"import { someThing } from './test-module'",
|
||||
|
|
@ -148,29 +175,28 @@ fn test() {
|
|||
"import { ActionTypes1 } from './qc'",
|
||||
"import {a, b, c, d, e} from './re-export'",
|
||||
"import { a } from './re-export-names'",
|
||||
// "export { bar } from './bar'",
|
||||
// "export bar2, { bar } from './bar'",
|
||||
"export { bar } from './bar'",
|
||||
"export bar2, { bar } from './bar'",
|
||||
// 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')",
|
||||
"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
|
||||
// "/*jsnext*/ import { createSnorlax } from 'redux'",
|
||||
// "import { baz } from 'es6-module'",
|
||||
"import { baz } from 'es6-module'",
|
||||
"import { foo, bar, bap } from './re-export-default'",
|
||||
"import { default as barDefault } from './re-export'",
|
||||
// export all
|
||||
"import { bar } from './export-all'",
|
||||
// TypeScript
|
||||
// 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'",
|
||||
"import { NotExported } from './typescript-export-assign-object'",
|
||||
"import { FooBar } from './typescript-export-assign-object'",
|
||||
];
|
||||
|
||||
Tester::new_without_config(Named::NAME, pass, fail)
|
||||
|
|
|
|||
|
|
@ -86,6 +86,68 @@ expression: named
|
|||
╰────
|
||||
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
|
||||
╭─[named.js:1:1]
|
||||
1 │ import { foo, bar, bap } from './re-export-default'
|
||||
|
|
@ -107,4 +169,18 @@ expression: named
|
|||
╰────
|
||||
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 {
|
||||
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 {
|
||||
span: Span::default(),
|
||||
module_request: module_request.clone(),
|
||||
export_name: ExportExportName::Name(NameSpan::new(
|
||||
ident.name.clone(),
|
||||
ident.span,
|
||||
)),
|
||||
local_name: ExportLocalName::Name(NameSpan::new(
|
||||
ident.name.clone(),
|
||||
ident.span,
|
||||
)),
|
||||
..ExportEntry::default()
|
||||
import_name: ExportImportName::Null,
|
||||
export_name,
|
||||
local_name,
|
||||
};
|
||||
self.add_export_entry(export_entry);
|
||||
self.add_export_binding(ident.name.clone(), ident.span);
|
||||
|
|
@ -321,17 +320,32 @@ impl ModuleRecordBuilder {
|
|||
}
|
||||
|
||||
for specifier in &decl.specifiers {
|
||||
let export_entry = ExportEntry {
|
||||
module_request: module_request.clone(),
|
||||
export_name: ExportExportName::Name(NameSpan::new(
|
||||
specifier.exported.name().clone(),
|
||||
specifier.exported.span(),
|
||||
)),
|
||||
local_name: ExportLocalName::Name(NameSpan::new(
|
||||
let export_name = ExportExportName::Name(NameSpan::new(
|
||||
specifier.exported.name().clone(),
|
||||
specifier.exported.span(),
|
||||
));
|
||||
let import_name = if module_request.is_some() {
|
||||
ExportImportName::Name(NameSpan::new(
|
||||
specifier.local.name().clone(),
|
||||
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_binding(specifier.exported.name().clone(), specifier.exported.span());
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ mod module_record_tests {
|
|||
let export_entry = ExportEntry {
|
||||
module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))),
|
||||
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()
|
||||
};
|
||||
assert_eq!(module_record.indirect_export_entries.len(), 1);
|
||||
|
|
@ -165,7 +165,7 @@ mod module_record_tests {
|
|||
let export_entry = ExportEntry {
|
||||
module_request: Some(NameSpan::new("mod".into(), Span::new(23, 28))),
|
||||
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()
|
||||
};
|
||||
assert_eq!(module_record.indirect_export_entries.len(), 1);
|
||||
|
|
|
|||
Loading…
Reference in a new issue