Skip to content

Commit b092024

Browse files
authored
Merge branch 'main' into fix-no-unknown-false-positives-for-latest-specs-by-integrating-csstools-css-syntax-patches-for-csstree--adaptable-jaguar-e2fb11b478
2 parents afa30d3 + 4b9c68b commit b092024

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

.changeset/rich-ties-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"stylelint": patch
3+
---
4+
5+
Fixed: `function-url-quotes` false positives when URLs require quoting

lib/rules/function-url-quotes/__tests__/index.mjs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,46 @@ testRule({
433433
{
434434
code: '@font-face { font-family: \'foo\'; src: url($variable + "foo.ttf" + $variable); }',
435435
},
436+
{
437+
code: 'a { background: url("data:image/svg+xml;utf8,<svg height=\\"100\\" width=\\"100\\"></svg>"); }',
438+
description: 'data URI with embedded quotes requires quotes (#8544)',
439+
},
440+
{
441+
code: 'a { background: url(\'data:image/svg+xml;utf8,<svg height="100" width="100"></svg>\'); }',
442+
description: 'data URI with embedded quotes requires quotes (#8544)',
443+
},
444+
{
445+
code: 'a { background-image: url(\'data:image/svg+xml;utf8,<svg height="100" width="100"><style>.cls-1{}</style></svg>\'); }',
446+
description: 'data URI with embedded quotes requires quotes (#8544)',
447+
},
448+
{
449+
code: 'a { background-image: url(\'data:image/svg+xml,<svg height="100" width="100"><style>.cls-1{fill-rule:evenodd;}</style></svg>\'); }',
450+
description: 'data URI with embedded quotes requires quotes (#8544)',
451+
},
452+
{
453+
code: 'a { background: url("image file.png"); }',
454+
description: 'URL with spaces requires quotes (#8544)',
455+
},
456+
{
457+
code: "a { background: url('image file.png'); }",
458+
description: 'URL with spaces requires quotes (#8544)',
459+
},
460+
{
461+
code: 'a { background: url("foo(bar).png"); }',
462+
description: 'URL with parentheses requires quotes (#8544)',
463+
},
464+
{
465+
code: "a { background: url('foo(bar).png'); }",
466+
description: 'URL with parentheses requires quotes (#8544)',
467+
},
468+
{
469+
code: 'a { background: url("foo)bogus"); }',
470+
description: 'URL with mismatched parentheses requires quotes (#8544)',
471+
},
472+
{
473+
code: 'a { background: url("\'foo\'"); }',
474+
description: 'URL with parentheses requires quotes (#8544)',
475+
},
436476
],
437477

438478
reject: [
@@ -763,6 +803,20 @@ testRule({
763803
},
764804
],
765805
},
806+
{
807+
code: `a { background-image: url( "data:image/svg+xml,<svg\\ viewBox=\\"0\\ 0\\ 10\\ 10\\"></svg>" ); }`,
808+
fixed:
809+
'a { background-image: url( data:image/svg+xml,<svg\\ viewBox=\\"0\\ 0\\ 10\\ 10\\"></svg> ); }',
810+
fix: {
811+
range: [27, 85],
812+
text: 'data:image/svg+xml,<svg\\ viewBox=\\"0\\ 0\\ 10\\ 10\\"></svg>',
813+
},
814+
message: messages.rejected('url'),
815+
line: 1,
816+
column: 28,
817+
endLine: 1,
818+
endColumn: 87,
819+
},
766820
],
767821
});
768822

lib/rules/function-url-quotes/index.cjs

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/rules/function-url-quotes/index.mjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isTokenURL, tokenize } from '@csstools/css-tokenizer';
2+
13
import { atRuleParamIndex, declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
24
import functionArgumentsSearch from '../../utils/functionArgumentsSearch.mjs';
35
import getAtRuleParams from '../../utils/getAtRuleParams.mjs';
@@ -152,6 +154,39 @@ const rule = (primary, secondaryOptions) => {
152154
}
153155
}
154156

157+
/**
158+
* Check if a URL requires quotes for valid CSS
159+
*
160+
* This function determines if a URL needs to be quoted when used in a CSS url() function.
161+
* It tokenizes the URL and checks if it would be parsed as a bad-url token, which indicates
162+
* that quotes are necessary for valid CSS.
163+
*
164+
* @param {string} url - The URL value, either quoted or unquoted
165+
* @returns {boolean} True if the URL requires quotes to be valid CSS, false otherwise
166+
*/
167+
function requiresQuotes(url) {
168+
// Extract the URL content (remove quotes if present)
169+
let urlContent = url;
170+
171+
const quote = url[0] ?? '';
172+
const endQuoteIndex = url.lastIndexOf(quote);
173+
174+
if (endQuoteIndex > 0) {
175+
urlContent = url.slice(1, endQuoteIndex);
176+
}
177+
178+
const tokens = tokenize({ css: `url(${urlContent})` });
179+
180+
// there is always an EOF token, so we expect 2 tokens for a valid URL
181+
if (tokens.length !== 2) return true;
182+
183+
const urlToken = tokens[0];
184+
185+
if (!isTokenURL(urlToken)) return true;
186+
187+
return false;
188+
}
189+
155190
/**
156191
* @param {string} args
157192
* @param {number} index
@@ -170,6 +205,9 @@ const rule = (primary, secondaryOptions) => {
170205

171206
const hasQuotes = leftTrimmedArgs.startsWith("'") || leftTrimmedArgs.startsWith('"');
172207

208+
// If quotes are required, don't complain about them being present
209+
if (hasQuotes && requiresQuotes(leftTrimmedArgs)) return;
210+
173211
if (expectQuotes && hasQuotes) return;
174212

175213
if (!expectQuotes && !hasQuotes) return;

0 commit comments

Comments
 (0)