UNPKG

4.21 kBJavaScriptView Raw
1'use strict';
2
3const addEmptyLineBefore = require('../../utils/addEmptyLineBefore');
4const blockString = require('../../utils/blockString');
5const hasEmptyLine = require('../../utils/hasEmptyLine');
6const isAfterComment = require('../../utils/isAfterComment');
7const isAfterStandardPropertyDeclaration = require('../../utils/isAfterStandardPropertyDeclaration');
8const isCustomProperty = require('../../utils/isCustomProperty');
9const isFirstNested = require('../../utils/isFirstNested');
10const isFirstNodeOfRoot = require('../../utils/isFirstNodeOfRoot');
11const isSingleLineString = require('../../utils/isSingleLineString');
12const isStandardSyntaxDeclaration = require('../../utils/isStandardSyntaxDeclaration');
13const optionsMatches = require('../../utils/optionsMatches');
14const removeEmptyLinesBefore = require('../../utils/removeEmptyLinesBefore');
15const report = require('../../utils/report');
16const ruleMessages = require('../../utils/ruleMessages');
17const validateOptions = require('../../utils/validateOptions');
18const { isAtRule, isRule } = require('../../utils/typeGuards');
19
20const ruleName = 'declaration-empty-line-before';
21
22const messages = ruleMessages(ruleName, {
23 expected: 'Expected empty line before declaration',
24 rejected: 'Unexpected empty line before declaration',
25});
26
27/** @type {import('stylelint').Rule} */
28const rule = (primary, secondaryOptions, context) => {
29 return (root, result) => {
30 const validOptions = validateOptions(
31 result,
32 ruleName,
33 {
34 actual: primary,
35 possible: ['always', 'never'],
36 },
37 {
38 actual: secondaryOptions,
39 possible: {
40 except: ['first-nested', 'after-comment', 'after-declaration'],
41 ignore: [
42 'after-comment',
43 'after-declaration',
44 'first-nested',
45 'inside-single-line-block',
46 ],
47 },
48 optional: true,
49 },
50 );
51
52 if (!validOptions) {
53 return;
54 }
55
56 root.walkDecls((decl) => {
57 const prop = decl.prop;
58 const parent = decl.parent;
59
60 if (parent == null) {
61 return;
62 }
63
64 // Ignore the first node
65 if (isFirstNodeOfRoot(decl)) {
66 return;
67 }
68
69 if (!isAtRule(parent) && !isRule(parent)) {
70 return;
71 }
72
73 if (!isStandardSyntaxDeclaration(decl)) {
74 return;
75 }
76
77 if (isCustomProperty(prop)) {
78 return;
79 }
80
81 // Optionally ignore the node if a comment precedes it
82 if (optionsMatches(secondaryOptions, 'ignore', 'after-comment') && isAfterComment(decl)) {
83 return;
84 }
85
86 // Optionally ignore the node if a declaration precedes it
87 if (
88 optionsMatches(secondaryOptions, 'ignore', 'after-declaration') &&
89 isAfterStandardPropertyDeclaration(decl)
90 ) {
91 return;
92 }
93
94 // Optionally ignore the node if it is the first nested
95 if (optionsMatches(secondaryOptions, 'ignore', 'first-nested') && isFirstNested(decl)) {
96 return;
97 }
98
99 // Optionally ignore nodes inside single-line blocks
100 if (
101 optionsMatches(secondaryOptions, 'ignore', 'inside-single-line-block') &&
102 isSingleLineString(blockString(parent))
103 ) {
104 return;
105 }
106
107 let expectEmptyLineBefore = primary === 'always';
108
109 // Optionally reverse the expectation if any exceptions apply
110 if (
111 (optionsMatches(secondaryOptions, 'except', 'first-nested') && isFirstNested(decl)) ||
112 (optionsMatches(secondaryOptions, 'except', 'after-comment') && isAfterComment(decl)) ||
113 (optionsMatches(secondaryOptions, 'except', 'after-declaration') &&
114 isAfterStandardPropertyDeclaration(decl))
115 ) {
116 expectEmptyLineBefore = !expectEmptyLineBefore;
117 }
118
119 // Check for at least one empty line
120 const hasEmptyLineBefore = hasEmptyLine(decl.raws.before);
121
122 // Return if the expectation is met
123 if (expectEmptyLineBefore === hasEmptyLineBefore) {
124 return;
125 }
126
127 // Fix
128 if (context.fix) {
129 if (context.newline == null) return;
130
131 if (expectEmptyLineBefore) {
132 addEmptyLineBefore(decl, context.newline);
133 } else {
134 removeEmptyLinesBefore(decl, context.newline);
135 }
136
137 return;
138 }
139
140 const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
141
142 report({ message, node: decl, result, ruleName });
143 });
144 };
145};
146
147rule.ruleName = ruleName;
148rule.messages = messages;
149module.exports = rule;