From 77c71c5e34ac33f88f038e44a687b0289058c067 Mon Sep 17 00:00:00 2001 From: Cameron Moon Date: Tue, 3 Jun 2025 11:56:34 +1000 Subject: [PATCH 1/4] Fix bug with transitive values containing spaces --- index.js | 21 +++++++-------------- index.test.js | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index b236de3..16d079e 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,15 @@ const replaceValueSymbols = (valueString, replacements) => { }; const getDefinition = (atRule, existingDefinitions, requiredDefinitions) => { - let matches; - const definition = {}; + const [/* match */, name, middle, value, end] = matchValueDefinition.exec(atRule.params); + const valueWithReplacements = replaceValueSymbols(value, existingDefinitions); - // 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); - - 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) => { diff --git a/index.test.js b/index.test.js index 74b5204..9e1ab6a 100644 --- a/index.test.js +++ b/index.test.js @@ -255,6 +255,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 +310,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%); }', ); }); From 64c7795fd64f25e4e8e7064b1a73cbf922fad72b Mon Sep 17 00:00:00 2001 From: Eugene Datsky Date: Wed, 4 Jun 2025 12:39:47 +1000 Subject: [PATCH 2/4] 4.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8249921..2833c5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-modules-values-replace", - "version": "4.2.0", + "version": "4.2.1", "description": "PostCSS plugin to work around CSS Modules values limitations", "keywords": [ "postcss", From cac20c0ca5364117cb1e88d39a2a9e83f0e1aae3 Mon Sep 17 00:00:00 2001 From: Cameron Moon Date: Thu, 5 Jun 2025 17:13:04 +1000 Subject: [PATCH 3/4] Warn on invalid `@value` definitions (#57) --- index.js | 10 +++++++++- index.test.js | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 16d079e..8c80a2b 100644 --- a/index.js +++ b/index.js @@ -38,7 +38,12 @@ const replaceValueSymbols = (valueString, replacements) => { }; const getDefinition = (atRule, existingDefinitions, requiredDefinitions) => { - const [/* match */, name, middle, value, end] = matchValueDefinition.exec(atRule.params); + const matches = matchValueDefinition.exec(atRule.params); + if (!matches) { + return null; + } + + const [/* match */, name, middle, value, end] = matches; const valueWithReplacements = replaceValueSymbols(value, existingDefinitions); if (!requiredDefinitions) { @@ -110,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 9e1ab6a..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!'); From 887d995f5db82e3a9d586d40ec12452f96595c6e Mon Sep 17 00:00:00 2001 From: Eugene Datsky Date: Thu, 5 Jun 2025 17:13:58 +1000 Subject: [PATCH 4/4] 4.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2833c5c..74c4120 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-modules-values-replace", - "version": "4.2.1", + "version": "4.2.2", "description": "PostCSS plugin to work around CSS Modules values limitations", "keywords": [ "postcss",