0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-04 10:39:43 +08:00
discourse/stylelint-rules/uc-classes-in-where.mjs
David Taylor ce04edc1ae
DEV: Enforce :where() for .uc-* upcoming-change classes via stylelint (#41917)
Upcoming changes with `body_class: true` add a `uc-*` class to `<body>`.
This class is temporary, so we do not want core/theme/plugin CSS to
become dependent on it. Therefore we must not allow it to contribute
specificity to selectors.

This commit adds a `discourse/uc-classes-in-where` which reports any
misuse of `.uc-*` classes, fixes up some existing cases, and documents
the pattern in the upcoming-changes skill.
2026-07-22 15:26:59 +01:00

48 lines
1.6 KiB
JavaScript
Vendored

import stylelint from "stylelint";
import parser from "postcss-selector-parser";
const ruleName = "discourse/uc-classes-in-where";
// `.uc-*` classes are the `uc-<dasherized-setting-name>` body classes emitted
// for upcoming changes (feature flags with `include_css: true`). They gate
// transitional CSS for a change and are removed once it becomes permanent, so
// they must never contribute specificity: every use has to sit inside a
// `:where()` clause. This keeps the styling safe to unwrap and delete later
// without leaving behind rules that silently relied on the class's specificity.
//
// A `.uc-*` class is allowed only when one of its ancestor nodes is a
// `:where()` pseudo-class.
function isInsideWhere(node) {
for (let parent = node.parent; parent; parent = parent.parent) {
if (parent.type === "pseudo" && parent.value.toLowerCase() === ":where") {
return true;
}
}
return false;
}
export default stylelint.createPlugin(ruleName, (primaryOption) => {
return (root, result) => {
if (!primaryOption) {
return;
}
root.walkRules((rule) => {
parser((selectors) => {
selectors.walkClasses((classNode) => {
if (!classNode.value.startsWith("uc-") || isInsideWhere(classNode)) {
return;
}
stylelint.utils.report({
message: `Wrap the upcoming-change class ".${classNode.value}" in a :where() clause so it does not contribute specificity`,
node: rule,
result,
ruleName,
word: "." + classNode.value,
});
});
}).processSync(rule.selector);
});
};
});