From b93c18d836b7cc8471494506a493247eba932d33 Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Thu, 30 May 2019 18:43:59 -0700 Subject: [PATCH 1/3] creating new rule that's based on quote rule --- .../README.md | 46 ++++++++++++ .../__tests__/index.js | 64 ++++++++++++++++ .../index.js | 73 +++++++++++++++++++ src/rules/index.js | 2 + 4 files changed, 185 insertions(+) create mode 100644 src/rules/function-unquote-no-unquoted-strings-inside/README.md create mode 100644 src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js create mode 100644 src/rules/function-unquote-no-unquoted-strings-inside/index.js diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/README.md b/src/rules/function-unquote-no-unquoted-strings-inside/README.md new file mode 100644 index 00000000..c7144139 --- /dev/null +++ b/src/rules/function-unquote-no-unquoted-strings-inside/README.md @@ -0,0 +1,46 @@ +# function-quote-no-quoted-strings-inside + +Disallow quoted strings inside the [quote function](https://sass-lang.com/documentation/functions/string#quote) + +```scss +p { + font-family: quote("Helvetica"); + /** ↑ ↑ + * These quotes are unnecessary + */ +} +``` + +## Options + +### `true` + +The following patterns are considered violations: + +```scss +a { + font-family: quote("Helvetica"); +} +``` + +```scss +$font: "Helvetica"; +p { + font-family: quote($font); +} +``` + +The following patterns are _not_ considered violations: + +```scss +a { + color: quote(blue); +} +``` + +```scss +$font: Helvetica; +p { + font-family: quote($font); +} +``` diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js b/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js new file mode 100644 index 00000000..8ac8514b --- /dev/null +++ b/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js @@ -0,0 +1,64 @@ +import rule, { ruleName, messages } from ".."; + +// always-intermediate +testRule(rule, { + ruleName, + config: [true], + syntax: "scss", + fix: true, + + accept: [ + { + code: ` + p { + font-family: quote(Helvetica); + } + `, + description: "accepts strings without quotes" + }, + { + code: ` + $font: Helvetica; + p { + font-family: quote($font); + } + `, + description: "accepts variables representing strings that are unquoted." + } + ], + + reject: [ + { + code: ` + p { + font-family: quote("Helvetica"); + } + `, + description: "does not accept strings with quotes", + message: messages.rejected, + line: 3, + fixed: ` + p { + font-family: "Helvetica"; + } + ` + }, + { + code: ` + $font: "Helvetica"; + p { + font-family: quote($font); + } + `, + description: + "does not accept variables representing strings that are quoted.", + line: 4, + fixed: ` + $font: "Helvetica"; + p { + font-family: $font; + } + ` + } + ] +}); diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/index.js b/src/rules/function-unquote-no-unquoted-strings-inside/index.js new file mode 100644 index 00000000..5d72e50d --- /dev/null +++ b/src/rules/function-unquote-no-unquoted-strings-inside/index.js @@ -0,0 +1,73 @@ +import { utils } from "stylelint"; +import { namespace, isNativeCssFunction } from "../../utils"; +import valueParser from "postcss-value-parser"; + +export const ruleName = namespace( + "function-unquote-no-unquoted-strings-inside" +); + +export const messages = utils.ruleMessages(ruleName, { + rejected: "Unquote function used with an already-unquoted string" +}); + +function rule(primary, _, context) { + return (root, result) => { + const validOptions = utils.validateOptions(result, ruleName, { + actual: primary + }); + + if (!validOptions) { + return; + } + + // Setup variable naming. + const vars = {}; + + root.walkDecls(decl => { + if (decl.prop[0] !== "$") { + return; + } + + valueParser(decl.value).walk(node => { + vars[decl.prop] = node.type; + }); + }); + + root.walkDecls(decl => { + valueParser(decl.value).walk(node => { + // Verify that we're only looking at functions. + if ( + node.type !== "function" || + isNativeCssFunction(node.value) || + node.value === "" + ) { + return; + } + + // Verify we're only looking at quote() calls. + if (node.value !== "quote") { + return; + } + + // Report error if first character is a quote. + // postcss-value-parser represents quoted strings as type 'string' (as opposed to word) + if (node.nodes[0].quote || vars[node.nodes[0].value] === "string") { + if (context.fix) { + const contents = /quote\((.*)\)/.exec(decl.value); + + decl.value = contents[1]; + } else { + utils.report({ + message: messages.rejected, + node: decl, + result, + ruleName + }); + } + } + }); + }); + }; +} + +export default rule; diff --git a/src/rules/index.js b/src/rules/index.js index f316b694..73a1bf75 100644 --- a/src/rules/index.js +++ b/src/rules/index.js @@ -30,6 +30,7 @@ import doubleSlashCommentEmptyLineBefore from "./double-slash-comment-empty-line import doubleSlashCommentInline from "./double-slash-comment-inline"; import doubleSlashCommentWhitespaceInside from "./double-slash-comment-whitespace-inside"; import functionNoQuotedStrings from "./function-quote-no-quoted-strings-inside"; +import functionNoUnquotedStrings from "./function-unquote-no-unquoted-strings-inside"; import mediaFeatureValueDollarVariable from "./media-feature-value-dollar-variable"; import noDollarVariables from "./no-dollar-variables"; import noDuplicateDollarVariables from "./no-duplicate-dollar-variables"; @@ -74,6 +75,7 @@ export default { "double-slash-comment-inline": doubleSlashCommentInline, "double-slash-comment-whitespace-inside": doubleSlashCommentWhitespaceInside, "function-quote-no-quoted-strings-inside": functionNoQuotedStrings, + "function-unquote-no-unquoted-strings-inside": functionNoUnquotedStrings, "media-feature-value-dollar-variable": mediaFeatureValueDollarVariable, "no-dollar-variables": noDollarVariables, "no-duplicate-dollar-variables": noDuplicateDollarVariables, From 16dae299087cff66782db3e3020d927d2d399768 Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Thu, 30 May 2019 18:46:58 -0700 Subject: [PATCH 2/3] updated readmes and tests --- .../README.md | 20 ++++++++--------- .../__tests__/index.js | 22 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/README.md b/src/rules/function-unquote-no-unquoted-strings-inside/README.md index c7144139..1db35354 100644 --- a/src/rules/function-unquote-no-unquoted-strings-inside/README.md +++ b/src/rules/function-unquote-no-unquoted-strings-inside/README.md @@ -1,12 +1,12 @@ -# function-quote-no-quoted-strings-inside +# function-unquote-no-unquoted-strings-inside -Disallow quoted strings inside the [quote function](https://sass-lang.com/documentation/functions/string#quote) +Disallow unquoted strings inside the [unquote function](https://sass-lang.com/documentation/functions/string#unquote) ```scss p { - font-family: quote("Helvetica"); - /** ↑ ↑ - * These quotes are unnecessary + font-family: unquote(Helvetica); + /** ↑ ↑ + * This function call is unnecessary */ } ``` @@ -19,12 +19,12 @@ The following patterns are considered violations: ```scss a { - font-family: quote("Helvetica"); + font-family: unquote(Helvetica); } ``` ```scss -$font: "Helvetica"; +$font: Helvetica; p { font-family: quote($font); } @@ -34,13 +34,13 @@ The following patterns are _not_ considered violations: ```scss a { - color: quote(blue); + color: unquote("blue"); } ``` ```scss -$font: Helvetica; +$font: "Helvetica"; p { - font-family: quote($font); + font-family: unquote($font); } ``` diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js b/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js index 8ac8514b..5404c8be 100644 --- a/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js +++ b/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js @@ -11,19 +11,19 @@ testRule(rule, { { code: ` p { - font-family: quote(Helvetica); + font-family: unquote("Helvetica"); } `, - description: "accepts strings without quotes" + description: "accepts strings with quotes" }, { code: ` - $font: Helvetica; + $font: "Helvetica"; p { - font-family: quote($font); + font-family: unquote($font); } `, - description: "accepts variables representing strings that are unquoted." + description: "accepts variables representing strings that are quoted." } ], @@ -31,30 +31,30 @@ testRule(rule, { { code: ` p { - font-family: quote("Helvetica"); + font-family: unquote(Helvetica); } `, - description: "does not accept strings with quotes", + description: "does not accept strings without quotes", message: messages.rejected, line: 3, fixed: ` p { - font-family: "Helvetica"; + font-family: Helvetica; } ` }, { code: ` - $font: "Helvetica"; + $font: Helvetica; p { - font-family: quote($font); + font-family: unquote($font); } `, description: "does not accept variables representing strings that are quoted.", line: 4, fixed: ` - $font: "Helvetica"; + $font: Helvetica; p { font-family: $font; } From d8ccd2a5d0384cd134c74e4e2f6a9875f40c1e9d Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Thu, 30 May 2019 18:59:29 -0700 Subject: [PATCH 3/3] unquote string rule working --- .../function-unquote-no-unquoted-strings-inside/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/index.js b/src/rules/function-unquote-no-unquoted-strings-inside/index.js index 5d72e50d..a031a637 100644 --- a/src/rules/function-unquote-no-unquoted-strings-inside/index.js +++ b/src/rules/function-unquote-no-unquoted-strings-inside/index.js @@ -45,15 +45,18 @@ function rule(primary, _, context) { } // Verify we're only looking at quote() calls. - if (node.value !== "quote") { + if (node.value !== "unquote") { return; } // Report error if first character is a quote. // postcss-value-parser represents quoted strings as type 'string' (as opposed to word) - if (node.nodes[0].quote || vars[node.nodes[0].value] === "string") { + if ( + (!node.nodes[0].quote && node.nodes[0].value[0] !== "$") || + vars[node.nodes[0].value] === "word" + ) { if (context.fix) { - const contents = /quote\((.*)\)/.exec(decl.value); + const contents = /unquote\((.*)\)/.exec(decl.value); decl.value = contents[1]; } else {