-
-
Notifications
You must be signed in to change notification settings - Fork 443
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(isolated_declarations): Emit computed properties when they are we…
…ll known symbols
- Loading branch information
MichaelMitchell-at
committed
Jul 15, 2024
1 parent
b94540d
commit 14de332
Showing
7 changed files
with
290 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
crates/oxc_isolated_declarations/src/global_symbol_binding_tracker.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
use std::cell::Cell; | ||
|
||
use oxc_allocator::{Allocator, Vec}; | ||
#[allow(clippy::wildcard_imports)] | ||
use oxc_ast::ast::*; | ||
use oxc_ast::AstBuilder; | ||
#[allow(clippy::wildcard_imports)] | ||
use oxc_ast::{visit::walk::*, Visit}; | ||
use oxc_span::{Atom, GetSpan, Span}; | ||
use oxc_syntax::scope::{ScopeFlags, ScopeId}; | ||
use rustc_hash::FxHashSet; | ||
|
||
/// Declaration scope. | ||
#[derive(Debug)] | ||
struct Scope { | ||
has_symbol_binding: bool, | ||
has_global_this_binding: bool, | ||
} | ||
|
||
impl Scope { | ||
fn new() -> Self { | ||
Self { has_symbol_binding: false, has_global_this_binding: false } | ||
} | ||
} | ||
|
||
/// Linear tree of declaration scopes. | ||
pub struct GlobalSymbolBindingTracker<'a> { | ||
levels: Vec<'a, Scope>, | ||
computed_properties_using_non_global_symbol: FxHashSet<Span>, | ||
computed_properties_using_non_global_global_this: FxHashSet<Span>, | ||
} | ||
|
||
impl<'a> GlobalSymbolBindingTracker<'a> { | ||
pub fn new(allocator: &'a Allocator) -> Self { | ||
let ast = AstBuilder::new(allocator); | ||
let levels = ast.vec1(Scope::new()); | ||
Self { | ||
levels, | ||
computed_properties_using_non_global_symbol: FxHashSet::default(), | ||
computed_properties_using_non_global_global_this: FxHashSet::default(), | ||
} | ||
} | ||
|
||
fn does_computed_property_reference_non_global_symbol(&self, key: &PropertyKey) -> bool { | ||
self.computed_properties_using_non_global_symbol.contains(&key.span()) | ||
} | ||
|
||
fn does_computed_property_reference_non_global_global_this(&self, key: &PropertyKey) -> bool { | ||
self.computed_properties_using_non_global_global_this.contains(&key.span()) | ||
} | ||
|
||
pub fn does_computed_property_reference_well_known_symbol(&self, key: &PropertyKey) -> bool { | ||
if let PropertyKey::StaticMemberExpression(expr) = key { | ||
if let Expression::Identifier(identifier) = &expr.object { | ||
identifier.name == "Symbol" | ||
&& !self.does_computed_property_reference_non_global_symbol(key) | ||
} else if let Expression::StaticMemberExpression(static_member) = &expr.object { | ||
if let Expression::Identifier(identifier) = &static_member.object { | ||
identifier.name == "globalThis" | ||
&& static_member.property.name == "Symbol" | ||
&& !self.does_computed_property_reference_non_global_global_this(key) | ||
} else { | ||
false | ||
} | ||
} else { | ||
false | ||
} | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
fn has_in_scope_symbol_binding(&self) -> bool { | ||
self.levels.iter().any(|level| level.has_symbol_binding) | ||
} | ||
|
||
fn has_in_scope_global_this_binding(&self) -> bool { | ||
self.levels.iter().any(|level| level.has_global_this_binding) | ||
} | ||
|
||
fn handle_name_binding(&mut self, name: &Atom) { | ||
match name.as_str() { | ||
"Symbol" => { | ||
let scope = self.levels.last_mut().unwrap(); | ||
scope.has_symbol_binding = true; | ||
} | ||
"globalThis" => { | ||
let scope = self.levels.last_mut().unwrap(); | ||
scope.has_global_this_binding = true; | ||
} | ||
_ => {} | ||
} | ||
} | ||
} | ||
|
||
impl<'a> Visit<'a> for GlobalSymbolBindingTracker<'a> { | ||
fn enter_scope(&mut self, _: ScopeFlags, _: &Cell<Option<ScopeId>>) { | ||
let scope = Scope::new(); | ||
self.levels.push(scope); | ||
} | ||
|
||
fn leave_scope(&mut self) { | ||
self.levels.pop(); | ||
} | ||
|
||
fn visit_ts_type(&mut self, _: &TSType<'a>) { | ||
// Optimization: we don't need to traverse down into types. | ||
} | ||
|
||
fn visit_binding_pattern(&mut self, pattern: &BindingPattern<'a>) { | ||
if let BindingPatternKind::BindingIdentifier(ident) = &pattern.kind { | ||
self.handle_name_binding(&ident.name); | ||
} | ||
walk_binding_pattern(self, pattern); | ||
} | ||
|
||
fn visit_declaration(&mut self, declaration: &Declaration<'a>) { | ||
match declaration { | ||
Declaration::VariableDeclaration(_) | Declaration::UsingDeclaration(_) => { | ||
// handled in BindingPattern | ||
} | ||
Declaration::FunctionDeclaration(decl) => { | ||
if let Some(id) = decl.id.as_ref() { | ||
self.handle_name_binding(&id.name); | ||
} | ||
} | ||
Declaration::ClassDeclaration(decl) => { | ||
if let Some(id) = decl.id.as_ref() { | ||
self.handle_name_binding(&id.name); | ||
} | ||
} | ||
Declaration::TSModuleDeclaration(decl) => { | ||
if let TSModuleDeclarationName::Identifier(ident) = &decl.id { | ||
self.handle_name_binding(&ident.name); | ||
} | ||
} | ||
Declaration::TSImportEqualsDeclaration(decl) => { | ||
self.handle_name_binding(&decl.id.name); | ||
} | ||
Declaration::TSTypeAliasDeclaration(_) | ||
| Declaration::TSInterfaceDeclaration(_) | ||
| Declaration::TSEnumDeclaration(_) => return, | ||
} | ||
walk_declaration(self, declaration); | ||
} | ||
|
||
fn visit_object_property(&mut self, prop: &ObjectProperty<'a>) { | ||
if prop.computed { | ||
if let PropertyKey::StaticMemberExpression(expr) = &prop.key { | ||
if self.has_in_scope_symbol_binding() { | ||
if let Expression::Identifier(identifier) = &expr.object { | ||
if identifier.name == "Symbol" { | ||
self.computed_properties_using_non_global_symbol | ||
.insert(prop.key.span()); | ||
} | ||
} | ||
} | ||
|
||
if self.has_in_scope_global_this_binding() { | ||
if let Expression::StaticMemberExpression(static_member) = &expr.object { | ||
if let Expression::Identifier(identifier) = &static_member.object { | ||
if identifier.name == "globalThis" | ||
&& static_member.property.name == "Symbol" | ||
{ | ||
self.computed_properties_using_non_global_global_this | ||
.insert(prop.key.span()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
walk_object_property(self, prop); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
crates/oxc_isolated_declarations/tests/fixtures/well-known-symbols.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Correct | ||
export const foo = { | ||
[Symbol.iterator]: (): void => {}, | ||
[Symbol.asyncIterator]: async (): Promise<void> => {}, | ||
[globalThis.Symbol.iterator]: (): void => {}, | ||
} | ||
|
||
export abstract class Foo { | ||
[Symbol.iterator](): void {} | ||
async [Symbol.asyncIterator](): Promise<void> {} | ||
[globalThis.Symbol.iterator](): void {} | ||
} | ||
|
||
// Incorrect | ||
|
||
export namespace Foo { | ||
const Symbol = {}; | ||
const globalThis = {}; | ||
|
||
export const foo = { | ||
[Symbol.iterator]: (): void => {}, | ||
[globalThis.Symbol.iterator]: (): void => {}, | ||
} | ||
} | ||
|
||
export function bar(Symbol: {}, globalThis: {}) { | ||
return { | ||
[Symbol.iterator]: (): void => {}, | ||
[globalThis.Symbol.iterator]: (): void => {}, | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
crates/oxc_isolated_declarations/tests/snapshots/well-known-symbols.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
--- | ||
source: crates/oxc_isolated_declarations/tests/mod.rs | ||
input_file: crates/oxc_isolated_declarations/tests/fixtures/well-known-symbols.ts | ||
--- | ||
==================== .D.TS ==================== | ||
|
||
export declare const foo: { | ||
[Symbol.iterator]: () => void; | ||
[Symbol.asyncIterator]: () => Promise<void>; | ||
[globalThis.Symbol.iterator]: () => void; | ||
}; | ||
export declare abstract class Foo { | ||
[Symbol.iterator](): void; | ||
[Symbol.asyncIterator](): Promise<void>; | ||
[globalThis.Symbol.iterator](): void; | ||
} | ||
export declare namespace Foo { | ||
export const foo: {}; | ||
} | ||
export declare function bar(Symbol: {}, globalThis: {}): {}; | ||
|
||
|
||
==================== Errors ==================== | ||
|
||
x TS9038: Computed property names on class or object literals cannot be | ||
| inferred with --isolatedDeclarations. | ||
,-[21:6] | ||
20 | export const foo = { | ||
21 | [Symbol.iterator]: (): void => {}, | ||
: ^^^^^^^^^^^^^^^ | ||
22 | [globalThis.Symbol.iterator]: (): void => {}, | ||
`---- | ||
x TS9038: Computed property names on class or object literals cannot be | ||
| inferred with --isolatedDeclarations. | ||
,-[22:6] | ||
21 | [Symbol.iterator]: (): void => {}, | ||
22 | [globalThis.Symbol.iterator]: (): void => {}, | ||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
23 | } | ||
`---- | ||
|
||
x TS9038: Computed property names on class or object literals cannot be | ||
| inferred with --isolatedDeclarations. | ||
,-[28:6] | ||
27 | return { | ||
28 | [Symbol.iterator]: (): void => {}, | ||
: ^^^^^^^^^^^^^^^ | ||
29 | [globalThis.Symbol.iterator]: (): void => {}, | ||
`---- | ||
|
||
x TS9038: Computed property names on class or object literals cannot be | ||
| inferred with --isolatedDeclarations. | ||
,-[29:6] | ||
28 | [Symbol.iterator]: (): void => {}, | ||
29 | [globalThis.Symbol.iterator]: (): void => {}, | ||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
30 | } | ||
`---- |
Oops, something went wrong.