Skip to content

Commit

Permalink
add selector-not-notation
Browse files Browse the repository at this point in the history
  • Loading branch information
Mouvedia committed Mar 18, 2022
1 parent e026f57 commit 1237872
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/user-guide/rules/list.md
Expand Up @@ -198,6 +198,7 @@ Within each cateogory, the rules are grouped by the [_thing_](http://apps.workfl
- [`selector-nested-pattern`](../../../lib/rules/selector-nested-pattern/README.md): Specify a pattern for the selectors of rules nested within rules.
- [`selector-no-qualifying-type`](../../../lib/rules/selector-no-qualifying-type/README.md): Disallow qualifying a selector by type.
- [`selector-no-vendor-prefix`](../../../lib/rules/selector-no-vendor-prefix/README.md): Disallow vendor prefixes for selectors (Autofixable).
- [`selector-not-notation`](../../../lib/rules/selector-not-notation/README.md): Specify simple or complex notation for the usage of `:not()`.
- [`selector-pseudo-class-allowed-list`](../../../lib/rules/selector-pseudo-class-allowed-list/README.md): Specify a list of allowed pseudo-class selectors.
- [`selector-pseudo-class-disallowed-list`](../../../lib/rules/selector-pseudo-class-disallowed-list/README.md): Specify a list of disallowed pseudo-class selectors.
- [`selector-pseudo-element-allowed-list`](../../../lib/rules/selector-pseudo-element-allowed-list/README.md): Specify a list of allowed pseudo-element selectors.
Expand Down
1 change: 1 addition & 0 deletions lib/rules/index.js
Expand Up @@ -214,6 +214,7 @@ const rules = {
'selector-nested-pattern': importLazy('./selector-nested-pattern'),
'selector-no-qualifying-type': importLazy('./selector-no-qualifying-type'),
'selector-no-vendor-prefix': importLazy('./selector-no-vendor-prefix'),
'selector-not-notation': importLazy('./selector-not-notation'),
'selector-pseudo-class-allowed-list': importLazy('./selector-pseudo-class-allowed-list'),
'selector-pseudo-class-case': importLazy('./selector-pseudo-class-case'),
'selector-pseudo-class-disallowed-list': importLazy('./selector-pseudo-class-disallowed-list'),
Expand Down
57 changes: 57 additions & 0 deletions lib/rules/selector-not-notation/README.md
@@ -0,0 +1,57 @@
# selector-not-notation

## Options

`string`: `"simple"|"complex"`

### `"simple"`

The following patterns are considered problems:

<!-- prettier-ignore -->
```css
:not(a, div) {}
```

<!-- prettier-ignore -->
```css
:not(a.foo) {}
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
:not(a):not(div) {}
```

<!-- prettier-ignore -->
```css
:not(a) {}
```

### `"complex"`

The following pattern is considered a problem:

<!-- prettier-ignore -->
```css
:not(a):not(div) {}
```

The following patterns are _not_ considered problems:

<!-- prettier-ignore -->
```css
:not(a, div) {}
```

<!-- prettier-ignore -->
```css
:not(a.foo) {}
```

<!-- prettier-ignore -->
```css
:not(a).foo:not(:empty) {}
```
95 changes: 95 additions & 0 deletions lib/rules/selector-not-notation/__tests__/index.js
@@ -0,0 +1,95 @@
'use strict';

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

testRule({
ruleName,
config: ['simple'],
fix: false,

accept: [
{
code: ':not() {}',
},
{
code: ':not( a ) {}',
},
{
code: ':nOt(a) {}',
},
{
code: ':not(*) {}',
},
{
code: ':not(:link) {}',
},
{
code: ':not(.foo) {}',
},
{
code: ':not([title]) {}',
},
],

reject: [
{
code: ':not(:not()) {}',
fixed: ':not()',
message: messages.expected('simple'),
line: 1,
column: 1,
},
{
code: ':not(::before) {}',
fixed: ':not()',
message: messages.expected('simple'),
line: 1,
column: 1,
},
{
code: ':not(a, div) {}',
fixed: ':not(a):not(div) {}',
message: messages.expected('simple'),
line: 1,
column: 1,
},
{
code: ':not(a.foo) {}',
unfixable: true,
message: messages.expected('simple'),
line: 1,
column: 1,
},
],
});

testRule({
ruleName,
config: ['complex'],
fix: false,

accept: [
{
code: ':not()::after {}',
},
{
code: ':not(a, div) {}',
},
{
code: ':not(a.foo) {}',
},
{
code: ':not(a).foo:not(:empty) {}',
},
],

reject: [
{
code: ':not(.foo):not(a) {}',
fixed: ':not(.foo, a) {} {}',
message: messages.expected('complex'),
line: 1,
column: 11,
},
],
});
117 changes: 117 additions & 0 deletions lib/rules/selector-not-notation/index.js
@@ -0,0 +1,117 @@
'use strict';

const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
const parseSelector = require('../../utils/parseSelector');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const {
isPseudoClass,
isAttribute,
isClassName,
isUniversal,
isIdentifier,
isTag,
} = require('postcss-selector-parser');

const ruleName = 'selector-not-notation';
const messages = ruleMessages(ruleName, {
expected: (type) => `Expected ${type} :not() pseudo-class notation`,
});
const meta = { url: 'https://stylelint.io/user-guide/rules/list/selector-not-notation' };

/** @typedef {import('postcss-selector-parser').Node} Node */

/**
* @param {Node} node
* @returns {boolean}
*/
const isSimpleSelector = (node) =>
isPseudoClass(node) ||
isAttribute(node) ||
isClassName(node) ||
isUniversal(node) ||
isIdentifier(node) ||
isTag(node);

/**
* @param {Node} node
* @returns {boolean}
*/
const isNot = (node) => isPseudoClass(node) && node.value.toLowerCase() === ':not';

/**
* @param {import('postcss-selector-parser').Pseudo} node
* @returns {boolean}
*/
const isSimple = ({ nodes: list }) => {
if (list.length > 1) return false;

const [first, second] = list[0].nodes; // list is never empty

if (!first) return true;

if (second) return false;

return isSimpleSelector(first) && !isNot(first);
};

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

if (!validOptions) {
return;
}

root.walkRules((ruleNode) => {
if (!isStandardSyntaxRule(ruleNode)) {
return;
}

const selector = ruleNode.selector;

/*const fixedSelector = */ parseSelector(selector, result, ruleNode, (container) => {
container.walkPseudos((pseudo) => {
if (!isNot(pseudo)) return;

if (primary === 'complex') {
const prev = pseudo.prev();
const hasConsecutiveNot = prev && isNot(prev);

if (!hasConsecutiveNot) return;

if (context.fix) {
/* TODO */
return;
}
} else {
if (isSimple(pseudo)) return;

if (context.fix) {
/* TODO */
return;
}
}

report({
message: messages.expected(primary),
node: ruleNode,
index: pseudo.sourceIndex,
result,
ruleName,
});
});
});
});
};
};

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

0 comments on commit 1237872

Please sign in to comment.