Skip to content

Commit

Permalink
Merge branch 'master' into browser-bundle
Browse files Browse the repository at this point in the history
# By Mike Allanson (2) and others
* master:
  Fix changelog formatting
  A small refactor (#4837)
  Update CHANGELOG.md
  Add ignoreContextFunctionalPseudoClasses to selector-max-id (#4835)
  • Loading branch information
m-allanson committed Jun 22, 2020
2 parents f375429 + e652b3e commit 48ccd71
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project are documented in this file.

## Head

- Added: `ignoreContextFunctionalPseudoClasses` to `selector-max-id` ([#4835](https://github.com/stylelint/stylelint/pull/4835)).

## 13.6.1

- Fixed: `max-empty-lines` TypeError from inline comment with autofix and sugarss syntax ([#4821](https://github.com/stylelint/stylelint/pull/4821)).
Expand Down
4 changes: 2 additions & 2 deletions lib/getPostcssResult.js
Expand Up @@ -43,7 +43,7 @@ module.exports = function (stylelint, options = {}) {
let syntax = null;

if (stylelint._options.customSyntax) {
syntax = resolveCustomSyntax(stylelint._options.customSyntax);
syntax = getCustomSyntax(stylelint._options.customSyntax);
} else if (stylelint._options.syntax) {
if (stylelint._options.syntax === 'css') {
syntax = cssSyntax(stylelint);
Expand Down Expand Up @@ -114,7 +114,7 @@ module.exports = function (stylelint, options = {}) {
* @param {CustomSyntax} customSyntax
* @returns {Syntax}
*/
function resolveCustomSyntax(customSyntax) {
function getCustomSyntax(customSyntax) {
let resolved;

if (typeof customSyntax === 'object') {
Expand Down
29 changes: 29 additions & 0 deletions lib/rules/selector-max-id/README.md
Expand Up @@ -74,3 +74,32 @@ The following patterns are _not_ considered violations:
/* `#bar` is inside `:not()`, so it is evaluated separately */
#foo #bar:not(#baz) {}
```

### `ignoreContextFunctionalPseudoClasses: ["/regex/", /regex/, "non-regex"]`

Ignore selectors inside of specified [functional pseudo-classes](https://drafts.csswg.org/selectors-4/#pseudo-classes) that provide [evaluation contexts](https://drafts.csswg.org/selectors-4/#specificity-rules).

Given:

```js
[":not", /^:(h|H)as$/];
```

The following patterns are considered violations:

<!-- prettier-ignore -->
```css
a:matches(#foo) {}
```

While the following patters are _not_ considered violations:

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

<!-- prettier-ignore -->
```css
a:has(#foo) {}
```
25 changes: 25 additions & 0 deletions lib/rules/selector-max-id/__tests__/index.js
Expand Up @@ -459,3 +459,28 @@ testRule({
},
],
});

testRule({
ruleName,
config: [0, { ignoreContextFunctionalPseudoClasses: [':not', /^:(h|H)as$/] }],

accept: [
{
code: 'a:not(#foo) {}',
description: 'selector within ignored pseudo-class (string input)',
},
{
code: 'a:has(#foo) {}',
description: 'selector within ignored pseudo-class (regex input)',
},
],
reject: [
{
code: 'a:matches(#foo) {}',
description: 'selector within non-ignored pseudo class',
message: messages.expected('#foo', 0),
line: 1,
column: 11,
},
],
});
44 changes: 34 additions & 10 deletions lib/rules/selector-max-id/index.js
Expand Up @@ -2,8 +2,10 @@

'use strict';

const _ = require('lodash');
const isLogicalCombination = require('../../utils/isLogicalCombination');
const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
const optionsMatches = require('../../utils/optionsMatches');
const parseSelector = require('../../utils/parseSelector');
const report = require('../../utils/report');
const resolvedNestedSelector = require('postcss-resolve-nested-selector');
Expand All @@ -17,25 +19,40 @@ const messages = ruleMessages(ruleName, {
`Expected "${selector}" to have no more than ${max} ID ${max === 1 ? 'selector' : 'selectors'}`,
});

function rule(max) {
function rule(max, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: max,
possible: [
function (max) {
return typeof max === 'number' && max >= 0;
const validOptions = validateOptions(
result,
ruleName,
{
actual: max,
possible: [
function (max) {
return typeof max === 'number' && max >= 0;
},
],
},
{
actual: options,
possible: {
ignoreContextFunctionalPseudoClasses: [_.isString, _.isRegExp],
},
],
});
optional: true,
},
);

if (!validOptions) {
return;
}

function checkSelector(selectorNode, ruleNode) {
const count = selectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors and logical combinations
if (childNode.type === 'selector' || isLogicalCombination(childNode)) {
// Only traverse inside actual selectors and logical combinations that are not part of ignored functional pseudo-classes
if (
childNode.type === 'selector' ||
(isLogicalCombination(childNode) &&
!isIgnoredContextFunctionalPseudoClass(childNode, options))
) {
checkSelector(childNode, ruleNode);
}

Expand All @@ -53,6 +70,13 @@ function rule(max) {
}
}

function isIgnoredContextFunctionalPseudoClass(node, options) {
return (
node.type === 'pseudo' &&
optionsMatches(options, 'ignoreContextFunctionalPseudoClasses', node.value)
);
}

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

0 comments on commit 48ccd71

Please sign in to comment.