Skip to content

Commit

Permalink
fix(typescript): Allow references to the global Symbol in computed pr…
Browse files Browse the repository at this point in the history
…operty names under `isolatedDeclarations` (#9869)

fixes #9859
ref: microsoft/TypeScript#58771

Pass `unresolved_mark` to FastDts so it can check whether a `Symbol` ident refs to global Symbol.
  • Loading branch information
CPunisher authored Jan 23, 2025
1 parent 606ffe5 commit e4c1e03
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .changeset/long-turtles-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
swc: major
swc_typescript: major
---

fix(typescript): Allow references to the global Symbol in computed property names under isolatedDeclarations
1 change: 1 addition & 0 deletions crates/swc/benches/isolated_declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fn bench_isolated_declarations(criterion: &mut Criterion) {
let internal_annotations = FastDts::get_internal_annotations(&comments);
let mut checker = FastDts::new(
fm.name.clone(),
unresolved_mark,
FastDtsOptions {
internal_annotations: Some(internal_annotations),
},
Expand Down
3 changes: 3 additions & 0 deletions crates/swc/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ impl Options {
.into_bool(),
codegen_inline_script,
emit_isolated_dts: experimental.emit_isolated_dts.into_bool(),
unresolved_mark,
resolver,
})
}
Expand Down Expand Up @@ -1124,6 +1125,7 @@ pub struct BuiltInput<P: Pass> {
pub codegen_inline_script: bool,

pub emit_isolated_dts: bool,
pub unresolved_mark: Mark,
pub resolver: Option<(FileName, Arc<dyn ImportResolver>)>,
}

Expand Down Expand Up @@ -1156,6 +1158,7 @@ where
emit_assert_for_import_attributes: self.emit_assert_for_import_attributes,
codegen_inline_script: self.codegen_inline_script,
emit_isolated_dts: self.emit_isolated_dts,
unresolved_mark: self.unresolved_mark,
resolver: self.resolver,
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/swc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,8 @@ impl Compiler {
let trailing = std::rc::Rc::new(RefCell::new(trailing.clone()));

let comments = SingleThreadedComments::from_leading_and_trailing(leading, trailing);
let mut checker = FastDts::new(fm.name.clone(), Default::default());
let mut checker =
FastDts::new(fm.name.clone(), config.unresolved_mark, Default::default());
let mut program = program.clone();

if let Some((base, resolver)) = config.resolver {
Expand Down
1 change: 1 addition & 0 deletions crates/swc_typescript/examples/isolated_declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub fn main() {
let internal_annotations = FastDts::get_internal_annotations(&comments);
let mut checker = FastDts::new(
fm.name.clone(),
unresolved_mark,
FastDtsOptions {
internal_annotations: Some(internal_annotations),
},
Expand Down
34 changes: 32 additions & 2 deletions crates/swc_typescript/src/fast_dts/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,12 +501,42 @@ impl FastDts {

pub(crate) fn report_property_key(&mut self, key: &PropName) -> bool {
if let Some(computed) = key.as_computed() {
let is_symbol = self.is_global_symbol_object(&computed.expr);
let is_literal = Self::is_literal(&computed.expr);
if !is_literal {
if !is_symbol && !is_literal {
self.computed_property_name(key.span());
}
return !is_literal;
return !is_symbol && !is_literal;
}
false
}

pub(crate) fn is_global_symbol_object(&self, expr: &Expr) -> bool {
let Some(obj) = (match expr {
Expr::Member(member) => Some(&member.obj),
Expr::OptChain(opt_chain) => opt_chain.base.as_member().map(|member| &member.obj),
_ => None,
}) else {
return false;
};

// https://github.com/microsoft/TypeScript/blob/cbac1ddfc73ca3b9d8741c1b51b74663a0f24695/src/compiler/transformers/declarations.ts#L1011
if let Some(ident) = obj.as_ident() {
// Exactly `Symbol.something` and `Symbol` either does not resolve
// or definitely resolves to the global Symbol
return ident.sym.as_str() == "Symbol" && ident.ctxt.has_mark(self.unresolved_mark);
}

if let Some(member_expr) = obj.as_member() {
// Exactly `globalThis.Symbol.something` and `globalThis` resolves
// to the global `globalThis`
if let Some(ident) = member_expr.obj.as_ident() {
return ident.sym.as_str() == "globalThis"
&& ident.ctxt.has_mark(self.unresolved_mark)
&& member_expr.prop.is_ident_with("Symbol");
}
}

false
}
}
7 changes: 5 additions & 2 deletions crates/swc_typescript/src/fast_dts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::{borrow::Cow, mem::take, sync::Arc};
use rustc_hash::{FxHashMap, FxHashSet};
use swc_atoms::Atom;
use swc_common::{
comments::SingleThreadedComments, util::take::Take, BytePos, FileName, Span, Spanned, DUMMY_SP,
comments::SingleThreadedComments, util::take::Take, BytePos, FileName, Mark, Span, Spanned,
DUMMY_SP,
};
use swc_ecma_ast::{
BindingIdent, Decl, DefaultDecl, ExportDefaultExpr, Id, Ident, ImportSpecifier, ModuleDecl,
Expand Down Expand Up @@ -38,6 +39,7 @@ mod visitors;
/// The original code is MIT licensed.
pub struct FastDts {
filename: Arc<FileName>,
unresolved_mark: Mark,
diagnostics: Vec<DtsIssue>,
// states
id_counter: u32,
Expand All @@ -53,10 +55,11 @@ pub struct FastDtsOptions {

/// Diagnostics
impl FastDts {
pub fn new(filename: Arc<FileName>, options: FastDtsOptions) -> Self {
pub fn new(filename: Arc<FileName>, unresolved_mark: Mark, options: FastDtsOptions) -> Self {
let internal_annotations = options.internal_annotations;
Self {
filename,
unresolved_mark,
diagnostics: Vec::new(),
id_counter: 0,
is_top_level: true,
Expand Down
9 changes: 7 additions & 2 deletions crates/swc_typescript/tests/deno_test.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Tests copied from deno
//! Make some changes to align with tsc
use swc_common::Mark;
use swc_ecma_ast::EsVersion;
use swc_ecma_codegen::to_code;
use swc_ecma_parser::{parse_file_as_program, Syntax, TsSyntax};
use swc_ecma_transforms_base::resolver;
use swc_typescript::fast_dts::FastDts;

#[track_caller]
Expand All @@ -14,8 +16,8 @@ fn transform_dts_test(source: &str, expected: &str) {
source.to_string(),
);

let mut checker = FastDts::new(fm.name.clone(), Default::default());

let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let mut program = parse_file_as_program(
&fm,
Syntax::Typescript(TsSyntax {
Expand All @@ -25,8 +27,11 @@ fn transform_dts_test(source: &str, expected: &str) {
None,
&mut Vec::new(),
)
.map(|program| program.apply(resolver(unresolved_mark, top_level_mark, true)))
.unwrap();

let mut checker = FastDts::new(fm.name.clone(), unresolved_mark, Default::default());

let _issues = checker.transform(&mut program);

let code = to_code(&program);
Expand Down
10 changes: 10 additions & 0 deletions crates/swc_typescript/tests/fixture/issues/9859.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
```==================== .D.TS ====================
export declare class NumberRange implements Iterable<number> {
private start;
private end;
constructor(start: number, end: number);
[Symbol.iterator](): Iterator<number>;
}
24 changes: 24 additions & 0 deletions crates/swc_typescript/tests/fixture/issues/9859.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export class NumberRange implements Iterable<number> {
private start: number;
private end: number;

constructor(start: number, end: number) {
this.start = start;
this.end = end;
}

[Symbol.iterator](): Iterator<number> {
let current = this.start;
const end = this.end;

return {
next(): IteratorResult<number> {
if (current <= end) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
},
};
}
}
58 changes: 58 additions & 0 deletions crates/swc_typescript/tests/fixture/symbol-properties.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
```==================== .D.TS ====================
// Correct
export declare const foo: {
[Symbol.iterator]: () => void;
[Symbol.asyncIterator]: () => Promise<void>;
[globalThis.Symbol.iterator]: () => void;
[Symbol.toStringTag]: string;
};
export declare abstract class Foo {
[Symbol.iterator](): void;
[Symbol.asyncIterator](): Promise<void>;
[globalThis.Symbol.iterator](): void;
get [Symbol.toStringTag](): string;
}
// Incorrect
export declare namespace Foo {
const foo: {
};
}
export declare function bar(Symbol: {
}, globalThis: {
}): {
};
==================== Errors ====================
x TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
,-[$DIR/tests/fixture/symbol-properties.ts:46:1]
45 | export const foo = {
46 | [Symbol.iterator]: (): void => {},
: ^^^^^^^^^^^^^^^^^
47 | [globalThis.Symbol.iterator]: (): void => {},
`----
x TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
,-[$DIR/tests/fixture/symbol-properties.ts:47:1]
46 | [Symbol.iterator]: (): void => {},
47 | [globalThis.Symbol.iterator]: (): void => {},
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48 | };
`----
x TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
,-[$DIR/tests/fixture/symbol-properties.ts:53:1]
52 | return {
53 | [Symbol.iterator]: (): void => {},
: ^^^^^^^^^^^^^^^^^
54 | [globalThis.Symbol.iterator]: (): void => {},
`----
x TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
,-[$DIR/tests/fixture/symbol-properties.ts:54:1]
53 | [Symbol.iterator]: (): void => {},
54 | [globalThis.Symbol.iterator]: (): void => {},
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55 | };
`----
```
56 changes: 56 additions & 0 deletions crates/swc_typescript/tests/fixture/symbol-properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Correct
export const foo = {
[Symbol.iterator]: (): void => {},
[Symbol.asyncIterator]: async (): Promise<void> => {},
[globalThis.Symbol.iterator]: (): void => {},
get [Symbol.toStringTag]() {
return "foo";
},
};

export abstract class Foo {
[Symbol.iterator](): void {}
async [Symbol.asyncIterator](): Promise<void> {}
[globalThis.Symbol.iterator](): void {}
get [Symbol.toStringTag]() {
return "Foo";
}
}

// OK because these are not exported

namespace Foo {
const Symbol = {};
const globalThis = {};

export const foo = {
[Symbol.iterator]: (): void => {},
[globalThis.Symbol.iterator]: (): void => {},
};
}

function bar(Symbol: {}, globalThis: {}) {
return {
[Symbol.iterator]: (): 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 => {},
};
}
1 change: 1 addition & 0 deletions crates/swc_typescript/tests/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn fixture(input: PathBuf) {
let internal_annotations = FastDts::get_internal_annotations(&comments);
let mut checker = FastDts::new(
fm.name.clone(),
unresolved_mark,
FastDtsOptions {
internal_annotations: Some(internal_annotations),
},
Expand Down

0 comments on commit e4c1e03

Please sign in to comment.