Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function-calculation-no-interpolation rule #849

Merged
merged 1 commit into from Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,9 @@
# 5.2.0

- Added: `function-calculation-no-interpolation` rule to forbid interpolation in calc functions (#539).

**Full Changelog**: https://github.com/stylelint-scss/stylelint-scss/compare/v5.1.0...v5.2.0

# 5.1.0

- Added: `function-disallowed-list` rule support to ban specific built-in functions (#422, #844).
Expand Down
5 changes: 2 additions & 3 deletions README.md
Expand Up @@ -30,9 +30,7 @@ Create the `.stylelintrc.json` config file (or open the existing one), add `styl

```jsonc
{
"plugins": [
"stylelint-scss"
],
"plugins": ["stylelint-scss"],
"rules": {
// recommended rules
"at-rule-no-unknown": null,
Expand Down Expand Up @@ -145,6 +143,7 @@ Please also see the [example configs](./docs/examples/) for special cases.
- [`function-color-relative`](./src/rules/function-color-relative/README.md): Encourage the use of the [scale-color](https://sass-lang.com/documentation/modules/color#scale-color) function over regular color functions.
- [`function-disallowed-list`](./src/rules/function-disallowed-list/README.md): Specify a list of disallowed functions. Should be used **instead of** Stylelint's [function-disallowed-list](https://stylelint.io/user-guide/rules/function-disallowed-list).
- [`function-no-unknown`](./src/rules/function-no-unknown/README.md): Disallow unknown functions. Should be used **instead of** Stylelint's [function-no-unknown](https://stylelint.io/user-guide/rules/function-no-unknown).
- [`function-calculation-no-interpolation`](./src/rules/function-calculation-no-interpolation/README.md): Forbids interpolation in `calc()`, `clamp()`, `min()`, and `max()` functions.
- [`function-quote-no-quoted-strings-inside`](./src/rules/function-quote-no-quoted-strings-inside/README.md): Disallow quoted strings inside the [quote function](https://sass-lang.com/documentation/modules/string#quote) (Autofixable).
- [`function-unquote-no-unquoted-strings-inside`](./src/rules/function-unquote-no-unquoted-strings-inside/README.md): Disallow unquoted strings inside the [unquote function](https://sass-lang.com/documentation/modules/string#unquote) (Autofixable).

Expand Down
64 changes: 64 additions & 0 deletions src/rules/function-calculation-no-interpolation/README.md
@@ -0,0 +1,64 @@
# function-calculation-no-interpolation

Disallow interpolation in `calc()`, `clamp()`, `min()`, and `max()`.

<!-- prettier-ignore -->
```scss
.a { .b: calc(#{$c} + 1); }
/** ↑
* This argument */
```

Since the release of [first-class `calc()`](https://sass-lang.com/documentation/values/calculations/),
calculation functions `calc()`, `clamp()`, `min()`, and `max()` accept variables
and function calls as arguments. This rule disallows interpolation to avoid
extra verbose or even invalid CSS.

## Options

### `true`

The following patterns are considered warnings:

<!-- prettier-ignore -->
```scss
$c: 1;
.a { .b: calc(#{$c + 1}); }
```

<!-- prettier-ignore -->
```scss
$c: 1;
.a { .b: calc(max(#{$c})); }
```

<!-- prettier-ignore -->
```scss
$c: 1;
.a { .b: min(#{$c}); }
```

<!-- prettier-ignore -->
```scss
$c: 1;
.a { .b: clamp(#{$c} + 2px); }
```

The following patterns are _not_ considered warnings:

<!-- prettier-ignore -->
```scss
.a { .b: calc(1 + 1); }
```

<!-- prettier-ignore -->
```scss
$c: 1;
.a { .b: abc(#{$c} + 1px); }
```

<!-- prettier-ignore -->
```scss
$c: 1;
.a { .b: calc(abc(#{$c})); }
```
82 changes: 82 additions & 0 deletions src/rules/function-calculation-no-interpolation/__tests__/index.js
@@ -0,0 +1,82 @@
"use strict";

const { messages, ruleName } = require("..");

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

accept: [
{
code: `.a { .b: calc(1 + 1); }`,
description: "`calc` function, no interpolation"
},
{
code: `
$c: 1;
.a { .b: abc(#{$c} + 1px); }
`,
description: "Allowed function with interpolation"
},
{
code: `
$c: 1;
.a { .b: calc(abc(#{$c})); }
`,
description: "Allowed function with interpolation nested in `calc`"
}
],
reject: [
{
code: `
$c: 1;
.a { .b: calc(#{$c} + 1); }
`,
line: 3,
column: 12,
message: messages.rejected("calc"),
description: "`calc` function one argument interpolated"
},
{
code: `
$c: 1;
.a { .b: calc(#{$c + 1}); }
`,
line: 3,
column: 12,
message: messages.rejected("calc"),
description: "`calc` function all arguments interpolated"
},
{
code: `
$c: 1;
.a { .b: calc(max(#{$c})); }
`,
line: 3,
column: 12,
message: messages.rejected("max"),
description: "`max` function with interpolation"
},
{
code: `
$c: 1;
.a { .b: min(#{$c} + 1px); }
`,
line: 3,
column: 12,
message: messages.rejected("min"),
description: "`min` function with interpolation"
},
{
code: `
$c: 1;
.a { .b: clamp(#{$c} + #{$d}); }
`,
line: 3,
column: 12,
message: messages.rejected("clamp"),
description: "`clamp` function with interpolation"
}
]
});
56 changes: 56 additions & 0 deletions src/rules/function-calculation-no-interpolation/index.js
@@ -0,0 +1,56 @@
"use strict";

const { utils } = require("stylelint");
const namespace = require("../../utils/namespace");
const ruleUrl = require("../../utils/ruleUrl");
const valueParser = require("postcss-value-parser");

const ruleName = namespace("function-calculation-no-interpolation");

const messages = utils.ruleMessages(ruleName, {
rejected: func => `Unexpected interpolation in "${func}".`
});

const meta = {
url: ruleUrl(ruleName)
};

function rule(actual) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, { actual });

if (!validOptions) {
return;
}

const calculationFunctions = ["calc", "max", "min", "clamp"];

root.walkDecls(decl => {
valueParser(decl.value).walk(node => {
if (node.type !== "function" || node.value === "") {
return;
}
if (
calculationFunctions.includes(node.value) &&
node.nodes.some(
args => args.type === "word" && /^#{.*|\s*}/.test(args.value)
)
) {
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;
1 change: 1 addition & 0 deletions src/rules/index.js
Expand Up @@ -43,6 +43,7 @@ const rules = {
"double-slash-comment-inline": require("./double-slash-comment-inline"),
"double-slash-comment-whitespace-inside": require("./double-slash-comment-whitespace-inside"),
"function-disallowed-list": require("./function-disallowed-list"),
"function-calculation-no-interpolation": require("./function-calculation-no-interpolation"),
"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"),
Expand Down