From 46115348f7bc192b8bc0e2db4ed04764fa663487 Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Wed, 4 Jan 2023 22:12:14 -0800 Subject: [PATCH 1/9] draft: failing / poor approach --- .../function-url-quotes/__tests__/index.js | 68 +++++++++++++++++++ lib/rules/function-url-quotes/index.js | 61 ++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/lib/rules/function-url-quotes/__tests__/index.js b/lib/rules/function-url-quotes/__tests__/index.js index aa6393996a..f8efb203fd 100644 --- a/lib/rules/function-url-quotes/__tests__/index.js +++ b/lib/rules/function-url-quotes/__tests__/index.js @@ -710,6 +710,74 @@ testRule({ ], }); +testRule({ + ruleName, + config: ['always'], + fix: true, + + reject: [ + { + code: '@import url(foo.css);', + fixed: '@import url("foo.css");', + message: messages.expected('url'), + }, + { + code: 'a { background: url(foo.css); }', + fixed: 'a { background: url("foo.css"); }', + message: messages.expected('url'), + }, + ], +}); + +testRule({ + ruleName, + config: ['never'], + fix: true, + + reject: [ + { + code: '@import url("foo.css");', + fixed: '@import url(foo.css);', + message: messages.rejected('url'), + }, + { + code: '@import url( "foo.css" );', + fixed: '@import url( foo.css );', + message: messages.rejected('url'), + }, + { + code: "@import url('foo.css');", + fixed: '@import url(foo.css);', + message: messages.rejected('url'), + }, + { + code: "@import url( 'foo.css' );", + fixed: '@import url( foo.css );', + message: messages.rejected('url'), + }, + // { + // code: '@document url("http://www.w3.org/");', + // fixed: '@document url(http://www.w3.org/);', + // message: messages.rejected('url'), + // }, + // { + // code: '@document url( "http://www.w3.org/" );', + // fixed: '@document url( http://www.w3.org/ );', + // message: messages.rejected('url'), + // }, + { + code: 'a { background: url("foo.css"); }', + fixed: 'a { background: url(foo.css); }', + message: messages.rejected('url'), + }, + { + code: "a { background: url('foo.css'); }", + fixed: 'a { background: url(foo.css); }', + message: messages.rejected('url'), + }, + ], +}); + testRule({ ruleName, config: ['always'], diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index dda521c49d..b761b6ded8 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -20,7 +20,7 @@ const meta = { }; /** @type {import('stylelint').Rule} */ -const rule = (primary, secondaryOptions) => { +const rule = (primary, secondaryOptions, context) => { return (root, result) => { const validOptions = validateOptions( result, @@ -72,8 +72,53 @@ const rule = (primary, secondaryOptions) => { } /** + * @param {import('postcss').Declaration | import('postcss').AtRule} node * @param {string} args - * @param {import('postcss').Node} node + * @param {number} index + */ + + function addQuotes(node, args, index) { + const fixedName = `"${args}"`; + const openIndex = index - args.length - 1 - ('params' in node ? 0 : 4); + const closeIndex = openIndex + args.length; + + if ('params' in node) { + node.params = + node.params.substring(0, openIndex) + fixedName + node.params.substring(closeIndex); + + return; + } + + node.value = + node.value.substring(0, openIndex) + fixedName + node.value.substring(closeIndex); + } + + /** + * @param {import('postcss').Declaration | import('postcss').AtRule} node + * @param {string} args + * @param {number} index + */ + + function removeQuotes(node, args, index) { + // console.log(node) + // console.log(args) + // console.log(index) + const fixedName = args.substring(1, args.length - 1); + const openIndex = index - args.length + 1 - ('params' in node ? 0 : 4); + const closeIndex = openIndex + args.length; + + if ('params' in node) { + node.params = node.params.slice(0, openIndex) + fixedName + node.params.slice(closeIndex); + + return; + } + + node.value = node.value.slice(0, openIndex) + fixedName + node.value.slice(closeIndex); + } + + /** + * @param {string} args + * @param {import('postcss').Declaration | import('postcss').AtRule} node * @param {number} index * @param {string} functionName */ @@ -102,12 +147,24 @@ const rule = (primary, secondaryOptions) => { return; } + if (context.fix) { + addQuotes(node, trimmedArg, complaintIndex); + + return; + } + complain(messages.expected(functionName), node, complaintIndex, complaintEndIndex); } else { if (!hasQuotes) { return; } + if (context.fix) { + removeQuotes(node, trimmedArg, complaintIndex); + + return; + } + complain(messages.rejected(functionName), node, complaintIndex, complaintEndIndex); } } From e0624f157c80f6ec7a7cb1d65dd0f01e58c99018 Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Sun, 8 Jan 2023 23:11:59 -0800 Subject: [PATCH 2/9] Refactor to use `raws`-based utilities --- .../function-url-quotes/__tests__/index.js | 32 +++++++++++++------ lib/rules/function-url-quotes/index.js | 25 +++++++++++---- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/rules/function-url-quotes/__tests__/index.js b/lib/rules/function-url-quotes/__tests__/index.js index f8efb203fd..6aed2b0a00 100644 --- a/lib/rules/function-url-quotes/__tests__/index.js +++ b/lib/rules/function-url-quotes/__tests__/index.js @@ -755,16 +755,22 @@ testRule({ fixed: '@import url( foo.css );', message: messages.rejected('url'), }, - // { - // code: '@document url("http://www.w3.org/");', - // fixed: '@document url(http://www.w3.org/);', - // message: messages.rejected('url'), - // }, - // { - // code: '@document url( "http://www.w3.org/" );', - // fixed: '@document url( http://www.w3.org/ );', - // message: messages.rejected('url'), - // }, + { + code: '@document url("http://www.w3.org/");', + fixed: '@document url(http://www.w3.org/);', + message: messages.rejected('url'), + }, + { + code: '@document url( "http://www.w3.org/" );', + fixed: '@document url( http://www.w3.org/ );', + message: messages.rejected('url'), + }, + { + code: '@document url( "http://www.w3.org/" );', + fixed: '@document url( http://www.w3.org/ );', + message: messages.rejected('url'), + description: 'multiple spaces after name', + }, { code: 'a { background: url("foo.css"); }', fixed: 'a { background: url(foo.css); }', @@ -775,6 +781,12 @@ testRule({ fixed: 'a { background: url(foo.css); }', message: messages.rejected('url'), }, + { + code: "a { background : url('foo.css'); }", + fixed: 'a { background : url(foo.css); }', + message: messages.rejected('url'), + description: 'multiple spaces in between', + }, ], }); diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index b761b6ded8..4569dfb9cb 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -19,6 +19,20 @@ const meta = { url: 'https://stylelint.io/user-guide/rules/function-url-quotes', }; +/** + * @param {import('postcss').Declaration} decl + * @returns {number} + */ +function getDeclarationValueIndex(decl) { + let index = decl.prop.length; + + if (decl.raws.between) { + index += decl.raws.between.length; + } + + return index; +} + /** @type {import('stylelint').Rule} */ const rule = (primary, secondaryOptions, context) => { return (root, result) => { @@ -79,7 +93,8 @@ const rule = (primary, secondaryOptions, context) => { function addQuotes(node, args, index) { const fixedName = `"${args}"`; - const openIndex = index - args.length - 1 - ('params' in node ? 0 : 4); + const openIndex = + 'params' in node ? index - atRuleParamIndex(node) : index - getDeclarationValueIndex(node); const closeIndex = openIndex + args.length; if ('params' in node) { @@ -100,11 +115,9 @@ const rule = (primary, secondaryOptions, context) => { */ function removeQuotes(node, args, index) { - // console.log(node) - // console.log(args) - // console.log(index) - const fixedName = args.substring(1, args.length - 1); - const openIndex = index - args.length + 1 - ('params' in node ? 0 : 4); + const fixedName = args.slice(1, args.length - 1); + const openIndex = + 'params' in node ? index - atRuleParamIndex(node) : index - getDeclarationValueIndex(node); const closeIndex = openIndex + args.length; if ('params' in node) { From b0d86c7c8b1f4e4819769bf93a904d1f24612b2b Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Sun, 8 Jan 2023 23:14:54 -0800 Subject: [PATCH 3/9] adds changeset --- .changeset/rude-dryers-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rude-dryers-fry.md diff --git a/.changeset/rude-dryers-fry.md b/.changeset/rude-dryers-fry.md new file mode 100644 index 0000000000..68867b82f8 --- /dev/null +++ b/.changeset/rude-dryers-fry.md @@ -0,0 +1,5 @@ +--- +"stylelint": minor +--- + +Added `function-url-quotes` autofix From d1cea8883859bd314ab20b6d85596254615dd4de Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Sun, 8 Jan 2023 23:44:29 -0800 Subject: [PATCH 4/9] Improves test coverage; "fixes" case insensitivity and autofix --- .../function-url-quotes/__tests__/index.js | 62 +++++++++++++++++++ lib/rules/function-url-quotes/index.js | 10 ++- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/lib/rules/function-url-quotes/__tests__/index.js b/lib/rules/function-url-quotes/__tests__/index.js index 6aed2b0a00..6bc8195187 100644 --- a/lib/rules/function-url-quotes/__tests__/index.js +++ b/lib/rules/function-url-quotes/__tests__/index.js @@ -721,11 +721,58 @@ testRule({ fixed: '@import url("foo.css");', message: messages.expected('url'), }, + { + code: '@import url( foo.css );', + fixed: '@import url( "foo.css" );', + message: messages.expected('url'), + }, + { + code: '@document url(http://www.w3.org/);', + fixed: '@document url("http://www.w3.org/");', + message: messages.expected('url'), + }, + { + code: '@document url( http://www.w3.org/ );', + fixed: '@document url( "http://www.w3.org/" );', + message: messages.expected('url'), + }, + { + code: '@document url( http://www.w3.org/ );', + fixed: '@document url( "http://www.w3.org/" );', + message: messages.expected('url'), + description: 'multiple spaces after name', + }, + { + code: '@document url-prefix(http://www.w3.org/Style);', + fixed: '@document url-prefix("http://www.w3.org/Style");', + message: messages.expected('url-prefix'), + }, + { + code: '@document url-prefix( http://www.w3.org/Style );', + fixed: '@document url-prefix( "http://www.w3.org/Style" );', + message: messages.expected('url-prefix'), + }, + { + code: '@document domain(mozilla.org);', + fixed: '@document domain("mozilla.org");', + message: messages.expected('domain'), + }, { code: 'a { background: url(foo.css); }', fixed: 'a { background: url("foo.css"); }', message: messages.expected('url'), }, + { + code: 'a { background : url(foo.css); }', + fixed: 'a { background : url("foo.css"); }', + message: messages.expected('url'), + description: 'multiple spaces in between', + }, + { + code: 'a { background-image: url(foo.css), url("bar.css"), url("baz.css"); }', + fixed: 'a { background-image: url("foo.css"), url("bar.css"), url("baz.css"); }', + message: messages.expected('url'), + }, ], }); @@ -771,6 +818,16 @@ testRule({ message: messages.rejected('url'), description: 'multiple spaces after name', }, + { + code: "@document url-prefix('http://www.w3.org/Style');", + fixed: '@document url-prefix(http://www.w3.org/Style);', + message: messages.rejected('url-prefix'), + }, + { + code: '@document domain("mozilla.org");', + fixed: '@document domain(mozilla.org);', + message: messages.rejected('domain'), + }, { code: 'a { background: url("foo.css"); }', fixed: 'a { background: url(foo.css); }', @@ -787,6 +844,11 @@ testRule({ message: messages.rejected('url'), description: 'multiple spaces in between', }, + { + code: "a { background-image: url('foo.css'), url(bar.css), url(baz.css); }", + fixed: 'a { background-image: url(foo.css), url(bar.css), url(baz.css); }', + message: messages.rejected('url'), + }, ], }); diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index 4569dfb9cb..8869948346 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -63,7 +63,7 @@ const rule = (primary, secondaryOptions, context) => { * @param {import('postcss').Declaration} decl */ function checkDeclParams(decl) { - functionArgumentsSearch(decl.toString().toLowerCase(), 'url', (args, index) => { + functionArgumentsSearch(decl.toString(), /^url$/i, (args, index) => { checkArgs(args, decl, index, 'url'); }); } @@ -72,15 +72,13 @@ const rule = (primary, secondaryOptions, context) => { * @param {import('postcss').AtRule} atRule */ function checkAtRuleParams(atRule) { - const atRuleParamsLowerCase = atRule.params.toLowerCase(); - - functionArgumentsSearch(atRuleParamsLowerCase, 'url', (args, index) => { + functionArgumentsSearch(atRule.params, /^url$/i, (args, index) => { checkArgs(args, atRule, index + atRuleParamIndex(atRule), 'url'); }); - functionArgumentsSearch(atRuleParamsLowerCase, 'url-prefix', (args, index) => { + functionArgumentsSearch(atRule.params, /^url-prefix$/i, (args, index) => { checkArgs(args, atRule, index + atRuleParamIndex(atRule), 'url-prefix'); }); - functionArgumentsSearch(atRuleParamsLowerCase, 'domain', (args, index) => { + functionArgumentsSearch(atRule.params, /^domain$/i, (args, index) => { checkArgs(args, atRule, index + atRuleParamIndex(atRule), 'domain'); }); } From 3b7a7d0a348494120d6f01d3d4acf949780d37bf Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Mon, 9 Jan 2023 01:19:20 -0800 Subject: [PATCH 5/9] minor changeset formatting --- .changeset/rude-dryers-fry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/rude-dryers-fry.md b/.changeset/rude-dryers-fry.md index 68867b82f8..bf8c2bf790 100644 --- a/.changeset/rude-dryers-fry.md +++ b/.changeset/rude-dryers-fry.md @@ -2,4 +2,4 @@ "stylelint": minor --- -Added `function-url-quotes` autofix +Added: `function-url-quotes` autofix From 511f882ea178c07075e97f201623bd1f1a44e4a3 Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Tue, 10 Jan 2023 10:09:04 -0800 Subject: [PATCH 6/9] Use `declarationValueIndex` instead of custom util --- lib/rules/function-url-quotes/index.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index 8869948346..7dd0646f79 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -1,6 +1,7 @@ 'use strict'; const atRuleParamIndex = require('../../utils/atRuleParamIndex'); +const declarationValueIndex = require('../../utils/declarationValueIndex'); const functionArgumentsSearch = require('../../utils/functionArgumentsSearch'); const isStandardSyntaxUrl = require('../../utils/isStandardSyntaxUrl'); const optionsMatches = require('../../utils/optionsMatches'); @@ -19,20 +20,6 @@ const meta = { url: 'https://stylelint.io/user-guide/rules/function-url-quotes', }; -/** - * @param {import('postcss').Declaration} decl - * @returns {number} - */ -function getDeclarationValueIndex(decl) { - let index = decl.prop.length; - - if (decl.raws.between) { - index += decl.raws.between.length; - } - - return index; -} - /** @type {import('stylelint').Rule} */ const rule = (primary, secondaryOptions, context) => { return (root, result) => { @@ -92,7 +79,7 @@ const rule = (primary, secondaryOptions, context) => { function addQuotes(node, args, index) { const fixedName = `"${args}"`; const openIndex = - 'params' in node ? index - atRuleParamIndex(node) : index - getDeclarationValueIndex(node); + 'params' in node ? index - atRuleParamIndex(node) : index - declarationValueIndex(node); const closeIndex = openIndex + args.length; if ('params' in node) { @@ -115,7 +102,7 @@ const rule = (primary, secondaryOptions, context) => { function removeQuotes(node, args, index) { const fixedName = args.slice(1, args.length - 1); const openIndex = - 'params' in node ? index - atRuleParamIndex(node) : index - getDeclarationValueIndex(node); + 'params' in node ? index - atRuleParamIndex(node) : index - declarationValueIndex(node); const closeIndex = openIndex + args.length; if ('params' in node) { From 47a9d8a29e71cc44e9ebd45edf3f5b8c44f91bb3 Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Tue, 10 Jan 2023 10:17:51 -0800 Subject: [PATCH 7/9] Reduce test duplication by adding `fix: true` to existing rules --- .../function-url-quotes/__tests__/index.js | 184 ++++-------------- 1 file changed, 42 insertions(+), 142 deletions(-) diff --git a/lib/rules/function-url-quotes/__tests__/index.js b/lib/rules/function-url-quotes/__tests__/index.js index 6bc8195187..ec2ec574a9 100644 --- a/lib/rules/function-url-quotes/__tests__/index.js +++ b/lib/rules/function-url-quotes/__tests__/index.js @@ -5,6 +5,7 @@ const { messages, ruleName } = require('..'); testRule({ ruleName, config: ['always'], + fix: true, accept: [ { @@ -180,6 +181,7 @@ testRule({ reject: [ { code: '@import url(foo.css);', + fixed: '@import url("foo.css");', message: messages.expected('url'), line: 1, column: 13, @@ -188,6 +190,7 @@ testRule({ }, { code: '@import url( foo.css );', + fixed: '@import url( "foo.css" );', message: messages.expected('url'), line: 1, column: 14, @@ -196,6 +199,7 @@ testRule({ }, { code: '@document url-prefix(http://www.w3.org/Style);', + fixed: '@document url-prefix("http://www.w3.org/Style");', message: messages.expected('url-prefix'), line: 1, column: 22, @@ -204,6 +208,7 @@ testRule({ }, { code: '@document url-prefix( http://www.w3.org/Style );', + fixed: '@document url-prefix( "http://www.w3.org/Style" );', message: messages.expected('url-prefix'), line: 1, column: 23, @@ -212,6 +217,7 @@ testRule({ }, { code: "@font-face { font-family: 'foo'; src: url(foo.ttf); }", + fixed: '@font-face { font-family: \'foo\'; src: url("foo.ttf"); }', message: messages.expected('url'), line: 1, column: 43, @@ -220,6 +226,7 @@ testRule({ }, { code: "@font-face { font-family: 'foo'; src: url( foo.ttf ); }", + fixed: '@font-face { font-family: \'foo\'; src: url( "foo.ttf" ); }', message: messages.expected('url'), line: 1, column: 44, @@ -228,6 +235,7 @@ testRule({ }, { code: 'a { cursor: url(foo.png); }', + fixed: 'a { cursor: url("foo.png"); }', message: messages.expected('url'), line: 1, column: 17, @@ -236,6 +244,7 @@ testRule({ }, { code: 'a { background-image: url(foo.css), url("bar.css"), url("baz.css"); }', + fixed: 'a { background-image: url("foo.css"), url("bar.css"), url("baz.css"); }', message: messages.expected('url'), line: 1, column: 27, @@ -244,6 +253,7 @@ testRule({ }, { code: 'a { background-image: url( foo.css ), url("bar.css"), url("baz.css"); }', + fixed: 'a { background-image: url( "foo.css" ), url("bar.css"), url("baz.css"); }', message: messages.expected('url'), line: 1, column: 28, @@ -252,6 +262,7 @@ testRule({ }, { code: 'a { background-image: url("foo.css"), url(bar.css), url("baz.css"); }', + fixed: 'a { background-image: url("foo.css"), url("bar.css"), url("baz.css"); }', message: messages.expected('url'), line: 1, column: 43, @@ -260,6 +271,7 @@ testRule({ }, { code: 'a { background-image: url("foo.css"), url( bar.css ), url("baz.css"); }', + fixed: 'a { background-image: url("foo.css"), url( "bar.css" ), url("baz.css"); }', message: messages.expected('url'), line: 1, column: 44, @@ -268,6 +280,7 @@ testRule({ }, { code: 'a { background-image: url("foo.css"), url("bar.css"), url(baz.css); }', + fixed: 'a { background-image: url("foo.css"), url("bar.css"), url("baz.css"); }', message: messages.expected('url'), line: 1, column: 59, @@ -276,6 +289,7 @@ testRule({ }, { code: 'a { background-image: url("foo.css"), url("bar.css"), url( baz.css ); }', + fixed: 'a { background-image: url("foo.css"), url("bar.css"), url( "baz.css" ); }', message: messages.expected('url'), line: 1, column: 60, @@ -288,6 +302,7 @@ testRule({ testRule({ ruleName, config: ['never'], + fix: true, accept: [ { @@ -397,6 +412,7 @@ testRule({ reject: [ { code: '@import url("foo.css");', + fixed: '@import url(foo.css);', message: messages.rejected('url'), line: 1, column: 13, @@ -405,6 +421,7 @@ testRule({ }, { code: '@import uRl("foo.css");', + fixed: '@import uRl(foo.css);', message: messages.rejected('url'), line: 1, column: 13, @@ -413,6 +430,7 @@ testRule({ }, { code: '@import URL("foo.css");', + fixed: '@import URL(foo.css);', message: messages.rejected('url'), line: 1, column: 13, @@ -421,6 +439,7 @@ testRule({ }, { code: '@import url( "foo.css" );', + fixed: '@import url( foo.css );', message: messages.rejected('url'), line: 1, column: 14, @@ -429,6 +448,7 @@ testRule({ }, { code: "@import url('foo.css');", + fixed: '@import url(foo.css);', message: messages.rejected('url'), line: 1, column: 13, @@ -437,6 +457,7 @@ testRule({ }, { code: "@import url( 'foo.css' );", + fixed: '@import url( foo.css );', message: messages.rejected('url'), line: 1, column: 14, @@ -445,6 +466,7 @@ testRule({ }, { code: '@document url("http://www.w3.org/");', + fixed: '@document url(http://www.w3.org/);', message: messages.rejected('url'), line: 1, column: 15, @@ -453,6 +475,7 @@ testRule({ }, { code: '@document url( "http://www.w3.org/" );', + fixed: '@document url( http://www.w3.org/ );', message: messages.rejected('url'), line: 1, column: 16, @@ -461,6 +484,7 @@ testRule({ }, { code: "@document url-prefix('http://www.w3.org/Style');", + fixed: '@document url-prefix(http://www.w3.org/Style);', message: messages.rejected('url-prefix'), line: 1, column: 22, @@ -469,6 +493,7 @@ testRule({ }, { code: "@document url-prefix( 'http://www.w3.org/Style' );", + fixed: '@document url-prefix( http://www.w3.org/Style );', message: messages.rejected('url-prefix'), line: 1, column: 23, @@ -477,6 +502,7 @@ testRule({ }, { code: '@document domain("mozilla.org");', + fixed: '@document domain(mozilla.org);', message: messages.rejected('domain'), line: 1, column: 18, @@ -485,6 +511,7 @@ testRule({ }, { code: '@document domain( "mozilla.org" );', + fixed: '@document domain( mozilla.org );', message: messages.rejected('domain'), line: 1, column: 19, @@ -493,6 +520,7 @@ testRule({ }, { code: "@font-face { font-family: foo; src: url('foo.ttf'); }", + fixed: '@font-face { font-family: foo; src: url(foo.ttf); }', message: messages.rejected('url'), line: 1, column: 41, @@ -501,6 +529,7 @@ testRule({ }, { code: "@font-face { font-family: foo; src: url( 'foo.ttf' ); }", + fixed: '@font-face { font-family: foo; src: url( foo.ttf ); }', message: messages.rejected('url'), line: 1, column: 42, @@ -509,6 +538,7 @@ testRule({ }, { code: 'a { background: url("foo.css"); }', + fixed: 'a { background: url(foo.css); }', message: messages.rejected('url'), line: 1, column: 21, @@ -517,6 +547,7 @@ testRule({ }, { code: 'a { background: uRl("foo.css"); }', + fixed: 'a { background: uRl(foo.css); }', message: messages.rejected('url'), line: 1, column: 21, @@ -525,6 +556,7 @@ testRule({ }, { code: 'a { background: URL("foo.css"); }', + fixed: 'a { background: URL(foo.css); }', message: messages.rejected('url'), line: 1, column: 21, @@ -533,6 +565,7 @@ testRule({ }, { code: 'a { background: url( "foo.css" ); }', + fixed: 'a { background: url( foo.css ); }', message: messages.rejected('url'), line: 1, column: 22, @@ -541,6 +574,7 @@ testRule({ }, { code: 'a { background: url( "foo.css" ); }', + fixed: 'a { background: url( foo.css ); }', message: messages.rejected('url'), line: 1, column: 23, @@ -549,6 +583,7 @@ testRule({ }, { code: 'a { cursor: url("foo.png"); }', + fixed: 'a { cursor: url(foo.png); }', message: messages.rejected('url'), line: 1, column: 17, @@ -557,6 +592,7 @@ testRule({ }, { code: "a { background-image: url('foo.css'), url(bar.css), url(baz.css); }", + fixed: 'a { background-image: url(foo.css), url(bar.css), url(baz.css); }', message: messages.rejected('url'), line: 1, column: 27, @@ -565,6 +601,7 @@ testRule({ }, { code: "a { background-image: url( 'foo.css' ), url(bar.css), url(baz.css); }", + fixed: 'a { background-image: url( foo.css ), url(bar.css), url(baz.css); }', message: messages.rejected('url'), line: 1, column: 28, @@ -573,6 +610,7 @@ testRule({ }, { code: "a { background-image: url(foo.css), url('bar.css'), url(baz.css); }", + fixed: 'a { background-image: url(foo.css), url(bar.css), url(baz.css); }', message: messages.rejected('url'), line: 1, column: 41, @@ -581,6 +619,7 @@ testRule({ }, { code: "a { background-image: url(foo.css), url( 'bar.css' ), url(baz.css); }", + fixed: 'a { background-image: url(foo.css), url( bar.css ), url(baz.css); }', message: messages.rejected('url'), line: 1, column: 42, @@ -589,6 +628,7 @@ testRule({ }, { code: "a { background-image: url(foo.css), url(bar.css), url('baz.css'); }", + fixed: 'a { background-image: url(foo.css), url(bar.css), url(baz.css); }', message: messages.rejected('url'), line: 1, column: 55, @@ -597,6 +637,7 @@ testRule({ }, { code: "a { background-image: url(foo.css), url(bar.css), url( 'baz.css' ); }", + fixed: 'a { background-image: url(foo.css), url(bar.css), url( baz.css ); }', message: messages.rejected('url'), line: 1, column: 56, @@ -605,6 +646,7 @@ testRule({ }, { code: 'a { background: url("/images/my_image@2x.png") }', + fixed: 'a { background: url(/images/my_image@2x.png) }', message: messages.rejected('url'), line: 1, column: 21, @@ -710,148 +752,6 @@ testRule({ ], }); -testRule({ - ruleName, - config: ['always'], - fix: true, - - reject: [ - { - code: '@import url(foo.css);', - fixed: '@import url("foo.css");', - message: messages.expected('url'), - }, - { - code: '@import url( foo.css );', - fixed: '@import url( "foo.css" );', - message: messages.expected('url'), - }, - { - code: '@document url(http://www.w3.org/);', - fixed: '@document url("http://www.w3.org/");', - message: messages.expected('url'), - }, - { - code: '@document url( http://www.w3.org/ );', - fixed: '@document url( "http://www.w3.org/" );', - message: messages.expected('url'), - }, - { - code: '@document url( http://www.w3.org/ );', - fixed: '@document url( "http://www.w3.org/" );', - message: messages.expected('url'), - description: 'multiple spaces after name', - }, - { - code: '@document url-prefix(http://www.w3.org/Style);', - fixed: '@document url-prefix("http://www.w3.org/Style");', - message: messages.expected('url-prefix'), - }, - { - code: '@document url-prefix( http://www.w3.org/Style );', - fixed: '@document url-prefix( "http://www.w3.org/Style" );', - message: messages.expected('url-prefix'), - }, - { - code: '@document domain(mozilla.org);', - fixed: '@document domain("mozilla.org");', - message: messages.expected('domain'), - }, - { - code: 'a { background: url(foo.css); }', - fixed: 'a { background: url("foo.css"); }', - message: messages.expected('url'), - }, - { - code: 'a { background : url(foo.css); }', - fixed: 'a { background : url("foo.css"); }', - message: messages.expected('url'), - description: 'multiple spaces in between', - }, - { - code: 'a { background-image: url(foo.css), url("bar.css"), url("baz.css"); }', - fixed: 'a { background-image: url("foo.css"), url("bar.css"), url("baz.css"); }', - message: messages.expected('url'), - }, - ], -}); - -testRule({ - ruleName, - config: ['never'], - fix: true, - - reject: [ - { - code: '@import url("foo.css");', - fixed: '@import url(foo.css);', - message: messages.rejected('url'), - }, - { - code: '@import url( "foo.css" );', - fixed: '@import url( foo.css );', - message: messages.rejected('url'), - }, - { - code: "@import url('foo.css');", - fixed: '@import url(foo.css);', - message: messages.rejected('url'), - }, - { - code: "@import url( 'foo.css' );", - fixed: '@import url( foo.css );', - message: messages.rejected('url'), - }, - { - code: '@document url("http://www.w3.org/");', - fixed: '@document url(http://www.w3.org/);', - message: messages.rejected('url'), - }, - { - code: '@document url( "http://www.w3.org/" );', - fixed: '@document url( http://www.w3.org/ );', - message: messages.rejected('url'), - }, - { - code: '@document url( "http://www.w3.org/" );', - fixed: '@document url( http://www.w3.org/ );', - message: messages.rejected('url'), - description: 'multiple spaces after name', - }, - { - code: "@document url-prefix('http://www.w3.org/Style');", - fixed: '@document url-prefix(http://www.w3.org/Style);', - message: messages.rejected('url-prefix'), - }, - { - code: '@document domain("mozilla.org");', - fixed: '@document domain(mozilla.org);', - message: messages.rejected('domain'), - }, - { - code: 'a { background: url("foo.css"); }', - fixed: 'a { background: url(foo.css); }', - message: messages.rejected('url'), - }, - { - code: "a { background: url('foo.css'); }", - fixed: 'a { background: url(foo.css); }', - message: messages.rejected('url'), - }, - { - code: "a { background : url('foo.css'); }", - fixed: 'a { background : url(foo.css); }', - message: messages.rejected('url'), - description: 'multiple spaces in between', - }, - { - code: "a { background-image: url('foo.css'), url(bar.css), url(baz.css); }", - fixed: 'a { background-image: url(foo.css), url(bar.css), url(baz.css); }', - message: messages.rejected('url'), - }, - ], -}); - testRule({ ruleName, config: ['always'], From 870e2d5d27a4db7b5f7f64105de8ac81ddcac40a Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Tue, 10 Jan 2023 10:23:53 -0800 Subject: [PATCH 8/9] Split up `addQuotes` and `removeQuotes` into at-rule and declaration-specific functions --- lib/rules/function-url-quotes/index.js | 60 +++++++++++++++++--------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index 7dd0646f79..f0540f44a2 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -71,45 +71,57 @@ const rule = (primary, secondaryOptions, context) => { } /** - * @param {import('postcss').Declaration | import('postcss').AtRule} node + * @param {import('postcss').AtRule} node * @param {string} args * @param {number} index */ - - function addQuotes(node, args, index) { + function addQuotesForAtRule(node, args, index) { const fixedName = `"${args}"`; - const openIndex = - 'params' in node ? index - atRuleParamIndex(node) : index - declarationValueIndex(node); + const openIndex = index - atRuleParamIndex(node); const closeIndex = openIndex + args.length; - if ('params' in node) { - node.params = - node.params.substring(0, openIndex) + fixedName + node.params.substring(closeIndex); + node.params = + node.params.substring(0, openIndex) + fixedName + node.params.substring(closeIndex); + } - return; - } + /** + * @param {import('postcss').Declaration} node + * @param {string} args + * @param {number} index + */ + function addQuotesForDecl(node, args, index) { + const fixedName = `"${args}"`; + const openIndex = index - declarationValueIndex(node); + const closeIndex = openIndex + args.length; node.value = node.value.substring(0, openIndex) + fixedName + node.value.substring(closeIndex); } /** - * @param {import('postcss').Declaration | import('postcss').AtRule} node + * @param {import('postcss').AtRule} node * @param {string} args * @param {number} index */ - function removeQuotes(node, args, index) { + function removeQuotesForAtRule(node, args, index) { const fixedName = args.slice(1, args.length - 1); - const openIndex = - 'params' in node ? index - atRuleParamIndex(node) : index - declarationValueIndex(node); + const openIndex = index - atRuleParamIndex(node); const closeIndex = openIndex + args.length; - if ('params' in node) { - node.params = node.params.slice(0, openIndex) + fixedName + node.params.slice(closeIndex); + node.params = node.params.slice(0, openIndex) + fixedName + node.params.slice(closeIndex); + } - return; - } + /** + * @param {import('postcss').Declaration} node + * @param {string} args + * @param {number} index + */ + + function removeQuotesForDecl(node, args, index) { + const fixedName = args.slice(1, args.length - 1); + const openIndex = index - declarationValueIndex(node); + const closeIndex = openIndex + args.length; node.value = node.value.slice(0, openIndex) + fixedName + node.value.slice(closeIndex); } @@ -146,7 +158,11 @@ const rule = (primary, secondaryOptions, context) => { } if (context.fix) { - addQuotes(node, trimmedArg, complaintIndex); + if (node.type === 'atrule') { + addQuotesForAtRule(node, trimmedArg, complaintIndex); + } else { + addQuotesForDecl(node, trimmedArg, complaintIndex); + } return; } @@ -158,7 +174,11 @@ const rule = (primary, secondaryOptions, context) => { } if (context.fix) { - removeQuotes(node, trimmedArg, complaintIndex); + if (node.type === 'atrule') { + removeQuotesForAtRule(node, trimmedArg, complaintIndex); + } else { + removeQuotesForDecl(node, trimmedArg, complaintIndex); + } return; } From a25ca7506756810c262039a1ae3e01ccbbb1bfa6 Mon Sep 17 00:00:00 2001 From: Matthew Wang Date: Tue, 10 Jan 2023 10:28:00 -0800 Subject: [PATCH 9/9] Adds documentation / metadata to indicate that the rule is now autofixable Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com> --- docs/user-guide/rules.md | 2 +- lib/rules/function-url-quotes/README.md | 2 ++ lib/rules/function-url-quotes/index.js | 1 + system-tests/002/__snapshots__/fs.test.js.snap | 1 + system-tests/002/__snapshots__/no-fs.test.js.snap | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/rules.md b/docs/user-guide/rules.md index 3d30827be2..0350b7adf6 100644 --- a/docs/user-guide/rules.md +++ b/docs/user-guide/rules.md @@ -238,7 +238,7 @@ Enforce naming conventions with these `pattern` rules. Require or disallow quotes with these `quotes` rules. - [`font-family-name-quotes`](../../lib/rules/font-family-name-quotes/README.md): Require or disallow quotes for font family names (Autofixable). -- [`function-url-quotes`](../../lib/rules/function-url-quotes/README.md): Require or disallow quotes for urls. +- [`function-url-quotes`](../../lib/rules/function-url-quotes/README.md): Require or disallow quotes for urls (Autofixable). - [`selector-attribute-quotes`](../../lib/rules/selector-attribute-quotes/README.md): Require or disallow quotes for attribute values (Autofixable). ### Redundant diff --git a/lib/rules/function-url-quotes/README.md b/lib/rules/function-url-quotes/README.md index 344a75217d..714b132d52 100644 --- a/lib/rules/function-url-quotes/README.md +++ b/lib/rules/function-url-quotes/README.md @@ -9,6 +9,8 @@ a { background: url("x.jpg") } * These quotes */ ``` +The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix most of the problems reported by this rule. + ## Options `string`: `"always"|"never"` diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index f0540f44a2..fef44047d8 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -18,6 +18,7 @@ const messages = ruleMessages(ruleName, { const meta = { url: 'https://stylelint.io/user-guide/rules/function-url-quotes', + fixable: true, }; /** @type {import('stylelint').Rule} */ diff --git a/system-tests/002/__snapshots__/fs.test.js.snap b/system-tests/002/__snapshots__/fs.test.js.snap index a84109f3a2..015378d2e2 100644 --- a/system-tests/002/__snapshots__/fs.test.js.snap +++ b/system-tests/002/__snapshots__/fs.test.js.snap @@ -113,6 +113,7 @@ Object { "url": "https://stylelint.io/user-guide/rules/function-url-no-scheme-relative", }, "function-url-quotes": Object { + "fixable": true, "url": "https://stylelint.io/user-guide/rules/function-url-quotes", }, "max-empty-lines": Object { diff --git a/system-tests/002/__snapshots__/no-fs.test.js.snap b/system-tests/002/__snapshots__/no-fs.test.js.snap index 324c84ba11..f103e70013 100644 --- a/system-tests/002/__snapshots__/no-fs.test.js.snap +++ b/system-tests/002/__snapshots__/no-fs.test.js.snap @@ -113,6 +113,7 @@ Object { "url": "https://stylelint.io/user-guide/rules/function-url-no-scheme-relative", }, "function-url-quotes": Object { + "fixable": true, "url": "https://stylelint.io/user-guide/rules/function-url-quotes", }, "max-empty-lines": Object {