UNPKG

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