diff --git a/crates/oxc_linter/fixtures/import/typescript-export-enum.ts b/crates/oxc_linter/fixtures/import/typescript-export-enum.ts new file mode 100644 index 0000000000000..0e64334374aeb --- /dev/null +++ b/crates/oxc_linter/fixtures/import/typescript-export-enum.ts @@ -0,0 +1,3 @@ +export enum Foo { + BAR = 0, +} diff --git a/crates/oxc_linter/src/rules/import/namespace.rs b/crates/oxc_linter/src/rules/import/namespace.rs index 4655069b93d56..38b9126fbcb3c 100644 --- a/crates/oxc_linter/src/rules/import/namespace.rs +++ b/crates/oxc_linter/src/rules/import/namespace.rs @@ -485,6 +485,11 @@ fn test() { (r"import { a } from './oxc/indirect-export'; console.log(a.nothing)", None), // Issue: (r"import * as acorn from 'acorn'; acorn.parse()", None), + // https://github.com/oxc-project/oxc/issues/10318 + ( + r#"import * as lib from "./typescript-export-enum"; export const bar = lib.Foo.BAR;"#, + None, + ), ]; let fail = vec![ diff --git a/crates/oxc_parser/src/module_record.rs b/crates/oxc_parser/src/module_record.rs index 746cea8d824a8..b32113ffbac70 100644 --- a/crates/oxc_parser/src/module_record.rs +++ b/crates/oxc_parser/src/module_record.rs @@ -317,11 +317,6 @@ impl<'a> ModuleRecordBuilder<'a> { } fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) { - // ignore all TypeScript syntax as they overload - if decl.declaration.as_ref().is_some_and(Declaration::is_typescript_syntax) { - return; - } - let module_request = decl.source.as_ref().map(|source| NameSpan::new(source.value, source.span)); @@ -338,7 +333,7 @@ impl<'a> ModuleRecordBuilder<'a> { } if let Some(d) = &decl.declaration { - d.bound_names(&mut |ident| { + iter_binding_identifiers_of_declaration(d, &mut |ident| { let export_name = ExportExportName::Name(NameSpan::new(ident.name, ident.span)); let local_name = ExportLocalName::Name(NameSpan::new(ident.name, ident.span)); let export_entry = ExportEntry { @@ -388,6 +383,17 @@ impl<'a> ModuleRecordBuilder<'a> { } } +fn iter_binding_identifiers_of_declaration<'a, F>(decl: &Declaration<'a>, f: &mut F) +where + F: FnMut(&BindingIdentifier<'a>), +{ + if let Declaration::VariableDeclaration(decl) = decl { + decl.bound_names(f); + } else if let Some(ident) = decl.id() { + f(ident); + } +} + #[cfg(test)] mod module_record_tests { use oxc_allocator::Allocator; diff --git a/napi/parser/test/esm.test.ts b/napi/parser/test/esm.test.ts index 4b8c50656b51c..157456dd91242 100644 --- a/napi/parser/test/esm.test.ts +++ b/napi/parser/test/esm.test.ts @@ -84,17 +84,20 @@ describe('hasModuleSyntax', () => { }); describe('export type', () => { - const code = [ - "export type * from 'mod'", - "export type * as ns from 'mod'", - 'export type { foo }', - 'export { type foo }', - "export type { foo } from 'mod'", - ]; - test.each(code)('%s', (s) => { - const ret = parseSync('test.ts', s); + const inputs = [ + ["export type * from 'mod'", true], + ["export type * as ns from 'mod'", true], + ['export type { foo }', true], + ['export { type foo }', true], + ["export type { foo } from 'mod'", true], + ['export type Foo = {}', true], + ['export interface Bar {}', true], + ['export namespace Baz {}', false], // namespace isn't considered a typed export + ] as const; + test.each(inputs)('%s', (source, isType) => { + const ret = parseSync('test.ts', source); expect(ret.module.staticExports.length).toBe(1); expect(ret.module.staticExports[0].entries.length).toBe(1); - expect(ret.module.staticExports[0].entries[0].isType).toBe(true); + expect(ret.module.staticExports[0].entries[0].isType).toBe(isType); }); }); diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index 8a5e3d4b751f4..6e36d22569340 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -3,7 +3,7 @@ commit: 15392346 parser_typescript Summary: AST Parsed : 6523/6531 (99.88%) Positive Passed: 6511/6531 (99.69%) -Negative Passed: 1308/5754 (22.73%) +Negative Passed: 1309/5754 (22.75%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ClassDeclaration24.ts 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 Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsdocTypedefMissingType.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsdocTypedefNoCrash2.ts - Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/jsxCallElaborationCheckNoCrash1.tsx 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 ╰──── help: Try insert a semicolon here + × Duplicated export 'foo' + ╭─[typescript/tests/cases/compiler/jsdocTypedefNoCrash2.ts:1:13] + 1 │ export type foo = 5; + · ─┬─ + · ╰── Export has already been declared here + 2 │ /** + ╰──── + ╭─[typescript/tests/cases/compiler/jsdocTypedefNoCrash2.ts:6:14] + 5 │ */ + 6 │ export const foo = 5; + · ─┬─ + · ╰── It cannot be redeclared here + ╰──── + × Unexpected token ╭─[typescript/tests/cases/compiler/jsxAttributeMissingInitializer.tsx:1:21] 1 │ const x =
;