import { skipTransparentExprWrappers } from "@babel/helper-skip-transparent-expression-wrappers"; import type { NodePath } from "@babel/traverse"; import { types as t } from "@babel/core"; // https://crbug.com/v8/11558 // check if there is a spread element followed by another argument. // (...[], 0) or (...[], ...[]) function matchAffectedArguments(argumentNodes) { const spreadIndex = argumentNodes.findIndex(node => t.isSpreadElement(node)); return spreadIndex >= 0 && spreadIndex !== argumentNodes.length - 1; } /** * Check whether the optional chain is affected by https://crbug.com/v8/11558. * This routine MUST not manipulate NodePath * * @export * @param {(NodePath)} path * @returns {boolean} */ export function shouldTransform( path: NodePath, ): boolean { let optionalPath = path; const chains = []; while ( optionalPath.isOptionalMemberExpression() || optionalPath.isOptionalCallExpression() ) { const { node } = optionalPath; chains.push(node); if (optionalPath.isOptionalMemberExpression()) { optionalPath = skipTransparentExprWrappers(optionalPath.get("object")); } else if (optionalPath.isOptionalCallExpression()) { optionalPath = skipTransparentExprWrappers(optionalPath.get("callee")); } } for (let i = 0; i < chains.length; i++) { const node = chains[i]; if ( t.isOptionalCallExpression(node) && matchAffectedArguments(node.arguments) ) { // f?.(...[], 0) if (node.optional) { return true; } // o?.m(...[], 0) // when node.optional is false, chains[i + 1] is always well defined const callee = chains[i + 1]; if (t.isOptionalMemberExpression(callee, { optional: true })) { return true; } } } return false; }