[babel 8] Enable allowDeclareFields option by default with TS (#12461)

This commit is contained in:
Nicolò Ribaudo 2021-01-08 01:28:20 +01:00 committed by GitHub
parent ff52acee79
commit 50462eb5e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 584 additions and 453 deletions

View File

@ -149,6 +149,14 @@ jobs:
at: /tmp/verdaccio-workspace at: /tmp/verdaccio-workspace
- run: ./scripts/integration-tests/e2e-babel.sh - run: ./scripts/integration-tests/e2e-babel.sh
e2e-babel-breaking:
executor: node-executor
steps:
- checkout
- attach_workspace:
at: /tmp/verdaccio-workspace
- run: BABEL_8_BREAKING=true ./scripts/integration-tests/e2e-babel.sh
e2e-babel-old-version: e2e-babel-old-version:
executor: node-executor executor: node-executor
steps: steps:
@ -185,6 +193,14 @@ jobs:
at: /tmp/verdaccio-workspace at: /tmp/verdaccio-workspace
- run: ./scripts/integration-tests/e2e-jest.sh - run: ./scripts/integration-tests/e2e-jest.sh
e2e-jest-breaking:
executor: node-python-executor
steps:
- checkout
- attach_workspace:
at: /tmp/verdaccio-workspace
- run: BABEL_8_BREAKING=true ./scripts/integration-tests/e2e-jest.sh
workflows: workflows:
version: 2 version: 2
build-standalone: build-standalone:
@ -247,7 +263,7 @@ workflows:
filters: filters:
branches: branches:
only: [main, next-8-dev, next-8-rebased] only: [main, next-8-dev, next-8-rebased]
- e2e-babel: - e2e-babel-breaking:
requires: requires:
- publish-verdaccio-babel-8-breaking - publish-verdaccio-babel-8-breaking
- e2e-create-react-app: - e2e-create-react-app:
@ -256,7 +272,7 @@ workflows:
- e2e-vue-cli: - e2e-vue-cli:
requires: requires:
- publish-verdaccio-babel-8-breaking - publish-verdaccio-babel-8-breaking
- e2e-jest: - e2e-jest-breaking:
requires: requires:
- publish-verdaccio-babel-8-breaking - publish-verdaccio-babel-8-breaking
@ -270,7 +286,7 @@ workflows:
- publish-verdaccio-babel-8-breaking: - publish-verdaccio-babel-8-breaking:
requires: requires:
- approve-e2e-breaking-run - approve-e2e-breaking-run
- e2e-babel: - e2e-babel-breaking:
requires: requires:
- publish-verdaccio-babel-8-breaking - publish-verdaccio-babel-8-breaking
- e2e-create-react-app: - e2e-create-react-app:
@ -279,7 +295,7 @@ workflows:
- e2e-vue-cli: - e2e-vue-cli:
requires: requires:
- publish-verdaccio-babel-8-breaking - publish-verdaccio-babel-8-breaking
- e2e-jest: - e2e-jest-breaking:
requires: requires:
- publish-verdaccio-babel-8-breaking - publish-verdaccio-babel-8-breaking

View File

@ -0,0 +1,6 @@
class C {
// Output should not use `_initialiseProps`
x: T;
y = 0;
constructor(T) {}
}

View File

@ -0,0 +1,4 @@
{
"BABEL_8_BREAKING": false,
"plugins": ["transform-typescript", "proposal-class-properties"]
}

View File

@ -1,8 +1,9 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class A { class C {
constructor() { // Output should not use `_initialiseProps`
_defineProperty(this, "y", void 0); constructor(T) {
_defineProperty(this, "y", 0);
} }
} }

View File

@ -0,0 +1,6 @@
class C {
// Output should not use `_initialiseProps`
x: T;
y = 0;
constructor(T) {}
}

View File

@ -0,0 +1,7 @@
{
"BABEL_8_BREAKING": false,
"plugins": [
"transform-typescript",
["proposal-class-properties", { "loose": true }]
]
}

View File

@ -0,0 +1,7 @@
class C {
// Output should not use `_initialiseProps`
constructor(T) {
this.y = 0;
}
}

View File

@ -1,4 +1,5 @@
{ {
"BABEL_8_BREAKING": true,
"plugins": [ "plugins": [
"transform-typescript", "transform-typescript",
["proposal-class-properties", { "loose": true }] ["proposal-class-properties", { "loose": true }]

View File

@ -1,6 +1,7 @@
class C { class C {
// Output should not use `_initialiseProps` // Output should not use `_initialiseProps`
constructor(T) { constructor(T) {
this.x = void 0;
this.y = 0; this.y = 0;
} }

View File

@ -1,3 +1,4 @@
{ {
"BABEL_8_BREAKING": true,
"plugins": ["transform-typescript", "proposal-class-properties"] "plugins": ["transform-typescript", "proposal-class-properties"]
} }

View File

@ -3,6 +3,8 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
class C { class C {
// Output should not use `_initialiseProps` // Output should not use `_initialiseProps`
constructor(T) { constructor(T) {
_defineProperty(this, "x", void 0);
_defineProperty(this, "y", 0); _defineProperty(this, "y", 0);
} }

View File

@ -45,52 +45,59 @@ function registerGlobalType(programScope, name) {
GLOBAL_TYPES.get(programScope.path.node).add(name); GLOBAL_TYPES.get(programScope.path.node).add(name);
} }
export default declare( export default declare((api, opts) => {
( api.assertVersion(7);
api,
{
jsxPragma = "React.createElement",
jsxPragmaFrag = "React.Fragment",
allowNamespaces = false,
allowDeclareFields = false,
onlyRemoveTypeImports = false,
},
) => {
api.assertVersion(7);
const JSX_PRAGMA_REGEX = /\*?\s*@jsx((?:Frag)?)\s+([^\s]+)/; const JSX_PRAGMA_REGEX = /\*?\s*@jsx((?:Frag)?)\s+([^\s]+)/;
const classMemberVisitors = { const {
field(path) { jsxPragma = "React.createElement",
const { node } = path; jsxPragmaFrag = "React.Fragment",
allowNamespaces = false,
onlyRemoveTypeImports = false,
} = opts;
if (!process.env.BABEL_8_BREAKING) {
// eslint-disable-next-line no-var
var { allowDeclareFields = false } = opts;
}
const classMemberVisitors = {
field(path) {
const { node } = path;
if (!process.env.BABEL_8_BREAKING) {
if (!allowDeclareFields && node.declare) { if (!allowDeclareFields && node.declare) {
throw path.buildCodeFrameError( throw path.buildCodeFrameError(
`The 'declare' modifier is only allowed when the 'allowDeclareFields' option of ` + `The 'declare' modifier is only allowed when the 'allowDeclareFields' option of ` +
`@babel/plugin-transform-typescript or @babel/preset-typescript is enabled.`, `@babel/plugin-transform-typescript or @babel/preset-typescript is enabled.`,
); );
} }
if (node.declare) { }
if (node.value) { if (node.declare) {
throw path.buildCodeFrameError( if (node.value) {
`Fields with the 'declare' modifier cannot be initialized here, but only in the constructor`, throw path.buildCodeFrameError(
); `Fields with the 'declare' modifier cannot be initialized here, but only in the constructor`,
} );
if (!node.decorators) { }
path.remove(); if (!node.decorators) {
} path.remove();
} else if (node.definite) { }
if (node.value) { } else if (node.definite) {
throw path.buildCodeFrameError( if (node.value) {
`Definitely assigned fields cannot be initialized here, but only in the constructor`, throw path.buildCodeFrameError(
); `Definitely assigned fields cannot be initialized here, but only in the constructor`,
} );
}
if (!process.env.BABEL_8_BREAKING) {
// keep the definitely assigned fields only when `allowDeclareFields` (equivalent of // keep the definitely assigned fields only when `allowDeclareFields` (equivalent of
// Typescript's `useDefineForClassFields`) is true // Typescript's `useDefineForClassFields`) is true
if (!allowDeclareFields && !node.decorators) { if (!allowDeclareFields && !node.decorators) {
path.remove(); path.remove();
} }
} else if ( }
} else if (!process.env.BABEL_8_BREAKING) {
if (
!allowDeclareFields && !allowDeclareFields &&
!node.value && !node.value &&
!node.decorators && !node.decorators &&
@ -98,405 +105,405 @@ export default declare(
) { ) {
path.remove(); path.remove();
} }
}
if (node.accessibility) node.accessibility = null; if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null; if (node.abstract) node.abstract = null;
if (node.readonly) node.readonly = null; if (node.readonly) node.readonly = null;
if (node.optional) node.optional = null; if (node.optional) node.optional = null;
if (node.typeAnnotation) node.typeAnnotation = null; if (node.typeAnnotation) node.typeAnnotation = null;
if (node.definite) node.definite = null; if (node.definite) node.definite = null;
if (node.declare) node.declare = null; if (node.declare) node.declare = null;
}, },
method({ node }) { method({ node }) {
if (node.accessibility) node.accessibility = null; if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null; if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null; if (node.optional) node.optional = null;
// Rest handled by Function visitor // Rest handled by Function visitor
}, },
constructor(path, classPath) { constructor(path, classPath) {
if (path.node.accessibility) path.node.accessibility = null; if (path.node.accessibility) path.node.accessibility = null;
// Collects parameter properties so that we can add an assignment // Collects parameter properties so that we can add an assignment
// for each of them in the constructor body // for each of them in the constructor body
// //
// We use a WeakSet to ensure an assignment for a parameter // We use a WeakSet to ensure an assignment for a parameter
// property is only added once. This is necessary for cases like // property is only added once. This is necessary for cases like
// using `transform-classes`, which causes this visitor to run // using `transform-classes`, which causes this visitor to run
// twice. // twice.
const parameterProperties = []; const parameterProperties = [];
for (const param of path.node.params) { for (const param of path.node.params) {
if ( if (
param.type === "TSParameterProperty" && param.type === "TSParameterProperty" &&
!PARSED_PARAMS.has(param.parameter) !PARSED_PARAMS.has(param.parameter)
) { ) {
PARSED_PARAMS.add(param.parameter); PARSED_PARAMS.add(param.parameter);
parameterProperties.push(param.parameter); parameterProperties.push(param.parameter);
}
} }
}
if (parameterProperties.length) { if (parameterProperties.length) {
const assigns = parameterProperties.map(p => { const assigns = parameterProperties.map(p => {
let id; let id;
if (t.isIdentifier(p)) { if (t.isIdentifier(p)) {
id = p; id = p;
} else if (t.isAssignmentPattern(p) && t.isIdentifier(p.left)) { } else if (t.isAssignmentPattern(p) && t.isIdentifier(p.left)) {
id = p.left; id = p.left;
} else { } else {
throw path.buildCodeFrameError( throw path.buildCodeFrameError(
"Parameter properties can not be destructuring patterns.", "Parameter properties can not be destructuring patterns.",
); );
} }
return template.statement.ast` return template.statement.ast`
this.${t.cloneNode(id)} = ${t.cloneNode(id)}`; this.${t.cloneNode(id)} = ${t.cloneNode(id)}`;
}); });
injectInitialization(classPath, path, assigns); injectInitialization(classPath, path, assigns);
}
},
};
return {
name: "transform-typescript",
inherits: syntaxTypeScript,
visitor: {
//"Pattern" alias doesn't include Identifier or RestElement.
Pattern: visitPattern,
Identifier: visitPattern,
RestElement: visitPattern,
Program(path, state) {
const { file } = state;
let fileJsxPragma = null;
let fileJsxPragmaFrag = null;
if (!GLOBAL_TYPES.has(path.node)) {
GLOBAL_TYPES.set(path.node, new Set());
} }
},
};
return { if (file.ast.comments) {
name: "transform-typescript", for (const comment of (file.ast.comments: Array<Object>)) {
inherits: syntaxTypeScript, const jsxMatches = JSX_PRAGMA_REGEX.exec(comment.value);
if (jsxMatches) {
visitor: { if (jsxMatches[1]) {
//"Pattern" alias doesn't include Identifier or RestElement. // isFragment
Pattern: visitPattern, fileJsxPragmaFrag = jsxMatches[2];
Identifier: visitPattern, } else {
RestElement: visitPattern, fileJsxPragma = jsxMatches[2];
Program(path, state) {
const { file } = state;
let fileJsxPragma = null;
let fileJsxPragmaFrag = null;
if (!GLOBAL_TYPES.has(path.node)) {
GLOBAL_TYPES.set(path.node, new Set());
}
if (file.ast.comments) {
for (const comment of (file.ast.comments: Array<Object>)) {
const jsxMatches = JSX_PRAGMA_REGEX.exec(comment.value);
if (jsxMatches) {
if (jsxMatches[1]) {
// isFragment
fileJsxPragmaFrag = jsxMatches[2];
} else {
fileJsxPragma = jsxMatches[2];
}
} }
} }
} }
}
let pragmaImportName = fileJsxPragma || jsxPragma; let pragmaImportName = fileJsxPragma || jsxPragma;
if (pragmaImportName) { if (pragmaImportName) {
[pragmaImportName] = pragmaImportName.split("."); [pragmaImportName] = pragmaImportName.split(".");
} }
let pragmaFragImportName = fileJsxPragmaFrag || jsxPragmaFrag; let pragmaFragImportName = fileJsxPragmaFrag || jsxPragmaFrag;
if (pragmaFragImportName) { if (pragmaFragImportName) {
[pragmaFragImportName] = pragmaFragImportName.split("."); [pragmaFragImportName] = pragmaFragImportName.split(".");
} }
// remove type imports
for (let stmt of path.get("body")) {
if (t.isImportDeclaration(stmt)) {
if (stmt.node.importKind === "type") {
stmt.remove();
continue;
}
// If onlyRemoveTypeImports is `true`, only remove type-only imports
// and exports introduced in TypeScript 3.8.
if (!onlyRemoveTypeImports) {
// 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,
pragmaImportName,
pragmaFragImportName,
})
) {
importsToRemove.push(binding.path);
} else {
allElided = false;
}
}
if (allElided) {
stmt.remove();
} else {
for (const importPath of importsToRemove) {
importPath.remove();
}
}
}
// remove type imports
for (let stmt of path.get("body")) {
if (t.isImportDeclaration(stmt)) {
if (stmt.node.importKind === "type") {
stmt.remove();
continue; continue;
} }
if (stmt.isExportDeclaration()) { // If onlyRemoveTypeImports is `true`, only remove type-only imports
stmt = stmt.get("declaration"); // and exports introduced in TypeScript 3.8.
} if (!onlyRemoveTypeImports) {
// Note: this will allow both `import { } from "m"` and `import "m";`.
if (stmt.isVariableDeclaration({ declare: true })) { // In TypeScript, the former would be elided.
for (const name of Object.keys(stmt.getBindingIdentifiers())) { if (stmt.node.specifiers.length === 0) {
registerGlobalType(path.scope, name); continue;
} }
} else if (
stmt.isTSTypeAliasDeclaration() ||
stmt.isTSDeclareFunction() ||
stmt.isTSInterfaceDeclaration() ||
stmt.isClassDeclaration({ declare: true }) ||
stmt.isTSEnumDeclaration({ declare: true }) ||
(stmt.isTSModuleDeclaration({ declare: true }) &&
stmt.get("id").isIdentifier())
) {
registerGlobalType(path.scope, stmt.node.id.name);
}
}
},
ExportNamedDeclaration(path) { let allElided = true;
if (path.node.exportKind === "type") { const importsToRemove: Path<Node>[] = [];
path.remove();
return;
}
// remove export declaration if it's exporting only types for (const specifier of stmt.node.specifiers) {
// This logic is needed when exportKind is "value", because const binding = stmt.scope.getBinding(specifier.local.name);
// currently the "type" keyword is optional.
// TODO:
// Also, currently @babel/parser sets exportKind to "value" for
// export interface A {}
// etc.
if (
!path.node.source &&
path.node.specifiers.length > 0 &&
path.node.specifiers.every(({ local }) =>
isGlobalType(path, local.name),
)
) {
path.remove();
}
},
ExportSpecifier(path) { // The binding may not exist if the import node was explicitly
// remove type exports // injected by another plugin. Currently core does not do a good job
if (!path.parent.source && isGlobalType(path, path.node.local.name)) { // of keeping scope bindings synchronized with the AST. For now we
path.remove(); // 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,
pragmaImportName,
pragmaFragImportName,
})
) {
importsToRemove.push(binding.path);
} else {
allElided = false;
}
}
ExportDefaultDeclaration(path) { if (allElided) {
// remove whole declaration if it's exporting a TS type stmt.remove();
if (
t.isIdentifier(path.node.declaration) &&
isGlobalType(path, 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;
},
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 => {
if (child.isClassMethod() || child.isClassPrivateMethod()) {
if (child.node.kind === "constructor") {
classMemberVisitors.constructor(child, path);
} else { } else {
classMemberVisitors.method(child, path); for (const importPath of importsToRemove) {
importPath.remove();
}
} }
} else if (
child.isClassProperty() ||
child.isClassPrivateProperty()
) {
classMemberVisitors.field(child, path);
} }
});
},
Function({ node }) { continue;
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 if (stmt.isExportDeclaration()) {
// rely on a `Function` visitor to deal with arguments, like stmt = stmt.get("declaration");
// `transform-parameters`, work properly. }
node.params = node.params.map(p => {
return p.type === "TSParameterProperty" ? p.parameter : p;
});
},
TSModuleDeclaration(path) { if (stmt.isVariableDeclaration({ declare: true })) {
transpileNamespace(path, t, allowNamespaces); for (const name of Object.keys(stmt.getBindingIdentifiers())) {
}, registerGlobalType(path.scope, name);
}
TSInterfaceDeclaration(path) { } else if (
path.remove(); stmt.isTSTypeAliasDeclaration() ||
}, stmt.isTSDeclareFunction() ||
stmt.isTSInterfaceDeclaration() ||
TSTypeAliasDeclaration(path) { stmt.isClassDeclaration({ declare: true }) ||
path.remove(); stmt.isTSEnumDeclaration({ declare: true }) ||
}, (stmt.isTSModuleDeclaration({ declare: true }) &&
stmt.get("id").isIdentifier())
TSEnumDeclaration(path) { ) {
transpileEnum(path, t); registerGlobalType(path.scope, stmt.node.id.name);
}, }
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;
},
OptionalCallExpression(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,
pragmaImportName,
pragmaFragImportName,
}) {
for (const path of binding.referencePaths) {
if (!isInType(path)) {
return false;
} }
} },
if ( ExportNamedDeclaration(path) {
binding.identifier.name !== pragmaImportName && if (path.node.exportKind === "type") {
binding.identifier.name !== pragmaFragImportName path.remove();
) { return;
return true; }
}
// "React" or the JSX pragma is referenced as a value if there are any JSX elements/fragments in the code. // remove export declaration if it's exporting only types
let sourceFileHasJsx = false; // This logic is needed when exportKind is "value", because
programPath.traverse({ // currently the "type" keyword is optional.
"JSXElement|JSXFragment"(path) { // TODO:
sourceFileHasJsx = true; // Also, currently @babel/parser sets exportKind to "value" for
path.stop(); // export interface A {}
}, // etc.
}); if (
return !sourceFileHasJsx; !path.node.source &&
path.node.specifiers.length > 0 &&
path.node.specifiers.every(({ local }) =>
isGlobalType(path, local.name),
)
) {
path.remove();
}
},
ExportSpecifier(path) {
// remove type exports
if (!path.parent.source && isGlobalType(path, path.node.local.name)) {
path.remove();
}
},
ExportDefaultDeclaration(path) {
// remove whole declaration if it's exporting a TS type
if (
t.isIdentifier(path.node.declaration) &&
isGlobalType(path, 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;
},
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 => {
if (child.isClassMethod() || child.isClassPrivateMethod()) {
if (child.node.kind === "constructor") {
classMemberVisitors.constructor(child, path);
} else {
classMemberVisitors.method(child, path);
}
} else if (
child.isClassProperty() ||
child.isClassPrivateProperty()
) {
classMemberVisitors.field(child, path);
}
});
},
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;
},
OptionalCallExpression(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,
pragmaImportName,
pragmaFragImportName,
}) {
for (const path of binding.referencePaths) {
if (!isInType(path)) {
return false;
}
} }
},
); if (
binding.identifier.name !== pragmaImportName &&
binding.identifier.name !== pragmaFragImportName
) {
return true;
}
// "React" or the JSX pragma is referenced as a value if there are any JSX elements/fragments in the code.
let sourceFileHasJsx = false;
programPath.traverse({
"JSXElement|JSXFragment"(path) {
sourceFileHasJsx = true;
path.stop();
},
});
return !sourceFileHasJsx;
}
});

View File

@ -0,0 +1,4 @@
class A {
declare x;
@foo declare y: string;
}

View File

@ -1,6 +1,7 @@
{ {
"BABEL_8_BREAKING": false,
"plugins": [ "plugins": [
["transform-typescript", { "allowDeclareFields": true }], ["transform-typescript", { "allowDeclareFields": true }],
"proposal-class-properties" ["syntax-decorators", { "legacy": true }]
] ]
} }

View File

@ -1,4 +1,5 @@
{ {
"BABEL_8_BREAKING": false,
"plugins": ["transform-typescript"], "plugins": ["transform-typescript"],
"throws": "The 'declare' modifier is only allowed when the 'allowDeclareFields' option of @babel/plugin-transform-typescript or @babel/preset-typescript is enabled." "throws": "The 'declare' modifier is only allowed when the 'allowDeclareFields' option of @babel/plugin-transform-typescript or @babel/preset-typescript is enabled."
} }

View File

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

View File

@ -1,6 +1,7 @@
{ {
"BABEL_8_BREAKING": true,
"plugins": [ "plugins": [
["transform-typescript", { "allowDeclareFields": true }], "transform-typescript",
["syntax-decorators", { "legacy": true }] ["syntax-decorators", { "legacy": true }]
] ]
} }

View File

@ -0,0 +1,4 @@
{
"BABEL_8_BREAKING": false,
"plugins": ["transform-typescript"]
}

View File

@ -0,0 +1,3 @@
class A {
x;
}

View File

@ -0,0 +1,4 @@
{
"BABEL_8_BREAKING": true,
"plugins": ["transform-typescript"]
}

View File

@ -0,0 +1,11 @@
class C {
public a?: number;
private b: number = 0;
readonly c: number = 1;
@foo d: number;
@foo e: number = 3;
f!: number;
@foo g!: number;
#h: string;
#i: number = 10;
}

View File

@ -0,0 +1,8 @@
{
"BABEL_8_BREAKING": false,
"plugins": [
"transform-typescript",
["syntax-decorators", { "legacy": true }],
"syntax-class-properties"
]
}

View File

@ -0,0 +1,12 @@
class C {
b = 0;
c = 1;
@foo
d;
@foo
e = 3;
@foo
g;
#h;
#i = 10;
}

View File

@ -1,4 +1,5 @@
{ {
"BABEL_8_BREAKING": true,
"plugins": [ "plugins": [
"transform-typescript", "transform-typescript",
["syntax-decorators", { "legacy": true }], ["syntax-decorators", { "legacy": true }],

View File

@ -1,10 +1,12 @@
class C { class C {
a;
b = 0; b = 0;
c = 1; c = 1;
@foo @foo
d; d;
@foo @foo
e = 3; e = 3;
f;
@foo @foo
g; g;
#h; #h;

View File

@ -1,7 +1,7 @@
{ {
"plugins": [ "plugins": [
"proposal-class-properties", "proposal-class-properties",
["transform-typescript", { "allowDeclareFields": true }] "transform-typescript"
], ],
"throws": "TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.\nIf you have already enabled that plugin (or '@babel/preset-typescript'), make sure that it runs before any plugin related to additional class features:\n - @babel/plugin-proposal-class-properties\n - @babel/plugin-proposal-private-methods\n - @babel/plugin-proposal-decorators" "throws": "TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.\nIf you have already enabled that plugin (or '@babel/preset-typescript'), make sure that it runs before any plugin related to additional class features:\n - @babel/plugin-proposal-class-properties\n - @babel/plugin-proposal-private-methods\n - @babel/plugin-proposal-decorators"
} }

View File

@ -1,3 +1,4 @@
{ {
"BABEL_8_BREAKING": false,
"plugins": [["transform-typescript", { "allowDeclareFields": true }]] "plugins": [["transform-typescript", { "allowDeclareFields": true }]]
} }

View File

@ -1,3 +1,4 @@
{ {
"BABEL_8_BREAKING": false,
"plugins": [["transform-typescript", { "allowDeclareFields": false }]] "plugins": [["transform-typescript", { "allowDeclareFields": false }]]
} }

View File

@ -0,0 +1,3 @@
class A {
x!;
}

View File

@ -0,0 +1,4 @@
{
"BABEL_8_BREAKING": true,
"plugins": ["transform-typescript"]
}

View File

@ -0,0 +1,3 @@
class A {
x;
}

View File

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

View File

@ -4,7 +4,9 @@ export let src;
let ns1; let ns1;
(function (_ns) { (function (_ns) {
class foo {} class foo {
F1 = "";
}
_ns.foo = foo; _ns.foo = foo;
})(ns1 || (ns1 = _src.ns1 || (_src.ns1 = {}))); })(ns1 || (ns1 = _src.ns1 || (_src.ns1 = {})));
@ -12,7 +14,9 @@ export let src;
let ns2; let ns2;
(function (_ns2) { (function (_ns2) {
class foo {} class foo {
F1 = "";
}
_ns2.foo = foo; _ns2.foo = foo;
})(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {}))); })(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {})));

View File

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

View File

@ -4,7 +4,9 @@ let src;
let ns1; let ns1;
(function (_ns) { (function (_ns) {
class foo {} class foo {
F1 = "";
}
_ns.foo = foo; _ns.foo = foo;
})(ns1 || (ns1 = _src.ns1 || (_src.ns1 = {}))); })(ns1 || (ns1 = _src.ns1 || (_src.ns1 = {})));
@ -12,7 +14,9 @@ let src;
let ns2; let ns2;
(function (_ns2) { (function (_ns2) {
class foo {} class foo {
F1 = "";
}
_ns2.foo = foo; _ns2.foo = foo;
})(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {}))); })(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {})));

View File

@ -7,7 +7,6 @@ export default declare((api, opts) => {
const { const {
allExtensions, allExtensions,
allowDeclareFields,
allowNamespaces, allowNamespaces,
isTSX, isTSX,
jsxPragma, jsxPragma,
@ -15,14 +14,22 @@ export default declare((api, opts) => {
onlyRemoveTypeImports, onlyRemoveTypeImports,
} = normalizeOptions(opts); } = normalizeOptions(opts);
const pluginOptions = isTSX => ({ const pluginOptions = process.env.BABEL_8_BREAKING
allowDeclareFields, ? isTSX => ({
allowNamespaces, allowNamespaces,
isTSX, isTSX,
jsxPragma, jsxPragma,
jsxPragmaFrag, jsxPragmaFrag,
onlyRemoveTypeImports, onlyRemoveTypeImports,
}); })
: isTSX => ({
allowDeclareFields: opts.allowDeclareFields,
allowNamespaces,
isTSX,
jsxPragma,
jsxPragmaFrag,
onlyRemoveTypeImports,
});
return { return {
overrides: allExtensions overrides: allExtensions

View File

@ -2,16 +2,10 @@ import { OptionValidator } from "@babel/helper-validator-option";
const v = new OptionValidator("@babel/preset-typescript"); const v = new OptionValidator("@babel/preset-typescript");
export default function normalizeOptions(options = {}) { export default function normalizeOptions(options = {}) {
let { let { allowNamespaces, jsxPragma, onlyRemoveTypeImports } = options;
allowDeclareFields,
allowNamespaces,
jsxPragma,
onlyRemoveTypeImports,
} = options;
if (process.env.BABEL_8_BREAKING) { if (process.env.BABEL_8_BREAKING) {
const TopLevelOptions = { const TopLevelOptions = {
allowDeclareFields: "allowDeclareFields",
allExtensions: "allExtensions", allExtensions: "allExtensions",
allowNamespaces: "allowNamespaces", allowNamespaces: "allowNamespaces",
isTSX: "isTSX", isTSX: "isTSX",
@ -20,11 +14,6 @@ export default function normalizeOptions(options = {}) {
onlyRemoveTypeImports: "onlyRemoveTypeImports", onlyRemoveTypeImports: "onlyRemoveTypeImports",
}; };
v.validateTopLevelOptions(options, TopLevelOptions); v.validateTopLevelOptions(options, TopLevelOptions);
allowDeclareFields = v.validateBooleanOption(
TopLevelOptions.allowDeclareFields,
options.allowDeclareFields,
true,
);
allowNamespaces = v.validateBooleanOption( allowNamespaces = v.validateBooleanOption(
TopLevelOptions.allowNamespaces, TopLevelOptions.allowNamespaces,
options.allowNamespaces, options.allowNamespaces,
@ -62,7 +51,6 @@ export default function normalizeOptions(options = {}) {
return { return {
allExtensions, allExtensions,
allowDeclareFields,
allowNamespaces, allowNamespaces,
isTSX, isTSX,
jsxPragma, jsxPragma,

View File

@ -7,7 +7,6 @@ describe("normalize options", () => {
); );
}); });
it.each([ it.each([
"allowDeclareFields",
"allExtensions", "allExtensions",
"allowNamespaces", "allowNamespaces",
"isTSX", "isTSX",
@ -32,7 +31,6 @@ describe("normalize options", () => {
expect(normalizeOptions({})).toMatchInlineSnapshot(` expect(normalizeOptions({})).toMatchInlineSnapshot(`
Object { Object {
"allExtensions": false, "allExtensions": false,
"allowDeclareFields": true,
"allowNamespaces": true, "allowNamespaces": true,
"isTSX": false, "isTSX": false,
"jsxPragma": "React", "jsxPragma": "React",
@ -80,7 +78,6 @@ describe("normalize options", () => {
expect(normalizeOptions({})).toMatchInlineSnapshot(` expect(normalizeOptions({})).toMatchInlineSnapshot(`
Object { Object {
"allExtensions": false, "allExtensions": false,
"allowDeclareFields": undefined,
"allowNamespaces": undefined, "allowNamespaces": undefined,
"isTSX": false, "isTSX": false,
"jsxPragma": undefined, "jsxPragma": undefined,

View File

@ -20,6 +20,11 @@ cd ../..
# TEST # # TEST #
#==============================================================================# #==============================================================================#
if [ "$BABEL_8_BREAKING" = true ] ; then
# This option is removed in Babel 8
sed -i 's/allowDeclareFields: true,\?/\/* allowDeclareFields: true *\//g' babel.config.js
fi
startLocalRegistry "$PWD"/scripts/integration-tests/verdaccio-config.yml startLocalRegistry "$PWD"/scripts/integration-tests/verdaccio-config.yml
# We only bump dependencies in the top-level package.json, because workspaces # We only bump dependencies in the top-level package.json, because workspaces
# already use the workspace: protocol so will get the version in the monorepo # already use the workspace: protocol so will get the version in the monorepo

View File

@ -37,11 +37,6 @@ python --version
# TEST # # TEST #
#==============================================================================# #==============================================================================#
startLocalRegistry "$root"/verdaccio-config.yml
yarn install
yarn dedupe '@babel/*'
yarn build
# Workaround for https://github.com/babel/babel/pull/12567 # Workaround for https://github.com/babel/babel/pull/12567
node -e ' node -e '
let snapshots = fs.readFileSync("packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap", "utf8"); let snapshots = fs.readFileSync("packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap", "utf8");
@ -49,6 +44,16 @@ node -e '
fs.writeFileSync("packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap", snapshots); fs.writeFileSync("packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap", snapshots);
' '
if [ "$BABEL_8_BREAKING" = true ] ; then
# This option is removed in Babel 8
sed -i 's/allowDeclareFields: true,\?/\/* allowDeclareFields: true *\//g' babel.config.js
fi
startLocalRegistry "$root"/verdaccio-config.yml
yarn install
yarn dedupe '@babel/*'
yarn build
# The full test suite takes about 20mins on CircleCI. We run only a few of them # The full test suite takes about 20mins on CircleCI. We run only a few of them
# to speed it up. # to speed it up.
# The goals of this e2e test are: # The goals of this e2e test are: