Skip to content

Commit

Permalink
Fix selector-max-universal end positions (#7597)
Browse files Browse the repository at this point in the history
* Fix `selector-max-universal` end positions

* Create sharp-pots-rush.md
  • Loading branch information
romainmenke authored Apr 8, 2024
1 parent 3cfe00a commit 84d307e
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-pots-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": patch
---

Fixed: `selector-max-universal` end positions
38 changes: 31 additions & 7 deletions lib/rules/selector-max-universal/__tests__/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ testRule({
},
{
code: ':not(*) {}',
message: messages.expected('*', 0),
message: messages.expected(':not(*)', 0),
line: 1,
column: 6,
column: 1,
endLine: 1,
endColumn: 7,
endColumn: 8,
},
],
});
Expand Down Expand Up @@ -259,11 +259,11 @@ testRule({
{
code: '* { &:hover > * * {} }',
description: 'nested selectors: greater than max universal selectors',
message: messages.expected('*:hover > * *', 2),
message: messages.expected('&:hover > * *', 2),
line: 1,
column: 5,
endLine: 1,
endColumn: 21,
endColumn: 18,
},
],
});
Expand Down Expand Up @@ -338,72 +338,96 @@ testRule({
message: messages.expected('*', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 2,
},
{
code: '.bar * { @include test {} }',
message: messages.expected('.bar *', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 7,
},
{
code: '*.bar { @include test {} }',
message: messages.expected('*.bar', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 6,
},
{
code: '* [lang^=en] { @include test {} }',
message: messages.expected('* [lang^=en]', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 13,
},
{
code: '*[lang^=en] { @include test {} }',
message: messages.expected('*[lang^=en]', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 12,
},
{
code: '.foo, .bar, *.baz { @include test {} }',
message: messages.expected('*.baz', 0),
line: 1,
column: 13,
endLine: 1,
endColumn: 18,
},
{
code: '* #id { @include test {} }',
message: messages.expected('* #id', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 6,
},
{
code: '*#id { @include test {} }',
message: messages.expected('*#id', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 5,
},
{
code: '.foo* { @include test {} }',
message: messages.expected('.foo*', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 6,
},
{
code: '*:hover { @include test {} }',
message: messages.expected('*:hover', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 8,
},
{
code: ':not(*) { @include test {} }',
message: messages.expected('*', 0),
message: messages.expected(':not(*)', 0),
line: 1,
column: 6,
column: 1,
endLine: 1,
endColumn: 8,
},
{
code: '@include test { * {} }',
message: messages.expected('*', 0),
line: 1,
column: 17,
endLine: 1,
endColumn: 18,
},
],
});
51 changes: 23 additions & 28 deletions lib/rules/selector-max-universal/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
// please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
'use strict';

const resolvedNestedSelector = require('postcss-resolve-nested-selector');
const selectorParser = require('postcss-selector-parser');
const flattenNestedSelectorsForRule = require('../../utils/flattenNestedSelectorsForRule.cjs');
const isNonNegativeInteger = require('../../utils/isNonNegativeInteger.cjs');
const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs');
const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector.cjs');
const validateTypes = require('../../utils/validateTypes.cjs');
const optionsMatches = require('../../utils/optionsMatches.cjs');
const parseSelector = require('../../utils/parseSelector.cjs');
const report = require('../../utils/report.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');
Expand Down Expand Up @@ -50,15 +49,16 @@ const rule = (primary, secondaryOptions) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(selectorNode, ruleNode) {
const count = selectorNode.reduce((total, childNode) => {
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors
// All logical combinations will be resolved as nested selector in `postcss-resolve-nested-selector`
if (childNode.type === 'selector') {
checkSelector(childNode, ruleNode);
checkSelector(childNode, selectorNode, ruleNode);
}

const prevChildNode = childNode.prev();
Expand All @@ -74,42 +74,37 @@ const rule = (primary, secondaryOptions) => {
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
const selector = selectorNode.toString();
const index = selectorNode.first?.sourceIndex ?? 0;
const selectorStr = selectorNode.toString().trim();

report({
ruleName,
result,
node: ruleNode,
message: messages.expected,
messageArgs: [selector, primary],
word: selector,
messageArgs: [selectorStr, primary],
index,
endIndex: index + selectorStr.length,
});
}
}

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

/** @type {string[]} */
const selectors = [];
if (!isStandardSyntaxSelector(ruleNode.selector)) return;

selectorParser()
.astSync(ruleNode.selector)
.walk((node) => {
if (node.type === 'selector') {
selectors.push(String(node).trim());
}
});
flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);

for (const selector of selectors) {
for (const resolvedSelector of resolvedNestedSelector(selector, ruleNode)) {
parseSelector(resolvedSelector, result, ruleNode, (container) =>
checkSelector(container, ruleNode),
);
}
}
resolvedSelector.walk((childSelector) => {
if (childSelector.type !== 'selector') return;

checkSelector(childSelector, selector, ruleNode);
});
});
});
});
};
};
Expand Down
52 changes: 23 additions & 29 deletions lib/rules/selector-max-universal/index.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import resolvedNestedSelector from 'postcss-resolve-nested-selector';
import selectorParser from 'postcss-selector-parser';

import flattenNestedSelectorsForRule from '../../utils/flattenNestedSelectorsForRule.mjs';
import isNonNegativeInteger from '../../utils/isNonNegativeInteger.mjs';
import isStandardSyntaxRule from '../../utils/isStandardSyntaxRule.mjs';
import isStandardSyntaxSelector from '../../utils/isStandardSyntaxSelector.mjs';
import { isString } from '../../utils/validateTypes.mjs';
import optionsMatches from '../../utils/optionsMatches.mjs';
import parseSelector from '../../utils/parseSelector.mjs';
import report from '../../utils/report.mjs';
import ruleMessages from '../../utils/ruleMessages.mjs';
import validateOptions from '../../utils/validateOptions.mjs';
Expand Down Expand Up @@ -47,15 +45,16 @@ const rule = (primary, secondaryOptions) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(selectorNode, ruleNode) {
const count = selectorNode.reduce((total, childNode) => {
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors
// All logical combinations will be resolved as nested selector in `postcss-resolve-nested-selector`
if (childNode.type === 'selector') {
checkSelector(childNode, ruleNode);
checkSelector(childNode, selectorNode, ruleNode);
}

const prevChildNode = childNode.prev();
Expand All @@ -71,42 +70,37 @@ const rule = (primary, secondaryOptions) => {
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
const selector = selectorNode.toString();
const index = selectorNode.first?.sourceIndex ?? 0;
const selectorStr = selectorNode.toString().trim();

report({
ruleName,
result,
node: ruleNode,
message: messages.expected,
messageArgs: [selector, primary],
word: selector,
messageArgs: [selectorStr, primary],
index,
endIndex: index + selectorStr.length,
});
}
}

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

/** @type {string[]} */
const selectors = [];
if (!isStandardSyntaxSelector(ruleNode.selector)) return;

selectorParser()
.astSync(ruleNode.selector)
.walk((node) => {
if (node.type === 'selector') {
selectors.push(String(node).trim());
}
});
flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);

for (const selector of selectors) {
for (const resolvedSelector of resolvedNestedSelector(selector, ruleNode)) {
parseSelector(resolvedSelector, result, ruleNode, (container) =>
checkSelector(container, ruleNode),
);
}
}
resolvedSelector.walk((childSelector) => {
if (childSelector.type !== 'selector') return;

checkSelector(childSelector, selector, ruleNode);
});
});
});
});
};
};
Expand Down

0 comments on commit 84d307e

Please sign in to comment.