From 3c559d5142143fa371880a01959b1e47e9dba130 Mon Sep 17 00:00:00 2001 From: Pamela Lozano De La Garza Date: Wed, 2 Aug 2023 18:41:49 -0700 Subject: [PATCH] Add function-blacklist rule (stylelint-scss#422) --- CHANGELOG.md | 4 + src/rules/function-blacklist/README.md | 60 ++++++++++++++ .../function-blacklist/__tests__/index.js | 78 +++++++++++++++++++ src/rules/function-blacklist/index.js | 62 +++++++++++++++ src/rules/index.js | 1 + 5 files changed, 205 insertions(+) create mode 100644 src/rules/function-blacklist/README.md create mode 100644 src/rules/function-blacklist/__tests__/index.js create mode 100644 src/rules/function-blacklist/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e75655aa..0f89c0d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 5.0.2 + +- Added: `function-blacklist` rule support to ban specific built-in functions (#422). + # 5.0.1 - Fixed: `function-no-unknown` ignore empty function names (#826). diff --git a/src/rules/function-blacklist/README.md b/src/rules/function-blacklist/README.md new file mode 100644 index 00000000..278e9254 --- /dev/null +++ b/src/rules/function-blacklist/README.md @@ -0,0 +1,60 @@ +# function-blacklist + +Specify a list of blacklisted functions. + + +```css +a { margin-left: math.random(100); } +/** ↑ +* This function */ +``` + +## Options + +`array|string|regex`: `["array", "of", "unprefixed", /functions/, "regex"]|"function"|"/regex/"|/regex/` + +If a string is surrounded with `"/"` (e.g. `"/^rgb/"`), it is interpreted as a regular expression. + +Given: + +```json +["math.random", "double"] +``` + +The following patterns are considered warnings: + + +```scss +a { margin-left: math.random(100); } +``` + + +```scss +@function double($num) {@return $num * 2;} +a { + margin-left: double($num); +} +``` + +The following patterns are _not_ considered warnings: + + +```scss +a { margin-left: math.abs(100); } +``` + + +```scss +@function timesTwo($num) {@return $num * 2;} +a { + margin-left: timesTwo($num); +} +``` + + +```scss +@function random($num) {@return $num;} +a { + margin-left: random($num); +} +``` diff --git a/src/rules/function-blacklist/__tests__/index.js b/src/rules/function-blacklist/__tests__/index.js new file mode 100644 index 00000000..bc65b2fc --- /dev/null +++ b/src/rules/function-blacklist/__tests__/index.js @@ -0,0 +1,78 @@ +"use strict"; + +const { messages, ruleName } = require(".."); + +// Testing single value +testRule({ + ruleName, + config: ["math.random"], + customSyntax: "postcss-scss", + + accept: [ + { + code: ` + @use "sass:math" + .a { margin-left: math.round(100.5); } + `, + description: "Math library function, not blacklisted." + } + ], + reject: [ + { + code: ` + @use "sass:math" + .a { margin-left: math.random(100); } + `, + line: 3, + column: 18, + message: messages.rejected("math.random"), + description: "Math library function, not blacklisted." + } + ] +}); + +// Testing an array +testRule({ + ruleName, + config: [["math.random", "test"]], + customSyntax: "postcss-scss", + + accept: [ + { + code: ` + @use "sass:math" + .a { margin-left: math.round(100.5); } + `, + description: "Math library function, not blacklisted." + }, + { + code: ` + @function funcName($num){@return $num;} + .a { margin-left: funcName(3); } + `, + description: "Declared function is not blacklisted." + } + ], + reject: [ + { + code: ` + @use "sass:math" + .a { margin-left: math.random(100); } + `, + line: 3, + column: 16, + message: messages.rejected("math.random"), + description: "Math library function, not blacklisted." + }, + { + code: ` + @function test($num){@return $num;} + .a { margin-left: test(3); } + `, + line: 3, + column: 14, + message: messages.rejected("test"), + description: "Declared function is blacklisted." + } + ] +}); diff --git a/src/rules/function-blacklist/index.js b/src/rules/function-blacklist/index.js new file mode 100644 index 00000000..acadb404 --- /dev/null +++ b/src/rules/function-blacklist/index.js @@ -0,0 +1,62 @@ +"use strict"; + +const { utils } = require("stylelint"); +const { isRegExp, isString } = require("../../utils/validateTypes"); +const namespace = require("../../utils/namespace"); +const ruleUrl = require("../../utils/ruleUrl"); +const valueParser = require("postcss-value-parser"); +const isNativeCssFunction = require("../../utils/isNativeCssFunction"); + +const ruleName = namespace("function-blacklist"); + +const messages = utils.ruleMessages(ruleName, { + rejected: func => `Unexpected function "${func}"` +}); + +const meta = { + url: ruleUrl(ruleName) +}; + +function rule(blacklistOption) { + const blacklist = [].concat(blacklistOption); + + return (root, result) => { + const validOptions = utils.validateOptions(result, ruleName, { + actual: blacklistOption, + possible: [isString, isRegExp] + }); + + if (!validOptions) { + return; + } + + root.walkDecls(decl => { + valueParser(decl.value).walk(node => { + if ( + node.type !== "function" || + isNativeCssFunction(node.value) || + node.value === "" + ) { + return; + } + blacklist.forEach(functionName => { + if (node.value === functionName) { + utils.report({ + message: messages.rejected(node.value), + node: decl, + word: decl.name, + result, + ruleName + }); + } + }); + }); + }); + }; +} + +rule.ruleName = ruleName; +rule.messages = messages; +rule.meta = meta; + +module.exports = rule; diff --git a/src/rules/index.js b/src/rules/index.js index 7d99314b..f6527858 100644 --- a/src/rules/index.js +++ b/src/rules/index.js @@ -42,6 +42,7 @@ const rules = { "double-slash-comment-empty-line-before": require("./double-slash-comment-empty-line-before"), "double-slash-comment-inline": require("./double-slash-comment-inline"), "double-slash-comment-whitespace-inside": require("./double-slash-comment-whitespace-inside"), + "function-blacklist": require("./function-blacklist"), "function-color-relative": require("./function-color-relative"), "function-no-unknown": require("./function-no-unknown"), "function-quote-no-quoted-strings-inside": require("./function-quote-no-quoted-strings-inside"),