2 * @fileoverview This rule sets a specific indentation style and width for your code
5 * @author Vitaly Puzrin
6 * @author Gyandeep Singh
11 //------------------------------------------------------------------------------
13 //------------------------------------------------------------------------------
15 const lodash
= require("lodash");
16 const astUtils
= require("./utils/ast-utils");
17 const createTree
= require("functional-red-black-tree");
19 //------------------------------------------------------------------------------
21 //------------------------------------------------------------------------------
23 const KNOWN_NODES
= new Set([
24 "AssignmentExpression",
28 "ArrowFunctionExpression",
38 "ConditionalExpression",
43 "ExperimentalRestProperty",
44 "ExperimentalSpreadProperty",
45 "ExpressionStatement",
49 "FunctionDeclaration",
71 "TaggedTemplateExpression",
79 "VariableDeclaration",
89 "JSXMemberExpression",
91 "JSXExpressionContainer",
98 "ExportDefaultDeclaration",
99 "ExportNamedDeclaration",
100 "ExportAllDeclaration",
104 "ImportDefaultSpecifier",
105 "ImportNamespaceSpecifier",
110 * General rule strategy:
111 * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another
112 * specified token or to the first column.
113 * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a
114 * BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly
115 * brace of the BlockStatement.
116 * 3. After traversing the AST, calculate the expected indentation levels of every token according to the
117 * OffsetStorage container.
118 * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file,
119 * and report the token if the two values are not equal.
124 * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique.
125 * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation
126 * can easily be swapped out.
128 class BinarySearchTree
{
131 * Creates an empty tree
134 this._rbTree
= createTree();
138 * Inserts an entry into the tree.
139 * @param {number} key The entry's key
140 * @param {*} value The entry's value
144 const iterator
= this._rbTree
.find(key
);
146 if (iterator
.valid
) {
147 this._rbTree
= iterator
.update(value
);
149 this._rbTree
= this._rbTree
.insert(key
, value
);
154 * Finds the entry with the largest key less than or equal to the provided key
155 * @param {number} key The provided key
156 * @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists.
159 const iterator
= this._rbTree
.le(key
);
161 return iterator
&& { key
: iterator
.key
, value
: iterator
.value
};
165 * Deletes all of the keys in the interval [start, end)
166 * @param {number} start The start of the range
167 * @param {number} end The end of the range
170 deleteRange(start
, end
) {
172 // Exit without traversing the tree if the range has zero size.
176 const iterator
= this._rbTree
.ge(start
);
178 while (iterator
.valid
&& iterator
.key
< end
) {
179 this._rbTree
= this._rbTree
.remove(iterator
.key
);
186 * A helper class to get token-based info related to indentation
190 // eslint-disable-next-line jsdoc/require-description
192 * @param {SourceCode} sourceCode A SourceCode object
194 constructor(sourceCode
) {
195 this.sourceCode
= sourceCode
;
196 this.firstTokensByLineNumber
= sourceCode
.tokensAndComments
.reduce((map
, token
) => {
197 if (!map
.has(token
.loc
.start
.line
)) {
198 map
.set(token
.loc
.start
.line
, token
);
200 if (!map
.has(token
.loc
.end
.line
) && sourceCode
.text
.slice(token
.range
[1] - token
.loc
.end
.column
, token
.range
[1]).trim()) {
201 map
.set(token
.loc
.end
.line
, token
);
208 * Gets the first token on a given token's line
209 * @param {Token|ASTNode} token a node or token
210 * @returns {Token} The first token on the given line
212 getFirstTokenOfLine(token
) {
213 return this.firstTokensByLineNumber
.get(token
.loc
.start
.line
);
217 * Determines whether a token is the first token in its line
218 * @param {Token} token The token
219 * @returns {boolean} `true` if the token is the first on its line
221 isFirstTokenOfLine(token
) {
222 return this.getFirstTokenOfLine(token
) === token
;
226 * Get the actual indent of a token
227 * @param {Token} token Token to examine. This should be the first token on its line.
228 * @returns {string} The indentation characters that precede the token
230 getTokenIndent(token
) {
231 return this.sourceCode
.text
.slice(token
.range
[0] - token
.loc
.start
.column
, token
.range
[0]);
236 * A class to store information on desired offsets of tokens from each other
238 class OffsetStorage
{
240 // eslint-disable-next-line jsdoc/require-description
242 * @param {TokenInfo} tokenInfo a TokenInfo instance
243 * @param {number} indentSize The desired size of each indentation level
244 * @param {string} indentType The indentation character
246 constructor(tokenInfo
, indentSize
, indentType
) {
247 this._tokenInfo
= tokenInfo
;
248 this._indentSize
= indentSize
;
249 this._indentType
= indentType
;
251 this._tree
= new BinarySearchTree();
252 this._tree
.insert(0, { offset
: 0, from: null, force
: false });
254 this._lockedFirstTokens
= new WeakMap();
255 this._desiredIndentCache
= new WeakMap();
256 this._ignoredTokens
= new WeakSet();
259 _getOffsetDescriptor(token
) {
260 return this._tree
.findLe(token
.range
[0]).value
;
264 * Sets the offset column of token B to match the offset column of token A.
265 * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
266 * most cases, `setDesiredOffset` should be used instead.
267 * @param {Token} baseToken The first token
268 * @param {Token} offsetToken The second token, whose offset should be matched to the first token
271 matchOffsetOf(baseToken
, offsetToken
) {
274 * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to
275 * the token that it depends on. For example, with the `ArrayExpression: first` option, the first
276 * token of each element in the array after the first will be mapped to the first token of the first
277 * element. The desired indentation of each of these tokens is computed based on the desired indentation
278 * of the "first" element, rather than through the normal offset mechanism.
280 this._lockedFirstTokens
.set(offsetToken
, baseToken
);
284 * Sets the desired offset of a token.
286 * This uses a line-based offset collapsing behavior to handle tokens on the same line.
287 * For example, consider the following two cases:
299 * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from
300 * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is
301 * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces)
302 * from the start of its line.
304 * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level
305 * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the
306 * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented
307 * by 1 indent level from the start of the line.
309 * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node,
310 * without needing to check which lines those tokens are on.
312 * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive
313 * behavior can occur. For example, consider the following cases:
326 * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz`
327 * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz`
328 * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no
329 * collapsing would occur).
331 * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and
332 * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed
333 * in the second case.
334 * @param {Token} token The token
335 * @param {Token} fromToken The token that `token` should be offset from
336 * @param {number} offset The desired indent level
339 setDesiredOffset(token
, fromToken
, offset
) {
340 return this.setDesiredOffsets(token
.range
, fromToken
, offset
);
344 * Sets the desired offset of all tokens in a range
345 * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens.
346 * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains
347 * it). This means that the offset of each token is updated O(AST depth) times.
348 * It would not be performant to store and update the offsets for each token independently, because the rule would end
349 * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files.
351 * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
352 * list could represent the state of the offset tree at a given point:
354 * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file
355 * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
356 * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
357 * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
358 * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token
360 * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
361 * `setDesiredOffsets([30, 43], fooToken, 1);`
362 * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied.
363 * @param {Token} fromToken The token that this is offset from
364 * @param {number} offset The desired indent level
365 * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false.
368 setDesiredOffsets(range
, fromToken
, offset
, force
) {
371 * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset
372 * descriptor. The tree for the example above would have the following nodes:
374 * * key: 0, value: { offset: 0, from: null }
375 * * key: 15, value: { offset: 1, from: barToken }
376 * * key: 30, value: { offset: 1, from: fooToken }
377 * * key: 43, value: { offset: 2, from: barToken }
378 * * key: 820, value: { offset: 1, from: bazToken }
380 * To find the offset descriptor for any given token, one needs to find the node with the largest key
381 * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary
382 * search tree indexed by key.
385 const descriptorToInsert
= { offset
, from: fromToken
, force
};
387 const descriptorAfterRange
= this._tree
.findLe(range
[1]).value
;
389 const fromTokenIsInRange
= fromToken
&& fromToken
.range
[0] >= range
[0] && fromToken
.range
[1] <= range
[1];
390 const fromTokenDescriptor
= fromTokenIsInRange
&& this._getOffsetDescriptor(fromToken
);
392 // First, remove any existing nodes in the range from the tree.
393 this._tree
.deleteRange(range
[0] + 1, range
[1]);
395 // Insert a new node into the tree for this range
396 this._tree
.insert(range
[0], descriptorToInsert
);
399 * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously,
400 * even if it's in the current range.
402 if (fromTokenIsInRange
) {
403 this._tree
.insert(fromToken
.range
[0], fromTokenDescriptor
);
404 this._tree
.insert(fromToken
.range
[1], descriptorToInsert
);
408 * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following
409 * tokens the same as it was before.
411 this._tree
.insert(range
[1], descriptorAfterRange
);
415 * Gets the desired indent of a token
416 * @param {Token} token The token
417 * @returns {string} The desired indent of the token
419 getDesiredIndent(token
) {
420 if (!this._desiredIndentCache
.has(token
)) {
422 if (this._ignoredTokens
.has(token
)) {
425 * If the token is ignored, use the actual indent of the token as the desired indent.
426 * This ensures that no errors are reported for this token.
428 this._desiredIndentCache
.set(
430 this._tokenInfo
.getTokenIndent(token
)
432 } else if (this._lockedFirstTokens
.has(token
)) {
433 const firstToken
= this._lockedFirstTokens
.get(token
);
435 this._desiredIndentCache
.set(
438 // (indentation for the first element's line)
439 this.getDesiredIndent(this._tokenInfo
.getFirstTokenOfLine(firstToken
)) +
441 // (space between the start of the first element's line and the first element)
442 this._indentType
.repeat(firstToken
.loc
.start
.column
- this._tokenInfo
.getFirstTokenOfLine(firstToken
).loc
.start
.column
)
445 const offsetInfo
= this._getOffsetDescriptor(token
);
448 offsetInfo
.from.loc
.start
.line
=== token
.loc
.start
.line
&&
449 !/^\s*?\n/u.test(token
.value
) &&
451 ) ? 0 : offsetInfo
.offset
* this._indentSize
;
453 this._desiredIndentCache
.set(
455 (offsetInfo
.from ? this.getDesiredIndent(offsetInfo
.from) : "") + this._indentType
.repeat(offset
)
459 return this._desiredIndentCache
.get(token
);
463 * Ignores a token, preventing it from being reported.
464 * @param {Token} token The token
468 if (this._tokenInfo
.isFirstTokenOfLine(token
)) {
469 this._ignoredTokens
.add(token
);
474 * Gets the first token that the given token's indentation is dependent on
475 * @param {Token} token The token
476 * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level
478 getFirstDependency(token
) {
479 return this._getOffsetDescriptor(token
).from;
483 const ELEMENT_LIST_SCHEMA
= {
490 enum: ["first", "off"]
500 description
: "enforce consistent indentation",
501 category
: "Stylistic Issues",
503 url
: "https://eslint.org/docs/rules/indent"
506 fixable
: "whitespace",
528 VariableDeclarator
: {
534 var: ELEMENT_LIST_SCHEMA
,
535 let: ELEMENT_LIST_SCHEMA
,
536 const: ELEMENT_LIST_SCHEMA
538 additionalProperties
: false
564 FunctionDeclaration
: {
567 parameters
: ELEMENT_LIST_SCHEMA
,
573 additionalProperties
: false
575 FunctionExpression
: {
578 parameters
: ELEMENT_LIST_SCHEMA
,
584 additionalProperties
: false
589 arguments
: ELEMENT_LIST_SCHEMA
591 additionalProperties
: false
593 ArrayExpression
: ELEMENT_LIST_SCHEMA
,
594 ObjectExpression
: ELEMENT_LIST_SCHEMA
,
595 ImportDeclaration
: ELEMENT_LIST_SCHEMA
,
596 flatTernaryExpressions
: {
600 offsetTernaryExpressions
: {
618 additionalProperties
: false
622 wrongIndentation
: "Expected indentation of {{expected}} but found {{actual}}."
627 const DEFAULT_VARIABLE_INDENT
= 1;
628 const DEFAULT_PARAMETER_INDENT
= 1;
629 const DEFAULT_FUNCTION_BODY_INDENT
= 1;
631 let indentType
= "space";
635 VariableDeclarator
: {
636 var: DEFAULT_VARIABLE_INDENT
,
637 let: DEFAULT_VARIABLE_INDENT
,
638 const: DEFAULT_VARIABLE_INDENT
641 FunctionDeclaration
: {
642 parameters
: DEFAULT_PARAMETER_INDENT
,
643 body
: DEFAULT_FUNCTION_BODY_INDENT
645 FunctionExpression
: {
646 parameters
: DEFAULT_PARAMETER_INDENT
,
647 body
: DEFAULT_FUNCTION_BODY_INDENT
650 arguments
: DEFAULT_PARAMETER_INDENT
655 ImportDeclaration
: 1,
656 flatTernaryExpressions
: false,
658 ignoreComments
: false
661 if (context
.options
.length
) {
662 if (context
.options
[0] === "tab") {
666 indentSize
= context
.options
[0];
667 indentType
= "space";
670 if (context
.options
[1]) {
671 Object
.assign(options
, context
.options
[1]);
673 if (typeof options
.VariableDeclarator
=== "number" || options
.VariableDeclarator
=== "first") {
674 options
.VariableDeclarator
= {
675 var: options
.VariableDeclarator
,
676 let: options
.VariableDeclarator
,
677 const: options
.VariableDeclarator
683 const sourceCode
= context
.getSourceCode();
684 const tokenInfo
= new TokenInfo(sourceCode
);
685 const offsets
= new OffsetStorage(tokenInfo
, indentSize
, indentType
=== "space" ? " " : "\t");
686 const parameterParens
= new WeakSet();
689 * Creates an error message for a line, given the expected/actual indentation.
690 * @param {int} expectedAmount The expected amount of indentation characters for this line
691 * @param {int} actualSpaces The actual number of indentation spaces that were found on this line
692 * @param {int} actualTabs The actual number of indentation tabs that were found on this line
693 * @returns {string} An error message for this line
695 function createErrorMessageData(expectedAmount
, actualSpaces
, actualTabs
) {
696 const expectedStatement
= `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
697 const foundSpacesWord
= `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
698 const foundTabsWord
= `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
701 if (actualSpaces
> 0) {
704 * Abbreviate the message if the expected indentation is also spaces.
705 * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
707 foundStatement
= indentType
=== "space" ? actualSpaces
: `${actualSpaces} ${foundSpacesWord}`;
708 } else if (actualTabs
> 0) {
709 foundStatement
= indentType
=== "tab" ? actualTabs
: `${actualTabs} ${foundTabsWord}`;
711 foundStatement
= "0";
714 expected
: expectedStatement
,
715 actual
: foundStatement
720 * Reports a given indent violation
721 * @param {Token} token Token violating the indent rule
722 * @param {string} neededIndent Expected indentation string
725 function report(token
, neededIndent
) {
726 const actualIndent
= Array
.from(tokenInfo
.getTokenIndent(token
));
727 const numSpaces
= actualIndent
.filter(char => char === " ").length
;
728 const numTabs
= actualIndent
.filter(char => char === "\t").length
;
732 messageId
: "wrongIndentation",
733 data
: createErrorMessageData(neededIndent
.length
, numSpaces
, numTabs
),
735 start
: { line
: token
.loc
.start
.line
, column
: 0 },
736 end
: { line
: token
.loc
.start
.line
, column
: token
.loc
.start
.column
}
739 const range
= [token
.range
[0] - token
.loc
.start
.column
, token
.range
[0]];
740 const newText
= neededIndent
;
742 return fixer
.replaceTextRange(range
, newText
);
748 * Checks if a token's indentation is correct
749 * @param {Token} token Token to examine
750 * @param {string} desiredIndent Desired indentation of the string
751 * @returns {boolean} `true` if the token's indentation is correct
753 function validateTokenIndent(token
, desiredIndent
) {
754 const indentation
= tokenInfo
.getTokenIndent(token
);
756 return indentation
=== desiredIndent
||
758 // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs.
759 indentation
.includes(" ") && indentation
.includes("\t");
763 * Check to see if the node is a file level IIFE
764 * @param {ASTNode} node The function node to check.
765 * @returns {boolean} True if the node is the outer IIFE
767 function isOuterIIFE(node
) {
770 * Verify that the node is an IIFE
772 if (!node
.parent
|| node
.parent
.type
!== "CallExpression" || node
.parent
.callee
!== node
) {
777 * Navigate legal ancestors to determine whether this IIFE is outer.
778 * A "legal ancestor" is an expression or statement that causes the function to get executed immediately.
779 * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator.
781 let statement
= node
.parent
&& node
.parent
.parent
;
784 statement
.type
=== "UnaryExpression" && ["!", "~", "+", "-"].indexOf(statement
.operator
) > -1 ||
785 statement
.type
=== "AssignmentExpression" ||
786 statement
.type
=== "LogicalExpression" ||
787 statement
.type
=== "SequenceExpression" ||
788 statement
.type
=== "VariableDeclarator"
790 statement
= statement
.parent
;
793 return (statement
.type
=== "ExpressionStatement" || statement
.type
=== "VariableDeclaration") && statement
.parent
.type
=== "Program";
797 * Counts the number of linebreaks that follow the last non-whitespace character in a string
798 * @param {string} string The string to check
799 * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character,
800 * or the total number of linebreaks if the string is all whitespace.
802 function countTrailingLinebreaks(string
) {
803 const trailingWhitespace
= string
.match(/\s*$/u)[0];
804 const linebreakMatches
= trailingWhitespace
.match(astUtils
.createGlobalLinebreakMatcher());
806 return linebreakMatches
=== null ? 0 : linebreakMatches
.length
;
810 * Check indentation for lists of elements (arrays, objects, function params)
811 * @param {ASTNode[]} elements List of elements that should be offset
812 * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '['
813 * @param {Token} endToken The end token of the list, e.g. ']'
814 * @param {number|string} offset The amount that the elements should be offset
817 function addElementListIndent(elements
, startToken
, endToken
, offset
) {
820 * Gets the first token of a given element, including surrounding parentheses.
821 * @param {ASTNode} element A node in the `elements` list
822 * @returns {Token} The first token of this element
824 function getFirstToken(element
) {
825 let token
= sourceCode
.getTokenBefore(element
);
827 while (astUtils
.isOpeningParenToken(token
) && token
!== startToken
) {
828 token
= sourceCode
.getTokenBefore(token
);
830 return sourceCode
.getTokenAfter(token
);
833 // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden)
834 offsets
.setDesiredOffsets(
835 [startToken
.range
[1], endToken
.range
[0]],
837 typeof offset
=== "number" ? offset
: 1
839 offsets
.setDesiredOffset(endToken
, startToken
, 0);
841 // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level.
842 if (offset
=== "first" && elements
.length
&& !elements
[0]) {
845 elements
.forEach((element
, index
) => {
848 // Skip holes in arrays
851 if (offset
=== "off") {
853 // Ignore the first token of every element if the "off" option is used
854 offsets
.ignoreToken(getFirstToken(element
));
857 // Offset the following elements correctly relative to the first element
861 if (offset
=== "first" && tokenInfo
.isFirstTokenOfLine(getFirstToken(element
))) {
862 offsets
.matchOffsetOf(getFirstToken(elements
[0]), getFirstToken(element
));
864 const previousElement
= elements
[index
- 1];
865 const firstTokenOfPreviousElement
= previousElement
&& getFirstToken(previousElement
);
866 const previousElementLastToken
= previousElement
&& sourceCode
.getLastToken(previousElement
);
870 previousElementLastToken
.loc
.end
.line
- countTrailingLinebreaks(previousElementLastToken
.value
) > startToken
.loc
.end
.line
872 offsets
.setDesiredOffsets(
873 [previousElement
.range
[1], element
.range
[1]],
874 firstTokenOfPreviousElement
,
883 * Check and decide whether to check for indentation for blockless nodes
884 * Scenarios are for or while statements without braces around them
885 * @param {ASTNode} node node to examine
888 function addBlocklessNodeIndent(node
) {
889 if (node
.type
!== "BlockStatement") {
890 const lastParentToken
= sourceCode
.getTokenBefore(node
, astUtils
.isNotOpeningParenToken
);
892 let firstBodyToken
= sourceCode
.getFirstToken(node
);
893 let lastBodyToken
= sourceCode
.getLastToken(node
);
896 astUtils
.isOpeningParenToken(sourceCode
.getTokenBefore(firstBodyToken
)) &&
897 astUtils
.isClosingParenToken(sourceCode
.getTokenAfter(lastBodyToken
))
899 firstBodyToken
= sourceCode
.getTokenBefore(firstBodyToken
);
900 lastBodyToken
= sourceCode
.getTokenAfter(lastBodyToken
);
903 offsets
.setDesiredOffsets([firstBodyToken
.range
[0], lastBodyToken
.range
[1]], lastParentToken
, 1);
906 * For blockless nodes with semicolon-first style, don't indent the semicolon.
909 * ; [1, 2, 3].map(foo)
911 const lastToken
= sourceCode
.getLastToken(node
);
913 if (node
.type
!== "EmptyStatement" && astUtils
.isSemicolonToken(lastToken
)) {
914 offsets
.setDesiredOffset(lastToken
, lastParentToken
, 0);
920 * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
921 * @param {ASTNode} node A CallExpression or NewExpression node
924 function addFunctionCallIndent(node
) {
927 if (node
.arguments
.length
) {
928 openingParen
= sourceCode
.getFirstTokenBetween(node
.callee
, node
.arguments
[0], astUtils
.isOpeningParenToken
);
930 openingParen
= sourceCode
.getLastToken(node
, 1);
932 const closingParen
= sourceCode
.getLastToken(node
);
934 parameterParens
.add(openingParen
);
935 parameterParens
.add(closingParen
);
937 const offsetAfterToken
= node
.callee
.type
=== "TaggedTemplateExpression" ? sourceCode
.getFirstToken(node
.callee
.quasi
) : openingParen
;
938 const offsetToken
= sourceCode
.getTokenBefore(offsetAfterToken
);
940 offsets
.setDesiredOffset(openingParen
, offsetToken
, 0);
942 addElementListIndent(node
.arguments
, openingParen
, closingParen
, options
.CallExpression
.arguments
);
946 * Checks the indentation of parenthesized values, given a list of tokens in a program
947 * @param {Token[]} tokens A list of tokens
950 function addParensIndent(tokens
) {
951 const parenStack
= [];
952 const parenPairs
= [];
954 tokens
.forEach(nextToken
=> {
956 // Accumulate a list of parenthesis pairs
957 if (astUtils
.isOpeningParenToken(nextToken
)) {
958 parenStack
.push(nextToken
);
959 } else if (astUtils
.isClosingParenToken(nextToken
)) {
960 parenPairs
.unshift({ left
: parenStack
.pop(), right
: nextToken
});
964 parenPairs
.forEach(pair
=> {
965 const leftParen
= pair
.left
;
966 const rightParen
= pair
.right
;
968 // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments.
969 if (!parameterParens
.has(leftParen
) && !parameterParens
.has(rightParen
)) {
970 const parenthesizedTokens
= new Set(sourceCode
.getTokensBetween(leftParen
, rightParen
));
972 parenthesizedTokens
.forEach(token
=> {
973 if (!parenthesizedTokens
.has(offsets
.getFirstDependency(token
))) {
974 offsets
.setDesiredOffset(token
, leftParen
, 1);
979 offsets
.setDesiredOffset(rightParen
, leftParen
, 0);
984 * Ignore all tokens within an unknown node whose offset do not depend
985 * on another token's offset within the unknown node
986 * @param {ASTNode} node Unknown Node
989 function ignoreNode(node
) {
990 const unknownNodeTokens
= new Set(sourceCode
.getTokens(node
, { includeComments
: true }));
992 unknownNodeTokens
.forEach(token
=> {
993 if (!unknownNodeTokens
.has(offsets
.getFirstDependency(token
))) {
994 const firstTokenOfLine
= tokenInfo
.getFirstTokenOfLine(token
);
996 if (token
=== firstTokenOfLine
) {
997 offsets
.ignoreToken(token
);
999 offsets
.setDesiredOffset(token
, firstTokenOfLine
, 0);
1006 * Check whether the given token is on the first line of a statement.
1007 * @param {Token} token The token to check.
1008 * @param {ASTNode} leafNode The expression node that the token belongs directly.
1009 * @returns {boolean} `true` if the token is on the first line of a statement.
1011 function isOnFirstLineOfStatement(token
, leafNode
) {
1012 let node
= leafNode
;
1014 while (node
.parent
&& !node
.parent
.type
.endsWith("Statement") && !node
.parent
.type
.endsWith("Declaration")) {
1019 return !node
|| node
.loc
.start
.line
=== token
.loc
.start
.line
;
1023 * Check whether there are any blank (whitespace-only) lines between
1024 * two tokens on separate lines.
1025 * @param {Token} firstToken The first token.
1026 * @param {Token} secondToken The second token.
1027 * @returns {boolean} `true` if the tokens are on separate lines and
1028 * there exists a blank line between them, `false` otherwise.
1030 function hasBlankLinesBetween(firstToken
, secondToken
) {
1031 const firstTokenLine
= firstToken
.loc
.end
.line
;
1032 const secondTokenLine
= secondToken
.loc
.start
.line
;
1034 if (firstTokenLine
=== secondTokenLine
|| firstTokenLine
=== secondTokenLine
- 1) {
1038 for (let line
= firstTokenLine
+ 1; line
< secondTokenLine
; ++line
) {
1039 if (!tokenInfo
.firstTokensByLineNumber
.has(line
)) {
1047 const ignoredNodeFirstTokens
= new Set();
1049 const baseOffsetListeners
= {
1050 "ArrayExpression, ArrayPattern"(node
) {
1051 const openingBracket
= sourceCode
.getFirstToken(node
);
1052 const closingBracket
= sourceCode
.getTokenAfter(lodash
.findLast(node
.elements
) || openingBracket
, astUtils
.isClosingBracketToken
);
1054 addElementListIndent(node
.elements
, openingBracket
, closingBracket
, options
.ArrayExpression
);
1057 "ObjectExpression, ObjectPattern"(node
) {
1058 const openingCurly
= sourceCode
.getFirstToken(node
);
1059 const closingCurly
= sourceCode
.getTokenAfter(
1060 node
.properties
.length
? node
.properties
[node
.properties
.length
- 1] : openingCurly
,
1061 astUtils
.isClosingBraceToken
1064 addElementListIndent(node
.properties
, openingCurly
, closingCurly
, options
.ObjectExpression
);
1067 ArrowFunctionExpression(node
) {
1068 const firstToken
= sourceCode
.getFirstToken(node
);
1070 if (astUtils
.isOpeningParenToken(firstToken
)) {
1071 const openingParen
= firstToken
;
1072 const closingParen
= sourceCode
.getTokenBefore(node
.body
, astUtils
.isClosingParenToken
);
1074 parameterParens
.add(openingParen
);
1075 parameterParens
.add(closingParen
);
1076 addElementListIndent(node
.params
, openingParen
, closingParen
, options
.FunctionExpression
.parameters
);
1078 addBlocklessNodeIndent(node
.body
);
1081 AssignmentExpression(node
) {
1082 const operator
= sourceCode
.getFirstTokenBetween(node
.left
, node
.right
, token
=> token
.value
=== node
.operator
);
1084 offsets
.setDesiredOffsets([operator
.range
[0], node
.range
[1]], sourceCode
.getLastToken(node
.left
), 1);
1085 offsets
.ignoreToken(operator
);
1086 offsets
.ignoreToken(sourceCode
.getTokenAfter(operator
));
1089 "BinaryExpression, LogicalExpression"(node
) {
1090 const operator
= sourceCode
.getFirstTokenBetween(node
.left
, node
.right
, token
=> token
.value
=== node
.operator
);
1093 * For backwards compatibility, don't check BinaryExpression indents, e.g.
1098 const tokenAfterOperator
= sourceCode
.getTokenAfter(operator
);
1100 offsets
.ignoreToken(operator
);
1101 offsets
.ignoreToken(tokenAfterOperator
);
1102 offsets
.setDesiredOffset(tokenAfterOperator
, operator
, 0);
1105 "BlockStatement, ClassBody"(node
) {
1106 let blockIndentLevel
;
1108 if (node
.parent
&& isOuterIIFE(node
.parent
)) {
1109 blockIndentLevel
= options
.outerIIFEBody
;
1110 } else if (node
.parent
&& (node
.parent
.type
=== "FunctionExpression" || node
.parent
.type
=== "ArrowFunctionExpression")) {
1111 blockIndentLevel
= options
.FunctionExpression
.body
;
1112 } else if (node
.parent
&& node
.parent
.type
=== "FunctionDeclaration") {
1113 blockIndentLevel
= options
.FunctionDeclaration
.body
;
1115 blockIndentLevel
= 1;
1119 * For blocks that aren't lone statements, ensure that the opening curly brace
1120 * is aligned with the parent.
1122 if (!astUtils
.STATEMENT_LIST_PARENTS
.has(node
.parent
.type
)) {
1123 offsets
.setDesiredOffset(sourceCode
.getFirstToken(node
), sourceCode
.getFirstToken(node
.parent
), 0);
1126 addElementListIndent(node
.body
, sourceCode
.getFirstToken(node
), sourceCode
.getLastToken(node
), blockIndentLevel
);
1129 CallExpression
: addFunctionCallIndent
,
1131 "ClassDeclaration[superClass], ClassExpression[superClass]"(node
) {
1132 const classToken
= sourceCode
.getFirstToken(node
);
1133 const extendsToken
= sourceCode
.getTokenBefore(node
.superClass
, astUtils
.isNotOpeningParenToken
);
1135 offsets
.setDesiredOffsets([extendsToken
.range
[0], node
.body
.range
[0]], classToken
, 1);
1138 ConditionalExpression(node
) {
1139 const firstToken
= sourceCode
.getFirstToken(node
);
1141 // `flatTernaryExpressions` option is for the following style:
1146 if (!options
.flatTernaryExpressions
||
1147 !astUtils
.isTokenOnSameLine(node
.test
, node
.consequent
) ||
1148 isOnFirstLineOfStatement(firstToken
, node
)
1150 const questionMarkToken
= sourceCode
.getFirstTokenBetween(node
.test
, node
.consequent
, token
=> token
.type
=== "Punctuator" && token
.value
=== "?");
1151 const colonToken
= sourceCode
.getFirstTokenBetween(node
.consequent
, node
.alternate
, token
=> token
.type
=== "Punctuator" && token
.value
=== ":");
1153 const firstConsequentToken
= sourceCode
.getTokenAfter(questionMarkToken
);
1154 const lastConsequentToken
= sourceCode
.getTokenBefore(colonToken
);
1155 const firstAlternateToken
= sourceCode
.getTokenAfter(colonToken
);
1157 offsets
.setDesiredOffset(questionMarkToken
, firstToken
, 1);
1158 offsets
.setDesiredOffset(colonToken
, firstToken
, 1);
1160 offsets
.setDesiredOffset(firstConsequentToken
, firstToken
,
1161 options
.offsetTernaryExpressions
? 2 : 1);
1164 * The alternate and the consequent should usually have the same indentation.
1165 * If they share part of a line, align the alternate against the first token of the consequent.
1166 * This allows the alternate to be indented correctly in cases like this:
1169 * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo`
1170 * baz // as a result, `baz` is offset by 1 rather than 2
1173 if (lastConsequentToken
.loc
.end
.line
=== firstAlternateToken
.loc
.start
.line
) {
1174 offsets
.setDesiredOffset(firstAlternateToken
, firstConsequentToken
, 0);
1178 * If the alternate and consequent do not share part of a line, offset the alternate from the first
1179 * token of the conditional expression. For example:
1183 * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up
1184 * having no expected indentation.
1186 offsets
.setDesiredOffset(firstAlternateToken
, firstToken
,
1187 firstAlternateToken
.type
=== "Punctuator" &&
1188 options
.offsetTernaryExpressions
? 2 : 1);
1193 "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement": node
=> addBlocklessNodeIndent(node
.body
),
1195 ExportNamedDeclaration(node
) {
1196 if (node
.declaration
=== null) {
1197 const closingCurly
= sourceCode
.getLastToken(node
, astUtils
.isClosingBraceToken
);
1199 // Indent the specifiers in `export {foo, bar, baz}`
1200 addElementListIndent(node
.specifiers
, sourceCode
.getFirstToken(node
, { skip
: 1 }), closingCurly
, 1);
1204 // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'`
1205 offsets
.setDesiredOffsets([closingCurly
.range
[1], node
.range
[1]], sourceCode
.getFirstToken(node
), 1);
1210 ForStatement(node
) {
1211 const forOpeningParen
= sourceCode
.getFirstToken(node
, 1);
1214 offsets
.setDesiredOffsets(node
.init
.range
, forOpeningParen
, 1);
1217 offsets
.setDesiredOffsets(node
.test
.range
, forOpeningParen
, 1);
1220 offsets
.setDesiredOffsets(node
.update
.range
, forOpeningParen
, 1);
1222 addBlocklessNodeIndent(node
.body
);
1225 "FunctionDeclaration, FunctionExpression"(node
) {
1226 const closingParen
= sourceCode
.getTokenBefore(node
.body
);
1227 const openingParen
= sourceCode
.getTokenBefore(node
.params
.length
? node
.params
[0] : closingParen
);
1229 parameterParens
.add(openingParen
);
1230 parameterParens
.add(closingParen
);
1231 addElementListIndent(node
.params
, openingParen
, closingParen
, options
[node
.type
].parameters
);
1235 addBlocklessNodeIndent(node
.consequent
);
1236 if (node
.alternate
&& node
.alternate
.type
!== "IfStatement") {
1237 addBlocklessNodeIndent(node
.alternate
);
1241 ImportDeclaration(node
) {
1242 if (node
.specifiers
.some(specifier
=> specifier
.type
=== "ImportSpecifier")) {
1243 const openingCurly
= sourceCode
.getFirstToken(node
, astUtils
.isOpeningBraceToken
);
1244 const closingCurly
= sourceCode
.getLastToken(node
, astUtils
.isClosingBraceToken
);
1246 addElementListIndent(node
.specifiers
.filter(specifier
=> specifier
.type
=== "ImportSpecifier"), openingCurly
, closingCurly
, options
.ImportDeclaration
);
1249 const fromToken
= sourceCode
.getLastToken(node
, token
=> token
.type
=== "Identifier" && token
.value
=== "from");
1250 const sourceToken
= sourceCode
.getLastToken(node
, token
=> token
.type
=== "String");
1251 const semiToken
= sourceCode
.getLastToken(node
, token
=> token
.type
=== "Punctuator" && token
.value
=== ";");
1254 const end
= semiToken
&& semiToken
.range
[1] === sourceToken
.range
[1] ? node
.range
[1] : sourceToken
.range
[1];
1256 offsets
.setDesiredOffsets([fromToken
.range
[0], end
], sourceCode
.getFirstToken(node
), 1);
1260 ImportExpression(node
) {
1261 const openingParen
= sourceCode
.getFirstToken(node
, 1);
1262 const closingParen
= sourceCode
.getLastToken(node
);
1264 parameterParens
.add(openingParen
);
1265 parameterParens
.add(closingParen
);
1266 offsets
.setDesiredOffset(openingParen
, sourceCode
.getTokenBefore(openingParen
), 0);
1268 addElementListIndent([node
.source
], openingParen
, closingParen
, options
.CallExpression
.arguments
);
1271 "MemberExpression, JSXMemberExpression, MetaProperty"(node
) {
1272 const object
= node
.type
=== "MetaProperty" ? node
.meta
: node
.object
;
1273 const firstNonObjectToken
= sourceCode
.getFirstTokenBetween(object
, node
.property
, astUtils
.isNotClosingParenToken
);
1274 const secondNonObjectToken
= sourceCode
.getTokenAfter(firstNonObjectToken
);
1276 const objectParenCount
= sourceCode
.getTokensBetween(object
, node
.property
, { filter
: astUtils
.isClosingParenToken
}).length
;
1277 const firstObjectToken
= objectParenCount
1278 ? sourceCode
.getTokenBefore(object
, { skip
: objectParenCount
- 1 })
1279 : sourceCode
.getFirstToken(object
);
1280 const lastObjectToken
= sourceCode
.getTokenBefore(firstNonObjectToken
);
1281 const firstPropertyToken
= node
.computed
? firstNonObjectToken
: secondNonObjectToken
;
1283 if (node
.computed
) {
1285 // For computed MemberExpressions, match the closing bracket with the opening bracket.
1286 offsets
.setDesiredOffset(sourceCode
.getLastToken(node
), firstNonObjectToken
, 0);
1287 offsets
.setDesiredOffsets(node
.property
.range
, firstNonObjectToken
, 1);
1291 * If the object ends on the same line that the property starts, match against the last token
1292 * of the object, to ensure that the MemberExpression is not indented.
1294 * Otherwise, match against the first token of the object, e.g.
1297 * .baz // <-- offset by 1 from `foo`
1299 const offsetBase
= lastObjectToken
.loc
.end
.line
=== firstPropertyToken
.loc
.start
.line
1303 if (typeof options
.MemberExpression
=== "number") {
1305 // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object.
1306 offsets
.setDesiredOffset(firstNonObjectToken
, offsetBase
, options
.MemberExpression
);
1309 * For computed MemberExpressions, match the first token of the property against the opening bracket.
1310 * Otherwise, match the first token of the property against the object.
1312 offsets
.setDesiredOffset(secondNonObjectToken
, node
.computed
? firstNonObjectToken
: offsetBase
, options
.MemberExpression
);
1315 // If the MemberExpression option is off, ignore the dot and the first token of the property.
1316 offsets
.ignoreToken(firstNonObjectToken
);
1317 offsets
.ignoreToken(secondNonObjectToken
);
1319 // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens.
1320 offsets
.setDesiredOffset(firstNonObjectToken
, offsetBase
, 0);
1321 offsets
.setDesiredOffset(secondNonObjectToken
, firstNonObjectToken
, 0);
1325 NewExpression(node
) {
1327 // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo`
1328 if (node
.arguments
.length
> 0 ||
1329 astUtils
.isClosingParenToken(sourceCode
.getLastToken(node
)) &&
1330 astUtils
.isOpeningParenToken(sourceCode
.getLastToken(node
, 1))) {
1331 addFunctionCallIndent(node
);
1336 if (!node
.shorthand
&& !node
.method
&& node
.kind
=== "init") {
1337 const colon
= sourceCode
.getFirstTokenBetween(node
.key
, node
.value
, astUtils
.isColonToken
);
1339 offsets
.ignoreToken(sourceCode
.getTokenAfter(colon
));
1343 SwitchStatement(node
) {
1344 const openingCurly
= sourceCode
.getTokenAfter(node
.discriminant
, astUtils
.isOpeningBraceToken
);
1345 const closingCurly
= sourceCode
.getLastToken(node
);
1347 offsets
.setDesiredOffsets([openingCurly
.range
[1], closingCurly
.range
[0]], openingCurly
, options
.SwitchCase
);
1349 if (node
.cases
.length
) {
1350 sourceCode
.getTokensBetween(
1351 node
.cases
[node
.cases
.length
- 1],
1353 { includeComments
: true, filter
: astUtils
.isCommentToken
}
1354 ).forEach(token
=> offsets
.ignoreToken(token
));
1359 if (!(node
.consequent
.length
=== 1 && node
.consequent
[0].type
=== "BlockStatement")) {
1360 const caseKeyword
= sourceCode
.getFirstToken(node
);
1361 const tokenAfterCurrentCase
= sourceCode
.getTokenAfter(node
);
1363 offsets
.setDesiredOffsets([caseKeyword
.range
[1], tokenAfterCurrentCase
.range
[0]], caseKeyword
, 1);
1367 TemplateLiteral(node
) {
1368 node
.expressions
.forEach((expression
, index
) => {
1369 const previousQuasi
= node
.quasis
[index
];
1370 const nextQuasi
= node
.quasis
[index
+ 1];
1371 const tokenToAlignFrom
= previousQuasi
.loc
.start
.line
=== previousQuasi
.loc
.end
.line
1372 ? sourceCode
.getFirstToken(previousQuasi
)
1375 offsets
.setDesiredOffsets([previousQuasi
.range
[1], nextQuasi
.range
[0]], tokenToAlignFrom
, 1);
1376 offsets
.setDesiredOffset(sourceCode
.getFirstToken(nextQuasi
), tokenToAlignFrom
, 0);
1380 VariableDeclaration(node
) {
1381 let variableIndent
= Object
.prototype.hasOwnProperty
.call(options
.VariableDeclarator
, node
.kind
)
1382 ? options
.VariableDeclarator
[node
.kind
]
1383 : DEFAULT_VARIABLE_INDENT
;
1385 const firstToken
= sourceCode
.getFirstToken(node
),
1386 lastToken
= sourceCode
.getLastToken(node
);
1388 if (options
.VariableDeclarator
[node
.kind
] === "first") {
1389 if (node
.declarations
.length
> 1) {
1390 addElementListIndent(
1399 variableIndent
= DEFAULT_VARIABLE_INDENT
;
1402 if (node
.declarations
[node
.declarations
.length
- 1].loc
.start
.line
> node
.loc
.start
.line
) {
1405 * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
1406 * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
1407 * the following indentations are correct:
1418 * Account for when exiting the AST (after indentations have already been set for the nodes in
1419 * the declaration) by manually increasing the indentation level of the tokens in this declarator
1420 * on the same line as the start of the declaration, provided that there are declarators that
1423 offsets
.setDesiredOffsets(node
.range
, firstToken
, variableIndent
, true);
1425 offsets
.setDesiredOffsets(node
.range
, firstToken
, variableIndent
);
1428 if (astUtils
.isSemicolonToken(lastToken
)) {
1429 offsets
.ignoreToken(lastToken
);
1433 VariableDeclarator(node
) {
1435 const equalOperator
= sourceCode
.getTokenBefore(node
.init
, astUtils
.isNotOpeningParenToken
);
1436 const tokenAfterOperator
= sourceCode
.getTokenAfter(equalOperator
);
1438 offsets
.ignoreToken(equalOperator
);
1439 offsets
.ignoreToken(tokenAfterOperator
);
1440 offsets
.setDesiredOffsets([tokenAfterOperator
.range
[0], node
.range
[1]], equalOperator
, 1);
1441 offsets
.setDesiredOffset(equalOperator
, sourceCode
.getLastToken(node
.id
), 0);
1445 "JSXAttribute[value]"(node
) {
1446 const equalsToken
= sourceCode
.getFirstTokenBetween(node
.name
, node
.value
, token
=> token
.type
=== "Punctuator" && token
.value
=== "=");
1448 offsets
.setDesiredOffsets([equalsToken
.range
[0], node
.value
.range
[1]], sourceCode
.getFirstToken(node
.name
), 1);
1452 if (node
.closingElement
) {
1453 addElementListIndent(node
.children
, sourceCode
.getFirstToken(node
.openingElement
), sourceCode
.getFirstToken(node
.closingElement
), 1);
1457 JSXOpeningElement(node
) {
1458 const firstToken
= sourceCode
.getFirstToken(node
);
1461 if (node
.selfClosing
) {
1462 closingToken
= sourceCode
.getLastToken(node
, { skip
: 1 });
1463 offsets
.setDesiredOffset(sourceCode
.getLastToken(node
), closingToken
, 0);
1465 closingToken
= sourceCode
.getLastToken(node
);
1467 offsets
.setDesiredOffsets(node
.name
.range
, sourceCode
.getFirstToken(node
));
1468 addElementListIndent(node
.attributes
, firstToken
, closingToken
, 1);
1471 JSXClosingElement(node
) {
1472 const firstToken
= sourceCode
.getFirstToken(node
);
1474 offsets
.setDesiredOffsets(node
.name
.range
, firstToken
, 1);
1478 const firstOpeningToken
= sourceCode
.getFirstToken(node
.openingFragment
);
1479 const firstClosingToken
= sourceCode
.getFirstToken(node
.closingFragment
);
1481 addElementListIndent(node
.children
, firstOpeningToken
, firstClosingToken
, 1);
1484 JSXOpeningFragment(node
) {
1485 const firstToken
= sourceCode
.getFirstToken(node
);
1486 const closingToken
= sourceCode
.getLastToken(node
);
1488 offsets
.setDesiredOffsets(node
.range
, firstToken
, 1);
1489 offsets
.matchOffsetOf(firstToken
, closingToken
);
1492 JSXClosingFragment(node
) {
1493 const firstToken
= sourceCode
.getFirstToken(node
);
1494 const slashToken
= sourceCode
.getLastToken(node
, { skip
: 1 });
1495 const closingToken
= sourceCode
.getLastToken(node
);
1496 const tokenToMatch
= astUtils
.isTokenOnSameLine(slashToken
, closingToken
) ? slashToken
: closingToken
;
1498 offsets
.setDesiredOffsets(node
.range
, firstToken
, 1);
1499 offsets
.matchOffsetOf(firstToken
, tokenToMatch
);
1502 JSXExpressionContainer(node
) {
1503 const openingCurly
= sourceCode
.getFirstToken(node
);
1504 const closingCurly
= sourceCode
.getLastToken(node
);
1506 offsets
.setDesiredOffsets(
1507 [openingCurly
.range
[1], closingCurly
.range
[0]],
1513 JSXSpreadAttribute(node
) {
1514 const openingCurly
= sourceCode
.getFirstToken(node
);
1515 const closingCurly
= sourceCode
.getLastToken(node
);
1517 offsets
.setDesiredOffsets(
1518 [openingCurly
.range
[1], closingCurly
.range
[0]],
1525 const firstToken
= sourceCode
.getFirstToken(node
);
1527 // Ensure that the children of every node are indented at least as much as the first token.
1528 if (firstToken
&& !ignoredNodeFirstTokens
.has(firstToken
)) {
1529 offsets
.setDesiredOffsets(node
.range
, firstToken
, 0);
1534 const listenerCallQueue
= [];
1537 * To ignore the indentation of a node:
1538 * 1. Don't call the node's listener when entering it (if it has a listener)
1539 * 2. Don't set any offsets against the first token of the node.
1540 * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
1542 const offsetListeners
= lodash
.mapValues(
1543 baseOffsetListeners
,
1546 * Offset listener calls are deferred until traversal is finished, and are called as
1547 * part of the final `Program:exit` listener. This is necessary because a node might
1548 * be matched by multiple selectors.
1550 * Example: Suppose there is an offset listener for `Identifier`, and the user has
1551 * specified in configuration that `MemberExpression > Identifier` should be ignored.
1552 * Due to selector specificity rules, the `Identifier` listener will get called first. However,
1553 * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener
1554 * should not have been called at all. Without doing extra selector matching, we don't know
1555 * whether the Identifier matches the `MemberExpression > Identifier` selector until the
1556 * `MemberExpression > Identifier` listener is called.
1558 * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
1559 * ignored nodes are known.
1563 listenerCallQueue
.push({ listener
, node
})
1566 // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
1567 const ignoredNodes
= new Set();
1571 * @param {ASTNode} node The node to ignore
1574 function addToIgnoredNodes(node
) {
1575 ignoredNodes
.add(node
);
1576 ignoredNodeFirstTokens
.add(sourceCode
.getFirstToken(node
));
1579 const ignoredNodeListeners
= options
.ignoredNodes
.reduce(
1580 (listeners
, ignoredSelector
) => Object
.assign(listeners
, { [ignoredSelector
]: addToIgnoredNodes
}),
1585 * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation
1588 * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears
1589 * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored,
1590 * so those listeners wouldn't be called anyway.
1592 return Object
.assign(
1594 ignoredNodeListeners
,
1598 // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it.
1599 if (!KNOWN_NODES
.has(node
.type
)) {
1600 addToIgnoredNodes(node
);
1605 // If ignoreComments option is enabled, ignore all comment tokens.
1606 if (options
.ignoreComments
) {
1607 sourceCode
.getAllComments()
1608 .forEach(comment
=> offsets
.ignoreToken(comment
));
1611 // Invoke the queued offset listeners for the nodes that aren't ignored.
1613 .filter(nodeInfo
=> !ignoredNodes
.has(nodeInfo
.node
))
1614 .forEach(nodeInfo
=> nodeInfo
.listener(nodeInfo
.node
));
1616 // Update the offsets for ignored nodes to prevent their child tokens from being reported.
1617 ignoredNodes
.forEach(ignoreNode
);
1619 addParensIndent(sourceCode
.ast
.tokens
);
1622 * Create a Map from (tokenOrComment) => (precedingToken).
1623 * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
1625 const precedingTokens
= sourceCode
.ast
.comments
.reduce((commentMap
, comment
) => {
1626 const tokenOrCommentBefore
= sourceCode
.getTokenBefore(comment
, { includeComments
: true });
1628 return commentMap
.set(comment
, commentMap
.has(tokenOrCommentBefore
) ? commentMap
.get(tokenOrCommentBefore
) : tokenOrCommentBefore
);
1631 sourceCode
.lines
.forEach((line
, lineIndex
) => {
1632 const lineNumber
= lineIndex
+ 1;
1634 if (!tokenInfo
.firstTokensByLineNumber
.has(lineNumber
)) {
1636 // Don't check indentation on blank lines
1640 const firstTokenOfLine
= tokenInfo
.firstTokensByLineNumber
.get(lineNumber
);
1642 if (firstTokenOfLine
.loc
.start
.line
!== lineNumber
) {
1644 // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
1648 if (astUtils
.isCommentToken(firstTokenOfLine
)) {
1649 const tokenBefore
= precedingTokens
.get(firstTokenOfLine
);
1650 const tokenAfter
= tokenBefore
? sourceCode
.getTokenAfter(tokenBefore
) : sourceCode
.ast
.tokens
[0];
1651 const mayAlignWithBefore
= tokenBefore
&& !hasBlankLinesBetween(tokenBefore
, firstTokenOfLine
);
1652 const mayAlignWithAfter
= tokenAfter
&& !hasBlankLinesBetween(firstTokenOfLine
, tokenAfter
);
1655 * If a comment precedes a line that begins with a semicolon token, align to that token, i.e.
1659 * ;(async () => {})()
1661 if (tokenAfter
&& astUtils
.isSemicolonToken(tokenAfter
) && !astUtils
.isTokenOnSameLine(firstTokenOfLine
, tokenAfter
)) {
1662 offsets
.setDesiredOffset(firstTokenOfLine
, tokenAfter
, 0);
1665 // If a comment matches the expected indentation of the token immediately before or after, don't report it.
1667 mayAlignWithBefore
&& validateTokenIndent(firstTokenOfLine
, offsets
.getDesiredIndent(tokenBefore
)) ||
1668 mayAlignWithAfter
&& validateTokenIndent(firstTokenOfLine
, offsets
.getDesiredIndent(tokenAfter
))
1674 // If the token matches the expected indentation, don't report it.
1675 if (validateTokenIndent(firstTokenOfLine
, offsets
.getDesiredIndent(firstTokenOfLine
))) {
1679 // Otherwise, report the token/comment.
1680 report(firstTokenOfLine
, offsets
.getDesiredIndent(firstTokenOfLine
));