mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
build: add stylelint rule to validate ampersands in theme selectors (#10949)
Replaces the `selector-nested-pattern-scoped` Stylelint rule with a new rule called `no-ampersand-beyond-selector-start`. The new rule will enforce that ampersands can only be used at the start of selectors in theme mixins. This will catch issues like #10889 and #10904 where having an ampersand anywhere else in the selector will work in most cases, but will break down once the mixin is wrapped in a selector.
This commit is contained in:
committed by
Andrew Seguin
parent
39341eab1f
commit
b590cec04b
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"plugins": [
|
||||
"./tools/stylelint/no-prefixes/no-prefixes.js",
|
||||
"./tools/stylelint/selector-nested-pattern-scoped/index.js",
|
||||
"./tools/stylelint/no-ampersand-beyond-selector-start/index.js",
|
||||
"./tools/stylelint/selector-no-deep/index.js",
|
||||
"./tools/stylelint/no-nested-mixin/index.js",
|
||||
"./tools/stylelint/no-concrete-rules/index.js",
|
||||
@@ -11,8 +11,7 @@
|
||||
"material/no-prefixes": [["last 2 versions", "not ie <= 10", "not ie_mob <= 10"]],
|
||||
"material/selector-no-deep": true,
|
||||
"material/no-nested-mixin": true,
|
||||
"material/selector-nested-pattern-scoped": [".*[^&]$", {
|
||||
"message": "The & operator is not allowed at the end of theme selectors.",
|
||||
"material/no-ampersand-beyond-selector-start": [true, {
|
||||
"filePattern": "-theme\\.scss$"
|
||||
}],
|
||||
"material/no-concrete-rules": [true, {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
const stylelint = require('stylelint');
|
||||
const path = require('path');
|
||||
const isStandardSyntaxRule = require('stylelint/lib/utils/isStandardSyntaxRule');
|
||||
const isStandardSyntaxSelector = require('stylelint/lib/utils/isStandardSyntaxSelector');
|
||||
|
||||
const ruleName = 'material/no-ampersand-beyond-selector-start';
|
||||
const messages = stylelint.utils.ruleMessages(ruleName, {
|
||||
expected: () => 'Ampersand is only allowed at the beginning of a selector',
|
||||
});
|
||||
|
||||
/**
|
||||
* Stylelint rule that doesn't allow for an ampersand to be used anywhere
|
||||
* except at the start of a selector. Skips private mixins.
|
||||
*
|
||||
* Based off the `selector-nested-pattern` Stylelint rule.
|
||||
* Source: https://github.com/stylelint/stylelint/blob/master/lib/rules/selector-nested-pattern/
|
||||
*/
|
||||
const plugin = stylelint.createPlugin(ruleName, (isEnabled, options) => {
|
||||
return (root, result) => {
|
||||
if (!isEnabled) return;
|
||||
|
||||
const filePattern = new RegExp(options.filePattern);
|
||||
const fileName = path.basename(root.source.input.file);
|
||||
|
||||
if (!filePattern.test(fileName)) return;
|
||||
|
||||
root.walkRules(rule => {
|
||||
if (
|
||||
rule.parent.type === 'rule' &&
|
||||
isStandardSyntaxRule(rule) &&
|
||||
isStandardSyntaxSelector(rule.selector) &&
|
||||
// Using the ampersand at the beginning is fine, anything else can cause issues in themes.
|
||||
rule.selector.indexOf('&') > 0) {
|
||||
|
||||
const mixinName = getClosestMixinName(rule);
|
||||
|
||||
// Skip rules inside private mixins.
|
||||
if (!mixinName || !mixinName.startsWith('_')) {
|
||||
stylelint.utils.report({
|
||||
result,
|
||||
ruleName,
|
||||
message: messages.expected(),
|
||||
node: rule
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Walks up the AST and finds the name of the closest mixin. */
|
||||
function getClosestMixinName(node) {
|
||||
let parent = node.parent;
|
||||
|
||||
while (parent) {
|
||||
if (parent.type === 'atrule' && parent.name === 'mixin') {
|
||||
return parent.params;
|
||||
}
|
||||
|
||||
parent = parent.parent;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
plugin.ruleName = ruleName;
|
||||
plugin.messages = messages;
|
||||
module.exports = plugin;
|
||||
@@ -1,46 +0,0 @@
|
||||
const stylelint = require('stylelint');
|
||||
const path = require('path');
|
||||
const isStandardSyntaxRule = require('stylelint/lib/utils/isStandardSyntaxRule');
|
||||
const isStandardSyntaxSelector = require('stylelint/lib/utils/isStandardSyntaxSelector');
|
||||
|
||||
const ruleName = 'material/selector-nested-pattern-scoped';
|
||||
const messages = stylelint.utils.ruleMessages(ruleName, {
|
||||
expected: selector => `Expected nested selector '${selector}' to match specified pattern`,
|
||||
});
|
||||
|
||||
/**
|
||||
* Re-implementation of the `selector-nested-pattern` Stylelint rule, allowing us
|
||||
* to scope it to a particular set of files via the custom `filePattern` option. The
|
||||
* primary use-case is to be able to apply the rule only to theme files.
|
||||
*
|
||||
* Reference: https://stylelint.io/user-guide/rules/selector-nested-pattern/
|
||||
* Source: https://github.com/stylelint/stylelint/blob/master/lib/rules/selector-nested-pattern/
|
||||
*/
|
||||
const plugin = stylelint.createPlugin(ruleName, (pattern, options) => {
|
||||
return (root, result) => {
|
||||
const selectorPattern = new RegExp(pattern);
|
||||
const filePattern = new RegExp(options.filePattern);
|
||||
const fileName = path.basename(root.source.input.file);
|
||||
|
||||
if (!filePattern.test(fileName)) return;
|
||||
|
||||
root.walkRules(rule => {
|
||||
if (rule.parent.type === 'rule' &&
|
||||
isStandardSyntaxRule(rule) &&
|
||||
isStandardSyntaxSelector(rule.selector) &&
|
||||
!selectorPattern.test(rule.selector)) {
|
||||
|
||||
stylelint.utils.report({
|
||||
result,
|
||||
ruleName,
|
||||
message: messages.expected(rule.selector),
|
||||
node: rule
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
plugin.ruleName = ruleName;
|
||||
plugin.messages = messages;
|
||||
module.exports = plugin;
|
||||
Reference in New Issue
Block a user