Skip to content

Commit

Permalink
Add scss/function-no-unknown rule (#591)
Browse files Browse the repository at this point in the history
* Add `scss/function-no-unknown` rule

* Update functions list

* Update functions list

* npm i github:stylelint/stylelint#bump-css-functions-list

* git+https

* Revert "git+https"

This reverts commit f70c826.

* Revert "npm i github:stylelint/stylelint#bump-css-functions-list"

This reverts commit a38124c.

* Update lockfile

* Bump stylelint to 14.5.1

* Add reject case for unknown SCSS function

* Add more accept cases
  • Loading branch information
ybiquitous committed Mar 15, 2022
1 parent 2c4285e commit 03af755
Show file tree
Hide file tree
Showing 8 changed files with 654 additions and 134 deletions.
306 changes: 174 additions & 132 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
"postcss-scss": "^4.0.1",
"prettier": "^1.19.1",
"rimraf": "^3.0.2",
"stylelint": "^14.0.1"
"stylelint": "^14.5.1"
},
"peerDependencies": {
"stylelint": "^14.0.0"
"stylelint": "^14.5.1"
},
"eslintConfig": {
"extends": [
Expand Down
64 changes: 64 additions & 0 deletions src/rules/function-no-unknown/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# function-no-unknown

Disallow unknown functions. Should be used **instead of** Stylelint's [function-no-unknown](https://stylelint.io/user-guide/rules/list/at-rule-no-unknown).

```css
a { color: unknown(1); }
/** ↑
* Functions like this */
```

This rule is basically a wrapper around the mentioned core rule. You must disable Stylelint's core rule to make this rule work:

```json
{
"rules": {
"function-no-unknown": null,
"scss/function-no-unknown": true
}
}
```

## Options

### `true`

The following patterns are considered warnings:

```css
a { color: unknown(1); }
```

The following patterns are *not* considered warnings:

```css
a { color: hsl(240 100% 50%); }
```

```css
a { color: if(true, green, red); }
```

## Optional secondary options

### `ignoreFunctions: ["/regex/", /regex/, "non-regex"]`

Given:

```json
["/^my-/i", "foo"]
```

The following patterns are *not* considered warnings:

```css
a { color: my-func(1); }
```

```css
a { color: MY-FUNC(1); }
```

```css
a { color: foo(1); }
```
85 changes: 85 additions & 0 deletions src/rules/function-no-unknown/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ruleName, messages } from "..";

testRule({
ruleName,
config: [true],
customSyntax: "postcss-scss",

accept: [
{
code: "a { color: hwb(240 100% 50%); }",
description: "Normal CSS function"
},
{
code: "a { color: hsl(240 100% 50%); }",
description: "Function both in CSS and SCSS"
},
{
code: "a { color: if(true, green, red); }",
description: "SCSS function"
},
{
code: "a { color: adjust-color(#6b717f, $red: 15); }"
},
{
code: "a { color: color.adjust(#6b717f, $red: 15); }"
}
],

reject: [
{
code: "a { color: unknown(1); }",
message: messages.rejected("unknown"),
line: 1,
column: 12
},
{
code: "a { color: color.unknown(#6b717f, $red: 15); }",
message: messages.rejected("color.unknown"),
line: 1,
column: 12
}
]
});

testRule({
ruleName,
config: [true, { ignoreFunctions: ["/^my-/i", /foo$/, "bar"] }],
customSyntax: "postcss-scss",

accept: [
{
code: "a { color: my-func(1); }"
},
{
code: "a { color: MY-FUNC(1); }"
},
{
code: "a { color: func-foo(1); }"
},
{
code: "a { color: bar(1); }"
}
],

reject: [
{
code: "a { color: my(1); }",
message: messages.rejected("my"),
line: 1,
column: 12
},
{
code: "a { color: foo-func(1); }",
message: messages.rejected("foo-func"),
line: 1,
column: 12
},
{
code: "a { color: barrr(1); }",
message: messages.rejected("barrr"),
line: 1,
column: 12
}
]
});
77 changes: 77 additions & 0 deletions src/rules/function-no-unknown/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { isRegExp, isString } from "lodash";
import { rules, utils } from "stylelint";
import valueParser from "postcss-value-parser";
import { namespace, ALL_FUNCTIONS } from "../../utils";

const ruleToCheckAgainst = "function-no-unknown";

export const ruleName = namespace(ruleToCheckAgainst);

export const messages = utils.ruleMessages(ruleName, {
rejected: (...args) => {
return rules[ruleToCheckAgainst].messages
.rejected(...args)
.replace(` (${ruleToCheckAgainst})`, "");
}
});

export default function rule(primaryOption, secondaryOptions) {
return (root, result) => {
const validOptions = utils.validateOptions(
result,
ruleName,
{
actual: primaryOption
},
{
actual: secondaryOptions,
possible: {
ignoreFunctions: [isString, isRegExp]
},
optional: true
}
);

if (!validOptions) {
return;
}

const optionsFunctions =
(secondaryOptions && secondaryOptions.ignoreFunctions) || [];
const ignoreFunctions = ALL_FUNCTIONS.concat(optionsFunctions);
const ignoreFunctionsAsSet = new Set(ignoreFunctions);
const newSecondaryOptions = Object.assign({}, secondaryOptions, {
ignoreFunctions
});

utils.checkAgainstRule(
{
ruleName: ruleToCheckAgainst,
ruleSettings: [primaryOption, newSecondaryOptions],
root
},
warning => {
const { node, index } = warning;

// NOTE: Using `valueParser` is necessary for extracting a function name. This may be a performance waste.
valueParser(node.value).walk(valueNode => {
const { type, value: funcName } = valueNode;

if (type !== "function") {
return;
}

if (!ignoreFunctionsAsSet.has(funcName)) {
utils.report({
message: messages.rejected(funcName),
ruleName,
result,
node,
index
});
}
});
}
);
};
}
2 changes: 2 additions & 0 deletions src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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 functionColorRelative from "./function-color-relative";
import functionNoUnknown from "./function-no-unknown";
import functionNoQuotedStrings from "./function-quote-no-quoted-strings-inside";
import functionNoUnquotedStrings from "./function-unquote-no-unquoted-strings-inside";
import mapKeysQuotes from "./map-keys-quotes";
Expand Down Expand Up @@ -102,6 +103,7 @@ export default {
"function-quote-no-quoted-strings-inside": functionNoQuotedStrings,
"function-unquote-no-unquoted-strings-inside": functionNoUnquotedStrings,
"function-color-relative": functionColorRelative,
"function-no-unknown": functionNoUnknown,
"map-keys-quotes": mapKeysQuotes,
"media-feature-value-dollar-variable": mediaFeatureValueDollarVariable,
"no-dollar-variables": noDollarVariables,
Expand Down

0 comments on commit 03af755

Please sign in to comment.