stylelint
Version:
Modern CSS linter
106 lines (86 loc) • 3.08 kB
JavaScript
import { isString } from "lodash"
import {
hasBlock,
optionsHaveException,
optionsHaveIgnored,
optionsHaveIgnoredAtRule,
report,
ruleMessages,
validateOptions,
} from "../../utils"
export const ruleName = "at-rule-empty-line-before"
export const messages = ruleMessages(ruleName, {
expected: "Expected empty line before at-rule",
rejected: "Unexpected empty line before at-rule",
})
export default function (expectation, options) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
possible: [ "always", "never" ],
}, {
actual: options,
possible: {
except: [ "blockless-group", "first-nested", "all-nested" ],
ignore: [ "blockless-group", "after-comment", "all-nested" ],
ignoreAtRules: [isString],
},
optional: true,
})
if (!validOptions) { return }
root.walkAtRules(atRule => {
// Ignore the first node
if (atRule === root.first) { return }
// Return early if at-rule is to be ignored
if (optionsHaveIgnoredAtRule(options, atRule)) { return }
// Optionally ignore the expectation if the node is blockless
if (optionsHaveIgnored(options, "blockless-group") && !hasBlock(atRule)) { return }
// Optionally ignore the expectation if the node is nested
const isNested = atRule.parent !== root
if (optionsHaveIgnored(options, "all-nested") && isNested) { return }
// Optionally ignore the expectation if a comment precedes this node
if (optionsHaveIgnored(options, "after-comment")
&& atRule.prev() && atRule.prev().type === "comment") { return }
const before = atRule.raw("before")
const emptyLineBefore = before && (
before.indexOf("\n\n") !== -1
|| before.indexOf("\r\n\r\n") !== -1
|| before.indexOf("\n\r\n") !== -1
)
let expectEmptyLineBefore = (expectation === "always") ? true : false
const previousNode = atRule.prev()
// Reverse the expectation if any exceptions apply
if (
(optionsHaveException(options, "all-nested") && isNested)
|| getsFirstNestedException()
|| getsBlocklessGroupException()
) {
expectEmptyLineBefore = !expectEmptyLineBefore
}
// Return if the exceptation is met
if (expectEmptyLineBefore === emptyLineBefore) { return }
const message = expectEmptyLineBefore ? messages.expected : messages.rejected
report({
message,
node: atRule,
result,
ruleName,
})
function getsBlocklessGroupException() {
return (
optionsHaveException(options, "blockless-group")
&& previousNode && previousNode.type === "atrule"
&& !hasBlock(previousNode)
&& !hasBlock(atRule)
)
}
function getsFirstNestedException() {
return (
optionsHaveException(options, "first-nested")
&& isNested
&& atRule === atRule.parent.first
)
}
})
}
}