Skip to content

Commit

Permalink
fix(ts/fast-strip): Handle unsupported module keyword (#10022)
Browse files Browse the repository at this point in the history
**Related issue:**

- Closes #10017
  • Loading branch information
magic-akari authored Feb 13, 2025
1 parent 6dab49a commit 308f5d0
Show file tree
Hide file tree
Showing 17 changed files with 407 additions and 41 deletions.
6 changes: 6 additions & 0 deletions .changeset/three-ties-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
swc_core: patch
swc_fast_ts_strip: patch
---

fix(es/ts_strip): Handle unsupported `module` keyword
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ exports[`transform in strip-only mode should strip type declarations 1`] = `
exports[`transform in strip-only mode should throw an error when it encounters a module 1`] = `
{
"code": "UnsupportedSyntax",
"message": " x TypeScript namespace declaration is not supported in strip-only mode
"message": " x \`module\` keyword is not supported. Use \`namespace\` instead.
,----
1 | module foo { export const m = 1; }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1 | module foo { }
: ^^^^^^
\`----
",
}
Expand Down Expand Up @@ -153,6 +153,15 @@ exports[`transform in strip-only mode should throw an error when it encounters a
}
`;
exports[`transform in transform mode should throw an error when it encounters a module 1`] = `
" x \`module\` keyword is not supported. Use \`namespace\` instead.
,----
1 | module foo { }
: ^^^^^^
\`----
"
`;
exports[`transform should strip types 1`] = `
{
"code": "
Expand Down
14 changes: 13 additions & 1 deletion bindings/binding_typescript_wasm/__tests__/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ describe("transform", () => {

it("should throw an error when it encounters a module", async () => {
await expect(
swc.transform("module foo { export const m = 1; }", {
swc.transform("module foo { }", {
mode: "strip-only",
deprecatedTsModuleAsError: true,
}),
).rejects.toMatchSnapshot();
});
Expand Down Expand Up @@ -160,4 +161,15 @@ describe("transform", () => {
).rejects.toMatchSnapshot();
});
});

describe("in transform mode", () => {
it("should throw an error when it encounters a module", async () => {
await expect(
swc.transform("module foo { }", {
mode: "transform",
deprecatedTsModuleAsError: true,
}),
).rejects.toMatchSnapshot();
});
});
});
4 changes: 2 additions & 2 deletions crates/swc_fast_ts_strip/benches/assets/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let x /**/: number/**/ = 1!;
[] as [] satisfies [];
// ^^^^^^^^^^^^^^^^^^

(<string>"test");
"test" as "test" satisfies "test";
//^^^^^^^^

class C /**/<T>/*︎*/ extends Array/**/<T> /*︎*/ implements I, J/*︎*/ {
Expand Down Expand Up @@ -119,7 +119,7 @@ void 0;

void 0;

/**/declare module M { }
/**/declare module "M" { }
// ^^^^^^^^^^^^^^^^^^^ `declare module`

void 0;
Expand Down
112 changes: 98 additions & 14 deletions crates/swc_fast_ts_strip/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ use swc_ecma_ast::{
ImportDecl, ImportSpecifier, ModuleDecl, ModuleItem, NamedExport, ObjectPat, Param, Pat,
PrivateMethod, PrivateProp, Program, ReturnStmt, SetterProp, Stmt, ThrowStmt, TsAsExpr,
TsConstAssertion, TsEnumDecl, TsExportAssignment, TsImportEqualsDecl, TsIndexSignature,
TsInstantiation, TsModuleDecl, TsModuleName, TsNamespaceBody, TsNamespaceDecl, TsNonNullExpr,
TsParamPropParam, TsSatisfiesExpr, TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion,
TsTypeParamDecl, TsTypeParamInstantiation, VarDeclarator, WhileStmt, YieldExpr,
TsInstantiation, TsModuleDecl, TsModuleName, TsNamespaceBody, TsNonNullExpr, TsParamPropParam,
TsSatisfiesExpr, TsTypeAliasDecl, TsTypeAnn, TsTypeAssertion, TsTypeParamDecl,
TsTypeParamInstantiation, VarDeclarator, WhileStmt, YieldExpr,
};
use swc_ecma_parser::{
lexer::Lexer,
Expand Down Expand Up @@ -54,6 +54,9 @@ pub struct Options {
#[serde(default)]
pub transform: Option<typescript::Config>,

#[serde(default)]
pub deprecated_ts_module_as_error: Option<bool>,

#[serde(default)]
pub source_map: bool,
}
Expand All @@ -66,6 +69,7 @@ interface Options {
filename?: string;
mode?: Mode;
transform?: TransformConfig;
deprecatedTsModuleAsError?: boolean;
sourceMap?: boolean;
}
Expand Down Expand Up @@ -239,14 +243,24 @@ pub fn operate(

drop(parser);

let deprecated_ts_module_as_error = options.deprecated_ts_module_as_error.unwrap_or_default();

match options.mode {
Mode::StripOnly => {
let mut tokens = RefCell::into_inner(Rc::try_unwrap(tokens).unwrap());

tokens.sort_by_key(|t| t.span);

if deprecated_ts_module_as_error {
program.visit_with(&mut ErrorOnTsModule {
src: &fm.src,
tokens: &tokens,
});
}

// Strip typescript types
let mut ts_strip = TsStrip::new(fm.src.clone(), tokens);

program.visit_with(&mut ts_strip);
if handler.has_errors() {
return Err(TsError {
Expand Down Expand Up @@ -343,6 +357,17 @@ pub fn operate(
HELPERS.set(&Helpers::new(false), || {
program.mutate(&mut resolver(unresolved_mark, top_level_mark, true));

if deprecated_ts_module_as_error {
let mut tokens = RefCell::into_inner(Rc::try_unwrap(tokens).unwrap());

tokens.sort_by_key(|t| t.span);

program.visit_with(&mut ErrorOnTsModule {
src: &fm.src,
tokens: &tokens,
});
}

program.mutate(&mut typescript::typescript(
options.transform.unwrap_or_default(),
unresolved_mark,
Expand Down Expand Up @@ -404,6 +429,76 @@ pub fn operate(
}
}

struct ErrorOnTsModule<'a> {
src: &'a str,
tokens: &'a [TokenAndSpan],
}

// All namespaces or modules are either at the top level or nested within
// another namespace or module.
impl Visit for ErrorOnTsModule<'_> {
fn visit_stmt(&mut self, n: &Stmt) {
if n.is_decl() {
n.visit_children_with(self);
}
}

fn visit_decl(&mut self, n: &Decl) {
if n.is_ts_module() {
n.visit_children_with(self);
}
}

fn visit_module_decl(&mut self, n: &ModuleDecl) {
if n.is_export_decl() {
n.visit_children_with(self);
}
}

fn visit_ts_module_decl(&mut self, n: &TsModuleDecl) {
n.visit_children_with(self);

if n.global || n.id.is_str() {
return;
}

let mut pos = n.span.lo;

if n.declare {
let declare_index = self
.tokens
.binary_search_by_key(&pos, |t| t.span.lo)
.unwrap();

debug_assert_eq!(
self.tokens[declare_index].token,
Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Declare)))
);

let TokenAndSpan { token, span, .. } = &self.tokens[declare_index + 1];
// declare global
// declare module
// declare namespace
if let Token::Word(Word::Ident(IdentLike::Known(KnownIdent::Namespace))) = token {
return;
}

pos = span.lo;
} else if self.src.as_bytes()[pos.0 as usize - 1] != b'm' {
return;
}

HANDLER.with(|handler| {
handler
.struct_span_err(
span(pos, pos + BytePos(6)),
"`module` keyword is not supported. Use `namespace` instead.",
)
.emit();
});
}
}

struct TsStrip {
src: Lrc<String>,

Expand Down Expand Up @@ -1220,17 +1315,6 @@ impl Visit for TsStrip {
});
}

fn visit_ts_namespace_decl(&mut self, n: &TsNamespaceDecl) {
HANDLER.with(|handler| {
handler
.struct_span_err(
n.span(),
"TypeScript module declaration is not supported in strip-only mode",
)
.emit();
});
}

fn visit_ts_non_null_expr(&mut self, n: &TsNonNullExpr) {
self.add_replacement(span(n.span.hi - BytePos(1), n.span.hi));

Expand Down
31 changes: 25 additions & 6 deletions crates/swc_fast_ts_strip/tests/errors/modules.swc-stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
x TypeScript namespace declaration is not supported in strip-only mode
x `module` keyword is not supported. Use `namespace` instead.
,----
1 | module aModuleKeywordNamespace { export const m = 1; }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1 | module aModuleKeywordNamespace { }
: ^^^^^^
`----
x TypeScript namespace declaration is not supported in strip-only mode
x `module` keyword is not supported. Use `namespace` instead.
,-[3:1]
2 |
3 | export module aModuleKeywordExportedNamespace { export const m = 1; }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | declare module aModuleKeywordDeclareNamespace { }
: ^^^^^^
`----
x `module` keyword is not supported. Use `namespace` instead.
,-[5:1]
4 |
5 | export module aModuleKeywordExportedNamespace { }
: ^^^^^^
`----
x `module` keyword is not supported. Use `namespace` instead.
,-[7:1]
6 |
7 | export declare module aModuleKeywordExportedDeclareNamespace { }
: ^^^^^^
`----
x `module` keyword is not supported. Use `namespace` instead.
,-[10:1]
9 | namespace foo {
10 | export module aModuleKeywordExportedNamespaceInNamespace { }
: ^^^^^^
11 | }
`----
12 changes: 10 additions & 2 deletions crates/swc_fast_ts_strip/tests/errors/modules.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module aModuleKeywordNamespace { export const m = 1; }
module aModuleKeywordNamespace { }

export module aModuleKeywordExportedNamespace { export const m = 1; }
declare module aModuleKeywordDeclareNamespace { }

export module aModuleKeywordExportedNamespace { }

export declare module aModuleKeywordExportedDeclareNamespace { }

namespace foo {
export module aModuleKeywordExportedNamespaceInNamespace { }
}
7 changes: 7 additions & 0 deletions crates/swc_fast_ts_strip/tests/errors/ts-module.swc-stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
x `module` keyword is not supported. Use `namespace` instead.
,-[3:1]
2 |
3 | module Foo {
: ^^^^^^
4 | export const foo = 1;
`----
x TypeScript namespace declaration is not supported in strip-only mode
,-[3:1]
2 |
Expand Down
1 change: 1 addition & 0 deletions crates/swc_fast_ts_strip/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fn opts(mode: Mode) -> Options {
native_class_properties: true,
..Default::default()
}),
deprecated_ts_module_as_error: true.into(),
..Default::default()
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/swc_fast_ts_strip/tests/fixture/export-declare.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@




// `module` is not a allowed. See https://github.com/swc-project/swc/issues/10017
// export declare module Garply { }
3 changes: 2 additions & 1 deletion crates/swc_fast_ts_strip/tests/fixture/export-declare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export declare enum Qux {}
export declare const Quux = 0;
export declare function Corge(): void;
export declare namespace Grault {}
export declare module Garply {}
// `module` is not a allowed. See https://github.com/swc-project/swc/issues/10017
// export declare module Garply { }
15 changes: 14 additions & 1 deletion crates/swc_fast_ts_strip/tests/fixture/modules.js
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@

// https://www.typescriptlang.org/docs/handbook/modules/reference.html#ambient-modules
export { };






// https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation





Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { };
15 changes: 14 additions & 1 deletion crates/swc_fast_ts_strip/tests/fixture/modules.ts
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
module 'myAmbientModuleDeclaration' { }
// https://www.typescriptlang.org/docs/handbook/modules/reference.html#ambient-modules
export { };
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}

// https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Loading

0 comments on commit 308f5d0

Please sign in to comment.