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