Skip to content

Commit

Permalink
Strip type-only TS namespaces (#16071)
Browse files Browse the repository at this point in the history
  • Loading branch information
colinaaa committed Nov 6, 2023
1 parent cc4f178 commit b9d051a
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 23 deletions.
24 changes: 24 additions & 0 deletions packages/babel-plugin-transform-typescript/src/global-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { NodePath, Scope } from "@babel/traverse";

export const GLOBAL_TYPES = new WeakMap<Scope, Set<string>>();

export function isGlobalType({ scope }: NodePath, name: string) {
if (scope.hasBinding(name)) return false;
if (GLOBAL_TYPES.get(scope).has(name)) return true;

console.warn(
`The exported identifier "${name}" is not declared in Babel's scope tracker\n` +
`as a JavaScript value binding, and "@babel/plugin-transform-typescript"\n` +
`never encountered it as a TypeScript type declaration.\n` +
`It will be treated as a JavaScript value.\n\n` +
`This problem is likely caused by another plugin injecting\n` +
`"${name}" without registering it in the scope tracker. If you are the author\n` +
` of that plugin, please use "scope.registerDeclaration(declarationPath)".`,
);

return false;
}

export function registerGlobalType(programScope: Scope, name: string) {
GLOBAL_TYPES.get(programScope).add(name);
}
29 changes: 6 additions & 23 deletions packages/babel-plugin-transform-typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import { declare } from "@babel/helper-plugin-utils";
import syntaxTypeScript from "@babel/plugin-syntax-typescript";
import type { PluginPass, types as t } from "@babel/core";
import { injectInitialization } from "@babel/helper-create-class-features-plugin";
import type { Binding, NodePath, Scope } from "@babel/traverse";
import type { Binding, NodePath } from "@babel/traverse";
import type { Options as SyntaxOptions } from "@babel/plugin-syntax-typescript";

import transpileConstEnum from "./const-enum.ts";
import type { NodePathConstEnum } from "./const-enum.ts";
import transpileEnum from "./enum.ts";
import {
GLOBAL_TYPES,
isGlobalType,
registerGlobalType,
} from "./global-types.ts";
import transpileNamespace from "./namespace.ts";

function isInType(path: NodePath) {
Expand Down Expand Up @@ -36,34 +41,12 @@ function isInType(path: NodePath) {
}
}

const GLOBAL_TYPES = new WeakMap<Scope, Set<string>>();
// Track programs which contain imports/exports of values, so that we can include
// empty exports for programs that do not, but were parsed as modules. This allows
// tools to infer unambiguously that results are ESM.
const NEEDS_EXPLICIT_ESM = new WeakMap();
const PARSED_PARAMS = new WeakSet();

function isGlobalType({ scope }: NodePath, name: string) {
if (scope.hasBinding(name)) return false;
if (GLOBAL_TYPES.get(scope).has(name)) return true;

console.warn(
`The exported identifier "${name}" is not declared in Babel's scope tracker\n` +
`as a JavaScript value binding, and "@babel/plugin-transform-typescript"\n` +
`never encountered it as a TypeScript type declaration.\n` +
`It will be treated as a JavaScript value.\n\n` +
`This problem is likely caused by another plugin injecting\n` +
`"${name}" without registering it in the scope tracker. If you are the author\n` +
` of that plugin, please use "scope.registerDeclaration(declarationPath)".`,
);

return false;
}

function registerGlobalType(programScope: Scope, name: string) {
GLOBAL_TYPES.get(programScope).add(name);
}

// A hack to avoid removing the impl Binding when we remove the declare NodePath
function safeRemove(path: NodePath) {
const ids = path.getBindingIdentifiers();
Expand Down
7 changes: 7 additions & 0 deletions packages/babel-plugin-transform-typescript/src/namespace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { template, types as t } from "@babel/core";
import type { NodePath } from "@babel/traverse";

import { registerGlobalType } from "./global-types.ts";

export default function transpileNamespace(
path: NodePath<t.TSModuleDeclaration>,
allowNamespaces: boolean,
Expand All @@ -24,6 +26,11 @@ export default function transpileNamespace(
const name = path.node.id.name;
const value = handleNested(path, t.cloneNode(path.node, true));
if (value === null) {
// This means that `path` is a type-only namespace.
// We call `registerGlobalType` here to allow it to be stripped.
const program = path.findParent(p => p.isProgram());
registerGlobalType(program.scope, name);

path.remove();
} else if (path.scope.hasOwnBinding(name)) {
path.replaceWith(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// #15940
namespace Platform {
export type Name = "web";

export namespace Data {
export type Type = string;
export declare namespace Empty {}
}
}

export { Platform };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};

0 comments on commit b9d051a

Please sign in to comment.