Skip to content

Commit a9785e3

Browse files
authored
fix(parser,linter): consider typescript declarations for named exports (#10532)
Only VariableDeclaration, FunctionDeclaration and ClassDeclaration were considered when collecting named export declarations for the module record. Now the parser also considers TSTypeAliasDeclaration, TSInterfaceDeclaration, TSEnumDeclaration, TSImportEqualsDeclaration and TSModuleDeclaration named export declarations of the module. Maybe the other `decl.declaration.is_typescript_syntax()` check in `module_record.rs` also needs to be removed? Closes #10318 Closes #10556
1 parent 7059ffa commit a9785e3

File tree

5 files changed

+48
-19
lines changed

5 files changed

+48
-19
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export enum Foo {
2+
BAR = 0,
3+
}

crates/oxc_linter/src/rules/import/namespace.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,11 @@ fn test() {
485485
(r"import { a } from './oxc/indirect-export'; console.log(a.nothing)", None),
486486
// Issue: <https://github.com/oxc-project/oxc/issues/7696>
487487
(r"import * as acorn from 'acorn'; acorn.parse()", None),
488+
// https://github.com/oxc-project/oxc/issues/10318
489+
(
490+
r#"import * as lib from "./typescript-export-enum"; export const bar = lib.Foo.BAR;"#,
491+
None,
492+
),
488493
];
489494

490495
let fail = vec![

crates/oxc_parser/src/module_record.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,11 +317,6 @@ impl<'a> ModuleRecordBuilder<'a> {
317317
}
318318

319319
fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) {
320-
// ignore all TypeScript syntax as they overload
321-
if decl.declaration.as_ref().is_some_and(Declaration::is_typescript_syntax) {
322-
return;
323-
}
324-
325320
let module_request =
326321
decl.source.as_ref().map(|source| NameSpan::new(source.value, source.span));
327322

@@ -338,7 +333,7 @@ impl<'a> ModuleRecordBuilder<'a> {
338333
}
339334

340335
if let Some(d) = &decl.declaration {
341-
d.bound_names(&mut |ident| {
336+
iter_binding_identifiers_of_declaration(d, &mut |ident| {
342337
let export_name = ExportExportName::Name(NameSpan::new(ident.name, ident.span));
343338
let local_name = ExportLocalName::Name(NameSpan::new(ident.name, ident.span));
344339
let export_entry = ExportEntry {
@@ -388,6 +383,17 @@ impl<'a> ModuleRecordBuilder<'a> {
388383
}
389384
}
390385

386+
fn iter_binding_identifiers_of_declaration<'a, F>(decl: &Declaration<'a>, f: &mut F)
387+
where
388+
F: FnMut(&BindingIdentifier<'a>),
389+
{
390+
if let Declaration::VariableDeclaration(decl) = decl {
391+
decl.bound_names(f);
392+
} else if let Some(ident) = decl.id() {
393+
f(ident);
394+
}
395+
}
396+
391397
#[cfg(test)]
392398
mod module_record_tests {
393399
use oxc_allocator::Allocator;

napi/parser/test/esm.test.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,20 @@ describe('hasModuleSyntax', () => {
8484
});
8585

8686
describe('export type', () => {
87-
const code = [
88-
"export type * from 'mod'",
89-
"export type * as ns from 'mod'",
90-
'export type { foo }',
91-
'export { type foo }',
92-
"export type { foo } from 'mod'",
93-
];
94-
test.each(code)('%s', (s) => {
95-
const ret = parseSync('test.ts', s);
87+
const inputs = [
88+
["export type * from 'mod'", true],
89+
["export type * as ns from 'mod'", true],
90+
['export type { foo }', true],
91+
['export { type foo }', true],
92+
["export type { foo } from 'mod'", true],
93+
['export type Foo = {}', true],
94+
['export interface Bar {}', true],
95+
['export namespace Baz {}', false], // namespace isn't considered a typed export
96+
] as const;
97+
test.each(inputs)('%s', (source, isType) => {
98+
const ret = parseSync('test.ts', source);
9699
expect(ret.module.staticExports.length).toBe(1);
97100
expect(ret.module.staticExports[0].entries.length).toBe(1);
98-
expect(ret.module.staticExports[0].entries[0].isType).toBe(true);
101+
expect(ret.module.staticExports[0].entries[0].isType).toBe(isType);
99102
});
100103
});

tasks/coverage/snapshots/parser_typescript.snap

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ commit: 15392346
33
parser_typescript Summary:
44
AST Parsed : 6523/6531 (99.88%)
55
Positive Passed: 6511/6531 (99.69%)
6-
Negative Passed: 1308/5754 (22.73%)
6+
Negative Passed: 1309/5754 (22.75%)
77
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration24.ts
88

99
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment7.ts
@@ -2540,8 +2540,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsdocTypeNon
25402540

25412541
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsdocTypedefMissingType.ts
25422542

2543-
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsdocTypedefNoCrash2.ts
2544-
25452543
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsxCallElaborationCheckNoCrash1.tsx
25462544

25472545
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsxChildWrongType.tsx
@@ -13415,6 +13413,20 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private
1341513413
╰────
1341613414
help: Try insert a semicolon here
1341713415

13416+
× Duplicated export 'foo'
13417+
╭─[typescript/tests/cases/compiler/jsdocTypedefNoCrash2.ts:1:13]
13418+
1 │ export type foo = 5;
13419+
· ─┬─
13420+
· ╰── Export has already been declared here
13421+
2 │ /**
13422+
╰────
13423+
╭─[typescript/tests/cases/compiler/jsdocTypedefNoCrash2.ts:6:14]
13424+
5 │ */
13425+
6 │ export const foo = 5;
13426+
· ─┬─
13427+
· ╰── It cannot be redeclared here
13428+
╰────
13429+
1341813430
× Unexpected token
1341913431
╭─[typescript/tests/cases/compiler/jsxAttributeMissingInitializer.tsx:1:21]
1342013432
1 │ const x = <div foo= ></div>;

0 commit comments

Comments
 (0)