From b71636b1f10c031f0817bc99e6c11e2951d74e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 30 May 2022 17:50:03 -0400 Subject: [PATCH 01/17] external-helpers --- packages/babel-plugin-external-helpers/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-plugin-external-helpers/src/index.ts b/packages/babel-plugin-external-helpers/src/index.ts index 53da9423c4eb..c9f4b67f617f 100644 --- a/packages/babel-plugin-external-helpers/src/index.ts +++ b/packages/babel-plugin-external-helpers/src/index.ts @@ -25,7 +25,7 @@ export default declare((api, options: Options) => { return { name: "external-helpers", pre(file) { - file.set("helperGenerator", name => { + file.set("helperGenerator", (name: string) => { // If the helper didn't exist yet at the version given, we bail // out and let Babel either insert it directly, or throw an error // so that plugins can handle that case properly. From f5a47a960670d0ca3de8242ceaa8bc9217dbc063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 30 May 2022 18:06:06 -0400 Subject: [PATCH 02/17] bugfix --- .../src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/util.ts b/packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/util.ts index c813c79a7281..0c3f0863185c 100644 --- a/packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/util.ts +++ b/packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/util.ts @@ -6,7 +6,7 @@ import { types as t } from "@babel/core"; // check if there is a spread element followed by another argument. // (...[], 0) or (...[], ...[]) -function matchAffectedArguments(argumentNodes) { +function matchAffectedArguments(argumentNodes: t.CallExpression["arguments"]) { const spreadIndex = argumentNodes.findIndex(node => t.isSpreadElement(node)); return spreadIndex >= 0 && spreadIndex !== argumentNodes.length - 1; } From 3557925b0fe8d22a7e03755290e5b7d495a912f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 30 May 2022 13:23:34 -0400 Subject: [PATCH 03/17] transform-parameters --- .../src/params.ts | 55 ++++-- .../src/rest.ts | 158 +++++++++--------- 2 files changed, 116 insertions(+), 97 deletions(-) diff --git a/packages/babel-plugin-transform-parameters/src/params.ts b/packages/babel-plugin-transform-parameters/src/params.ts index d9bfd15af987..76adffdd0746 100644 --- a/packages/babel-plugin-transform-parameters/src/params.ts +++ b/packages/babel-plugin-transform-parameters/src/params.ts @@ -1,4 +1,5 @@ import { template, types as t } from "@babel/core"; +import type { NodePath, Scope, Visitor } from "@babel/traverse"; const buildDefaultParam = template(` let VARIABLE_NAME = @@ -22,8 +23,11 @@ const buildSafeArgumentsAccess = template(` let $0 = arguments.length > $1 ? arguments[$1] : undefined; `); -const iifeVisitor = { - "ReferencedIdentifier|BindingIdentifier"(path, state) { +const iifeVisitor: Visitor = { + "ReferencedIdentifier|BindingIdentifier"( + path: NodePath, + state, + ) { const { scope, node } = path; const { name } = node; @@ -38,15 +42,25 @@ const iifeVisitor = { }, // type annotations don't use or introduce "real" bindings "TypeAnnotation|TSTypeAnnotation|TypeParameterDeclaration|TSTypeParameterDeclaration": - path => path.skip(), + (path: NodePath) => path.skip(), +}; + +type State = { + stop: boolean; + needsOuterBinding: boolean; + scope: Scope; }; // last 2 parameters are optional -- they are used by proposal-object-rest-spread/src/index.js export default function convertFunctionParams( - path, - ignoreFunctionLength, - shouldTransformParam?, - replaceRestElement?, + path: NodePath, + ignoreFunctionLength: boolean | void, + shouldTransformParam?: (index: number) => boolean, + replaceRestElement?: ( + path: NodePath, + paramPath: NodePath, + transformedRestNodes: t.Statement[], + ) => void, ) { const params = path.get("params"); @@ -62,7 +76,7 @@ export default function convertFunctionParams( }; const body = []; - const shadowedParams = new Set(); + const shadowedParams = new Set(); for (const param of params) { for (const name of Object.keys(param.getBindingIdentifiers())) { @@ -117,15 +131,15 @@ export default function convertFunctionParams( if (shouldTransformParam && !shouldTransformParam(i)) { continue; } - const transformedRestNodes = []; + const transformedRestNodes: t.Statement[] = []; if (replaceRestElement) { - replaceRestElement(param.parentPath, param, transformedRestNodes); + replaceRestElement(path, param, transformedRestNodes); } const paramIsAssignmentPattern = param.isAssignmentPattern(); if ( paramIsAssignmentPattern && - (ignoreFunctionLength || node.kind === "set") + (ignoreFunctionLength || t.isMethod(node, { kind: "set" })) ) { const left = param.get("left"); const right = param.get("right"); @@ -198,14 +212,21 @@ export default function convertFunctionParams( path.ensureBlock(); if (state.needsOuterBinding || shadowedParams.size > 0) { - body.push(buildScopeIIFE(shadowedParams, path.get("body").node)); + body.push( + buildScopeIIFE( + shadowedParams, + (path.get("body") as NodePath).node, + ), + ); - path.set("body", t.blockStatement(body)); + path.set("body", t.blockStatement(body as t.Statement[])); // We inject an arrow and then transform it to a normal function, to be // sure that we correctly handle this and arguments. - const bodyPath = path.get("body.body"); - const arrowPath = bodyPath[bodyPath.length - 1].get("argument.callee"); + const bodyPath = path.get("body.body") as NodePath[]; + const arrowPath = bodyPath[bodyPath.length - 1].get( + "argument.callee", + ) as NodePath; // This is an IIFE, so we don't need to worry about the noNewArrows assumption arrowPath.arrowFunctionToExpression(); @@ -217,13 +238,13 @@ export default function convertFunctionParams( // throws, it must reject asynchronously. path.node.generator = false; } else { - path.get("body").unshiftContainer("body", body); + path.get("body").unshiftContainer("body", body as t.Statement[]); } return true; } -function buildScopeIIFE(shadowedParams, body) { +function buildScopeIIFE(shadowedParams: Set, body: t.BlockStatement) { const args = []; const params = []; diff --git a/packages/babel-plugin-transform-parameters/src/rest.ts b/packages/babel-plugin-transform-parameters/src/rest.ts index 3699cd823ba8..7087a936bac2 100644 --- a/packages/babel-plugin-transform-parameters/src/rest.ts +++ b/packages/babel-plugin-transform-parameters/src/rest.ts @@ -1,13 +1,47 @@ import { template, types as t } from "@babel/core"; import type { NodePath, Visitor } from "@babel/traverse"; +const buildRest = template.statement(` + for (var LEN = ARGUMENTS.length, + ARRAY = new Array(ARRAY_LEN), + KEY = START; + KEY < LEN; + KEY++) { + ARRAY[ARRAY_KEY] = ARGUMENTS[KEY]; + } +`); + +const restIndex = template.expression(` + (INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX] +`); + +const restIndexImpure = template.expression(` + REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF] +`); + +const restLength = template.expression(` + ARGUMENTS.length <= OFFSET ? 0 : ARGUMENTS.length - OFFSET +`); + +function referencesRest( + path: NodePath, + state: State, +) { + if (path.node.name === state.name) { + // Check rest parameter is not shadowed by a binding in another scope. + return path.scope.bindingIdentifierEquals(state.name, state.outerBinding); + } + + return false; +} + type Candidate = { - cause: "indexGetter" | "lengthGetter" | "argSpread"; + cause: "argSpread" | "indexGetter" | "lengthGetter"; path: NodePath; }; type State = { - references: []; + references: NodePath[]; offset: number; argumentsNode: t.Identifier; @@ -32,43 +66,9 @@ type State = { nested function. */ deopted: boolean; - noOptimise: boolean; + noOptimise?: boolean; }; -const buildRest = template(` - for (var LEN = ARGUMENTS.length, - ARRAY = new Array(ARRAY_LEN), - KEY = START; - KEY < LEN; - KEY++) { - ARRAY[ARRAY_KEY] = ARGUMENTS[KEY]; - } -`); - -const restIndex = template(` - (INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX] -`); - -const restIndexImpure = template(` - REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF] -`); - -const restLength = template(` - ARGUMENTS.length <= OFFSET ? 0 : ARGUMENTS.length - OFFSET -`); - -function referencesRest( - path: NodePath, - state: State, -) { - if (path.node.name === state.name) { - // Check rest parameter is not shadowed by a binding in another scope. - return path.scope.bindingIdentifierEquals(state.name, state.outerBinding); - } - - return false; -} - const memberExpressionOptimisationVisitor: Visitor = { Scope(path, state) { // check if this scope has a local binding that will shadow the rest parameter @@ -157,7 +157,10 @@ const memberExpressionOptimisationVisitor: Visitor = { state.candidates.push({ cause: "indexGetter", path }); return; } - } else if (parentPath.node.property.name === "length") { + } else if ( + // @ts-expect-error .length must not be a private name + parentPath.node.property.name === "length" + ) { // args.length state.candidates.push({ cause: "lengthGetter", path }); return; @@ -194,7 +197,7 @@ const memberExpressionOptimisationVisitor: Visitor = { }, }; -function getParamsCount(node) { +function getParamsCount(node: t.Function) { let count = node.params.length; // skip the first parameter if it is a TypeScript 'this parameter' if (count > 0 && t.isIdentifier(node.params[0], { name: "this" })) { @@ -203,33 +206,38 @@ function getParamsCount(node) { return count; } -function hasRest(node) { +function hasRest(node: t.Function) { const length = node.params.length; return length > 0 && t.isRestElement(node.params[length - 1]); } -function optimiseIndexGetter(path, argsId, offset) { +function optimiseIndexGetter( + path: NodePath, + argsId: t.Identifier, + offset: number, +) { const offsetLiteral = t.numericLiteral(offset); let index; + const parent = path.parent as t.MemberExpression; - if (t.isNumericLiteral(path.parent.property)) { - index = t.numericLiteral(path.parent.property.value + offset); + if (t.isNumericLiteral(parent.property)) { + index = t.numericLiteral(parent.property.value + offset); } else if (offset === 0) { // Avoid unnecessary '+ 0' - index = path.parent.property; + index = parent.property; } else { index = t.binaryExpression( "+", - path.parent.property, + parent.property, t.cloneNode(offsetLiteral), ); } - const { scope } = path; + const { scope, parentPath } = path; if (!scope.isPure(index)) { const temp = scope.generateUidIdentifierBasedOnNode(index); scope.push({ id: temp, kind: "var" }); - path.parentPath.replaceWith( + parentPath.replaceWith( restIndexImpure({ ARGUMENTS: argsId, OFFSET: offsetLiteral, @@ -238,7 +246,6 @@ function optimiseIndexGetter(path, argsId, offset) { }), ); } else { - const parentPath = path.parentPath; parentPath.replaceWith( restIndex({ ARGUMENTS: argsId, @@ -246,22 +253,29 @@ function optimiseIndexGetter(path, argsId, offset) { INDEX: index, }), ); + const replacedParentPath = parentPath as NodePath; // See if we can statically evaluate the first test (i.e. index < offset) // and optimize the AST accordingly. - const offsetTestPath = parentPath.get("test").get("left"); - const valRes = offsetTestPath.evaluate(); + const offsetTestPath = replacedParentPath.get( + "test", + ) as NodePath; + const valRes = offsetTestPath.get("left").evaluate(); if (valRes.confident) { if (valRes.value === true) { - parentPath.replaceWith(parentPath.scope.buildUndefinedNode()); + replacedParentPath.replaceWith(scope.buildUndefinedNode()); } else { - parentPath.get("test").replaceWith(parentPath.get("test").get("right")); + offsetTestPath.replaceWith(offsetTestPath.get("right")); } } } } -function optimiseLengthGetter(path, argsId, offset) { +function optimiseLengthGetter( + path: NodePath, + argsId: t.Identifier, + offset: number, +) { if (offset) { path.parentPath.replaceWith( restLength({ @@ -274,15 +288,13 @@ function optimiseLengthGetter(path, argsId, offset) { } } -export default function convertFunctionRest(path) { +export default function convertFunctionRest(path: NodePath) { const { node, scope } = path; if (!hasRest(node)) return false; - let rest = node.params.pop().argument; - - if (rest.name === "arguments") scope.rename(rest.name); - - const argsId = t.identifier("arguments"); + let rest = (node.params.pop() as t.RestElement).argument as + | t.Pattern + | t.Identifier; if (t.isPattern(rest)) { const pattern = rest; @@ -291,37 +303,23 @@ export default function convertFunctionRest(path) { const declar = t.variableDeclaration("let", [ t.variableDeclarator(pattern, rest), ]); - node.body.body.unshift(declar); + path.ensureBlock(); + (node.body as t.BlockStatement).body.unshift(declar); + } else if (rest.name === "arguments") { + scope.rename(rest.name); } + const argsId = t.identifier("arguments"); const paramsCount = getParamsCount(node); // check and optimise for extremely common cases - const state = { + const state: State = { references: [], offset: paramsCount, - argumentsNode: argsId, outerBinding: scope.getBindingIdentifier(rest.name), - - // candidate member expressions we could optimise if there are no other references candidates: [], - - // local rest binding name name: rest.name, - - /* - It may be possible to optimize the output code in certain ways, such as - not generating code to initialize an array (perhaps substituting direct - references to arguments[i] or arguments.length for reads of the - corresponding rest parameter property) or positioning the initialization - code so that it may not have to execute depending on runtime conditions. - - This property tracks eligibility for optimization. "deopted" means give up - and don't perform optimization. For example, when any of rest's elements / - properties is assigned to at the top level, or referenced at all in a - nested function. - */ deopted: false, }; @@ -385,7 +383,7 @@ export default function convertFunctionRest(path) { }); if (state.deopted) { - node.body.body.unshift(loop); + (node.body as t.BlockStatement).body.unshift(loop); } else { let target = path .getEarliestCommonAncestorFrom(state.references) From 3e8a35010ca460a62681bf7eb9ba99bd245fa737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 27 May 2022 16:27:19 -0400 Subject: [PATCH 04/17] object-rest-spread --- .../src/index.ts | 33 ++++++++++++------- .../src/shouldStoreRHSInTemporaryVariable.ts | 4 ++- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/babel-plugin-proposal-object-rest-spread/src/index.ts b/packages/babel-plugin-proposal-object-rest-spread/src/index.ts index d0acd5b93711..d34d35d56b6c 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/src/index.ts +++ b/packages/babel-plugin-proposal-object-rest-spread/src/index.ts @@ -20,6 +20,7 @@ if (!process.env.BABEL_8_BREAKING) { var ZERO_REFS = t.isReferenced(node, property, pattern) ? 1 : 0; } +type Param = NodePath; export interface Options { useBuiltIns?: boolean; loose?: boolean; @@ -52,7 +53,7 @@ export default declare((api, opts: Options) => { : file.addHelper("extends"); } - function hasRestElement(path) { + function hasRestElement(path: Param) { let foundRestElement = false; visitRestElements(path, restElement => { foundRestElement = true; @@ -242,7 +243,7 @@ export default declare((api, opts: Options) => { function replaceRestElement( parentPath: NodePath, - paramPath: NodePath, + paramPath: Param | NodePath, container?: t.VariableDeclaration[], ): void { if (paramPath.isAssignmentPattern()) { @@ -254,7 +255,12 @@ export default declare((api, opts: Options) => { const elements = paramPath.get("elements"); for (let i = 0; i < elements.length; i++) { - replaceRestElement(parentPath, elements[i], container); + replaceRestElement( + parentPath, + // @ts-expect-error Fixme: handle TSAsExpression + elements[i], + container, + ); } } @@ -283,7 +289,7 @@ export default declare((api, opts: Options) => { // function a({ b, ...c }) {} Function(path) { const params = path.get("params"); - const paramsWithRestElement = new Set(); + const paramsWithRestElement = new Set(); const idsInRestParams = new Set(); for (let i = 0; i < params.length; ++i) { const param = params[i]; @@ -300,7 +306,10 @@ export default declare((api, opts: Options) => { // example: f({...R}, a = R) let idInRest = false; - const IdentifierHandler = function (path, functionScope) { + const IdentifierHandler = function ( + path: NodePath, + functionScope: Scope, + ) { const name = path.node.name; if ( path.scope.getBinding(name) === functionScope.getBinding(name) && @@ -311,12 +320,12 @@ export default declare((api, opts: Options) => { } }; - let i; + let i: number; for (i = 0; i < params.length && !idInRest; ++i) { const param = params[i]; if (!paramsWithRestElement.has(i)) { if (param.isReferencedIdentifier() || param.isBindingIdentifier()) { - IdentifierHandler(path, path.scope); + IdentifierHandler(param, path.scope); } else { param.traverse( { @@ -337,7 +346,7 @@ export default declare((api, opts: Options) => { } } } else { - const shouldTransformParam = idx => + const shouldTransformParam = (idx: number) => idx >= i - 1 || paramsWithRestElement.has(idx); convertFunctionParams( path, @@ -584,7 +593,7 @@ export default declare((api, opts: Options) => { // [{a, ...b}] = c; ArrayPattern(path) { - const objectPatterns = []; + const objectPatterns: t.VariableDeclarator[] = []; visitRestElements(path, path => { if (!path.parentPath.isObjectPattern()) { @@ -620,7 +629,7 @@ export default declare((api, opts: Options) => { ObjectExpression(path, file) { if (!hasSpread(path.node)) return; - let helper; + let helper: t.Identifier | t.MemberExpression; if (setSpreadProperties) { helper = getExtendsHelper(file); } else { @@ -638,8 +647,8 @@ export default declare((api, opts: Options) => { } } - let exp = null; - let props = []; + let exp: t.CallExpression = null; + let props: t.ObjectMember[] = []; function make() { const hadProps = props.length > 0; diff --git a/packages/babel-plugin-proposal-object-rest-spread/src/shouldStoreRHSInTemporaryVariable.ts b/packages/babel-plugin-proposal-object-rest-spread/src/shouldStoreRHSInTemporaryVariable.ts index 27cbf3765df0..0e0a1231e2af 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/src/shouldStoreRHSInTemporaryVariable.ts +++ b/packages/babel-plugin-proposal-object-rest-spread/src/shouldStoreRHSInTemporaryVariable.ts @@ -15,7 +15,9 @@ const { * See https://github.com/babel/babel/pull/13711#issuecomment-914388382 for discussion * on further optimizations. */ -export default function shouldStoreRHSInTemporaryVariable(node: t.LVal) { +export default function shouldStoreRHSInTemporaryVariable( + node: t.LVal, +): boolean { if (isArrayPattern(node)) { const nonNullElements = node.elements.filter(element => element !== null); if (nonNullElements.length > 1) return true; From f020a338294c4341cf18b1d54a924a877624b0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 30 May 2022 16:20:02 -0400 Subject: [PATCH 05/17] destructuring-private --- .../src/index.ts | 2 +- .../src/util.ts | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/babel-plugin-proposal-destructuring-private/src/index.ts b/packages/babel-plugin-proposal-destructuring-private/src/index.ts index f210f5146e52..4270b44d0813 100644 --- a/packages/babel-plugin-proposal-destructuring-private/src/index.ts +++ b/packages/babel-plugin-proposal-destructuring-private/src/index.ts @@ -40,7 +40,7 @@ export default declare(function ({ assertVersion, assumption, types: t }) { ); if (firstPrivateIndex === -1) return; // wrap function body within IIFE if any param is shadowed - convertFunctionParams(path, ignoreFunctionLength, () => false, false); + convertFunctionParams(path, ignoreFunctionLength, () => false); // invariant: path.body is always a BlockStatement after `convertFunctionParams` const { node, scope } = path; const { params } = node; diff --git a/packages/babel-plugin-proposal-destructuring-private/src/util.ts b/packages/babel-plugin-proposal-destructuring-private/src/util.ts index 66ffbba18b53..bf755585f3f8 100644 --- a/packages/babel-plugin-proposal-destructuring-private/src/util.ts +++ b/packages/babel-plugin-proposal-destructuring-private/src/util.ts @@ -117,7 +117,7 @@ function buildAssignmentsFromPatternList( transformed: Transformed[]; } { const newElements: (t.Identifier | t.RestElement)[] = [], - transformed = []; + transformed: Transformed[] = []; for (let element of elements) { if (element === null) { newElements.push(null); @@ -142,7 +142,7 @@ function buildAssignmentsFromPatternList( }); } else { transformed.push({ - left: element, + left: element as Transformed["left"], right: cloneNode(tempId), }); } @@ -150,6 +150,12 @@ function buildAssignmentsFromPatternList( return { elements: newElements, transformed }; } +type StackItem = { + node: t.LVal | t.ObjectProperty | null; + index: number; + depth: number; +}; + /** * A DFS simplified pattern traverser. It skips computed property keys and assignment pattern * initializers. The following nodes will be delegated to the visitor: @@ -170,13 +176,9 @@ export function* traversePattern( depth: number, ) => Generator, ) { - const stack = []; + const stack: StackItem[] = []; stack.push({ node: root, index: 0, depth: 0 }); - let item: { - node: t.LVal | t.ObjectProperty | null; - index: number; - depth: number; - }; + let item: StackItem; while ((item = stack.pop()) !== undefined) { const { node, index } = item; if (node === null) continue; @@ -188,7 +190,7 @@ export function* traversePattern( break; case "ObjectProperty": // inherit the depth and index as an object property can not be an LHS without object pattern - stack.push({ node: node.value, index, depth: item.depth }); + stack.push({ node: node.value as t.LVal, index, depth: item.depth }); break; case "RestElement": stack.push({ node: node.argument, index: 0, depth }); @@ -250,7 +252,7 @@ export function hasPrivateClassElement(node: t.ClassBody): boolean { * @param {t.LVal} pattern */ export function* privateKeyPathIterator(pattern: t.LVal) { - const indexPath = []; + const indexPath: number[] = []; yield* traversePattern(pattern, function* (node, index, depth) { indexPath[depth] = index; if (isObjectProperty(node) && isPrivateName(node.key)) { From cf4e32ba04968946d05210d5afff0534ed68295e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 30 May 2022 16:39:40 -0400 Subject: [PATCH 06/17] transform-destructuring --- .../src/index.ts | 7 ++- .../src/util.ts | 60 ++++++++++--------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/packages/babel-plugin-transform-destructuring/src/index.ts b/packages/babel-plugin-transform-destructuring/src/index.ts index 5adeda9e870f..6c2bc0fb8c5a 100644 --- a/packages/babel-plugin-transform-destructuring/src/index.ts +++ b/packages/babel-plugin-transform-destructuring/src/index.ts @@ -5,6 +5,7 @@ import { convertVariableDeclaration, convertAssignmentExpression, unshiftForXStatementBody, + type DestructuringTransformerNode, } from "./util"; export { buildObjectExcludingKeys, unshiftForXStatementBody } from "./util"; @@ -112,7 +113,7 @@ export default declare((api, options: Options) => { t.variableDeclarator(key, null), ]); - const nodes = []; + const nodes: DestructuringTransformerNode[] = []; const destructuring = new DestructuringTransformer({ kind: left.kind, @@ -138,7 +139,7 @@ export default declare((api, options: Options) => { const ref = scope.generateUidIdentifier("ref"); node.param = ref; - const nodes = []; + const nodes: DestructuringTransformerNode[] = []; const destructuring = new DestructuringTransformer({ kind: "let", @@ -152,7 +153,7 @@ export default declare((api, options: Options) => { }); destructuring.init(pattern, ref); - node.body.body = nodes.concat(node.body.body); + node.body.body = [...nodes, ...node.body.body]; scope.crawl(); }, diff --git a/packages/babel-plugin-transform-destructuring/src/util.ts b/packages/babel-plugin-transform-destructuring/src/util.ts index 5f22b4a86ee5..ed5eba74b185 100644 --- a/packages/babel-plugin-transform-destructuring/src/util.ts +++ b/packages/babel-plugin-transform-destructuring/src/util.ts @@ -72,7 +72,7 @@ const arrayUnpackVisitor = ( } }; -type DestructuringTransformerNode = +export type DestructuringTransformerNode = | t.VariableDeclaration | t.ExpressionStatement | t.ReturnStatement; @@ -302,7 +302,7 @@ export class DestructuringTransformer { // Replace impure computed key expressions if we have a rest parameter if (hasObjectRest(pattern)) { - let copiedPattern; + let copiedPattern: t.ObjectPattern; for (let i = 0; i < pattern.properties.length; i++) { const prop = pattern.properties[i]; if (t.isRestElement(prop)) { @@ -322,7 +322,7 @@ export class DestructuringTransformer { }; } copiedPattern.properties[i] = { - ...copiedPattern.properties[i], + ...prop, key: name, }; } @@ -589,18 +589,19 @@ export function convertVariableDeclaration( const patternId = declar.init; const pattern = declar.id; - const destructuring = new DestructuringTransformer({ - // @ts-expect-error(todo): avoid internal properties access - blockHoist: node._blockHoist, - nodes: nodes, - scope: scope, - kind: node.kind, - iterableIsArray, - arrayLikeIsIterable, - useBuiltIns, - objectRestNoSymbols, - addHelper, - }); + const destructuring: DestructuringTransformer = + new DestructuringTransformer({ + // @ts-expect-error(todo): avoid internal properties access + blockHoist: node._blockHoist, + nodes: nodes, + scope: scope, + kind: node.kind, + iterableIsArray, + arrayLikeIsIterable, + useBuiltIns, + objectRestNoSymbols, + addHelper, + }); if (t.isPattern(pattern)) { destructuring.init(pattern, patternId); @@ -623,19 +624,24 @@ export function convertVariableDeclaration( let tail: t.VariableDeclaration | null = null; const nodesOut = []; for (const node of nodes) { - if (tail !== null && t.isVariableDeclaration(node)) { - // Create a single compound declarations - tail.declarations.push(...node.declarations); - } else { - // Make sure the original node kind is used for each compound declaration - node.kind = nodeKind; - // Propagate the original declaration node's location - if (!node.loc) { - node.loc = nodeLoc; + if (t.isVariableDeclaration(node)) { + if (tail !== null) { + // Create a single compound declarations + tail.declarations.push(...node.declarations); + continue; + } else { + // Make sure the original node kind is used for each compound declaration + node.kind = nodeKind; + tail = node; } - nodesOut.push(node); - tail = t.isVariableDeclaration(node) ? node : null; + } else { + tail = null; + } + // Propagate the original declaration node's location + if (!node.loc) { + node.loc = nodeLoc; } + nodesOut.push(node); } if (nodesOut.length === 1) { @@ -656,7 +662,7 @@ export function convertAssignmentExpression( ) { const { node, scope, parentPath } = path; - const nodes = []; + const nodes: DestructuringTransformerNode[] = []; const destructuring = new DestructuringTransformer({ operator: node.operator, From bc2c2a3bd137b3a1d1eb7a566dda7e02e1eaac88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 30 May 2022 17:44:36 -0400 Subject: [PATCH 07/17] proposal-decorators --- .../package.json | 2 + .../src/index.ts | 8 +- .../src/transformer-2021-12.ts | 10 +- .../src/transformer-legacy.ts | 129 ++++++++++++++---- yarn.lock | 8 ++ 5 files changed, 123 insertions(+), 34 deletions(-) diff --git a/packages/babel-plugin-proposal-decorators/package.json b/packages/babel-plugin-proposal-decorators/package.json index 7cdef3cd7555..f6c6b04e70db 100644 --- a/packages/babel-plugin-proposal-decorators/package.json +++ b/packages/babel-plugin-proposal-decorators/package.json @@ -34,7 +34,9 @@ "@babel/core": "workspace:^", "@babel/helper-plugin-test-runner": "workspace:^", "@babel/traverse": "workspace:^", + "@types/charcodes": "^0.2.0", "babel-plugin-polyfill-es-shims": "^0.6.0", + "charcodes": "^0.2.0", "object.getownpropertydescriptors": "^2.1.1" }, "engines": { diff --git a/packages/babel-plugin-proposal-decorators/src/index.ts b/packages/babel-plugin-proposal-decorators/src/index.ts index 6030513abdde..929fd5e824a2 100644 --- a/packages/babel-plugin-proposal-decorators/src/index.ts +++ b/packages/babel-plugin-proposal-decorators/src/index.ts @@ -8,7 +8,13 @@ import { } from "@babel/helper-create-class-features-plugin"; import legacyVisitor from "./transformer-legacy"; import transformer2021_12 from "./transformer-2021-12"; -import type { Options } from "@babel/plugin-syntax-decorators"; +import type { Options as SyntaxOptions } from "@babel/plugin-syntax-decorators"; + +interface Options extends SyntaxOptions { + /** @depreated use `constantSuper` assumption instead. Only supported in 2021-12 version. */ + loose?: boolean; +} + export type { Options }; export default declare((api, options: Options) => { diff --git a/packages/babel-plugin-proposal-decorators/src/transformer-2021-12.ts b/packages/babel-plugin-proposal-decorators/src/transformer-2021-12.ts index 7072a6baa289..a136ddd2139a 100644 --- a/packages/babel-plugin-proposal-decorators/src/transformer-2021-12.ts +++ b/packages/babel-plugin-proposal-decorators/src/transformer-2021-12.ts @@ -5,7 +5,7 @@ import ReplaceSupers from "@babel/helper-replace-supers"; import splitExportDeclaration from "@babel/helper-split-export-declaration"; import * as charCodes from "charcodes"; import type { PluginAPI, PluginObject, PluginPass } from "@babel/core"; -import type { Options } from ".."; +import type { Options } from "./index"; type ClassDecoratableElement = | t.ClassMethod @@ -52,7 +52,7 @@ function incrementId(id: number[], idx = id.length - 1): void { function createPrivateUidGeneratorForClass( classPath: NodePath, ): () => t.PrivateName { - const currentPrivateId = []; + const currentPrivateId: number[] = []; const privateNames = new Set(); classPath.traverse({ @@ -888,7 +888,11 @@ function transformClass( if (classDecorators) { locals.push(classLocal, classInitLocal); - const statics = []; + const statics: ( + | t.ClassProperty + | t.ClassPrivateProperty + | t.ClassPrivateMethod + )[] = []; let staticBlocks: t.StaticBlock[] = []; path.get("body.body").forEach(element => { // Static blocks cannot be compiled to "instance blocks", but we can inline diff --git a/packages/babel-plugin-proposal-decorators/src/transformer-legacy.ts b/packages/babel-plugin-proposal-decorators/src/transformer-legacy.ts index ca16608bbb4a..586793653290 100644 --- a/packages/babel-plugin-proposal-decorators/src/transformer-legacy.ts +++ b/packages/babel-plugin-proposal-decorators/src/transformer-legacy.ts @@ -1,19 +1,26 @@ // Fork of https://github.com/loganfsmyth/babel-plugin-proposal-decorators-legacy -import { template, types as t } from "@babel/core"; -import type { Visitor } from "@babel/traverse"; +import { template, types as t, type PluginPass } from "@babel/core"; +import type { NodePath, Visitor } from "@babel/traverse"; -const buildClassDecorator = template(` +const buildClassDecorator = template.statement(` DECORATOR(CLASS_REF = INNER) || CLASS_REF; -`) as (replacements: { DECORATOR; CLASS_REF; INNER }) => t.ExpressionStatement; +`) as (replacements: { + DECORATOR: t.Expression; + CLASS_REF: t.Identifier; + INNER: t.Expression; +}) => t.ExpressionStatement; const buildClassPrototype = template(` CLASS_REF.prototype; -`) as (replacements: { CLASS_REF }) => t.ExpressionStatement; +`) as (replacements: { CLASS_REF: t.Identifier }) => t.ExpressionStatement; const buildGetDescriptor = template(` Object.getOwnPropertyDescriptor(TARGET, PROPERTY); -`) as (replacements: { TARGET; PROPERTY }) => t.ExpressionStatement; +`) as (replacements: { + TARGET: t.Expression; + PROPERTY: t.Literal; +}) => t.ExpressionStatement; const buildGetObjectInitializer = template(` (TEMP = Object.getOwnPropertyDescriptor(TARGET, PROPERTY), (TEMP = TEMP ? TEMP.value : undefined), { @@ -24,21 +31,45 @@ const buildGetObjectInitializer = template(` return TEMP; } }) -`) as (replacements: { TEMP; TARGET; PROPERTY }) => t.ExpressionStatement; +`) as (replacements: { + TEMP: t.Identifier; + TARGET: t.Expression; + PROPERTY: t.Literal; +}) => t.ExpressionStatement; const WARNING_CALLS = new WeakSet(); +// legacy decorator does not support ClassAccessorProperty +type ClassDecoratableElement = + | t.ClassMethod + | t.ClassPrivateMethod + | t.ClassProperty + | t.ClassPrivateProperty; + /** * If the decorator expressions are non-identifiers, hoist them to before the class so we can be sure * that they are evaluated in order. */ -function applyEnsureOrdering(path) { +function applyEnsureOrdering( + path: NodePath, +) { // TODO: This should probably also hoist computed properties. - const decorators = ( + const decorators: t.Decorator[] = ( path.isClass() - ? [path].concat(path.get("body.body")) + ? [ + path, + ...(path.get("body.body") as NodePath[]), + ] : path.get("properties") - ).reduce((acc, prop) => acc.concat(prop.node.decorators || []), []); + ).reduce( + ( + acc: t.Decorator[], + prop: NodePath< + t.ObjectMember | t.ClassExpression | ClassDecoratableElement + >, + ) => acc.concat(prop.node.decorators || []), + [], + ); const identDecorators = decorators.filter( decorator => !t.isIdentifier(decorator.expression), @@ -47,7 +78,7 @@ function applyEnsureOrdering(path) { return t.sequenceExpression( identDecorators - .map(decorator => { + .map((decorator): t.Expression => { const expression = decorator.expression; const id = (decorator.expression = path.scope.generateDeclaredUidIdentifier("dec")); @@ -61,7 +92,7 @@ function applyEnsureOrdering(path) { * Given a class expression with class-level decorators, create a new expression * with the proper decorated behavior. */ -function applyClassDecorators(classPath) { +function applyClassDecorators(classPath: NodePath) { if (!hasClassDecorators(classPath.node)) return; const decorators = classPath.node.decorators || []; @@ -81,7 +112,7 @@ function applyClassDecorators(classPath) { }, classPath.node); } -function hasClassDecorators(classNode) { +function hasClassDecorators(classNode: t.Class) { return !!(classNode.decorators && classNode.decorators.length); } @@ -89,52 +120,88 @@ function hasClassDecorators(classNode) { * Given a class expression with method-level decorators, create a new expression * with the proper decorated behavior. */ -function applyMethodDecorators(path, state) { +function applyMethodDecorators( + path: NodePath, + state: PluginPass, +) { if (!hasMethodDecorators(path.node.body.body)) return; - return applyTargetDecorators(path, state, path.node.body.body); + return applyTargetDecorators( + path, + state, + // @ts-expect-error ClassAccessorProperty is not supported in legacy decorator + path.node.body.body, + ); } -function hasMethodDecorators(body) { - return body.some(node => node.decorators?.length); +function hasMethodDecorators( + body: t.ClassBody["body"] | t.ObjectExpression["properties"], +) { + return body.some( + node => + // @ts-expect-error decorators not in SpreadElement/StaticBlock + node.decorators?.length, + ); } /** * Given an object expression with property decorators, create a new expression * with the proper decorated behavior. */ -function applyObjectDecorators(path, state) { +function applyObjectDecorators( + path: NodePath, + state: PluginPass, +) { if (!hasMethodDecorators(path.node.properties)) return; - return applyTargetDecorators(path, state, path.node.properties); + return applyTargetDecorators( + path, + state, + path.node.properties.filter( + (prop): prop is t.ObjectMember => prop.type !== "SpreadElement", + ), + ); } /** * A helper to pull out property decorators into a sequence expression. */ -function applyTargetDecorators(path, state, decoratedProps) { +function applyTargetDecorators( + path: NodePath, + state: PluginPass, + decoratedProps: (t.ObjectMember | ClassDecoratableElement)[], +) { const name = path.scope.generateDeclaredUidIdentifier( path.isClass() ? "class" : "obj", ); const exprs = decoratedProps.reduce(function (acc, node) { - const decorators = node.decorators || []; - node.decorators = null; + let decorators: t.Decorator[] = []; + if (node.decorators != null) { + decorators = node.decorators; + node.decorators = null; + } if (decorators.length === 0) return acc; - if (node.computed) { + if ( + // @ts-expect-error computed is not in ClassPrivateProperty + node.computed + ) { throw path.buildCodeFrameError( "Computed method/property decorators are not yet supported.", ); } - const property = t.isLiteral(node.key) + const property: t.Literal = t.isLiteral(node.key) ? node.key - : t.stringLiteral(node.key.name); + : t.stringLiteral( + // @ts-expect-error: should we handle ClassPrivateProperty? + node.key.name, + ); const target = - path.isClass() && !node.static + path.isClass() && !(node as ClassDecoratableElement).static ? buildClassPrototype({ CLASS_REF: name, }).expression @@ -217,7 +284,7 @@ function applyTargetDecorators(path, state, decoratedProps) { ]); } -function decoratedClassToExpression({ node, scope }) { +function decoratedClassToExpression({ node, scope }: NodePath) { if (!hasClassDecorators(node) && !hasMethodDecorators(node.body.body)) { return; } @@ -231,7 +298,7 @@ function decoratedClassToExpression({ node, scope }) { ]); } -export default { +const visitor: Visitor = { ExportDefaultDeclaration(path) { const decl = path.get("declaration"); if (!decl.isClassDeclaration()) return; @@ -320,4 +387,6 @@ export default { ]), ); }, -} as Visitor; +}; + +export default visitor; diff --git a/yarn.lock b/yarn.lock index 3c8958ee30c7..d6165af4c318 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1298,6 +1298,7 @@ __metadata: "@babel/helper-split-export-declaration": "workspace:^" "@babel/plugin-syntax-decorators": "workspace:^" "@babel/traverse": "workspace:^" + "@types/charcodes": ^0.2.0 babel-plugin-polyfill-es-shims: ^0.6.0 charcodes: ^0.2.0 object.getownpropertydescriptors: ^2.1.1 @@ -4370,6 +4371,13 @@ __metadata: languageName: node linkType: hard +"@types/charcodes@npm:^0.2.0": + version: 0.2.0 + resolution: "@types/charcodes@npm:0.2.0" + checksum: 3403a591f7dba4164a2a1a48342855a0eee0e22f4f94903c39331339c4e96006ba4f9d5b81072bcd8f61f15d774891b6bb9f3cbe1fb202d558d9ae90f35baa49 + languageName: node + linkType: hard + "@types/color-name@npm:^1.1.1": version: 1.1.1 resolution: "@types/color-name@npm:1.1.1" From 335905b995f020caf12bfa6a3beb6c57e676ece0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Tue, 31 May 2022 10:52:16 -0400 Subject: [PATCH 08/17] optional-chaining --- .../src/transform.ts | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/babel-plugin-proposal-optional-chaining/src/transform.ts b/packages/babel-plugin-proposal-optional-chaining/src/transform.ts index ed916d5bfcf3..313d5206cb12 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/transform.ts +++ b/packages/babel-plugin-proposal-optional-chaining/src/transform.ts @@ -8,7 +8,9 @@ import { willPathCastToBoolean, findOutermostTransparentParent } from "./util"; const { ast } = template.expression; -function isSimpleMemberExpression(expression) { +function isSimpleMemberExpression( + expression: t.Expression, +): expression is t.Identifier | t.Super | t.MemberExpression { expression = skipTransparentExprWrapperNodes(expression); return ( t.isIdentifier(expression) || @@ -24,18 +26,21 @@ function isSimpleMemberExpression(expression) { * @param {NodePath} path * @returns {boolean} */ -function needsMemoize(path) { - let optionalPath = path; +function needsMemoize( + path: NodePath, +) { + let optionalPath: NodePath = path; const { scope } = path; while ( optionalPath.isOptionalMemberExpression() || optionalPath.isOptionalCallExpression() ) { const { node } = optionalPath; - const childKey = optionalPath.isOptionalMemberExpression() - ? "object" - : "callee"; - const childPath = skipTransparentExprWrappers(optionalPath.get(childKey)); + const childPath = skipTransparentExprWrappers( + optionalPath.isOptionalMemberExpression() + ? optionalPath.get("object") + : optionalPath.get("callee"), + ); if (node.optional) { return !scope.isStatic(childPath.node); } @@ -102,12 +107,16 @@ export function transform( isDeleteOperation = true; } for (let i = optionals.length - 1; i >= 0; i--) { - const node = optionals[i]; + const node = optionals[i] as unknown as + | t.MemberExpression + | t.CallExpression; const isCall = t.isCallExpression(node); - const replaceKey = isCall ? "callee" : "object"; - const chainWithTypes = node[replaceKey]; + const chainWithTypes = isCall + ? // V8 intrinsics must not be an optional call + (node.callee as t.Expression) + : node.object; const chain = skipTransparentExprWrapperNodes(chainWithTypes); let ref; @@ -115,12 +124,12 @@ export function transform( if (isCall && t.isIdentifier(chain, { name: "eval" })) { check = ref = chain; // `eval?.()` is an indirect eval call transformed to `(0,eval)()` - node[replaceKey] = t.sequenceExpression([t.numericLiteral(0), ref]); + node.callee = t.sequenceExpression([t.numericLiteral(0), ref]); } else if (pureGetters && isCall && isSimpleMemberExpression(chain)) { // If we assume getters are pure (avoiding a Function#call) and we are at the call, // we can avoid a needless memoize. We only do this if the callee is a simple member // expression, to avoid multiple calls to nested call expressions. - check = ref = chainWithTypes; + check = ref = node.callee; } else { ref = scope.maybeGenerateMemoised(chain); if (ref) { @@ -133,7 +142,7 @@ export function transform( chainWithTypes, ); - node[replaceKey] = ref; + isCall ? (node.callee = ref) : (node.object = ref); } else { check = ref = chainWithTypes; } From 5c0fe59d8b5278ac3287a9f90d74c0f4a8536617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Tue, 31 May 2022 10:55:28 -0400 Subject: [PATCH 09/17] helper-wrap-function --- packages/babel-helper-wrap-function/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/babel-helper-wrap-function/src/index.ts b/packages/babel-helper-wrap-function/src/index.ts index d38f4dfca4a9..b02014eea6bb 100644 --- a/packages/babel-helper-wrap-function/src/index.ts +++ b/packages/babel-helper-wrap-function/src/index.ts @@ -30,7 +30,7 @@ const buildNamedExpressionWrapper = template.expression(` })() `); -const buildDeclarationWrapper = template(` +const buildDeclarationWrapper = template.statements(` function NAME(PARAMS) { return REF.apply(this, arguments); } function REF() { REF = FUNCTION; @@ -109,8 +109,8 @@ function plainFunction( }); if (isDeclaration) { - path.replaceWith(container[0]); - path.insertAfter(container[1]); + path.replaceWith((container as t.Statement[])[0]); + path.insertAfter((container as t.Statement[])[1]); } else { // @ts-expect-error todo(flow->ts) separate `wrapper` for `isDeclaration` and `else` branches const retFunction = container.callee.body.body[1].argument; From ba2c2c92230b6a0c61ed42ec328981adb04e151a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 1 Jun 2022 07:14:42 -0400 Subject: [PATCH 10/17] explode-assignable-expression --- .../babel-helper-explode-assignable-expression/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-helper-explode-assignable-expression/src/index.ts b/packages/babel-helper-explode-assignable-expression/src/index.ts index deaebcc99b60..01762222cdaa 100644 --- a/packages/babel-helper-explode-assignable-expression/src/index.ts +++ b/packages/babel-helper-explode-assignable-expression/src/index.ts @@ -78,7 +78,7 @@ export default function ( scope: Scope, allowedSingleIdent?: boolean, ): { - uid: t.Identifier | t.MemberExpression; + uid: t.Identifier | t.MemberExpression | t.Super; ref: t.Identifier | t.MemberExpression; } { let obj; From 8ec9cb94339c9fc324cb7b8231769b73e926131e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 1 Jun 2022 13:19:30 -0400 Subject: [PATCH 11/17] helper-compilation-targets --- .../src/debug.ts | 2 +- .../src/index.ts | 107 ++++++++++-------- .../src/pretty.ts | 8 +- .../src/targets.ts | 11 +- .../src/types.ts | 8 +- .../src/utils.ts | 25 ++-- 6 files changed, 96 insertions(+), 65 deletions(-) diff --git a/packages/babel-helper-compilation-targets/src/debug.ts b/packages/babel-helper-compilation-targets/src/debug.ts index aef9fc12a249..d1ee514cd980 100644 --- a/packages/babel-helper-compilation-targets/src/debug.ts +++ b/packages/babel-helper-compilation-targets/src/debug.ts @@ -34,5 +34,5 @@ export function getInclusionReasons( } return result; - }, {}); + }, {} as Partial>); } diff --git a/packages/babel-helper-compilation-targets/src/index.ts b/packages/babel-helper-compilation-targets/src/index.ts index 31b9c40d0e27..b1bfbd17fe2f 100644 --- a/packages/babel-helper-compilation-targets/src/index.ts +++ b/packages/babel-helper-compilation-targets/src/index.ts @@ -12,7 +12,14 @@ import { import { OptionValidator } from "@babel/helper-validator-option"; import { browserNameMap } from "./targets"; import { TargetNames } from "./options"; -import type { Targets, InputTargets, Browsers, TargetsTuple } from "./types"; +import type { + Target, + Targets, + InputTargets, + Browsers, + BrowserslistBrowserName, + TargetsTuple, +} from "./types"; export type { Targets, InputTargets }; @@ -58,50 +65,49 @@ function validateBrowsers(browsers: Browsers | undefined) { } function getLowestVersions(browsers: Array): Targets { - return browsers.reduce((all: any, browser: string): any => { - const [browserName, browserVersion] = browser.split(" "); - const normalizedBrowserName = browserNameMap[browserName]; - - if (!normalizedBrowserName) { + return browsers.reduce((all, browser) => { + const [browserName, browserVersion] = browser.split(" ") as [ + BrowserslistBrowserName, + string, + ]; + const target: Target = browserNameMap[browserName]; + + if (!target) { return all; } try { // Browser version can return as "10.0-10.2" const splitVersion = browserVersion.split("-")[0].toLowerCase(); - const isSplitUnreleased = isUnreleasedVersion(splitVersion, browserName); + const isSplitUnreleased = isUnreleasedVersion(splitVersion, target); - if (!all[normalizedBrowserName]) { - all[normalizedBrowserName] = isSplitUnreleased + if (!all[target]) { + all[target] = isSplitUnreleased ? splitVersion : semverify(splitVersion); return all; } - const version = all[normalizedBrowserName]; - const isUnreleased = isUnreleasedVersion(version, browserName); + const version = all[target]; + const isUnreleased = isUnreleasedVersion(version, target); if (isUnreleased && isSplitUnreleased) { - all[normalizedBrowserName] = getLowestUnreleased( - version, - splitVersion, - browserName, - ); + all[target] = getLowestUnreleased(version, splitVersion, target); } else if (isUnreleased) { - all[normalizedBrowserName] = semverify(splitVersion); + all[target] = semverify(splitVersion); } else if (!isUnreleased && !isSplitUnreleased) { const parsedBrowserVersion = semverify(splitVersion); - all[normalizedBrowserName] = semverMin(version, parsedBrowserVersion); + all[target] = semverMin(version, parsedBrowserVersion); } } catch (e) {} return all; - }, {}); + }, {} as Record); } function outputDecimalWarning( - decimalTargets: Array<{ target: string; value: string }>, + decimalTargets: Array<{ target: string; value: number }>, ): void { if (!decimalTargets.length) { return; @@ -117,7 +123,7 @@ getting parsed as 6.1, which can lead to unexpected behavior. `); } -function semverifyTarget(target, value) { +function semverifyTarget(target: keyof Targets, value: string) { try { return semverify(value); } catch (error) { @@ -129,23 +135,24 @@ function semverifyTarget(target, value) { } } -const targetParserMap = { - __default(target, value) { - const version = isUnreleasedVersion(value, target) - ? value.toLowerCase() - : semverifyTarget(target, value); - return [target, version]; - }, - - // Parse `node: true` and `node: "current"` to version - node(target, value) { - const parsed = - value === true || value === "current" - ? process.versions.node - : semverifyTarget(target, value); - return [target, parsed]; - }, -}; +// Parse `node: true` and `node: "current"` to version +function nodeTargetParser(value: true | string) { + const parsed = + value === true || value === "current" + ? process.versions.node + : semverifyTarget("node", value); + return ["node" as const, parsed] as const; +} + +function defaultTargetParser( + target: Exclude, + value: string, +): readonly [Exclude, string] { + const version = isUnreleasedVersion(value, target) + ? value.toLowerCase() + : semverifyTarget(target, value); + return [target, version] as const; +} function generateTargets(inputTargets: InputTargets): Targets { const input = { ...inputTargets }; @@ -214,7 +221,10 @@ export default function getTargets( // These values OVERRIDE the `browsers` field. if (esmodules && (esmodules !== "intersect" || !browsers?.length)) { browsers = Object.keys(ESM_SUPPORT) - .map(browser => `${browser} >= ${ESM_SUPPORT[browser]}`) + .map( + (browser: keyof typeof ESM_SUPPORT) => + `${browser} >= ${ESM_SUPPORT[browser]}`, + ) .join(", "); esmodules = false; } @@ -226,13 +236,16 @@ export default function getTargets( const queryBrowsers = resolveTargets(browsers, options.browserslistEnv); if (esmodules === "intersect") { - for (const browser of Object.keys(queryBrowsers)) { + for (const browser of Object.keys(queryBrowsers) as Target[]) { const version = queryBrowsers[browser]; + const esmSupportVersion = + // @ts-ignore ie is not in ESM_SUPPORT + ESM_SUPPORT[browser]; - if (ESM_SUPPORT[browser]) { + if (esmSupportVersion) { queryBrowsers[browser] = getHighestUnreleased( version, - semverify(ESM_SUPPORT[browser]), + semverify(esmSupportVersion), browser, ); } else { @@ -247,7 +260,7 @@ export default function getTargets( // Parse remaining targets const result: Targets = {} as Targets; const decimalWarnings = []; - for (const target of Object.keys(targets).sort()) { + for (const target of Object.keys(targets).sort() as Target[]) { const value = targets[target]; // Warn when specifying minor/patch as a decimal @@ -255,10 +268,10 @@ export default function getTargets( decimalWarnings.push({ target, value }); } - // Check if we have a target parser? - // $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing - const parser = targetParserMap[target] ?? targetParserMap.__default; - const [parsedTarget, parsedValue] = parser(target, value); + const [parsedTarget, parsedValue] = + target === "node" + ? nodeTargetParser(value) + : defaultTargetParser(target, value as string); if (parsedValue) { // Merge (lowest wins) diff --git a/packages/babel-helper-compilation-targets/src/pretty.ts b/packages/babel-helper-compilation-targets/src/pretty.ts index 10afed440b75..34f5257b15ae 100644 --- a/packages/babel-helper-compilation-targets/src/pretty.ts +++ b/packages/babel-helper-compilation-targets/src/pretty.ts @@ -1,6 +1,6 @@ import semver from "semver"; import { unreleasedLabels } from "./targets"; -import type { Targets } from "./types"; +import type { Targets, Target } from "./types"; export function prettifyVersion(version: string) { if (typeof version !== "string") { @@ -23,10 +23,12 @@ export function prettifyVersion(version: string) { } export function prettifyTargets(targets: Targets): Targets { - return Object.keys(targets).reduce((results, target) => { + return Object.keys(targets).reduce((results, target: Target) => { let value = targets[target]; - const unreleasedLabel = unreleasedLabels[target]; + const unreleasedLabel = + // @ts-expect-error undefined is strictly compared with string later + unreleasedLabels[target]; if (typeof value === "string" && unreleasedLabel !== value) { value = prettifyVersion(value); } diff --git a/packages/babel-helper-compilation-targets/src/targets.ts b/packages/babel-helper-compilation-targets/src/targets.ts index 401d7904a995..ff08703a6492 100644 --- a/packages/babel-helper-compilation-targets/src/targets.ts +++ b/packages/babel-helper-compilation-targets/src/targets.ts @@ -1,8 +1,11 @@ export const unreleasedLabels = { safari: "tp", -}; +} as const; -export const browserNameMap = { +import type { Target } from "./types"; + +// Map from browserslist|@mdn/browser-compat-data browser names to @kangax/compat-table browser names +export const browserNameMap: Record = { and_chr: "chrome", and_ff: "firefox", android: "android", @@ -17,4 +20,6 @@ export const browserNameMap = { opera: "opera", safari: "safari", samsung: "samsung", -}; +} as const; + +export type BrowserslistBrowserName = keyof typeof browserNameMap; diff --git a/packages/babel-helper-compilation-targets/src/types.ts b/packages/babel-helper-compilation-targets/src/types.ts index 00035a1f0e14..d17f15299333 100644 --- a/packages/babel-helper-compilation-targets/src/types.ts +++ b/packages/babel-helper-compilation-targets/src/types.ts @@ -1,4 +1,4 @@ -// Targets +// Targets, engine names defined in compat-tables export type Target = | "node" | "chrome" @@ -17,7 +17,9 @@ export type Targets = { }; export type TargetsTuple = { - [target in Target]: string; + [target in Exclude]: string; +} & { + node: string | true; }; export type Browsers = string | ReadonlyArray; @@ -31,3 +33,5 @@ export type InputTargets = { // remove `intersect`. esmodules?: boolean | "intersect"; } & Targets; + +export type { BrowserslistBrowserName } from "./targets"; diff --git a/packages/babel-helper-compilation-targets/src/utils.ts b/packages/babel-helper-compilation-targets/src/utils.ts index d527247e9fc0..2beea6b0404e 100644 --- a/packages/babel-helper-compilation-targets/src/utils.ts +++ b/packages/babel-helper-compilation-targets/src/utils.ts @@ -38,20 +38,27 @@ export function semverify(version: number | string): string { export function isUnreleasedVersion( version: string | number, - env: string, + env: Target, ): boolean { - const unreleasedLabel = unreleasedLabels[env]; + const unreleasedLabel = + // @ts-expect-error unreleasedLabel will be guarded later + unreleasedLabels[env]; return ( !!unreleasedLabel && unreleasedLabel === version.toString().toLowerCase() ); } -export function getLowestUnreleased(a: string, b: string, env: string): string { - const unreleasedLabel = unreleasedLabels[env]; - const hasUnreleased = [a, b].some(item => item === unreleasedLabel); - if (hasUnreleased) { - // @ts-expect-error todo(flow->ts): probably a bug - types of a hasUnreleased to not overlap - return a === hasUnreleased ? b : a || b; +export function getLowestUnreleased(a: string, b: string, env: Target): string { + const unreleasedLabel: + | typeof unreleasedLabels[keyof typeof unreleasedLabels] + | undefined = + // @ts-ignore unreleasedLabel is undefined when env is not safari + unreleasedLabels[env]; + if (a === unreleasedLabel) { + return b; + } + if (b === unreleasedLabel) { + return a; } return semverMin(a, b); } @@ -59,7 +66,7 @@ export function getLowestUnreleased(a: string, b: string, env: string): string { export function getHighestUnreleased( a: string, b: string, - env: string, + env: Target, ): string { return getLowestUnreleased(a, b, env) === a ? b : a; } From c2820ce19bfecabf19e7f5b80000005b67132e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 1 Jun 2022 14:04:29 -0400 Subject: [PATCH 12/17] helper-plugin-utils --- .../babel-helper-plugin-utils/src/index.ts | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/babel-helper-plugin-utils/src/index.ts b/packages/babel-helper-plugin-utils/src/index.ts index 96c4296b4a24..d1327b6bf0ce 100644 --- a/packages/babel-helper-plugin-utils/src/index.ts +++ b/packages/babel-helper-plugin-utils/src/index.ts @@ -19,13 +19,16 @@ export function declare( ) => PluginObject { // @ts-ignore return (api, options: Option, dirname: string) => { - let clonedApi; + let clonedApi: PluginAPI; - for (const name of Object.keys(apiPolyfills)) { + for (const name of Object.keys( + apiPolyfills, + ) as (keyof typeof apiPolyfills)[]) { if (api[name]) continue; // TODO: Use ??= when flow lets us to do so clonedApi = clonedApi ?? copyApiObject(api); + // @ts-expect-error The shape of API polyfill is guaranteed by APIPolyfillFactory clonedApi[name] = apiPolyfills[name](clonedApi); } @@ -38,11 +41,21 @@ export const declarePreset = declare as