Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(typescript): Allow references to the global Symbol in computed property names under isolatedDeclarations #9869

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
30 changes: 28 additions & 2 deletions crates/swc_typescript/src/fast_dts/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,38 @@ 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(member_expr) = expr.as_member() else {
return false;
};

// https://github.com/microsoft/TypeScript/blob/cbac1ddfc73ca3b9d8741c1b51b74663a0f24695/src/compiler/transformers/declarations.ts#L1011
if let Some(ident) = member_expr.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) = 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
4 changes: 3 additions & 1 deletion crates/swc_typescript/src/fast_dts/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ impl FastDts {

let is_not_allowed = match key {
Expr::Ident(_) => false,
Expr::Member(member) => !member.get_first_object().is_ident(),
Expr::Member(member) => {
!member.get_first_object().is_ident() && !self.is_global_symbol_object(key)
}
_ => !Self::is_literal(key),
};

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
Loading