3 const _ = require("lodash");
4 const addEmptyLineBefore = require("../../utils/addEmptyLineBefore");
5 const getPreviousNonSharedLineCommentNode = require("../../utils/getPreviousNonSharedLineCommentNode");
6 const hasEmptyLine = require("../../utils/hasEmptyLine");
7 const isAfterComment = require("../../utils/isAfterComment");
8 const isBlocklessAtRuleAfterBlocklessAtRule = require("../../utils/isBlocklessAtRuleAfterBlocklessAtRule");
9 const isBlocklessAtRuleAfterSameNameBlocklessAtRule = require("../../utils/isBlocklessAtRuleAfterSameNameBlocklessAtRule");
10 const isFirstNested = require("../../utils/isFirstNested");
11 const isFirstNodeOfRoot = require("../../utils/isFirstNodeOfRoot");
12 const optionsMatches = require("../../utils/optionsMatches");
13 const removeEmptyLinesBefore = require("../../utils/removeEmptyLinesBefore");
14 const report = require("../../utils/report");
15 const ruleMessages = require("../../utils/ruleMessages");
16 const validateOptions = require("../../utils/validateOptions");
18 const ruleName = "at-rule-empty-line-before";
20 const messages = ruleMessages(ruleName, {
21 expected: "Expected empty line before at-rule",
22 rejected: "Unexpected empty line before at-rule"
25 const rule = function(expectation, options, context) {
26 return (root, result) => {
27 const validOptions = validateOptions(
32 possible: ["always", "never"]
40 "blockless-after-same-name-blockless",
41 "blockless-after-blockless",
47 "blockless-after-same-name-blockless",
48 "blockless-after-blockless"
50 ignoreAtRules: [_.isString]
59 root.walkAtRules(atRule => {
60 const isNested = atRule.parent.type !== "root";
61 // Ignore the first node
62 if (isFirstNodeOfRoot(atRule)) {
66 // Return early if at-rule is to be ignored
67 if (optionsMatches(options, "ignoreAtRules", atRule.name)) {
71 // Optionally ignore the expectation if the node is blockless
73 optionsMatches(options, "ignore", "blockless-after-blockless") &&
74 isBlocklessAtRuleAfterBlocklessAtRule(atRule)
79 // Optionally ignore the expection if the node is blockless
80 // and following another blockless at-rule with the same name
85 "blockless-after-same-name-blockless"
87 isBlocklessAtRuleAfterSameNameBlocklessAtRule(atRule)
92 // Optionally ignore the expectation if the node is inside a block
93 if (optionsMatches(options, "ignore", "inside-block") && isNested) {
97 // Optionally ignore the expectation if a comment precedes this node
99 optionsMatches(options, "ignore", "after-comment") &&
100 isAfterComment(atRule)
105 const hasEmptyLineBefore = hasEmptyLine(atRule.raws.before);
106 let expectEmptyLineBefore = expectation === "always" ? true : false;
108 // Optionally reverse the expectation if any exceptions apply
110 (optionsMatches(options, "except", "after-same-name") &&
111 isAtRuleAfterSameNameAtRule(atRule)) ||
112 (optionsMatches(options, "except", "inside-block") && isNested) ||
113 (optionsMatches(options, "except", "first-nested") &&
114 isFirstNested(atRule)) ||
115 (optionsMatches(options, "except", "blockless-after-blockless") &&
116 isBlocklessAtRuleAfterBlocklessAtRule(atRule)) ||
120 "blockless-after-same-name-blockless"
122 isBlocklessAtRuleAfterSameNameBlocklessAtRule(atRule))
124 expectEmptyLineBefore = !expectEmptyLineBefore;
127 // Return if the expectation is met
128 if (expectEmptyLineBefore === hasEmptyLineBefore) {
134 if (expectEmptyLineBefore) {
135 addEmptyLineBefore(atRule, context.newline);
137 removeEmptyLinesBefore(atRule, context.newline);
143 const message = expectEmptyLineBefore
147 report({ message, node: atRule, result, ruleName });
152 function isAtRuleAfterSameNameAtRule(atRule) {
153 const previousNode = getPreviousNonSharedLineCommentNode(atRule);
156 previousNode.type === "atrule" &&
157 previousNode.name === atRule.name
161 rule.ruleName = ruleName;
162 rule.messages = messages;
163 module.exports = rule;