]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/key-spacing.js
c09cebb513ae3dd141a4e73699d8515c0a923214
2 * @fileoverview Rule to specify spacing of object literal keys and values
3 * @author Brandon Mills
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const astUtils
= require("./utils/ast-utils");
13 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
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.
23 function containsLineTerminator(str
) {
24 return astUtils
.LINEBREAK_MATCHER
.test(str
);
28 * Gets the last element of an array.
29 * @param {Array} arr An array.
30 * @returns {any} Last element of arr.
33 return arr
[arr
.length
- 1];
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.
41 function isSingleLine(node
) {
42 return (node
.loc
.end
.line
=== node
.loc
.start
.line
);
46 * Checks whether the properties on a single line.
47 * @param {ASTNode[]} properties List of Property AST nodes.
48 * @returns {boolean} True if all properties is on a single line.
50 function isSingleLineProperties(properties
) {
51 const [firstProp
] = properties
,
52 lastProp
= last(properties
);
54 return firstProp
.loc
.start
.line
=== lastProp
.loc
.end
.line
;
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
63 function initOptionProperty(toOptions
, fromOptions
) {
64 toOptions
.mode
= fromOptions
.mode
|| "strict";
66 // Set value of beforeColon
67 if (typeof fromOptions
.beforeColon
!== "undefined") {
68 toOptions
.beforeColon
= +fromOptions
.beforeColon
;
70 toOptions
.beforeColon
= 0;
73 // Set value of afterColon
74 if (typeof fromOptions
.afterColon
!== "undefined") {
75 toOptions
.afterColon
= +fromOptions
.afterColon
;
77 toOptions
.afterColon
= 1;
80 // Set align if exists
81 if (typeof fromOptions
.align
!== "undefined") {
82 if (typeof fromOptions
.align
=== "object") {
83 toOptions
.align
= fromOptions
.align
;
86 on
: fromOptions
.align
,
88 beforeColon
: toOptions
.beforeColon
,
89 afterColon
: toOptions
.afterColon
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
103 function initOptions(toOptions
, fromOptions
) {
104 if (typeof fromOptions
.align
=== "object") {
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";
111 toOptions
.multiLine
= initOptionProperty({}, (fromOptions
.multiLine
|| fromOptions
));
112 toOptions
.singleLine
= initOptionProperty({}, (fromOptions
.singleLine
|| fromOptions
));
114 } else { // string or undefined
115 toOptions
.multiLine
= initOptionProperty({}, (fromOptions
.multiLine
|| fromOptions
));
116 toOptions
.singleLine
= initOptionProperty({}, (fromOptions
.singleLine
|| fromOptions
));
118 // If alignment options are defined in multiLine, pull them out into the general align configuration
119 if (toOptions
.multiLine
.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
132 //------------------------------------------------------------------------------
134 //------------------------------------------------------------------------------
141 description
: "enforce consistent spacing between keys and values in object literal properties",
143 url
: "https://eslint.org/docs/rules/key-spacing"
146 fixable
: "whitespace",
156 enum: ["colon", "value"]
162 enum: ["strict", "minimum"]
165 enum: ["colon", "value"]
174 additionalProperties
: false
179 enum: ["strict", "minimum"]
188 additionalProperties
: false
197 enum: ["strict", "minimum"]
206 additionalProperties
: false
214 enum: ["colon", "value"]
220 enum: ["strict", "minimum"]
223 enum: ["colon", "value"]
232 additionalProperties
: false
237 enum: ["strict", "minimum"]
246 additionalProperties
: false
249 additionalProperties
: false
258 enum: ["strict", "minimum"]
267 additionalProperties
: false
273 enum: ["strict", "minimum"]
282 additionalProperties
: false
288 enum: ["strict", "minimum"]
291 enum: ["colon", "value"]
300 additionalProperties
: false
303 additionalProperties
: false
308 extraKey
: "Extra space after {{computed}}key '{{key}}'.",
309 extraValue
: "Extra space before value for {{computed}}key '{{key}}'.",
310 missingKey
: "Missing space after {{computed}}key '{{key}}'.",
311 missingValue
: "Missing space before value for {{computed}}key '{{key}}'."
319 * "key-spacing": [2, {
320 * beforeColon: false,
322 * align: "colon" // Optional, or "value"
325 const options
= context
.options
[0] || {},
326 ruleOptions
= initOptions({}, options
),
327 multiLineOptions
= ruleOptions
.multiLine
,
328 singleLineOptions
= ruleOptions
.singleLine
,
329 alignmentOptions
= ruleOptions
.align
|| null;
331 const sourceCode
= context
.getSourceCode();
334 * Checks whether a property is a member of the property group it follows.
335 * @param {ASTNode} lastMember The last Property known to be in the group.
336 * @param {ASTNode} candidate The next Property that might be in the group.
337 * @returns {boolean} True if the candidate property is part of the group.
339 function continuesPropertyGroup(lastMember
, candidate
) {
340 const groupEndLine
= lastMember
.loc
.start
.line
,
341 candidateStartLine
= candidate
.loc
.start
.line
;
343 if (candidateStartLine
- groupEndLine
<= 1) {
348 * Check that the first comment is adjacent to the end of the group, the
349 * last comment is adjacent to the candidate property, and that successive
350 * comments are adjacent to each other.
352 const leadingComments
= sourceCode
.getCommentsBefore(candidate
);
355 leadingComments
.length
&&
356 leadingComments
[0].loc
.start
.line
- groupEndLine
<= 1 &&
357 candidateStartLine
- last(leadingComments
).loc
.end
.line
<= 1
359 for (let i
= 1; i
< leadingComments
.length
; i
++) {
360 if (leadingComments
[i
].loc
.start
.line
- leadingComments
[i
- 1].loc
.end
.line
> 1) {
371 * Determines if the given property is key-value property.
372 * @param {ASTNode} property Property node to check.
373 * @returns {boolean} Whether the property is a key-value property.
375 function isKeyValueProperty(property
) {
378 property
.shorthand
||
379 property
.kind
!== "init" || property
.type
!== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement"
384 * Starting from the given a node (a property.key node here) looks forward
385 * until it finds the last token before a colon punctuator and returns it.
386 * @param {ASTNode} node The node to start looking from.
387 * @returns {ASTNode} The last token before a colon punctuator.
389 function getLastTokenBeforeColon(node
) {
390 const colonToken
= sourceCode
.getTokenAfter(node
, astUtils
.isColonToken
);
392 return sourceCode
.getTokenBefore(colonToken
);
396 * Starting from the given a node (a property.key node here) looks forward
397 * until it finds the colon punctuator and returns it.
398 * @param {ASTNode} node The node to start looking from.
399 * @returns {ASTNode} The colon punctuator.
401 function getNextColon(node
) {
402 return sourceCode
.getTokenAfter(node
, astUtils
.isColonToken
);
406 * Gets an object literal property's key as the identifier name or string value.
407 * @param {ASTNode} property Property node whose key to retrieve.
408 * @returns {string} The property's key.
410 function getKey(property
) {
411 const key
= property
.key
;
413 if (property
.computed
) {
414 return sourceCode
.getText().slice(key
.range
[0], key
.range
[1]);
416 return astUtils
.getStaticPropertyName(property
);
420 * Reports an appropriately-formatted error if spacing is incorrect on one
422 * @param {ASTNode} property Key-value pair in an object literal.
423 * @param {string} side Side being verified - either "key" or "value".
424 * @param {string} whitespace Actual whitespace string.
425 * @param {int} expected Expected whitespace length.
426 * @param {string} mode Value of the mode as "strict" or "minimum"
429 function report(property
, side
, whitespace
, expected
, mode
) {
430 const diff
= whitespace
.length
- expected
;
433 diff
&& mode
=== "strict" ||
434 diff
< 0 && mode
=== "minimum" ||
435 diff
> 0 && !expected
&& mode
=== "minimum") &&
436 !(expected
&& containsLineTerminator(whitespace
))
438 const nextColon
= getNextColon(property
.key
),
439 tokenBeforeColon
= sourceCode
.getTokenBefore(nextColon
, { includeComments
: true }),
440 tokenAfterColon
= sourceCode
.getTokenAfter(nextColon
, { includeComments
: true }),
441 isKeySide
= side
=== "key",
443 diffAbs
= Math
.abs(diff
),
444 spaces
= Array(diffAbs
+ 1).join(" ");
446 const locStart
= isKeySide
? tokenBeforeColon
.loc
.end
: nextColon
.loc
.start
;
447 const locEnd
= isKeySide
? nextColon
.loc
.start
: tokenAfterColon
.loc
.start
;
448 const missingLoc
= isKeySide
? tokenBeforeColon
.loc
: tokenAfterColon
.loc
;
449 const loc
= isExtra
? { start
: locStart
, end
: locEnd
} : missingLoc
;
458 range
= [tokenBeforeColon
.range
[1], tokenBeforeColon
.range
[1] + diffAbs
];
460 range
= [tokenAfterColon
.range
[0] - diffAbs
, tokenAfterColon
.range
[0]];
462 fix = function(fixer
) {
463 return fixer
.removeRange(range
);
469 fix = function(fixer
) {
470 return fixer
.insertTextAfter(tokenBeforeColon
, spaces
);
473 fix = function(fixer
) {
474 return fixer
.insertTextBefore(tokenAfterColon
, spaces
);
482 messageId
= side
=== "key" ? "extraKey" : "extraValue";
484 messageId
= side
=== "key" ? "missingKey" : "missingValue";
488 node
: property
[side
],
492 computed
: property
.computed
? "computed " : "",
493 key
: getKey(property
)
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.
506 function getKeyWidth(property
) {
507 const startToken
= sourceCode
.getFirstToken(property
);
508 const endToken
= getLastTokenBeforeColon(property
.key
);
510 return endToken
.range
[1] - startToken
.range
[0];
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.
518 function getPropertyWhitespace(property
) {
519 const whitespace
= /(\s*):(\s*)/u.exec(sourceCode
.getText().slice(
520 property
.key
.range
[1], property
.value
.range
[0]
525 beforeColon
: whitespace
[1],
526 afterColon
: whitespace
[2]
533 * Creates groups of properties.
534 * @param {ASTNode} node ObjectExpression node being evaluated.
535 * @returns {Array<ASTNode[]>} Groups of property AST node lists.
537 function createGroups(node
) {
538 if (node
.properties
.length
=== 1) {
539 return [node
.properties
];
542 return node
.properties
.reduce((groups
, property
) => {
543 const currentGroup
= last(groups
),
544 prev
= last(currentGroup
);
546 if (!prev
|| continuesPropertyGroup(prev
, property
)) {
547 currentGroup
.push(property
);
549 groups
.push([property
]);
559 * Verifies correct vertical alignment of a group of properties.
560 * @param {ASTNode[]} properties List of Property AST nodes.
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
;
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
;
575 beforeColon
= multiLineOptions
.beforeColon
;
576 afterColon
= multiLineOptions
.afterColon
;
577 mode
= alignmentOptions
.mode
;
580 // Conditionally include one space before or after colon
581 targetWidth
+= (align
=== "colon" ? beforeColon
: afterColon
);
583 for (let i
= 0; i
< length
; i
++) {
584 const property
= properties
[i
];
585 const whitespace
= getPropertyWhitespace(property
);
587 if (whitespace
) { // Object literal getters/setters lack a colon
588 const width
= widths
[i
];
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
);
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
607 function verifySpacing(node
, lineOptions
) {
608 const actual
= getPropertyWhitespace(node
);
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
);
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
622 function verifyListSpacing(properties
, lineOptions
) {
623 const length
= properties
.length
;
625 for (let i
= 0; i
< length
; i
++) {
626 verifySpacing(properties
[i
], lineOptions
);
631 * Verifies vertical alignment, taking into account groups of properties.
632 * @param {ASTNode} node ObjectExpression node being evaluated.
635 function verifyAlignment(node
) {
636 createGroups(node
).forEach(group
=> {
637 const properties
= group
.filter(isKeyValueProperty
);
639 if (properties
.length
> 0 && isSingleLineProperties(properties
)) {
640 verifyListSpacing(properties
, multiLineOptions
);
642 verifyGroupAlignment(properties
);
647 //--------------------------------------------------------------------------
649 //--------------------------------------------------------------------------
651 if (alignmentOptions
) { // Verify vertical alignment
654 ObjectExpression(node
) {
655 if (isSingleLine(node
)) {
656 verifyListSpacing(node
.properties
.filter(isKeyValueProperty
), singleLineOptions
);
658 verifyAlignment(node
);
665 // Obey beforeColon and afterColon in each property as configured
668 verifySpacing(node
, isSingleLine(node
.parent
) ? singleLineOptions
: multiLineOptions
);