]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to specify spacing of object literal keys and values | |
3 | * @author Brandon Mills | |
4 | */ | |
5 | "use strict"; | |
6 | ||
7 | //------------------------------------------------------------------------------ | |
8 | // Requirements | |
9 | //------------------------------------------------------------------------------ | |
10 | ||
11 | const astUtils = require("./utils/ast-utils"); | |
12 | ||
13 | //------------------------------------------------------------------------------ | |
14 | // Helpers | |
15 | //------------------------------------------------------------------------------ | |
16 | ||
17 | /** | |
18 | * Checks whether a string contains a line terminator as defined in | |
19 | * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 | |
20 | * @param {string} str String to test. | |
21 | * @returns {boolean} True if str contains a line terminator. | |
22 | */ | |
23 | function containsLineTerminator(str) { | |
24 | return astUtils.LINEBREAK_MATCHER.test(str); | |
25 | } | |
26 | ||
27 | /** | |
28 | * Gets the last element of an array. | |
29 | * @param {Array} arr An array. | |
30 | * @returns {any} Last element of arr. | |
31 | */ | |
32 | function last(arr) { | |
33 | return arr[arr.length - 1]; | |
34 | } | |
35 | ||
36 | /** | |
37 | * Checks whether a node is contained on a single line. | |
38 | * @param {ASTNode} node AST Node being evaluated. | |
39 | * @returns {boolean} True if the node is a single line. | |
40 | */ | |
41 | function isSingleLine(node) { | |
42 | return (node.loc.end.line === node.loc.start.line); | |
43 | } | |
44 | ||
45 | /** | |
46 | * Checks whether the properties on a single line. | |
47 | * @param {ASTNode[]} properties List of Property AST nodes. | |
56c4a2cb | 48 | * @returns {boolean} True if all properties is on a single line. |
eb39fafa DC |
49 | */ |
50 | function isSingleLineProperties(properties) { | |
51 | const [firstProp] = properties, | |
52 | lastProp = last(properties); | |
53 | ||
54 | return firstProp.loc.start.line === lastProp.loc.end.line; | |
55 | } | |
56 | ||
57 | /** | |
58 | * Initializes a single option property from the configuration with defaults for undefined values | |
59 | * @param {Object} toOptions Object to be initialized | |
60 | * @param {Object} fromOptions Object to be initialized from | |
61 | * @returns {Object} The object with correctly initialized options and values | |
62 | */ | |
63 | function initOptionProperty(toOptions, fromOptions) { | |
64 | toOptions.mode = fromOptions.mode || "strict"; | |
65 | ||
66 | // Set value of beforeColon | |
67 | if (typeof fromOptions.beforeColon !== "undefined") { | |
68 | toOptions.beforeColon = +fromOptions.beforeColon; | |
69 | } else { | |
70 | toOptions.beforeColon = 0; | |
71 | } | |
72 | ||
73 | // Set value of afterColon | |
74 | if (typeof fromOptions.afterColon !== "undefined") { | |
75 | toOptions.afterColon = +fromOptions.afterColon; | |
76 | } else { | |
77 | toOptions.afterColon = 1; | |
78 | } | |
79 | ||
80 | // Set align if exists | |
81 | if (typeof fromOptions.align !== "undefined") { | |
82 | if (typeof fromOptions.align === "object") { | |
83 | toOptions.align = fromOptions.align; | |
84 | } else { // "string" | |
85 | toOptions.align = { | |
86 | on: fromOptions.align, | |
87 | mode: toOptions.mode, | |
88 | beforeColon: toOptions.beforeColon, | |
89 | afterColon: toOptions.afterColon | |
90 | }; | |
91 | } | |
92 | } | |
93 | ||
94 | return toOptions; | |
95 | } | |
96 | ||
97 | /** | |
98 | * Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values | |
99 | * @param {Object} toOptions Object to be initialized | |
100 | * @param {Object} fromOptions Object to be initialized from | |
101 | * @returns {Object} The object with correctly initialized options and values | |
102 | */ | |
103 | function initOptions(toOptions, fromOptions) { | |
104 | if (typeof fromOptions.align === "object") { | |
105 | ||
106 | // Initialize the alignment configuration | |
107 | toOptions.align = initOptionProperty({}, fromOptions.align); | |
108 | toOptions.align.on = fromOptions.align.on || "colon"; | |
109 | toOptions.align.mode = fromOptions.align.mode || "strict"; | |
110 | ||
111 | toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions)); | |
112 | toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions)); | |
113 | ||
114 | } else { // string or undefined | |
115 | toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions)); | |
116 | toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions)); | |
117 | ||
118 | // If alignment options are defined in multiLine, pull them out into the general align configuration | |
119 | if (toOptions.multiLine.align) { | |
120 | toOptions.align = { | |
121 | on: toOptions.multiLine.align.on, | |
122 | mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode, | |
123 | beforeColon: toOptions.multiLine.align.beforeColon, | |
124 | afterColon: toOptions.multiLine.align.afterColon | |
125 | }; | |
126 | } | |
127 | } | |
128 | ||
129 | return toOptions; | |
130 | } | |
131 | ||
132 | //------------------------------------------------------------------------------ | |
133 | // Rule Definition | |
134 | //------------------------------------------------------------------------------ | |
135 | ||
136 | module.exports = { | |
137 | meta: { | |
138 | type: "layout", | |
139 | ||
140 | docs: { | |
141 | description: "enforce consistent spacing between keys and values in object literal properties", | |
142 | category: "Stylistic Issues", | |
143 | recommended: false, | |
144 | url: "https://eslint.org/docs/rules/key-spacing" | |
145 | }, | |
146 | ||
147 | fixable: "whitespace", | |
148 | ||
149 | schema: [{ | |
150 | anyOf: [ | |
151 | { | |
152 | type: "object", | |
153 | properties: { | |
154 | align: { | |
155 | anyOf: [ | |
156 | { | |
157 | enum: ["colon", "value"] | |
158 | }, | |
159 | { | |
160 | type: "object", | |
161 | properties: { | |
162 | mode: { | |
163 | enum: ["strict", "minimum"] | |
164 | }, | |
165 | on: { | |
166 | enum: ["colon", "value"] | |
167 | }, | |
168 | beforeColon: { | |
169 | type: "boolean" | |
170 | }, | |
171 | afterColon: { | |
172 | type: "boolean" | |
173 | } | |
174 | }, | |
175 | additionalProperties: false | |
176 | } | |
177 | ] | |
178 | }, | |
179 | mode: { | |
180 | enum: ["strict", "minimum"] | |
181 | }, | |
182 | beforeColon: { | |
183 | type: "boolean" | |
184 | }, | |
185 | afterColon: { | |
186 | type: "boolean" | |
187 | } | |
188 | }, | |
189 | additionalProperties: false | |
190 | }, | |
191 | { | |
192 | type: "object", | |
193 | properties: { | |
194 | singleLine: { | |
195 | type: "object", | |
196 | properties: { | |
197 | mode: { | |
198 | enum: ["strict", "minimum"] | |
199 | }, | |
200 | beforeColon: { | |
201 | type: "boolean" | |
202 | }, | |
203 | afterColon: { | |
204 | type: "boolean" | |
205 | } | |
206 | }, | |
207 | additionalProperties: false | |
208 | }, | |
209 | multiLine: { | |
210 | type: "object", | |
211 | properties: { | |
212 | align: { | |
213 | anyOf: [ | |
214 | { | |
215 | enum: ["colon", "value"] | |
216 | }, | |
217 | { | |
218 | type: "object", | |
219 | properties: { | |
220 | mode: { | |
221 | enum: ["strict", "minimum"] | |
222 | }, | |
223 | on: { | |
224 | enum: ["colon", "value"] | |
225 | }, | |
226 | beforeColon: { | |
227 | type: "boolean" | |
228 | }, | |
229 | afterColon: { | |
230 | type: "boolean" | |
231 | } | |
232 | }, | |
233 | additionalProperties: false | |
234 | } | |
235 | ] | |
236 | }, | |
237 | mode: { | |
238 | enum: ["strict", "minimum"] | |
239 | }, | |
240 | beforeColon: { | |
241 | type: "boolean" | |
242 | }, | |
243 | afterColon: { | |
244 | type: "boolean" | |
245 | } | |
246 | }, | |
247 | additionalProperties: false | |
248 | } | |
249 | }, | |
250 | additionalProperties: false | |
251 | }, | |
252 | { | |
253 | type: "object", | |
254 | properties: { | |
255 | singleLine: { | |
256 | type: "object", | |
257 | properties: { | |
258 | mode: { | |
259 | enum: ["strict", "minimum"] | |
260 | }, | |
261 | beforeColon: { | |
262 | type: "boolean" | |
263 | }, | |
264 | afterColon: { | |
265 | type: "boolean" | |
266 | } | |
267 | }, | |
268 | additionalProperties: false | |
269 | }, | |
270 | multiLine: { | |
271 | type: "object", | |
272 | properties: { | |
273 | mode: { | |
274 | enum: ["strict", "minimum"] | |
275 | }, | |
276 | beforeColon: { | |
277 | type: "boolean" | |
278 | }, | |
279 | afterColon: { | |
280 | type: "boolean" | |
281 | } | |
282 | }, | |
283 | additionalProperties: false | |
284 | }, | |
285 | align: { | |
286 | type: "object", | |
287 | properties: { | |
288 | mode: { | |
289 | enum: ["strict", "minimum"] | |
290 | }, | |
291 | on: { | |
292 | enum: ["colon", "value"] | |
293 | }, | |
294 | beforeColon: { | |
295 | type: "boolean" | |
296 | }, | |
297 | afterColon: { | |
298 | type: "boolean" | |
299 | } | |
300 | }, | |
301 | additionalProperties: false | |
302 | } | |
303 | }, | |
304 | additionalProperties: false | |
305 | } | |
306 | ] | |
307 | }], | |
308 | messages: { | |
309 | extraKey: "Extra space after {{computed}}key '{{key}}'.", | |
310 | extraValue: "Extra space before value for {{computed}}key '{{key}}'.", | |
311 | missingKey: "Missing space after {{computed}}key '{{key}}'.", | |
312 | missingValue: "Missing space before value for {{computed}}key '{{key}}'." | |
313 | } | |
314 | }, | |
315 | ||
316 | create(context) { | |
317 | ||
318 | /** | |
319 | * OPTIONS | |
320 | * "key-spacing": [2, { | |
321 | * beforeColon: false, | |
322 | * afterColon: true, | |
323 | * align: "colon" // Optional, or "value" | |
324 | * } | |
325 | */ | |
326 | const options = context.options[0] || {}, | |
327 | ruleOptions = initOptions({}, options), | |
328 | multiLineOptions = ruleOptions.multiLine, | |
329 | singleLineOptions = ruleOptions.singleLine, | |
330 | alignmentOptions = ruleOptions.align || null; | |
331 | ||
332 | const sourceCode = context.getSourceCode(); | |
333 | ||
334 | /** | |
335 | * Checks whether a property is a member of the property group it follows. | |
336 | * @param {ASTNode} lastMember The last Property known to be in the group. | |
337 | * @param {ASTNode} candidate The next Property that might be in the group. | |
338 | * @returns {boolean} True if the candidate property is part of the group. | |
339 | */ | |
340 | function continuesPropertyGroup(lastMember, candidate) { | |
341 | const groupEndLine = lastMember.loc.start.line, | |
342 | candidateStartLine = candidate.loc.start.line; | |
343 | ||
344 | if (candidateStartLine - groupEndLine <= 1) { | |
345 | return true; | |
346 | } | |
347 | ||
348 | /* | |
349 | * Check that the first comment is adjacent to the end of the group, the | |
350 | * last comment is adjacent to the candidate property, and that successive | |
351 | * comments are adjacent to each other. | |
352 | */ | |
353 | const leadingComments = sourceCode.getCommentsBefore(candidate); | |
354 | ||
355 | if ( | |
356 | leadingComments.length && | |
357 | leadingComments[0].loc.start.line - groupEndLine <= 1 && | |
358 | candidateStartLine - last(leadingComments).loc.end.line <= 1 | |
359 | ) { | |
360 | for (let i = 1; i < leadingComments.length; i++) { | |
361 | if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) { | |
362 | return false; | |
363 | } | |
364 | } | |
365 | return true; | |
366 | } | |
367 | ||
368 | return false; | |
369 | } | |
370 | ||
371 | /** | |
372 | * Determines if the given property is key-value property. | |
373 | * @param {ASTNode} property Property node to check. | |
374 | * @returns {boolean} Whether the property is a key-value property. | |
375 | */ | |
376 | function isKeyValueProperty(property) { | |
377 | return !( | |
378 | (property.method || | |
379 | property.shorthand || | |
380 | property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement" | |
381 | ); | |
382 | } | |
383 | ||
384 | /** | |
385 | * Starting from the given a node (a property.key node here) looks forward | |
386 | * until it finds the last token before a colon punctuator and returns it. | |
387 | * @param {ASTNode} node The node to start looking from. | |
388 | * @returns {ASTNode} The last token before a colon punctuator. | |
389 | */ | |
390 | function getLastTokenBeforeColon(node) { | |
391 | const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken); | |
392 | ||
393 | return sourceCode.getTokenBefore(colonToken); | |
394 | } | |
395 | ||
396 | /** | |
397 | * Starting from the given a node (a property.key node here) looks forward | |
398 | * until it finds the colon punctuator and returns it. | |
399 | * @param {ASTNode} node The node to start looking from. | |
400 | * @returns {ASTNode} The colon punctuator. | |
401 | */ | |
402 | function getNextColon(node) { | |
403 | return sourceCode.getTokenAfter(node, astUtils.isColonToken); | |
404 | } | |
405 | ||
406 | /** | |
407 | * Gets an object literal property's key as the identifier name or string value. | |
408 | * @param {ASTNode} property Property node whose key to retrieve. | |
409 | * @returns {string} The property's key. | |
410 | */ | |
411 | function getKey(property) { | |
412 | const key = property.key; | |
413 | ||
414 | if (property.computed) { | |
415 | return sourceCode.getText().slice(key.range[0], key.range[1]); | |
416 | } | |
417 | return astUtils.getStaticPropertyName(property); | |
418 | } | |
419 | ||
420 | /** | |
421 | * Reports an appropriately-formatted error if spacing is incorrect on one | |
422 | * side of the colon. | |
423 | * @param {ASTNode} property Key-value pair in an object literal. | |
424 | * @param {string} side Side being verified - either "key" or "value". | |
425 | * @param {string} whitespace Actual whitespace string. | |
426 | * @param {int} expected Expected whitespace length. | |
427 | * @param {string} mode Value of the mode as "strict" or "minimum" | |
428 | * @returns {void} | |
429 | */ | |
430 | function report(property, side, whitespace, expected, mode) { | |
431 | const diff = whitespace.length - expected, | |
432 | nextColon = getNextColon(property.key), | |
433 | tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }), | |
434 | tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }), | |
435 | isKeySide = side === "key", | |
eb39fafa DC |
436 | isExtra = diff > 0, |
437 | diffAbs = Math.abs(diff), | |
438 | spaces = Array(diffAbs + 1).join(" "); | |
439 | ||
6f036462 TL |
440 | const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start; |
441 | const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start; | |
442 | const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc; | |
443 | const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc; | |
444 | ||
eb39fafa DC |
445 | if (( |
446 | diff && mode === "strict" || | |
447 | diff < 0 && mode === "minimum" || | |
448 | diff > 0 && !expected && mode === "minimum") && | |
449 | !(expected && containsLineTerminator(whitespace)) | |
450 | ) { | |
451 | let fix; | |
452 | ||
453 | if (isExtra) { | |
454 | let range; | |
455 | ||
456 | // Remove whitespace | |
457 | if (isKeySide) { | |
458 | range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs]; | |
459 | } else { | |
460 | range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]]; | |
461 | } | |
462 | fix = function(fixer) { | |
463 | return fixer.removeRange(range); | |
464 | }; | |
465 | } else { | |
466 | ||
467 | // Add whitespace | |
468 | if (isKeySide) { | |
469 | fix = function(fixer) { | |
470 | return fixer.insertTextAfter(tokenBeforeColon, spaces); | |
471 | }; | |
472 | } else { | |
473 | fix = function(fixer) { | |
474 | return fixer.insertTextBefore(tokenAfterColon, spaces); | |
475 | }; | |
476 | } | |
477 | } | |
478 | ||
479 | let messageId = ""; | |
480 | ||
481 | if (isExtra) { | |
482 | messageId = side === "key" ? "extraKey" : "extraValue"; | |
483 | } else { | |
484 | messageId = side === "key" ? "missingKey" : "missingValue"; | |
485 | } | |
486 | ||
487 | context.report({ | |
488 | node: property[side], | |
6f036462 | 489 | loc, |
eb39fafa DC |
490 | messageId, |
491 | data: { | |
492 | computed: property.computed ? "computed " : "", | |
493 | key: getKey(property) | |
494 | }, | |
495 | fix | |
496 | }); | |
497 | } | |
498 | } | |
499 | ||
500 | /** | |
501 | * Gets the number of characters in a key, including quotes around string | |
502 | * keys and braces around computed property keys. | |
503 | * @param {ASTNode} property Property of on object literal. | |
504 | * @returns {int} Width of the key. | |
505 | */ | |
506 | function getKeyWidth(property) { | |
507 | const startToken = sourceCode.getFirstToken(property); | |
508 | const endToken = getLastTokenBeforeColon(property.key); | |
509 | ||
510 | return endToken.range[1] - startToken.range[0]; | |
511 | } | |
512 | ||
513 | /** | |
514 | * Gets the whitespace around the colon in an object literal property. | |
515 | * @param {ASTNode} property Property node from an object literal. | |
516 | * @returns {Object} Whitespace before and after the property's colon. | |
517 | */ | |
518 | function getPropertyWhitespace(property) { | |
519 | const whitespace = /(\s*):(\s*)/u.exec(sourceCode.getText().slice( | |
520 | property.key.range[1], property.value.range[0] | |
521 | )); | |
522 | ||
523 | if (whitespace) { | |
524 | return { | |
525 | beforeColon: whitespace[1], | |
526 | afterColon: whitespace[2] | |
527 | }; | |
528 | } | |
529 | return null; | |
530 | } | |
531 | ||
532 | /** | |
533 | * Creates groups of properties. | |
534 | * @param {ASTNode} node ObjectExpression node being evaluated. | |
535 | * @returns {Array.<ASTNode[]>} Groups of property AST node lists. | |
536 | */ | |
537 | function createGroups(node) { | |
538 | if (node.properties.length === 1) { | |
539 | return [node.properties]; | |
540 | } | |
541 | ||
542 | return node.properties.reduce((groups, property) => { | |
543 | const currentGroup = last(groups), | |
544 | prev = last(currentGroup); | |
545 | ||
546 | if (!prev || continuesPropertyGroup(prev, property)) { | |
547 | currentGroup.push(property); | |
548 | } else { | |
549 | groups.push([property]); | |
550 | } | |
551 | ||
552 | return groups; | |
553 | }, [ | |
554 | [] | |
555 | ]); | |
556 | } | |
557 | ||
558 | /** | |
559 | * Verifies correct vertical alignment of a group of properties. | |
560 | * @param {ASTNode[]} properties List of Property AST nodes. | |
561 | * @returns {void} | |
562 | */ | |
563 | function verifyGroupAlignment(properties) { | |
564 | const length = properties.length, | |
565 | widths = properties.map(getKeyWidth), // Width of keys, including quotes | |
566 | align = alignmentOptions.on; // "value" or "colon" | |
567 | let targetWidth = Math.max(...widths), | |
568 | beforeColon, afterColon, mode; | |
569 | ||
570 | if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration. | |
571 | beforeColon = alignmentOptions.beforeColon; | |
572 | afterColon = alignmentOptions.afterColon; | |
573 | mode = alignmentOptions.mode; | |
574 | } else { | |
575 | beforeColon = multiLineOptions.beforeColon; | |
576 | afterColon = multiLineOptions.afterColon; | |
577 | mode = alignmentOptions.mode; | |
578 | } | |
579 | ||
580 | // Conditionally include one space before or after colon | |
581 | targetWidth += (align === "colon" ? beforeColon : afterColon); | |
582 | ||
583 | for (let i = 0; i < length; i++) { | |
584 | const property = properties[i]; | |
585 | const whitespace = getPropertyWhitespace(property); | |
586 | ||
587 | if (whitespace) { // Object literal getters/setters lack a colon | |
588 | const width = widths[i]; | |
589 | ||
590 | if (align === "value") { | |
591 | report(property, "key", whitespace.beforeColon, beforeColon, mode); | |
592 | report(property, "value", whitespace.afterColon, targetWidth - width, mode); | |
593 | } else { // align = "colon" | |
594 | report(property, "key", whitespace.beforeColon, targetWidth - width, mode); | |
595 | report(property, "value", whitespace.afterColon, afterColon, mode); | |
596 | } | |
597 | } | |
598 | } | |
599 | } | |
600 | ||
601 | /** | |
602 | * Verifies spacing of property conforms to specified options. | |
603 | * @param {ASTNode} node Property node being evaluated. | |
604 | * @param {Object} lineOptions Configured singleLine or multiLine options | |
605 | * @returns {void} | |
606 | */ | |
607 | function verifySpacing(node, lineOptions) { | |
608 | const actual = getPropertyWhitespace(node); | |
609 | ||
610 | if (actual) { // Object literal getters/setters lack colons | |
611 | report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); | |
612 | report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); | |
613 | } | |
614 | } | |
615 | ||
616 | /** | |
617 | * Verifies spacing of each property in a list. | |
618 | * @param {ASTNode[]} properties List of Property AST nodes. | |
619 | * @param {Object} lineOptions Configured singleLine or multiLine options | |
620 | * @returns {void} | |
621 | */ | |
622 | function verifyListSpacing(properties, lineOptions) { | |
623 | const length = properties.length; | |
624 | ||
625 | for (let i = 0; i < length; i++) { | |
626 | verifySpacing(properties[i], lineOptions); | |
627 | } | |
628 | } | |
629 | ||
630 | /** | |
631 | * Verifies vertical alignment, taking into account groups of properties. | |
632 | * @param {ASTNode} node ObjectExpression node being evaluated. | |
633 | * @returns {void} | |
634 | */ | |
635 | function verifyAlignment(node) { | |
636 | createGroups(node).forEach(group => { | |
637 | const properties = group.filter(isKeyValueProperty); | |
638 | ||
639 | if (properties.length > 0 && isSingleLineProperties(properties)) { | |
640 | verifyListSpacing(properties, multiLineOptions); | |
641 | } else { | |
642 | verifyGroupAlignment(properties); | |
643 | } | |
644 | }); | |
645 | } | |
646 | ||
647 | //-------------------------------------------------------------------------- | |
648 | // Public API | |
649 | //-------------------------------------------------------------------------- | |
650 | ||
651 | if (alignmentOptions) { // Verify vertical alignment | |
652 | ||
653 | return { | |
654 | ObjectExpression(node) { | |
655 | if (isSingleLine(node)) { | |
656 | verifyListSpacing(node.properties.filter(isKeyValueProperty), singleLineOptions); | |
657 | } else { | |
658 | verifyAlignment(node); | |
659 | } | |
660 | } | |
661 | }; | |
662 | ||
663 | } | |
664 | ||
665 | // Obey beforeColon and afterColon in each property as configured | |
666 | return { | |
667 | Property(node) { | |
668 | verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions); | |
669 | } | |
670 | }; | |
671 | ||
672 | ||
673 | } | |
674 | }; |