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
JavaScript
'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;