Skip to content

Commit b3c0b37

Browse files
committed
build: ampersand selector lint rule not catching some cases
I noticed this while working on something else. The ampersand selector mixin looks for one `&` instance and then gives up, but there might be other ampersands inside the same selector which are invalid. These changes update the logic to be a bit more robust and remove the limitation where failures weren't reported on private mixins.
1 parent 3284496 commit b3c0b37

File tree

1 file changed

+23
-29
lines changed

1 file changed

+23
-29
lines changed

tools/stylelint/no-ampersand-beyond-selector-start.ts

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,43 +37,37 @@ const plugin = createPlugin(ruleName, (isEnabled: boolean, _options?) => {
3737
}
3838

3939
root.walkRules(rule => {
40-
if (
41-
rule.parent.type === 'rule' &&
42-
isStandardSyntaxRule(rule) &&
43-
isStandardSyntaxSelector(rule.selector) &&
44-
// Using the ampersand at the beginning is fine, anything else can cause issues in themes.
45-
rule.selector.indexOf('&') > 0) {
46-
47-
const mixinName = getClosestMixinName(rule);
48-
49-
// Skip rules inside private mixins.
50-
if (!mixinName || !mixinName.startsWith('_')) {
51-
utils.report({
52-
result,
53-
ruleName,
54-
message: messages.expected(),
55-
node: rule
56-
});
57-
}
40+
if (rule.parent.type === 'rule' &&
41+
isStandardSyntaxRule(rule) &&
42+
isStandardSyntaxSelector(rule.selector) &&
43+
hasInvalidAmpersandUsage(rule.selector)) {
44+
utils.report({
45+
result,
46+
ruleName,
47+
message: messages.expected(),
48+
node: rule
49+
});
5850
}
5951
});
6052
};
53+
});
6154

62-
/** Walks up the AST and finds the name of the closest mixin. */
63-
function getClosestMixinName(node: Node): string | undefined {
64-
let parent = node.parent;
65-
66-
while (parent) {
67-
if (parent.type === 'atrule' && parent.name === 'mixin') {
68-
return parent.params;
69-
}
55+
function hasInvalidAmpersandUsage(selector: string): boolean {
56+
let index = selector.indexOf('&');
7057

71-
parent = parent.parent;
58+
while (index > -1) {
59+
// The ampersand is invalid if it's used after the start of
60+
// the selector and not in a combination with another selector.
61+
if (index > 0 && selector[index - 1] === ' ' && (index === selector.length - 1 ||
62+
selector[index + 1] === ' ' || selector[index + 1] === ',')) {
63+
return true;
7264
}
7365

74-
return undefined;
66+
index = selector.indexOf('&', index + 1);
7567
}
76-
});
68+
69+
return false;
70+
}
7771

7872
plugin.ruleName = ruleName;
7973
plugin.messages = messages;

0 commit comments

Comments
 (0)