I've seem max-nesting-depth but I don't think it will work for my use case.
Here's some examples of valid and invalid code:
.classname {
color: red; /* fine */
.second-class {
color: blue; /* also fine */
.third-class {
color: green; /* also fine */
}
}
@media (min-width: 100px) {
color: blue; /* fine */
}
@media (min-width: 200px) {
color: green; /* fine */
.second-class { // <-- prevent this
background-color: blue; /* NOT fine */
.third-class {
background-color: blue; /* also NOT fine */
}
}
}
}
We have some code examples in our projects which mix a top level media query with nested selectors and media queries nested deep in selectors and I want to lint it so the latter is preferred.
You can use the stylelint-no-restricted-syntax Stylelint plugin for this specific use case, as Stylelint's built-in rules are designed for more general patterns. The plugin enables you to query the AST (the code's structure) to disallow specific patterns:
{
"extends": ["stylelint-config-standard"],
"plugins": ["stylelint-no-restricted-syntax"],
"rules": {
"plugin/no-restricted-syntax": [
[
{
"selector": "atrule rule",
"message": "Unexpected rule inside of at-rule"
}
]
]
}
}
This will disallow any rules that are descendants of at-rules. Demo.
Alternatively, and if you want more control, you can write a Stylelint plugin to do the same thing:
import stylelint from "stylelint";
const {
createPlugin,
utils: { report, ruleMessages, validateOptions },
} = stylelint;
const ruleName = "plugin/no-rules-inside-atrule";
const messages = ruleMessages(ruleName, {
rejected: () => "Unexpected rule inside at-rule",
});
/** @type {import('stylelint').Rule} */
const ruleFunction = (primary) => {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: primary,
possible: [true],
});
if (!validOptions) return;
root.walkAtRules((atRule) => {
atRule.walkRules((ruleNode) => {
report({
message: messages.rejected(),
node: ruleNode,
result,
ruleName,
});
});
});
};
};
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
export default createPlugin(ruleName, ruleFunction);
/** @type {import('stylelint').Config} */
export default {
extends: ["stylelint-config-standard"],
plugins: ["./no-rules-inside-atrule.mjs"],
rules: {
"plugin/no-rules-inside-atrule": true,
},
};