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

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

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

<!-- prettier-ignore -->
```css
a { background-image: url('http://www.example.com/file.jpg'); }
/** ↑ ↑
* These properties and these values */
```

## Options

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

Given:

```
["data", "/^http/"]
```

The following patterns are considered violations:

<!-- prettier-ignore -->
```css
a { background-image: url('file://file.jpg'); }
```

The following patterns are _not_ considered violations:

<!-- prettier-ignore -->
```css
a { background-image: url('example.com/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('/example.com/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('//example.com/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('./path/to/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('http://www.example.com/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('https://www.example.com/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('HTTPS://www.example.com/file.jpg'); }
```

<!-- prettier-ignore -->
```css
a { background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='); }
```
71 changes: 71 additions & 0 deletions lib/rules/function-name-arguments-whitelist/__tests__/index.js
@@ -0,0 +1,71 @@
'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: [['/^http/']],

accept: [
{
code: 'a { background: url(http://example.com/file.jpg); }',
},
{
code: 'a { background: url(HTTP://example.com/file.jpg); }',
},
{
code: 'a { background: url(https://example.com/file.jpg); }',
},
],

reject: [
{
code: 'a { background: url(ftp://example.com/file.jpg); }',
message: messages.rejected('ftp'),
line: 1,
column: 21,
},
{
code:
"a { background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='); }",
message: messages.rejected('data'),
line: 1,
column: 27,
},
],
});

testRule({
ruleName,

config: [[/^http/]],

accept: [
{
code: 'a { background: url(http://example.com/file.jpg); }',
},
{
code: 'a { background: url(HTTP://example.com/file.jpg); }',
},
{
code: 'a { background: url(https://example.com/file.jpg); }',
},
],

reject: [
{
code: 'a { background: url(ftp://example.com/file.jpg); }',
message: messages.rejected('ftp'),
line: 1,
column: 21,
},
{
code:
"a { background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='); }",
message: messages.rejected('data'),
line: 1,
column: 27,
},
],
});
64 changes: 64 additions & 0 deletions lib/rules/function-name-arguments-whitelist/index.js
@@ -0,0 +1,64 @@
// @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 functionArgumentsSearch = require('../../utils/functionArgumentsSearch');
const getSchemeFromUrl = require('../../utils/getSchemeFromUrl');
const isStandardSyntaxUrl = require('../../utils/isStandardSyntaxUrl');
const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp');
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: [_.isString, _.isRegExp],
});

if (!validOptions) {
return;
}

root.walkDecls((decl) => {
functionArgumentsSearch(decl.toString().toLowerCase(), 'url', (args, index) => {
const unspacedUrlString = _.trim(args, ' ');

if (!isStandardSyntaxUrl(unspacedUrlString)) {
return;
}

const urlString = _.trim(unspacedUrlString, '\'"');
const scheme = getSchemeFromUrl(urlString);

if (scheme === null) {
return;
}

if (matchesStringOrRegExp(scheme, whitelist)) {
return;
}

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

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
3 changes: 3 additions & 0 deletions lib/rules/index.js
Expand Up @@ -145,6 +145,9 @@ const rules = {
'function-url-scheme-whitelist': importLazy(() => require('./function-url-scheme-whitelist'))(),
'function-whitelist': importLazy(() => require('./function-whitelist'))(),
'function-whitespace-after': importLazy(() => require('./function-whitespace-after'))(),
'function-name-arguments-whitelist': importLazy(() =>
require('./function-name-arguments-whitelist'),
)(),
'hue-degree-notation': importLazy(() => require('./hue-degree-notation'))(),
'keyframe-declaration-no-important': importLazy(() =>
require('./keyframe-declaration-no-important'),
Expand Down