Implement TypeScript namespace support (#9785)

* Add module tests for typescript namespace transform

Fixes #8244, fixes #10038
This commit is contained in:
Wesley Wolfe 2019-03-28 12:15:40 -05:00 committed by Nicolò Ribaudo
parent 8bf9714d69
commit 0a98814329
No known key found for this signature in database
GPG Key ID: 6F2E38DF3E4A6D0C
39 changed files with 982 additions and 312 deletions

View File

@ -4,6 +4,7 @@ import { types as t, template } from "@babel/core";
import { injectInitialization } from "@babel/helper-create-class-features-plugin";
import transpileEnum from "./enum";
import transpileNamespace from "./namespace";
function isInType(path) {
switch (path.parent.type) {
@ -19,333 +20,335 @@ function isInType(path) {
const PARSED_PARAMS = new WeakSet();
export default declare((api, { jsxPragma = "React" }) => {
api.assertVersion(7);
export default declare(
(api, { jsxPragma = "React", allowNamespaces = false }) => {
api.assertVersion(7);
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
return {
name: "transform-typescript",
inherits: syntaxTypeScript,
return {
name: "transform-typescript",
inherits: syntaxTypeScript,
visitor: {
//"Pattern" alias doesn't include Identifier or RestElement.
Pattern: visitPattern,
Identifier: visitPattern,
RestElement: visitPattern,
visitor: {
//"Pattern" alias doesn't include Identifier or RestElement.
Pattern: visitPattern,
Identifier: visitPattern,
RestElement: visitPattern,
Program(path, state) {
const { file } = state;
let fileJsxPragma = null;
Program(path, state) {
const { file } = state;
let fileJsxPragma = null;
if (file.ast.comments) {
for (const comment of (file.ast.comments: Array<Object>)) {
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
if (jsxMatches) {
fileJsxPragma = jsxMatches[1];
}
}
}
// remove type imports
for (const stmt of path.get("body")) {
if (t.isImportDeclaration(stmt)) {
// Note: this will allow both `import { } from "m"` and `import "m";`.
// In TypeScript, the former would be elided.
if (stmt.node.specifiers.length === 0) {
continue;
}
let allElided = true;
const importsToRemove: Path<Node>[] = [];
for (const specifier of stmt.node.specifiers) {
const binding = stmt.scope.getBinding(specifier.local.name);
// The binding may not exist if the import node was explicitly
// injected by another plugin. Currently core does not do a good job
// of keeping scope bindings synchronized with the AST. For now we
// just bail if there is no binding, since chances are good that if
// the import statement was injected then it wasn't a typescript type
// import anyway.
if (
binding &&
isImportTypeOnly({
binding,
programPath: path,
jsxPragma: fileJsxPragma || jsxPragma,
})
) {
importsToRemove.push(binding.path);
} else {
allElided = false;
}
}
if (allElided) {
stmt.remove();
} else {
for (const importPath of importsToRemove) {
importPath.remove();
if (file.ast.comments) {
for (const comment of (file.ast.comments: Array<Object>)) {
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
if (jsxMatches) {
fileJsxPragma = jsxMatches[1];
}
}
}
}
},
ExportNamedDeclaration(path) {
// remove export declaration if it's exporting only types
if (
path.node.specifiers.length > 0 &&
!path.node.specifiers.find(exportSpecifier =>
path.scope.hasOwnBinding(exportSpecifier.local.name),
)
) {
path.remove();
}
},
ExportSpecifier(path) {
// remove type exports
if (!path.scope.hasOwnBinding(path.node.local.name)) {
path.remove();
}
},
ExportDefaultDeclaration(path) {
// remove whole declaration if it's exporting a TS type
if (
t.isIdentifier(path.node.declaration) &&
!path.scope.hasOwnBinding(path.node.declaration.name)
) {
path.remove();
}
},
TSDeclareFunction(path) {
path.remove();
},
TSDeclareMethod(path) {
path.remove();
},
VariableDeclaration(path) {
if (path.node.declare) path.remove();
},
VariableDeclarator({ node }) {
if (node.definite) node.definite = null;
},
ClassMethod(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null;
// Rest handled by Function visitor
},
ClassProperty(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.readonly) node.readonly = null;
if (node.optional) node.optional = null;
if (node.definite) node.definite = null;
if (node.typeAnnotation) node.typeAnnotation = null;
},
TSIndexSignature(path) {
path.remove();
},
ClassDeclaration(path) {
const { node } = path;
if (node.declare) {
path.remove();
return;
}
},
Class(path) {
const { node } = path;
if (node.typeParameters) node.typeParameters = null;
if (node.superTypeParameters) node.superTypeParameters = null;
if (node.implements) node.implements = null;
if (node.abstract) node.abstract = null;
// Similar to the logic in `transform-flow-strip-types`, we need to
// handle `TSParameterProperty` and `ClassProperty` here because the
// class transform would transform the class, causing more specific
// visitors to not run.
path.get("body.body").forEach(child => {
const childNode = child.node;
if (t.isClassMethod(childNode, { kind: "constructor" })) {
// Collects parameter properties so that we can add an assignment
// for each of them in the constructor body
//
// We use a WeakSet to ensure an assignment for a parameter
// property is only added once. This is necessary for cases like
// using `transform-classes`, which causes this visitor to run
// twice.
const parameterProperties = [];
for (const param of childNode.params) {
if (
param.type === "TSParameterProperty" &&
!PARSED_PARAMS.has(param.parameter)
) {
PARSED_PARAMS.add(param.parameter);
parameterProperties.push(param.parameter);
// remove type imports
for (const stmt of path.get("body")) {
if (t.isImportDeclaration(stmt)) {
// Note: this will allow both `import { } from "m"` and `import "m";`.
// In TypeScript, the former would be elided.
if (stmt.node.specifiers.length === 0) {
continue;
}
}
if (parameterProperties.length) {
const assigns = parameterProperties.map(p => {
let id;
if (t.isIdentifier(p)) {
id = p;
} else if (t.isAssignmentPattern(p) && t.isIdentifier(p.left)) {
id = p.left;
let allElided = true;
const importsToRemove: Path<Node>[] = [];
for (const specifier of stmt.node.specifiers) {
const binding = stmt.scope.getBinding(specifier.local.name);
// The binding may not exist if the import node was explicitly
// injected by another plugin. Currently core does not do a good job
// of keeping scope bindings synchronized with the AST. For now we
// just bail if there is no binding, since chances are good that if
// the import statement was injected then it wasn't a typescript type
// import anyway.
if (
binding &&
isImportTypeOnly({
binding,
programPath: path,
jsxPragma: fileJsxPragma || jsxPragma,
})
) {
importsToRemove.push(binding.path);
} else {
throw path.buildCodeFrameError(
"Parameter properties can not be destructuring patterns.",
);
allElided = false;
}
}
return template.statement.ast`this.${id} = ${id}`;
});
injectInitialization(path, child, assigns);
}
} else if (child.isClassProperty()) {
childNode.typeAnnotation = null;
if (!childNode.value && !childNode.decorators) {
child.remove();
if (allElided) {
stmt.remove();
} else {
for (const importPath of importsToRemove) {
importPath.remove();
}
}
}
}
});
},
ExportNamedDeclaration(path) {
// remove export declaration if it's exporting only types
if (
path.node.specifiers.length > 0 &&
!path.node.specifiers.find(exportSpecifier =>
path.scope.hasOwnBinding(exportSpecifier.local.name),
)
) {
path.remove();
}
},
ExportSpecifier(path) {
// remove type exports
if (!path.scope.hasOwnBinding(path.node.local.name)) {
path.remove();
}
},
ExportDefaultDeclaration(path) {
// remove whole declaration if it's exporting a TS type
if (
t.isIdentifier(path.node.declaration) &&
!path.scope.hasOwnBinding(path.node.declaration.name)
) {
path.remove();
}
},
TSDeclareFunction(path) {
path.remove();
},
TSDeclareMethod(path) {
path.remove();
},
VariableDeclaration(path) {
if (path.node.declare) path.remove();
},
VariableDeclarator({ node }) {
if (node.definite) node.definite = null;
},
ClassMethod(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null;
// Rest handled by Function visitor
},
ClassProperty(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.readonly) node.readonly = null;
if (node.optional) node.optional = null;
if (node.definite) node.definite = null;
if (node.typeAnnotation) node.typeAnnotation = null;
},
TSIndexSignature(path) {
path.remove();
},
ClassDeclaration(path) {
const { node } = path;
if (node.declare) {
path.remove();
return;
}
},
Class(path) {
const { node } = path;
if (node.typeParameters) node.typeParameters = null;
if (node.superTypeParameters) node.superTypeParameters = null;
if (node.implements) node.implements = null;
if (node.abstract) node.abstract = null;
// Similar to the logic in `transform-flow-strip-types`, we need to
// handle `TSParameterProperty` and `ClassProperty` here because the
// class transform would transform the class, causing more specific
// visitors to not run.
path.get("body.body").forEach(child => {
const childNode = child.node;
if (t.isClassMethod(childNode, { kind: "constructor" })) {
// Collects parameter properties so that we can add an assignment
// for each of them in the constructor body
//
// We use a WeakSet to ensure an assignment for a parameter
// property is only added once. This is necessary for cases like
// using `transform-classes`, which causes this visitor to run
// twice.
const parameterProperties = [];
for (const param of childNode.params) {
if (
param.type === "TSParameterProperty" &&
!PARSED_PARAMS.has(param.parameter)
) {
PARSED_PARAMS.add(param.parameter);
parameterProperties.push(param.parameter);
}
}
if (parameterProperties.length) {
const assigns = parameterProperties.map(p => {
let id;
if (t.isIdentifier(p)) {
id = p;
} else if (
t.isAssignmentPattern(p) &&
t.isIdentifier(p.left)
) {
id = p.left;
} else {
throw path.buildCodeFrameError(
"Parameter properties can not be destructuring patterns.",
);
}
return template.statement.ast`this.${id} = ${id}`;
});
injectInitialization(path, child, assigns);
}
} else if (child.isClassProperty()) {
childNode.typeAnnotation = null;
if (!childNode.value && !childNode.decorators) {
child.remove();
}
}
});
},
Function({ node }) {
if (node.typeParameters) node.typeParameters = null;
if (node.returnType) node.returnType = null;
const p0 = node.params[0];
if (p0 && t.isIdentifier(p0) && p0.name === "this") {
node.params.shift();
}
// We replace `TSParameterProperty` here so that transforms that
// rely on a `Function` visitor to deal with arguments, like
// `transform-parameters`, work properly.
node.params = node.params.map(p => {
return p.type === "TSParameterProperty" ? p.parameter : p;
});
},
TSModuleDeclaration(path) {
transpileNamespace(path, t, allowNamespaces);
},
TSInterfaceDeclaration(path) {
path.remove();
},
TSTypeAliasDeclaration(path) {
path.remove();
},
TSEnumDeclaration(path) {
transpileEnum(path, t);
},
TSImportEqualsDeclaration(path) {
throw path.buildCodeFrameError(
"`import =` is not supported by @babel/plugin-transform-typescript\n" +
"Please consider using " +
"`import <moduleName> from '<moduleName>';` alongside " +
"Typescript's --allowSyntheticDefaultImports option.",
);
},
TSExportAssignment(path) {
throw path.buildCodeFrameError(
"`export =` is not supported by @babel/plugin-transform-typescript\n" +
"Please consider using `export <value>;`.",
);
},
TSTypeAssertion(path) {
path.replaceWith(path.node.expression);
},
TSAsExpression(path) {
let { node } = path;
do {
node = node.expression;
} while (t.isTSAsExpression(node));
path.replaceWith(node);
},
TSNonNullExpression(path) {
path.replaceWith(path.node.expression);
},
CallExpression(path) {
path.node.typeParameters = null;
},
NewExpression(path) {
path.node.typeParameters = null;
},
JSXOpeningElement(path) {
path.node.typeParameters = null;
},
TaggedTemplateExpression(path) {
path.node.typeParameters = null;
},
},
};
Function({ node }) {
if (node.typeParameters) node.typeParameters = null;
if (node.returnType) node.returnType = null;
function visitPattern({ node }) {
if (node.typeAnnotation) node.typeAnnotation = null;
if (t.isIdentifier(node) && node.optional) node.optional = null;
// 'access' and 'readonly' are only for parameter properties, so constructor visitor will handle them.
}
const p0 = node.params[0];
if (p0 && t.isIdentifier(p0) && p0.name === "this") {
node.params.shift();
function isImportTypeOnly({ binding, programPath, jsxPragma }) {
for (const path of binding.referencePaths) {
if (!isInType(path)) {
return false;
}
// We replace `TSParameterProperty` here so that transforms that
// rely on a `Function` visitor to deal with arguments, like
// `transform-parameters`, work properly.
node.params = node.params.map(p => {
return p.type === "TSParameterProperty" ? p.parameter : p;
});
},
TSModuleDeclaration(path) {
if (!path.node.declare && path.node.id.type !== "StringLiteral") {
throw path.buildCodeFrameError("Namespaces are not supported.");
}
path.remove();
},
TSInterfaceDeclaration(path) {
path.remove();
},
TSTypeAliasDeclaration(path) {
path.remove();
},
TSEnumDeclaration(path) {
transpileEnum(path, t);
},
TSImportEqualsDeclaration(path) {
throw path.buildCodeFrameError(
"`import =` is not supported by @babel/plugin-transform-typescript\n" +
"Please consider using " +
"`import <moduleName> from '<moduleName>';` alongside " +
"Typescript's --allowSyntheticDefaultImports option.",
);
},
TSExportAssignment(path) {
throw path.buildCodeFrameError(
"`export =` is not supported by @babel/plugin-transform-typescript\n" +
"Please consider using `export <value>;`.",
);
},
TSTypeAssertion(path) {
path.replaceWith(path.node.expression);
},
TSAsExpression(path) {
let { node } = path;
do {
node = node.expression;
} while (t.isTSAsExpression(node));
path.replaceWith(node);
},
TSNonNullExpression(path) {
path.replaceWith(path.node.expression);
},
CallExpression(path) {
path.node.typeParameters = null;
},
NewExpression(path) {
path.node.typeParameters = null;
},
JSXOpeningElement(path) {
path.node.typeParameters = null;
},
TaggedTemplateExpression(path) {
path.node.typeParameters = null;
},
},
};
function visitPattern({ node }) {
if (node.typeAnnotation) node.typeAnnotation = null;
if (t.isIdentifier(node) && node.optional) node.optional = null;
// 'access' and 'readonly' are only for parameter properties, so constructor visitor will handle them.
}
function isImportTypeOnly({ binding, programPath, jsxPragma }) {
for (const path of binding.referencePaths) {
if (!isInType(path)) {
return false;
}
}
if (binding.identifier.name !== jsxPragma) {
return true;
}
if (binding.identifier.name !== jsxPragma) {
return true;
}
// "React" or the JSX pragma is referenced as a value if there are any JSX elements in the code.
let sourceFileHasJsx = false;
programPath.traverse({
JSXElement() {
sourceFileHasJsx = true;
},
JSXFragment() {
sourceFileHasJsx = true;
},
});
return !sourceFileHasJsx;
}
});
// "React" or the JSX pragma is referenced as a value if there are any JSX elements in the code.
let sourceFileHasJsx = false;
programPath.traverse({
JSXElement() {
sourceFileHasJsx = true;
},
JSXFragment() {
sourceFileHasJsx = true;
},
});
return !sourceFileHasJsx;
}
},
);

View File

@ -0,0 +1,171 @@
import { template } from "@babel/core";
export default function transpileNamespace(path, t, allowNamespaces) {
if (path.node.declare || path.node.id.type === "StringLiteral") {
path.remove();
return;
}
if (!allowNamespaces) {
throw path.hub.file.buildCodeFrameError(
path.node.id,
"Namespace not marked type-only declare." +
" Non-declarative namespaces are only supported experimentally in Babel." +
" To enable and review caveats see:" +
" https://babeljs.io/docs/en/babel-plugin-transform-typescript",
);
}
const name = path.node.id.name;
const value = handleNested(path, t, t.cloneDeep(path.node));
const bound = path.scope.hasOwnBinding(name);
if (path.parent.type === "ExportNamedDeclaration") {
if (!bound) {
path.parentPath.insertAfter(value);
path.replaceWith(getDeclaration(t, name));
path.scope.registerDeclaration(path.parentPath);
} else {
path.parentPath.replaceWith(value);
}
} else if (bound) {
path.replaceWith(value);
} else {
path.scope.registerDeclaration(
path.replaceWithMultiple([getDeclaration(t, name), value])[0],
);
}
}
function getDeclaration(t, name) {
return t.variableDeclaration("let", [
t.variableDeclarator(t.identifier(name)),
]);
}
function getMemberExpression(t, name, itemName) {
return t.memberExpression(t.identifier(name), t.identifier(itemName));
}
function handleNested(path, t, node, parentExport) {
const names = new Set();
const realName = node.id;
const name = path.scope.generateUid(realName.name);
const namespaceTopLevel = node.body.body;
for (let i = 0; i < namespaceTopLevel.length; i++) {
const subNode = namespaceTopLevel[i];
// The first switch is mainly to detect name usage. Only export
// declarations require further transformation.
switch (subNode.type) {
case "TSModuleDeclaration": {
const transformed = handleNested(path, t, subNode);
const moduleName = subNode.id.name;
if (names.has(moduleName)) {
namespaceTopLevel[i] = transformed;
} else {
names.add(moduleName);
namespaceTopLevel.splice(
i++,
1,
getDeclaration(t, moduleName),
transformed,
);
}
continue;
}
case "TSEnumDeclaration":
case "FunctionDeclaration":
case "ClassDeclaration":
names.add(subNode.id.name);
continue;
case "VariableDeclaration":
for (const variable of subNode.declarations) {
names.add(variable.id.name);
}
continue;
default:
// Neither named declaration nor export, continue to next item.
continue;
case "ExportNamedDeclaration":
// Export declarations get parsed using the next switch.
}
// Transform the export declarations that occur inside of a namespace.
switch (subNode.declaration.type) {
case "TSEnumDeclaration":
case "FunctionDeclaration":
case "ClassDeclaration": {
const itemName = subNode.declaration.id.name;
names.add(itemName);
namespaceTopLevel.splice(
i++,
1,
subNode.declaration,
t.expressionStatement(
t.assignmentExpression(
"=",
getMemberExpression(t, name, itemName),
t.identifier(itemName),
),
),
);
break;
}
case "VariableDeclaration":
if (subNode.declaration.kind !== "const") {
throw path.hub.file.buildCodeFrameError(
subNode.declaration,
"Namespaces exporting non-const are not supported by Babel." +
" Change to const or see:" +
" https://babeljs.io/docs/en/babel-plugin-transform-typescript",
);
}
for (const variable of subNode.declaration.declarations) {
variable.init = t.assignmentExpression(
"=",
getMemberExpression(t, name, variable.id.name),
variable.init,
);
}
namespaceTopLevel[i] = subNode.declaration;
break;
case "TSModuleDeclaration": {
const transformed = handleNested(
path,
t,
subNode.declaration,
t.identifier(name),
);
const moduleName = subNode.declaration.id.name;
if (names.has(moduleName)) {
namespaceTopLevel[i] = transformed;
} else {
names.add(moduleName);
namespaceTopLevel.splice(
i++,
1,
getDeclaration(t, moduleName),
transformed,
);
}
}
}
}
// {}
let fallthroughValue = t.objectExpression([]);
if (parentExport) {
fallthroughValue = template.expression.ast`
${parentExport}.${realName} || (
${parentExport}.${realName} = ${fallthroughValue}
)
`;
}
return template.statement.ast`
(function (${t.identifier(name)}) {
${namespaceTopLevel}
})(${realName} || (${realName} = ${fallthroughValue}));
`;
}

View File

@ -0,0 +1,4 @@
; // Otherwise-empty file
export declare namespace P {
export namespace C {}
}

View File

@ -0,0 +1 @@
; // Otherwise-empty file

View File

@ -0,0 +1,35 @@
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
constructor() {
console.log("1");
}
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
let strings = ["Hello", "98052", "101"];
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
for (let s of strings) {
for (let name in validators) {
console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
}

View File

@ -0,0 +1,39 @@
let Validation;
(function (_Validation) {
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
class LettersOnlyValidator {
constructor() {
console.log("1");
}
isAcceptable(s) {
return lettersRegexp.test(s);
}
}
_Validation.LettersOnlyValidator = LettersOnlyValidator;
class ZipCodeValidator {
isAcceptable(s) {
return s.length === 5 && numberRegexp.test(s);
}
}
_Validation.ZipCodeValidator = ZipCodeValidator;
})(Validation || (Validation = {}));
let strings = ["Hello", "98052", "101"];
let validators = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
for (let s of strings) {
for (let name in validators) {
console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);
}
}

View File

@ -0,0 +1,4 @@
class A { }
namespace A {
export const B = 1;
}

View File

@ -0,0 +1,5 @@
class A {}
(function (_A) {
const B = _A.B = 1;
})(A || (A = {}));

View File

@ -0,0 +1,6 @@
enum A {
C = 2,
}
namespace A {
export const B = 1;
}

View File

@ -0,0 +1,9 @@
var A;
(function (A) {
A[A["C"] = 2] = "C";
})(A || (A = {}));
(function (_A) {
const B = _A.B = 1;
})(A || (A = {}));

View File

@ -0,0 +1,3 @@
export class N {}
export namespace N {}
export default N;

View File

@ -0,0 +1,5 @@
export class N {}
(function (_N) {})(N || (N = {}));
export default N;

View File

@ -0,0 +1,3 @@
import N from 'n';
namespace N {}

View File

@ -0,0 +1,3 @@
import N from 'n';
(function (_N) {})(N || (N = {}));

View File

@ -0,0 +1,36 @@
namespace N {
namespace N {}
namespace constructor {}
namespace length {}
namespace concat {}
namespace copyWithin {}
namespace fill {}
namespace find {}
namespace findIndex {}
namespace lastIndexOf {}
namespace pop {}
namespace push {}
namespace reverse {}
namespace shift {}
namespace unshift {}
namespace slice {}
namespace sort {}
namespace splice {}
namespace includes {}
namespace indexOf {}
namespace join {}
namespace keys {}
namespace entries {}
namespace values {}
namespace forEach {}
namespace filter {}
namespace map {}
namespace every {}
namespace some {}
namespace reduce {}
namespace reduceRight {}
namespace toLocaleString {}
namespace toString {}
namespace flat {}
namespace flatMap {}
}

View File

@ -0,0 +1,139 @@
let N;
(function (_N) {
let N;
(function (_N2) {})(N || (N = {}));
let constructor;
(function (_constructor) {})(constructor || (constructor = {}));
let length;
(function (_length) {})(length || (length = {}));
let concat;
(function (_concat) {})(concat || (concat = {}));
let copyWithin;
(function (_copyWithin) {})(copyWithin || (copyWithin = {}));
let fill;
(function (_fill) {})(fill || (fill = {}));
let find;
(function (_find) {})(find || (find = {}));
let findIndex;
(function (_findIndex) {})(findIndex || (findIndex = {}));
let lastIndexOf;
(function (_lastIndexOf) {})(lastIndexOf || (lastIndexOf = {}));
let pop;
(function (_pop) {})(pop || (pop = {}));
let push;
(function (_push) {})(push || (push = {}));
let reverse;
(function (_reverse) {})(reverse || (reverse = {}));
let shift;
(function (_shift) {})(shift || (shift = {}));
let unshift;
(function (_unshift) {})(unshift || (unshift = {}));
let slice;
(function (_slice) {})(slice || (slice = {}));
let sort;
(function (_sort) {})(sort || (sort = {}));
let splice;
(function (_splice) {})(splice || (splice = {}));
let includes;
(function (_includes) {})(includes || (includes = {}));
let indexOf;
(function (_indexOf) {})(indexOf || (indexOf = {}));
let join;
(function (_join) {})(join || (join = {}));
let keys;
(function (_keys) {})(keys || (keys = {}));
let entries;
(function (_entries) {})(entries || (entries = {}));
let values;
(function (_values) {})(values || (values = {}));
let forEach;
(function (_forEach) {})(forEach || (forEach = {}));
let filter;
(function (_filter) {})(filter || (filter = {}));
let map;
(function (_map) {})(map || (map = {}));
let every;
(function (_every) {})(every || (every = {}));
let some;
(function (_some) {})(some || (some = {}));
let reduce;
(function (_reduce) {})(reduce || (reduce = {}));
let reduceRight;
(function (_reduceRight) {})(reduceRight || (reduceRight = {}));
let toLocaleString;
(function (_toLocaleString) {})(toLocaleString || (toLocaleString = {}));
let toString;
(function (_toString) {})(toString || (toString = {}));
let flat;
(function (_flat) {})(flat || (flat = {}));
let flatMap;
(function (_flatMap) {})(flatMap || (flatMap = {}));
})(N || (N = {}));

View File

@ -0,0 +1 @@
export namespace N {}

View File

@ -0,0 +1,3 @@
export let N;
(function (_N) {})(N || (N = {}));

View File

@ -1,3 +0,0 @@
{
"throws": "Namespaces are not supported."
}

View File

@ -0,0 +1,12 @@
export module src {
export namespace ns1 {
export class foo {
F1: string;
}
}
export namespace ns2 {
export class foo {
F1: string;
}
}
}

View File

@ -0,0 +1,19 @@
export let src;
(function (_src) {
let ns1;
(function (_ns) {
class foo {}
_ns.foo = foo;
})(ns1 || (ns1 = _src.ns1 || (_src.ns1 = {})));
let ns2;
(function (_ns2) {
class foo {}
_ns2.foo = foo;
})(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {})));
})(src || (src = {}));

View File

@ -0,0 +1,12 @@
module src {
export namespace ns1 {
export class foo {
F1: string;
}
}
export namespace ns2 {
export class foo {
F1: string;
}
}
}

View File

@ -0,0 +1,19 @@
let src;
(function (_src) {
let ns1;
(function (_ns) {
class foo {}
_ns.foo = foo;
})(ns1 || (ns1 = _src.ns1 || (_src.ns1 = {})));
let ns2;
(function (_ns2) {
class foo {}
_ns2.foo = foo;
})(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {})));
})(src || (src = {}));

View File

@ -0,0 +1,2 @@
namespace N {}
namespace N {}

View File

@ -0,0 +1,5 @@
let N;
(function (_N) {})(N || (N = {}));
(function (_N2) {})(N || (N = {}));

View File

@ -0,0 +1,3 @@
namespace N {
export let V;
}

View File

@ -0,0 +1,3 @@
{
"throws": "Namespaces exporting non-const are not supported by Babel. Change to const or see: https://babeljs.io/docs/en/babel-plugin-transform-typescript"
}

View File

@ -0,0 +1 @@
namespace N {}

View File

@ -0,0 +1,4 @@
{
"throws": "Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel. To enable and review caveats see: https://babeljs.io/docs/en/babel-plugin-transform-typescript",
"plugins": [["transform-typescript", { "allowNamespaces": false }]]
}

View File

@ -0,0 +1,26 @@
class A { }
namespace A {
export namespace C {
export class G {}
export const E = 7;
}
function M() {}
namespace M {
export const N = C.E;
}
export function D() {}
export namespace D {
const C = 5;
export enum H {
I = 11,
J = 13,
K = 17,
}
}
class F {}
namespace F {}
namespace G {}
enum L {
M = 19,
}
}

View File

@ -0,0 +1,49 @@
class A {}
(function (_A) {
let C;
(function (_C) {
class G {}
_C.G = G;
const E = _C.E = 7;
})(C || (C = _A.C || (_A.C = {})));
function M() {}
(function (_M) {
const N = _M.N = C.E;
})(M || (M = {}));
function D() {}
_A.D = D;
(function (_D) {
const C = 5;
let H;
(function (H) {
H[H["I"] = 11] = "I";
H[H["J"] = 13] = "J";
H[H["K"] = 17] = "K";
})(H || (H = {}));
_D.H = H;
})(D || (D = _A.D || (_A.D = {})));
class F {}
(function (_F) {})(F || (F = {}));
let G;
(function (_G) {})(G || (G = {}));
let L;
(function (L) {
L[L["M"] = 19] = "M";
})(L || (L = {}));
})(A || (A = {}));

View File

@ -0,0 +1,12 @@
namespace N {
namespace _N7 {}
export namespace N {
export function _N3() {}
}
export namespace N {
export class _N5 {}
}
export namespace N {
export enum _N {}
}
}

View File

@ -0,0 +1,29 @@
let N;
(function (_N2) {
let _N7;
(function (_N4) {})(_N7 || (_N7 = {}));
let N;
(function (_N6) {
function _N3() {}
_N6._N3 = _N3;
})(N || (N = _N2.N || (_N2.N = {})));
(function (_N8) {
class _N5 {}
_N8._N5 = _N5;
})(N || (N = _N2.N || (_N2.N = {})));
(function (_N9) {
let _N;
(function (_N) {})(_N || (_N = {}));
_N9._N = _N;
})(N || (N = _N2.N || (_N2.N = {})));
})(N || (N = {}));

View File

@ -0,0 +1 @@
namespace N {}

View File

@ -0,0 +1,3 @@
let N;
(function (_N) {})(N || (N = {}));

View File

@ -1,3 +1,3 @@
{
"plugins": ["transform-typescript"]
"plugins": [["transform-typescript", { "allowNamespaces": true }]]
}

View File

@ -399,6 +399,7 @@ defineType("TSModuleDeclaration", {
});
defineType("TSModuleBlock", {
aliases: ["Scopable", "Block", "BlockParent"],
visitor: ["body"],
fields: {
body: validateArrayOfType("Statement"),

View File

@ -3350,6 +3350,7 @@ export function isScopable(node: ?Object, opts?: Object): boolean {
"ForOfStatement" === nodeType ||
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType ||
"TSModuleBlock" === nodeType ||
(nodeType === "Placeholder" && "BlockStatement" === node.expectedNode)
) {
if (typeof opts === "undefined") {
@ -3382,6 +3383,7 @@ export function isBlockParent(node: ?Object, opts?: Object): boolean {
"ForOfStatement" === nodeType ||
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType ||
"TSModuleBlock" === nodeType ||
(nodeType === "Placeholder" && "BlockStatement" === node.expectedNode)
) {
if (typeof opts === "undefined") {
@ -3401,6 +3403,7 @@ export function isBlock(node: ?Object, opts?: Object): boolean {
nodeType === "Block" ||
"BlockStatement" === nodeType ||
"Program" === nodeType ||
"TSModuleBlock" === nodeType ||
(nodeType === "Placeholder" && "BlockStatement" === node.expectedNode)
) {
if (typeof opts === "undefined") {