Skip to content

Commit

Permalink
Add function-no-unknown (#5865)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnuletik committed Feb 7, 2022
1 parent 2005263 commit 6e6bd90
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/user-guide/rules/list.md
Expand Up @@ -28,6 +28,7 @@ Within each cateogory, the rules are grouped by the [_thing_](http://apps.workfl

- [`function-calc-no-unspaced-operator`](../../../lib/rules/function-calc-no-unspaced-operator/README.md): Disallow an unspaced operator within `calc` functions.
- [`function-linear-gradient-no-nonstandard-direction`](../../../lib/rules/function-linear-gradient-no-nonstandard-direction/README.md): Disallow direction values in `linear-gradient()` calls that are not valid according to the [standard syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient#Syntax).
- [`function-no-unknown`](../../../lib/rules/function-no-unknown/README.md): Disallow unknown functions.

### String

Expand Down
37 changes: 37 additions & 0 deletions lib/rules/function-no-unknown/README.md
@@ -0,0 +1,37 @@
# function-no-unknown

Disallow unknown functions.

<!-- prettier-ignore -->
```css
a { transform: unknown(1); }
/** ↑
* Functions like this */
```

This rule considers functions defined in the CSS Specifications to be known.

This rule ignores double-dashed custom functions, e.g. `--custom-function()`.

## Options

### `true`

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
a { transform: unknown(1); }
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
a { transform: scale(1); }
```

<!-- prettier-ignore -->
```css
a { transform: --custom-function(1); }
```
58 changes: 58 additions & 0 deletions lib/rules/function-no-unknown/__tests__/index.js
@@ -0,0 +1,58 @@
'use strict';

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

testRule({
ruleName,
config: true,
skipBasicChecks: true,

accept: [
{
code: 'a { transform: translate(1px); }',
},
{
code: 'a { transform: TRANSLATE(1px); }',
},
{
code: 'a { transform: --custom-function(1px); }',
},
{
code: 'a { transform: scale(0.5) translate(-100%, -100%); }',
},
],

reject: [
{
code: 'a { transform: unknown(4); }',
message: messages.rejected('unknown'),
line: 1,
column: 16,
},
{
code: 'a { transform: UNKNOWN(4); }',
message: messages.rejected('UNKNOWN'),
line: 1,
column: 16,
},
{
code: 'a { width: calc(10% * unknown(1)); }',
message: messages.rejected('unknown'),
line: 1,
column: 23,
},
],
});

testRule({
ruleName,
config: true,
customSyntax: 'postcss-scss',
skipBasicChecks: true,

accept: [
{
code: 'a { $list: (list) }',
},
],
});
72 changes: 72 additions & 0 deletions lib/rules/function-no-unknown/index.js
@@ -0,0 +1,72 @@
'use strict';

const fs = require('fs');
const { URL } = require('url');
const valueParser = require('postcss-value-parser');
const functionsListPath = require('css-functions-list');

const declarationValueIndex = require('../../utils/declarationValueIndex');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
const isCustomFunction = require('../../utils/isCustomFunction');

const ruleName = 'function-no-unknown';

const messages = ruleMessages(ruleName, {
rejected: (name) => `Unexpected unknown function "${name}"`,
});

const meta = {
url: 'https://stylelint.io/user-guide/rules/list/function-no-unknown',
};

/** @type {import('stylelint').Rule} */
const rule = (primary) => {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual: primary });

if (!validOptions) {
return;
}

const path = new URL(functionsListPath.toString(), 'file://');
const functionsList = JSON.parse(fs.readFileSync(path, 'utf8'));

root.walkDecls((decl) => {
const { value } = decl;

valueParser(value).walk((node) => {
if (node.type !== 'function') {
return;
}

if (!isStandardSyntaxFunction(node)) {
return;
}

if (isCustomFunction(node.value)) {
return;
}

if (functionsList.includes(node.value.toLowerCase())) {
return;
}

report({
message: messages.rejected(node.value),
node: decl,
index: declarationValueIndex(decl) + node.sourceIndex,
result,
ruleName,
});
});
});
};
};

rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = meta;
module.exports = rule;
1 change: 1 addition & 0 deletions lib/rules/index.js
Expand Up @@ -109,6 +109,7 @@ const rules = {
),
'function-max-empty-lines': importLazy('./function-max-empty-lines'),
'function-name-case': importLazy('./function-name-case'),
'function-no-unknown': importLazy('./function-no-unknown'),
'function-parentheses-newline-inside': importLazy('./function-parentheses-newline-inside'),
'function-parentheses-space-inside': importLazy('./function-parentheses-space-inside'),
'function-url-no-scheme-relative': importLazy('./function-url-no-scheme-relative'),
Expand Down
12 changes: 12 additions & 0 deletions lib/utils/__tests__/isCustomFunction.test.js
@@ -0,0 +1,12 @@
'use strict';

const isCustomFunction = require('../isCustomFunction');

it('isCustomFunction', () => {
expect(isCustomFunction('--custom-function')).toBeTruthy();
expect(isCustomFunction('calc')).toBeFalsy();
expect(isCustomFunction('$sass-variable')).toBeFalsy();
expect(isCustomFunction('@less-variable')).toBeFalsy();
expect(isCustomFunction('var(--something)')).toBeFalsy();
expect(isCustomFunction('var( --something )')).toBeFalsy();
});
11 changes: 11 additions & 0 deletions lib/utils/isCustomFunction.js
@@ -0,0 +1,11 @@
'use strict';

/**
* Check whether a function is custom / user-defined
* https://github.com/w3c/css-houdini-drafts/issues/1007
* @param {string} func
* @returns {boolean}
*/
module.exports = function (func) {
return func.startsWith('--');
};
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -111,6 +111,7 @@
"balanced-match": "^2.0.0",
"colord": "^2.9.2",
"cosmiconfig": "^7.0.1",
"css-functions-list": "^3.0.0",
"debug": "^4.3.3",
"execall": "^2.0.0",
"fast-glob": "^3.2.11",
Expand Down

0 comments on commit 6e6bd90

Please sign in to comment.