Skip to content

Commit 05c2683

Browse files
AlbertLuciantogajus
authored andcommitted
fix: handle spread (#243)
* 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
1 parent 70550bc commit 05c2683

File tree

8 files changed

+191
-1
lines changed

8 files changed

+191
-1
lines changed

src/createSpreadMapper.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// @flow
2+
3+
import {
4+
Expression,
5+
memberExpression,
6+
binaryExpression,
7+
stringLiteral,
8+
logicalExpression,
9+
identifier
10+
} from '@babel/types';
11+
import optionsDefaults from './schemas/optionsDefaults';
12+
13+
const createSpreadMapper = (path: *, stats: *): { [destinationName: string]: Expression } => {
14+
const result = {};
15+
16+
let {attributeNames} = optionsDefaults;
17+
18+
if (stats.opts && stats.opts.attributeNames) {
19+
attributeNames = Object.assign({}, attributeNames, stats.opts.attributeNames);
20+
}
21+
22+
const attributes = Object
23+
.entries(attributeNames)
24+
.filter((pair) => {
25+
return pair[1];
26+
});
27+
28+
const attributeKeys = attributes.map((pair) => {
29+
return pair[0];
30+
});
31+
32+
path.traverse({
33+
JSXSpreadAttribute (spreadPath: *) {
34+
const spread = spreadPath.node;
35+
36+
for (const attributeKey of attributeKeys) {
37+
const destinationName = attributeNames[attributeKey];
38+
39+
if (result[destinationName]) {
40+
result[destinationName] = binaryExpression(
41+
'+',
42+
result[destinationName],
43+
binaryExpression(
44+
'+',
45+
stringLiteral(' '),
46+
logicalExpression(
47+
'||',
48+
memberExpression(
49+
spread.argument,
50+
identifier(destinationName),
51+
),
52+
stringLiteral('')
53+
)
54+
),
55+
);
56+
} else {
57+
result[destinationName] = logicalExpression(
58+
'||',
59+
memberExpression(
60+
spread.argument,
61+
identifier(destinationName),
62+
),
63+
stringLiteral('')
64+
);
65+
}
66+
}
67+
}
68+
});
69+
70+
return result;
71+
};
72+
73+
export default createSpreadMapper;

src/handleSpreadClassName.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// @flow
2+
3+
import {
4+
Expression,
5+
isStringLiteral,
6+
isJSXExpressionContainer,
7+
jsxExpressionContainer,
8+
binaryExpression,
9+
stringLiteral
10+
} from '@babel/types';
11+
12+
const handleSpreadClassName = (
13+
path: *,
14+
destinationName: string,
15+
classNamesFromSpread: Expression
16+
) => {
17+
const destinationAttribute = path.node.openingElement.attributes
18+
.find((attribute) => {
19+
return typeof attribute.name !== 'undefined' && attribute.name.name === destinationName;
20+
});
21+
22+
if (!destinationAttribute) {
23+
return;
24+
}
25+
26+
if (isStringLiteral(destinationAttribute.value)) {
27+
destinationAttribute.value = jsxExpressionContainer(
28+
binaryExpression(
29+
'+',
30+
destinationAttribute.value,
31+
binaryExpression(
32+
'+',
33+
stringLiteral(' '),
34+
classNamesFromSpread,
35+
)
36+
)
37+
);
38+
} else if (isJSXExpressionContainer(destinationAttribute.value)) {
39+
destinationAttribute.value = jsxExpressionContainer(
40+
binaryExpression(
41+
'+',
42+
destinationAttribute.value.expression,
43+
binaryExpression(
44+
'+',
45+
stringLiteral(' '),
46+
classNamesFromSpread
47+
)
48+
)
49+
);
50+
}
51+
};
52+
53+
export default handleSpreadClassName;

src/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import requireCssModule from './requireCssModule';
1515
import resolveStringLiteral from './resolveStringLiteral';
1616
import replaceJsxExpressionContainer from './replaceJsxExpressionContainer';
1717
import attributeNameExists from './attributeNameExists';
18+
import createSpreadMapper from './createSpreadMapper';
19+
import handleSpreadClassName from './handleSpreadClassName';
1820

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

221+
const spreadMap = createSpreadMapper(path, stats);
222+
219223
for (const attribute of attributes) {
220224
const destinationName = attributeNames[attribute.name.name];
221225

@@ -246,6 +250,14 @@ export default ({
246250
options
247251
);
248252
}
253+
254+
if (spreadMap[destinationName]) {
255+
handleSpreadClassName(
256+
path,
257+
destinationName,
258+
spreadMap[destinationName]
259+
);
260+
}
249261
}
250262
},
251263
Program (path: *, stats: *): void {

test/fixtures/react-css-modules/does not throw error if attribute has no name property/output.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ require("./bar.css");
55
const props = {
66
foo: 'bar'
77
};
8-
<div className="bar__a" {...props}></div>;
8+
<div className={"bar__a" + (" " + (props.className || ""))} {...props}></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.a {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import './foo.css';
2+
3+
const rest = {};
4+
5+
<div {...rest} styleName="a" className="b"></div>;
6+
7+
<div {...rest} styleName="a"></div>;
8+
9+
<div {...rest} activeClassName={this.props.activeClassName} activeStyleName="a" styleName="a"></div>;
10+
11+
<div {...rest} activeStyleName="a" activeClassName="b"></div>;
12+
13+
// Should be okay if rest is put on last
14+
<div styleName="a" {...rest}></div>;
15+
16+
const rest2 = {};
17+
18+
<div {...rest} {...rest2} styleName="a"></div>;
19+
20+
// Should not do anything
21+
<div {...rest} {...rest2}></div>;
22+
<div {...rest} {...rest2} className="b"></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"plugins": [
3+
[
4+
"../../../../src",
5+
{
6+
"generateScopedName": "[name]__[local]",
7+
"attributeNames": {
8+
"activeStyleName": "activeClassName"
9+
}
10+
}
11+
]
12+
]
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use strict";
2+
3+
require("./foo.css");
4+
5+
const rest = {};
6+
<div {...rest} className={"b foo__a" + (" " + (rest.className || ""))}></div>;
7+
<div {...rest} className={"foo__a" + (" " + (rest.className || ""))}></div>;
8+
<div {...rest} activeClassName={((void 0).props.activeClassName ? (void 0).props.activeClassName + " " : "") + "foo__a" + (" " + (rest.activeClassName || ""))} className={"foo__a" + (" " + (rest.className || ""))}></div>;
9+
<div {...rest} activeClassName={"b foo__a" + (" " + (rest.activeClassName || ""))}></div>; // Should be okay if rest is put on last
10+
11+
<div className={"foo__a" + (" " + (rest.className || ""))} {...rest}></div>;
12+
const rest2 = {};
13+
<div {...rest} {...rest2} className={"foo__a" + (" " + ((rest.className || "") + (" " + (rest2.className || ""))))}></div>; // Should not do anything
14+
15+
<div {...rest} {...rest2}></div>;
16+
<div {...rest} {...rest2} className="b"></div>;

0 commit comments

Comments
 (0)