From 5e69dc3d7513675dc9a3957468bdbc27fc09e6e7 Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Sun, 8 Jan 2023 20:45:05 +0100 Subject: [PATCH 1/3] Fix `function-no-unknown` false positives for interpolation and backticks in CSS-in-JS. Fixes #6564 --- .../function-no-unknown/__tests__/index.js | 16 +++++++++++++ .../isStandardSyntaxFunction.test.js | 24 +++++++++++++++++++ lib/utils/isStandardSyntaxFunction.js | 10 ++++++++ 3 files changed, 50 insertions(+) diff --git a/lib/rules/function-no-unknown/__tests__/index.js b/lib/rules/function-no-unknown/__tests__/index.js index 4e62855a47..a04a472f97 100644 --- a/lib/rules/function-no-unknown/__tests__/index.js +++ b/lib/rules/function-no-unknown/__tests__/index.js @@ -70,6 +70,22 @@ testRule({ ], }); +testRule({ + skip: true, + ruleName, + config: true, + customSyntax: 'postcss-styled-syntax', + + accept: [ + { + code: 'const buttonFontSize = css`font-size: ${({ size }) => (size === "small") ? "0.8em" : "1em"};`;', + }, + { + code: 'const buttonFontSize = css`border-radius: ${`calc(${token.radiusBase} + 2px)`};`;', + }, + ], +}); + testRule({ ruleName, config: [true, { ignoreFunctions: ['theme', '/^foo-/', /^bar$/i] }], diff --git a/lib/utils/__tests__/isStandardSyntaxFunction.test.js b/lib/utils/__tests__/isStandardSyntaxFunction.test.js index 9f9c72c7e9..1b8b84b53d 100644 --- a/lib/utils/__tests__/isStandardSyntaxFunction.test.js +++ b/lib/utils/__tests__/isStandardSyntaxFunction.test.js @@ -26,6 +26,30 @@ describe('isStandardSyntaxFunction', () => { false, ); }); + + it('CSS-in-JS interpolation', () => { + const functions = []; + + valueParser('${({ size }) => (size === "small") ? "0.8em" : "1em"}').walk((valueNode) => { + if (valueNode.type === 'function') { + functions.push(valueNode); + } + }); + + expect(isStandardSyntaxFunction(functions[0])).toBe(false); + }); + + it('CSS-in-JS syntax', () => { + const functions = []; + + valueParser('`calc(${token.radiusBase} + 2px)`').walk((valueNode) => { + if (valueNode.type === 'function') { + functions.push(valueNode); + } + }); + + expect(isStandardSyntaxFunction(functions[0])).toBe(false); + }); }); function func(css) { diff --git a/lib/utils/isStandardSyntaxFunction.js b/lib/utils/isStandardSyntaxFunction.js index d2f465fae1..5c1e29adf3 100644 --- a/lib/utils/isStandardSyntaxFunction.js +++ b/lib/utils/isStandardSyntaxFunction.js @@ -16,5 +16,15 @@ module.exports = function isStandardSyntaxFunction(node) { return false; } + // CSS-in-JS interpolation + if (node.value.startsWith('${')) { + return false; + } + + // CSS-in-JS syntax + if (node.value.startsWith('`')) { + return false; + } + return true; }; From 26861456734f9fb05beabcbcae89fd031ec0dced Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Sun, 8 Jan 2023 20:49:41 +0100 Subject: [PATCH 2/3] Add changelog --- .changeset/quiet-adults-leave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quiet-adults-leave.md diff --git a/.changeset/quiet-adults-leave.md b/.changeset/quiet-adults-leave.md new file mode 100644 index 0000000000..879d308801 --- /dev/null +++ b/.changeset/quiet-adults-leave.md @@ -0,0 +1,5 @@ +--- +"stylelint": patch +--- + +Fixed: `function-no-unknown` false positives for interpolation and backticks in CSS-in-JS From 221dbaa07db421f3fd710063f3fcc296d3cf01f4 Mon Sep 17 00:00:00 2001 From: Aleks Hudochenkov Date: Mon, 9 Jan 2023 19:09:22 +0100 Subject: [PATCH 3/3] Address review comments --- .../function-no-unknown/__tests__/index.js | 16 ------ .../isStandardSyntaxFunction.test.js | 53 +++++++------------ 2 files changed, 19 insertions(+), 50 deletions(-) diff --git a/lib/rules/function-no-unknown/__tests__/index.js b/lib/rules/function-no-unknown/__tests__/index.js index a04a472f97..4e62855a47 100644 --- a/lib/rules/function-no-unknown/__tests__/index.js +++ b/lib/rules/function-no-unknown/__tests__/index.js @@ -70,22 +70,6 @@ testRule({ ], }); -testRule({ - skip: true, - ruleName, - config: true, - customSyntax: 'postcss-styled-syntax', - - accept: [ - { - code: 'const buttonFontSize = css`font-size: ${({ size }) => (size === "small") ? "0.8em" : "1em"};`;', - }, - { - code: 'const buttonFontSize = css`border-radius: ${`calc(${token.radiusBase} + 2px)`};`;', - }, - ], -}); - testRule({ ruleName, config: [true, { ignoreFunctions: ['theme', '/^foo-/', /^bar$/i] }], diff --git a/lib/utils/__tests__/isStandardSyntaxFunction.test.js b/lib/utils/__tests__/isStandardSyntaxFunction.test.js index 1b8b84b53d..2ba1f9c084 100644 --- a/lib/utils/__tests__/isStandardSyntaxFunction.test.js +++ b/lib/utils/__tests__/isStandardSyntaxFunction.test.js @@ -1,66 +1,51 @@ 'use strict'; const isStandardSyntaxFunction = require('../isStandardSyntaxFunction'); -const postcss = require('postcss'); const valueParser = require('postcss-value-parser'); describe('isStandardSyntaxFunction', () => { it('calc', () => { - expect(isStandardSyntaxFunction(func('a { prop: calc(a + b) }'))).toBe(true); + expect(isStandardSyntaxFunction(getFunction('calc(a + b)'))).toBe(true); }); it('url', () => { - expect(isStandardSyntaxFunction(func("a { prop: url('x.css') }"))).toBe(true); + expect(isStandardSyntaxFunction(getFunction("url('x.css')"))).toBe(true); }); it('scss list', () => { - expect(isStandardSyntaxFunction(func('a { $list: (list) }'))).toBe(false); + // as in $list: (list) + expect(isStandardSyntaxFunction(getFunction('(list)'))).toBe(false); }); it('scss map', () => { - expect(isStandardSyntaxFunction(func('a { $map: (key: value) }'))).toBe(false); + // as in $map: (key: value) + expect(isStandardSyntaxFunction(getFunction('(key: value)'))).toBe(false); }); - it('scss function in custom prop', () => { - expect(isStandardSyntaxFunction(func('a { --primary-color: #{darken(#fff, 0.2)} }'))).toBe( - false, - ); + it('scss function in scss interpolation', () => { + expect(isStandardSyntaxFunction(getFunction('#{darken(#fff, 0.2)}'))).toBe(false); }); it('CSS-in-JS interpolation', () => { - const functions = []; - - valueParser('${({ size }) => (size === "small") ? "0.8em" : "1em"}').walk((valueNode) => { - if (valueNode.type === 'function') { - functions.push(valueNode); - } - }); - - expect(isStandardSyntaxFunction(functions[0])).toBe(false); + expect( + isStandardSyntaxFunction( + getFunction('${({ size }) => (size === "small") ? "0.8em" : "1em"}'), + ), + ).toBe(false); }); it('CSS-in-JS syntax', () => { - const functions = []; - - valueParser('`calc(${token.radiusBase} + 2px)`').walk((valueNode) => { - if (valueNode.type === 'function') { - functions.push(valueNode); - } - }); - - expect(isStandardSyntaxFunction(functions[0])).toBe(false); + expect(isStandardSyntaxFunction(getFunction('`calc(${token.radiusBase} + 2px)`'))).toBe(false); }); }); -function func(css) { +function getFunction(declValue) { const functions = []; - postcss.parse(css).walkDecls((decl) => { - valueParser(decl.value).walk((valueNode) => { - if (valueNode.type === 'function') { - functions.push(valueNode); - } - }); + valueParser(declValue).walk((valueNode) => { + if (valueNode.type === 'function') { + functions.push(valueNode); + } }); return functions[0];