@@ -54,7 +51,7 @@ exports[`html output: generated html 1`] = `
className="one two"
/>
`;
-exports[`static html output: static html 1`] = `"
"`;
+exports[`static html output: static html 1`] = `"
"`;
diff --git a/src/__tests__/__snapshots__/option-attribute-alias.test.js.snap b/src/__tests__/__snapshots__/option-attribute-alias.test.js.snap
new file mode 100644
index 0000000..e0c682c
--- /dev/null
+++ b/src/__tests__/__snapshots__/option-attribute-alias.test.js.snap
@@ -0,0 +1,118 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Expect an error to be thrown 1`] = `
+"
/src/__tests__/option-attribute-alias.input.js:13
+ 11| p.b1.b2(class=\\"b3 b4\\")
+ 12| p.c1(className=\\"c2 c3\\")
+ > 13| p.d1(class=classes)
+ 14| p.e1(class=['e2', 'e3'])
+ 15| p.f1(class=['f2', ...classesArray])
+ 16| p(class=$.Red)
+
+We can't use expressions in shorthands, use \\"className\\" instead of \\"class\\""
+`;
+
+exports[`JavaScript output: transformed source code 1`] = `
+"const $ = {
+ Red: \\"color-red\\"
+};
+const classes = \\"d2 d3\\";
+const classesArray = [\\"f3\\", $.Red];
+const showK = true;
+const showL = false;
+
+const handleClick = () => {};
+
+const svgGroup = \\"\\";
+module.exports = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+"
+`;
+
+exports[`html output: generated html 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
",
+ }
+ }
+ />
+ ",
+ }
+ }
+ />
+
+`;
+
+exports[`static html output: static html 1`] = `""`;
diff --git a/src/__tests__/__snapshots__/option-class-attribute.test.js.snap b/src/__tests__/__snapshots__/option-class-attribute.test.js.snap
index 67af569..e78e9b9 100644
--- a/src/__tests__/__snapshots__/option-class-attribute.test.js.snap
+++ b/src/__tests__/__snapshots__/option-class-attribute.test.js.snap
@@ -16,7 +16,7 @@ module.exports = (
return (
);
})}
diff --git a/src/__tests__/option-attribute-alias.input.js b/src/__tests__/option-attribute-alias.input.js
new file mode 100644
index 0000000..3f3fffe
--- /dev/null
+++ b/src/__tests__/option-attribute-alias.input.js
@@ -0,0 +1,27 @@
+const $ = {Red: 'color-red'};
+const classes = 'd2 d3';
+const classesArray = ['f3', $.Red];
+const showK = true;
+const showL = false;
+const handleClick = () => {};
+const svgGroup = '';
+
+module.exports = pug`
+ div.a1
+ p.b1.b2(class="b3 b4")
+ p.c1(className="c2 c3")
+ p.d1(class=classes)
+ p.e1(class=['e2', 'e3'])
+ p.f1(class=['f2', ...classesArray])
+ p(class=$.Red)
+ p(class=${`g1 ${$.Red}`})
+ p.i1(class=${`i2 ${$.Red}`})
+ p.j1(
+ class="j2 j3",
+ className="j4 j5")
+ p(class=(showK && "k1"))
+ p.l1(class=(showL ? "l2" : ""))
+ a.m1(@click=handleClick)
+ svg.n1(@html={ __html: "" })
+ svg.o1(@html={ __html: svgGroup })
+`;
diff --git a/src/__tests__/option-attribute-alias.test.js b/src/__tests__/option-attribute-alias.test.js
new file mode 100644
index 0000000..4b33573
--- /dev/null
+++ b/src/__tests__/option-attribute-alias.test.js
@@ -0,0 +1,11 @@
+import testHelper, {testCompileError} from './test-helper';
+
+testCompileError(__dirname + '/option-attribute-alias.input.js');
+
+testHelper(__dirname + '/option-attribute-alias.input.js', {
+ attributeAlias: {
+ class: 'className',
+ '@click': 'onClick',
+ '@html': 'dangerouslySetInnerHTML',
+ },
+});
diff --git a/src/context.js b/src/context.js
index 1d62807..2c223c3 100644
--- a/src/context.js
+++ b/src/context.js
@@ -15,6 +15,9 @@ type Variable = {
type Options = {
classAttribute: string,
+ attributeAlias: {
+ [string]: string,
+ },
};
class Context {
diff --git a/src/index.js b/src/index.js
index 550f80b..8b37c04 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,6 +8,7 @@ import {setBabelTypes} from './lib/babel-types';
const DEFAULT_OPTIONS = {
classAttribute: 'className',
+ attributeAlias: {},
};
export default function(babel) {
diff --git a/src/utils/get-class-name-value.js b/src/utils/get-class-name-value.js
index 3b8f597..4816a20 100644
--- a/src/utils/get-class-name-value.js
+++ b/src/utils/get-class-name-value.js
@@ -2,92 +2,136 @@
import t from '../lib/babel-types';
-function getPlainShorthandValue(classes: Array): string | null {
- if (classes.length) {
- return classes
- .map(item => item.value)
- .filter(Boolean)
- .join(' ');
- }
+function isParsableLiteral(expr: Expression): boolean {
+ return (
+ t.isStringLiteral(expr) ||
+ t.isNumericLiteral(expr) ||
+ t.isBooleanLiteral(expr) ||
+ t.isNullLiteral(expr)
+ );
+}
- return null;
+function flattenAndFilterAttributeExpressions(
+ classes: Array,
+): Array {
+ return [].concat(
+ ...classes.map(item => {
+ if (t.isArrayExpression(item)) {
+ /*:: item = ((item: any): ArrayExpression) */
+ return (item.elements: any).filter(
+ item => item && !t.isSpreadElement(item),
+ );
+ } else {
+ return item;
+ }
+ }),
+ );
}
-function getPlainClassNameValue(
- classes: Array,
-): string | ArrayExpression | CallExpression | null | Array {
- if (classes.every(item => t.isStringLiteral(item))) {
- return classes
- .map(item => item.value)
- .filter(Boolean)
- .join(' ');
- }
+function combineParsableLiteralsAndExpressions(
+ classes: Array,
+): Array | Array> {
+ const literalOfClasses = classes.map(item => isParsableLiteral(item));
- if (classes.every(item => t.isArrayExpression(item))) {
- return classes.reduce((all, item) => all.concat(item.elements), []);
+ const result = [];
+ const len = classes.length;
+
+ let i = 0;
+ let lookLiteral = true;
+
+ while (i < len) {
+ const nextDifferentIndex = literalOfClasses.indexOf(!lookLiteral, i);
+ const start = i;
+ const end = nextDifferentIndex < 0 ? len : nextDifferentIndex;
+
+ result.push(classes.slice(start, end));
+
+ lookLiteral = !lookLiteral;
+ i = end;
}
- if (Array.isArray(classes)) {
- return classes[0];
+ return result;
+}
+
+function getValueOfLiterals(literals: Array): string {
+ if (literals.length < 1) {
+ return '';
}
- return null;
+ return literals
+ .map(item => (item: any).value)
+ .filter(Boolean)
+ .join(' ');
}
-function mergeStringWithClassName(
- shorthand: string | null,
- attribute: string | ArrayExpression | CallExpression | null | Array,
-) {
- // There are several branches:
- // - when attribute exists
- // - when shorthand only exists
- // - otherwise
-
- if (attribute) {
- if (typeof attribute === 'string') {
- if (shorthand) {
- return t.stringLiteral(shorthand + ' ' + attribute);
- }
- return t.stringLiteral(attribute);
- }
+function getMergedJSXExpression(
+ classes: Array,
+): StringLiteral | JSXExpressionContainer {
+ const combined = combineParsableLiteralsAndExpressions(classes);
- if (Array.isArray(attribute)) {
- if (shorthand) {
- return t.jSXExpressionContainer(
- t.arrayExpression([t.stringLiteral(shorthand)].concat(attribute)),
- );
- }
- return t.jSXExpressionContainer(t.arrayExpression(attribute));
- }
+ if (combined.length === 1) {
+ const value = getValueOfLiterals((combined[0]: any));
+ return t.stringLiteral(value);
+ }
- if (shorthand) {
- return t.jSXExpressionContainer(
- t.binaryExpression('+', t.stringLiteral(shorthand + ' '), attribute),
- );
+ if (combined.length === 2) {
+ if (combined[0].length === 0 && combined[1].length === 1) {
+ return t.jSXExpressionContainer(combined[1][0]);
}
+ }
- return t.jSXExpressionContainer(attribute);
+ // Keep combined items in odd
+ if (combined.length % 2 === 0) {
+ combined.push(([]: Array));
}
- if (shorthand) {
- if (typeof shorthand === 'string') {
- return t.stringLiteral(shorthand);
+ const quasis = [];
+ const expressions = [];
+ const len = combined.length;
+
+ for (let i = 0; i < len; ++i) {
+ let items = combined[i];
+ const itemsLen = items.length;
+ const isQuasi = i % 2 === 0;
+ const isFirst = i === 0;
+ const isLast = len - i <= 1;
+
+ if (isQuasi) {
+ /*:: items = ((items: any): Array) */
+ const value = getValueOfLiterals(items);
+ const raw = value
+ ? (isFirst ? '' : ' ') + value + (isLast ? '' : ' ')
+ : '';
+ const cooked = raw;
+
+ quasis.push(t.templateElement({raw, cooked}, isLast));
+ } else {
+ expressions.push(items[0]);
+
+ if (itemsLen > 1) {
+ for (let j = 1; j < itemsLen; ++j) {
+ const raw = ' ';
+ const cooked = ' ';
+ quasis.push(t.templateElement({raw, cooked}, false));
+ expressions.push(items[j]);
+ }
+ }
}
-
- return t.jSXExpressionContainer(shorthand);
}
- return null;
+ return t.jSXExpressionContainer(t.templateLiteral(quasis, expressions));
}
function getClassNameValue(
classesViaShorthand: Array,
- classesViaAttribute: Array,
+ classesViaAttribute: Array,
): any {
- const shorthandValue = getPlainShorthandValue(classesViaShorthand);
- const attributeValue = getPlainClassNameValue(classesViaAttribute);
+ const attrs = flattenAndFilterAttributeExpressions([
+ ...classesViaShorthand,
+ ...classesViaAttribute,
+ ]);
- return mergeStringWithClassName(shorthandValue, attributeValue);
+ return getMergedJSXExpression(attrs);
}
export default getClassNameValue;
diff --git a/src/visitors/Tag.js b/src/visitors/Tag.js
index e2ccb11..3d56f9e 100644
--- a/src/visitors/Tag.js
+++ b/src/visitors/Tag.js
@@ -45,21 +45,15 @@ function getAttributes(node: Object, context: Context): Array {
const attrs: Array = node.attrs
.map(
({name, val, mustEscape}: PugAttribute): Attribute | null => {
- if (/\.\.\./.test(name) && val === true) {
+ if (/^\.\.\./.test(name)) {
+ if (!val) {
+ throw new Error('spread attributes must not have a value');
+ }
return t.jSXSpreadAttribute(parseExpression(name.substr(3), context));
}
- // TODO: Need to drop all aliases for attributes
- switch (name) {
- case 'for':
- name = 'htmlFor';
- break;
- case 'maxlength':
- name = 'maxLength';
- break;
- }
-
const expr = parseExpression(String(val), context);
+ const attrName = context._options.attributeAlias[name] || name;
if (!mustEscape) {
const canSkipEscaping =
@@ -77,7 +71,7 @@ function getAttributes(node: Object, context: Context): Array {
return null;
}
- if (name === 'class') {
+ if (attrName === 'class') {
if (!t.isStringLiteral(expr)) {
throw context.error(
'INVALID_EXPRESSION',
@@ -87,11 +81,13 @@ function getAttributes(node: Object, context: Context): Array {
);
}
- classesViaShorthand.push(expr);
- return null;
+ if (!context._options.attributeAlias[name]) {
+ classesViaShorthand.push(expr);
+ return null;
+ }
}
- if (name === context._options.classAttribute) {
+ if (attrName === context._options.classAttribute) {
classesViaAttribute.push(expr);
return null;
}
@@ -101,11 +97,7 @@ function getAttributes(node: Object, context: Context): Array {
t.asJSXElement(expr) ||
t.jSXExpressionContainer(expr);
- if (/\.\.\./.test(name)) {
- throw new Error('spread attributes must not have a value');
- }
-
- return t.jSXAttribute(t.jSXIdentifier(name), jsxValue);
+ return t.jSXAttribute(t.jSXIdentifier(attrName), jsxValue);
},
)
.filter(Boolean);
@@ -137,6 +129,7 @@ function getAttributesAndChildren(
} {
const children = getChildren(node, context);
+ // TODO Implement node.attributeBlocks
if (node.attributeBlocks.length) {
throw new Error('Attribute blocks are not yet supported in react-pug');
}