blob: 35455ff8d35dabe58529faf510a5b9b1d1ba5b9a [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import stylelint from 'stylelint';
const {
createPlugin,
utils: { report, ruleMessages },
} = stylelint;
const RULE_NAME = 'plugin/check_highlight_scope';
const messages = ruleMessages(RULE_NAME, {
expected: selector =>
`::highlight not be scoped in "${selector}". Observed performance regressions (https://crbug.com/461462682).`,
});
const ruleFunction = () => {
return function (postcssRoot, postcssResult) {
postcssRoot.walkRules(rule => {
rule.selectors.forEach(selector => {
const normalizedSelector = selector.trim();
if (!normalizedSelector.includes('::highlight(')) {
return;
}
// Regex: Matches ::highlight if it is at the start of the string (^)
// OR if it is preceded by a combinator (space, >, +, ~).
// This bans: "::highlight", "div > ::highlight", "div ::highlight"
// This allows: "div::highlight", ":host::highlight"
const isUnattached = /(?:^|[\s>+~])::highlight\(/.test(
normalizedSelector,
);
if (isUnattached) {
report({
message: messages.expected(normalizedSelector),
ruleName: RULE_NAME,
node: rule,
result: postcssResult,
word: selector,
});
}
});
});
};
};
ruleFunction.ruleName = RULE_NAME;
ruleFunction.messages = messages;
export default createPlugin(RULE_NAME, ruleFunction);