diff --git a/crates/swc/benches/isolated_declarations.rs b/crates/swc/benches/isolated_declarations.rs index 0a8db81a0861..82048d5e1b10 100644 --- a/crates/swc/benches/isolated_declarations.rs +++ b/crates/swc/benches/isolated_declarations.rs @@ -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), }, diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs index 2a960fe709d7..a62074c3609d 100644 --- a/crates/swc/src/config/mod.rs +++ b/crates/swc/src/config/mod.rs @@ -812,6 +812,7 @@ impl Options { .into_bool(), codegen_inline_script, emit_isolated_dts: experimental.emit_isolated_dts.into_bool(), + unresolved_mark, resolver, }) } @@ -1124,6 +1125,7 @@ pub struct BuiltInput { pub codegen_inline_script: bool, pub emit_isolated_dts: bool, + pub unresolved_mark: Mark, pub resolver: Option<(FileName, Arc)>, } @@ -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, } } diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index 3e4330cef0f4..463423131162 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -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 { diff --git a/crates/swc_typescript/examples/isolated_declarations.rs b/crates/swc_typescript/examples/isolated_declarations.rs index 0ffb9a987c97..4d20b128aa4a 100644 --- a/crates/swc_typescript/examples/isolated_declarations.rs +++ b/crates/swc_typescript/examples/isolated_declarations.rs @@ -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), }, diff --git a/crates/swc_typescript/src/fast_dts/class.rs b/crates/swc_typescript/src/fast_dts/class.rs index f9dac8cfff48..9826ecf44235 100644 --- a/crates/swc_typescript/src/fast_dts/class.rs +++ b/crates/swc_typescript/src/fast_dts/class.rs @@ -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 + } } diff --git a/crates/swc_typescript/src/fast_dts/mod.rs b/crates/swc_typescript/src/fast_dts/mod.rs index d70c60ecbef1..113f7e320692 100644 --- a/crates/swc_typescript/src/fast_dts/mod.rs +++ b/crates/swc_typescript/src/fast_dts/mod.rs @@ -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, @@ -38,6 +39,7 @@ mod visitors; /// The original code is MIT licensed. pub struct FastDts { filename: Arc, + unresolved_mark: Mark, diagnostics: Vec, // states id_counter: u32, @@ -53,10 +55,11 @@ pub struct FastDtsOptions { /// Diagnostics impl FastDts { - pub fn new(filename: Arc, options: FastDtsOptions) -> Self { + pub fn new(filename: Arc, 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, diff --git a/crates/swc_typescript/src/fast_dts/types.rs b/crates/swc_typescript/src/fast_dts/types.rs index 09b2e24cb5ca..53ec9a86fce8 100644 --- a/crates/swc_typescript/src/fast_dts/types.rs +++ b/crates/swc_typescript/src/fast_dts/types.rs @@ -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), }; diff --git a/crates/swc_typescript/tests/deno_test.rs b/crates/swc_typescript/tests/deno_test.rs index 2158e82eea15..df3702414244 100644 --- a/crates/swc_typescript/tests/deno_test.rs +++ b/crates/swc_typescript/tests/deno_test.rs @@ -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] @@ -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 { @@ -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); diff --git a/crates/swc_typescript/tests/fixture/issues/9859.snap b/crates/swc_typescript/tests/fixture/issues/9859.snap new file mode 100644 index 000000000000..0e74be457a41 --- /dev/null +++ b/crates/swc_typescript/tests/fixture/issues/9859.snap @@ -0,0 +1,10 @@ +```==================== .D.TS ==================== + +export declare class NumberRange implements Iterable { + private start; + private end; + constructor(start: number, end: number); + [Symbol.iterator](): Iterator; +} + + diff --git a/crates/swc_typescript/tests/fixture/issues/9859.ts b/crates/swc_typescript/tests/fixture/issues/9859.ts new file mode 100644 index 000000000000..bf5d43de6bfe --- /dev/null +++ b/crates/swc_typescript/tests/fixture/issues/9859.ts @@ -0,0 +1,24 @@ +export class NumberRange implements Iterable { + private start: number; + private end: number; + + constructor(start: number, end: number) { + this.start = start; + this.end = end; + } + + [Symbol.iterator](): Iterator { + let current = this.start; + const end = this.end; + + return { + next(): IteratorResult { + if (current <= end) { + return { value: current++, done: false }; + } else { + return { value: undefined, done: true }; + } + }, + }; + } +} diff --git a/crates/swc_typescript/tests/fixture/symbol-properties.snap b/crates/swc_typescript/tests/fixture/symbol-properties.snap new file mode 100644 index 000000000000..4e8f9dc5ec06 --- /dev/null +++ b/crates/swc_typescript/tests/fixture/symbol-properties.snap @@ -0,0 +1,58 @@ +```==================== .D.TS ==================== + +// Correct +export declare const foo: { + [Symbol.iterator]: () => void; + [Symbol.asyncIterator]: () => Promise; + [globalThis.Symbol.iterator]: () => void; + [Symbol.toStringTag]: string; +}; +export declare abstract class Foo { + [Symbol.iterator](): void; + [Symbol.asyncIterator](): Promise; + [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 | }; + `---- + + +``` diff --git a/crates/swc_typescript/tests/fixture/symbol-properties.ts b/crates/swc_typescript/tests/fixture/symbol-properties.ts new file mode 100644 index 000000000000..06aff32626a7 --- /dev/null +++ b/crates/swc_typescript/tests/fixture/symbol-properties.ts @@ -0,0 +1,56 @@ +// Correct +export const foo = { + [Symbol.iterator]: (): void => {}, + [Symbol.asyncIterator]: async (): Promise => {}, + [globalThis.Symbol.iterator]: (): void => {}, + get [Symbol.toStringTag]() { + return "foo"; + }, +}; + +export abstract class Foo { + [Symbol.iterator](): void {} + async [Symbol.asyncIterator](): Promise {} + [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 => {}, + }; +} diff --git a/crates/swc_typescript/tests/typescript.rs b/crates/swc_typescript/tests/typescript.rs index a31ccc3fbebb..7e14f5a4ecdc 100644 --- a/crates/swc_typescript/tests/typescript.rs +++ b/crates/swc_typescript/tests/typescript.rs @@ -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), },