From 79af10070deff5ef8a08420e904f653ba1102645 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Fri, 27 Dec 2024 14:20:17 +0000 Subject: [PATCH] fix(semantic): reference flags not correctly resolved when after an export stmt (#8134) fixes https://github.com/oxc-project/oxc/issues/7879#issuecomment-2562574889 TLDR is curently here: https://github.com/oxc-project/oxc/blob/cdd121bfa4a66d5b2cf72a444f35e82daf81d11e/crates/oxc_semantic/src/builder.rs#L2130-L2135 `self.current_reference_flags.is_empty()` is not empty, which causes the current ref flags to be used (this is incorrect, we should be using a fresh version of reference flags). Buy setting ref flags to `None` on exit export node, this issue is avoided `export` **BEFORE** the reference ( incorrect reference flags) https://playground.oxc.rs/#eNpVjjuOwzAMRK8isElj7A/YxtttkVOkkR3aECCJBskkdgzdPZISG0ilGc3DcFbooQUXJmI1q/m3bJIZmII5fHx2lg9/p4hzTXWZsCL3N+RekB3qvRUxRyKDs2I8S61cEzRA0K7Al1geWaLaGVrlCzbgXdRNS08T7mYJHfnNKdsoA3GAdrBeMDUwWRbk3Jh1adn0jtYPUMsj5hOA8vP1/QuZ6OmMI5Yx2QQX3eCebLBx9K8FlYvK5I+ebiW9InckOX4uSOkBCrdvkw== `export` **AFTER** the reference ( correct reference flags) https://playground.oxc.rs/#eNpVjj2uwjAQhK9ibZMmen/Sa0JHwSlonLCJLNm70a6BhMh3xzEEQeUZz6fZWaCDBlwYWaJZzN6KSaYXDqb6+m6tVLsjHQmnknfeqpoDs8EpIp208Et6Q+I8Yum5ffTcqh3UwNAsIGdaH50p2gmaKGeswTuKm9aOR3yZObTsNxfFkvYsAZreesVUw2hFUXJj1mvLpl9o+YBoZcB8AlD/fn7/IRMdn3DAdUw2wZHr3YMNlgb/XFA4isL+4Pm6pheUljXHjwUp3QEtmnBd this PR fixes this issue by resetting the reference flags after exising an export stmt --- .../typescript/consistent_type_imports.rs | 8 ++++ crates/oxc_semantic/src/builder.rs | 28 ++++++------ .../tests/fixtures/oxc/ts/issue-7879.snap | 43 +++++++++++++++++++ .../tests/fixtures/oxc/ts/issue-7879.ts | 4 ++ 4 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap create mode 100644 crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts diff --git a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs index 607643b279a45..0c29635f85a5b 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs @@ -1517,6 +1517,14 @@ fn test() { // ", // None, // ), + ( + "import { Bar } from './bar'; +export type { Baz } from './baz'; + +export class Foo extends Bar {} +", + None, + ), ]; let fail = vec![ diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 9503630f0f2ff..7a98ecb01f443 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -1861,25 +1861,27 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { self.visit_declaration(declaration); } - for specifier in &it.specifiers { - // `export type { a }` or `export { type a }` -> `a` is a type reference - if it.export_kind.is_type() || specifier.export_kind.is_type() { - self.current_reference_flags = ReferenceFlags::Type; - } else { - // If the export specifier is not a explicit type export, we consider it as a potential - // type and value reference. If it references to a value in the end, we would delete the - // `ReferenceFlags::Type` flag in `fn resolve_references_for_current_scope`. - self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; - } - self.visit_export_specifier(specifier); - } - if let Some(source) = &it.source { self.visit_string_literal(source); + self.visit_export_specifiers(&it.specifiers); + } else { + for specifier in &it.specifiers { + // `export type { a }` or `export { type a }` -> `a` is a type reference + if it.export_kind.is_type() || specifier.export_kind.is_type() { + self.current_reference_flags = ReferenceFlags::Type; + } else { + // If the export specifier is not a explicit type export, we consider it as a potential + // type and value reference. If it references to a value in the end, we would delete the + // `ReferenceFlags::Type` flag in `fn resolve_references_for_current_scope`. + self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; + } + self.visit_export_specifier(specifier); + } } if let Some(with_clause) = &it.with_clause { self.visit_with_clause(with_clause); } + self.leave_node(kind); } diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap new file mode 100644 index 0000000000000..d45c6096c3b79 --- /dev/null +++ b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.snap @@ -0,0 +1,43 @@ +--- +source: crates/oxc_semantic/tests/main.rs +input_file: crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts +--- +[ + { + "children": [ + { + "children": [], + "flags": "ScopeFlags(StrictMode)", + "id": 1, + "node": "Class(Foo)", + "symbols": [] + } + ], + "flags": "ScopeFlags(StrictMode | Top)", + "id": 0, + "node": "Program", + "symbols": [ + { + "flags": "SymbolFlags(Import)", + "id": 0, + "name": "Bar", + "node": "ImportSpecifier(Bar)", + "references": [ + { + "flags": "ReferenceFlags(Read)", + "id": 0, + "name": "Bar", + "node_id": 17 + } + ] + }, + { + "flags": "SymbolFlags(Class)", + "id": 1, + "name": "Foo", + "node": "Class(Foo)", + "references": [] + } + ] + } +] diff --git a/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts new file mode 100644 index 0000000000000..8a0d8a108d6da --- /dev/null +++ b/crates/oxc_semantic/tests/fixtures/oxc/ts/issue-7879.ts @@ -0,0 +1,4 @@ +import { Bar } from "./bar"; +export type { Baz } from "./baz"; + +export class Foo extends Bar {}