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

feat: drop support for legacy module namespaces #39

Merged
merged 3 commits into from
Feb 12, 2025
Merged
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
17 changes: 12 additions & 5 deletions docs/unsupported_syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The following TypeScript features can not be erased by `ts-blank-space` because

- `enum` (unless `declare enum`) [more details](#enums)
- `namespace` (unless only contains types) [more details](#namespace-declarations)
- `module` (unless `declare module`) [more details](#module-namespace-declarations)
- `module` (unless `declare module "path"`) [more details](#module-namespace-declarations)
- `import lib = ...`, `export = ...` (TypeScript style CommonJS)
- `constructor(public x) {}` [more details](#constructor-parameter-properties)

Expand Down Expand Up @@ -78,15 +78,22 @@ Examples of supported namespace syntax can be seen in the test fixture [tests/fi

### `module` namespace declarations

`ts-blank-space` only erases TypeScript's `module` namespace declarations if they are marked with `declare` (see [`declare` hazard](#the-declare--hazard)).
`ts-blank-space` only erases TypeScript's `module` statements if they represent [Ambient Modules](https://www.typescriptlang.org/docs/handbook/modules/reference.html#ambient-modules) and applies to both _Ambient Module Augmentations_ as well as _Ambient Module Declarations_.

All other TypeScript `module` declarations will trigger the `onError` callback and be left in the output text verbatim. Including an empty declaration:
All other TypeScript `module` declarations will trigger the `onError` callback and be left in the output text verbatim.

```ts
module M {} // `ts-blank-space` error
// erasable ambient module augmentation:
declare module "./path" {}
```

Note that, since TypeScript 5.6, use of `module` namespace declarations (not to be confused with _"ambient module declarations"_) will be shown with a strike-through (~~`module`~~) to hint that the syntax is deprecated in favour of [`namespace`](#namespace-declarations).
```ts
// deprecated module namespaces:
module M1 {} // `ts-blank-space` error
declare module M2 {} // `ts-blank-space` error
```

Note that, since TypeScript 5.6, use of `module` namespace declarations will be shown with a strike-through (~~`module`~~) to hint that the syntax is deprecated in favour of [`namespace`](#namespace-declarations).

See https://github.com/microsoft/TypeScript/issues/51825 for more information.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ts-blank-space",
"description": "A small, fast, pure JavaScript type-stripper that uses the official TypeScript parser.",
"version": "0.5.1",
"version": "0.6.0",
"license": "Apache-2.0",
"homepage": "https://bloomberg.github.io/ts-blank-space",
"contributors": [
Expand Down
28 changes: 23 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ function innerVisitor(node: ts.Node, kind: ts.SyntaxKind): VisitResult {
case SK.FunctionExpression:
case SK.GetAccessor:
case SK.SetAccessor: return visitFunctionLikeDeclaration(node as ts.FunctionLikeDeclaration, kind);
case SK.EnumDeclaration:
case SK.ModuleDeclaration: return visitEnumOrModule(node as (ts.EnumDeclaration | ts.ModuleDeclaration));
case SK.EnumDeclaration: return visitEnum(node as ts.EnumDeclaration);
case SK.ModuleDeclaration: return visitModule(node as ts.ModuleDeclaration);
case SK.IndexSignature: blankExact(node); return VISIT_BLANKED;
case SK.TaggedTemplateExpression: return visitTaggedTemplate(node as ts.TaggedTemplateExpression);
case SK.TypeAssertionExpression: return visitLegacyTypeAssertion(node as ts.TypeAssertion);
Expand Down Expand Up @@ -516,10 +516,18 @@ function visitExportAssignment(node: ts.ExportAssignment): VisitResult {
return VISITED_JS;
}

function visitEnumOrModule(node: ts.EnumDeclaration | ts.ModuleDeclaration): VisitResult {
function visitModule(node: ts.ModuleDeclaration): VisitResult {
if (
(node.modifiers && modifiersContainsDeclare(node.modifiers)) ||
(node.kind === SK.ModuleDeclaration && !valueNamespaceWorker(node as ts.ModuleDeclaration))
// `declare global {...}
node.flags & tslib.NodeFlags.GlobalAugmentation ||
// `namespace N {...}`
(node.flags & tslib.NodeFlags.Namespace &&
// `declare namespace N {...}`
((node.modifiers && modifiersContainsDeclare(node.modifiers)) ||
// `namespace N { <no values> }`
!valueNamespaceWorker(node))) ||
// `declare module "./path" {...}`
node.name.kind === SK.StringLiteral
) {
blankStatement(node);
return VISIT_BLANKED;
Expand All @@ -529,6 +537,16 @@ function visitEnumOrModule(node: ts.EnumDeclaration | ts.ModuleDeclaration): Vis
}
}

function visitEnum(node: ts.EnumDeclaration): VisitResult {
if (node.modifiers && modifiersContainsDeclare(node.modifiers)) {
blankStatement(node);
return VISIT_BLANKED;
} else {
onError && onError(node);
return VISITED_JS;
}
}

function valueNamespaceWorker(node: ts.Node): boolean {
switch (node.kind) {
case SK.TypeAliasDeclaration:
Expand Down
14 changes: 14 additions & 0 deletions tests/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,20 @@ it("importing instantiated namespace", () => {
);
});

it("errors on declared legacy modules", () => {
const onError = mock.fn();
const out = tsBlankSpace(`declare module M {}\n`, onError);
assert.equal(onError.mock.callCount(), 1);
assert.equal(out, "declare module M {}\n");
});

it("errors on non-instantiated legacy modules", () => {
const onError = mock.fn();
const out = tsBlankSpace(`module M {}\n`, onError);
assert.equal(onError.mock.callCount(), 1);
assert.equal(out, "module M {}\n");
});

it("errors on CJS export assignment syntax", () => {
const onError = mock.fn();
const out = tsBlankSpace(
Expand Down
9 changes: 7 additions & 2 deletions tests/fixture/cases/a.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ void 0;

void 0;

/**/declare module M {}
// ^^^^^^^^^^^^^^^^^^^ `declare module`
/**/declare module "./a" {}
// ^^^^^^^^^^^^^^^^^^^^^^^ `declare module "path"`

void 0;

/**/declare global {}
// ^^^^^^^^^^^^^^^^^ `declare global {}`

void 0;

Expand Down
9 changes: 7 additions & 2 deletions tests/fixture/output/a.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions tests/valid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,18 @@ it("allows declared namespace value", () => {
assert.equal(jsOutput, " \n");
});

it("allows declared module value", () => {
it("allows declared module augmentation value", () => {
const onError = mock.fn();
const jsOutput = tsBlankSpace(`declare module M {}\n`, onError);
const jsOutput = tsBlankSpace(`declare module "" {}\n`, onError);
assert.equal(onError.mock.callCount(), 0);
assert.equal(jsOutput, " \n");
assert.equal(jsOutput, " \n");
});

it("allows declared global augmentation value", () => {
const onError = mock.fn();
const jsOutput = tsBlankSpace(`declare global {}\n`, onError);
assert.equal(onError.mock.callCount(), 0);
assert.equal(jsOutput, " \n");
});

it("TSX is preserved in the output", () => {
Expand Down