diff --git a/index.js b/index.js index b236de3..8c80a2b 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ const { urlToRequest } = require('loader-utils'); const ICSSUtils = require('icss-utils'); const matchImports = /^(.+?|\([\s\S]+?\))\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/; -const matchValueDefinition = /(?:\s+|^)([\w-]+)(:?\s+)(.+?)(\s*)$/g; +const matchValueDefinition = /(?:\s+|^)([\w-]+)(:?\s+)(.+?)(\s*)$/; const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/; const matchPath = /"[^"]*"|'[^']*'/; @@ -38,22 +38,20 @@ const replaceValueSymbols = (valueString, replacements) => { }; const getDefinition = (atRule, existingDefinitions, requiredDefinitions) => { - let matches; - const definition = {}; + const matches = matchValueDefinition.exec(atRule.params); + if (!matches) { + return null; + } - // eslint-disable-next-line no-cond-assign - while (matches = matchValueDefinition.exec(atRule.params)) { - const [/* match */, requiredName, middle, value, end] = matches; - // Add to the definitions, knowing that values can refer to each other - definition[requiredName] = replaceValueSymbols(value, existingDefinitions); + const [/* match */, name, middle, value, end] = matches; + const valueWithReplacements = replaceValueSymbols(value, existingDefinitions); - if (!requiredDefinitions) { - // eslint-disable-next-line no-param-reassign - atRule.params = requiredName + middle + definition[requiredName] + end; - } + if (!requiredDefinitions) { + // eslint-disable-next-line no-param-reassign + atRule.params = name + middle + valueWithReplacements + end; } - return definition; + return { [name]: valueWithReplacements }; }; const getImports = (aliases) => { @@ -117,6 +115,9 @@ const walk = async (requiredDefinitions, walkFile, root, result) => { } const newDefinitions = getDefinition(atRule, existingDefinitions, requiredDefinitions); + if (!newDefinitions) { + result.warn(`Invalid value definition: ${atRule.params}`); + } return Object.assign(existingDefinitions, newDefinitions); }; diff --git a/index.test.js b/index.test.js index 74b5204..579766a 100644 --- a/index.test.js +++ b/index.test.js @@ -51,7 +51,7 @@ test('should remove exports if noEmitExports is true', async (t) => { await run(t, '@value red blue;', '', { noEmitExports: true }); }); -test('gives an error when there is no semicolon between lines', async (t) => { +test('gives a warning when there is no semicolon between lines', async (t) => { const input = '@value red blue\n@value green yellow'; const processor = postcss([plugin]); const result = await processor.process(input, { from: undefined }); @@ -61,13 +61,23 @@ test('gives an error when there is no semicolon between lines', async (t) => { t.expect(warnings[0].text).toBe('Invalid value definition: red blue\n@value green yellow'); }); +test('gives a warning when @value definition is invalid', async (t) => { + const input = '@value oops:;'; + const processor = postcss([plugin]); + const result = await processor.process(input, { from: undefined }); + const warnings = result.warnings(); + + t.expect(warnings.length).toBe(1); + t.expect(warnings[0].text).toBe('Invalid value definition: oops:'); +}); + test('gives an error when path to imported file is wrong', async (t) => { const input = '@value red from "./non-existent-file.css"'; const processor = postcss([plugin]); await t.expect(processor.process(input, parserOpts)).rejects.toThrow("Can't resolve './non-existent-file.css'"); }); -test('gives an error when @value statement is invalid', async (t) => { +test('gives an error when @value import statement is invalid', async (t) => { const input = '@value , from "./colors.css"'; const processor = postcss([plugin]); await t.expect(processor.process(input, parserOpts)).rejects.toThrow('@value statement "" is invalid!'); @@ -255,6 +265,16 @@ test('should allow transitive values within calc without spaces', async (t) => { ); }); +test('should allow transitive values containing spaces, like CSS relative colors', async (t) => { + await run( + t, + '@value mixedColor: color-mix(in hsl, hsl(200 50 80), coral 80%);\n' + + '@value mixedColorA90: hsl(from mixedColor h s l / 90%);', + '@value mixedColor: color-mix(in hsl, hsl(200 50 80), coral 80%);\n' + + '@value mixedColorA90: hsl(from color-mix(in hsl, hsl(200 50 80), coral 80%) h s l / 90%);', + ); +}); + test('should replace inside custom properties', async (t) => { await run( t, @@ -300,10 +320,10 @@ test('should allow custom-property-style names', async (t) => { test('should allow all colour types', async (t) => { await run( t, - '@value named: red; @value hex3char #0f0; @value hex6char #00ff00; @value rgba rgba(34, 12, 64, 0.3); @value hsla hsla(220, 13.0%, 18.0%, 1);\n' - + '.foo { color: named; background-color: hex3char; border-top-color: hex6char; border-bottom-color: rgba; outline-color: hsla; }', - '@value named: red; @value hex3char #0f0; @value hex6char #00ff00; @value rgba rgba(34, 12, 64, 0.3); @value hsla hsla(220, 13.0%, 18.0%, 1);\n' - + '.foo { color: red; background-color: #0f0; border-top-color: #00ff00; border-bottom-color: rgba(34, 12, 64, 0.3); outline-color: hsla(220, 13.0%, 18.0%, 1); }', + '@value named: red; @value hex3char #0f0; @value hex6char #00ff00; @value rgba rgba(34, 12, 64, 0.3); @value hsla hsla(220, 13.0%, 18.0%, 1); @value hsl hsl(264 100% 62% / 70%);\n' + + '.foo { color: named; background-color: hex3char; border-top-color: hex6char; border-bottom-color: rgba; outline-color: hsla; border-left-color: hsl; }', + '@value named: red; @value hex3char #0f0; @value hex6char #00ff00; @value rgba rgba(34, 12, 64, 0.3); @value hsla hsla(220, 13.0%, 18.0%, 1); @value hsl hsl(264 100% 62% / 70%);\n' + + '.foo { color: red; background-color: #0f0; border-top-color: #00ff00; border-bottom-color: rgba(34, 12, 64, 0.3); outline-color: hsla(220, 13.0%, 18.0%, 1); border-left-color: hsl(264 100% 62% / 70%); }', ); }); diff --git a/package.json b/package.json index 8249921..74c4120 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-modules-values-replace", - "version": "4.2.0", + "version": "4.2.2", "description": "PostCSS plugin to work around CSS Modules values limitations", "keywords": [ "postcss",