UNPKG

5.76 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const _ = require('lodash');
6const declarationValueIndex = require('../../utils/declarationValueIndex');
7const isSingleLineString = require('../../utils/isSingleLineString');
8const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
9const report = require('../../utils/report');
10const ruleMessages = require('../../utils/ruleMessages');
11const validateOptions = require('../../utils/validateOptions');
12const valueParser = require('postcss-value-parser');
13
14const ruleName = 'function-parentheses-newline-inside';
15
16const messages = ruleMessages(ruleName, {
17 expectedOpening: 'Expected newline after "("',
18 expectedClosing: 'Expected newline before ")"',
19 expectedOpeningMultiLine: 'Expected newline after "(" in a multi-line function',
20 rejectedOpeningMultiLine: 'Unexpected whitespace after "(" in a multi-line function',
21 expectedClosingMultiLine: 'Expected newline before ")" in a multi-line function',
22 rejectedClosingMultiLine: 'Unexpected whitespace before ")" in a multi-line function',
23});
24
25function rule(expectation, options, context) {
26 return (root, result) => {
27 const validOptions = validateOptions(result, ruleName, {
28 actual: expectation,
29 possible: ['always', 'always-multi-line', 'never-multi-line'],
30 });
31
32 if (!validOptions) {
33 return;
34 }
35
36 root.walkDecls((decl) => {
37 if (!decl.value.includes('(')) {
38 return;
39 }
40
41 let hasFixed = false;
42 const declValue = _.get(decl, 'raws.value.raw', decl.value);
43 const parsedValue = valueParser(declValue);
44
45 parsedValue.walk((valueNode) => {
46 if (valueNode.type !== 'function') {
47 return;
48 }
49
50 if (!isStandardSyntaxFunction(valueNode)) {
51 return;
52 }
53
54 const functionString = valueParser.stringify(valueNode);
55 const isMultiLine = !isSingleLineString(functionString);
56
57 function containsNewline(str) {
58 return str.includes('\n');
59 }
60
61 // Check opening ...
62
63 const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1;
64 const checkBefore = getCheckBefore(valueNode);
65
66 if (expectation === 'always' && !containsNewline(checkBefore)) {
67 if (context.fix) {
68 hasFixed = true;
69 fixBeforeForAlways(valueNode, context.newline);
70 } else {
71 complain(messages.expectedOpening, openingIndex);
72 }
73 }
74
75 if (isMultiLine && expectation === 'always-multi-line' && !containsNewline(checkBefore)) {
76 if (context.fix) {
77 hasFixed = true;
78 fixBeforeForAlways(valueNode, context.newline);
79 } else {
80 complain(messages.expectedOpeningMultiLine, openingIndex);
81 }
82 }
83
84 if (isMultiLine && expectation === 'never-multi-line' && checkBefore !== '') {
85 if (context.fix) {
86 hasFixed = true;
87 fixBeforeForNever(valueNode);
88 } else {
89 complain(messages.rejectedOpeningMultiLine, openingIndex);
90 }
91 }
92
93 // Check closing ...
94
95 const closingIndex = valueNode.sourceIndex + functionString.length - 2;
96 const checkAfter = getCheckAfter(valueNode);
97
98 if (expectation === 'always' && !containsNewline(checkAfter)) {
99 if (context.fix) {
100 hasFixed = true;
101 fixAfterForAlways(valueNode, context.newline);
102 } else {
103 complain(messages.expectedClosing, closingIndex);
104 }
105 }
106
107 if (isMultiLine && expectation === 'always-multi-line' && !containsNewline(checkAfter)) {
108 if (context.fix) {
109 hasFixed = true;
110 fixAfterForAlways(valueNode, context.newline);
111 } else {
112 complain(messages.expectedClosingMultiLine, closingIndex);
113 }
114 }
115
116 if (isMultiLine && expectation === 'never-multi-line' && checkAfter !== '') {
117 if (context.fix) {
118 hasFixed = true;
119 fixAfterForNever(valueNode);
120 } else {
121 complain(messages.rejectedClosingMultiLine, closingIndex);
122 }
123 }
124 });
125
126 if (hasFixed) {
127 if (!decl.raws.value) {
128 decl.value = parsedValue.toString();
129 } else {
130 decl.raws.value.raw = parsedValue.toString();
131 }
132 }
133
134 function complain(message, offset) {
135 report({
136 ruleName,
137 result,
138 message,
139 node: decl,
140 index: declarationValueIndex(decl) + offset,
141 });
142 }
143 });
144 };
145}
146
147function getCheckBefore(valueNode) {
148 let before = valueNode.before;
149
150 for (const node of valueNode.nodes) {
151 if (node.type === 'comment') {
152 continue;
153 }
154
155 if (node.type === 'space') {
156 before += node.value;
157 continue;
158 }
159
160 break;
161 }
162
163 return before;
164}
165
166function getCheckAfter(valueNode) {
167 let after = '';
168
169 for (const node of valueNode.nodes.slice().reverse()) {
170 if (node.type === 'comment') {
171 continue;
172 }
173
174 if (node.type === 'space') {
175 after = node.value + after;
176 continue;
177 }
178
179 break;
180 }
181
182 after += valueNode.after;
183
184 return after;
185}
186
187function fixBeforeForAlways(valueNode, newline) {
188 let target;
189
190 for (const node of valueNode.nodes) {
191 if (node.type === 'comment') {
192 continue;
193 }
194
195 if (node.type === 'space') {
196 target = node;
197 continue;
198 }
199
200 break;
201 }
202
203 if (target) {
204 target.value = newline + target.value;
205 } else {
206 valueNode.before = newline + valueNode.before;
207 }
208}
209
210function fixBeforeForNever(valueNode) {
211 valueNode.before = '';
212
213 for (const node of valueNode.nodes) {
214 if (node.type === 'comment') {
215 continue;
216 }
217
218 if (node.type === 'space') {
219 node.value = '';
220 continue;
221 }
222
223 break;
224 }
225}
226
227function fixAfterForAlways(valueNode, newline) {
228 valueNode.after = newline + valueNode.after;
229}
230
231function fixAfterForNever(valueNode) {
232 valueNode.after = '';
233
234 for (const node of valueNode.nodes.slice().reverse()) {
235 if (node.type === 'comment') {
236 continue;
237 }
238
239 if (node.type === 'space') {
240 node.value = '';
241 continue;
242 }
243
244 break;
245 }
246}
247
248rule.ruleName = ruleName;
249rule.messages = messages;
250module.exports = rule;