feat(semantic): add SymbolFlags::Ambient for declaration with declare modifier (#10355)

Part of https://github.com/oxc-project/oxc/issues/7951.

After we have a `SymbolFlags::Ambient` within a Symbol that is declared by the `declare` modifier,  we can determine whether to remove an `ExportSpecifier` by tracing its referent and examining its `SymbolFlags`.
This commit is contained in:
Dunqing committed 2025-04-11 09:58:46 +00:00
1 parent dd2aef0bf2
commit 1a37e3fa37
8 files changed
+76 -41

No files matched your search

+54 -32
View File
@@ -20,7 +20,12 @@ pub trait Binder<'a> {
impl<'a> Binder<'a> for VariableDeclarator<'a> {
fn bind(&self, builder: &mut SemanticBuilder<'a>) {
let (includes, excludes) = match self.kind {
let is_declare = builder
.nodes
.parent_kind(builder.current_node_id)
.is_some_and(|kind| matches!(kind, AstKind::VariableDeclaration(decl) if decl.declare));
let (mut includes, excludes) = match self.kind {
VariableDeclarationKind::Const
| VariableDeclarationKind::Using
| VariableDeclarationKind::AwaitUsing => (
@@ -35,6 +40,10 @@ impl<'a> Binder<'a> for VariableDeclarator<'a> {
}
};
if is_declare {
includes |= SymbolFlags::Ambient;
}
if self.kind.is_lexical() {
self.id.bound_names(&mut |ident| {
let symbol_id = builder.declare_symbol(ident.span, &ident.name, includes, excludes);
@@ -117,13 +126,14 @@ impl<'a> Binder<'a> for VariableDeclarator<'a> {
impl<'a> Binder<'a> for Class<'a> {
fn bind(&self, builder: &mut SemanticBuilder) {
let includes = if self.declare {
SymbolFlags::Class | SymbolFlags::Ambient
} else {
SymbolFlags::Class
};
let Some(ident) = &self.id else { return };
let symbol_id = builder.declare_symbol(
ident.span,
&ident.name,
SymbolFlags::Class,
SymbolFlags::ClassExcludes,
);
let symbol_id =
builder.declare_symbol(ident.span, &ident.name, includes, SymbolFlags::ClassExcludes);
ident.symbol_id.set(Some(symbol_id));
}
}
@@ -152,30 +162,32 @@ fn is_function_part_of_if_statement(function: &Function, builder: &SemanticBuild
impl<'a> Binder<'a> for Function<'a> {
fn bind(&self, builder: &mut SemanticBuilder) {
let includes = if self.declare {
SymbolFlags::Function | SymbolFlags::Ambient
} else {
SymbolFlags::Function
};
if let Some(ident) = &self.id {
if is_function_part_of_if_statement(self, builder) {
let symbol_id = builder.scoping.create_symbol(
ident.span,
ident.name.into(),
SymbolFlags::Function,
includes,
ScopeId::new(u32::MAX - 1), // Not bound to any scope.
builder.current_node_id,
);
ident.symbol_id.set(Some(symbol_id));
} else {
let symbol_id = builder.declare_symbol(
ident.span,
&ident.name,
SymbolFlags::Function,
if builder.source_type.is_typescript() {
SymbolFlags::FunctionExcludes
} else {
// `var x; function x() {}` is valid in non-strict mode, but `TypeScript`
// doesn't care about non-strict mode, so we need to exclude this,
// and further check in checker.
SymbolFlags::FunctionExcludes - SymbolFlags::FunctionScopedVariable
},
);
let excludes = if builder.source_type.is_typescript() {
SymbolFlags::FunctionExcludes
} else {
// `var x; function x() {}` is valid in non-strict mode, but `TypeScript`
// doesn't care about non-strict mode, so we need to exclude this,
// and further check in checker.
SymbolFlags::FunctionExcludes - SymbolFlags::FunctionScopedVariable
};
let symbol_id = builder.declare_symbol(ident.span, &ident.name, includes, excludes);
ident.symbol_id.set(Some(symbol_id));
}
}
@@ -336,10 +348,15 @@ impl<'a> Binder<'a> for TSImportEqualsDeclaration<'a> {
impl<'a> Binder<'a> for TSTypeAliasDeclaration<'a> {
fn bind(&self, builder: &mut SemanticBuilder) {
let includes = if self.declare {
SymbolFlags::TypeAlias | SymbolFlags::Ambient
} else {
SymbolFlags::TypeAlias
};
let symbol_id = builder.declare_symbol(
self.id.span,
&self.id.name,
SymbolFlags::TypeAlias,
includes,
SymbolFlags::TypeAliasExcludes,
);
self.id.symbol_id.set(Some(symbol_id));
@@ -348,10 +365,15 @@ impl<'a> Binder<'a> for TSTypeAliasDeclaration<'a> {
impl<'a> Binder<'a> for TSInterfaceDeclaration<'a> {
fn bind(&self, builder: &mut SemanticBuilder) {
let includes = if self.declare {
SymbolFlags::Interface | SymbolFlags::Ambient
} else {
SymbolFlags::Interface
};
let symbol_id = builder.declare_symbol(
self.id.span,
&self.id.name,
SymbolFlags::Interface,
includes,
SymbolFlags::InterfaceExcludes,
);
self.id.symbol_id.set(Some(symbol_id));
@@ -361,11 +383,11 @@ impl<'a> Binder<'a> for TSInterfaceDeclaration<'a> {
impl<'a> Binder<'a> for TSEnumDeclaration<'a> {
fn bind(&self, builder: &mut SemanticBuilder) {
let is_const = self.r#const;
let includes = if is_const { SymbolFlags::ConstEnum } else { SymbolFlags::RegularEnum };
let excludes = if is_const {
SymbolFlags::ConstEnumExcludes
let includes = if self.declare { SymbolFlags::Ambient } else { SymbolFlags::empty() };
let (includes, excludes) = if is_const {
(SymbolFlags::ConstEnum | includes, SymbolFlags::ConstEnumExcludes)
} else {
SymbolFlags::RegularEnumExcludes
(SymbolFlags::RegularEnum | includes, SymbolFlags::RegularEnumExcludes)
};
let symbol_id = builder.declare_symbol(self.id.span, &self.id.name, includes, excludes);
self.id.symbol_id.set(Some(symbol_id));
@@ -392,16 +414,16 @@ impl<'a> Binder<'a> for TSModuleDeclaration<'a> {
let TSModuleDeclarationName::Identifier(id) = &self.id else { return };
let instantiated =
get_module_instance_state(builder, self, builder.current_node_id).is_instantiated();
let (includes, excludes) = if instantiated {
let (mut includes, excludes) = if instantiated {
(SymbolFlags::ValueModule, SymbolFlags::ValueModuleExcludes)
} else {
(SymbolFlags::NameSpaceModule, SymbolFlags::NamespaceModuleExcludes)
};
// At declaration time a module has no value declaration it is only when a value declaration
// is made inside a the scope of a module that the symbol is modified
let ambient = if self.declare { SymbolFlags::Ambient } else { SymbolFlags::None };
let symbol_id = builder.declare_symbol(id.span, &id.name, includes | ambient, excludes);
if self.declare {
includes |= SymbolFlags::Ambient;
}
let symbol_id = builder.declare_symbol(id.span, &id.name, includes, excludes);
id.set_symbol_id(symbol_id);
}
@@ -41,7 +41,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/decorators/clas
"node": "Program",
"symbols": [
{
"flags": "SymbolFlags(Function)",
"flags": "SymbolFlags(Function | Ambient)",
"id": 0,
"name": "deco",
"node": "Function(deco)",
@@ -33,7 +33,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/jsx/this-jsxide
"node": "Program",
"symbols": [
{
"flags": "SymbolFlags(BlockScopedVariable | ConstVariable)",
"flags": "SymbolFlags(BlockScopedVariable | ConstVariable | Ambient)",
"id": 0,
"name": "React",
"node": "VariableDeclarator(React)",
@@ -68,7 +68,7 @@ input_file: crates/oxc_semantic/tests/fixtures/typescript-eslint/type-declaratio
]
},
{
"flags": "SymbolFlags(Function)",
"flags": "SymbolFlags(Function | Ambient)",
"id": 2,
"name": "setAlignment",
"node": "Function(setAlignment)",
+11 -1
View File
@@ -125,7 +125,12 @@ bitflags! {
const TypeParameter = 1 << 13;
const NameSpaceModule = 1 << 14;
const ValueModule = 1 << 15;
// In a dts file or there is a declare flag
/// Declared with `declare` modifier, like `declare function x() {}`.
//
// This flag is not part of TypeScript's `SymbolFlags`, it comes from TypeScript's `NodeFlags`. We introduced it into
// here because `NodeFlags` is incomplete and we only can access to `NodeFlags` in the Semantic, but we also need to
// access it in the Transformer.
// https://github.com/microsoft/TypeScript/blob/15392346d05045742e653eab5c87538ff2a3c863/src/compiler/types.ts#L819-L820
const Ambient = 1 << 16;
const Enum = Self::ConstEnum.bits() | Self::RegularEnum.bits();
@@ -245,6 +250,11 @@ impl SymbolFlags {
self.contains(Self::TypeImport)
}
#[inline]
pub fn is_ambient(&self) -> bool {
self.contains(Self::Ambient)
}
/// If true, then the symbol can be referenced by a type reference
#[inline]
pub fn can_be_referenced_by_type(&self) -> bool {
+1 -1
View File
@@ -1458,7 +1458,7 @@ semantic error: Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
rebuilt : ScopeId(0): [ScopeId(1)]
Symbol flags mismatch for "C":
after transform: SymbolId(0): SymbolFlags(Class | Function)
after transform: SymbolId(0): SymbolFlags(Class | Function | Ambient)
rebuilt : SymbolId(0): SymbolFlags(Function)
Symbol span mismatch for "C":
after transform: SymbolId(0): Span { start: 14, end: 15 }
@@ -379,7 +379,7 @@ Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4), ScopeId(5)]
rebuilt : ScopeId(0): [ScopeId(1)]
Symbol flags mismatch for "D":
after transform: SymbolId(3): SymbolFlags(Class | ValueModule)
after transform: SymbolId(3): SymbolFlags(Class | ValueModule | Ambient)
rebuilt : SymbolId(0): SymbolFlags(BlockScopedVariable)
Symbol span mismatch for "D":
after transform: SymbolId(3): Span { start: 97, end: 98 }
@@ -398,7 +398,7 @@ semantic error: Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
rebuilt : ScopeId(0): [ScopeId(1)]
Symbol flags mismatch for "foo":
after transform: SymbolId(0): SymbolFlags(Class | Function)
after transform: SymbolId(0): SymbolFlags(Class | Function | Ambient)
rebuilt : SymbolId(0): SymbolFlags(Function)
Symbol span mismatch for "foo":
after transform: SymbolId(0): Span { start: 14, end: 17 }
@@ -46653,6 +46653,9 @@ tasks/coverage/typescript/tests/cases/conformance/jsx/jsxReactTestSuite.tsx
semantic error: Bindings mismatch:
after transform: ScopeId(0): ["Child", "Component", "Composite", "Composite2", "Namespace", "React", "_jsxFileName", "_objectSpread", "_reactJsxRuntime", "bar", "foo", "hasOwnProperty", "x", "y", "z"]
rebuilt : ScopeId(0): ["_jsxFileName", "_objectSpread", "_reactJsxRuntime", "x"]
Symbol flags mismatch for "x":
after transform: SymbolId(9): SymbolFlags(FunctionScopedVariable | Ambient)
rebuilt : SymbolId(3): SymbolFlags(FunctionScopedVariable)
Symbol span mismatch for "x":
after transform: SymbolId(9): Span { start: 231, end: 236 }
rebuilt : SymbolId(3): Span { start: 537, end: 538 }
@@ -1705,7 +1705,7 @@ Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(4)]
rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2)]
Symbol flags mismatch for "Signal":
after transform: SymbolId(0): SymbolFlags(Class | Function)
after transform: SymbolId(0): SymbolFlags(Class | Function | Ambient)
rebuilt : SymbolId(0): SymbolFlags(Function)
Symbol span mismatch for "Signal":
after transform: SymbolId(0): Span { start: 14, end: 20 }
@@ -1717,7 +1717,7 @@ Symbol redeclarations mismatch for "Signal":
after transform: SymbolId(0): [Span { start: 14, end: 20 }, Span { start: 54, end: 60 }]
rebuilt : SymbolId(0): []
Symbol flags mismatch for "Signal2":
after transform: SymbolId(3): SymbolFlags(Class | Function)
after transform: SymbolId(3): SymbolFlags(Class | Function | Ambient)
rebuilt : SymbolId(2): SymbolFlags(Function)
Symbol reference IDs mismatch for "Signal2":
after transform: SymbolId(3): [ReferenceId(4), ReferenceId(7)]