UNPKG

4.92 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const _ = require('lodash');
6const addEmptyLineBefore = require('../../utils/addEmptyLineBefore');
7const getPreviousNonSharedLineCommentNode = require('../../utils/getPreviousNonSharedLineCommentNode');
8const hasEmptyLine = require('../../utils/hasEmptyLine');
9const isAfterComment = require('../../utils/isAfterComment');
10const isBlocklessAtRuleAfterBlocklessAtRule = require('../../utils/isBlocklessAtRuleAfterBlocklessAtRule');
11const isBlocklessAtRuleAfterSameNameBlocklessAtRule = require('../../utils/isBlocklessAtRuleAfterSameNameBlocklessAtRule');
12const isFirstNested = require('../../utils/isFirstNested');
13const isFirstNodeOfRoot = require('../../utils/isFirstNodeOfRoot');
14const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
15const optionsMatches = require('../../utils/optionsMatches');
16const removeEmptyLinesBefore = require('../../utils/removeEmptyLinesBefore');
17const report = require('../../utils/report');
18const ruleMessages = require('../../utils/ruleMessages');
19const validateOptions = require('../../utils/validateOptions');
20
21const ruleName = 'at-rule-empty-line-before';
22
23const messages = ruleMessages(ruleName, {
24 expected: 'Expected empty line before at-rule',
25 rejected: 'Unexpected empty line before at-rule',
26});
27
28function rule(expectation, options, context) {
29 return (root, result) => {
30 const validOptions = validateOptions(
31 result,
32 ruleName,
33 {
34 actual: expectation,
35 possible: ['always', 'never'],
36 },
37 {
38 actual: options,
39 possible: {
40 except: [
41 'after-same-name',
42 'inside-block',
43 'blockless-after-same-name-blockless',
44 'blockless-after-blockless',
45 'first-nested',
46 ],
47 ignore: [
48 'after-comment',
49 'first-nested',
50 'inside-block',
51 'blockless-after-same-name-blockless',
52 'blockless-after-blockless',
53 ],
54 ignoreAtRules: [_.isString],
55 },
56 optional: true,
57 },
58 );
59
60 if (!validOptions) {
61 return;
62 }
63
64 root.walkAtRules((atRule) => {
65 const isNested = atRule.parent.type !== 'root';
66
67 // Ignore the first node
68 if (isFirstNodeOfRoot(atRule)) {
69 return;
70 }
71
72 if (!isStandardSyntaxAtRule(atRule)) {
73 return;
74 }
75
76 // Return early if at-rule is to be ignored
77 if (optionsMatches(options, 'ignoreAtRules', atRule.name)) {
78 return;
79 }
80
81 // Optionally ignore the expectation if the node is blockless
82 if (
83 optionsMatches(options, 'ignore', 'blockless-after-blockless') &&
84 isBlocklessAtRuleAfterBlocklessAtRule(atRule)
85 ) {
86 return;
87 }
88
89 // Optionally ignore the node if it is the first nested
90 if (optionsMatches(options, 'ignore', 'first-nested') && isFirstNested(atRule)) {
91 return;
92 }
93
94 // Optionally ignore the expectation if the node is blockless
95 // and following another blockless at-rule with the same name
96 if (
97 optionsMatches(options, 'ignore', 'blockless-after-same-name-blockless') &&
98 isBlocklessAtRuleAfterSameNameBlocklessAtRule(atRule)
99 ) {
100 return;
101 }
102
103 // Optionally ignore the expectation if the node is inside a block
104 if (optionsMatches(options, 'ignore', 'inside-block') && isNested) {
105 return;
106 }
107
108 // Optionally ignore the expectation if a comment precedes this node
109 if (optionsMatches(options, 'ignore', 'after-comment') && isAfterComment(atRule)) {
110 return;
111 }
112
113 const hasEmptyLineBefore = hasEmptyLine(atRule.raws.before);
114 let expectEmptyLineBefore = expectation === 'always';
115
116 // Optionally reverse the expectation if any exceptions apply
117 if (
118 (optionsMatches(options, 'except', 'after-same-name') &&
119 isAtRuleAfterSameNameAtRule(atRule)) ||
120 (optionsMatches(options, 'except', 'inside-block') && isNested) ||
121 (optionsMatches(options, 'except', 'first-nested') && isFirstNested(atRule)) ||
122 (optionsMatches(options, 'except', 'blockless-after-blockless') &&
123 isBlocklessAtRuleAfterBlocklessAtRule(atRule)) ||
124 (optionsMatches(options, 'except', 'blockless-after-same-name-blockless') &&
125 isBlocklessAtRuleAfterSameNameBlocklessAtRule(atRule))
126 ) {
127 expectEmptyLineBefore = !expectEmptyLineBefore;
128 }
129
130 // Return if the expectation is met
131 if (expectEmptyLineBefore === hasEmptyLineBefore) {
132 return;
133 }
134
135 // Fix
136 if (context.fix) {
137 if (expectEmptyLineBefore) {
138 addEmptyLineBefore(atRule, context.newline);
139 } else {
140 removeEmptyLinesBefore(atRule, context.newline);
141 }
142
143 return;
144 }
145
146 const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
147
148 report({ message, node: atRule, result, ruleName });
149 });
150 };
151}
152
153function isAtRuleAfterSameNameAtRule(atRule) {
154 const previousNode = getPreviousNonSharedLineCommentNode(atRule);
155
156 return previousNode && previousNode.type === 'atrule' && previousNode.name === atRule.name;
157}
158
159rule.ruleName = ruleName;
160rule.messages = messages;
161module.exports = rule;