diff --git a/.eslintrc.json b/.eslintrc.json index 2039fcec10e..91c11fbba92 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,6 +9,7 @@ "rules": { "comma-dangle": ["error", "always-multiline"], "curly": ["error", "all"], + "no-dupe-class-members": "off", "no-mixed-operators": "error", "no-console": "off", "no-undef": "off", diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts new file mode 100644 index 00000000000..002fbd3d8ee --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts @@ -0,0 +1,60 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import createTree = require('functional-red-black-tree'); + +export type TokenOrComment = TSESTree.Token | TSESTree.Comment; +export interface TreeValue { + offset: number; + from: TokenOrComment | null; + force: boolean; +} + +/** + * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique. + * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation + * can easily be swapped out. + */ +export class BinarySearchTree { + private rbTree = createTree(); + + /** + * Inserts an entry into the tree. + */ + public insert(key: number, value: TreeValue): void { + const iterator = this.rbTree.find(key); + + if (iterator.valid) { + this.rbTree = iterator.update(value); + } else { + this.rbTree = this.rbTree.insert(key, value); + } + } + + /** + * Finds the entry with the largest key less than or equal to the provided key + * @returns The found entry, or null if no such entry exists. + */ + public findLe(key: number): { key: number; value: TreeValue } { + const iterator = this.rbTree.le(key); + + return { key: iterator.key, value: iterator.value }; + } + + /** + * Deletes all of the keys in the interval [start, end) + */ + public deleteRange(start: number, end: number): void { + // Exit without traversing the tree if the range has zero size. + if (start === end) { + return; + } + const iterator = this.rbTree.ge(start); + + while (iterator.valid && iterator.key < end) { + this.rbTree = this.rbTree.remove(iterator.key); + iterator.next(); + } + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts new file mode 100644 index 00000000000..1ab3dd71ebb --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts @@ -0,0 +1,277 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TokenInfo } from './TokenInfo'; +import { BinarySearchTree, TokenOrComment } from './BinarySearchTree'; +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +/** + * A class to store information on desired offsets of tokens from each other + */ +export class OffsetStorage { + private tokenInfo: TokenInfo; + private indentSize: number; + private indentType: string; + private tree: BinarySearchTree; + private lockedFirstTokens: WeakMap; + private desiredIndentCache: WeakMap; + private ignoredTokens: WeakSet; + /** + * @param tokenInfo a TokenInfo instance + * @param indentSize The desired size of each indentation level + * @param indentType The indentation character + */ + constructor(tokenInfo: TokenInfo, indentSize: number, indentType: string) { + this.tokenInfo = tokenInfo; + this.indentSize = indentSize; + this.indentType = indentType; + + this.tree = new BinarySearchTree(); + this.tree.insert(0, { offset: 0, from: null, force: false }); + + this.lockedFirstTokens = new WeakMap(); + this.desiredIndentCache = new WeakMap(); + this.ignoredTokens = new WeakSet(); + } + + private getOffsetDescriptor(token: TokenOrComment) { + return this.tree.findLe(token.range[0]).value; + } + + /** + * Sets the offset column of token B to match the offset column of token A. + * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In + * most cases, `setDesiredOffset` should be used instead. + * @param baseToken The first token + * @param offsetToken The second token, whose offset should be matched to the first token + */ + public matchOffsetOf( + baseToken: TokenOrComment, + offsetToken: TokenOrComment, + ): void { + /* + * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to + * the token that it depends on. For example, with the `ArrayExpression: first` option, the first + * token of each element in the array after the first will be mapped to the first token of the first + * element. The desired indentation of each of these tokens is computed based on the desired indentation + * of the "first" element, rather than through the normal offset mechanism. + */ + this.lockedFirstTokens.set(offsetToken, baseToken); + } + + /** + * Sets the desired offset of a token. + * + * This uses a line-based offset collapsing behavior to handle tokens on the same line. + * For example, consider the following two cases: + * + * ( + * [ + * bar + * ] + * ) + * + * ([ + * bar + * ]) + * + * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from + * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is + * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) + * from the start of its line. + * + * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level + * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the + * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented + * by 1 indent level from the start of the line. + * + * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, + * without needing to check which lines those tokens are on. + * + * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive + * behavior can occur. For example, consider the following cases: + * + * foo( + * ). + * bar( + * baz + * ) + * + * foo( + * ).bar( + * baz + * ) + * + * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` + * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` + * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no + * collapsing would occur). + * + * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and + * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed + * in the second case. + * + * @param token The token + * @param fromToken The token that `token` should be offset from + * @param offset The desired indent level + */ + public setDesiredOffset( + token: TokenOrComment, + fromToken: TokenOrComment | null, + offset: number, + ): void { + this.setDesiredOffsets(token.range, fromToken, offset); + } + + /** + * Sets the desired offset of all tokens in a range + * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens. + * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains + * it). This means that the offset of each token is updated O(AST depth) times. + * It would not be performant to store and update the offsets for each token independently, because the rule would end + * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files. + * + * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following + * list could represent the state of the offset tree at a given point: + * + * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file + * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token + * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token + * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token + * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token + * + * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: + * `setDesiredOffsets([30, 43], fooToken, 1);` + * + * @param range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied. + * @param fromToken The token that this is offset from + * @param offset The desired indent level + * @param force `true` if this offset should not use the normal collapsing behavior. This should almost always be false. + */ + public setDesiredOffsets( + range: [number, number], + fromToken: TokenOrComment | null, + offset: number = 0, + force: boolean = false, + ): void { + /* + * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset + * descriptor. The tree for the example above would have the following nodes: + * + * * key: 0, value: { offset: 0, from: null } + * * key: 15, value: { offset: 1, from: barToken } + * * key: 30, value: { offset: 1, from: fooToken } + * * key: 43, value: { offset: 2, from: barToken } + * * key: 820, value: { offset: 1, from: bazToken } + * + * To find the offset descriptor for any given token, one needs to find the node with the largest key + * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary + * search tree indexed by key. + */ + + const descriptorToInsert = { offset, from: fromToken, force }; + + const descriptorAfterRange = this.tree.findLe(range[1]).value; + + const fromTokenIsInRange = + fromToken && + fromToken.range[0] >= range[0] && + fromToken.range[1] <= range[1]; + // this has to be before the delete + insert below or else you'll get into a cycle + const fromTokenDescriptor = fromTokenIsInRange + ? this.getOffsetDescriptor(fromToken!) + : null; + + // First, remove any existing nodes in the range from the tree. + this.tree.deleteRange(range[0] + 1, range[1]); + + // Insert a new node into the tree for this range + this.tree.insert(range[0], descriptorToInsert); + + /* + * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, + * even if it's in the current range. + */ + if (fromTokenIsInRange) { + this.tree.insert(fromToken!.range[0], fromTokenDescriptor!); + this.tree.insert(fromToken!.range[1], descriptorToInsert); + } + + /* + * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following + * tokens the same as it was before. + */ + this.tree.insert(range[1], descriptorAfterRange); + } + + /** + * Gets the desired indent of a token + * @returns The desired indent of the token + */ + public getDesiredIndent(token: TokenOrComment): string { + if (!this.desiredIndentCache.has(token)) { + if (this.ignoredTokens.has(token)) { + /* + * If the token is ignored, use the actual indent of the token as the desired indent. + * This ensures that no errors are reported for this token. + */ + this.desiredIndentCache.set( + token, + this.tokenInfo.getTokenIndent(token), + ); + } else if (this.lockedFirstTokens.has(token)) { + const firstToken = this.lockedFirstTokens.get(token)!; + + this.desiredIndentCache.set( + token, + + // (indentation for the first element's line) + this.getDesiredIndent( + this.tokenInfo.getFirstTokenOfLine(firstToken), + ) + + // (space between the start of the first element's line and the first element) + this.indentType.repeat( + firstToken.loc.start.column - + this.tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column, + ), + ); + } else { + const offsetInfo = this.getOffsetDescriptor(token); + const offset = + offsetInfo.from && + offsetInfo.from.loc.start.line === token.loc.start.line && + !/^\s*?\n/u.test(token.value) && + !offsetInfo.force + ? 0 + : offsetInfo.offset * this.indentSize; + + this.desiredIndentCache.set( + token, + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : '') + + this.indentType.repeat(offset), + ); + } + } + + return this.desiredIndentCache.get(token)!; + } + + /** + * Ignores a token, preventing it from being reported. + */ + ignoreToken(token: TokenOrComment): void { + if (this.tokenInfo.isFirstTokenOfLine(token)) { + this.ignoredTokens.add(token); + } + } + + /** + * Gets the first token that the given token's indentation is dependent on + * @returns The token that the given token depends on, or `null` if the given token is at the top level + */ + getFirstDependency(token: TSESTree.Token): TSESTree.Token | null; + getFirstDependency(token: TokenOrComment): TokenOrComment | null; + getFirstDependency(token: TokenOrComment): TokenOrComment | null { + return this.getOffsetDescriptor(token).from; + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts new file mode 100644 index 00000000000..29aaecdaa6d --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts @@ -0,0 +1,65 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { SourceCode } from 'ts-eslint'; +import { TokenOrComment } from './BinarySearchTree'; + +/** + * A helper class to get token-based info related to indentation + */ +export class TokenInfo { + private sourceCode: SourceCode; + public firstTokensByLineNumber: Map; + + constructor(sourceCode: SourceCode) { + this.sourceCode = sourceCode; + this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce( + (map, token) => { + if (!map.has(token.loc.start.line)) { + map.set(token.loc.start.line, token); + } + if ( + !map.has(token.loc.end.line) && + sourceCode.text + .slice(token.range[1] - token.loc.end.column, token.range[1]) + .trim() + ) { + map.set(token.loc.end.line, token); + } + return map; + }, + new Map(), + ); + } + + /** + * Gets the first token on a given token's line + * @returns The first token on the given line + */ + public getFirstTokenOfLine( + token: TokenOrComment | TSESTree.Node, + ): TokenOrComment { + return this.firstTokensByLineNumber.get(token.loc.start.line)!; + } + + /** + * Determines whether a token is the first token in its line + * @returns `true` if the token is the first on its line + */ + public isFirstTokenOfLine(token: TokenOrComment): boolean { + return this.getFirstTokenOfLine(token) === token; + } + + /** + * Get the actual indent of a token + * @param token Token to examine. This should be the first token on its line. + * @returns The indentation characters that precede the token + */ + public getTokenIndent(token: TokenOrComment): string { + return this.sourceCode.text.slice( + token.range[0] - token.loc.start.column, + token.range[0], + ); + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts new file mode 100644 index 00000000000..f9a3ebcfb49 --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -0,0 +1,1723 @@ +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +import { + AST_NODE_TYPES, + TSESTree, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; +import { createGlobalLinebreakMatcher } from 'eslint/lib/util/ast-utils'; +import { + isOpeningParenToken, + isClosingParenToken, + isNotOpeningParenToken, + isSemicolonToken, + isClosingBracketToken, + isClosingBraceToken, + isOpeningBraceToken, + isNotClosingParenToken, + isColonToken, + isCommentToken, +} from 'eslint-utils'; +import { RuleListener, RuleFunction } from 'ts-eslint'; +import { TokenOrComment } from './BinarySearchTree'; +import { OffsetStorage } from './OffsetStorage'; +import { TokenInfo } from './TokenInfo'; +import { createRule, ExcludeKeys, RequireKeys } from '../../util'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const KNOWN_NODES = new Set([ + AST_NODE_TYPES.AssignmentExpression, + AST_NODE_TYPES.AssignmentPattern, + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ArrayPattern, + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.AwaitExpression, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.BinaryExpression, + AST_NODE_TYPES.BreakStatement, + AST_NODE_TYPES.CallExpression, + AST_NODE_TYPES.CatchClause, + AST_NODE_TYPES.ClassBody, + AST_NODE_TYPES.ClassDeclaration, + AST_NODE_TYPES.ClassExpression, + AST_NODE_TYPES.ConditionalExpression, + AST_NODE_TYPES.ContinueStatement, + AST_NODE_TYPES.DoWhileStatement, + AST_NODE_TYPES.DebuggerStatement, + AST_NODE_TYPES.EmptyStatement, + AST_NODE_TYPES.ExpressionStatement, + AST_NODE_TYPES.ForStatement, + AST_NODE_TYPES.ForInStatement, + AST_NODE_TYPES.ForOfStatement, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.Identifier, + AST_NODE_TYPES.IfStatement, + AST_NODE_TYPES.Literal, + AST_NODE_TYPES.LabeledStatement, + AST_NODE_TYPES.LogicalExpression, + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.MetaProperty, + AST_NODE_TYPES.MethodDefinition, + AST_NODE_TYPES.NewExpression, + AST_NODE_TYPES.ObjectExpression, + AST_NODE_TYPES.ObjectPattern, + AST_NODE_TYPES.Program, + AST_NODE_TYPES.Property, + AST_NODE_TYPES.RestElement, + AST_NODE_TYPES.ReturnStatement, + AST_NODE_TYPES.SequenceExpression, + AST_NODE_TYPES.SpreadElement, + AST_NODE_TYPES.Super, + AST_NODE_TYPES.SwitchCase, + AST_NODE_TYPES.SwitchStatement, + AST_NODE_TYPES.TaggedTemplateExpression, + AST_NODE_TYPES.TemplateElement, + AST_NODE_TYPES.TemplateLiteral, + AST_NODE_TYPES.ThisExpression, + AST_NODE_TYPES.ThrowStatement, + AST_NODE_TYPES.TryStatement, + AST_NODE_TYPES.UnaryExpression, + AST_NODE_TYPES.UpdateExpression, + AST_NODE_TYPES.VariableDeclaration, + AST_NODE_TYPES.VariableDeclarator, + AST_NODE_TYPES.WhileStatement, + AST_NODE_TYPES.WithStatement, + AST_NODE_TYPES.YieldExpression, + AST_NODE_TYPES.JSXIdentifier, + AST_NODE_TYPES.JSXNamespacedName, + AST_NODE_TYPES.JSXMemberExpression, + AST_NODE_TYPES.JSXEmptyExpression, + AST_NODE_TYPES.JSXExpressionContainer, + AST_NODE_TYPES.JSXElement, + AST_NODE_TYPES.JSXClosingElement, + AST_NODE_TYPES.JSXOpeningElement, + AST_NODE_TYPES.JSXAttribute, + AST_NODE_TYPES.JSXSpreadAttribute, + AST_NODE_TYPES.JSXText, + AST_NODE_TYPES.ExportDefaultDeclaration, + AST_NODE_TYPES.ExportNamedDeclaration, + AST_NODE_TYPES.ExportAllDeclaration, + AST_NODE_TYPES.ExportSpecifier, + AST_NODE_TYPES.ImportDeclaration, + AST_NODE_TYPES.ImportSpecifier, + AST_NODE_TYPES.ImportDefaultSpecifier, + AST_NODE_TYPES.ImportNamespaceSpecifier, + + // Class properties aren't yet supported by eslint... + AST_NODE_TYPES.ClassProperty, + + // ts keywords + AST_NODE_TYPES.TSAbstractKeyword, + AST_NODE_TYPES.TSAnyKeyword, + AST_NODE_TYPES.TSBooleanKeyword, + AST_NODE_TYPES.TSNeverKeyword, + AST_NODE_TYPES.TSNumberKeyword, + AST_NODE_TYPES.TSStringKeyword, + AST_NODE_TYPES.TSSymbolKeyword, + AST_NODE_TYPES.TSUndefinedKeyword, + AST_NODE_TYPES.TSUnknownKeyword, + AST_NODE_TYPES.TSVoidKeyword, + AST_NODE_TYPES.TSNullKeyword, + + // ts specific nodes we want to support + AST_NODE_TYPES.TSAbstractClassProperty, + AST_NODE_TYPES.TSAbstractMethodDefinition, + AST_NODE_TYPES.TSArrayType, + AST_NODE_TYPES.TSAsExpression, + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConditionalType, + AST_NODE_TYPES.TSConstructorType, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + AST_NODE_TYPES.TSDeclareFunction, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.TSEnumMember, + AST_NODE_TYPES.TSExportAssignment, + AST_NODE_TYPES.TSExternalModuleReference, + AST_NODE_TYPES.TSFunctionType, + AST_NODE_TYPES.TSImportType, + AST_NODE_TYPES.TSIndexedAccessType, + AST_NODE_TYPES.TSIndexSignature, + AST_NODE_TYPES.TSInferType, + AST_NODE_TYPES.TSInterfaceBody, + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSInterfaceHeritage, + AST_NODE_TYPES.TSIntersectionType, + AST_NODE_TYPES.TSImportEqualsDeclaration, + AST_NODE_TYPES.TSLiteralType, + AST_NODE_TYPES.TSMappedType, + AST_NODE_TYPES.TSMethodSignature, + 'TSMinusToken', + AST_NODE_TYPES.TSModuleBlock, + AST_NODE_TYPES.TSModuleDeclaration, + AST_NODE_TYPES.TSNonNullExpression, + AST_NODE_TYPES.TSParameterProperty, + AST_NODE_TYPES.TSParenthesizedType, + 'TSPlusToken', + AST_NODE_TYPES.TSPropertySignature, + AST_NODE_TYPES.TSQualifiedName, + AST_NODE_TYPES.TSQuestionToken, + AST_NODE_TYPES.TSRestType, + AST_NODE_TYPES.TSThisType, + AST_NODE_TYPES.TSTupleType, + AST_NODE_TYPES.TSTypeAnnotation, + AST_NODE_TYPES.TSTypeLiteral, + AST_NODE_TYPES.TSTypeOperator, + AST_NODE_TYPES.TSTypeParameter, + AST_NODE_TYPES.TSTypeParameterDeclaration, + AST_NODE_TYPES.TSTypeParameterInstantiation, + AST_NODE_TYPES.TSTypeReference, + AST_NODE_TYPES.TSUnionType, +]); +const STATEMENT_LIST_PARENTS = new Set([ + AST_NODE_TYPES.Program, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.SwitchCase, +]); +const DEFAULT_VARIABLE_INDENT = 1; +const DEFAULT_PARAMETER_INDENT = 1; +const DEFAULT_FUNCTION_BODY_INDENT = 1; + +/* + * General rule strategy: + * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another + * specified token or to the first column. + * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a + * BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly + * brace of the BlockStatement. + * 3. After traversing the AST, calculate the expected indentation levels of every token according to the + * OffsetStorage container. + * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file, + * and report the token if the two values are not equal. + */ + +const ELEMENT_LIST_SCHEMA = { + oneOf: [ + { + type: 'integer', + minimum: 0, + }, + { + enum: ['first', 'off'], + }, + ], +}; + +interface VariableDeclaratorObj { + var?: ElementList; + let?: ElementList; + const?: ElementList; +} +type ElementList = number | 'first' | 'off'; +interface IndentConfig { + SwitchCase?: number; + VariableDeclarator?: ElementList | VariableDeclaratorObj; + outerIIFEBody?: number; + MemberExpression?: number | 'off'; + FunctionDeclaration?: { + parameters?: ElementList; + body?: number; + }; + FunctionExpression?: { + parameters?: ElementList; + body?: number; + }; + CallExpression?: { + arguments?: ElementList; + }; + ArrayExpression?: ElementList; + ObjectExpression?: ElementList; + ImportDeclaration?: ElementList; + flatTernaryExpressions?: boolean; + ignoredNodes?: string[]; + ignoreComments?: boolean; +} +type Options = [('tab' | number)?, IndentConfig?]; +type MessageIds = 'wrongIndentation'; + +type AppliedOptions = ExcludeKeys< + RequireKeys, + 'VariableDeclarator' +> & { + VariableDeclarator: 'off' | VariableDeclaratorObj; +}; + +export default createRule({ + name: 'indent', + meta: { + type: 'layout', + docs: { + description: 'Enforce consistent indentation.', + category: 'Stylistic Issues', + recommended: false, + }, + fixable: 'whitespace', + schema: [ + { + oneOf: [ + { + enum: ['tab'], + }, + { + type: 'integer', + minimum: 0, + }, + ], + }, + { + type: 'object', + properties: { + SwitchCase: { + type: 'integer', + minimum: 0, + default: 0, + }, + VariableDeclarator: { + oneOf: [ + ELEMENT_LIST_SCHEMA, + { + type: 'object', + properties: { + var: ELEMENT_LIST_SCHEMA, + let: ELEMENT_LIST_SCHEMA, + const: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ], + }, + outerIIFEBody: { + type: 'integer', + minimum: 0, + }, + MemberExpression: { + oneOf: [ + { + type: 'integer', + minimum: 0, + }, + { + enum: ['off'], + }, + ], + }, + FunctionDeclaration: { + type: 'object', + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + FunctionExpression: { + type: 'object', + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + CallExpression: { + type: 'object', + properties: { + arguments: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ArrayExpression: ELEMENT_LIST_SCHEMA, + ObjectExpression: ELEMENT_LIST_SCHEMA, + ImportDeclaration: ELEMENT_LIST_SCHEMA, + flatTernaryExpressions: { + type: 'boolean', + default: false, + }, + ignoredNodes: { + type: 'array', + items: { + type: 'string', + not: { + pattern: ':exit$', + }, + }, + }, + ignoreComments: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + wrongIndentation: + 'Expected indentation of {{expected}} but found {{actual}}.', + }, + }, + defaultOptions: [ + // typescript docs and playground use 4 space indent + 4, + { + // typescript docs indent the case from the switch + // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-8.html#example-4 + SwitchCase: 1, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT, + }, + outerIIFEBody: 1, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT, + }, + MemberExpression: 1, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + ignoredNodes: [], + ignoreComments: false, + }, + ], + create(context, [userIndent, userOptions]) { + const indentType = userIndent === 'tab' ? 'tab' : 'space'; + const indentSize = userIndent === 'tab' ? 1 : userIndent!; + + const options = userOptions as AppliedOptions; + if ( + typeof userOptions!.VariableDeclarator === 'number' || + userOptions!.VariableDeclarator === 'first' + ) { + // typescript doesn't narrow the type for some reason + options.VariableDeclarator = { + var: userOptions!.VariableDeclarator as number | 'first', + let: userOptions!.VariableDeclarator as number | 'first', + const: userOptions!.VariableDeclarator as number | 'first', + }; + } + + const sourceCode = context.getSourceCode(); + const tokenInfo = new TokenInfo(sourceCode); + const offsets = new OffsetStorage( + tokenInfo, + indentSize, + indentType === 'space' ? ' ' : '\t', + ); + const parameterParens = new WeakSet(); + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param expectedAmount The expected amount of indentation characters for this line + * @param actualSpaces The actual number of indentation spaces that were found on this line + * @param actualTabs The actual number of indentation tabs that were found on this line + * @returns An error message for this line + */ + function createErrorMessageData( + expectedAmount: number, + actualSpaces: number, + actualTabs: number, + ) { + const expectedStatement = `${expectedAmount} ${indentType}${ + expectedAmount === 1 ? '' : 's' + }`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? '' : 's'}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? '' : 's'}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0) { + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = + indentType === 'space' + ? actualSpaces + : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = + indentType === 'tab' ? actualTabs : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = '0'; + } + return { + expected: expectedStatement, + actual: foundStatement, + }; + } + + /** + * Reports a given indent violation + * @param token Token violating the indent rule + * @param neededIndent Expected indentation string + */ + function report(token: TokenOrComment, neededIndent: string): void { + const actualIndent = Array.from(tokenInfo.getTokenIndent(token)); + const numSpaces = actualIndent.filter(char => char === ' ').length; + const numTabs = actualIndent.filter(char => char === '\t').length; + + context.report({ + node: token, + messageId: 'wrongIndentation', + data: createErrorMessageData(neededIndent.length, numSpaces, numTabs), + loc: { + start: { line: token.loc.start.line, column: 0 }, + end: { line: token.loc.start.line, column: token.loc.start.column }, + }, + fix(fixer) { + return fixer.replaceTextRange( + [token.range[0] - token.loc.start.column, token.range[0]], + neededIndent, + ); + }, + }); + } + + /** + * Checks if a token's indentation is correct + * @param token Token to examine + * @param desiredIndent Desired indentation of the string + * @returns `true` if the token's indentation is correct + */ + function validateTokenIndent( + token: TokenOrComment, + desiredIndent: string, + ): boolean { + const indentation = tokenInfo.getTokenIndent(token); + + return ( + indentation === desiredIndent || + // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs. + (indentation.includes(' ') && indentation.includes('\t')) + ); + } + + /** + * Check to see if the node is a file level IIFE + * @param node The function node to check. + * @returns True if the node is the outer IIFE + */ + function isOuterIIFE(node: TSESTree.Node): boolean { + /* + * Verify that the node is an IIFE + */ + if ( + !node.parent || + node.parent.type !== 'CallExpression' || + node.parent.callee !== node + ) { + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIFE is outer. + * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. + * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. + */ + let statement = node.parent && node.parent.parent; + + while ( + statement && + ((statement.type === AST_NODE_TYPES.UnaryExpression && + ['!', '~', '+', '-'].indexOf(statement.operator) > -1) || + statement.type === AST_NODE_TYPES.AssignmentExpression || + statement.type === AST_NODE_TYPES.LogicalExpression || + statement.type === AST_NODE_TYPES.SequenceExpression || + statement.type === AST_NODE_TYPES.VariableDeclarator) + ) { + statement = statement.parent; + } + + return ( + !!statement && + (statement.type === AST_NODE_TYPES.ExpressionStatement || + statement.type === AST_NODE_TYPES.VariableDeclaration) && + !!statement.parent && + statement.parent.type === AST_NODE_TYPES.Program + ); + } + + /** + * Counts the number of linebreaks that follow the last non-whitespace character in a string + * @param str The string to check + * @returns The number of JavaScript linebreaks that follow the last non-whitespace character, + * or the total number of linebreaks if the string is all whitespace. + */ + function countTrailingLinebreaks(str: string): number { + const trailingWhitespace = str.match(/\s*$/u)![0]; + const linebreakMatches = trailingWhitespace.match( + createGlobalLinebreakMatcher(), + ); + + return linebreakMatches === null ? 0 : linebreakMatches.length; + } + + /** + * Check indentation for lists of elements (arrays, objects, function params) + * @param elements List of elements that should be offset + * @param startToken The start token of the list that element should be aligned against, e.g. '[' + * @param endToken The end token of the list, e.g. ']' + * @param offset The amount that the elements should be offset + */ + function addElementListIndent( + elements: TSESTree.Node[], + startToken: TSESTree.Token, + endToken: TSESTree.Token, + offset: number | string, + ) { + /** + * Gets the first token of a given element, including surrounding parentheses. + * @param element A node in the `elements` list + * @returns The first token of this element + */ + function getFirstToken(element: TSESTree.Node): TSESTree.Token { + let token = sourceCode.getTokenBefore(element)!; + + while (isOpeningParenToken(token) && token !== startToken) { + token = sourceCode.getTokenBefore(token)!; + } + return sourceCode.getTokenAfter(token)!; + } + + // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden) + offsets.setDesiredOffsets( + [startToken.range[1], endToken.range[0]], + startToken, + typeof offset === 'number' ? offset : 1, + ); + offsets.setDesiredOffset(endToken, startToken, 0); + + // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level. + if (offset === 'first' && elements.length && !elements[0]) { + return; + } + elements.forEach((element, index) => { + if (!element) { + // Skip holes in arrays + return; + } + if (offset === 'off') { + // Ignore the first token of every element if the "off" option is used + offsets.ignoreToken(getFirstToken(element)); + } + + // Offset the following elements correctly relative to the first element + if (index === 0) { + return; + } + if ( + offset === 'first' && + tokenInfo.isFirstTokenOfLine(getFirstToken(element)) + ) { + offsets.matchOffsetOf( + getFirstToken(elements[0]), + getFirstToken(element), + ); + } else { + const previousElement = elements[index - 1]; + const firstTokenOfPreviousElement = + previousElement && getFirstToken(previousElement); + const previousElementLastToken = + previousElement && sourceCode.getLastToken(previousElement)!; + + if ( + previousElement && + previousElementLastToken.loc.end.line - + countTrailingLinebreaks(previousElementLastToken.value) > + startToken.loc.end.line + ) { + offsets.setDesiredOffsets( + [previousElement.range[1], element.range[1]], + firstTokenOfPreviousElement, + 0, + ); + } + } + }); + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + */ + function addBlocklessNodeIndent(node: TSESTree.Node): void { + if (node.type !== 'BlockStatement') { + const lastParentToken = sourceCode.getTokenBefore( + node, + isNotOpeningParenToken, + )!; + + let firstBodyToken = sourceCode.getFirstToken(node)!; + let lastBodyToken = sourceCode.getLastToken(node)!; + + while ( + isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)!) && + isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken)!) + ) { + firstBodyToken = sourceCode.getTokenBefore(firstBodyToken)!; + lastBodyToken = sourceCode.getTokenAfter(lastBodyToken)!; + } + + offsets.setDesiredOffsets( + [firstBodyToken.range[0], lastBodyToken.range[1]], + lastParentToken, + 1, + ); + + /* + * For blockless nodes with semicolon-first style, don't indent the semicolon. + * e.g. + * if (foo) bar() + * ; [1, 2, 3].map(foo) + */ + const lastToken = sourceCode.getLastToken(node); + + if ( + lastToken && + node.type !== 'EmptyStatement' && + isSemicolonToken(lastToken) + ) { + offsets.setDesiredOffset(lastToken, lastParentToken, 0); + } + } + } + + /** + * Checks the indentation for nodes that are like function calls + */ + function addFunctionCallIndent( + node: TSESTree.CallExpression | TSESTree.NewExpression, + ): void { + const openingParen = node.arguments.length + ? sourceCode.getFirstTokenBetween( + node.callee, + node.arguments[0], + isOpeningParenToken, + )! + : sourceCode.getLastToken(node, 1)!; + const closingParen = sourceCode.getLastToken(node)!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + offsets.setDesiredOffset( + openingParen, + sourceCode.getTokenBefore(openingParen)!, + 0, + ); + + addElementListIndent( + node.arguments, + openingParen, + closingParen, + options.CallExpression.arguments!, + ); + } + + /** + * Checks the indentation of parenthesized values, given a list of tokens in a program + * @param tokens A list of tokens + */ + function addParensIndent(tokens: TSESTree.Token[]): void { + const parenStack: TSESTree.Token[] = []; + const parenPairs: { left: TSESTree.Token; right: TSESTree.Token }[] = []; + + tokens.forEach(nextToken => { + // Accumulate a list of parenthesis pairs + if (isOpeningParenToken(nextToken)) { + parenStack.push(nextToken); + } else if (isClosingParenToken(nextToken)) { + parenPairs.unshift({ left: parenStack.pop()!, right: nextToken }); + } + }); + + parenPairs.forEach(pair => { + const leftParen = pair.left; + const rightParen = pair.right; + + // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. + if ( + !parameterParens.has(leftParen) && + !parameterParens.has(rightParen) + ) { + const parenthesizedTokens = new Set( + sourceCode.getTokensBetween(leftParen, rightParen), + ); + + parenthesizedTokens.forEach(token => { + if (!parenthesizedTokens.has(offsets.getFirstDependency(token)!)) { + offsets.setDesiredOffset(token, leftParen, 1); + } + }); + } + + offsets.setDesiredOffset(rightParen, leftParen, 0); + }); + } + + /** + * Ignore all tokens within an unknown node whose offset do not depend + * on another token's offset within the unknown node + */ + function ignoreNode(node: TSESTree.Node): void { + const unknownNodeTokens = new Set( + sourceCode.getTokens(node, { includeComments: true }), + ); + + unknownNodeTokens.forEach(token => { + if (!unknownNodeTokens.has(offsets.getFirstDependency(token)!)) { + const firstTokenOfLine = tokenInfo.getFirstTokenOfLine(token); + + if (token === firstTokenOfLine) { + offsets.ignoreToken(token); + } else { + offsets.setDesiredOffset(token, firstTokenOfLine, 0); + } + } + }); + } + + /** + * Check whether the given token is on the first line of a statement. + * @param leafNode The expression node that the token belongs directly. + * @returns `true` if the token is on the first line of a statement. + */ + function isOnFirstLineOfStatement( + token: TSESTree.Token, + leafNode: TSESTree.Node, + ): boolean { + let node: TSESTree.Node | undefined = leafNode; + + while ( + node.parent && + !node.parent.type.endsWith('Statement') && + !node.parent.type.endsWith('Declaration') + ) { + node = node.parent; + } + node = node.parent; + + return !node || node.loc.start.line === token.loc.start.line; + } + + /** + * Check whether there are any blank (whitespace-only) lines between + * two tokens on separate lines. + * @returns `true` if the tokens are on separate lines and + * there exists a blank line between them, `false` otherwise. + */ + function hasBlankLinesBetween( + firstToken: TSESTree.Token, + secondToken: TSESTree.Token, + ): boolean { + const firstTokenLine = firstToken.loc.end.line; + const secondTokenLine = secondToken.loc.start.line; + + if ( + firstTokenLine === secondTokenLine || + firstTokenLine === secondTokenLine - 1 + ) { + return false; + } + + for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) { + if (!tokenInfo.firstTokensByLineNumber.has(line)) { + return true; + } + } + + return false; + } + + const ignoredNodeFirstTokens = new Set(); + + const baseOffsetListeners: RuleListener = { + 'ArrayExpression, ArrayPattern'( + node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, + ) { + const openingBracket = sourceCode.getFirstToken(node)!; + const closingBracket = sourceCode.getTokenAfter( + node.elements[node.elements.length - 1] || openingBracket, + isClosingBracketToken, + )!; + + addElementListIndent( + node.elements, + openingBracket, + closingBracket, + options.ArrayExpression, + ); + }, + + ArrowFunctionExpression(node) { + const firstToken = sourceCode.getFirstToken(node)!; + + if (isOpeningParenToken(firstToken)) { + const openingParen = firstToken; + const closingParen = sourceCode.getTokenBefore( + node.body, + isClosingParenToken, + )!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options.FunctionExpression.parameters!, + ); + } + addBlocklessNodeIndent(node.body); + }, + + AssignmentExpression(node) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + )!; + + offsets.setDesiredOffsets( + [operator.range[0], node.range[1]], + sourceCode.getLastToken(node.left)!, + 1, + ); + offsets.ignoreToken(operator); + offsets.ignoreToken(sourceCode.getTokenAfter(operator)!); + }, + + 'BinaryExpression, LogicalExpression'( + node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, + ) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + )!; + + /* + * For backwards compatibility, don't check BinaryExpression indents, e.g. + * var foo = bar && + * baz; + */ + + const tokenAfterOperator = sourceCode.getTokenAfter(operator)!; + + offsets.ignoreToken(operator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffset(tokenAfterOperator, operator, 0); + }, + + 'BlockStatement, ClassBody'( + node: TSESTree.BlockStatement | TSESTree.ClassBody, + ) { + let blockIndentLevel; + + if (node.parent && isOuterIIFE(node.parent)) { + blockIndentLevel = options.outerIIFEBody; + } else if ( + node.parent && + (node.parent.type === AST_NODE_TYPES.FunctionExpression || + node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression) + ) { + blockIndentLevel = options.FunctionExpression.body; + } else if ( + node.parent && + node.parent.type === AST_NODE_TYPES.FunctionDeclaration + ) { + blockIndentLevel = options.FunctionDeclaration.body; + } else { + blockIndentLevel = 1; + } + + /* + * For blocks that aren't lone statements, ensure that the opening curly brace + * is aligned with the parent. + */ + if (node.parent && !STATEMENT_LIST_PARENTS.has(node.parent.type)) { + offsets.setDesiredOffset( + sourceCode.getFirstToken(node)!, + sourceCode.getFirstToken(node.parent)!, + 0, + ); + } + addElementListIndent( + node.body, + sourceCode.getFirstToken(node)!, + sourceCode.getLastToken(node)!, + blockIndentLevel!, + ); + }, + + CallExpression: addFunctionCallIndent, + + 'ClassDeclaration[superClass], ClassExpression[superClass]'( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, + ) { + const classToken = sourceCode.getFirstToken(node)!; + const extendsToken = sourceCode.getTokenBefore( + node.superClass!, + isNotOpeningParenToken, + )!; + + offsets.setDesiredOffsets( + [extendsToken.range[0], node.body.range[0]], + classToken, + 1, + ); + }, + + ConditionalExpression(node) { + const firstToken = sourceCode.getFirstToken(node)!; + + // `flatTernaryExpressions` option is for the following style: + // var a = + // foo > 0 ? bar : + // foo < 0 ? baz : + // /*else*/ qiz ; + if ( + !options.flatTernaryExpressions || + node.test.loc.end.line !== node.consequent.loc.start.line || + isOnFirstLineOfStatement(firstToken, node) + ) { + const questionMarkToken = sourceCode.getFirstTokenBetween( + node.test, + node.consequent, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?', + )!; + const colonToken = sourceCode.getFirstTokenBetween( + node.consequent, + node.alternate, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === ':', + )!; + + const firstConsequentToken = sourceCode.getTokenAfter( + questionMarkToken, + )!; + const lastConsequentToken = sourceCode.getTokenBefore(colonToken)!; + const firstAlternateToken = sourceCode.getTokenAfter(colonToken)!; + + offsets.setDesiredOffset(questionMarkToken, firstToken, 1); + offsets.setDesiredOffset(colonToken, firstToken, 1); + + offsets.setDesiredOffset(firstConsequentToken, firstToken, 1); + + /* + * The alternate and the consequent should usually have the same indentation. + * If they share part of a line, align the alternate against the first token of the consequent. + * This allows the alternate to be indented correctly in cases like this: + * foo ? ( + * bar + * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo` + * baz // as a result, `baz` is offset by 1 rather than 2 + * ) + */ + if ( + lastConsequentToken.loc.end.line === + firstAlternateToken.loc.start.line + ) { + offsets.setDesiredOffset( + firstAlternateToken, + firstConsequentToken, + 0, + ); + } else { + /** + * If the alternate and consequent do not share part of a line, offset the alternate from the first + * token of the conditional expression. For example: + * foo ? bar + * : baz + * + * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up + * having no expected indentation. + */ + offsets.setDesiredOffset(firstAlternateToken, firstToken, 1); + } + } + }, + + 'DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement': ( + node: + | TSESTree.DoWhileStatement + | TSESTree.WhileStatement + | TSESTree.ForInStatement + | TSESTree.ForOfStatement, + ) => { + addBlocklessNodeIndent(node.body); + }, + + ExportNamedDeclaration(node) { + if (node.declaration === null) { + const closingCurly = sourceCode.getLastToken( + node, + isClosingBraceToken, + )!; + + // Indent the specifiers in `export {foo, bar, baz}` + addElementListIndent( + node.specifiers, + sourceCode.getFirstToken(node, { skip: 1 })!, + closingCurly, + 1, + ); + + if (node.source) { + // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'` + offsets.setDesiredOffsets( + [closingCurly.range[1], node.range[1]], + sourceCode.getFirstToken(node)!, + 1, + ); + } + } + }, + + ForStatement(node) { + const forOpeningParen = sourceCode.getFirstToken(node, 1)!; + + if (node.init) { + offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1); + } + if (node.test) { + offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1); + } + if (node.update) { + offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1); + } + addBlocklessNodeIndent(node.body); + }, + + 'FunctionDeclaration, FunctionExpression'( + node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, + ) { + const closingParen = sourceCode.getTokenBefore(node.body!)!; + const openingParen = sourceCode.getTokenBefore( + node.params.length ? node.params[0] : closingParen, + )!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options[node.type].parameters!, + ); + }, + + IfStatement(node) { + addBlocklessNodeIndent(node.consequent); + if (node.alternate && node.alternate.type !== 'IfStatement') { + addBlocklessNodeIndent(node.alternate); + } + }, + + ImportDeclaration(node) { + if ( + node.specifiers.some( + specifier => specifier.type === 'ImportSpecifier', + ) + ) { + const openingCurly = sourceCode.getFirstToken( + node, + isOpeningBraceToken, + )!; + const closingCurly = sourceCode.getLastToken( + node, + isClosingBraceToken, + )!; + + addElementListIndent( + node.specifiers.filter( + specifier => specifier.type === 'ImportSpecifier', + ), + openingCurly, + closingCurly, + options.ImportDeclaration, + ); + } + + const fromToken = sourceCode.getLastToken( + node, + token => token.type === 'Identifier' && token.value === 'from', + )!; + const sourceToken = sourceCode.getLastToken( + node, + token => token.type === 'String', + )!; + const semiToken = sourceCode.getLastToken( + node, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === ';', + )!; + + if (fromToken) { + const end = + semiToken && semiToken.range[1] === sourceToken.range[1] + ? node.range[1] + : sourceToken.range[1]; + + offsets.setDesiredOffsets( + [fromToken.range[0], end], + sourceCode.getFirstToken(node)!, + 1, + ); + } + }, + + 'MemberExpression, JSXMemberExpression, MetaProperty'( + node: + | TSESTree.MemberExpression + | TSESTree.JSXMemberExpression + | TSESTree.MetaProperty, + ) { + const object = + node.type === AST_NODE_TYPES.MetaProperty ? node.meta : node.object; + const isComputed = 'computed' in node && node.computed; + const firstNonObjectToken = sourceCode.getFirstTokenBetween( + object, + node.property, + isNotClosingParenToken, + )!; + const secondNonObjectToken = sourceCode.getTokenAfter( + firstNonObjectToken, + )!; + + const objectParenCount = sourceCode.getTokensBetween( + object, + node.property, + { filter: isClosingParenToken }, + ).length; + const firstObjectToken = objectParenCount + ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 })! + : sourceCode.getFirstToken(object)!; + const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken)!; + const firstPropertyToken = isComputed + ? firstNonObjectToken + : secondNonObjectToken; + + if (isComputed) { + // For computed MemberExpressions, match the closing bracket with the opening bracket. + offsets.setDesiredOffset( + sourceCode.getLastToken(node)!, + firstNonObjectToken, + 0, + ); + offsets.setDesiredOffsets( + node.property.range, + firstNonObjectToken, + 1, + ); + } + + /* + * If the object ends on the same line that the property starts, match against the last token + * of the object, to ensure that the MemberExpression is not indented. + * + * Otherwise, match against the first token of the object, e.g. + * foo + * .bar + * .baz // <-- offset by 1 from `foo` + */ + const offsetBase = + lastObjectToken.loc.end.line === firstPropertyToken.loc.start.line + ? lastObjectToken + : firstObjectToken; + + if (typeof options.MemberExpression === 'number') { + // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object. + offsets.setDesiredOffset( + firstNonObjectToken, + offsetBase, + options.MemberExpression, + ); + + /* + * For computed MemberExpressions, match the first token of the property against the opening bracket. + * Otherwise, match the first token of the property against the object. + */ + offsets.setDesiredOffset( + secondNonObjectToken, + isComputed ? firstNonObjectToken : offsetBase, + options.MemberExpression, + ); + } else { + // If the MemberExpression option is off, ignore the dot and the first token of the property. + offsets.ignoreToken(firstNonObjectToken); + offsets.ignoreToken(secondNonObjectToken); + + // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens. + offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0); + offsets.setDesiredOffset( + secondNonObjectToken, + firstNonObjectToken, + 0, + ); + } + }, + + NewExpression(node) { + // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo` + if ( + node.arguments.length > 0 || + (isClosingParenToken(sourceCode.getLastToken(node)!) && + isOpeningParenToken(sourceCode.getLastToken(node, 1)!)) + ) { + addFunctionCallIndent(node); + } + }, + + 'ObjectExpression, ObjectPattern'( + node: TSESTree.ObjectExpression | TSESTree.ObjectPattern, + ) { + const openingCurly = sourceCode.getFirstToken(node)!; + const closingCurly = sourceCode.getTokenAfter( + node.properties.length + ? node.properties[node.properties.length - 1] + : openingCurly, + isClosingBraceToken, + )!; + + addElementListIndent( + node.properties, + openingCurly, + closingCurly, + options.ObjectExpression, + ); + }, + + Property(node) { + if (!node.shorthand && !node.method && node.kind === 'init') { + const colon = sourceCode.getFirstTokenBetween( + node.key, + node.value, + isColonToken, + )!; + + offsets.ignoreToken(sourceCode.getTokenAfter(colon)!); + } + }, + + SwitchStatement(node) { + const openingCurly = sourceCode.getTokenAfter( + node.discriminant, + isOpeningBraceToken, + )!; + const closingCurly = sourceCode.getLastToken(node)!; + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + options.SwitchCase, + ); + + if (node.cases.length) { + sourceCode + .getTokensBetween(node.cases[node.cases.length - 1], closingCurly, { + includeComments: true, + filter: isCommentToken, + }) + .forEach(token => offsets.ignoreToken(token)); + } + }, + + SwitchCase(node) { + if ( + !( + node.consequent.length === 1 && + node.consequent[0].type === 'BlockStatement' + ) + ) { + const caseKeyword = sourceCode.getFirstToken(node)!; + const tokenAfterCurrentCase = sourceCode.getTokenAfter(node)!; + + offsets.setDesiredOffsets( + [caseKeyword.range[1], tokenAfterCurrentCase.range[0]], + caseKeyword, + 1, + ); + } + }, + + TemplateLiteral(node) { + node.expressions.forEach((_, index) => { + const previousQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + const tokenToAlignFrom = + previousQuasi.loc.start.line === previousQuasi.loc.end.line + ? sourceCode.getFirstToken(previousQuasi) + : null; + + offsets.setDesiredOffsets( + [previousQuasi.range[1], nextQuasi.range[0]], + tokenToAlignFrom, + 1, + ); + offsets.setDesiredOffset( + sourceCode.getFirstToken(nextQuasi)!, + tokenToAlignFrom, + 0, + ); + }); + }, + + VariableDeclaration(node) { + let variableIndent = Object.prototype.hasOwnProperty.call( + options.VariableDeclarator, + node.kind, + ) + ? (options.VariableDeclarator as VariableDeclaratorObj)[node.kind] + : DEFAULT_VARIABLE_INDENT; + + const firstToken = sourceCode.getFirstToken(node)!; + const lastToken = sourceCode.getLastToken(node)!; + + if (variableIndent === 'first') { + if (node.declarations.length > 1) { + addElementListIndent( + node.declarations, + firstToken, + lastToken, + 'first', + ); + return; + } + + variableIndent = DEFAULT_VARIABLE_INDENT; + } + + if ( + node.declarations[node.declarations.length - 1].loc.start.line > + node.loc.start.line + ) { + /* + * VariableDeclarator indentation is a bit different from other forms of indentation, in that the + * indentation of an opening bracket sometimes won't match that of a closing bracket. For example, + * the following indentations are correct: + * + * var foo = { + * ok: true + * }; + * + * var foo = { + * ok: true, + * }, + * bar = 1; + * + * Account for when exiting the AST (after indentations have already been set for the nodes in + * the declaration) by manually increasing the indentation level of the tokens in this declarator + * on the same line as the start of the declaration, provided that there are declarators that + * follow this one. + */ + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent as number, + true, + ); + } else { + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent as number, + ); + } + + if (isSemicolonToken(lastToken)) { + offsets.ignoreToken(lastToken); + } + }, + + VariableDeclarator(node) { + if (node.init) { + const equalOperator = sourceCode.getTokenBefore( + node.init, + isNotOpeningParenToken, + )!; + const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator)!; + + offsets.ignoreToken(equalOperator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffsets( + [tokenAfterOperator.range[0], node.range[1]], + equalOperator, + 1, + ); + offsets.setDesiredOffset( + equalOperator, + sourceCode.getLastToken(node.id), + 0, + ); + } + }, + + 'JSXAttribute[value]'(node: TSESTree.JSXAttribute) { + const nodeValue = node.value!; + const equalsToken = sourceCode.getFirstTokenBetween( + node.name, + nodeValue, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=', + )!; + + offsets.setDesiredOffsets( + [equalsToken.range[0], nodeValue.range[1]], + sourceCode.getFirstToken(node.name), + 1, + ); + }, + + JSXElement(node) { + if (node.closingElement) { + addElementListIndent( + node.children, + sourceCode.getFirstToken(node.openingElement)!, + sourceCode.getFirstToken(node.closingElement)!, + 1, + ); + } + }, + + JSXOpeningElement(node) { + const firstToken = sourceCode.getFirstToken(node)!; + let closingToken; + + if (node.selfClosing) { + closingToken = sourceCode.getLastToken(node, { skip: 1 })!; + offsets.setDesiredOffset( + sourceCode.getLastToken(node)!, + closingToken, + 0, + ); + } else { + closingToken = sourceCode.getLastToken(node)!; + } + offsets.setDesiredOffsets( + node.name.range, + sourceCode.getFirstToken(node)!, + ); + addElementListIndent(node.attributes, firstToken, closingToken, 1); + }, + + JSXClosingElement(node) { + const firstToken = sourceCode.getFirstToken(node); + + offsets.setDesiredOffsets(node.name.range, firstToken, 1); + }, + + JSXExpressionContainer(node) { + const openingCurly = sourceCode.getFirstToken(node)!; + const closingCurly = sourceCode.getLastToken(node)!; + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1, + ); + }, + + '*'(node: TSESTree.Node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } + }, + }; + + const listenerCallQueue: { + listener: RuleFunction; + node: TSESTree.Node; + }[] = []; + + /* + * To ignore the indentation of a node: + * 1. Don't call the node's listener when entering it (if it has a listener) + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + */ + const offsetListeners = Object.keys(baseOffsetListeners).reduce< + RuleListener + >( + /* + * Offset listener calls are deferred until traversal is finished, and are called as + * part of the final `Program:exit` listener. This is necessary because a node might + * be matched by multiple selectors. + * + * Example: Suppose there is an offset listener for `Identifier`, and the user has + * specified in configuration that `MemberExpression > Identifier` should be ignored. + * Due to selector specificity rules, the `Identifier` listener will get called first. However, + * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener + * should not have been called at all. Without doing extra selector matching, we don't know + * whether the Identifier matches the `MemberExpression > Identifier` selector until the + * `MemberExpression > Identifier` listener is called. + * + * To avoid this, the `Identifier` listener isn't called until traversal finishes and all + * ignored nodes are known. + */ + (acc, key) => { + const listener = baseOffsetListeners[key] as RuleFunction< + TSESTree.Node + >; + acc[key] = node => listenerCallQueue.push({ listener, node }); + + return acc; + }, + {}, + ); + + // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. + const ignoredNodes = new Set(); + + /** + * Ignores a node + * @param node The node to ignore + */ + function addToIgnoredNodes(node: TSESTree.Node): void { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } + + const ignoredNodeListeners = options.ignoredNodes.reduce( + (listeners, ignoredSelector) => + Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), + {}, + ); + + /* + * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation + * at the end. + * + * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears + * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored, + * so those listeners wouldn't be called anyway. + */ + return Object.assign(offsetListeners, ignoredNodeListeners, { + '*:exit'(node: TSESTree.Node) { + // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. + if (!KNOWN_NODES.has(node.type)) { + addToIgnoredNodes(node); + } + }, + 'Program:exit'() { + // If ignoreComments option is enabled, ignore all comment tokens. + if (options.ignoreComments) { + sourceCode + .getAllComments() + .forEach(comment => offsets.ignoreToken(comment)); + } + + // Invoke the queued offset listeners for the nodes that aren't ignored. + listenerCallQueue + .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node)) + .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node)); + + // Update the offsets for ignored nodes to prevent their child tokens from being reported. + ignoredNodes.forEach(ignoreNode); + + addParensIndent(sourceCode.ast.tokens); + + /* + * Create a Map from (tokenOrComment) => (precedingToken). + * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. + */ + const precedingTokens = sourceCode.ast.comments.reduce( + (commentMap, comment) => { + const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { + includeComments: true, + })!; + + return commentMap.set( + comment, + commentMap.has(tokenOrCommentBefore) + ? commentMap.get(tokenOrCommentBefore) + : tokenOrCommentBefore, + ); + }, + new WeakMap(), + ); + + sourceCode.lines.forEach((_, lineIndex) => { + const lineNumber = lineIndex + 1; + + if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) { + // Don't check indentation on blank lines + return; + } + + const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get( + lineNumber, + )!; + + if (firstTokenOfLine.loc.start.line !== lineNumber) { + // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. + return; + } + + // If the token matches the expected expected indentation, don't report it. + if ( + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(firstTokenOfLine), + ) + ) { + return; + } + + if (isCommentToken(firstTokenOfLine)) { + const tokenBefore = precedingTokens.get(firstTokenOfLine); + const tokenAfter = tokenBefore + ? sourceCode.getTokenAfter(tokenBefore)! + : sourceCode.ast.tokens[0]; + + const mayAlignWithBefore = + tokenBefore && + !hasBlankLinesBetween(tokenBefore, firstTokenOfLine); + const mayAlignWithAfter = + tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter); + + // If a comment matches the expected indentation of the token immediately before or after, don't report it. + if ( + (mayAlignWithBefore && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenBefore), + )) || + (mayAlignWithAfter && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenAfter), + )) + ) { + return; + } + } + + // Otherwise, report the token/comment. + report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); + }); + }, + }); + }, +}); diff --git a/packages/eslint-plugin/src/util/applyDefault.ts b/packages/eslint-plugin/src/util/applyDefault.ts index 56d513963a2..b54ca219864 100644 --- a/packages/eslint-plugin/src/util/applyDefault.ts +++ b/packages/eslint-plugin/src/util/applyDefault.ts @@ -19,7 +19,7 @@ export function applyDefault( } options.forEach((opt, i) => { - if (userOptions[i]) { + if (userOptions[i] !== undefined) { const userOpt = userOptions[i]; if (isObjectNotArray(userOpt) && isObjectNotArray(opt)) { diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 4e3d34937f9..07b6a0b3c3d 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -109,3 +109,12 @@ function keyCanBeReadAsPropertyName( node.type === AST_NODE_TYPES.Identifier ); } + +export type ExcludeKeys< + TObj extends Record, + TKeys extends keyof TObj +> = { [k in Exclude]: TObj[k] }; +export type RequireKeys< + TObj extends Record, + TKeys extends keyof TObj +> = ExcludeKeys & { [k in TKeys]-?: Exclude }; diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index d708a53cb4a..2703d00b5c2 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,5 +1,8 @@ import { ParserOptions } from '@typescript-eslint/parser'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; import { RuleTester as ESLintRuleTester } from 'eslint'; import * as path from 'path'; import RuleModule from 'ts-eslint'; @@ -28,7 +31,7 @@ interface InvalidTestCase< interface TestCaseError { messageId: TMessageIds; data?: Record; - type?: AST_NODE_TYPES; + type?: AST_NODE_TYPES | AST_TOKEN_TYPES; line?: number; column?: number; } diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js new file mode 100644 index 00000000000..f03507ff61e --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js @@ -0,0 +1,530 @@ +if (a) { + var b = c; + var d = e + * f; + var e = f; // <- +// -> + function g() { + if (h) { + var i = j; + } // <- + } // <- + + while (k) l++; + while (m) { + n--; // -> + } // <- + + do { + o = p + + q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + o = p + + q; + } while(r); // <- + + for (var s in t) { + u++; + } + + for (;;) { + v++; // <- + } + + if ( w ) { + x++; + } else if (y) { + z++; // <- + aa++; + } else { // <- + bb++; // -> +} // -> +} + +/**/var b; // NO ERROR: single line multi-line comments followed by code is OK +/* + * + */ var b; // NO ERROR: multi-line comments followed by code is OK + +var arr = [ + a, + b, + c, + function (){ + d + }, // <- + {}, + { + a: b, + c: d, + d: e + }, + [ + f, + g, + h, + i + ], + [j] +]; + +var obj = { + a: { + b: { + c: d, + e: f, + g: h + + i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + } + }, + g: [ + h, + i, + j, + k + ] +}; + +var arrObject = {a:[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]}; + +var objArray = [{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}]; + +var arrArray = [[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]]; + +var objObject = {a:{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}}; + + +switch (a) { + case 'a': + var a = 'b'; // -> + break; + case 'b': + var a = 'b'; + break; + case 'c': + var a = 'b'; // <- + break; + case 'd': + var a = 'b'; + break; // -> + case 'f': + var a = 'b'; + break; + case 'g': { + var a = 'b'; + break; + } + case 'z': + default: + break; // <- +} + +a.b('hi') + .c(a.b()) // <- + .d(); // <- + +if ( a ) { + if ( b ) { +d.e(f) // -> + .g() // -> + .h(); // -> + + i.j(m) + .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + + n.o(p) // <- + .q() // <- + .r(); // <- + } +} + +var a = b, + c = function () { + h = i; // -> + j = k; + l = m; // <- + }, + e = { + f: g, + n: o, + p: q + }, + r = [ + s, + t, + u + ]; + +var a = function () { +b = c; // -> + d = e; + f = g; // <- +}; + +function c(a, b) { + if (a || (a && + b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + return d; + } +} + +if ( a + || b ) { +var x; // -> + var c, + d = function(a, + b) { // <- + a; // -> + b; + c; // <- + } +} + + +a({ + d: 1 +}); + +a( +1 +); + +a( + b({ + d: 1 + }) +); + +a( + b( + c({ + d: 1, + e: 1, + f: 1 + }) + ) +); + +a({ d: 1 }); + +aa( + b({ // NO ERROR: CallExpression args not linted by default + c: d, // -> + e: f, + f: g + }) // -> +); + +aaaaaa( + b, + c, + { + d: a + } +); + +a(b, c, + d, e, + f, g // NO ERROR: alignment of arguments of callExpression not checked + ); // <- + +a( + ); // <- + +aaaaaa( + b, + c, { + d: a + }, { + e: f + } +); + +a.b() + .c(function(){ + var a; + }).d.e; + +if (a == 'b') { + if (c && d) e = f + else g('h').i('j') +} + +a = function (b, c) { + return a(function () { + var d = e + var f = g + var h = i + + if (!j) k('l', (m = n)) + if (o) p + else if (q) r + }) +} + +var a = function() { + "b" + .replace(/a/, "a") + .replace(/bc?/, function(e) { + return "b" + (e.f === 2 ? "c" : "f"); + }) + .replace(/d/, "d"); +}; + +$(b) + .on('a', 'b', function() { $(c).e('f'); }) + .on('g', 'h', function() { $(i).j('k'); }); + +a + .b('c', + 'd'); // NO ERROR: CallExpression args not linted by default + +a + .b('c', [ 'd', function(e) { + e++; + }]); + +var a = function() { + a++; + b++; // <- + c++; // <- + }, + b; + +var b = [ + a, + b, + c + ], + c; + +var c = { + a: 1, + b: 2, + c: 3 + }, + d; + +// holes in arrays indentation +x = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 +]; + +try { + a++; + b++; // <- +c++; // -> +} catch (d) { + e++; + f++; // <- +g++; // -> +} finally { + h++; + i++; // <- +j++; // -> +} + +if (array.some(function(){ + return true; +})) { +a++; // -> + b++; + c++; // <- +} + +var a = b.c(function() { + d++; + }), + e; + +switch (true) { + case (a + && b): +case (c // -> +&& d): + case (e // <- + && f): + case (g +&& h): + var i = j; // <- + var k = l; + var m = n; // -> +} + +if (a) { + b(); +} +else { +c(); // -> + d(); + e(); // <- +} + +if (a) b(); +else { +c(); // -> + d(); + e(); // <- +} + +if (a) { + b(); +} else c(); + +if (a) { + b(); +} +else c(); + +a(); + +if( "very very long multi line" + + "with weird indentation" ) { + b(); +a(); // -> + c(); // <- +} + +a( "very very long multi line" + + "with weird indentation", function() { + b(); +a(); // -> + c(); // <- + }); // <- + +a = function(content, dom) { + b(); + c(); // <- +d(); // -> +}; + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> + }; + +a = function(content, dom) { + b(); // -> + }; + +a = function(content, dom) { +b(); // -> + }; + +a('This is a terribly long description youll ' + + 'have to read', function () { + b(); // <- + c(); // <- + }); // <- + +if ( + array.some(function(){ + return true; + }) +) { +a++; // -> + b++; + c++; // <- +} + +function c(d) { + return { + e: function(f, g) { + } + }; +} + +function a(b) { + switch(x) { + case 1: + if (foo) { + return 5; + } + } +} + +function a(b) { + switch(x) { + case 1: + c; + } +} + +function a(b) { + switch(x) { + case 1: c; + } +} + +function test() { + var a = 1; + { + a(); + } +} + +{ + a(); +} + +function a(b) { + switch(x) { + case 1: + { // <- + a(); // -> + } + break; + default: + { + b(); + } + } +} + +switch (a) { + default: + if (b) + c(); +} + +function test(x) { + switch (x) { + case 1: + return function() { + var a = 5; + return a; + }; + } +} + +switch (a) { + default: + if (b) + c(); +} diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js new file mode 100644 index 00000000000..5c298429f69 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js @@ -0,0 +1,530 @@ +if (a) { + var b = c; + var d = e + * f; + var e = f; // <- + // -> + function g() { + if (h) { + var i = j; + } // <- + } // <- + + while (k) l++; + while (m) { + n--; // -> + } // <- + + do { + o = p + + q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + o = p + + q; + } while(r); // <- + + for (var s in t) { + u++; + } + + for (;;) { + v++; // <- + } + + if ( w ) { + x++; + } else if (y) { + z++; // <- + aa++; + } else { // <- + bb++; // -> + } // -> +} + +/**/var b; // NO ERROR: single line multi-line comments followed by code is OK +/* + * + */ var b; // NO ERROR: multi-line comments followed by code is OK + +var arr = [ + a, + b, + c, + function (){ + d + }, // <- + {}, + { + a: b, + c: d, + d: e + }, + [ + f, + g, + h, + i + ], + [j] +]; + +var obj = { + a: { + b: { + c: d, + e: f, + g: h + + i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + } + }, + g: [ + h, + i, + j, + k + ] +}; + +var arrObject = {a:[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]}; + +var objArray = [{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}]; + +var arrArray = [[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]]; + +var objObject = {a:{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}}; + + +switch (a) { + case 'a': + var a = 'b'; // -> + break; + case 'b': + var a = 'b'; + break; + case 'c': + var a = 'b'; // <- + break; + case 'd': + var a = 'b'; + break; // -> + case 'f': + var a = 'b'; + break; + case 'g': { + var a = 'b'; + break; + } + case 'z': + default: + break; // <- +} + +a.b('hi') + .c(a.b()) // <- + .d(); // <- + +if ( a ) { + if ( b ) { + d.e(f) // -> + .g() // -> + .h(); // -> + + i.j(m) + .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + + n.o(p) // <- + .q() // <- + .r(); // <- + } +} + +var a = b, + c = function () { + h = i; // -> + j = k; + l = m; // <- + }, + e = { + f: g, + n: o, + p: q + }, + r = [ + s, + t, + u + ]; + +var a = function () { + b = c; // -> + d = e; + f = g; // <- +}; + +function c(a, b) { + if (a || (a && + b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + return d; + } +} + +if ( a + || b ) { + var x; // -> + var c, + d = function(a, + b) { // <- + a; // -> + b; + c; // <- + } +} + + +a({ + d: 1 +}); + +a( +1 +); + +a( + b({ + d: 1 + }) +); + +a( + b( + c({ + d: 1, + e: 1, + f: 1 + }) + ) +); + +a({ d: 1 }); + +aa( + b({ // NO ERROR: CallExpression args not linted by default + c: d, // -> + e: f, + f: g + }) // -> +); + +aaaaaa( + b, + c, + { + d: a + } +); + +a(b, c, + d, e, + f, g // NO ERROR: alignment of arguments of callExpression not checked +); // <- + +a( +); // <- + +aaaaaa( + b, + c, { + d: a + }, { + e: f + } +); + +a.b() + .c(function(){ + var a; + }).d.e; + +if (a == 'b') { + if (c && d) e = f + else g('h').i('j') +} + +a = function (b, c) { + return a(function () { + var d = e + var f = g + var h = i + + if (!j) k('l', (m = n)) + if (o) p + else if (q) r + }) +} + +var a = function() { + "b" + .replace(/a/, "a") + .replace(/bc?/, function(e) { + return "b" + (e.f === 2 ? "c" : "f"); + }) + .replace(/d/, "d"); +}; + +$(b) + .on('a', 'b', function() { $(c).e('f'); }) + .on('g', 'h', function() { $(i).j('k'); }); + +a + .b('c', + 'd'); // NO ERROR: CallExpression args not linted by default + +a + .b('c', [ 'd', function(e) { + e++; + }]); + +var a = function() { + a++; + b++; // <- + c++; // <- + }, + b; + +var b = [ + a, + b, + c + ], + c; + +var c = { + a: 1, + b: 2, + c: 3 + }, + d; + +// holes in arrays indentation +x = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 +]; + +try { + a++; + b++; // <- + c++; // -> +} catch (d) { + e++; + f++; // <- + g++; // -> +} finally { + h++; + i++; // <- + j++; // -> +} + +if (array.some(function(){ + return true; +})) { + a++; // -> + b++; + c++; // <- +} + +var a = b.c(function() { + d++; + }), + e; + +switch (true) { + case (a + && b): + case (c // -> +&& d): + case (e // <- + && f): + case (g +&& h): + var i = j; // <- + var k = l; + var m = n; // -> +} + +if (a) { + b(); +} +else { + c(); // -> + d(); + e(); // <- +} + +if (a) b(); +else { + c(); // -> + d(); + e(); // <- +} + +if (a) { + b(); +} else c(); + +if (a) { + b(); +} +else c(); + +a(); + +if( "very very long multi line" + + "with weird indentation" ) { + b(); + a(); // -> + c(); // <- +} + +a( "very very long multi line" + + "with weird indentation", function() { + b(); + a(); // -> + c(); // <- +}); // <- + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> +}; + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> +}; + +a = function(content, dom) { + b(); // -> +}; + +a = function(content, dom) { + b(); // -> +}; + +a('This is a terribly long description youll ' + + 'have to read', function () { + b(); // <- + c(); // <- +}); // <- + +if ( + array.some(function(){ + return true; + }) +) { + a++; // -> + b++; + c++; // <- +} + +function c(d) { + return { + e: function(f, g) { + } + }; +} + +function a(b) { + switch(x) { + case 1: + if (foo) { + return 5; + } + } +} + +function a(b) { + switch(x) { + case 1: + c; + } +} + +function a(b) { + switch(x) { + case 1: c; + } +} + +function test() { + var a = 1; + { + a(); + } +} + +{ + a(); +} + +function a(b) { + switch(x) { + case 1: + { // <- + a(); // -> + } + break; + default: + { + b(); + } + } +} + +switch (a) { + default: + if (b) + c(); +} + +function test(x) { + switch (x) { + case 1: + return function() { + var a = 5; + return a; + }; + } +} + +switch (a) { + default: + if (b) + c(); +} diff --git a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts new file mode 100644 index 00000000000..ea44a421249 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts @@ -0,0 +1,9646 @@ +// The following tests are adapted from the the tests in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +// NOTE - this test suite is intentionally kept in a separate file to our +// custom tests. This is to keep a clear boundary between the two. + +import { + AST_TOKEN_TYPES, + AST_NODE_TYPES, +} from '@typescript-eslint/typescript-estree'; +import fs from 'fs'; +import path from 'path'; +import rule from '../../../src/rules/indent-new-do-not-use'; +import { RuleTester } from '../../RuleTester'; +import { expectedErrors, unIndent } from './utils'; + +const fixture = fs.readFileSync( + path.join(__dirname, '../../fixtures/indent/indent-invalid-fixture-1.js'), + 'utf8', +); +const fixedFixture = fs.readFileSync( + path.join(__dirname, '../../fixtures/indent/indent-valid-fixture-1.js'), + 'utf8', +); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('indent', rule, { + valid: [ + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', 'test23', function(responseData) { + window.ah.mobileAppVersion = responseData; + } + ); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', 'test23', function(responseData) { + window.ah.mobileAppVersion = responseData; + }); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', + null, + function responseCallback(responseData) { + window.ah.mobileAppVersion = responseData; + } + ); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', + null, + function responseCallback(responseData) { + window.ah.mobileAppVersion = responseData; + }); + `, + options: [2], + }, + { + code: unIndent` + function doStuff(keys) { + _.forEach( + keys, + key => { + doSomething(key); + } + ); + } + `, + options: [4], + }, + { + code: unIndent` + example( + function () { + console.log('example'); + } + ); + `, + options: [4], + }, + { + code: unIndent` + let foo = somethingList + .filter(x => { + return x; + }) + .map(x => { + return 100 * x; + }); + `, + options: [4], + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + options: [4], + }, + { + code: unIndent` + var x = 0 && + \t{ + \t\ta: 1, + \t\tb: 2 + \t}; + `, + options: ['tab'], + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }|| + { + c: 3, + d: 4 + }; + `, + options: [4], + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + }, + { + code: unIndent` + var x = ['a', + 'b', + 'c', + ]; + `, + options: [4], + }, + { + code: 'var x = 0 && 1;', + options: [4], + }, + { + code: 'var x = 0 && { a: 1, b: 2 };', + options: [4], + }, + { + code: unIndent` + var x = 0 && + ( + 1 + ); + `, + options: [4], + }, + { + code: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + options: [2], + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + // hi + }) + .then(function () { + return FunctionalHelpers.clearBrowserState(self, { + contentServer: true, + contentServer1: true + }); + }); + } + `, + options: [2], + }, + { + code: unIndent` + it('should... some lengthy test description that is forced to be' + + 'wrapped into two lines since the line length limit is set', () => { + expect(true).toBe(true); + }); + `, + options: [2], + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + var x = 1; + var y = 1; + }, function(err){ + var o = 1 - 2; + var y = 1 - 2; + return true; + }) + } + `, + options: [4], + }, + { + // https://github.com/eslint/eslint/issues/11802 + code: unIndent` + import foo from "foo" + + ;(() => {})() + `, + options: [4], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + var x = 1; + var y = 1; + }, function(err){ + var o = 1 - 2; + var y = 1 - 2; + return true; + }); + } + `, + options: [4, { MemberExpression: 0 }], + }, + + { + code: '// hi', + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var Command = function() { + var fileList = [], + files = [] + + files.concat(fileList) + }; + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: ' ', + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + if(data) { + console.log('hi'); + b = true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + foo = () => { + console.log('hi'); + return true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + function test(data) { + console.log('hi'); + return true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var test = function(data) { + console.log('hi'); + }; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + arr.forEach(function(data) { + otherdata.forEach(function(zero) { + console.log('hi'); + }) }); + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + a = [ + ,3 + ] + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + [ + ['gzip', AST_TOKEN_TYPES.gunzip], + ['gzip', AST_TOKEN_TYPES.unzip], + ['deflate', AST_TOKEN_TYPES.inflate], + ['deflateRaw', AST_TOKEN_TYPES.inflateRaw], + ].forEach(function(method) { + console.log(method); + }); + `, + options: [2, { SwitchCase: 1, VariableDeclarator: 2 }], + }, + { + code: unIndent` + test(123, { + bye: { + hi: [1, + { + b: 2 + } + ] + } + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var xyz = 2, + lmn = [ + { + a: 1 + } + ]; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + lmnn = [{ + a: 1 + }, + { + b: 2 + }, { + x: 2 + }]; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + unIndent` + [{ + foo: 1 + }, { + foo: 2 + }, { + foo: 3 + }] + `, + unIndent` + foo([ + bar + ], [ + baz + ], [ + qux + ]); + `, + { + code: unIndent` + abc({ + test: [ + [ + c, + xyz, + 2 + ].join(',') + ] + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc = { + test: [ + [ + c, + xyz, + 2 + ] + ] + }; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc( + { + a: 1, + b: 2 + } + ); + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc({ + a: 1, + b: 2 + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = + [ + c, + xyz, + { + a: 1, + b: 2 + } + ]; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = [ + c, + xyz, + { + a: 1, + b: 2 + } + ]; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` + var + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + const + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + let + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + var foo = { a: 1 }, bar = { + b: 2 + }; + `, + unIndent` + var foo = { a: 1 }, bar = { + b: 2 + }, + baz = { + c: 3 + } + `, + unIndent` + const { + foo + } = 1, + bar = 2 + `, + { + code: unIndent` + var foo = 1, + bar = + 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo = 1, + bar + = 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = 1, + bar + = 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = + 1, + bar + = + 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = (1), + bar + = (2) + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let foo = 'foo', + bar = bar // <-- no semicolon here + const a = 'a', + b = 'b' // <-- no semicolon here + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + var foo = 1, + bar = 2, + baz = 3 + ; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` + var foo = 1, + bar = 2, + baz = 3 + ; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = bar; + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = 'bar' // <-- no semicolon here + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + }, + { + code: unIndent` + let foo = 1, + bar = 2, + baz + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let + foo + `, + options: [4, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let foo = 1, + bar = + 2 + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = new abc({ + a: 1, + b: 2 + }), + b = 2; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = 2, + c = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var x = 2, + y = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var e = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function test() { + if (true || + false){ + console.log(val); + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` + var foo = bar || + !( + baz + ); + `, + unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + { + code: unIndent` + for (var val in obj) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function hi(){ var a = 1; + y++; x++; + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + for(;length > index; index++)if(NO_HOLES || index in self){ + x++; + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var geometry = 2, + rotate = 2; + `, + options: [2, { VariableDeclarator: 0 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [4, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + \trotate; + `, + options: ['tab', { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + let geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + const geometry = 2, + rotate = 3; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: + 'var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;', + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + if (1 < 2){ + //hi sd + } + `, + options: [2], + }, + { + code: unIndent` + while (1 < 2){ + //hi sd + } + `, + options: [2], + }, + { + code: "while (1 < 2) console.log('hi');", + options: [2], + }, + + { + code: unIndent` + [a, boop, + c].forEach((index) => { + index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, c].forEach((index) => { + index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + options: [4], + }, + { + code: unIndent` + (foo) + .bar([ + baz + ]); + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + switch (x) { + case "foo": + a(); + break; + case "bar": + switch (y) { + case "1": + break; + case "2": + a = 6; + break; + } + case "test": + break; + } + `, + options: [4, { SwitchCase: 1 }], + }, + { + code: unIndent` + switch (x) { + case "foo": + a(); + break; + case "bar": + switch (y) { + case "1": + break; + case "2": + a = 6; + break; + } + case "test": + break; + } + `, + options: [4, { SwitchCase: 2 }], + }, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + switch(x){ + case '1': + break; + case '2': + a = 6; + break; + } + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + if(x){ + a = 2; + } + else{ + a = 6; + } + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + if(x){ + a = 2; + } + else + a = 6; + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + a(); break; + case "baz": + a(); break; + } + `, + unIndent` + switch (0) { + } + `, + unIndent` + function foo() { + var a = "a"; + switch(a) { + case "a": + return "A"; + case "b": + return "B"; + } + } + foo(); + `, + { + code: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + a(); + break; + } + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + options: [4, { SwitchCase: 1 }], + }, + unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + unIndent` + if (a) { + (1 + 2 + 3); // no error on this line + } + `, + 'switch(value){ default: a(); break; }', + { + code: unIndent` + import {addons} from 'react/addons' + import React from 'react' + `, + options: [2], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var foo = 0, bar = 0; baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var a = 1, + b = 2, + c = 3; + `, + options: [4], + }, + { + code: unIndent` + var a = 1 + ,b = 2 + ,c = 3; + `, + options: [4], + }, + { + code: "while (1 < 2) console.log('hi')", + options: [2], + }, + { + code: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + var items = [ + { + foo: 'bar' + } + ]; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + const a = 1, + b = 2; + const items1 = [ + { + foo: 'bar' + } + ]; + const items2 = Items( + { + foo: 'bar' + } + ); + `, + options: [2, { VariableDeclarator: 3 }], + }, + { + code: unIndent` + const geometry = 2, + rotate = 3; + var a = 1, + b = 2; + let light = true, + shadow = false; + `, + options: [2, { VariableDeclarator: { const: 3, let: 2 } }], + }, + { + code: unIndent` + const abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + let abc2 = 5, + c2 = 2, + xyz2 = + { + a: 1, + b: 2 + }; + var abc3 = 5, + c3 = 2, + xyz3 = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }], + }, + { + code: unIndent` + module.exports = { + 'Unit tests': + { + rootPath: './', + environment: 'node', + tests: + [ + 'test/test-*.js' + ], + sources: + [ + '*.js', + 'test/**.js' + ] + } + }; + `, + options: [2], + }, + { + code: unIndent` + foo = + bar; + `, + options: [2], + }, + { + code: unIndent` + foo = ( + bar + ); + `, + options: [2], + }, + { + code: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + options: [2], + }, + unIndent` + var a = 1 + ,b = 2 + ; + `, + { + code: unIndent` + export function create (some, + argument) { + return Object.create({ + a: some, + b: argument + }); + }; + `, + options: [2, { FunctionDeclaration: { parameters: 'first' } }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + export function create (id, xfilter, rawType, + width=defaultWidth, height=defaultHeight, + footerHeight=defaultFooterHeight, + padding=defaultPadding) { + // ... function body, indented two spaces + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first' } }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var obj = { + foo: function () { + return new p() + .then(function (ok) { + return ok; + }, function () { + // ignore things + }); + } + }; + `, + options: [2], + }, + { + code: unIndent` + a.b() + .c(function(){ + var a; + }).d.e; + `, + options: [2], + }, + { + code: unIndent` + const YO = 'bah', + TE = 'mah' + + var res, + a = 5, + b = 4 + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` + const YO = 'bah', + TE = 'mah' + + var res, + a = 5, + b = 4 + + if (YO) console.log(TE) + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = 'bar', + baz = function() { + + } + + function hello () { + + } + `, + options: [2], + }, + { + code: unIndent` + var obj = { + send: function () { + return P.resolve({ + type: 'POST' + }) + .then(function () { + return true; + }, function () { + return false; + }); + } + }; + `, + options: [2], + }, + { + code: unIndent` + var obj = { + send: function () { + return P.resolve({ + type: 'POST' + }) + .then(function () { + return true; + }, function () { + return false; + }); + } + }; + `, + options: [2, { MemberExpression: 0 }], + }, + unIndent` + const someOtherFunction = argument => { + console.log(argument); + }, + someOtherValue = 'someOtherValue'; + `, + { + code: unIndent` + [ + 'a', + 'b' + ].sort().should.deepEqual([ + 'x', + 'y' + ]); + `, + options: [2], + }, + { + code: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = 1, + B = + class { + constructor(){} + a(){} + get b(){} + }, + c = 3; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = { + some: 1 + , name: 2 + }; + `, + options: [2], + }, + { + code: unIndent` + a.c = { + aa: function() { + 'test1'; + return 'aa'; + } + , bb: function() { + return this.bb(); + } + }; + `, + options: [4], + }, + { + code: unIndent` + var a = + { + actions: + [ + { + name: 'compile' + } + ] + }; + `, + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = + [ + { + name: 'compile' + } + ]; + `, + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + unIndent` + [[ + ], function( + foo + ) {} + ] + `, + unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + { + code: unIndent` + const func = function (opts) { + return Promise.resolve() + .then(() => { + [ + 'ONE', 'TWO' + ].forEach(command => { doSomething(); }); + }); + }; + `, + options: [4, { MemberExpression: 0 }], + }, + { + code: unIndent` + const func = function (opts) { + return Promise.resolve() + .then(() => { + [ + 'ONE', 'TWO' + ].forEach(command => { doSomething(); }); + }); + }; + `, + options: [4], + }, + { + code: unIndent` + var haveFun = function () { + SillyFunction( + { + value: true, + }, + { + _id: true, + } + ); + }; + `, + options: [4], + }, + { + code: unIndent` + var haveFun = function () { + new SillyFunction( + { + value: true, + }, + { + _id: true, + } + ); + }; + `, + options: [4], + }, + { + code: unIndent` + let object1 = { + doThing() { + return _.chain([]) + .map(v => ( + { + value: true, + } + )) + .value(); + } + }; + `, + options: [2], + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + options: [2], + }, + { + code: unIndent` + class Foo + extends Bar { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + class Foo extends + Bar { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + class Foo extends + ( + Bar + ) { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + fs.readdirSync(path.join(__dirname, '../rules')).forEach(name => { + files[name] = foo; + }); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` + (function(x, y){ + function foo(x) { + return x + 1; + } + })(1, 2); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + }()); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + !function(){ + function foo(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + !function(){ + \t\t\tfunction foo(x) { + \t\t\t\treturn x + 1; + \t\t\t} + }(); + `, + options: ['tab', { outerIIFEBody: 3 }], + }, + { + code: unIndent` + var out = function(){ + function fooVar(x) { + return x + 1; + } + }; + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + ns = function(){ + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = (function(){ + function fooVar(x) { + return x + 1; + } + }(x)); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = (function(){ + function fooVar(x) { + return x + 1; + } + }(x)); + `, + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` + var obj = { + foo: function() { + return true; + } + }; + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + while ( + function() { + return true; + }()) { + + x = x + 1; + }; + `, + options: [2, { outerIIFEBody: 20 }], + }, + { + code: unIndent` + (() => { + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + function foo() { + } + `, + options: ['tab', { outerIIFEBody: 0 }], + }, + { + code: unIndent` + ;(() => { + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + if(data) { + console.log('hi'); + } + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: 'Buffer.length', + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .indexOf('a') + .toString() + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer. + length + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + \t.foo + \t.bar + `, + options: ['tab', { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + options: [2, { MemberExpression: 2 }], + }, + unIndent` + ( + foo + .bar + ) + `, + unIndent` + ( + ( + foo + .bar + ) + ) + `, + unIndent` + ( + foo + ) + .bar + `, + unIndent` + ( + ( + foo + ) + .bar + ) + `, + unIndent` + ( + ( + foo + ) + [ + ( + bar + ) + ] + ) + `, + unIndent` + ( + foo[bar] + ) + .baz + `, + unIndent` + ( + (foo.bar) + ) + .baz + `, + { + code: unIndent` + MemberExpression + .can + .be + .turned + .off(); + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo = bar.baz() + .bip(); + `, + options: [4, { MemberExpression: 1 }], + }, + unIndent` + function foo() { + new + .target + } + `, + unIndent` + function foo() { + new. + target + } + `, + { + code: unIndent` + if (foo) { + bar(); + } else if (baz) { + foobar(); + } else if (qux) { + qux(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + }, + { + code: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + }, + { + code: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 1 } }], + }, + { + code: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + options: [2, { FunctionDeclaration: { body: 3 } }], + }, + { + code: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 2 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + options: [4, { FunctionExpression: { parameters: 'first', body: 1 } }], + }, + { + code: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 'first', body: 3 } }], + }, + { + code: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + options: [ + 2, + { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }, + ], + }, + { + code: unIndent` + function foo() { + bar(); + \tbaz(); + \t \t\t\t \t\t\t \t \tqux(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1 } }], + }, + { + code: unIndent` + function foo() { + bar(); + \t\t} + `, + options: [2], + }, + { + code: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + }, + { + code: unIndent` + (( + foo + )) + `, + options: [4], + }, + + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` + foo + ? bar + : baz + `, + options: [2], + }, + { + code: unIndent` + foo = (bar ? + baz : + qux + ); + `, + options: [2], + }, + unIndent` + [ + foo ? + bar : + baz, + qux + ]; + `, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/3845, https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` + foo(); + // Line + /* multiline + Line */ + bar(); + // trailing comment + `, + options: [2], + }, + { + code: unIndent` + switch (foo) { + case bar: + baz(); + // call the baz function + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + switch (foo) { + case bar: + baz(); + // no default + } + `, + options: [2, { SwitchCase: 1 }], + }, + unIndent` + [ + // no elements + ] + `, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + options: [2], + }, + { + code: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + options: [2], + }, + { + code: unIndent` + const { + a + } + = + { + a: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const { + a + } = { + a: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const + { + a + } = { + a: 1 + }; + `, + options: [2], + }, + { + code: unIndent` + const + foo = { + bar: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const [ + a + ] = [ + 1 + ] + `, + options: [2], + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` + var folder = filePath + .foo() + .bar; + `, + options: [2, { MemberExpression: 2 }], + }, + { + code: unIndent` + for (const foo of bar) + baz(); + `, + options: [2], + }, + { + code: unIndent` + var x = () => + 5; + `, + options: [2], + }, + unIndent` + ( + foo + )( + bar + ) + `, + unIndent` + (() => + foo + )( + bar + ) + `, + unIndent` + (() => { + foo(); + })( + bar + ) + `, + { + // Don't lint the indentation of the first token after a : + code: unIndent` + ({code: + "foo.bar();"}) + `, + options: [2], + }, + { + // Don't lint the indentation of the first token after a : + code: unIndent` + ({code: + "foo.bar();"}) + `, + options: [2], + }, + unIndent` + ({ + foo: + bar + }) + `, + unIndent` + ({ + [foo]: + bar + }) + `, + { + // Comments in switch cases + code: unIndent` + switch (foo) { + // comment + case study: + // comment + bar(); + case closed: + /* multiline comment + */ + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + // Comments in switch cases + code: unIndent` + switch (foo) { + // comment + case study: + // the comment can also be here + case closed: + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && ( + bar + ) + `, + options: [4], + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && (( + bar + )) + `, + options: [4], + }, + { + code: unIndent` + foo && + ( + bar + ) + `, + options: [4], + }, + unIndent` + foo && + !bar( + ) + `, + unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + { + code: unIndent` + foo = + bar; + `, + options: [4], + }, + { + code: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + options: [2, { FunctionExpression: { parameters: 3 } }], + }, + unIndent` + function foo() { + return (bar === 1 || bar === 2 && + (/Function/.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } + `, + { + code: unIndent` + function foo() { + return (foo === bar || ( + baz === qux && ( + foo === foo || + bar === bar || + baz === baz + ) + )) + } + `, + options: [4], + }, + unIndent` + if ( + foo === 1 || + bar === 1 || + // comment + (baz === 1 && qux === 1) + ) {} + `, + { + code: unIndent` + foo = + (bar + baz); + `, + options: [2], + }, + { + code: unIndent` + function foo() { + return (bar === 1 || bar === 2) && + (z === 3 || z === 4); + } + `, + options: [2], + }, + { + code: unIndent` + /* comment */ if (foo) { + bar(); + } + `, + options: [2], + }, + { + // Comments at the end of if blocks that have `else` blocks can either refer to the lines above or below them + code: unIndent` + if (foo) { + bar(); + // Otherwise, if foo is false, do baz. + // baz is very important. + } else { + baz(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo() { + return ((bar === 1 || bar === 2) && + (z === 3 || z === 4)); + } + `, + options: [2], + }, + { + code: unIndent` + foo( + bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` + foo( + \tbar, + \tbaz, + \tqux + ); + `, + options: ['tab', { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` + foo(bar, + baz, + qux); + `, + options: [4, { CallExpression: { arguments: 2 } }], + }, + { + code: unIndent` + foo( + bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 0 } }], + }, + { + code: unIndent` + foo(bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo(bar, baz, + qux, barbaz, + barqux, bazqux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + options: [2, { CallExpression: { arguments: 4 } }], + }, + unIndent` + foo( + (bar) + ); + `, + { + code: unIndent` + foo( + (bar) + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + }, + + // https://github.com/eslint/eslint/issues/7484 + { + code: unIndent` + var foo = function() { + return bar( + [{ + }].concat(baz) + ); + }; + `, + options: [2], + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` + return ( + foo + ); + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: unIndent` + return ( + foo + ) + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + unIndent` + var foo = [ + bar, + baz + ] + `, + unIndent` + var foo = [bar, + baz, + qux + ] + `, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 0 }], + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 8 }], + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [bar, + baz, qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + baz: 4 } + ] + `, + options: [4, { ArrayExpression: 2, ObjectExpression: 'first' }], + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + options: [2, { ObjectExpression: 0 }], + }, + { + code: unIndent` + var foo = { foo: 1, bar: 2, + baz: 3 } + `, + options: [2, { ObjectExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ + { + foo: 1 + } + ] + `, + options: [4, { ArrayExpression: 2 }], + }, + { + code: unIndent` + function foo() { + [ + foo + ] + } + `, + options: [2, { ArrayExpression: 4 }], + }, + { + code: '[\n]', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: '[\n]', + options: [2, { ArrayExpression: 1 }], + }, + { + code: '{\n}', + options: [2, { ObjectExpression: 'first' }], + }, + { + code: '{\n}', + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` + var foo = [ + [ + 1 + ] + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ 1, + [ + 2 + ] + ]; + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = bar(1, + [ 2, + 3 + ] + ); + `, + options: [ + 4, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + }, + { + code: unIndent` + var foo = + [ + ]() + `, + options: [ + 4, + { CallExpression: { arguments: 'first' }, ArrayExpression: 'first' }, + ], + }, + + // https://github.com/eslint/eslint/issues/7732 + { + code: unIndent` + const lambda = foo => { + Object.assign({}, + filterName, + { + display + } + ); + } + `, + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` + const lambda = foo => { + Object.assign({}, + filterName, + { + display + } + ); + } + `, + options: [2, { ObjectExpression: 'first' }], + }, + + // https://github.com/eslint/eslint/issues/7733 + { + code: unIndent` + var foo = function() { + \twindow.foo('foo', + \t\t{ + \t\t\tfoo: 'bar', + \t\t\tbar: { + \t\t\t\tfoo: 'bar' + \t\t\t} + \t\t} + \t); + } + `, + options: ['tab'], + }, + { + code: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + options: [ + 2, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + }, + { + code: unIndent` + if (foo) + bar(); + // Otherwise, if foo is false, do baz. + // baz is very important. + else { + baz(); + } + `, + options: [2], + }, + { + code: unIndent` + if ( + foo && bar || + baz && qux // This line is ignored because BinaryExpressions are not checked. + ) { + qux(); + } + `, + options: [4], + }, + unIndent` + [ + ] || [ + ] + `, + unIndent` + ( + [ + ] || [ + ] + ) + `, + unIndent` + 1 + + ( + 1 + ) + `, + unIndent` + ( + foo && ( + bar || + baz + ) + ) + `, + unIndent` + foo + || ( + bar + ) + `, + unIndent` + foo + || ( + bar + ) + `, + { + code: unIndent` + var foo = + 1; + `, + options: [4, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + var foo = 1, + bar = + 2; + `, + options: [4], + }, + { + code: unIndent` + switch (foo) { + case bar: + { + baz(); + } + } + `, + options: [2, { SwitchCase: 1 }], + }, + + // Template curlies + { + code: unIndent` + \`foo\${ + bar}\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + options: [2], + }, + unIndent` + foo(\` + bar + \`, { + baz: 1 + }); + `, + unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + unIndent` + JSON + .stringify( + { + ok: true + } + ); + `, + + // Don't check AssignmentExpression assignments + unIndent` + foo = + bar = + baz; + `, + unIndent` + foo = + bar = + baz; + `, + unIndent` + function foo() { + const template = \`this indentation is not checked + because it's part of a template literal.\`; + } + `, + unIndent` + function foo() { + const template = \`the indentation of a \${ + node.type + } node is checked.\`; + } + `, + { + // https://github.com/eslint/eslint/issues/7320 + code: unIndent` + JSON + .stringify( + { + test: 'test' + } + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + }, + unIndent` + [ + foo, + // comment + // another comment + bar + ] + `, + unIndent` + if (foo) { + /* comment */ bar(); + } + `, + unIndent` + function foo() { + return ( + 1 + ); + } + `, + unIndent` + function foo() { + return ( + 1 + ) + } + `, + unIndent` + if ( + foo && + !( + bar + ) + ) {} + `, + { + // https://github.com/eslint/eslint/issues/6007 + code: unIndent` + var abc = [ + ( + '' + ), + def, + ] + `, + options: [2], + }, + { + code: unIndent` + var abc = [ + ( + '' + ), + ( + 'bar' + ) + ] + `, + options: [2], + }, + unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + options: [4, { MemberExpression: 1 }], + }, + + // https://github.com/eslint/eslint/issues/7242 + unIndent` + var x = [ + [1], + [2] + ] + `, + unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + unIndent` + foo( + ) + `, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + }, + 'new Foo', + 'new (Foo)', + unIndent` + if (Foo) { + new Foo + } + `, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return ( + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + ) + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return foo + ? bar + : baz + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return ( + foo + ? bar + : baz + ) + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + foo + ? bar + : baz + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo(foo + ? bar + : baz + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + }, + { + code: '[,]', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: '[,]', + options: [2, { ArrayExpression: 'off' }], + }, + { + code: unIndent` + [ + , + foo + ] + `, + options: [4, { ArrayExpression: 'first' }], + }, + { + code: '[sparse, , array];', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` + ( + { + foo: 1, + baz: 2 + } + ); + `, + options: [2, { ObjectExpression: 'first' }], + }, + { + code: unIndent` + foo(() => { + bar; + }, () => { + baz; + }) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + options: [2, { ArrayExpression: 'first', MemberExpression: 1 }], + }, + unIndent` + foo = bar[ + baz + ]; + `, + { + code: unIndent` + foo[ + bar + ]; + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + foo[ + ( + bar + ) + ]; + `, + options: [4, { MemberExpression: 1 }], + }, + unIndent` + if (foo) + bar; + else if (baz) + qux; + `, + unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + unIndent` + if (foo) + ; + `, + 'x => {}', + { + code: unIndent` + import {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: "import 'foo'", + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 1 }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { + foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 1 }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { apple as a, + banana as b } from 'fruits'; + import { cat } from 'animals'; + `, + options: [4, { ImportDeclaration: 'first' }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { declaration, + can, + be, + turned } from 'off'; + `, + options: [4, { ImportDeclaration: 'off' }], + parserOptions: { sourceType: 'module' }, + }, + + // https://github.com/eslint/eslint/issues/8455 + unIndent` + ( + a + ) => b => { + c + } + `, + unIndent` + ( + a + ) => b => c => d => { + e + } + `, + unIndent` + ( + a + ) => + ( + b + ) => { + c + } + `, + unIndent` + if ( + foo + ) bar( + baz + ); + `, + unIndent` + if (foo) + { + bar(); + } + `, + unIndent` + function foo(bar) + { + baz(); + } + `, + unIndent` + () => + ({}) + `, + unIndent` + () => + (({})) + `, + unIndent` + ( + () => + ({}) + ) + `, + unIndent` + var x = function foop(bar) + { + baz(); + } + `, + unIndent` + var x = (bar) => + { + baz(); + } + `, + unIndent` + class Foo + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + `, + unIndent` + class Foo + extends Bar + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + `, + unIndent` + ( + class Foo + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + ) + `, + { + code: unIndent` + switch (foo) + { + case 1: + bar(); + } + `, + options: [4, { SwitchCase: 1 }], + }, + unIndent` + foo + .bar(function() { + baz + }) + `, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 2 }], + }, + unIndent` + foo + [bar](function() { + baz + }) + `, + unIndent` + foo. + bar. + baz + `, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo + [bar](function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo. + bar. + baz + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo = bar( + ).baz( + ) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo[ + bar ? baz : + qux + ] + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function foo() { + return foo ? bar : + baz + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + throw foo ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + bar + ) ? baz : + qux + `, + options: [4, { flatTernaryExpressions: true }], + }, + unIndent` + foo + [ + bar + ] + .baz(function() { + quz(); + }) + `, + unIndent` + [ + foo + ][ + "map"](function() { + qux(); + }) + `, + unIndent` + ( + a.b(function() { + c; + }) + ) + `, + unIndent` + ( + foo + ).bar(function() { + baz(); + }) + `, + unIndent` + new Foo( + bar + .baz + .qux + ) + `, + unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + unIndent` + const foo = a.b(), + longName = + baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName = + baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName + = baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName + = baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName = + ('fff'); + `, + unIndent` + const foo = a.b(), + longName = + ('fff'); + `, + unIndent` + const foo = a.b(), + longName + = ('fff'); + + `, + unIndent` + const foo = a.b(), + longName + = ('fff'); + + `, + unIndent` + const foo = a.b(), + longName = + ( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName = + ( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + + unIndent` + foo(\`foo + \`, { + ok: true + }, + { + ok: false + }) + `, + unIndent` + foo(tag\`foo + \`, { + ok: true + }, + { + ok: false + } + ) + `, + + // https://github.com/eslint/eslint/issues/8815 + unIndent` + async function test() { + const { + foo, + bar, + } = await doSomethingAsync( + 1, + 2, + 3, + ); + } + `, + unIndent` + function* test() { + const { + foo, + bar, + } = yield doSomethingAsync( + 1, + 2, + 3, + ); + } + `, + unIndent` + ({ + a: b + } = +foo( + bar + )); + `, + unIndent` + const { + foo, + bar, + } = typeof foo( + 1, + 2, + 3, + ); + `, + unIndent` + const { + foo, + bar, + } = +( + foo + ); + `, + + //---------------------------------------------------------------------- + // JSX tests + // https://github.com/eslint/eslint/issues/8425 + // Some of the following tests are adapted from the the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + ';', + unIndent` + ; + `, + 'var foo = ;', + unIndent` + var foo = ; + `, + unIndent` + var foo = (); + `, + unIndent` + var foo = ( + + ); + `, + unIndent` + < + Foo + a="b" + c="d" + />; + `, + unIndent` + ; + `, + unIndent` + < + Foo + a="b" + c="d"/>; + `, + 'bar;', + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + < + a + href="foo"> + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = < + a + href="bar"> + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + + `, + unIndent` + var foo = ( + baz + ); + `, + unIndent` + var foo = ( + baz + ); + `, + unIndent` + var foo = ( + + baz + + ); + `, + unIndent` + var foo = ( + + baz + + ); + `, + 'var foo = baz;', + unIndent` + + { + } + + `, + unIndent` + + { + foo + } + + `, + unIndent` + function foo() { + return ( + + { + b.forEach(() => { + // comment + a = c + .d() + .e(); + }) + } + + ); + } + `, + '', + unIndent` + + + `, + { + code: unIndent` + + + + `, + options: [2], + }, + { + code: unIndent` + + + + `, + options: [0], + }, + { + code: unIndent` + + \t + + `, + options: ['tab'], + }, + { + code: unIndent` + function App() { + return + + ; + } + `, + options: [2], + }, + { + code: unIndent` + function App() { + return ( + + ); + } + `, + options: [2], + }, + { + code: unIndent` + function App() { + return ( + + + + ); + } + `, + options: [2], + }, + { + code: unIndent` + it( + ( +
+ +
+ ) + ) + `, + options: [2], + }, + { + code: unIndent` + it( + (
+ + + +
) + ) + `, + options: [2], + }, + { + code: unIndent` + ( +
+ +
+ ) + `, + options: [2], + }, + { + code: unIndent` + { + head.title && +

+ {head.title} +

+ } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && +

+ {head.title} +

+ } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && ( +

+ {head.title} +

) + } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && ( +

+ {head.title} +

+ ) + } + `, + options: [2], + }, + { + code: unIndent` + [ +
, +
+ ] + `, + options: [2], + }, + unIndent` +
+ { + [ + , + + ] + } +
+ `, + unIndent` +
+ {foo && + [ + , + + ] + } +
+ `, + unIndent` +
+ bar
+ bar + bar {foo} + bar
+
+ `, + unIndent` + foo ? + : + + `, + unIndent` + foo ? + + : + `, + unIndent` + foo ? + + : + + `, + unIndent` +
+ {!foo ? + + : + + } +
+ `, + { + code: unIndent` + + {condition ? + : + + } + + `, + options: [2], + }, + { + code: unIndent` + + {condition ? + : + + } + + `, + options: [2], + }, + { + code: unIndent` + function foo() { + + {condition ? + : + + } + + } + `, + options: [2], + }, + unIndent` + + `, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: [0], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + unIndent` + + `, + unIndent` + + `, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + var x = function() { + return + } + `, + options: [2], + }, + { + code: unIndent` + var x = + `, + options: [2], + }, + { + code: unIndent` + + + + `, + options: [2], + }, + { + code: unIndent` + + {baz && } + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + var x = + `, + options: ['tab'], + }, + unIndent` + + `, + unIndent` +
+ unrelated{ + foo + } +
+ `, + unIndent` +
unrelated{ + foo + } +
+ `, + unIndent` + < + foo + .bar + .baz + > + foo + + `, + unIndent` + < + input + type= + "number" + /> + `, + unIndent` + < + input + type= + {'number'} + /> + `, + unIndent` + < + input + type + ="number" + /> + `, + unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + unIndent` +
+ { + /* foo */ + } +
+ `, + + // https://github.com/eslint/eslint/issues/8832 + unIndent` +
+ { + ( + 1 + ) + } +
+ `, + unIndent` + function A() { + return ( +
+ { + b && ( +
+
+ ) + } +
+ ); + } + `, + unIndent` +
foo +
bar
+
+ `, + unIndent` + Foo bar  + baz qux. + + `, + { + code: unIndent` + a(b + , c + ) + `, + options: [2, { CallExpression: { arguments: 'off' } }], + }, + { + code: unIndent` + a( + new B({ + c, + }) + ); + `, + options: [2, { CallExpression: { arguments: 'off' } }], + }, + { + code: unIndent` + foo + ? bar + : baz + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [4, { ignoredNodes: ['ClassBody'] }], + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [ + 4, + { ignoredNodes: ['ClassBody', AST_NODE_TYPES.BlockStatement] }, + ], + }, + { + code: unIndent` + foo({ + bar: 1 + }, + { + baz: 2 + }, + { + qux: 3 + }) + `, + options: [4, { ignoredNodes: ['CallExpression > ObjectExpression'] }], + }, + { + code: unIndent` + foo + .bar + `, + options: [4, { ignoredNodes: ['MemberExpression'] }], + }, + { + code: unIndent` + $(function() { + + foo(); + bar(); + + }); + `, + options: [ + 4, + { + ignoredNodes: [ + "Program > ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + }, + { + code: unIndent` + + `, + options: [4, { ignoredNodes: ['JSXOpeningElement'] }], + }, + { + code: unIndent` + foo && + + + `, + options: [ + 4, + { ignoredNodes: ['JSXElement', AST_NODE_TYPES.JSXOpeningElement] }, + ], + }, + { + code: unIndent` + (function($) { + $(function() { + foo; + }); + }()) + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement', + ], + }, + ], + }, + { + code: unIndent` + const value = ( + condition ? + valueIfTrue : + valueIfFalse + ); + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + var a = 0, b = 0, c = 0; + export default foo( + a, + b, { + c + } + ) + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExportDefaultDeclaration > CallExpression > ObjectExpression', + ], + }, + ], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + foobar = baz + ? qux + : boop + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + \` + SELECT + \${ + foo + } FROM THE_DATABASE + \` + `, + options: [4, { ignoredNodes: ['TemplateLiteral'] }], + }, + { + code: unIndent` + + Text + + `, + options: [4, { ignoredNodes: ['JSXOpeningElement'] }], + }, + { + code: unIndent` + { + \tvar x = 1, + \t y = 2; + } + `, + options: ['tab'], + }, + { + code: unIndent` + var x = 1, + y = 2; + var z; + `, + options: ['tab', { ignoredNodes: ['VariableDeclarator'] }], + }, + { + code: unIndent` + [ + foo(), + bar + ] + `, + options: [ + 'tab', + { ArrayExpression: 'first', ignoredNodes: ['CallExpression'] }, + ], + }, + { + code: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + options: [4, { ignoreComments: true }], + }, + { + code: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + options: [4, { ignoreComments: true }], + }, + unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + unIndent` + if (foo) { + // Comment can align with code immediately above even if "incorrect" alignment + doSomething(); + } + `, + unIndent` + if (foo) { + doSomething(); + // Comment can align with code immediately below even if "incorrect" alignment + } + `, + unIndent` + if (foo) { + // Comment can be in correct alignment even if not aligned with code above/below + } + `, + unIndent` + if (foo) { + + // Comment can be in correct alignment even if gaps between (and not aligned with) code above/below + + } + `, + unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + unIndent` + [{ + foo + }, + + // Comment between nodes + + { // comment + bar + }]; + `, + ], + + invalid: [ + { + code: unIndent` + var a = b; + if (a) { + b(); + } + `, + output: unIndent` + var a = b; + if (a) { + b(); + } + `, + options: [2], + errors: expectedErrors([[3, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + output: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + options: [2], + errors: expectedErrors([ + [2, 2, 18, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if (array.some(function(){ + return true; + })) { + a++; // -> + b++; + c++; // <- + } + `, + output: unIndent` + if (array.some(function(){ + return true; + })) { + a++; // -> + b++; + c++; // <- + } + `, + options: [2], + errors: expectedErrors([ + [4, 2, 0, AST_TOKEN_TYPES.Identifier], + [6, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (a){ + \tb=c; + \t\tc=d; + e=f; + } + `, + output: unIndent` + if (a){ + \tb=c; + \tc=d; + \te=f; + } + `, + options: ['tab'], + errors: expectedErrors('tab', [ + [3, 1, 2, AST_TOKEN_TYPES.Identifier], + [4, 1, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (a){ + b=c; + c=d; + e=f; + } + `, + output: unIndent` + if (a){ + b=c; + c=d; + e=f; + } + `, + options: [4], + errors: expectedErrors([ + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + [4, 4, 1, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: fixture, + output: fixedFixture, + options: [ + 2, + { + SwitchCase: 1, + MemberExpression: 1, + CallExpression: { arguments: 'off' }, + }, + ], + errors: expectedErrors([ + [5, 2, 4, AST_TOKEN_TYPES.Keyword], + [6, 2, 0, AST_TOKEN_TYPES.Line], + [10, 4, 6, AST_TOKEN_TYPES.Punctuator], + [11, 2, 4, AST_TOKEN_TYPES.Punctuator], + + [15, 4, 2, AST_TOKEN_TYPES.Identifier], + [16, 2, 4, AST_TOKEN_TYPES.Punctuator], + [23, 2, 4, AST_TOKEN_TYPES.Punctuator], + [29, 2, 4, AST_TOKEN_TYPES.Keyword], + [30, 4, 6, AST_TOKEN_TYPES.Identifier], + [36, 4, 6, AST_TOKEN_TYPES.Identifier], + [38, 2, 4, AST_TOKEN_TYPES.Punctuator], + [39, 4, 2, AST_TOKEN_TYPES.Identifier], + [40, 2, 0, AST_TOKEN_TYPES.Punctuator], + [54, 2, 4, AST_TOKEN_TYPES.Punctuator], + [114, 4, 2, AST_TOKEN_TYPES.Keyword], + [120, 4, 6, AST_TOKEN_TYPES.Keyword], + [124, 4, 2, AST_TOKEN_TYPES.Keyword], + [134, 4, 6, AST_TOKEN_TYPES.Keyword], + [138, 2, 3, AST_TOKEN_TYPES.Punctuator], + [139, 2, 3, AST_TOKEN_TYPES.Punctuator], + [143, 4, 0, AST_TOKEN_TYPES.Identifier], + [144, 6, 2, AST_TOKEN_TYPES.Punctuator], + [145, 6, 2, AST_TOKEN_TYPES.Punctuator], + [151, 4, 6, AST_TOKEN_TYPES.Identifier], + [152, 6, 8, AST_TOKEN_TYPES.Punctuator], + [153, 6, 8, AST_TOKEN_TYPES.Punctuator], + [159, 4, 2, AST_TOKEN_TYPES.Identifier], + [161, 4, 6, AST_TOKEN_TYPES.Identifier], + [175, 2, 0, AST_TOKEN_TYPES.Identifier], + [177, 2, 4, AST_TOKEN_TYPES.Identifier], + [189, 2, 0, AST_TOKEN_TYPES.Keyword], + [192, 6, 18, AST_TOKEN_TYPES.Identifier], + [193, 6, 4, AST_TOKEN_TYPES.Identifier], + [195, 6, 8, AST_TOKEN_TYPES.Identifier], + [228, 5, 4, AST_TOKEN_TYPES.Identifier], + [231, 3, 2, AST_TOKEN_TYPES.Punctuator], + [245, 0, 2, AST_TOKEN_TYPES.Punctuator], + [248, 0, 2, AST_TOKEN_TYPES.Punctuator], + [304, 4, 6, AST_TOKEN_TYPES.Identifier], + [306, 4, 8, AST_TOKEN_TYPES.Identifier], + [307, 2, 4, AST_TOKEN_TYPES.Punctuator], + [308, 2, 4, AST_TOKEN_TYPES.Identifier], + [311, 4, 6, AST_TOKEN_TYPES.Identifier], + [312, 4, 6, AST_TOKEN_TYPES.Identifier], + [313, 4, 6, AST_TOKEN_TYPES.Identifier], + [314, 2, 4, AST_TOKEN_TYPES.Punctuator], + [315, 2, 4, AST_TOKEN_TYPES.Identifier], + [318, 4, 6, AST_TOKEN_TYPES.Identifier], + [319, 4, 6, AST_TOKEN_TYPES.Identifier], + [320, 4, 6, AST_TOKEN_TYPES.Identifier], + [321, 2, 4, AST_TOKEN_TYPES.Punctuator], + [322, 2, 4, AST_TOKEN_TYPES.Identifier], + [326, 2, 1, AST_TOKEN_TYPES.Numeric], + [327, 2, 1, AST_TOKEN_TYPES.Numeric], + [328, 2, 1, AST_TOKEN_TYPES.Numeric], + [329, 2, 1, AST_TOKEN_TYPES.Numeric], + [330, 2, 1, AST_TOKEN_TYPES.Numeric], + [331, 2, 1, AST_TOKEN_TYPES.Numeric], + [332, 2, 1, AST_TOKEN_TYPES.Numeric], + [333, 2, 1, AST_TOKEN_TYPES.Numeric], + [334, 2, 1, AST_TOKEN_TYPES.Numeric], + [335, 2, 1, AST_TOKEN_TYPES.Numeric], + [340, 2, 4, AST_TOKEN_TYPES.Identifier], + [341, 2, 0, AST_TOKEN_TYPES.Identifier], + [344, 2, 4, AST_TOKEN_TYPES.Identifier], + [345, 2, 0, AST_TOKEN_TYPES.Identifier], + [348, 2, 4, AST_TOKEN_TYPES.Identifier], + [349, 2, 0, AST_TOKEN_TYPES.Identifier], + [355, 2, 0, AST_TOKEN_TYPES.Identifier], + [357, 2, 4, AST_TOKEN_TYPES.Identifier], + [361, 4, 6, AST_TOKEN_TYPES.Identifier], + [362, 2, 4, AST_TOKEN_TYPES.Punctuator], + [363, 2, 4, AST_TOKEN_TYPES.Identifier], + [368, 2, 0, AST_TOKEN_TYPES.Keyword], + [370, 2, 4, AST_TOKEN_TYPES.Keyword], + [374, 4, 6, AST_TOKEN_TYPES.Keyword], + [376, 4, 2, AST_TOKEN_TYPES.Keyword], + [383, 2, 0, AST_TOKEN_TYPES.Identifier], + [385, 2, 4, AST_TOKEN_TYPES.Identifier], + [390, 2, 0, AST_TOKEN_TYPES.Identifier], + [392, 2, 4, AST_TOKEN_TYPES.Identifier], + [409, 2, 0, AST_TOKEN_TYPES.Identifier], + [410, 2, 4, AST_TOKEN_TYPES.Identifier], + [416, 2, 0, AST_TOKEN_TYPES.Identifier], + [417, 2, 4, AST_TOKEN_TYPES.Identifier], + [418, 0, 4, AST_TOKEN_TYPES.Punctuator], + [422, 2, 4, AST_TOKEN_TYPES.Identifier], + [423, 2, 0, AST_TOKEN_TYPES.Identifier], + [427, 2, 6, AST_TOKEN_TYPES.Identifier], + [428, 2, 8, AST_TOKEN_TYPES.Identifier], + [429, 2, 4, AST_TOKEN_TYPES.Identifier], + [430, 0, 4, AST_TOKEN_TYPES.Punctuator], + [433, 2, 4, AST_TOKEN_TYPES.Identifier], + [434, 0, 4, AST_TOKEN_TYPES.Punctuator], + [437, 2, 0, AST_TOKEN_TYPES.Identifier], + [438, 0, 4, AST_TOKEN_TYPES.Punctuator], + [442, 2, 4, AST_TOKEN_TYPES.Identifier], + [443, 2, 4, AST_TOKEN_TYPES.Identifier], + [444, 0, 2, AST_TOKEN_TYPES.Punctuator], + [451, 2, 0, AST_TOKEN_TYPES.Identifier], + [453, 2, 4, AST_TOKEN_TYPES.Identifier], + [499, 6, 8, AST_TOKEN_TYPES.Punctuator], + [500, 8, 6, AST_TOKEN_TYPES.Identifier], + [504, 4, 6, AST_TOKEN_TYPES.Punctuator], + [505, 6, 8, AST_TOKEN_TYPES.Identifier], + [506, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Keyword], + [7, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + options: [4], + errors: expectedErrors([ + [3, 8, 7, AST_TOKEN_TYPES.Identifier], + [4, 8, 10, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([9, 8, 4, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + break; + } + switch(value){ + case "1": + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + output: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + break; + } + switch(value){ + case "1": + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [11, 8, 4, AST_TOKEN_TYPES.Keyword], + [14, 8, 4, AST_TOKEN_TYPES.Keyword], + [17, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 8, 4, AST_TOKEN_TYPES.Keyword], + [5, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 8, 4, AST_TOKEN_TYPES.Keyword], + [7, 4, 0, AST_TOKEN_TYPES.Keyword], + [8, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + output: unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + output: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 8, 0, AST_TOKEN_TYPES.Identifier], + [4, 8, 0, AST_TOKEN_TYPES.Keyword], + [5, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 8, 0, AST_TOKEN_TYPES.Identifier], + [7, 8, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var foo = function(){ + foo + .bar + } + `, + output: unIndent` + var foo = function(){ + foo + .bar + } + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 8, 10, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ( + foo + .bar + ) + `, + output: unIndent` + ( + foo + .bar + ) + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var foo = function(){ + foo + .bar + } + `, + output: unIndent` + var foo = function(){ + foo + .bar + } + `, + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var foo = () => { + foo + .bar + } + `, + output: unIndent` + var foo = () => { + foo + .bar + } + `, + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + TestClass.prototype.method = function () { + return Promise.resolve(3) + .then(function (x) { + return x; + }); + }; + `, + output: unIndent` + TestClass.prototype.method = function () { + return Promise.resolve(3) + .then(function (x) { + return x; + }); + }; + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([3, 4, 6, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + while (a) + b(); + `, + output: unIndent` + while (a) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + lmn = [{ + a: 1 + }, + { + b: 2 + }, + { + x: 2 + }]; + `, + output: unIndent` + lmn = [{ + a: 1 + }, + { + b: 2 + }, + { + x: 2 + }]; + `, + errors: expectedErrors([ + [2, 4, 8, AST_TOKEN_TYPES.Identifier], + [3, 0, 4, AST_TOKEN_TYPES.Punctuator], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + [5, 4, 8, AST_TOKEN_TYPES.Identifier], + [6, 0, 4, AST_TOKEN_TYPES.Punctuator], + [7, 0, 4, AST_TOKEN_TYPES.Punctuator], + [8, 4, 8, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + output: unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + output: unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + for (;;) + b(); + `, + output: unIndent` + for (;;) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + for (a in x) + b(); + `, + output: unIndent` + for (a in x) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + do + b(); + while(true) + `, + output: unIndent` + do + b(); + while(true) + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + if(true) + b(); + `, + output: unIndent` + if(true) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var test = { + a: 1, + b: 2 + }; + `, + output: unIndent` + var test = { + a: 1, + b: 2 + }; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 6, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var a = function() { + a++; + b++; + c++; + }, + b; + `, + output: unIndent` + var a = function() { + a++; + b++; + c++; + }, + b; + `, + options: [4], + errors: expectedErrors([ + [2, 8, 6, AST_TOKEN_TYPES.Identifier], + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 8, 10, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = 1, + b = 2, + c = 3; + `, + output: unIndent` + var a = 1, + b = 2, + c = 3; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + [a, b, + c].forEach((index) => { + index; + }); + `, + output: unIndent` + [a, b, + c].forEach((index) => { + index; + }); + `, + options: [4], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + output: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + output: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + options: [4], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + (foo) + .bar([ + baz + ]); + `, + output: unIndent` + (foo) + .bar([ + baz + ]); + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var x = ['a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = ['a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c', + 'd']; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c', + 'd']; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + [5, 4, 0, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [[ + ], function( + foo + ) {} + ] + `, + output: unIndent` + [[ + ], function( + foo + ) {} + ] + `, + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + output: unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + while (1 < 2) + console.log('foo') + console.log('bar') + `, + output: unIndent` + while (1 < 2) + console.log('foo') + console.log('bar') + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + output: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[3, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + output: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + output: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + options: [4, { SwitchCase: 2 }], + errors: expectedErrors([ + [2, 8, 0, AST_TOKEN_TYPES.Keyword], + [3, 12, 0, AST_TOKEN_TYPES.Identifier], + [4, 12, 0, AST_TOKEN_TYPES.Keyword], + [5, 8, 0, AST_TOKEN_TYPES.Keyword], + [6, 12, 0, AST_TOKEN_TYPES.Identifier], + [7, 12, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var geometry, + rotate; + `, + output: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 1 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var geometry, + rotate; + `, + output: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var geometry, + \trotate; + `, + output: unIndent` + var geometry, + \t\trotate; + `, + options: ['tab', { VariableDeclarator: 2 }], + errors: expectedErrors('tab', [[2, 2, 1, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + let geometry, + rotate; + `, + output: unIndent` + let geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + output: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + options: [2, { VariableDeclarator: 'first' }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 6, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = 'foo', + bar = bar; + `, + output: unIndent` + var foo = 'foo', + bar = bar; + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + output: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var a = { + a: 1, + b: 2 + } + `, + output: unIndent` + var a = { + a: 1, + b: 2 + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = [ + a, + b + ] + `, + output: unIndent` + var a = [ + a, + b + ] + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + let a = [ + a, + b + ] + `, + output: unIndent` + let a = [ + a, + b + ] + `, + options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + `, + output: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + `, + options: [4], + errors: expectedErrors([ + [2, 8, 6, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + const c = new Test({ + a: 1 + }), + d = 4; + `, + output: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + const c = new Test({ + a: 1 + }), + d = 4; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + errors: expectedErrors([ + [6, 4, 6, AST_TOKEN_TYPES.Identifier], + [7, 2, 4, AST_TOKEN_TYPES.Punctuator], + [8, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([6, 6, 7, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([4, 7, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + output: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + options: [2], + errors: expectedErrors([ + [4, 6, 8, AST_TOKEN_TYPES.Identifier], + [5, 4, 6, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + output: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + options: [2], + errors: expectedErrors([[2, 2, 1, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + var a = 1 + ,b = 2 + ; + `, + output: unIndent` + var a = 1 + ,b = 2 + ; + `, + errors: expectedErrors([[2, 4, 3, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + output: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + }; + `, + output: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + output: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[3, 6, 4, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + { + if(a){ + foo(); + } + else{ + bar(); + } + } + `, + output: unIndent` + { + if(a){ + foo(); + } + else{ + bar(); + } + } + `, + options: [4], + errors: expectedErrors([[5, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + { + if(a){ + foo(); + } + else + bar(); + + } + `, + output: unIndent` + { + if(a){ + foo(); + } + else + bar(); + + } + `, + options: [4], + errors: expectedErrors([[5, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + { + if(a) + foo(); + else + bar(); + } + `, + output: unIndent` + { + if(a) + foo(); + else + bar(); + } + `, + options: [4], + errors: expectedErrors([[4, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + output: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Keyword], + [3, 2, 4, AST_TOKEN_TYPES.Keyword], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + output: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, AST_TOKEN_TYPES.Keyword], + [3, 12, 8, AST_TOKEN_TYPES.Keyword], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if(data) { + console.log('hi'); + } + `, + output: unIndent` + if(data) { + console.log('hi'); + } + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(x); + `, + output: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(x); + `, + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, AST_TOKEN_TYPES.Keyword], + [3, 12, 8, AST_TOKEN_TYPES.Keyword], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var obj = { + foo: function() { + return true; + }() + }; + `, + output: unIndent` + var obj = { + foo: function() { + return true; + }() + }; + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[3, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + typeof function() { + function fooVar(x) { + return x + 1; + } + }(); + `, + output: unIndent` + typeof function() { + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Keyword], + [3, 4, 6, AST_TOKEN_TYPES.Keyword], + [4, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + { + \t!function(x) { + \t\t\t\treturn x + 1; + \t}() + }; + `, + output: unIndent` + { + \t!function(x) { + \t\treturn x + 1; + \t}() + }; + `, + options: ['tab', { outerIIFEBody: 3 }], + errors: expectedErrors('tab', [[3, 2, 4, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + Buffer + .toString() + `, + output: unIndent` + Buffer + .toString() + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + Buffer + .indexOf('a') + .toString() + `, + output: unIndent` + Buffer + .indexOf('a') + .toString() + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[3, 4, 0, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + Buffer. + length + `, + output: unIndent` + Buffer. + length + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + Buffer. + \t\tlength + `, + output: unIndent` + Buffer. + \tlength + `, + options: ['tab', { MemberExpression: 1 }], + errors: expectedErrors('tab', [[2, 1, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + output: unIndent` + Buffer + .foo + .bar + `, + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + new + .target + } + `, + output: unIndent` + function foo() { + new + .target + } + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function foo() { + new. + target + } + `, + output: unIndent` + function foo() { + new. + target + } + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 + + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (qux) qux(); + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (qux) qux(); + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else qux(); + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else qux(); + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + foo(); + if (baz) foobar(); + else qux(); + `, + output: unIndent` + foo(); + if (baz) foobar(); + else qux(); + `, + options: [2], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Keyword], + [3, 0, 2, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (bip) { + qux(); + } + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (bip) { + qux(); + } + `, + options: [2], + errors: expectedErrors([ + [3, 0, 5, AST_TOKEN_TYPES.Keyword], + [4, 2, 7, AST_TOKEN_TYPES.Identifier], + [5, 0, 5, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) { + foobar(); + } else if (boop) { + qux(); + } + `, + output: unIndent` + if (foo) bar(); + else if (baz) { + foobar(); + } else if (boop) { + qux(); + } + `, + options: [2], + errors: expectedErrors([ + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 5, AST_TOKEN_TYPES.Punctuator], + [5, 2, 7, AST_TOKEN_TYPES.Identifier], + [6, 0, 5, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + output: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + errors: expectedErrors([ + [2, 6, 2, AST_TOKEN_TYPES.Identifier], + [3, 2, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + errors: expectedErrors([ + [2, 4, 8, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 12, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 1 } }], + errors: expectedErrors([ + [2, 13, 2, AST_TOKEN_TYPES.Identifier], + [3, 13, 19, AST_TOKEN_TYPES.Identifier], + [4, 2, 3, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + output: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + options: [2, { FunctionDeclaration: { body: 3 } }], + errors: expectedErrors([3, 6, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + output: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 2 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 4, 6, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + errors: expectedErrors([ + [2, 2, 3, AST_TOKEN_TYPES.Identifier], + [3, 2, 1, AST_TOKEN_TYPES.Identifier], + [4, 20, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + options: [4, { FunctionExpression: { parameters: 'first', body: 1 } }], + errors: expectedErrors([ + [2, 19, 2, AST_TOKEN_TYPES.Identifier], + [3, 19, 24, AST_TOKEN_TYPES.Identifier], + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + output: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 'first', body: 3 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 6, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = bar; + \t\t\tvar baz = qux; + `, + output: unIndent` + var foo = bar; + var baz = qux; + `, + options: [2], + errors: expectedErrors([ + 2, + '0 spaces', + '3 tabs', + AST_TOKEN_TYPES.Keyword, + ]), + }, + { + code: unIndent` + function foo() { + \tbar(); + baz(); + qux(); + } + `, + output: unIndent` + function foo() { + \tbar(); + \tbaz(); + \tqux(); + } + `, + options: ['tab'], + errors: expectedErrors('tab', [ + [3, '1 tab', '2 spaces', AST_TOKEN_TYPES.Identifier], + [4, '1 tab', '14 spaces', AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo() { + bar(); + \t\t} + `, + output: unIndent` + function foo() { + bar(); + } + `, + options: [2], + errors: expectedErrors([ + [3, '0 spaces', '2 tabs', AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + output: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1 } }], + errors: expectedErrors([3, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + output: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + errors: expectedErrors([3, 6, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + output: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + options: [2, { FunctionExpression: { parameters: 3 } }], + errors: expectedErrors([3, 8, 10, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + output: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + options: [ + 2, + { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }, + ], + errors: expectedErrors([3, 12, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + { + try { + } + catch (err) { + } + finally { + } + } + `, + output: unIndent` + { + try { + } + catch (err) { + } + finally { + } + } + `, + errors: expectedErrors([ + [4, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 4, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + { + do { + } + while (true) + } + `, + output: unIndent` + { + do { + } + while (true) + } + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + function foo() { + return ( + 1 + ) + } + `, + output: unIndent` + function foo() { + return ( + 1 + ) + } + `, + options: [2], + errors: expectedErrors([[4, 2, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + function foo() { + return ( + 1 + ); + } + `, + output: unIndent` + function foo() { + return ( + 1 + ); + } + `, + options: [2], + errors: expectedErrors([[4, 2, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + output: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + function foo() { + return 1 + } + `, + output: unIndent` + function foo() { + return 1 + } + `, + options: [2], + errors: expectedErrors([[2, 2, 3, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + foo( + bar, + baz, + qux); + `, + output: unIndent` + foo( + bar, + baz, + qux); + `, + options: [2, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo( + \tbar, + \tbaz); + `, + output: unIndent` + foo( + bar, + baz); + `, + options: [2, { CallExpression: { arguments: 2 } }], + errors: expectedErrors([ + [2, '4 spaces', '1 tab', AST_TOKEN_TYPES.Identifier], + [3, '4 spaces', '1 tab', AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, + \t\tbaz, + \t\tqux); + `, + output: unIndent` + foo(bar, + \tbaz, + \tqux); + `, + options: ['tab', { CallExpression: { arguments: 1 } }], + errors: expectedErrors('tab', [ + [2, 1, 2, AST_TOKEN_TYPES.Identifier], + [3, 1, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, baz, + qux); + `, + output: unIndent` + foo(bar, baz, + qux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([2, 4, 9, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo( + bar, + baz); + `, + output: unIndent` + foo( + bar, + baz); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([ + [2, 2, 10, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + output: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + options: [2, { CallExpression: { arguments: 3 } }], + errors: expectedErrors([ + [2, 6, 2, AST_TOKEN_TYPES.Numeric], + [3, 6, 14, AST_TOKEN_TYPES.Punctuator], + [4, 6, 8, AST_TOKEN_TYPES.Keyword], + ]), + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` + return ( + foo + ); + `, + output: unIndent` + return ( + foo + ); + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + return ( + foo + ) + `, + output: unIndent` + return ( + foo + ) + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + + // https://github.com/eslint/eslint/issues/7604 + { + code: unIndent` + if (foo) { + /* comment */bar(); + } + `, + output: unIndent` + if (foo) { + /* comment */bar(); + } + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + foo('bar', + /** comment */{ + ok: true + }); + `, + output: unIndent` + foo('bar', + /** comment */{ + ok: true + }); + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + foo( + (bar) + ); + `, + output: unIndent` + foo( + (bar) + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + (( + foo + )) + `, + output: unIndent` + (( + foo + )) + `, + options: [4], + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` + foo + ? bar + : baz + `, + output: unIndent` + foo + ? bar + : baz + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Punctuator], + [3, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ + foo ? + bar : + baz, + qux + ] + `, + output: unIndent` + [ + foo ? + bar : + baz, + qux + ] + `, + errors: expectedErrors([5, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` + foo(); + // comment + /* multiline + comment */ + bar(); + // trailing comment + `, + output: unIndent` + foo(); + // comment + /* multiline + comment */ + bar(); + // trailing comment + `, + options: [2], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Line], + [3, 0, 4, AST_TOKEN_TYPES.Block], + [6, 0, 1, AST_TOKEN_TYPES.Line], + ]), + }, + { + code: ' // comment', + output: '// comment', + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + foo + // comment + `, + output: unIndent` + foo + // comment + `, + errors: expectedErrors([2, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + // comment + foo + `, + output: unIndent` + // comment + foo + `, + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + [ + // no elements + ] + `, + output: unIndent` + [ + // no elements + ] + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Line]), + }, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + output: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 2, 6, AST_TOKEN_TYPES.Identifier], + [6, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const { + a + } = { + a: 1 + } + `, + output: unIndent` + const { + a + } = { + a: 1 + } + `, + options: [2], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var foo = [ + bar, + baz + ] + `, + output: unIndent` + var foo = [ + bar, + baz + ] + `, + errors: expectedErrors([ + [2, 4, 11, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 0, 10, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 8 }], + errors: expectedErrors([ + [2, 16, 2, AST_TOKEN_TYPES.Identifier], + [3, 16, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + errors: expectedErrors([ + [2, 11, 4, AST_TOKEN_TYPES.Identifier], + [3, 11, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, qux + ] + `, + output: unIndent` + var foo = [bar, + baz, qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + errors: expectedErrors([2, 11, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + qux: 4 } + ] + `, + output: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + qux: 4 } + ] + `, + options: [4, { ArrayExpression: 2, ObjectExpression: 'first' }], + errors: expectedErrors([ + [3, 10, 12, AST_TOKEN_TYPES.Identifier], + [5, 10, 12, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + output: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + options: [2, { ObjectExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var quux = { foo: 1, bar: 2, + baz: 3 } + `, + output: unIndent` + var quux = { foo: 1, bar: 2, + baz: 3 } + `, + options: [2, { ObjectExpression: 'first' }], + errors: expectedErrors([2, 13, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + [ + foo + ] + } + `, + output: unIndent` + function foo() { + [ + foo + ] + } + `, + options: [2, { ArrayExpression: 4 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Punctuator], + [3, 10, 12, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + output: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 2, 6, AST_TOKEN_TYPES.Identifier], + [6, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + output: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + output: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 'first' }], + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([[3, 9, 10, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + output: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [2, { ImportDeclaration: 2 }], + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([[3, 4, 5, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + }; + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + }; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` + var folder = filePath + .foo() + .bar; + `, + output: unIndent` + var folder = filePath + .foo() + .bar; + `, + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Punctuator], + [3, 4, 6, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + for (const foo of bar) + baz(); + `, + output: unIndent` + for (const foo of bar) + baz(); + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var x = () => + 5; + `, + output: unIndent` + var x = () => + 5; + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Numeric]), + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && ( + bar + ) + `, + output: unIndent` + foo && ( + bar + ) + `, + options: [4], + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo && + !bar( + ) + `, + output: unIndent` + foo && + !bar( + ) + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + output: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ + ] || [ + ] + `, + output: unIndent` + [ + ] || [ + ] + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo + || ( + bar + ) + `, + output: unIndent` + foo + || ( + bar + ) + `, + errors: expectedErrors([ + [3, 12, 16, AST_TOKEN_TYPES.Identifier], + [4, 8, 12, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + 1 + + ( + 1 + ) + `, + output: unIndent` + 1 + + ( + 1 + ) + `, + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Numeric], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + + // Template curlies + { + code: unIndent` + \`foo\${ + bar}\` + `, + output: unIndent` + \`foo\${ + bar}\` + `, + options: [2], + errors: expectedErrors([2, 2, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + output: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Template], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + output: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Template], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Template], + [5, 0, 2, AST_TOKEN_TYPES.Template], + ]), + }, + { + code: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + output: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 2, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + output: unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + errors: expectedErrors([ + [3, 8, 0, AST_TOKEN_TYPES.Identifier], + [4, 8, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo() { + const template = \`the indentation of + a curly element in a \${ + node.type + } node is checked.\`; + } + `, + output: unIndent` + function foo() { + const template = \`the indentation of + a curly element in a \${ + node.type + } node is checked.\`; + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Template], + ]), + }, + { + code: unIndent` + function foo() { + const template = \`this time the + closing curly is at the end of the line \${ + foo} + so the spaces before this line aren't removed.\`; + } + `, + output: unIndent` + function foo() { + const template = \`this time the + closing curly is at the end of the line \${ + foo} + so the spaces before this line aren't removed.\`; + } + `, + errors: expectedErrors([4, 4, 12, AST_TOKEN_TYPES.Identifier]), + }, + { + /* + * https://github.com/eslint/eslint/issues/1801 + * Note: This issue also mentioned checking the indentation for the 2 below. However, + * this is intentionally ignored because everyone seems to have a different idea of how + * BinaryExpressions should be indented. + */ + code: unIndent` + if (true) { + a = ( + 1 + + 2); + } + `, + output: unIndent` + if (true) { + a = ( + 1 + + 2); + } + `, + errors: expectedErrors([3, 8, 0, AST_TOKEN_TYPES.Numeric]), + }, + { + // https://github.com/eslint/eslint/issues/3737 + code: unIndent` + if (true) { + for (;;) { + b(); + } + } + `, + output: unIndent` + if (true) { + for (;;) { + b(); + } + } + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Keyword], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + output: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + options: [4, { MemberExpression: 1, CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 15, AST_TOKEN_TYPES.String], + [5, 12, 14, AST_TOKEN_TYPES.Punctuator], + [6, 16, 14, AST_TOKEN_TYPES.Numeric], + [7, 16, 9, AST_TOKEN_TYPES.Numeric], + [8, 16, 35, AST_TOKEN_TYPES.Numeric], + [9, 12, 22, AST_TOKEN_TYPES.Punctuator], + [10, 8, 0, AST_TOKEN_TYPES.Punctuator], + [11, 0, 1, AST_TOKEN_TYPES.Punctuator], + ]), + }, + + // https://github.com/eslint/eslint/issues/7242 + { + code: unIndent` + var x = [ + [1], + [2] + ] + `, + output: unIndent` + var x = [ + [1], + [2] + ] + `, + errors: expectedErrors([ + [2, 4, 6, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + output: unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + errors: expectedErrors([ + [2, 4, 6, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + output: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + options: [ + 2, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + errors: expectedErrors([ + [2, 13, 12, AST_TOKEN_TYPES.Punctuator], + [3, 14, 13, AST_TOKEN_TYPES.String], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7522 + code: unIndent` + foo( + ) + `, + output: unIndent` + foo( + ) + `, + errors: expectedErrors([2, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + output: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([[2, 4, 8, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: ' new Foo', + output: 'new Foo', + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo + ? bar + : baz + `, + output: unIndent` + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo ? + bar : + baz + `, + output: unIndent` + foo ? + bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo ? + bar + : baz + `, + output: unIndent` + foo ? + bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo + ? bar : + baz + `, + output: unIndent` + foo + ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + output: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Punctuator], + [4, 4, 12, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + output: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 4, 12, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + output: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = + foo + ? bar + : baz + `, + output: unIndent` + var a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + output: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + output: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 12, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + output: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + [5, 8, 4, AST_TOKEN_TYPES.Punctuator], + [6, 12, 4, AST_TOKEN_TYPES.Punctuator], + [7, 12, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + output: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Identifier], + [5, 8, 4, AST_TOKEN_TYPES.Identifier], + [6, 12, 4, AST_TOKEN_TYPES.Identifier], + [7, 12, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + output: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([2, 2, 10, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + output: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + output: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + output: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + options: [2, { ArrayExpression: 'first', MemberExpression: 1 }], + errors: expectedErrors([ + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo[ + bar + ]; + `, + output: unIndent` + foo[ + bar + ]; + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo({ + bar: 1, + baz: 2 + }) + `, + output: unIndent` + foo({ + bar: 1, + baz: 2 + }) + `, + options: [4, { ObjectExpression: 'first' }], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo( + bar, baz, + qux); + `, + output: unIndent` + foo( + bar, baz, + qux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([ + [2, 2, 24, AST_TOKEN_TYPES.Identifier], + [3, 2, 24, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + output: unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + if (foo) + ; + `, + output: unIndent` + if (foo) + ; + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + import {foo} + from 'bar'; + `, + output: unIndent` + import {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + export {foo} + from 'bar'; + `, + output: unIndent` + export {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + ( + a + ) => b => { + c + } + `, + output: unIndent` + ( + a + ) => b => { + c + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + ( + a + ) => b => c => d => { + e + } + `, + output: unIndent` + ( + a + ) => b => c => d => { + e + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if ( + foo + ) bar( + baz + ); + `, + output: unIndent` + if ( + foo + ) bar( + baz + ); + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + ( + foo + )( + bar + ) + `, + output: unIndent` + ( + foo + )( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (() => + foo + )( + bar + ) + `, + output: unIndent` + (() => + foo + )( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (() => { + foo(); + })( + bar + ) + `, + output: unIndent` + (() => { + foo(); + })( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo. + bar. + baz + `, + output: unIndent` + foo. + bar. + baz + `, + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName + = (baz( + 'bar', + 'bar' + )); + `, + output: unIndent` + const foo = a.b(), + longName + = (baz( + 'bar', + 'bar' + )); + `, + errors: expectedErrors([ + [4, 8, 12, AST_TOKEN_TYPES.String], + [5, 8, 12, AST_TOKEN_TYPES.String], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + output: unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + errors: expectedErrors([ + [4, 8, 12, AST_TOKEN_TYPES.String], + [5, 8, 12, AST_TOKEN_TYPES.String], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName + =baz( + 'bar', + 'bar' + ); + `, + output: unIndent` + const foo = a.b(), + longName + =baz( + 'bar', + 'bar' + ); + `, + errors: expectedErrors([[6, 8, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + output: unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + errors: expectedErrors([[4, 12, 8, AST_TOKEN_TYPES.String]]), + }, + + //---------------------------------------------------------------------- + // JSX tests + // Some of the following tests are adapted from the the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + { + code: unIndent` + + + + `, + output: unIndent` + + + + `, + errors: expectedErrors([2, 4, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + `, + output: unIndent` + + + + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + `, + output: unIndent` + + \t + + `, + options: ['tab'], + errors: expectedErrors([ + 2, + '1 tab', + '4 spaces', + AST_TOKEN_TYPES.Punctuator, + ]), + }, + { + code: unIndent` + function App() { + return + + ; + } + `, + output: unIndent` + function App() { + return + + ; + } + `, + options: [2], + errors: expectedErrors([4, 2, 9, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function App() { + return ( + + ); + } + `, + output: unIndent` + function App() { + return ( + + ); + } + `, + options: [2], + errors: expectedErrors([4, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function App() { + return ( + + + + ); + } + `, + output: unIndent` + function App() { + return ( + + + + ); + } + `, + options: [2], + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Punctuator], + [4, 6, 2, AST_TOKEN_TYPES.Punctuator], + [5, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + + {test} + + `, + output: unIndent` + + {test} + + `, + errors: expectedErrors([2, 4, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + {options.map((option, index) => ( + + ))} + + `, + output: unIndent` + + {options.map((option, index) => ( + + ))} + + `, + errors: expectedErrors([4, 12, 11, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + [ +
, +
+ ] + `, + output: unIndent` + [ +
, +
+ ] + `, + options: [2], + errors: expectedErrors([3, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + + + `, + output: unIndent` + + + \t + + + `, + options: ['tab'], + errors: expectedErrors([ + 3, + '1 tab', + '1 space', + AST_TOKEN_TYPES.Punctuator, + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression) + */ + code: unIndent` + foo ? + : + + `, + output: unIndent` + foo ? + : + + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + /* + * Multiline ternary + * (colon on its own line) + */ + code: unIndent` + foo ? + + : + + `, + output: unIndent` + foo ? + + : + + `, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Punctuator], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression, parenthesized first expression) + */ + code: unIndent` + foo ? ( + + ) : + + `, + output: unIndent` + foo ? ( + + ) : + + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + errors: expectedErrors([2, 4, 2, AST_TOKEN_TYPES.JSXIdentifier]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + const Button = function(props) { + return ( + + ); + }; + `, + output: unIndent` + const Button = function(props) { + return ( + + ); + }; + `, + options: [2], + errors: expectedErrors([6, 4, 36, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = function() { + return + } + `, + output: unIndent` + var x = function() { + return + } + `, + options: [2], + errors: expectedErrors([4, 2, 9, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = + `, + output: unIndent` + var x = + `, + options: [2], + errors: expectedErrors([3, 0, 8, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = ( + + ) + `, + output: unIndent` + var x = ( + + ) + `, + options: [2], + errors: expectedErrors([3, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: ['tab'], + errors: expectedErrors('tab', [3, 0, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: ['tab'], + errors: expectedErrors('tab', [3, 0, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + < + foo + .bar + .baz + > + foo + + `, + output: unIndent` + < + foo + .bar + .baz + > + foo + + `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + [9, 8, 4, AST_TOKEN_TYPES.JSXIdentifier], + [10, 8, 4, AST_TOKEN_TYPES.JSXIdentifier], + ]), + }, + { + code: unIndent` + < + input + type= + "number" + /> + `, + output: unIndent` + < + input + type= + "number" + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.JSXText]), + }, + { + code: unIndent` + < + input + type= + {'number'} + /> + `, + output: unIndent` + < + input + type= + {'number'} + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + < + input + type + ="number" + /> + `, + output: unIndent` + < + input + type + ="number" + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + output: unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + output: unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + errors: expectedErrors([ + [5, 4, 8, AST_TOKEN_TYPES.Punctuator], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + [7, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` +
+ { + ( + 1 + ) + } +
+ `, + output: unIndent` +
+ { + ( + 1 + ) + } +
+ `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 8, AST_TOKEN_TYPES.Numeric], + [5, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` +
+ { + /* foo */ + } +
+ `, + output: unIndent` +
+ { + /* foo */ + } +
+ `, + errors: expectedErrors([3, 8, 6, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` +
foo +
bar
+
+ `, + output: unIndent` +
foo +
bar
+
+ `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + Foo bar  + baz qux. + + `, + output: unIndent` + Foo bar  + baz qux. + + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ({ + foo + }: bar) => baz + `, + output: unIndent` + ({ + foo + }: bar) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ([ + foo + ]: bar) => baz + `, + output: unIndent` + ([ + foo + ]: bar) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ({ + foo + }: {}) => baz + `, + output: unIndent` + ({ + foo + }: {}) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + output: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [4, { ignoredNodes: ['ClassBody'] }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + $(function() { + + foo(); + bar(); + + foo(function() { + baz(); + }); + + }); + `, + output: unIndent` + $(function() { + + foo(); + bar(); + + foo(function() { + baz(); + }); + + }); + `, + options: [ + 4, + { + ignoredNodes: [ + "ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + errors: expectedErrors([7, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + (function($) { + $(function() { + foo; + }); + })() + `, + output: unIndent` + (function($) { + $(function() { + foo; + }); + })() + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement', + ], + }, + ], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + output: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + options: [4, { ignoreComments: false }], + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + output: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + options: [4, { ignoreComments: false }], + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + output: unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + errors: expectedErrors([4, 12, 8, AST_TOKEN_TYPES.Numeric]), + }, + + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + { + code: unIndent` + if (foo) { + + // Comment cannot align with code immediately above if there is a whitespace gap + doSomething(); + } + `, + output: unIndent` + if (foo) { + + // Comment cannot align with code immediately above if there is a whitespace gap + doSomething(); + } + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + if (foo) { + foo( + bar); + // Comment cannot align with code immediately below if there is a whitespace gap + + } + `, + output: unIndent` + if (foo) { + foo( + bar); + // Comment cannot align with code immediately below if there is a whitespace gap + + } + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + output: unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + errors: expectedErrors([5, 0, 4, AST_TOKEN_TYPES.Line]), + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts similarity index 99% rename from packages/eslint-plugin/tests/rules/indent.test.ts rename to packages/eslint-plugin/tests/rules/indent/indent.test.ts index 7fb18428d9f..647cbf753fc 100644 --- a/packages/eslint-plugin/tests/rules/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -1,9 +1,9 @@ -import rule from '../../src/rules/indent'; -import { RuleTester, RunTests, TestCaseError } from '../RuleTester'; +import rule from '../../../src/rules/indent'; +import { RuleTester, RunTests, TestCaseError } from '../../RuleTester'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, -} from '../../src/util'; +} from '../../../src/util'; type MessageIds = InferMessageIdsTypeFromRule; type Options = InferOptionsTypeFromRule; diff --git a/packages/eslint-plugin/tests/rules/indent/utils.ts b/packages/eslint-plugin/tests/rules/indent/utils.ts new file mode 100644 index 00000000000..c4bf28c08fb --- /dev/null +++ b/packages/eslint-plugin/tests/rules/indent/utils.ts @@ -0,0 +1,103 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; +import { TestCaseError } from '../../RuleTester'; +import rule from '../../../src/rules/indent'; +import { InferMessageIdsTypeFromRule } from '../../../src/util'; + +type MessageIds = InferMessageIdsTypeFromRule; + +/** + * Prevents leading spaces in a multiline template literal from appearing in the resulting string + * @param strings The strings in the template literal + * @returns The template literal, with spaces removed from all lines + */ +export function unIndent(strings: TemplateStringsArray): string { + const templateValue = strings[0]; + const lines = templateValue + .replace(/^\n/u, '') + .replace(/\n\s*$/u, '') + .split('\n'); + const lineIndents = lines + .filter(line => line.trim()) + .map(line => line.match(/ */u)![0].length); + const minLineIndent = Math.min(...lineIndents); + + return lines.map(line => line.slice(minLineIndent)).join('\n'); +} + +type ProvidedError = [ + // line number + number, + // expected indent + number | string, + // actual indent + number | string, + // node type + AST_NODE_TYPES | AST_TOKEN_TYPES +]; + +function is2DProvidedErrorArr( + providedErrors?: ProvidedError | ProvidedError[], +): providedErrors is ProvidedError[] { + return !!providedErrors && Array.isArray(providedErrors[0]); +} + +/** + * Create error message object for failure cases with a single 'found' indentation type + * @param providedErrors error info + * @returns returns the error messages collection + */ +export function expectedErrors( + providedErrors: ProvidedError | ProvidedError[], +): TestCaseError[]; +/** + * Create error message object for failure cases with a single 'found' indentation type + * @param providedIndentType indent type of string or tab + * @param providedErrors error info + * @returns returns the error messages collection + */ +export function expectedErrors( + providedIndentType: string, + providedErrors: ProvidedError | ProvidedError[], +): TestCaseError[]; +export function expectedErrors( + providedIndentType: string | ProvidedError | ProvidedError[], + providedErrors?: ProvidedError | ProvidedError[], +): TestCaseError[] { + let indentType: string; + let errors: ProvidedError[]; + + if (Array.isArray(providedIndentType)) { + errors = is2DProvidedErrorArr(providedIndentType) + ? providedIndentType + : [providedIndentType]; + indentType = 'space'; + } else { + errors = is2DProvidedErrorArr(providedErrors) + ? providedErrors + : [providedErrors!]; + indentType = providedIndentType; + } + + return errors.map(err => { + const [line, expected, actual, type] = err; + + return { + messageId: 'wrongIndentation', + data: { + expected: + typeof expected === 'string' && typeof actual === 'string' + ? expected + : `${expected} ${indentType}${expected === 1 ? '' : 's'}`, + actual, + }, + type, + line, + }; + }); +} diff --git a/packages/eslint-plugin/typings/eslint-ast-util.d.ts b/packages/eslint-plugin/typings/eslint-ast-util.d.ts new file mode 100644 index 00000000000..6a144813a49 --- /dev/null +++ b/packages/eslint-plugin/typings/eslint-ast-util.d.ts @@ -0,0 +1,3 @@ +declare module 'eslint/lib/util/ast-utils' { + export function createGlobalLinebreakMatcher(): RegExp; +} diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index cf3693453f1..7ea46da7c0b 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -56,8 +56,8 @@ declare module 'eslint/lib/rules/indent' { const rule: RuleModule< 'wrongIndentation', [ - 'tab' | number, - { + ('tab' | number)?, + ({ SwitchCase?: number; VariableDeclarator?: | ElementList @@ -85,7 +85,7 @@ declare module 'eslint/lib/rules/indent' { flatTernaryExpressions?: boolean; ignoredNodes?: string[]; ignoreComments?: boolean; - } + })? ], { '*:exit'(node: TSESTree.Node): void; diff --git a/packages/eslint-plugin/typings/functional-red-black-tree.d.ts b/packages/eslint-plugin/typings/functional-red-black-tree.d.ts new file mode 100644 index 00000000000..7066de81fb3 --- /dev/null +++ b/packages/eslint-plugin/typings/functional-red-black-tree.d.ts @@ -0,0 +1,55 @@ +declare module 'functional-red-black-tree' { + class RBNode { + public readonly key: TKey; + public readonly left: RBNode; + public readonly right: RBNode; + public readonly value: TValue; + } + + class RedBlackTreeIterator { + public readonly hasNext: boolean; + public readonly hasPrev: boolean; + public readonly index: number; + public readonly key: TKey; + public readonly node: RBNode | null; + public readonly tree: RBTree; + public readonly valid: boolean; + public readonly value: TValue; + + public clone(): RedBlackTreeIterator; + public remove(): RBTree; + public update(value: TValue): RBTree; + public next(): void; + public prev(): void; + } + + class RBTree { + public begin: RedBlackTreeIterator; + public end: RedBlackTreeIterator; + public readonly keys: TKey[]; + public readonly length: number; + public root: RBNode | null; + public readonly values: TValue[]; + + public get(key: TKey): TValue; + public insert(key: TKey, value: TValue): RBTree; + public remove(key: TKey): this; + public find(key: TKey): RedBlackTreeIterator; + public forEach( + visitor: (key: TKey, value: TValue) => void, + low: TKey, + high: TKey, + ): void; + + public ge(key: TKey): RedBlackTreeIterator; + public gt(key: TKey): RedBlackTreeIterator; + public le(key: TKey): RedBlackTreeIterator; + public lt(key: TKey): RedBlackTreeIterator; + public at(position: number): RedBlackTreeIterator; + } + + function createRBTree( + compare?: (a: TKey, b: TKey) => number, + ): RBTree; + export = createRBTree; +} diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts index e32069feccf..62ef79199a2 100644 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ b/packages/eslint-plugin/typings/ts-eslint.d.ts @@ -10,15 +10,22 @@ The def is wrapped up in a fake module so that it can be used in eslint-rules.d. declare module 'ts-eslint' { import { TSESTree } from '@typescript-eslint/typescript-estree'; import { ParserServices } from '@typescript-eslint/parser'; - import { AST, Linter, Rule } from 'eslint'; + import { Linter } from 'eslint'; import { JSONSchema4 } from 'json-schema'; //#region SourceCode + interface Program extends TSESTree.Program { + comments: TSESTree.Comment[]; + tokens: TSESTree.Token[]; + loc: TSESTree.SourceLocation; + range: TSESTree.Range; + } + namespace SourceCode { export interface Config { text: string; - ast: AST.Program; + ast: Program; parserServices?: ParserServices; scopeManager?: Scope.ScopeManager; visitorKeys?: VisitorKeys; @@ -53,15 +60,15 @@ declare module 'ts-eslint' { class SourceCode { text: string; - ast: AST.Program; + ast: Program; lines: string[]; hasBOM: boolean; parserServices: ParserServices; scopeManager: Scope.ScopeManager; visitorKeys: SourceCode.VisitorKeys; + tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; - constructor(text: string, ast: AST.Program); - // eslint-disable-next-line no-dupe-class-members + constructor(text: string, ast: Program); constructor(config: SourceCode.Config); static splitLines(text: string): string[]; @@ -179,7 +186,6 @@ declare module 'ts-eslint' { beforeCount?: number, afterCount?: number, ): TSESTree.Token[]; - // eslint-disable-next-line no-dupe-class-members getTokens( node: TSESTree.Node, options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions, @@ -270,7 +276,7 @@ declare module 'ts-eslint' { } interface RuleFix { - range: AST.Range; + range: TSESTree.Range; text: string; } @@ -280,25 +286,25 @@ declare module 'ts-eslint' { text: string, ): RuleFix; - insertTextAfterRange(range: AST.Range, text: string): RuleFix; + insertTextAfterRange(range: TSESTree.Range, text: string): RuleFix; insertTextBefore( nodeOrToken: TSESTree.Node | TSESTree.Token, text: string, ): RuleFix; - insertTextBeforeRange(range: AST.Range, text: string): RuleFix; + insertTextBeforeRange(range: TSESTree.Range, text: string): RuleFix; remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix; - removeRange(range: AST.Range): RuleFix; + removeRange(range: TSESTree.Range): RuleFix; replaceText( nodeOrToken: TSESTree.Node | TSESTree.Token, text: string, ): RuleFix; - replaceTextRange(range: AST.Range, text: string): RuleFix; + replaceTextRange(range: TSESTree.Range, text: string): RuleFix; } type ReportFixFunction = ( @@ -411,6 +417,7 @@ declare module 'ts-eslint' { ArrayPattern?: RuleFunction; ArrowFunctionExpression?: RuleFunction; AssignmentPattern?: RuleFunction; + AssignmentExpression?: RuleFunction; AwaitExpression?: RuleFunction; BlockStatement?: RuleFunction; BreakStatement?: RuleFunction; diff --git a/packages/typescript-estree/src/ts-estree/ast-node-types.ts b/packages/typescript-estree/src/ts-estree/ast-node-types.ts index a1e06027639..4ae1b5dcf86 100644 --- a/packages/typescript-estree/src/ts-estree/ast-node-types.ts +++ b/packages/typescript-estree/src/ts-estree/ast-node-types.ts @@ -175,4 +175,8 @@ export enum AST_TOKEN_TYPES { RegularExpression = 'RegularExpression', String = 'String', Template = 'Template', + + // comment types + Block = 'Block', + Line = 'Line', } diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index da1523fabc5..7043cafa046 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -20,6 +20,7 @@ export interface SourceLocation { */ end: LineAndColumnData; } +export type Range = [number, number]; export interface BaseNode { /** @@ -31,7 +32,7 @@ export interface BaseNode { * Both numbers are a 0-based index which is the position in the array of source code characters. * The first is the start position of the node, the second is the end position of the node. */ - range: [number, number]; + range: Range; /** * The parent node of the current node */