Skip to content

Commit

Permalink
fix: handle template and string literals correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
merceyz committed Jun 20, 2019
1 parent 8b5c717 commit f90ddaf
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 105 deletions.
16 changes: 4 additions & 12 deletions src/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path';
import generate from '@babel/generator';
import _ from 'lodash';
import hash from 'object-hash';
import { isStringLike } from './strings';

export function flattenLogicalExpression(rootNode) {
const result = [];
Expand Down Expand Up @@ -130,22 +131,13 @@ export function isSafeConditionalExpression(node) {

const { consequent, alternate } = node;

if (
t.isStringLiteral(consequent) &&
t.isStringLiteral(alternate) &&
consequent.value.length > 0 &&
alternate.value.length > 0
) {
if (isStringLike(consequent) && isStringLike(alternate)) {
return true;
}

if (
(t.isStringLiteral(consequent) &&
consequent.value.length > 0 &&
isSafeConditionalExpression(alternate)) ||
(t.isStringLiteral(alternate) &&
alternate.value.length > 0 &&
isSafeConditionalExpression(consequent))
(isStringLike(consequent) && isSafeConditionalExpression(alternate)) ||
(isStringLike(alternate) && isSafeConditionalExpression(consequent))
) {
return true;
}
Expand Down
80 changes: 80 additions & 0 deletions src/utils/strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as t from '@babel/types';

export function isStringLike(node) {
return t.isStringLiteral(node) || t.isTemplateLiteral(node);
}

export 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');
}

export 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) {
return {
type: 'TemplateElement',
value: {
raw: value,
},
tail,
};
}
76 changes: 3 additions & 73 deletions src/visitors/combineStringLiterals.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,19 @@
import * as t from '@babel/types';
import _ from 'lodash';
import { isStringLike, combineStringLike } from '../utils/strings';

function combineStringsInArray(array) {
if (array.length < 2) {
return array;
}

const [match, noMatch] = _.partition(
array,
item => t.isStringLiteral(item) || t.isTemplateLiteral(item),
);
const [match, noMatch] = _.partition(array, isStringLike);

if (match.length < 2) {
return array;
}

const [strings, templates] = _.partition(match, t.isStringLiteral);

const expressions = [];
const quasis = [];

// Combine string literals
if (strings.length > 0) {
quasis.push(
templateElement(
strings.reduce((prev, curr) => t.stringLiteral(prev.value + ' ' + curr.value)).value,
true,
),
);
}

// Combine template literals
templates.forEach(item => {
if (item.expressions.length === 0) {
const newValue = item.quasis.reduce((prev, curr) =>
templateElement(prev.value.raw + ' ' + curr.value.raw, true),
);

if (quasis.length === 0) {
quasis.push(newValue);
return;
}

const prevItem = quasis[quasis.length - 1];
quasis[quasis.length - 1] = templateElement(
prevItem.value.raw + ' ' + newValue.value.raw,
true,
);
return;
}

item.quasis.forEach(item => {
if (quasis.length !== 0) {
const prevItem = quasis[quasis.length - 1];

if (item.tail === false && prevItem.tail) {
quasis[quasis.length - 1] = templateElement(
prevItem.value.raw + ' ' + item.value.raw,
true,
);
return;
}
}

quasis.push(item);
});

expressions.push(...item.expressions);
});

if (expressions.length === 0 && quasis.length === 1) {
return [t.stringLiteral(quasis[0].value.raw), ...noMatch];
}

return [t.templateLiteral(quasis, expressions), ...noMatch];
return [match.reduce(combineStringLike), ...noMatch];
}

const arrayVisitor = {
Expand Down Expand Up @@ -102,13 +42,3 @@ const visitor = {
export default (path, options) => {
path.traverse(visitor, { options });
};

function templateElement(value, tail = false) {
return {
type: 'TemplateElement',
value: {
raw: value,
},
tail,
};
}
42 changes: 26 additions & 16 deletions src/visitors/removeUnnecessaryCalls.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as t from '@babel/types';
import { isSafeConditionalExpression } from '../utils/helpers';
import { isStringLike, combineStringLike, isStringLikeEmpty } from '../utils/strings';

const transforms = [
function noArgumentsToString(args) {
Expand All @@ -9,7 +10,7 @@ const transforms = [
},

function singleStringLiteral(args) {
if (args.length === 1 && (t.isStringLiteral(args[0]) || t.isTemplateLiteral(args[0]))) {
if (args.length === 1 && isStringLike(args[0])) {
return args[0];
}
},
Expand All @@ -25,11 +26,11 @@ const transforms = [

const [arg1, arg2] = args;

if (isSafeConditionalExpression(arg1) && isSafeConditionalExpression(arg2)) {
if (args.every(isSafeConditionalExpression)) {
const newCond = t.conditionalExpression(
arg1.test,
t.stringLiteral(arg1.consequent.value + ' '),
t.stringLiteral(arg1.alternate.value + ' '),
combineStringLike(arg1.consequent, t.stringLiteral('')),
combineStringLike(arg1.alternate, t.stringLiteral('')),
);

return t.binaryExpression('+', newCond, arg2);
Expand All @@ -41,24 +42,33 @@ const transforms = [

const [arg1, arg2] = args;

if (
(t.isStringLiteral(arg1) || t.isStringLiteral(arg2)) &&
(isSafeConditionalExpression(arg1) || isSafeConditionalExpression(arg2))
) {
const string = t.isStringLiteral(arg1) ? arg1 : arg2;
const conditional = t.isStringLiteral(arg2) ? arg1 : arg2;
if (args.some(isStringLike) && args.some(isSafeConditionalExpression)) {
const string = isStringLike(arg1) ? arg1 : arg2;
const conditional = isStringLike(arg2) ? arg1 : arg2;

if (isStringLikeEmpty(conditional.consequent) || isStringLikeEmpty(conditional.alternate)) {
return t.binaryExpression(
'+',
string,
t.conditionalExpression(
conditional.test,
combineStringLike(t.stringLiteral(''), conditional.consequent),
combineStringLike(t.stringLiteral(''), conditional.alternate),
),
);
}

return t.binaryExpression('+', t.stringLiteral(string.value + ' '), conditional);
return t.binaryExpression('+', combineStringLike(string, t.stringLiteral('')), conditional);
}
},

function singleLogicalExpression(args) {
if (args.length !== 1) return;

const [arg] = args;
if (t.isLogicalExpression(arg, { operator: '&&' }) && t.isStringLiteral(arg.right)) {
if (t.isLogicalExpression(arg, { operator: '&&' }) && isStringLike(arg.right)) {
return t.conditionalExpression(arg.left, arg.right, t.stringLiteral(''));
} else if (t.isLogicalExpression(arg, { operator: '||' }) && t.isStringLiteral(arg.right)) {
} else if (t.isLogicalExpression(arg, { operator: '||' }) && isStringLike(arg.right)) {
// Assume that arg.left returns a string value
return arg;
}
Expand All @@ -69,16 +79,16 @@ const transforms = [

const [arg1, arg2] = args;
if (
t.isStringLiteral(arg1) &&
isStringLike(arg1) &&
t.isLogicalExpression(arg2, { operator: '&&' }) &&
t.isStringLiteral(arg2.right)
isStringLike(arg2.right)
) {
return t.binaryExpression(
'+',
arg1,
t.conditionalExpression(
arg2.left,
t.stringLiteral(' ' + arg2.right.value),
combineStringLike(t.stringLiteral(''), arg2.right),
t.stringLiteral(''),
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const x = clsx('foo', `bar-${baz}`);
const y = clsx(`bar-${baz}`, 'foo');
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const x = clsx(`foo bar-${baz}`);
const y = clsx(`bar-${baz} foo`);
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
const classA = clsx('foo bar', foo ? 'a' : 'b');
const classB = clsx(foo ? 'a' : 'b', 'foo bar');
const x1 = clsx('foo bar', foo ? 'a' : 'b');
const x2 = clsx(foo ? 'a' : 'b', 'foo bar');
const x3 = clsx('foo bar', foo ? 'a' : ``);
const x4 = clsx('foo bar', foo ? 'a' : '');
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
const classA = 'foo bar ' + (foo ? 'a' : 'b');
const classB = 'foo bar ' + (foo ? 'a' : 'b');
const x1 = 'foo bar ' + (foo ? 'a' : 'b');
const x2 = 'foo bar ' + (foo ? 'a' : 'b');
const x3 = 'foo bar' + (foo ? ' a' : '');
const x4 = 'foo bar' + (foo ? ' a' : '');

0 comments on commit f90ddaf

Please sign in to comment.