From 05c268317e97046a915c94ff28d8ab1a63ff77cb Mon Sep 17 00:00:00 2001 From: Albert Lucianto Date: Thu, 21 Mar 2019 19:27:00 +0800 Subject: [PATCH] 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 --- src/createSpreadMapper.js | 73 +++++++++++++++++++ src/handleSpreadClassName.js | 53 ++++++++++++++ src/index.js | 12 +++ .../output.js | 2 +- .../handle spread attributes/foo.css | 1 + .../handle spread attributes/input.js | 22 ++++++ .../handle spread attributes/options.json | 13 ++++ .../handle spread attributes/output.js | 16 ++++ 8 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/createSpreadMapper.js create mode 100644 src/handleSpreadClassName.js create mode 100644 test/fixtures/react-css-modules/handle spread attributes/foo.css create mode 100644 test/fixtures/react-css-modules/handle spread attributes/input.js create mode 100644 test/fixtures/react-css-modules/handle spread attributes/options.json create mode 100644 test/fixtures/react-css-modules/handle spread attributes/output.js diff --git a/src/createSpreadMapper.js b/src/createSpreadMapper.js new file mode 100644 index 0000000..7b99cf5 --- /dev/null +++ b/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; diff --git a/src/handleSpreadClassName.js b/src/handleSpreadClassName.js new file mode 100644 index 0000000..62ea482 --- /dev/null +++ b/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; diff --git a/src/index.js b/src/index.js index 5c728f7..305d941 100644 --- a/src/index.js +++ b/src/index.js @@ -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 @@ -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]; @@ -246,6 +250,14 @@ export default ({ options ); } + + if (spreadMap[destinationName]) { + handleSpreadClassName( + path, + destinationName, + spreadMap[destinationName] + ); + } } }, Program (path: *, stats: *): void { diff --git a/test/fixtures/react-css-modules/does not throw error if attribute has no name property/output.js b/test/fixtures/react-css-modules/does not throw error if attribute has no name property/output.js index 7613724..c2084e1 100644 --- a/test/fixtures/react-css-modules/does not throw error if attribute has no name property/output.js +++ b/test/fixtures/react-css-modules/does not throw error if attribute has no name property/output.js @@ -5,4 +5,4 @@ require("./bar.css"); const props = { foo: 'bar' }; -
; +
; diff --git a/test/fixtures/react-css-modules/handle spread attributes/foo.css b/test/fixtures/react-css-modules/handle spread attributes/foo.css new file mode 100644 index 0000000..40ebb53 --- /dev/null +++ b/test/fixtures/react-css-modules/handle spread attributes/foo.css @@ -0,0 +1 @@ +.a {} \ No newline at end of file diff --git a/test/fixtures/react-css-modules/handle spread attributes/input.js b/test/fixtures/react-css-modules/handle spread attributes/input.js new file mode 100644 index 0000000..efb4b6d --- /dev/null +++ b/test/fixtures/react-css-modules/handle spread attributes/input.js @@ -0,0 +1,22 @@ +import './foo.css'; + +const rest = {}; + +
; + +
; + +
; + +
; + +// Should be okay if rest is put on last +
; + +const rest2 = {}; + +
; + +// Should not do anything +
; +
; diff --git a/test/fixtures/react-css-modules/handle spread attributes/options.json b/test/fixtures/react-css-modules/handle spread attributes/options.json new file mode 100644 index 0000000..93a418e --- /dev/null +++ b/test/fixtures/react-css-modules/handle spread attributes/options.json @@ -0,0 +1,13 @@ +{ + "plugins": [ + [ + "../../../../src", + { + "generateScopedName": "[name]__[local]", + "attributeNames": { + "activeStyleName": "activeClassName" + } + } + ] + ] +} diff --git a/test/fixtures/react-css-modules/handle spread attributes/output.js b/test/fixtures/react-css-modules/handle spread attributes/output.js new file mode 100644 index 0000000..49609e0 --- /dev/null +++ b/test/fixtures/react-css-modules/handle spread attributes/output.js @@ -0,0 +1,16 @@ +"use strict"; + +require("./foo.css"); + +const rest = {}; +
; +
; +
; +
; // Should be okay if rest is put on last + +
; +const rest2 = {}; +
; // Should not do anything + +
; +
;