From c36b8d0b332ac2613210d387e928578c3afe1874 Mon Sep 17 00:00:00 2001 From: Roman Letsin Date: Tue, 17 Nov 2020 16:33:14 +0200 Subject: [PATCH] Add selector-attribute-name-disallowed-list (#4992) --- docs/user-guide/rules/list.md | 1 + lib/rules/index.js | 3 + .../README.md | 54 +++++++++++++++ .../__tests__/index.js | 63 ++++++++++++++++++ .../index.js | 66 +++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 lib/rules/selector-attribute-name-disallowed-list/README.md create mode 100644 lib/rules/selector-attribute-name-disallowed-list/__tests__/index.js create mode 100644 lib/rules/selector-attribute-name-disallowed-list/index.js diff --git a/docs/user-guide/rules/list.md b/docs/user-guide/rules/list.md index fb3b6f7d1a..7d6f4ea482 100644 --- a/docs/user-guide/rules/list.md +++ b/docs/user-guide/rules/list.md @@ -169,6 +169,7 @@ Grouped first by the following categories and then by the [_thing_](http://apps. ### Selector +- [`selector-attribute-name-disallowed-list`](../../../lib/rules/selector-attribute-name-disallowed-list/README.md): Specify a list of disallowed attribute names. - [`selector-attribute-operator-allowed-list`](../../../lib/rules/selector-attribute-operator-allowed-list/README.md): Specify a list of allowed attribute operators. - [`selector-attribute-operator-blacklist`](../../../lib/rules/selector-attribute-operator-blacklist/README.md): Specify a list of disallowed attribute operators. **(deprecated)** - [`selector-attribute-operator-disallowed-list`](../../../lib/rules/selector-attribute-operator-disallowed-list/README.md): Specify a list of disallowed attribute operators. diff --git a/lib/rules/index.js b/lib/rules/index.js index 8d51bfd21c..51ef1353a8 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -256,6 +256,9 @@ const rules = { 'selector-attribute-brackets-space-inside': importLazy(() => require('./selector-attribute-brackets-space-inside'), )(), + 'selector-attribute-name-disallowed-list': importLazy(() => + require('./selector-attribute-name-disallowed-list'), + )(), 'selector-attribute-operator-allowed-list': importLazy(() => require('./selector-attribute-operator-allowed-list'), )(), diff --git a/lib/rules/selector-attribute-name-disallowed-list/README.md b/lib/rules/selector-attribute-name-disallowed-list/README.md new file mode 100644 index 0000000000..36f7a2a759 --- /dev/null +++ b/lib/rules/selector-attribute-name-disallowed-list/README.md @@ -0,0 +1,54 @@ +# selector-attribute-name-disallowed-list + +Specify a list of disallowed attribute names. + + +```css + [class~="foo"] {} +/** ↑ + * This name */ +``` + +## Options + +`array|string|regex`: `["array", "of", /names/ or "regex"]|"name"|/regex/` + +Given: + +``` +["class", "id", "/^data-/"] +``` + +The following patterns are considered violations: + + +```css +[class*="foo"] {} +``` + + +```css +[id~="bar"] {} +``` + + +```css +[data-foo*="bar"] {} +``` + +The following patterns are _not_ considered violations: + + +```css +[lang~="en-us"] {} +``` + + +```css +[target="_blank"] {} +``` + + +```css +[href$=".bar"] {} +``` diff --git a/lib/rules/selector-attribute-name-disallowed-list/__tests__/index.js b/lib/rules/selector-attribute-name-disallowed-list/__tests__/index.js new file mode 100644 index 0000000000..c321ce391d --- /dev/null +++ b/lib/rules/selector-attribute-name-disallowed-list/__tests__/index.js @@ -0,0 +1,63 @@ +'use strict'; + +const { messages, ruleName } = require('..'); + +testRule({ + ruleName, + + config: ['class', 'id', '/^data-/'], + + accept: [ + { + code: 'a[title] { }', + }, + { + code: 'a[target="_blank"] { }', + }, + { + code: 'a[href$=".pdf"] { }', + }, + { + code: 'div[lang~="en-us"] { }', + }, + ], + + reject: [ + { + code: '[class*="foo"] { }', + message: messages.rejected('class'), + line: 1, + column: 2, + }, + { + code: '[ class*="foo" ] { }', + message: messages.rejected('class'), + line: 1, + column: 3, + }, + { + code: '[class *= "foo"] { }', + message: messages.rejected('class'), + line: 1, + column: 2, + }, + { + code: '[id~="bar"] { }', + message: messages.rejected('id'), + line: 1, + column: 2, + }, + { + code: '[data-foo*="bar"] { }', + message: messages.rejected('data-foo'), + line: 1, + column: 2, + }, + { + code: '[ data-foo *= "bar" ] { }', + message: messages.rejected('data-foo'), + line: 1, + column: 3, + }, + ], +}); diff --git a/lib/rules/selector-attribute-name-disallowed-list/index.js b/lib/rules/selector-attribute-name-disallowed-list/index.js new file mode 100644 index 0000000000..28bf32f489 --- /dev/null +++ b/lib/rules/selector-attribute-name-disallowed-list/index.js @@ -0,0 +1,66 @@ +// @ts-nocheck + +'use strict'; + +const _ = require('lodash'); +const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule'); +const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp'); +const parseSelector = require('../../utils/parseSelector'); +const report = require('../../utils/report'); +const ruleMessages = require('../../utils/ruleMessages'); +const validateOptions = require('../../utils/validateOptions'); + +const ruleName = 'selector-attribute-name-disallowed-list'; + +const messages = ruleMessages(ruleName, { + rejected: (name) => `Unexpected name "${name}"`, +}); + +function rule(listInput) { + const list = [].concat(listInput); + + return (root, result) => { + const validOptions = validateOptions(result, ruleName, { + actual: list, + possible: [_.isString, _.isRegExp], + }); + + if (!validOptions) { + return; + } + + root.walkRules((ruleNode) => { + if (!isStandardSyntaxRule(ruleNode)) { + return; + } + + if (!ruleNode.selector.includes('[') || !ruleNode.selector.includes('=')) { + return; + } + + parseSelector(ruleNode.selector, result, ruleNode, (selectorTree) => { + selectorTree.walkAttributes((attributeNode) => { + const attributeName = attributeNode.qualifiedAttribute; + + if (!matchesStringOrRegExp(attributeName, list)) { + return; + } + + report({ + message: messages.rejected(attributeName), + node: ruleNode, + index: attributeNode.sourceIndex + attributeNode.offsetOf('attribute'), + result, + ruleName, + }); + }); + }); + }); + }; +} + +rule.primaryOptionArray = true; + +rule.ruleName = ruleName; +rule.messages = messages; +module.exports = rule;