Skip to content

Commit

Permalink
fix: handle spread (#243)
Browse files Browse the repository at this point in the history
* fix: handle spread

* test: handle spread

* test: add test case for spread

* spread: remove keys exclusion, avoid falsy values

* refactor: handle spread separated in another file
  • Loading branch information
AlbertLucianto authored and gajus committed Mar 21, 2019
1 parent 70550bc commit 05c2683
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 1 deletion.
73 changes: 73 additions & 0 deletions src/createSpreadMapper.js
@@ -0,0 +1,73 @@
// @flow

import {
Expression,
memberExpression,
binaryExpression,
stringLiteral,
logicalExpression,
identifier
} from '@babel/types';
import optionsDefaults from './schemas/optionsDefaults';

const createSpreadMapper = (path: *, stats: *): { [destinationName: string]: Expression } => {
const result = {};

let {attributeNames} = optionsDefaults;

if (stats.opts && stats.opts.attributeNames) {
attributeNames = Object.assign({}, attributeNames, stats.opts.attributeNames);
}

const attributes = Object
.entries(attributeNames)
.filter((pair) => {
return pair[1];
});

const attributeKeys = attributes.map((pair) => {
return pair[0];
});

path.traverse({
JSXSpreadAttribute (spreadPath: *) {
const spread = spreadPath.node;

for (const attributeKey of attributeKeys) {
const destinationName = attributeNames[attributeKey];

if (result[destinationName]) {
result[destinationName] = binaryExpression(
'+',
result[destinationName],
binaryExpression(
'+',
stringLiteral(' '),
logicalExpression(
'||',
memberExpression(
spread.argument,
identifier(destinationName),
),
stringLiteral('')
)
),
);
} else {
result[destinationName] = logicalExpression(
'||',
memberExpression(
spread.argument,
identifier(destinationName),
),
stringLiteral('')
);
}
}
}
});

return result;
};

export default createSpreadMapper;
53 changes: 53 additions & 0 deletions src/handleSpreadClassName.js
@@ -0,0 +1,53 @@
// @flow

import {
Expression,
isStringLiteral,
isJSXExpressionContainer,
jsxExpressionContainer,
binaryExpression,
stringLiteral
} from '@babel/types';

const handleSpreadClassName = (
path: *,
destinationName: string,
classNamesFromSpread: Expression
) => {
const destinationAttribute = path.node.openingElement.attributes
.find((attribute) => {
return typeof attribute.name !== 'undefined' && attribute.name.name === destinationName;
});

if (!destinationAttribute) {
return;
}

if (isStringLiteral(destinationAttribute.value)) {
destinationAttribute.value = jsxExpressionContainer(
binaryExpression(
'+',
destinationAttribute.value,
binaryExpression(
'+',
stringLiteral(' '),
classNamesFromSpread,
)
)
);
} else if (isJSXExpressionContainer(destinationAttribute.value)) {
destinationAttribute.value = jsxExpressionContainer(
binaryExpression(
'+',
destinationAttribute.value.expression,
binaryExpression(
'+',
stringLiteral(' '),
classNamesFromSpread
)
)
);
}
};

export default handleSpreadClassName;
12 changes: 12 additions & 0 deletions src/index.js
Expand Up @@ -15,6 +15,8 @@ import requireCssModule from './requireCssModule';
import resolveStringLiteral from './resolveStringLiteral';
import replaceJsxExpressionContainer from './replaceJsxExpressionContainer';
import attributeNameExists from './attributeNameExists';
import createSpreadMapper from './createSpreadMapper';
import handleSpreadClassName from './handleSpreadClassName';

const ajv = new Ajv({
// eslint-disable-next-line id-match
Expand Down Expand Up @@ -216,6 +218,8 @@ export default ({
autoResolveMultipleImports = optionsDefaults.autoResolveMultipleImports
} = stats.opts || {};

const spreadMap = createSpreadMapper(path, stats);

for (const attribute of attributes) {
const destinationName = attributeNames[attribute.name.name];

Expand Down Expand Up @@ -246,6 +250,14 @@ export default ({
options
);
}

if (spreadMap[destinationName]) {
handleSpreadClassName(
path,
destinationName,
spreadMap[destinationName]
);
}
}
},
Program (path: *, stats: *): void {
Expand Down
Expand Up @@ -5,4 +5,4 @@ require("./bar.css");
const props = {
foo: 'bar'
};
<div className="bar__a" {...props}></div>;
<div className={"bar__a" + (" " + (props.className || ""))} {...props}></div>;
@@ -0,0 +1 @@
.a {}
22 changes: 22 additions & 0 deletions test/fixtures/react-css-modules/handle spread attributes/input.js
@@ -0,0 +1,22 @@
import './foo.css';

const rest = {};

<div {...rest} styleName="a" className="b"></div>;

<div {...rest} styleName="a"></div>;

<div {...rest} activeClassName={this.props.activeClassName} activeStyleName="a" styleName="a"></div>;

<div {...rest} activeStyleName="a" activeClassName="b"></div>;

// Should be okay if rest is put on last
<div styleName="a" {...rest}></div>;

const rest2 = {};

<div {...rest} {...rest2} styleName="a"></div>;

// Should not do anything
<div {...rest} {...rest2}></div>;
<div {...rest} {...rest2} className="b"></div>;
@@ -0,0 +1,13 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]",
"attributeNames": {
"activeStyleName": "activeClassName"
}
}
]
]
}
16 changes: 16 additions & 0 deletions test/fixtures/react-css-modules/handle spread attributes/output.js
@@ -0,0 +1,16 @@
"use strict";

require("./foo.css");

const rest = {};
<div {...rest} className={"b foo__a" + (" " + (rest.className || ""))}></div>;
<div {...rest} className={"foo__a" + (" " + (rest.className || ""))}></div>;
<div {...rest} activeClassName={((void 0).props.activeClassName ? (void 0).props.activeClassName + " " : "") + "foo__a" + (" " + (rest.activeClassName || ""))} className={"foo__a" + (" " + (rest.className || ""))}></div>;
<div {...rest} activeClassName={"b foo__a" + (" " + (rest.activeClassName || ""))}></div>; // Should be okay if rest is put on last

<div className={"foo__a" + (" " + (rest.className || ""))} {...rest}></div>;
const rest2 = {};
<div {...rest} {...rest2} className={"foo__a" + (" " + ((rest.className || "") + (" " + (rest2.className || ""))))}></div>; // Should not do anything

<div {...rest} {...rest2}></div>;
<div {...rest} {...rest2} className="b"></div>;

0 comments on commit 05c2683

Please sign in to comment.