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-name-arguments-allowed-list #4816

96 changes: 96 additions & 0 deletions lib/rules/function-name-arguments-whitelist/README.md
@@ -0,0 +1,96 @@
# function-name-arguments-whitelist

Specify a whitelist of allowed property and value pairs within declarations.

<!-- prettier-ignore -->
```css
a { text-transform: uppercase; }
/** ↑ ↑
* These properties and these values */
```

## Options

`object`: `{"unprefixed-function-name": ["string", "/regex/", /regex/] }`

If a property name is found in the object, only its whitelisted property values are allowed. This rule complains about all non-matching values. (If the property name is not included in the object, anything goes.)

If a property name is surrounded with `"/"` (e.g. `"/^animation/"`), it is interpreted as a regular expression. This allows, for example, easy targeting of shorthands: `/^animation/` will match `animation`, `animation-duration`, `animation-timing-function`, etc.

The same goes for values. Keep in mind that a regular expression value is matched against the entire value of the declaration, not specific parts of it. For example, a value like `"10px solid rgba( 255 , 0 , 0 , 0.5 )"` will _not_ match `"/^solid/"` (notice beginning of the line boundary) but _will_ match `"/\\s+solid\\s+/"` or `"/\\bsolid\\b/"`.

Be careful with regex matching not to accidentally consider quoted string values and `url()` arguments. For example, `"/red/"` will match value such as `"1px dotted red"` as well as `"\"red\""` and `"white url(/mysite.com/red.png)"`.

Given:

```
{
"transform": ["/scale/"],
"whitespace": ["nowrap"],
"/color/": ["/^green/"]
}
```

The following patterns are considered violations:

<!-- prettier-ignore -->
```css
a { whitespace: pre; }
```

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

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

<!-- prettier-ignore -->
```css
a { color: pink; }
```

<!-- prettier-ignore -->
```css
a { background-color: pink; }
```

The following patterns are _not_ considered violations:

<!-- prettier-ignore -->
```css
a { color: pink; }
```

<!-- prettier-ignore -->
```css
a { whitespace: nowrap; }
```

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

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

<!-- prettier-ignore -->
```css
a { color: green; }
```

<!-- prettier-ignore -->
```css
a { background-color: green; }
```

<!-- prettier-ignore -->
```css
a { background: pink; }
```
78 changes: 78 additions & 0 deletions lib/rules/function-name-arguments-whitelist/__tests__/index.js
@@ -0,0 +1,78 @@
'use strict';

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

testRule({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your best next step is to change the tests so they align with the intended behaviour of the rule. I suggest starting with:

'use strict';

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

testRule({
	ruleName,
	config: [
		{
			brightness: ['50%'],
			"/^url/": ['/^images/', '/^vendor/'],
		},
	],
	accept: [
		{
			code: 'a { filter: blur(20px); }',
		},
		{
			code: 'a { filter: brightness(50%); }',
		},
		{
			code: 'a { background: url(images/x.jpg); }',
		},
		{
			code: 'a { background: url(vendor/x.jpg); }',
		},
		{
			code: 'a { background: url-x(vendor/x.jpg); }',
		},
	],

	reject: [
		{
			code: 'a { filter: brightness(25%); }',
			message: messages.rejected('brightness', '25%'),
			line: 1,
			column: 16,
		},
		{
			code: 'a { background: url(x.jpg); }',
			message: messages.rejected('url', 'x.jpg'),
			line: 1,
			column: 16,
		},
		{
			code: 'a { background: url-x(x.jpg); }',
			message: messages.rejected('url-x', 'x.jpg'),
			line: 1,
			column: 16,
		}
	],
});

The config is saying:

  • for the brightness function the parameters can only be 50%
  • for any function that starts with url the parameters must start with either images or vendor

Then there are accept and reject test cases for this config.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should validate against the background this contains either base64 string or URL function.

If URL present means have to validate starts with strings

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should validate against the background this contains either base64 string or URL function.

The rule isn't concerned with the type of arguments. It should always treat the arguments as a string for comparsion.

ruleName,

config: [
{
transform: ['/scale/'],
whitespace: ['nowrap'],
'/color/': ['/^green/'],
},
],

accept: [
{
code: 'div { whitespace: nowrap; }',
},
{
code: 'a { transform: scale(1, 1); }',
},
{
code: 'a { -webkit-transform: scale(1, 1); }',
},
{
code: 'a { color: green; }',
},
{
code: 'a { background-color: green; }',
},
],

reject: [
{
code: 'div { whitespace: pre; }',
message: messages.rejected('whitespace', 'pre'),
line: 1,
column: 7,
},
{
code: 'a { transform: translate(1, 1); }',
message: messages.rejected('transform', 'translate(1, 1)'),
line: 1,
column: 5,
},
{
code: 'a { -webkit-transform: translate(1, 1); }',
message: messages.rejected('-webkit-transform', 'translate(1, 1)'),
line: 1,
column: 5,
},
{
code: 'a { color: pink; }',
message: messages.rejected('color', 'pink'),
line: 1,
column: 5,
},
{
code: 'a { background-color: pink; }',
message: messages.rejected('background-color', 'pink'),
line: 1,
column: 5,
},
],
});

testRule({
ruleName,
config: { position: ['static'] },
skipBasicChecks: true,
accept: [
{
code: 'a { font-size: 1em; }',
description: 'irrelevant CSS',
},
],
});
58 changes: 58 additions & 0 deletions lib/rules/function-name-arguments-whitelist/index.js
@@ -0,0 +1,58 @@
// @ts-nocheck
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the declaration-property-unit-whitelist as a blueprint rather than function-url-scheme-whitelist one. The latter only accepts an array of whitelisted values, whereas this rule should accept an object of function and parameter pairs (just like declaration-property-unit-whitelist accepts an object of property and unit pairs), .e.g.:

{
  "rules": {
    "function-name-arguments-whitelist": {
      "url": ["/^images/", "^fonts/"],
      "blur": ["50%"]
    }
  }
}

The declaration-property-unit-whitelist looks for units within a declaration's value and compares it to the declaration property. You'll need to adapt the code so that this new rule looks for functions within a declaration's value, and compares the name of the function to the parameters of the function.


'use strict';

const _ = require('lodash');
const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp');
const postcss = require('postcss');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');

const ruleName = 'function-name-arguments-whitelist';

const messages = ruleMessages(ruleName, {
rejected: (property, value) => `Unexpected arguments "${value}" for function "${property}"`,
});

function rule(whitelist) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: whitelist,
possible: [_.isObject],
});

if (!validOptions) {
return;
}

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

const unprefixedProp = postcss.vendor.unprefixed(prop);
const propWhitelist = _.find(whitelist, (list, propIdentifier) =>
matchesStringOrRegExp(unprefixedProp, propIdentifier),
);

if (_.isEmpty(propWhitelist)) {
return;
}

if (matchesStringOrRegExp(value, propWhitelist)) {
return;
}

report({
message: messages.rejected(prop, value),
node: decl,
result,
ruleName,
});
});
};
}

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;