Skip to content

Commit

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

* fmt

* Create short-cobras-join.md
  • Loading branch information
romainmenke authored and emmacharp committed May 7, 2024
1 parent a753a55 commit 94cc82d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-cobras-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": patch
---

Fixed: `selector-max-attribute` end positions
41 changes: 33 additions & 8 deletions lib/rules/selector-max-attribute/__tests__/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -158,48 +158,62 @@ testRule({
message: messages.expected('[type="text"][name="message"][data-attribute="value"]', 2),
line: 1,
column: 1,
endLine: 1,
endColumn: 54,
},
{
code: '[type="text"][name="message"][disabled] {}',
description: 'greater than max classes with attribute selector without value',
message: messages.expected('[type="text"][name="message"][disabled]', 2),
line: 1,
column: 1,
endLine: 1,
endColumn: 40,
},
{
code: '[type="text"] [name="message"] [data-attribute="value"] {}',
description: 'compound selector: greater than max classes',
message: messages.expected('[type="text"] [name="message"] [data-attribute="value"]', 2),
line: 1,
column: 1,
endLine: 1,
endColumn: 56,
},
{
code: '[type="text"], \n[type="number"][name="quality"][data-attribute="value"] {}',
description: 'multiple selectors: greater than max classes',
message: messages.expected('[type="number"][name="quality"][data-attribute="value"]', 2),
line: 2,
column: 1,
endLine: 2,
endColumn: 56,
},
{
code: '[type="text"], \n[type="number"][name="quality"][disabled] {}',
description: 'multiple selectors: greater than max classes',
message: messages.expected('[type="number"][name="quality"][disabled]', 2),
line: 2,
column: 1,
endLine: 2,
endColumn: 42,
},
{
code: ':not([type="text"][name="message"][data-attribute="value"]) {}',
description: ':not(): greater than max classes, inside',
message: messages.expected('[type="text"][name="message"][data-attribute="value"]', 2),
message: messages.expected(':not([type="text"][name="message"][data-attribute="value"])', 2),
line: 1,
column: 6,
column: 1,
endLine: 1,
endColumn: 60,
},
{
code: ':not([type="text"][name="message"][disabled]) {}',
description: ':not(): greater than max classes, inside',
message: messages.expected('[type="text"][name="message"][disabled]', 2),
message: messages.expected(':not([type="text"][name="message"][disabled])', 2),
line: 1,
column: 6,
column: 1,
endLine: 1,
endColumn: 46,
},
{
code: '[type="text"][name="message"][data-attribute="value"] :not([data-attribute-2="value"]) {}',
Expand All @@ -210,6 +224,8 @@ testRule({
),
line: 1,
column: 1,
endLine: 1,
endColumn: 87,
},
{
code: '[type="text"][name="message"][data-attribute="value"]:not([disabled]) {}',
Expand All @@ -220,16 +236,17 @@ testRule({
),
line: 1,
column: 1,
endLine: 1,
endColumn: 70,
},
{
code: '[type="text"] { &:hover > [data-attribute="value"][data-attribute-2="value"] {} }',
description: 'nested selectors: greater than max classes',
message: messages.expected(
'[type="text"]:hover > [data-attribute="value"][data-attribute-2="value"]',
2,
),
message: messages.expected('&:hover > [data-attribute="value"][data-attribute-2="value"]', 2),
line: 1,
column: 17,
endLine: 1,
endColumn: 77,
},
],
});
Expand Down Expand Up @@ -262,6 +279,8 @@ testRule({
message: messages.expected('[type="text"][name="message"]', 0),
line: 1,
column: 17,
endLine: 1,
endColumn: 46,
},
],
});
Expand Down Expand Up @@ -299,12 +318,16 @@ testRule({
message: messages.expected('[foo]', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 6,
},
{
code: '[not-my-attr] {}',
message: messages.expected('[not-my-attr]', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 14,
},
],
});
Expand All @@ -325,6 +348,8 @@ testRule({
message: messages.expected('[foo]', 0),
line: 1,
column: 1,
endLine: 1,
endColumn: 6,
},
],
});
37 changes: 19 additions & 18 deletions lib/rules/selector-max-attribute/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
'use strict';

const resolvedNestedSelector = require('postcss-resolve-nested-selector');
const validateTypes = require('../../utils/validateTypes.cjs');
const flattenNestedSelectorsForRule = require('../../utils/flattenNestedSelectorsForRule.cjs');
const isContextFunctionalPseudoClass = require('../../utils/isContextFunctionalPseudoClass.cjs');
const isNonNegativeInteger = require('../../utils/isNonNegativeInteger.cjs');
const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs');
const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector.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,14 +50,15 @@ 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 and context functional pseudo-classes
if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, ruleNode);
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type !== 'attribute') {
Expand All @@ -76,31 +77,31 @@ 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;

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

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
});
});
});
};
};
Expand Down
38 changes: 19 additions & 19 deletions lib/rules/selector-max-attribute/index.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import resolvedNestedSelector from 'postcss-resolve-nested-selector';

import { isRegExp, isString } from '../../utils/validateTypes.mjs';
import flattenNestedSelectorsForRule from '../../utils/flattenNestedSelectorsForRule.mjs';
import isContextFunctionalPseudoClass from '../../utils/isContextFunctionalPseudoClass.mjs';
import isNonNegativeInteger from '../../utils/isNonNegativeInteger.mjs';
import isStandardSyntaxRule from '../../utils/isStandardSyntaxRule.mjs';
import isStandardSyntaxSelector from '../../utils/isStandardSyntaxSelector.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,14 +46,15 @@ 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 and context functional pseudo-classes
if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, ruleNode);
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type !== 'attribute') {
Expand All @@ -73,31 +73,31 @@ 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;

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

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
});
});
});
};
};
Expand Down

0 comments on commit 94cc82d

Please sign in to comment.