UNPKG

babel-plugin-optimize-clsx

Version:

Babel plugin to optimize the use of clsx, classnames, and all libraries with a compatible API

1,172 lines (969 loc) 34.5 kB
'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var t = require('@babel/types'); var generate = _interopDefault(require('@babel/generator')); var findCacheDir = _interopDefault(require('find-cache-dir')); var fs = _interopDefault(require('fs')); var path = _interopDefault(require('path')); var _flatMap = _interopDefault(require('lodash/flatMap')); var _flattenDeep = _interopDefault(require('lodash/flattenDeep')); var _isEqualWith = _interopDefault(require('lodash/isEqualWith')); var hash = _interopDefault(require('object-hash')); var template = _interopDefault(require('@babel/template')); var _map = _interopDefault(require('lodash/fp/map')); var _uniqBy = _interopDefault(require('lodash/fp/uniqBy')); var _flow = _interopDefault(require('lodash/fp/flow')); var _partition = _interopDefault(require('lodash/partition')); var _values = _interopDefault(require('lodash/fp/values')); var _groupBy = _interopDefault(require('lodash/fp/groupBy')); var _partition$1 = _interopDefault(require('lodash/fp/partition')); 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 ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } const DefaultSettings = { libraries: ['clsx', 'classnames'], functionNames: [], removeUnnecessaryCalls: true, collectCalls: false }; function getOptions(options = {}) { return _objectSpread2({}, DefaultSettings, {}, options); } const getFunctions = { CallExpression(path) { const { callee } = path.node; if (t.isIdentifier(callee) && this.options.functionNames.includes(callee.name)) { path.setData('is_optimizable', true); this.functions.push(path); } } }; function isImportMap(node) { return node.referenceCount !== undefined; } function findExpressions(program, options) { const result = []; if (options.functionNames.length) { program.traverse(getFunctions, { options, functions: result }); } if (!options.libraries.length) { return result; } const addToState = (path, name) => { const binding = path.scope.getBinding(name); if (!binding) { return; } const expressions = []; const refPaths = binding.referencePaths; for (const ref of refPaths) { const call = ref.parentPath; if (call.isCallExpression()) { call.setData('is_optimizable', true); expressions.push(call); } } if (expressions.length) { result.push({ source: path, referenceCount: binding.references, expressions }); } }; const body = program.get('body'); for (const statement of body) { // import x from 'y'; // import * as x from 'y'; if (statement.isImportDeclaration() && options.libraries.includes(statement.node.source.value) && statement.node.specifiers.length === 1 && (t.isImportDefaultSpecifier(statement.node.specifiers[0]) || t.isImportNamespaceSpecifier(statement.node.specifiers[0]))) { addToState(statement, statement.node.specifiers[0].local.name); } // const x = require('y'); else if (statement.isVariableDeclaration()) { statement.get('declarations').forEach(decPath => { const dec = decPath.node; if (t.isIdentifier(dec.id) && t.isCallExpression(dec.init) && t.isIdentifier(dec.init.callee, { name: 'require' }) && dec.init.arguments.length === 1 && t.isStringLiteral(dec.init.arguments[0]) && options.libraries.includes(dec.init.arguments[0].value)) { addToState(decPath, dec.id.name); } }); } } return result; } let stream = null; let count = 0; const collectCalls = ({ expression, options, filename }) => { if (!options.collectCalls) { return; } if (stream === null) { const cacheDir = findCacheDir({ name: 'optimize-clsx', create: true }); if (!cacheDir) { throw new Error('Unable to locate cache directory'); } const filePath = path.join(cacheDir, `log-${new Date(Date.now()).toISOString().replace(/:/g, '.')}.js`); stream = fs.createWriteStream(filePath, { flags: 'w' }); console.log('Writing CallExpressions to ' + filePath); } let locationStr = ''; if (expression.node.loc) { const location = expression.node.loc.start; locationStr = `:${location.line}:${location.column}`; } stream.write(`// ${filename}${locationStr}\nconst x${++count} = ${generate(expression.node).code};\n\n`); }; const extractObjectProperties = ({ expression }) => { expression.node.arguments = _flatMap(expression.node.arguments, node => { if (!t.isObjectExpression(node)) { return node; } return node.properties.map(p => { if (t.isSpreadElement(p)) { return p.argument; } else { const getKey = () => { return p.computed ? p.key : t.isStringLiteral(p.key) ? p.key : t.stringLiteral(p.key.name); }; if (t.isObjectMethod(p)) { return getKey(); } else { // @ts-ignore return t.logicalExpression('&&', p.value, getKey()); } } }); }); }; function unwrapArrayExpression(nodes) { return nodes.map(item => t.isArrayExpression(item) ? unwrapArrayExpression(item.elements) : item); } const flattenArrays = ({ expression }) => { expression.node.arguments = _flattenDeep(unwrapArrayExpression(expression.node.arguments)); }; function isStringLike(node) { return t.isStringLiteral(node) || t.isTemplateLiteral(node); } function combineStringLike(a, b) { if (isStringLikeEmpty(a) && isStringLikeEmpty(b)) { return t.stringLiteral(''); } if (t.isStringLiteral(a) && t.isStringLiteral(b)) { return t.stringLiteral(a.value + ' ' + b.value); } if (t.isTemplateLiteral(a) && t.isTemplateLiteral(b)) { const expressions = [...a.expressions, ...b.expressions]; const quasis = [...a.quasis]; quasis[quasis.length - 1] = templateElement(quasis[quasis.length - 1].value.raw + ' ' + b.quasis[0].value.raw); quasis.push(...b.quasis.slice(1)); return templateOrStringLiteral(quasis, expressions); } if (t.isTemplateLiteral(a) && t.isStringLiteral(b)) { const expressions = [...a.expressions]; const quasis = [...a.quasis]; const i = quasis.length - 1; quasis[i] = templateElement(quasis[i].value.raw + ' ' + b.value, true); return templateOrStringLiteral(quasis, expressions); } if (t.isStringLiteral(a) && t.isTemplateLiteral(b)) { const expressions = [...b.expressions]; const quasis = [...b.quasis]; const i = 0; quasis[i] = templateElement(a.value + ' ' + quasis[i].value.raw, true); return templateOrStringLiteral(quasis, expressions); } throw new Error('Unable to handle that input'); } function isStringLikeEmpty(node) { if (t.isStringLiteral(node)) { return node.value.length === 0; } if (t.isTemplateLiteral(node)) { return node.expressions.length === 0 && node.quasis.every(q => q.value.raw.length === 0); } return false; } function templateOrStringLiteral(quasis, expressions) { if (expressions.length === 0) { return t.stringLiteral(quasis[0].value.raw); } return t.templateLiteral(quasis, expressions); } function templateElement(value, tail = false) { // Workaround to get the cooked value // https://github.com/babel/babel/issues/9242 const valueAST = template.ast(`\`${value}\``).expression.quasis[0].value; return t.templateElement(valueAST, tail); } function flattenLogicalExpression(rootNode) { const result = []; checkNode(rootNode); return result; function checkNode(node) { if (t.isLogicalExpression(node)) { checkNode(node.left); result.push(node.right); } else { result.push(node); } } } function isNestedLogicalAndExpression(node) { if (!t.isLogicalExpression(node, { operator: '&&' })) { return false; } let temp = node.left; while (t.isLogicalExpression(temp)) { if (temp.operator !== '&&') { return false; } temp = temp.left; } return true; } function getMostFrequentNode(operators) { let maxNode = null; let maxCount = 0; let operators_n = operators.length; for (let y = 0, y_n = operators_n - 1; y < y_n; y++) { for (let x = 0, row = operators[y], x_n = row.length - 1; x < x_n; x++) { let col = row[x]; let count = 0; for (let y2 = y + 1; y2 < operators_n; y2++) { for (let x2 = 0, row2 = operators[y2]; x2 < row2.length - 1; x2++) { if (compareNodes(col, row2[x2])) { count += 1; } } } if (count > maxCount) { maxNode = col; maxCount = count; } } } return maxNode; } function compareNodes(obj1, obj2) { if (obj1.type !== obj2.type) { return false; } switch (obj1.type) { case 'NullLiteral': { return true; } case 'RegExpLiteral': { return t.isNodesEquivalent(obj1, obj2); } case 'Identifier': return obj1.name === obj2.name; case 'MemberExpression': return compareNodes(obj1.object, obj2.object) && compareNodes(obj1.property, obj2.property); case 'BinaryExpression': return obj1.operator === obj2.operator && compareNodes(obj1.left, obj2.left) && compareNodes(obj1.right, obj2.right); default: { if (t.isLiteral(obj1) && !t.isTemplateLiteral(obj1)) { return obj1.value === obj2.value; } return _isEqualWith(obj1, obj2, (v1, v2, key) => key === 'start' || key === 'end' || key === 'loc' ? true : undefined); } } } function hashNode(node) { return hash(node, { excludeKeys: key => key === 'start' || key === 'end' || key === 'loc' || key === 'extra' ? true : false }); } function isSafeConditionalExpression(node) { if (!t.isConditionalExpression(node)) { return false; } const { consequent, alternate } = node; if (isStringLike(consequent) && isStringLike(alternate)) { return true; } if (isStringLike(consequent) && isSafeConditionalExpression(alternate) || isStringLike(alternate) && isSafeConditionalExpression(consequent)) { return true; } return false; } function createLogicalAndExpression(items) { return items.reduce((prev, curr) => t.logicalExpression('&&', prev, curr)); } function isNodeFalsy(node) { return isStringLikeEmpty(node) || t.isNullLiteral(node) || t.isIdentifier(node, { name: 'undefined' }) || t.isBooleanLiteral(node, { value: false }) || t.isNumericLiteral(node, { value: 0 }); } const replaceVisitor = { BinaryExpression(path) { const node = path.node; if (t.isBinaryExpression(node) && (node.operator === '===' || node.operator === '!==') && (t.isStringLiteral(node.right) || t.isStringLiteral(node.left))) { const propType = this.types.find(obj => obj.matches.some(item => (obj.isRequired || item.hasDefaultValue) && (compareNodes(node.left, item) || compareNodes(node.right, item)))); if (propType === undefined) return; const valueToUse = propType.optionA.length < propType.optionB.length ? propType.optionA : propType.optionB; const target = t.isStringLiteral(node.right) ? 'right' : 'left'; if (node[target].value !== valueToUse && (node[target].value === propType.optionA || node[target].value === propType.optionB)) { node[target] = t.stringLiteral(valueToUse); node.operator = node.operator === '!==' ? '===' : '!=='; } } } }; const functionVisitor = { 'FunctionExpression|FunctionDeclaration'(path) { const node = path.node; for (const propType of this.propTypes) { if (t.isFunction(node) && t.isIdentifier(node.id, { name: propType.name }) && node.params.length !== 0) { getObjectPattern(node.params[0], null); function getObjectPattern(item, matcher) { if (Array.isArray(item)) { for (const obj of item) { getObjectPattern(obj, matcher); } return; } if (t.isBlockStatement(item)) { return getObjectPattern(item.body, matcher); } if (t.isVariableDeclaration(item)) { return getObjectPattern(item.declarations, matcher); } if (t.isObjectPattern(item)) { return getObjectPattern(item.properties, null); } if (t.isObjectProperty(item) && t.isIdentifier(item.key)) { let matchValue = null; if (t.isIdentifier(item.value)) { matchValue = item.value; } else if (t.isAssignmentPattern(item.value)) { matchValue = item.value.left; matchValue.hasDefaultValue = true; } else { return; } propType.values = propType.values.map(prop => { if (prop.name !== item.key.name) return prop; if (prop.matches === undefined) { prop.matches = []; } return _objectSpread2({}, prop, { matches: [...prop.matches, matchValue] }); }); return; } if (t.isRestElement(item) && t.isIdentifier(item.argument)) { return getObjectPattern(item.argument, null); } if (t.isIdentifier(item)) { propType.values = propType.values.map(prop => { if (prop.matches === undefined) { prop.matches = []; } return _objectSpread2({}, prop, { matches: [...prop.matches, t.memberExpression(item, t.identifier(prop.name))] }); }); return getObjectPattern(node.body, item); } if (t.isVariableDeclarator(item)) { if (t.isMemberExpression(item.init) && compareNodes(item.init.object, matcher) && t.isIdentifier(item.init.property) && t.isIdentifier(item.id)) { propType.values = propType.values.map(prop => { if (prop.name !== item.init.property.name) return prop; if (prop.matches === undefined) { prop.matches = []; } return _objectSpread2({}, prop, { matches: [...prop.matches, item.id] }); }); } else if (t.isIdentifier(item.init) && compareNodes(item.init, matcher) && t.isObjectPattern(item.id)) { return getObjectPattern(item.id, null); } } } path.traverse(replaceVisitor, { types: propType.values }); path.skip(); } } } }; function getPropTypes(body) { const propTypeName = getPropTypesName(body); if (propTypeName === undefined) return []; const result = []; for (const node of body) { if (t.isExpressionStatement(node)) { const expr = node.expression; if (t.isAssignmentExpression(expr, { operator: '=' }) && t.isMemberExpression(expr.left) && t.isIdentifier(expr.left.property, { name: 'propTypes' }) && t.isIdentifier(expr.left.object)) { let propType = { name: expr.left.object.name, values: [] }; if (t.isObjectExpression(expr.right)) { for (const prop of expr.right.properties) { getArrayElements(prop.value, false); function getArrayElements(element, isRequired) { if (t.isMemberExpression(element) && t.isIdentifier(element.property, { name: 'isRequired' })) { getArrayElements(element.object, true); } else if (t.isCallExpression(element) && t.isMemberExpression(element.callee) && t.isIdentifier(element.callee.object, { name: propTypeName }) && t.isIdentifier(element.callee.property, { name: 'oneOf' }) && element.arguments.length === 1) { const value = element.arguments[0]; if (t.isArrayExpression(value) && value.elements.length === 2 && t.isStringLiteral(value.elements[0]) && t.isStringLiteral(value.elements[1])) { propType.values.push({ name: prop.key.name, isRequired: isRequired, optionA: value.elements[0].value, optionB: value.elements[1].value }); } } } } } if (propType.values.length !== 0) { result.push(propType); } } } } return result; } function getPropTypesName(body) { for (const node of body) { if (t.isImportDeclaration(node) && node.source.value === 'prop-types' && node.specifiers.length === 1) { const spec = node.specifiers[0]; if (t.isImportDefaultSpecifier(spec)) { return spec.local.name; } } } } const propTypes = ({ program: path, state }) => { // This visitor should only run once if (state.has('proptypes')) { return; } state.add('proptypes'); const propTypes = getPropTypes(path.node.body); if (propTypes.length === 0) return; path.traverse(functionVisitor, { propTypes }); }; const optimizations = { ConditionalExpression(node) { // foo ? bar : '' --> foo && bar if (isNodeFalsy(node.alternate)) { return t.logicalExpression('&&', node.test, node.consequent); } // foo ? '' : bar --> !foo && bar if (isNodeFalsy(node.consequent)) { return t.logicalExpression('&&', t.unaryExpression('!', node.test), node.alternate); } }, BinaryExpression(node) { // This transform allows createConditionalExpression to optimize the expression later // foo % 2 === 1 --> foo % 2 !== 0 // foo % 2 !== 1 --> foo % 2 === 0 if ((node.operator === '!==' || node.operator === '===') && t.isNumericLiteral(node.right, { value: 1 }) && t.isBinaryExpression(node.left, { operator: '%' }) && t.isNumericLiteral(node.left.right, { value: 2 })) { return _objectSpread2({}, node, { right: t.numericLiteral(0), operator: node.operator === '===' ? '!==' : '===' }); } }, LogicalExpression(node) { // foo || '' -> foo if (node.operator === '||' && isNodeFalsy(node.right)) { return node.left; } if (isNestedLogicalAndExpression(node)) { return _flow(flattenLogicalExpression, // Remove duplicates in the same expression // foo && bar && bar --> foo && bar _uniqBy(hashNode), // Optimize individual expressions _map(optimizeExpression), createLogicalAndExpression)(node); } } }; function optimizeExpression(node) { if (node.type in optimizations) { // @ts-ignore const result = optimizations[node.type](node); if (result) { // @ts-ignore return result.type !== node.type || result.operator !== node.operator ? optimizeExpression(result) : result; } } return node; } const optimizeExpressions = ({ expression }) => { expression.node.arguments = expression.node.arguments.map(optimizeExpression); }; const stripLiterals = ({ expression: path }) => { const [match, noMatch] = _partition(path.node.arguments, isNestedLogicalAndExpression); const result = match.map(flattenLogicalExpression).map(expression => // Remove values that will always be true expression.filter((item, index) => !(t.isBooleanLiteral(item, { value: true }) || index !== expression.length - 1 && t.isNumericLiteral(item) && item.value !== 0 || index !== expression.length - 1 && isStringLike(item) && !isStringLikeEmpty(item)))) // Remove expressions that will always be false .filter(expression => !(expression.length === 0 || expression.some(isNodeFalsy))).map(createLogicalAndExpression); const rest = noMatch.filter(item => !isNodeFalsy(item)); path.node.arguments = [...rest, ...result]; }; const combineArguments = ({ expression: path }) => { // Not enough arguments to optimize if (path.node.arguments.length < 2) return; const [match, noMatch] = _partition(path.node.arguments, isNestedLogicalAndExpression); // Not enough arguments to optimize if (match.length < 2) return; const operators = match.map(flattenLogicalExpression); const node = getMostFrequentNode(operators); // No nodes appear more than once if (node === null) return; const rootNode = combineOperators(operators, node); const newAST = convertToAST(rootNode); path.node.arguments = [...noMatch, ...newAST]; return; function convertToAST(node) { if (node.type !== 'rootNode') { return node; } const result = []; if (node.child.type === 'rootNode') { let right = convertToAST(node.child); if (right.length === 1) { right = right[0]; } else { right = t.arrayExpression(right); } result.push(t.logicalExpression('&&', node.node, right)); } else { result.push(t.logicalExpression('&&', node.node, node.child)); } if (node.next !== undefined) { const r = convertToAST(node.next); Array.isArray(r) ? result.push(...r) : result.push(r); } return result; } function combineOperators(operators, node) { const newNode = { type: 'rootNode', node: node, child: [], next: [] }; operators.forEach(row => { const filtered = row.filter(item => !compareNodes(item, node)); if (filtered.length === row.length) { newNode.next.push(row); } else { newNode.child.push(filtered); } }); newNode.next = checkSub(newNode.next); const child = checkSub(newNode.child); if (Array.isArray(child)) { newNode.child = child.length === 1 ? child[0] : t.arrayExpression(child); } else { newNode.child = child; } return newNode; function checkSub(items) { if (items.length === 0) return undefined; if (items.length > 1) { const nextCheck = getMostFrequentNode(items); if (nextCheck !== null) { return combineOperators(items, nextCheck); } } return items.map(createLogicalAndExpression); } } }; function combineStringsInArray(array) { if (array.length < 2) { return array; } const [match, noMatch] = _partition(array, isStringLike); if (match.length < 2) { return array; } return [match.reduce(combineStringLike), ...noMatch]; } const arrayVisitor = { ArrayExpression(path) { const { node } = path; node.elements = combineStringsInArray(node.elements); if (node.elements.length === 1 && node.elements[0]) { path.replaceWith(node.elements[0]); } } }; const combineStringLiterals = ({ expression: path }) => { path.node.arguments = combineStringsInArray(path.node.arguments); path.traverse(arrayVisitor); }; const createConditionalExpression = ({ expression: path }) => { path.node.arguments = combine(path.node.arguments); function combine(args) { const [match, noMatch] = _partition(args, isNestedLogicalAndExpression); if (match.length === 0) return noMatch; let operators = match.map(flattenLogicalExpression) // If the last item in each row is an array, recursivly call ourself .map(row => { let col = row.pop(); if (t.isArrayExpression(col)) { col.elements = combine(col.elements); if (col.elements.length === 1) { col = col.elements[0]; } } row.push(col); return row; }); const result = noMatch; for (let row_index = 0; row_index < operators.length; row_index++) { const row = operators[row_index]; if (checkRow()) { row_index -= 1; } function checkRow() { for (let col_index = 0; col_index < row.length - 1; col_index++) { const col = row[col_index]; const isUnary = t.isUnaryExpression(col, { operator: '!' }); const isBinary = t.isBinaryExpression(col, { operator: '!==' }); if (!isUnary && !isBinary) continue; for (let row2_index = 0; row2_index < operators.length; row2_index++) { const row2 = operators[row2_index]; for (let col2_index = 0; col2_index < row2.length - 1; col2_index++) { const col2 = row2[col2_index]; if (row_index === row2_index && col_index === col2_index) continue; if (isUnary && !compareNodes(col.argument, col2)) { continue; } else if (isBinary && !(t.isBinaryExpression(col2, { operator: '===' }) && (compareNodes(col.left, col2.left) && compareNodes(col.right, col2.right) || compareNodes(col.left, col2.right) && compareNodes(col.right, col2.left)))) { continue; } const left = createLogicalAndExpression(row2.filter((e, index) => index !== col2_index)); const right = createLogicalAndExpression(row.filter((e, index) => index !== col_index)); // isUnary: col2 is 1 char shorter (col2: foo vs col: !foo) // isBinary: col2 has the === operator result.push(t.conditionalExpression(col2, left, right)); // Remove from collection operators = operators.filter((e, index) => index !== row_index && index !== row2_index); return true; } } } return false; } } return [...result, ...operators.map(createLogicalAndExpression)]; } }; const transforms = [function noArgumentsToString() { return t.stringLiteral(''); }, function singleStringLike(arg) { if (isStringLike(arg) || isSafeConditionalExpression(arg)) { return arg; } }, function multipleSafeConditionals(conditionalOne, conditionalTwo) { if (isSafeConditionalExpression(conditionalOne) && isSafeConditionalExpression(conditionalTwo)) { const newCond = t.conditionalExpression(conditionalOne.test, combineStringLike(conditionalOne.consequent, t.stringLiteral('')), combineStringLike(conditionalOne.alternate, t.stringLiteral(''))); return t.binaryExpression('+', newCond, conditionalTwo); } }, function stringAndSafeConditional(stringLike, conditional) { if (isStringLike(stringLike) && isSafeConditionalExpression(conditional)) { if (isStringLikeEmpty(conditional.consequent) || isStringLikeEmpty(conditional.alternate)) { return t.binaryExpression('+', stringLike, t.conditionalExpression(conditional.test, combineStringLike(t.stringLiteral(''), conditional.consequent), combineStringLike(t.stringLiteral(''), conditional.alternate))); } return t.binaryExpression('+', combineStringLike(stringLike, t.stringLiteral('')), conditional); } }, function singleLogicalExpression(logicalExpr) { if (t.isLogicalExpression(logicalExpr, { operator: '&&' }) && (isStringLike(logicalExpr.right) || isSafeConditionalExpression(logicalExpr.right) || t.isBinaryExpression(logicalExpr.right, { operator: '+' }) && isStringLike(logicalExpr.right.left) && isSafeConditionalExpression(logicalExpr.right.right))) { return t.conditionalExpression(logicalExpr.left, logicalExpr.right, t.stringLiteral('')); } }, function stringAndLogicalExpression(stringLike, logicalExpr) { if (isStringLike(stringLike) && t.isLogicalExpression(logicalExpr, { operator: '&&' }) && isStringLike(logicalExpr.right)) { return t.binaryExpression('+', stringLike, t.conditionalExpression(logicalExpr.left, combineStringLike(t.stringLiteral(''), logicalExpr.right), t.stringLiteral(''))); } }]; function runTransforms(path, elements) { const reversed = [...elements].reverse(); for (const tr of transforms) { var _tr$apply; if (tr.length !== elements.length) { continue; } const result = (_tr$apply = tr.apply(undefined, elements)) !== null && _tr$apply !== void 0 ? _tr$apply : tr.apply(undefined, reversed); if (result !== undefined) { path.replaceWith(result); break; } } } const arrayVisitor$1 = { ArrayExpression(path) { runTransforms(path, path.node.elements); } }; const removeUnnecessaryCalls = ({ expression: path, options }) => { if (!options.removeUnnecessaryCalls) { return; } path.traverse(arrayVisitor$1); runTransforms(path, path.node.arguments); }; function matchLeftOrRight(node, checkOrChecks) { if (!Array.isArray(checkOrChecks)) { return checkOrChecks(node.left) || checkOrChecks(node.right); } for (const _check of checkOrChecks) { if (_check(node.left) || _check(node.right)) { return true; } } return false; } function combineFromArray(arr) { // x === 'foo', 'foo' === x // x.y === 'foo', 'foo' === x.y // x?.y === 'foo', 'foo' === x?.y const [match, noMatch] = _partition$1(itm => { return checkItem(itm); function checkItem(item) { if (isNestedLogicalAndExpression(item)) { return checkItem(item.left); } return t.isBinaryExpression(item, { operator: '===' }) && matchLeftOrRight(item, t.isStringLiteral) && matchLeftOrRight(item, [t.isMemberExpression, t.isIdentifier, t.isOptionalMemberExpression]); } }, arr); if (match.length === 0) { return arr; } const newArgs = _flow(_map(flattenLogicalExpression), // Set the string to always be on the right side of === // Simplifies the rest of the code _map(row => { const tempNode = row[0]; if (t.isStringLiteral(tempNode.left)) { const strNode = tempNode.left; tempNode.left = tempNode.right; tempNode.right = strNode; } return row; }), // Group on whatever the strings are compared to _groupBy(row => hashNode(row[0].left)), // Removes the key created by groupBy _values, // Create the objects _map(group => { if (group.length === 1) { return createLogicalAndExpression(group[0]); } return t.memberExpression(t.objectExpression(group.map(row => t.objectProperty(row[0].right, createLogicalAndExpression(row.slice(1))))), group[0][0].left, true); }))(match); return [...noMatch, ...newArgs]; } const arrayVisitor$2 = { ArrayExpression(path) { path.node.elements = combineFromArray(path.node.elements); if (path.node.elements.length === 1) { path.replaceWith(path.node.elements[0]); } } }; const createObjectKeyLookups = ({ expression: path }) => { path.traverse(arrayVisitor$2); path.node.arguments = combineFromArray(path.node.arguments); }; const referencedObjects = ({ expression, pushToQueue }) => { const args = expression.get('arguments'); for (const nodePath of args) { if (!nodePath.isIdentifier()) { continue; } const binding = nodePath.scope.getBinding(nodePath.node.name); if (!binding) { continue; } if (binding.constant && binding.path.isVariableDeclarator() && t.isObjectExpression(binding.path.node.init) && binding.referencePaths.every(ref => ref.parentPath.isCallExpression() && ref.parentPath.getData('is_optimizable') === true)) { const init = binding.path.get('init'); init.replaceWith(t.callExpression(t.cloneNode(expression.node.callee), [binding.path.node.init])); pushToQueue(init); } } }; const visitors = [collectCalls, flattenArrays, extractObjectProperties, propTypes, optimizeExpressions, stripLiterals, combineArguments, combineStringLiterals, createConditionalExpression, removeUnnecessaryCalls, createObjectKeyLookups, referencedObjects]; var index = (() => ({ visitor: { Program(path, state) { const options = getOptions(state.opts); const expressions = findExpressions(path, options); if (expressions.length === 0) { return; } const internalState = new Set(); const runVisitors = (expression, pushToQueue) => { for (const visitor of visitors) { visitor({ program: path, expression, state: internalState, options, filename: state.filename, pushToQueue }); if (!expression.isCallExpression()) { return false; } } }; for (let x = 0; x < expressions.length; x++) { const item = expressions[x]; if (isImportMap(item)) { for (let y = 0; y < item.expressions.length; y++) { if (runVisitors(item.expressions[y], newExpression => { newExpression.setData('is_optimizable', true); item.expressions.push(newExpression); item.referenceCount += 1; }) === false) { item.expressions.splice(y, 1); item.referenceCount -= 1; y -= 1; } if (item.referenceCount === 0) { item.source.remove(); expressions.splice(x, 1); x -= 1; break; } } } else if (runVisitors(item, newExpression => { newExpression.setData('is_optimizable', true); expressions.push(newExpression); }) === false) { expressions.splice(x, 1); x -= 1; } } } } })); module.exports = index;