diff --git a/packages/babel-plugin-transform-typescript/src/index.js b/packages/babel-plugin-transform-typescript/src/index.js index 29afc79cf112..8a8ca8bfd778 100644 --- a/packages/babel-plugin-transform-typescript/src/index.js +++ b/packages/babel-plugin-transform-typescript/src/index.js @@ -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) { @@ -24,331 +25,333 @@ interface State { const PARSED_PARAMS = new WeakSet(); const PRAGMA_KEY = "@babel/plugin-transform-typescript/jsxPragma"; -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: State) { - state.programPath = path; + Program(path, state: State) { + state.programPath = path; - const { file } = state; + const { file } = state; - if (file.ast.comments) { - for (const comment of (file.ast.comments: Array)) { - const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value); - if (jsxMatches) { - file.set(PRAGMA_KEY, jsxMatches[1]); + if (file.ast.comments) { + for (const comment of (file.ast.comments: Array)) { + const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value); + if (jsxMatches) { + file.set(PRAGMA_KEY, 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; - } + // 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[] = []; - - 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(file, binding, state.programPath) - ) { - importsToRemove.push(binding.path); - } else { - allElided = false; + let allElided = true; + const importsToRemove: Path[] = []; + + 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(file, binding, state.programPath) + ) { + importsToRemove.push(binding.path); + } else { + allElided = false; + } } - } - if (allElided) { - stmt.remove(); - } else { - for (const importPath of importsToRemove) { - importPath.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(); + } + }, - 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(); + } + }, - ExportSpecifier(path) { - // remove type exports - if (!path.scope.hasOwnBinding(path.node.local.name)) { + TSDeclareFunction(path) { 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) - ) { + TSDeclareMethod(path) { path.remove(); - } - }, + }, - TSDeclareFunction(path) { - path.remove(); - }, + VariableDeclaration(path) { + if (path.node.declare) path.remove(); + }, - TSDeclareMethod(path) { - path.remove(); - }, + VariableDeclarator({ node }) { + if (node.definite) node.definite = null; + }, - VariableDeclaration(path) { - if (path.node.declare) path.remove(); - }, + ClassMethod(path) { + const { node } = path; - VariableDeclarator({ node }) { - if (node.definite) node.definite = null; - }, + if (node.accessibility) node.accessibility = null; + if (node.abstract) node.abstract = null; + if (node.optional) node.optional = null; - ClassMethod(path) { - const { node } = path; + // Rest handled by Function visitor + }, - if (node.accessibility) node.accessibility = null; - if (node.abstract) node.abstract = null; - if (node.optional) node.optional = null; + ClassProperty(path) { + const { node } = path; - // 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(); - }, + 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; + }, - ClassDeclaration(path) { - const { node } = path; - if (node.declare) { + TSIndexSignature(path) { 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.", - ); + 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); } + } - return template.statement.ast`this.${id} = ${id}`; - }); - - injectInitialization(path, child, assigns); - } - } else if (child.isClassProperty()) { - childNode.typeAnnotation = null; + 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(); + 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) { - 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 from '';` alongside " + - "Typescript's --allowSyntheticDefaultImports option.", - ); - }, - - TSExportAssignment(path) { - throw path.buildCodeFrameError( - "`export =` is not supported by @babel/plugin-transform-typescript\n" + - "Please consider using `export ;`.", - ); - }, + Function({ node }) { + if (node.typeParameters) node.typeParameters = null; + if (node.returnType) node.returnType = null; - TSTypeAssertion(path) { - path.replaceWith(path.node.expression); - }, - - TSAsExpression(path) { - let { node } = path; - do { - node = node.expression; - } while (t.isTSAsExpression(node)); - path.replaceWith(node); - }, + const p0 = node.params[0]; + if (p0 && t.isIdentifier(p0) && p0.name === "this") { + node.params.shift(); + } - TSNonNullExpression(path) { - path.replaceWith(path.node.expression); - }, + // 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; + }); + }, - CallExpression(path) { - path.node.typeParameters = null; - }, + TSModuleDeclaration(path) { + transpileNamespace(path, t, allowNamespaces); + }, - NewExpression(path) { - path.node.typeParameters = null; - }, + TSInterfaceDeclaration(path) { + path.remove(); + }, - JSXOpeningElement(path) { - path.node.typeParameters = null; + 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 from '';` alongside " + + "Typescript's --allowSyntheticDefaultImports option.", + ); + }, + + TSExportAssignment(path) { + throw path.buildCodeFrameError( + "`export =` is not supported by @babel/plugin-transform-typescript\n" + + "Please consider using `export ;`.", + ); + }, + + 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; + }, }, + }; - 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 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(file, binding, programPath) { + for (const path of binding.referencePaths) { + if (!isInType(path)) { + return false; + } + } - function isImportTypeOnly(file, binding, programPath) { - for (const path of binding.referencePaths) { - if (!isInType(path)) { - return false; + const fileJsxPragma = file.get(PRAGMA_KEY) || jsxPragma; + if (binding.identifier.name !== fileJsxPragma) { + return true; } - } - const fileJsxPragma = file.get(PRAGMA_KEY) || jsxPragma; - if (binding.identifier.name !== fileJsxPragma) { - 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; - } -}); + }, +); diff --git a/packages/babel-plugin-transform-typescript/src/namespace.js b/packages/babel-plugin-transform-typescript/src/namespace.js new file mode 100644 index 000000000000..32d53447a80a --- /dev/null +++ b/packages/babel-plugin-transform-typescript/src/namespace.js @@ -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})); + `; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/input.mjs new file mode 100644 index 000000000000..8d01bb41f132 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/input.mjs @@ -0,0 +1,4 @@ +; // Otherwise-empty file +export declare namespace P { + export namespace C {} +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs new file mode 100644 index 000000000000..dab5d3d1c154 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs @@ -0,0 +1 @@ +; // Otherwise-empty file diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/input.mjs new file mode 100644 index 000000000000..58f6b5b21389 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/input.mjs @@ -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 }`); + } +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs new file mode 100644 index 000000000000..f6993c98b8f4 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs @@ -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}`); + } +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/input.mjs new file mode 100644 index 000000000000..f8debe984b83 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/input.mjs @@ -0,0 +1,4 @@ +class A { } +namespace A { + export const B = 1; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs new file mode 100644 index 000000000000..98c88f006331 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs @@ -0,0 +1,5 @@ +class A {} + +(function (_A) { + const B = _A.B = 1; +})(A || (A = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/input.mjs new file mode 100644 index 000000000000..7b569657b080 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/input.mjs @@ -0,0 +1,6 @@ +enum A { + C = 2, +} +namespace A { + export const B = 1; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs new file mode 100644 index 000000000000..5a1aa715f308 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs @@ -0,0 +1,9 @@ +var A; + +(function (A) { + A[A["C"] = 2] = "C"; +})(A || (A = {})); + +(function (_A) { + const B = _A.B = 1; +})(A || (A = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-export/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-export/input.mjs new file mode 100644 index 000000000000..8122f8e6cce5 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-export/input.mjs @@ -0,0 +1,3 @@ +export class N {} +export namespace N {} +export default N; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-export/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-export/output.mjs new file mode 100644 index 000000000000..e7bc977078a7 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-export/output.mjs @@ -0,0 +1,5 @@ +export class N {} + +(function (_N) {})(N || (N = {})); + +export default N; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-import/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-import/input.mjs new file mode 100644 index 000000000000..ef68acd5adb9 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-import/input.mjs @@ -0,0 +1,3 @@ +import N from 'n'; + +namespace N {} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-import/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-import/output.mjs new file mode 100644 index 000000000000..5ee84b002fe6 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-import/output.mjs @@ -0,0 +1,3 @@ +import N from 'n'; + +(function (_N) {})(N || (N = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/input.mjs new file mode 100644 index 000000000000..efc42d05c9c9 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/input.mjs @@ -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 {} +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs new file mode 100644 index 000000000000..749100aaa780 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs @@ -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 = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/export/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/export/input.mjs new file mode 100644 index 000000000000..a27f292cbf1c --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/export/input.mjs @@ -0,0 +1 @@ +export namespace N {} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/export/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/export/output.mjs new file mode 100644 index 000000000000..edd98b8f9582 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/export/output.mjs @@ -0,0 +1,3 @@ +export let N; + +(function (_N) {})(N || (N = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/fails/input.js b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/fails/input.js deleted file mode 100644 index 4db486e32f06..000000000000 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/fails/input.js +++ /dev/null @@ -1 +0,0 @@ -namespace N {} \ No newline at end of file diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/fails/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/fails/options.json deleted file mode 100644 index 8449a74304e1..000000000000 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/fails/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "Namespaces are not supported." -} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/input.mjs new file mode 100644 index 000000000000..35c080283606 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/input.mjs @@ -0,0 +1,2 @@ +namespace N {} +namespace N {} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs new file mode 100644 index 000000000000..12b4c7a92253 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs @@ -0,0 +1,5 @@ +let N; + +(function (_N) {})(N || (N = {})); + +(function (_N2) {})(N || (N = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/mutable-fail/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/mutable-fail/input.mjs new file mode 100644 index 000000000000..167274d7aee5 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/mutable-fail/input.mjs @@ -0,0 +1,3 @@ +namespace N { + export let V; +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/mutable-fail/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/mutable-fail/options.json new file mode 100644 index 000000000000..1a58e10cc896 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/mutable-fail/options.json @@ -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" +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-flag/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-flag/input.mjs new file mode 100644 index 000000000000..fe71f4004e4a --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-flag/input.mjs @@ -0,0 +1 @@ +namespace N {} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-flag/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-flag/options.json new file mode 100644 index 000000000000..186a430133f0 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-flag/options.json @@ -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 }]] +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/input.mjs new file mode 100644 index 000000000000..87dd9140b28f --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/input.mjs @@ -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, + } +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs new file mode 100644 index 000000000000..1e85423d2c94 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs @@ -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 = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/input.mjs new file mode 100644 index 000000000000..ff59e9d4fa36 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/input.mjs @@ -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 {} + } +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs new file mode 100644 index 000000000000..b8db977b690f --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs @@ -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 = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/input.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/input.mjs new file mode 100644 index 000000000000..fe71f4004e4a --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/input.mjs @@ -0,0 +1 @@ +namespace N {} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs new file mode 100644 index 000000000000..eca9b4570a75 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs @@ -0,0 +1,3 @@ +let N; + +(function (_N) {})(N || (N = {})); diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/options.json index 5c79172a6082..ed634a8c9790 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/options.json +++ b/packages/babel-plugin-transform-typescript/test/fixtures/options.json @@ -1,3 +1,3 @@ { - "plugins": ["transform-typescript"] + "plugins": [["transform-typescript", { "allowNamespaces": true }]] } diff --git a/packages/babel-types/src/definitions/typescript.js b/packages/babel-types/src/definitions/typescript.js index f6a89fa8dcd3..843f2408ecc9 100644 --- a/packages/babel-types/src/definitions/typescript.js +++ b/packages/babel-types/src/definitions/typescript.js @@ -399,6 +399,7 @@ defineType("TSModuleDeclaration", { }); defineType("TSModuleBlock", { + aliases: ["Scopable", "Block", "BlockParent"], visitor: ["body"], fields: { body: validateArrayOfType("Statement"), diff --git a/packages/babel-types/src/validators/generated/index.js b/packages/babel-types/src/validators/generated/index.js index 890999e5d0e1..076495d5cb28 100644 --- a/packages/babel-types/src/validators/generated/index.js +++ b/packages/babel-types/src/validators/generated/index.js @@ -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") {