diff --git a/packages/ast-spec/src/unions/LeftHandSideExpression.ts b/packages/ast-spec/src/unions/LeftHandSideExpression.ts index 5d6922ed42d..fc3a80b2361 100644 --- a/packages/ast-spec/src/unions/LeftHandSideExpression.ts +++ b/packages/ast-spec/src/unions/LeftHandSideExpression.ts @@ -9,6 +9,7 @@ import type { JSXFragment } from '../expression/JSXFragment/spec'; import type { MemberExpression } from '../expression/MemberExpression/spec'; import type { MetaProperty } from '../expression/MetaProperty/spec'; import type { ObjectExpression } from '../expression/ObjectExpression/spec'; +import type { SequenceExpression } from '../expression/spec'; import type { Super } from '../expression/Super/spec'; import type { TaggedTemplateExpression } from '../expression/TaggedTemplateExpression/spec'; import type { ThisExpression } from '../expression/ThisExpression/spec'; @@ -34,6 +35,7 @@ export type LeftHandSideExpression = | MetaProperty | ObjectExpression | ObjectPattern + | SequenceExpression | Super | TaggedTemplateExpression | ThisExpression diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 6f890d27256..a60d885093f 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -190,43 +190,44 @@ In these cases, we create what we call an extension rule; a rule within our plug **Key**: :white_check_mark: = recommended, :wrench: = fixable, :thought_balloon: = requires type information -| Name | Description | :white_check_mark: | :wrench: | :thought_balloon: | -| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------ | -------- | ----------------- | -| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | | -| [`@typescript-eslint/comma-dangle`](./docs/rules/comma-dangle.md) | Require or disallow trailing comma | | :wrench: | | -| [`@typescript-eslint/comma-spacing`](./docs/rules/comma-spacing.md) | Enforces consistent spacing before and after commas | | :wrench: | | -| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | | -| [`@typescript-eslint/dot-notation`](./docs/rules/dot-notation.md) | enforce dot notation whenever possible | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | -| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | | -| [`@typescript-eslint/init-declarations`](./docs/rules/init-declarations.md) | require or disallow initialization in variable declarations | | | | -| [`@typescript-eslint/keyword-spacing`](./docs/rules/keyword-spacing.md) | Enforce consistent spacing before and after keywords | | :wrench: | | -| [`@typescript-eslint/lines-between-class-members`](./docs/rules/lines-between-class-members.md) | Require or disallow an empty line between class members | | :wrench: | | -| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :white_check_mark: | :wrench: | | -| [`@typescript-eslint/no-dupe-class-members`](./docs/rules/no-dupe-class-members.md) | Disallow duplicate class members | | | | -| [`@typescript-eslint/no-duplicate-imports`](./docs/rules/no-duplicate-imports.md) | Disallow duplicate imports | | | | -| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :white_check_mark: | | | -| [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | -| [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | :white_check_mark: | :wrench: | | -| [`@typescript-eslint/no-implied-eval`](./docs/rules/no-implied-eval.md) | Disallow the use of `eval()`-like methods | :white_check_mark: | | :thought_balloon: | -| [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | Disallow `this` keywords outside of classes or class-like objects | | | | -| [`@typescript-eslint/no-loop-func`](./docs/rules/no-loop-func.md) | Disallow function declarations that contain unsafe references inside loop statements | | | | -| [`@typescript-eslint/no-loss-of-precision`](./docs/rules/no-loss-of-precision.md) | Disallow literal numbers that lose precision | | | | -| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | | -| [`@typescript-eslint/no-redeclare`](./docs/rules/no-redeclare.md) | Disallow variable redeclaration | | | | -| [`@typescript-eslint/no-shadow`](./docs/rules/no-shadow.md) | Disallow variable declarations from shadowing variables declared in the outer scope | | | | -| [`@typescript-eslint/no-throw-literal`](./docs/rules/no-throw-literal.md) | Disallow throwing literals as exceptions | | | :thought_balloon: | -| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | | -| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :white_check_mark: | | | -| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | | -| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | | -| [`@typescript-eslint/object-curly-spacing`](./docs/rules/object-curly-spacing.md) | Enforce consistent spacing inside braces | | :wrench: | | -| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | | -| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :white_check_mark: | | :thought_balloon: | -| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | -| [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | | -| [`@typescript-eslint/space-infix-ops`](./docs/rules/space-infix-ops.md) | This rule is aimed at ensuring there are spaces around infix operators. | | :wrench: | | +| Name | Description | :white_check_mark: | :wrench: | :thought_balloon: | +| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------ | -------- | ----------------- | +| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | | +| [`@typescript-eslint/comma-dangle`](./docs/rules/comma-dangle.md) | Require or disallow trailing comma | | :wrench: | | +| [`@typescript-eslint/comma-spacing`](./docs/rules/comma-spacing.md) | Enforces consistent spacing before and after commas | | :wrench: | | +| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | | +| [`@typescript-eslint/dot-notation`](./docs/rules/dot-notation.md) | enforce dot notation whenever possible | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | +| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | | +| [`@typescript-eslint/init-declarations`](./docs/rules/init-declarations.md) | require or disallow initialization in variable declarations | | | | +| [`@typescript-eslint/keyword-spacing`](./docs/rules/keyword-spacing.md) | Enforce consistent spacing before and after keywords | | :wrench: | | +| [`@typescript-eslint/lines-between-class-members`](./docs/rules/lines-between-class-members.md) | Require or disallow an empty line between class members | | :wrench: | | +| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :white_check_mark: | :wrench: | | +| [`@typescript-eslint/no-dupe-class-members`](./docs/rules/no-dupe-class-members.md) | Disallow duplicate class members | | | | +| [`@typescript-eslint/no-duplicate-imports`](./docs/rules/no-duplicate-imports.md) | Disallow duplicate imports | | | | +| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :white_check_mark: | | | +| [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | +| [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | :white_check_mark: | :wrench: | | +| [`@typescript-eslint/no-implied-eval`](./docs/rules/no-implied-eval.md) | Disallow the use of `eval()`-like methods | :white_check_mark: | | :thought_balloon: | +| [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | Disallow `this` keywords outside of classes or class-like objects | | | | +| [`@typescript-eslint/no-loop-func`](./docs/rules/no-loop-func.md) | Disallow function declarations that contain unsafe references inside loop statements | | | | +| [`@typescript-eslint/no-loss-of-precision`](./docs/rules/no-loss-of-precision.md) | Disallow literal numbers that lose precision | | | | +| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | | +| [`@typescript-eslint/no-redeclare`](./docs/rules/no-redeclare.md) | Disallow variable redeclaration | | | | +| [`@typescript-eslint/no-shadow`](./docs/rules/no-shadow.md) | Disallow variable declarations from shadowing variables declared in the outer scope | | | | +| [`@typescript-eslint/no-throw-literal`](./docs/rules/no-throw-literal.md) | Disallow throwing literals as exceptions | | | :thought_balloon: | +| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | | +| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :white_check_mark: | | | +| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | | +| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | | +| [`@typescript-eslint/object-curly-spacing`](./docs/rules/object-curly-spacing.md) | Enforce consistent spacing inside braces | | :wrench: | | +| [`@typescript-eslint/padding-line-between-statements`](./docs/rules/padding-line-between-statements.md) | require or disallow padding lines between statements | | :wrench: | | +| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | | +| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :white_check_mark: | | :thought_balloon: | +| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | +| [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | | +| [`@typescript-eslint/space-infix-ops`](./docs/rules/space-infix-ops.md) | This rule is aimed at ensuring there are spaces around infix operators. | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/padding-line-between-statements.md b/packages/eslint-plugin/docs/rules/padding-line-between-statements.md new file mode 100644 index 00000000000..b23ce702b78 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/padding-line-between-statements.md @@ -0,0 +1,48 @@ +# require or disallow padding lines between statements (`padding-line-between-statements`) + +## Rule Details + +This rule extends the base [`eslint/padding-line-between-statements`](https://eslint.org/docs/rules/padding-line-between-statements) rule. + +**It adds support for TypeScript constructs such as `interface` and `type`.** + +## How to use + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "padding-line-between-statements": "off", + "@typescript-eslint/padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "var", + "next": "return" + } + ] +} +``` + +```jsonc +{ + // Example - Add blank lines before interface and type definitions. + // note you must disable the base rule as it can report incorrect errors + "padding-line-between-statements": "off", + "@typescript-eslint/padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "*", + "next": ["interface", "type"] + } + ] +} +``` + +## Options + +See [`eslint/padding-line-between-statements` options](https://eslint.org/docs/rules/padding-line-between-statements#options). + +**Note** - In addition to options provided by ESLint, we have also added options for `interface` and `type`. + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/padding-line-between-statements.md) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 66209b80623..4d94a83fa59 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -116,6 +116,8 @@ export = { '@typescript-eslint/non-nullable-type-assertion-style': 'error', 'object-curly-spacing': 'off', '@typescript-eslint/object-curly-spacing': 'error', + 'padding-line-between-statements': 'off', + '@typescript-eslint/padding-line-between-statements': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/prefer-for-of': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 1b6fa0f6ea1..54dd9c24ac3 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -81,6 +81,7 @@ import noUselessConstructor from './no-useless-constructor'; import noVarRequires from './no-var-requires'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; import objectCurlySpacing from './object-curly-spacing'; +import paddingLineBetweenStatements from './padding-line-between-statements'; import preferAsConst from './prefer-as-const'; import preferEnumInitializers from './prefer-enum-initializers'; import preferForOf from './prefer-for-of'; @@ -200,6 +201,7 @@ export default { 'no-var-requires': noVarRequires, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, 'object-curly-spacing': objectCurlySpacing, + 'padding-line-between-statements': paddingLineBetweenStatements, 'prefer-as-const': preferAsConst, 'prefer-enum-initializers': preferEnumInitializers, 'prefer-for-of': preferForOf, diff --git a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts new file mode 100644 index 00000000000..f686476e5de --- /dev/null +++ b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts @@ -0,0 +1,775 @@ +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +/** + * This rule is a replica of padding-line-between-statements. + * + * Ideally we would want to extend the rule support typescript specific support. + * But since not all the state is exposed by the eslint and eslint has frozen stylistic rules, + * (see - https://eslint.org/blog/2020/05/changes-to-rules-policies for details.) + * we are forced to re-implement the rule here. + * + * We have tried to keep the implementation as close as possible to the eslint implementation, to make + * patching easier for future contributors. + * + * Reference rule - https://github.com/eslint/eslint/blob/master/lib/rules/padding-line-between-statements.js + */ + +type NodeTest = ( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +) => boolean; + +interface NodeTestObject { + test: NodeTest; +} + +interface PaddingOption { + blankLine: keyof typeof PaddingTypes; + prev: string | string[]; + next: string | string[]; +} + +type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; +type Options = PaddingOption[]; + +const LT = `[${Array.from( + new Set(['\r\n', '\r', '\n', '\u2028', '\u2029']), +).join('')}]`; +const PADDING_LINE_SEQUENCE = new RegExp( + String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, + 'u', +); + +/** + * Creates tester which check if a node starts with specific keyword. + * @param keyword The keyword to test. + * @returns the created tester. + * @private + */ +function newKeywordTester(keyword: string): NodeTestObject { + return { + test(node, sourceCode): boolean { + return sourceCode.getFirstToken(node)?.value === keyword; + }, + }; +} + +/** + * Creates tester which check if a node starts with specific keyword and spans a single line. + * @param keyword The keyword to test. + * @returns the created tester. + * @private + */ +function newSinglelineKeywordTester(keyword: string): NodeTestObject { + return { + test(node, sourceCode): boolean { + return ( + node.loc.start.line === node.loc.end.line && + sourceCode.getFirstToken(node)!.value === keyword + ); + }, + }; +} + +/** + * Creates tester which check if a node starts with specific keyword and spans multiple lines. + * @param keyword The keyword to test. + * @returns the created tester. + * @private + */ +function newMultilineKeywordTester(keyword: string): NodeTestObject { + return { + test(node, sourceCode): boolean { + return ( + node.loc.start.line !== node.loc.end.line && + sourceCode.getFirstToken(node)!.value === keyword + ); + }, + }; +} + +/** + * Creates tester which check if a node is specific type. + * @param type The node type to test. + * @returns the created tester. + * @private + */ +function newNodeTypeTester(type: AST_NODE_TYPES): NodeTestObject { + return { + test: (node): boolean => node.type === type, + }; +} + +/** + * Skips a chain expression node + * @paramnode The node to test + * @returnsA non-chain expression + * @private + */ +function skipChainExpression(node: TSESTree.Node): TSESTree.Node { + return node && node.type === AST_NODE_TYPES.ChainExpression + ? node.expression + : node; +} + +/** + * Checks the given node is an expression statement of IIFE. + * @paramnode The node to check. + * @returns `true` if the node is an expression statement of IIFE. + * @private + */ +function isIIFEStatement(node: TSESTree.Node): boolean { + if (node.type === AST_NODE_TYPES.ExpressionStatement) { + let expression = skipChainExpression(node.expression); + if (expression.type === AST_NODE_TYPES.UnaryExpression) { + expression = skipChainExpression(expression.argument); + } + if (expression.type === AST_NODE_TYPES.CallExpression) { + let node: TSESTree.Node = expression.callee; + while (node.type === AST_NODE_TYPES.SequenceExpression) { + node = node.expressions[node.expressions.length - 1]; + } + return util.isFunction(node); + } + } + return false; +} + +/** + * Checks the given node is a CommonJS require statement + * @paramnode The node to check. + * @returns `true` if the node is a CommonJS require statement. + * @private + */ +function isCJSRequire(node: TSESTree.Node): boolean { + if (node.type === AST_NODE_TYPES.VariableDeclaration) { + const declaration = node.declarations[0]; + if (declaration?.init) { + let call = declaration?.init; + while (call.type === AST_NODE_TYPES.MemberExpression) { + call = call.object; + } + if ( + call.type === AST_NODE_TYPES.CallExpression && + call.callee.type === AST_NODE_TYPES.Identifier + ) { + return call.callee.name === 'require'; + } + } + } + return false; +} + +/** + * Checks whether the given node is a block-like statement. + * This checks the last token of the node is the closing brace of a block. + * @param sourceCode The source code to get tokens. + * @paramnode The node to check. + * @returns `true` if the node is a block-like statement. + * @private + */ +function isBlockLikeStatement( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): boolean { + // do-while with a block is a block-like statement. + if ( + node.type === AST_NODE_TYPES.DoWhileStatement && + node.body.type === AST_NODE_TYPES.BlockStatement + ) { + return true; + } + + /** + * IIFE is a block-like statement specially from + * JSCS#disallowPaddingNewLinesAfterBlocks. + */ + if (isIIFEStatement(node)) { + return true; + } + + // Checks the last token is a closing brace of blocks. + const lastToken = sourceCode.getLastToken(node, util.isNotSemicolonToken); + const belongingNode = + lastToken && util.isClosingBraceToken(lastToken) + ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) + : null; + + return ( + !!belongingNode && + (belongingNode.type === AST_NODE_TYPES.BlockStatement || + belongingNode.type === AST_NODE_TYPES.SwitchStatement) + ); +} + +/** + * Check whether the given node is a directive or not. + * @paramnode The node to check. + * @param sourceCode The source code object to get tokens. + * @returns `true` if the node is a directive. + */ +function isDirective( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): boolean { + return ( + node.type === AST_NODE_TYPES.ExpressionStatement && + (node.parent?.type === AST_NODE_TYPES.Program || + (node.parent?.type === AST_NODE_TYPES.BlockStatement && + util.isFunction(node.parent.parent))) && + node.expression.type === AST_NODE_TYPES.Literal && + typeof node.expression.value === 'string' && + !util.isParenthesized(node.expression, sourceCode) + ); +} + +/** + * Check whether the given node is a part of directive prologue or not. + * @paramnode The node to check. + * @param sourceCode The source code object to get tokens. + * @returns `true` if the node is a part of directive prologue. + */ +function isDirectivePrologue( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): boolean { + if ( + isDirective(node, sourceCode) && + node.parent && + 'body' in node.parent && + Array.isArray(node.parent.body) + ) { + for (const sibling of node.parent.body) { + if (sibling === node) { + break; + } + if (!isDirective(sibling, sourceCode)) { + return false; + } + } + return true; + } + return false; +} + +/** + * Checks the given node is a CommonJS export statement + * @paramnode The node to check. + * @returns `true` if the node is a CommonJS export statement. + * @private + */ +function isCJSExport(node: TSESTree.Node): boolean { + if (node.type === AST_NODE_TYPES.ExpressionStatement) { + const expression = node.expression; + if (expression.type === AST_NODE_TYPES.AssignmentExpression) { + let left = expression.left; + if (left.type === AST_NODE_TYPES.MemberExpression) { + while (left.object.type === AST_NODE_TYPES.MemberExpression) { + left = left.object; + } + return ( + left.object.type === AST_NODE_TYPES.Identifier && + (left.object.name === 'exports' || + (left.object.name === 'module' && + left.property.type === AST_NODE_TYPES.Identifier && + left.property.name === 'exports')) + ); + } + } + } + return false; +} + +/** + * Check whether the given node is an expression + * @paramnode The node to check. + * @param sourceCode The source code object to get tokens. + * @returns `true` if the node is an expression + */ +function isExpression( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): boolean { + return ( + node.type === AST_NODE_TYPES.ExpressionStatement && + !isDirectivePrologue(node, sourceCode) + ); +} + +/** + * Gets the actual last token. + * + * If a semicolon is semicolon-less style's semicolon, this ignores it. + * For example: + * + * foo() + * ;[1, 2, 3].forEach(bar) + * @param sourceCode The source code to get tokens. + * @paramnode The node to get. + * @returns The actual last token. + * @private + */ +function getActualLastToken( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): TSESTree.Token | null { + const semiToken = sourceCode.getLastToken(node)!; + const prevToken = sourceCode.getTokenBefore(semiToken); + const nextToken = sourceCode.getTokenAfter(semiToken); + const isSemicolonLessStyle = + prevToken && + nextToken && + prevToken.range[0] >= node.range[0] && + util.isSemicolonToken(semiToken) && + semiToken.loc.start.line !== prevToken.loc.end.line && + semiToken.loc.end.line === nextToken.loc.start.line; + + return isSemicolonLessStyle ? prevToken : semiToken; +} + +/** + * This returns the concatenation of the first 2 captured strings. + * @param _ Unused. Whole matched string. + * @param trailingSpaces The trailing spaces of the first line. + * @param indentSpaces The indentation spaces of the last line. + * @returns The concatenation of trailingSpaces and indentSpaces. + * @private + */ +function replacerToRemovePaddingLines( + _: string, + trailingSpaces: string, + indentSpaces: string, +): string { + return trailingSpaces + indentSpaces; +} + +/** + * Check and report statements for `any` configuration. + * It does nothing. + * + * @private + */ +function verifyForAny(): void { + // Empty +} + +/** + * Check and report statements for `never` configuration. + * This autofix removes blank lines between the given 2 statements. + * However, if comments exist between 2 blank lines, it does not remove those + * blank lines automatically. + * @param context The rule context to report. + * @param_ Unused. The previous node to check. + * @paramnextNode The next node to check. + * @param paddingLines The array of token pairs that blank + * lines exist between the pair. + * + * @private + */ +function verifyForNever( + context: TSESLint.RuleContext, + _: TSESTree.Node, + nextNode: TSESTree.Node, + paddingLines: [TSESTree.Token, TSESTree.Token][], +): void { + if (paddingLines.length === 0) { + return; + } + + context.report({ + node: nextNode, + messageId: 'unexpectedBlankLine', + fix(fixer) { + if (paddingLines.length >= 2) { + return null; + } + + const prevToken = paddingLines[0][0]; + const nextToken = paddingLines[0][1]; + const start = prevToken.range[1]; + const end = nextToken.range[0]; + const text = context + .getSourceCode() + .text.slice(start, end) + .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); + + return fixer.replaceTextRange([start, end], text); + }, + }); +} + +/** + * Check and report statements for `always` configuration. + * This autofix inserts a blank line between the given 2 statements. + * If the `prevNode` has trailing comments, it inserts a blank line after the + * trailing comments. + * @param context The rule context to report. + * @paramprevNode The previous node to check. + * @paramnextNode The next node to check. + * @param paddingLines The array of token pairs that blank + * lines exist between the pair. + * + * @private + */ +function verifyForAlways( + context: TSESLint.RuleContext, + prevNode: TSESTree.Node, + nextNode: TSESTree.Node, + paddingLines: [TSESTree.Token, TSESTree.Token][], +): void { + if (paddingLines.length > 0) { + return; + } + + context.report({ + node: nextNode, + messageId: 'expectedBlankLine', + fix(fixer) { + const sourceCode = context.getSourceCode(); + let prevToken = getActualLastToken( + prevNode, + sourceCode, + ) as TSESTree.Token; + const nextToken = + (sourceCode.getFirstTokenBetween(prevToken, nextNode, { + includeComments: true, + + /** + * Skip the trailing comments of the previous node. + * This inserts a blank line after the last trailing comment. + * + * For example: + * + * foo(); // trailing comment. + * // comment. + * bar(); + * + * Get fixed to: + * + * foo(); // trailing comment. + * + * // comment. + * bar(); + * @param token The token to check. + * @returns `true` if the token is not a trailing comment. + * @private + */ + filter(token) { + if (util.isTokenOnSameLine(prevToken, token)) { + prevToken = token; + return false; + } + return true; + }, + }) as TSESTree.Token) || nextNode; + const insertText = util.isTokenOnSameLine(prevToken, nextToken) + ? '\n\n' + : '\n'; + + return fixer.insertTextAfter(prevToken, insertText); + }, + }); +} + +/** + * Types of blank lines. + * `any`, `never`, and `always` are defined. + * Those have `verify` method to check and report statements. + * @private + */ +const PaddingTypes = { + any: { verify: verifyForAny }, + never: { verify: verifyForNever }, + always: { verify: verifyForAlways }, +}; + +/** + * Types of statements. + * Those have `test` method to check it matches to the given statement. + * @private + */ +const StatementTypes: Record = { + '*': { test: (): boolean => true }, + 'block-like': { test: isBlockLikeStatement }, + exports: { test: isCJSExport }, + require: { test: isCJSRequire }, + directive: { test: isDirectivePrologue }, + expression: { test: isExpression }, + iife: { test: isIIFEStatement }, + + 'multiline-block-like': { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + isBlockLikeStatement(node, sourceCode), + }, + 'multiline-expression': { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + node.type === AST_NODE_TYPES.ExpressionStatement && + !isDirectivePrologue(node, sourceCode), + }, + + 'multiline-const': newMultilineKeywordTester('const'), + 'multiline-let': newMultilineKeywordTester('let'), + 'multiline-var': newMultilineKeywordTester('var'), + 'singleline-const': newSinglelineKeywordTester('const'), + 'singleline-let': newSinglelineKeywordTester('let'), + 'singleline-var': newSinglelineKeywordTester('var'), + + block: newNodeTypeTester(AST_NODE_TYPES.BlockStatement), + empty: newNodeTypeTester(AST_NODE_TYPES.EmptyStatement), + function: newNodeTypeTester(AST_NODE_TYPES.FunctionDeclaration), + + break: newKeywordTester('break'), + case: newKeywordTester('case'), + class: newKeywordTester('class'), + const: newKeywordTester('const'), + continue: newKeywordTester('continue'), + debugger: newKeywordTester('debugger'), + default: newKeywordTester('default'), + do: newKeywordTester('do'), + export: newKeywordTester('export'), + for: newKeywordTester('for'), + if: newKeywordTester('if'), + import: newKeywordTester('import'), + let: newKeywordTester('let'), + return: newKeywordTester('return'), + switch: newKeywordTester('switch'), + throw: newKeywordTester('throw'), + try: newKeywordTester('try'), + var: newKeywordTester('var'), + while: newKeywordTester('while'), + with: newKeywordTester('with'), + + // Additional Typescript constructs + interface: newKeywordTester('interface'), + type: newKeywordTester('type'), +}; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +export default util.createRule({ + name: 'padding-line-between-statements', + meta: { + type: 'layout', + docs: { + description: 'require or disallow padding lines between statements', + category: 'Stylistic Issues', + recommended: false, + extendsBaseRule: true, + }, + fixable: 'whitespace', + schema: { + definitions: { + paddingType: { + enum: Object.keys(PaddingTypes), + }, + statementType: { + anyOf: [ + { enum: Object.keys(StatementTypes) }, + { + type: 'array', + items: { enum: Object.keys(StatementTypes) }, + minItems: 1, + uniqueItems: true, + additionalItems: false, + }, + ], + }, + }, + type: 'array', + items: { + type: 'object', + properties: { + blankLine: { $ref: '#/definitions/paddingType' }, + prev: { $ref: '#/definitions/statementType' }, + next: { $ref: '#/definitions/statementType' }, + }, + additionalProperties: false, + required: ['blankLine', 'prev', 'next'], + }, + additionalItems: false, + }, + messages: { + unexpectedBlankLine: 'Unexpected blank line before this statement.', + expectedBlankLine: 'Expected blank line before this statement.', + }, + }, + defaultOptions: [], + create(context) { + const sourceCode = context.getSourceCode(); + const configureList = context.options || []; + + type Scope = null | { + upper: Scope; + prevNode: TSESTree.Node | null; + }; + + let scopeInfo: Scope = null; + + /** + * Processes to enter to new scope. + * This manages the current previous statement. + * + * @private + */ + function enterScope(): void { + scopeInfo = { + upper: scopeInfo, + prevNode: null, + }; + } + + /** + * Processes to exit from the current scope. + * + * @private + */ + function exitScope(): void { + if (scopeInfo) { + scopeInfo = scopeInfo.upper; + } + } + + /** + * Checks whether the given node matches the given type. + * @paramnode The statement node to check. + * @param type The statement type to check. + * @returns `true` if the statement node matched the type. + * @private + */ + function match(node: TSESTree.Node, type: string | string[]): boolean { + let innerStatementNode = node; + + while (innerStatementNode.type === AST_NODE_TYPES.LabeledStatement) { + innerStatementNode = innerStatementNode.body; + } + + if (Array.isArray(type)) { + return type.some(match.bind(null, innerStatementNode)); + } + + return StatementTypes[type].test(innerStatementNode, sourceCode); + } + + /** + * Finds the last matched configure from configureList. + * @paramprevNode The previous statement to match. + * @paramnextNode The current statement to match. + * @returns The tester of the last matched configure. + * @private + */ + function getPaddingType( + prevNode: TSESTree.Node, + nextNode: TSESTree.Node, + ): typeof PaddingTypes[keyof typeof PaddingTypes] { + for (let i = configureList.length - 1; i >= 0; --i) { + const configure = configureList[i]; + if ( + match(prevNode, configure.prev) && + match(nextNode, configure.next) + ) { + return PaddingTypes[configure.blankLine]; + } + } + return PaddingTypes.any; + } + + /** + * Gets padding line sequences between the given 2 statements. + * Comments are separators of the padding line sequences. + * @paramprevNode The previous statement to count. + * @paramnextNode The current statement to count. + * @returns The array of token pairs. + * @private + */ + function getPaddingLineSequences( + prevNode: TSESTree.Node, + nextNode: TSESTree.Node, + ): [TSESTree.Token, TSESTree.Token][] { + const pairs: [TSESTree.Token, TSESTree.Token][] = []; + let prevToken: TSESTree.Token = getActualLastToken(prevNode, sourceCode)!; + + if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { + do { + const token: TSESTree.Token = sourceCode.getTokenAfter(prevToken, { + includeComments: true, + })!; + + if (token.loc.start.line - prevToken.loc.end.line >= 2) { + pairs.push([prevToken, token]); + } + prevToken = token; + } while (prevToken.range[0] < nextNode.range[0]); + } + + return pairs; + } + + /** + * Verify padding lines between the given node and the previous node. + * @paramnode The node to verify. + * + * @private + */ + function verify(node: TSESTree.Node): void { + if ( + !node.parent || + ![ + AST_NODE_TYPES.SwitchStatement, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.Program, + AST_NODE_TYPES.SwitchCase, + ].includes(node.parent.type) + ) { + return; + } + + // Save this node as the current previous statement. + const prevNode = scopeInfo!.prevNode; + + // Verify. + if (prevNode) { + const type = getPaddingType(prevNode, node); + const paddingLines = getPaddingLineSequences(prevNode, node); + + type.verify(context, prevNode, node, paddingLines); + } + + scopeInfo!.prevNode = node; + } + + /** + * Verify padding lines between the given node and the previous node. + * Then process to enter to new scope. + * @paramnode The node to verify. + * + * @private + */ + function verifyThenEnterScope(node: TSESTree.Node): void { + verify(node); + enterScope(); + } + + return { + Program: enterScope, + BlockStatement: enterScope, + SwitchStatement: enterScope, + 'Program:exit': exitScope, + 'BlockStatement:exit': exitScope, + 'SwitchStatement:exit': exitScope, + + ':statement': verify, + + SwitchCase: verifyThenEnterScope, + 'SwitchCase:exit': exitScope, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/padding-line-between-statements.test.ts b/packages/eslint-plugin/tests/rules/padding-line-between-statements.test.ts new file mode 100644 index 00000000000..d018f189929 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/padding-line-between-statements.test.ts @@ -0,0 +1,5060 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests new lines which prettier tries to fix, breaking the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + +import rule from '../../src/rules/padding-line-between-statements'; +import { RuleTester } from '../RuleTester'; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('padding-line-between-statements', rule, { + valid: [ + // do nothing if no options. + "'use strict'; foo(); if (a) { bar(); }", + + // do nothing for single statement. + { + code: 'foo()', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + }, + { + code: 'foo()', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + }, + + //---------------------------------------------------------------------- + // wildcard + //---------------------------------------------------------------------- + + { + code: 'foo();bar();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + }, + { + code: 'foo();\nbar();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + }, + { + code: 'foo();\n//comment\nbar();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + }, + { + code: 'foo();\n/*comment*/\nbar();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + }, + { + code: 'foo();\n\nbar();', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + }, + { + code: 'foo();\n\n//comment\nbar();', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + }, + { + code: 'foo();\n//comment\n\nbar();', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + }, + { + code: 'foo();\n//comment\n\n//comment\nbar();', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + }, + { + code: 'if(a){}\n\n;[].map(b)', + options: [ + { blankLine: 'always', prev: 'if', next: '*' }, + { blankLine: 'never', prev: 'empty', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // block-like + //---------------------------------------------------------------------- + + { + code: 'foo();\n\n{ foo() }\n\nfoo();', + options: [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'block-like', next: 'block-like' }, + ], + }, + { + code: '{ foo() } { foo() }', + options: [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'block-like', next: 'block-like' }, + ], + }, + { + code: '{ foo() }\n{ foo() }', + options: [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'block-like', next: 'block-like' }, + ], + }, + { + code: '{ foo() }\n\n{ foo() }', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: 'block-like' }, + ], + }, + { + code: '{ foo() }\n\n//comment\n{ foo() }', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: 'block-like' }, + ], + }, + { + code: 'if(a);\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: 'do;while(a);\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: 'do{}while(a);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: 'a={}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: 'let a={}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: 'foo(function(){})\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: '(function(){})()\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + { + code: '!function(){}()\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block-like', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // exports + //---------------------------------------------------------------------- + + { + code: 'module.exports=1', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'exports', next: '*' }, + ], + }, + { + code: 'module.exports=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'exports', next: '*' }, + ], + }, + { + code: 'module.exports.foo=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'exports', next: '*' }, + ], + }, + { + code: 'exports.foo=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'exports', next: '*' }, + ], + }, + { + code: 'm.exports=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'exports', next: '*' }, + ], + }, + { + code: 'module.foo=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'exports', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // require + //---------------------------------------------------------------------- + + { + code: 'foo=require("foo")\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'require', next: '*' }, + ], + }, + { + code: 'const foo=a.require("foo")\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'require', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // directive + //---------------------------------------------------------------------- + + { + code: '"use strict"\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: 'function foo(){"use strict"\n\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: '(function foo(){"use strict"\n\nfoo()})', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: '(()=>{"use strict"\n\nfoo()})', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: "'use strict'\n\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: 'foo("use strict")\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: '`use strict`\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: '("use strict")\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: "'use '+'strict'\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: 'foo()\n"use strict"\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + { + code: '{"use strict"\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'directive', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // multiline-block-like + //---------------------------------------------------------------------- + + { + code: '{}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'if(a){}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'while(a){}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: '{\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'if(a){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'while(a){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'do{\n}while(a)\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'for(;;){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'for(a in b){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'for(a of b){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'switch(a){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'function foo(a){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'var a=function foo(a){\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // block + //---------------------------------------------------------------------- + + { + code: '{}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block', next: '*' }, + ], + }, + { + code: '{\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block', next: '*' }, + ], + }, + { + code: '{\nfoo()\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block', next: '*' }, + ], + }, + { + code: 'if(a){}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block', next: '*' }, + ], + }, + { + code: 'a={}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'block', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // empty + //---------------------------------------------------------------------- + + { + code: ';\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'empty', next: '*' }, + ], + }, + { + code: '1;\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'empty', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // expression + //---------------------------------------------------------------------- + + { + code: 'foo()\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'expression', next: '*' }, + ], + }, + { + code: 'a=b+c\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'expression', next: '*' }, + ], + }, + { + code: 'var a=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'expression', next: '*' }, + ], + }, + { + code: "'use strict'\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'expression', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // multiline-expression + //---------------------------------------------------------------------- + + { + code: 'foo()\n\nfoo(\n\tx,\n\ty\n)', + options: [ + { blankLine: 'always', prev: '*', next: 'multiline-expression' }, + ], + }, + { + code: 'foo()\nfoo()', + options: [ + { blankLine: 'always', prev: '*', next: 'multiline-expression' }, + ], + }, + { + code: '() => {\n\tsomeArray.forEach(x => doSomething(x));\n\treturn theThing;\n}', + options: [ + { blankLine: 'always', prev: 'multiline-expression', next: 'return' }, + ], + }, + { + code: '() => {\n\tsomeArray.forEach(\n\t\tx => doSomething(x)\n\t);\n\n\treturn theThing;\n}', + options: [ + { blankLine: 'always', prev: 'multiline-expression', next: 'return' }, + ], + }, + + //---------------------------------------------------------------------- + // break + //---------------------------------------------------------------------- + + { + code: 'A:{break A\n\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'break', next: '*' }, + ], + }, + { + code: 'while(a){break\n\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'break', next: '*' }, + ], + }, + { + code: 'switch(a){case 0:break\n\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'break', next: '*' }, + ], + }, + { + code: 'switch(a){case 0:break\ncase 1:break}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'break', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // case + //---------------------------------------------------------------------- + + { + code: 'switch(a){case 0:\nfoo()\n\ncase 1:\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'case', next: '*' }, + ], + }, + { + code: 'switch(a){case 0:\nfoo()\n\ndefault:\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'case', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // class + //---------------------------------------------------------------------- + + { + code: 'class A{}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'class', next: '*' }, + ], + }, + { + code: 'var A = class{}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'class', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // const + //---------------------------------------------------------------------- + + { + code: 'const a=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'const', next: '*' }, + ], + }, + { + code: 'let a=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'const', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // type + //---------------------------------------------------------------------- + + { + code: 'type a=number\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'type', next: '*' }, + ], + }, + { + code: 'let a=number\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'type', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // interface + //---------------------------------------------------------------------- + + { + code: 'interface Test{\na:number;\n}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'interface', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // continue + //---------------------------------------------------------------------- + + { + code: 'while(a){continue\n\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'continue', next: '*' }, + ], + }, + { + code: 'while(a){break\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'continue', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // debugger + //---------------------------------------------------------------------- + + { + code: 'debugger\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'debugger', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // default + //---------------------------------------------------------------------- + + { + code: 'switch(a){default:\nfoo()\n\ncase 0:\nfoo()\ncase 1:}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'default', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // do + //---------------------------------------------------------------------- + + { + code: 'do;while(a)\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'do', next: '*' }, + ], + }, + { + code: 'while(a);\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'do', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // export + //---------------------------------------------------------------------- + + { + code: 'export default 1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'export', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export let a=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'export', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var a = 0; export {a}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'export', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'exports.foo=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'export', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'module.exports={}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'export', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + //---------------------------------------------------------------------- + // for + //---------------------------------------------------------------------- + + { + code: 'for(;;);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'for', next: '*' }, + ], + }, + { + code: 'for(a in b);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'for', next: '*' }, + ], + }, + { + code: 'for(a of b);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'for', next: '*' }, + ], + }, + { + code: 'while(a);\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'for', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // function + //---------------------------------------------------------------------- + + { + code: 'function foo(){}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'function', next: '*' }, + ], + }, + { + code: 'var foo=function(){}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'function', next: '*' }, + ], + }, + { + code: 'async function foo(){}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'function', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // if + //---------------------------------------------------------------------- + + { + code: 'if(a);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'if', next: '*' }, + ], + }, + { + code: 'if(a);else;\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'if', next: '*' }, + ], + }, + { + code: 'if(a);else if(b);else;\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'if', next: '*' }, + ], + }, + { + code: 'for(;;);\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'if', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // iife + //---------------------------------------------------------------------- + + { + code: '(function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + }, + { + code: '+(function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + }, + { + code: '(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'iife', next: '*' }], + }, + { + code: '+(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'iife', next: '*' }], + }, + { + code: '(1, 2, 3, function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + }, + + //---------------------------------------------------------------------- + // import + //---------------------------------------------------------------------- + + { + code: "import 'a'\n\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'import', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import a from 'a'\n\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'import', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import * as a from 'a'\n\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'import', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {a} from 'a'\n\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'import', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "const a=require('a')\nfoo()", + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'import', next: '*' }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + //---------------------------------------------------------------------- + // let + //---------------------------------------------------------------------- + + { + code: 'let a=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'let', next: '*' }, + ], + }, + { + code: 'var a=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'let', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // return + //---------------------------------------------------------------------- + + { + code: 'function foo(){return\n\nfoo()}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'return', next: '*' }, + ], + }, + { + code: 'throw a\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'return', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // switch + //---------------------------------------------------------------------- + + { + code: 'switch(a){}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'switch', next: '*' }, + ], + }, + { + code: 'if(a){}\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'switch', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // throw + //---------------------------------------------------------------------- + + { + code: 'throw a\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'throw', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // try + //---------------------------------------------------------------------- + + { + code: 'try{}catch(e){}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'try', next: '*' }, + ], + }, + { + code: 'try{}finally{}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'try', next: '*' }, + ], + }, + { + code: 'try{}catch(e){}finally{}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'try', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // var + //---------------------------------------------------------------------- + + { + code: 'var a=1\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'var', next: '*' }, + ], + }, + { + code: 'const a=1\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'var', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // while + //---------------------------------------------------------------------- + + { + code: 'while(a);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'while', next: '*' }, + ], + }, + { + code: 'do;while(a)\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'while', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // with + //---------------------------------------------------------------------- + + { + code: 'with(a);\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'with', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // multiline-const + //---------------------------------------------------------------------- + + { + code: 'const a={\nb:1,\nc:2\n}\n\nconst d=3', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-const', next: '*' }, + ], + }, + { + code: 'const a=1\n\nconst b={\nc:2,\nd:3\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'multiline-const' }, + ], + }, + { + code: 'const a=1\nconst b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-const', next: '*' }, + ], + }, + { + code: 'const a=1\nconst b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'multiline-const' }, + ], + }, + + //---------------------------------------------------------------------- + // multiline-let + //---------------------------------------------------------------------- + + { + code: 'let a={\nb:1,\nc:2\n}\n\nlet d=3', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-let', next: '*' }, + ], + }, + { + code: 'let a=1\n\nlet b={\nc:2,\nd:3\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'multiline-let' }, + ], + }, + { + code: 'let a=1\nlet b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-let', next: '*' }, + ], + }, + { + code: 'let a=1\nlet b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'multiline-let' }, + ], + }, + + //---------------------------------------------------------------------- + // multiline-var + //---------------------------------------------------------------------- + + { + code: 'var a={\nb:1,\nc:2\n}\n\nvar d=3', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-var', next: '*' }, + ], + }, + { + code: 'var a=1\n\nvar b={\nc:2,\nd:3\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'multiline-var' }, + ], + }, + { + code: 'var a=1\nvar b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'multiline-var', next: '*' }, + ], + }, + { + code: 'var a=1\nvar b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'multiline-var' }, + ], + }, + + //---------------------------------------------------------------------- + // single line const + //---------------------------------------------------------------------- + + { + code: 'const a=1\n\nconst b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'singleline-const', next: '*' }, + ], + }, + { + code: 'const a=1\n\nconst b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'singleline-const' }, + ], + }, + { + code: 'const a={\nb:1,\nc:2\n}\nconst d={\ne:3,\nf:4\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'singleline-const', next: '*' }, + ], + }, + { + code: 'const a={\nb:1,\nc:2\n}\nconst d={\ne:3,\nf:4\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'singleline-const' }, + ], + }, + + //---------------------------------------------------------------------- + // single line let + //---------------------------------------------------------------------- + + { + code: 'let a=1\n\nlet b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'singleline-let', next: '*' }, + ], + }, + { + code: 'let a=1\n\nlet b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'singleline-let' }, + ], + }, + { + code: 'let a={\nb:1,\nc:2\n}\nlet d={\ne:3,\nf:4\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'singleline-let', next: '*' }, + ], + }, + { + code: 'let a={\nb:1,\nc:2\n}\nlet d={\ne:3,\nf:4\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'singleline-let' }, + ], + }, + + //---------------------------------------------------------------------- + // single line var + //---------------------------------------------------------------------- + + { + code: 'var a=1\n\nvar b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'singleline-var', next: '*' }, + ], + }, + { + code: 'var a=1\n\nvar b=2', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'singleline-var' }, + ], + }, + { + code: 'var a={\nb:1,\nc:2\n}\nvar d={\ne:3,\nf:4\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'singleline-var', next: '*' }, + ], + }, + { + code: 'var a={\nb:1,\nc:2\n}\nvar d={\ne:3,\nf:4\n}', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: '*', next: 'singleline-var' }, + ], + }, + + //---------------------------------------------------------------------- + // Tests from newline-after-var + //---------------------------------------------------------------------- + + // should skip rule entirely + { + code: 'console.log(greet);', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'console.log(greet);', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should ignore a `var` with no following token + { + code: "var greet = 'hello';", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow no line break in "never" mode + { + code: "var greet = 'hello';console.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow no blank line in "never" mode + { + code: "var greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow one blank line in "always" mode + { + code: "var greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow two or more blank lines in "always" mode + { + code: "var greet = 'hello';\n\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n\n\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow trailing whitespace after the `var` + { + code: "var greet = 'hello'; \n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello'; \nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow inline comments after the `var` + { + code: "var greet = 'hello'; // inline comment\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello'; // inline comment\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow a comment on the next line in "never" mode + { + code: "var greet = 'hello';\n// next-line comment\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n/* block comment\nblock comment */\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow comments on the next line followed by a blank in "always" mode + { + code: "var greet = 'hello';\n// next-line comment\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n/* block comment\nblock comment */\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n// next-line comment\n// second-line comment\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow comments on the next line followed by no blank in "never" mode + { + code: "var greet = 'hello';\n// next-line comment\n// second-line comment\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n// next-line comment\n/* block comment\nblock comment */\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow another `var` statement to follow without blank line + { + code: "var greet = 'hello';var name = 'world';console.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\nvar name = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\nvar name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should allow a comment directly between `var` statements + { + code: "var greet = 'hello';\n// inline comment\nvar name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n/* block comment\nblock comment */\nvar name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n// inline comment\nvar name = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello';\n/* block comment\nblock comment */\nvar name = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle single `var` statement with multiple declarations + { + code: "var greet = 'hello', name = 'world';console.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello', name = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello', name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle single `var` statement with multi-line declaration + { + code: "var greet = 'hello',\nname = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello',\nname = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello', // inline comment\nname = 'world'; // inline comment\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello', // inline comment\nname = 'world'; // inline comment\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello',\nname = 'world';\n// next-line comment\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var greet = 'hello',\nname = 'world';\n/* block comment\nblock comment */\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle ES6 `let` block binding + { + code: "let greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "let greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle ES6 `const` block binding + { + code: "const greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "const greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle a mix of `var`, `let`, or `const` + { + code: "let greet = 'hello';\nvar name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "const greet = 'hello';\nvar name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "let greet = 'hello';\nconst name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle a mix of `var` or `let` inside for variations + { + code: 'for(let a = 1; a < 1; a++){\n break;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(var a = 1; a < 1; a++){\n break;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(let a = 1; a < 1; a++){\n break;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(var a = 1; a < 1; a++){\n break;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(let a in obj){\n break;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(var a in obj){\n break;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(let a in obj){\n break;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'for(var a in obj){\n break;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle export specifiers + { + code: 'export let a = 1;\nexport let b = 2;', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export let a = 1;\nexport let b = 2;', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export var a = 1;\nexport var b = 2;', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export var a = 1;\nexport var b = 2;', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export const a = 1;\nexport const b = 2;', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export const a = 1;\nexport const b = 2;', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // should allow no blank line at end of block + { + code: "function example() {\nvar greet = 'hello'\n}", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "function example() {\nvar greet = 'hello'\n}", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "function example() {\nvar greet = 'hello';\nconsole.log(greet);\n}", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var f = function() {\nvar greet = 'hello'\n};", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var f = function() {\nvar greet = 'hello'\n};", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "var f = function() {\nvar greet = 'hello';\nconsole.log(greet);\n};", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "() => {\nvar greet = 'hello';\n}", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "() => {\nvar greet = 'hello';\n}", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: "() => {\nvar greet = 'hello';\nconsole.log(greet);\n}", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: '{\nvar foo;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: '{\nvar foo;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'if(true) {\nvar foo;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'if(true) {\nvar foo;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'switch(a) {\ncase 0:\nvar foo;\n}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'switch(a) {\ncase 0:\nvar foo;\n}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // should handle one/no blank before case. + { + code: 'switch(a) {\ncase 0:\nvar foo;\n\ncase 1:}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'switch(a) {\ncase 0:\nvar foo;\ncase 1:}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + // https://github.com/eslint/eslint/issues/6834 + { + code: ` +var a = 1 + +;(b || c).doSomething() + `, + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: ` +var a = 1 +;(b || c).doSomething() + `, + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: ` +var a = 1 +; +(b || c).doSomething(); + `, + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + { + code: 'switch(a) {\ncase 0:\nvar foo;\n\ncase 1:}', + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: 'switch(a) {\ncase 0:\nvar foo;\ncase 1:}', + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + { + code: ` +var a = 1 + +; +(b || c).doSomething(); + `, + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + }, + + //---------------------------------------------------------------------- + // Tests from newline-before-return + //---------------------------------------------------------------------- + + { + code: 'function a() {\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nvar b;\n\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) { return; }\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\nreturn;\n}\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\n\nreturn;\n}\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (!b) {\nreturn;\n} else {\nreturn b;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (!b) {\nreturn;\n} else {\n\nreturn b;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\nreturn b;\n} else if (c) {\nreturn c;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\nreturn b;\n} else if (c) {\nreturn c;\n} else {\nreturn d;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) {\nreturn b;\n} else if (c) {\nreturn c;\n} else {\nreturn d;\n}\n\nreturn a;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse return d;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\nreturn d;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne();\n\nreturn d;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nwhile (b) return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n while (b) \nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n while (b) { return; }\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n while (b) {\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n while (b) {\nc();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nvar c;\nwhile (b) {\n c = d; //comment\n}\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\ndo return;\nwhile (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\ndo \nreturn;\nwhile (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\ndo { return; } while (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\ndo { return; }\nwhile (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\ndo {\nreturn;\n} while (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\ndo {\nc();\n\nreturn;\n} while (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++) return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++)\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++) {\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++) {\nc();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++) {\nif (d) {\nbreak; //comment\n}\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b in c)\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b in c) { return; }\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b in c) {\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b in c) {\nd();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b of c) return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b of c)\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b of c) {\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nfor (b of c) {\nd();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: "function a() {\nswitch (b) {\ncase 'b': return;\n}\n}", + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: "function a() {\nswitch (b) {\ncase 'b':\nreturn;\n}\n}", + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: "function a() {\nswitch (b) {\ncase 'b': {\nreturn;\n}\n}\n}", + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n//comment\nreturn b;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n{\n//comment\n}\n\nreturn\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nvar b = {\n//comment\n};\n\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {/*multi-line\ncomment*/return b;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n/*comment\ncomment*/\n//comment\nreturn b;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n/*comment\ncomment*/\n//comment\nif (b) return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\n/*comment\ncomment*/\n//comment\nif (b) {\nc();\n\nreturn b;\n} else {\n//comment\nreturn d;\n}\n\n/*multi-line\ncomment*/\nreturn e;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) { //comment\nreturn;\n}\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) { return; } //comment\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) { return; } /*multi-line\ncomment*/\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'function a() {\nif (b) { return; }\n\n/*multi-line\ncomment*/ return c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + }, + { + code: 'return;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: 'var a;\n\nreturn;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: '// comment\nreturn;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: '/* comment */\nreturn;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: '/* multi-line\ncomment */\nreturn;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + + //---------------------------------------------------------------------- + // From JSCS disallowPaddingNewLinesAfterBlocks + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/disallow-padding-newlines-after-blocks.js + //---------------------------------------------------------------------- + + { + code: 'if(true){}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){}\n', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){}\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){\nif(true) {}\n}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'var a = {\nfoo: function() {\n},\nbar: function() {\n}}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: '(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true) {\n}\nelse\n{\n}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true) {\n} else {\n var a = 2; }', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true) {\n}\nelse if(true)\n{\n}\nelse {\n}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'do{\n}\nwhile(true)', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'try{\n}\ncatch(e) {}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'try{\n}\nfinally {}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: 'try{\n}\ncatch(e) {\n}\nfinally {\n}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + { + code: '[].map(function() {})\n.filter(function(){})', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + }, + + //---------------------------------------------------------------------- + // From JSCS disallowPaddingNewLinesBeforeExport + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/disallow-padding-newlines-before-export.js + //---------------------------------------------------------------------- + + { + code: 'var a = 2;\nmodule.exports = a;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + }, + { + code: 'module.exports = 2;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + }, + { + code: 'var a = 2;\n// foo\nmodule.exports = a;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + }, + + /* + * TODO: May it need an option to ignore blank lines followed by comments? + * { + * code: "var a = 2;\n\n// foo\nmodule.exports = a;", + * options: [ + * { blankLine: "never", prev: "*", next: "exports" } + * ] + * }, + */ + { + code: 'var a = 2;\n\nfoo.exports = a;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + }, + { + code: 'var a = 2;\n\nmodule.foo = a;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + }, + { + code: 'var a = 2;\n\nfoo = a;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + }, + + //---------------------------------------------------------------------- + // From JSCS requirePaddingNewLinesAfterBlocks + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/require-padding-newlines-after-blocks.js + //---------------------------------------------------------------------- + + { + code: '{}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){}\n', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){}\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){}\n\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true){\nif(true) {}\n}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'var a = {\nfoo: function() {\n},\n\nbar: function() {\n}}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: '(function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true) {\n}\nelse\n{\n}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true) {\n} else {\n var a = 2; }', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'if(true) {\n}\nelse if(true)\n{\n}\nelse {\n}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'do{\n}\nwhile(true)', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'try{\n}\ncatch(e) {}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'try{\n}\nfinally {}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'try{\n}\ncatch(e) {\n}\nfinally {\n}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: '[].map(function() {})\n.filter(function(){})', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'func(\n2,\n3,\nfunction() {\n}\n)', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: '[\n2,\n3,\nfunction() {\n}\n]', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'a(res => {\n})\n.b();', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + }, + { + code: 'var foo = (\n\nfoo\n\n);', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + { + code: 'var i = 0;\nwhile (i < 100) {\nif(i % 2 === 0) {continue;}\n++i;\n}', + options: [ + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + { + code: 'var i = 0;\nwhile (i < 100) {\nif(i % 2 === 0) {if(i === 4) {continue;}}\n++i;\n}', + options: [ + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + }, + + //---------------------------------------------------------------------- + // From JSCS requirePaddingNewLinesBeforeExport + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/require-padding-newlines-before-export.js + //---------------------------------------------------------------------- + + { + code: 'module.exports = 2;', + options: [{ blankLine: 'always', prev: '*', next: 'exports' }], + }, + { + code: 'var a = 2;\n\nmodule.exports = a;', + options: [{ blankLine: 'always', prev: '*', next: 'exports' }], + }, + { + code: 'var a = 2;\nfoo.exports = a;', + options: [{ blankLine: 'always', prev: '*', next: 'exports' }], + }, + { + code: 'var a = 2;\nmodule.foo = a;', + options: [{ blankLine: 'always', prev: '*', next: 'exports' }], + }, + { + code: 'if (true) {\nmodule.exports = a;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'exports' }], + }, + + //---------------------------------------------------------------------- + // From JSCS requirePaddingNewlinesBeforeKeywords + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/require-padding-newlines-before-keywords.js + //---------------------------------------------------------------------- + + { + code: 'function x() { return; }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + }, + { + code: 'if (true) {} else if (false) {}', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + }, + { + code: 'function x() { var a = true; do { a = !a; } while (a); }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + }, + { + code: 'function x() { if (true) return; }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + }, + { + code: 'function test() {};', + options: [ + { blankLine: 'always', prev: 'block-like', next: 'block-like' }, + ], + }, + ], + invalid: [ + //---------------------------------------------------------------------- + // wildcard + //---------------------------------------------------------------------- + + { + code: 'foo();\n\nfoo();', + output: 'foo();\nfoo();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'foo();\n\n//comment\nfoo();', + output: 'foo();\n//comment\nfoo();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: ' foo();\n \n //comment\n foo();', + output: ' foo();\n //comment\n foo();', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'if (a) {}\n\nfor (;;) {}', + output: 'if (a) {}\nfor (;;) {}', + options: [{ blankLine: 'never', prev: '*', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'foo();\nfoo();', + output: 'foo();\n\nfoo();', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: ' function a() {}\n do {} while (a)', + output: ' function a() {}\n\n do {} while (a)', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'foo();//trailing-comment\n//comment\n//comment\nfoo();', + output: 'foo();//trailing-comment\n\n//comment\n//comment\nfoo();', + options: [{ blankLine: 'always', prev: '*', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // block-like + //---------------------------------------------------------------------- + + { + code: '{}\n\nfoo()', + output: '{}\nfoo()', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '{}\nfoo()', + output: '{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){}\nfoo()', + output: 'if(a){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){}else{}\nfoo()', + output: 'if(a){}else{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){}else if(b){}\nfoo()', + output: 'if(a){}else if(b){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){}else if(b){}else{}\nfoo()', + output: 'if(a){}else if(b){}else{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'switch(a){}\nfoo()', + output: 'switch(a){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'switch(a){case 0:}\nfoo()', + output: 'switch(a){case 0:}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{}catch(e){}\nfoo()', + output: 'try{}catch(e){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{}finally{}\nfoo()', + output: 'try{}finally{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{}catch(e){}finally{}\nfoo()', + output: 'try{}catch(e){}finally{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'while(a){}\nfoo()', + output: 'while(a){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'do{}while(a)\nfoo()', + output: 'do{}while(a)\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(;;){}\nfoo()', + output: 'for(;;){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(a in b){}\nfoo()', + output: 'for(a in b){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(a of b){}\nfoo()', + output: 'for(a of b){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'a=function(){}\nfoo()', + output: 'a=function(){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'a=()=>{}\nfoo()', + output: 'a=()=>{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a(){}\nfoo()', + output: 'function a(){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'let a=function(){}\nfoo()', + output: 'let a=function(){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // exports + //---------------------------------------------------------------------- + + { + code: 'module.exports=1\n\nfoo()', + output: 'module.exports=1\nfoo()', + options: [{ blankLine: 'never', prev: 'exports', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'module.exports=1\nfoo()', + output: 'module.exports=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'exports', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'module.exports.foo=1\nfoo()', + output: 'module.exports.foo=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'exports', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'module.exports[foo]=1\nfoo()', + output: 'module.exports[foo]=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'exports', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'exports.foo=1\nfoo()', + output: 'exports.foo=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'exports', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'exports[foo]=1\nfoo()', + output: 'exports[foo]=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'exports', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // require + //---------------------------------------------------------------------- + + { + code: 'const foo=require("foo")\n\nfoo()', + output: 'const foo=require("foo")\nfoo()', + options: [{ blankLine: 'never', prev: 'require', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'const foo=require("foo")\nfoo()', + output: 'const foo=require("foo")\n\nfoo()', + options: [{ blankLine: 'always', prev: 'require', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'const foo=require("foo").Foo\nfoo()', + output: 'const foo=require("foo").Foo\n\nfoo()', + options: [{ blankLine: 'always', prev: 'require', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'const foo=require("foo")[a]\nfoo()', + output: 'const foo=require("foo")[a]\n\nfoo()', + options: [{ blankLine: 'always', prev: 'require', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // directive + //---------------------------------------------------------------------- + + { + code: '"use strict"\n\nfoo()', + output: '"use strict"\nfoo()', + options: [{ blankLine: 'never', prev: 'directive', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '"use strict"\nfoo()', + output: '"use strict"\n\nfoo()', + options: [{ blankLine: 'always', prev: 'directive', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "'use strict'\nfoo()", + output: "'use strict'\n\nfoo()", + options: [{ blankLine: 'always', prev: 'directive', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "'use asm'\nfoo()", + output: "'use asm'\n\nfoo()", + options: [{ blankLine: 'always', prev: 'directive', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // multiline-block-like + //---------------------------------------------------------------------- + + { + code: '{\n}\n\nfoo()', + output: '{\n}\nfoo()', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '{\n}\nfoo()', + output: '{\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){\n}\nfoo()', + output: 'if(a){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){\n}else{\n}\nfoo()', + output: 'if(a){\n}else{\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){\n}else if(b){\n}\nfoo()', + output: 'if(a){\n}else if(b){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a){\n}else if(b){\n}else{\n}\nfoo()', + output: 'if(a){\n}else if(b){\n}else{\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'switch(a){\n}\nfoo()', + output: 'switch(a){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{\n}catch(e){\n}\nfoo()', + output: 'try{\n}catch(e){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{\n}finally{\n}\nfoo()', + output: 'try{\n}finally{\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{\n}catch(e){\n}finally{\n}\nfoo()', + output: 'try{\n}catch(e){\n}finally{\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'while(a){\n}\nfoo()', + output: 'while(a){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'do{\n}while(a)\nfoo()', + output: 'do{\n}while(a)\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(;;){\n}\nfoo()', + output: 'for(;;){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(a in b){\n}\nfoo()', + output: 'for(a in b){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(a of b){\n}\nfoo()', + output: 'for(a of b){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'a=function(){\n}\nfoo()', + output: 'a=function(){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'a=()=>{\n}\nfoo()', + output: 'a=()=>{\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a(){\n}\nfoo()', + output: 'function a(){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'let a=function(){\n}\nfoo()', + output: 'let a=function(){\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // block + //---------------------------------------------------------------------- + + { + code: '{}\n\nfoo()', + output: '{}\nfoo()', + options: [{ blankLine: 'never', prev: 'block', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '{}\nfoo()', + output: '{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'block', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // empty + //---------------------------------------------------------------------- + + { + code: ';\n\nfoo()', + output: ';\nfoo()', + options: [{ blankLine: 'never', prev: 'empty', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: ';\nfoo()', + output: ';\n\nfoo()', + options: [{ blankLine: 'always', prev: 'empty', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // expression + //---------------------------------------------------------------------- + + { + code: 'foo()\n\nfoo()', + output: 'foo()\nfoo()', + options: [{ blankLine: 'never', prev: 'expression', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'foo()\nfoo()', + output: 'foo()\n\nfoo()', + options: [{ blankLine: 'always', prev: 'expression', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // multiline-expression + //---------------------------------------------------------------------- + + { + code: 'foo()\n\nfoo(\n\tx,\n\ty\n)', + output: 'foo()\nfoo(\n\tx,\n\ty\n)', + options: [ + { blankLine: 'never', prev: '*', next: 'multiline-expression' }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'foo()\nfoo(\n\tx,\n\ty\n)', + output: 'foo()\n\nfoo(\n\tx,\n\ty\n)', + options: [ + { blankLine: 'always', prev: '*', next: 'multiline-expression' }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: '() => {\n\tsomeArray.forEach(\n\t\tx => doSomething(x)\n\t);\n\treturn theThing;\n}', + output: + '() => {\n\tsomeArray.forEach(\n\t\tx => doSomething(x)\n\t);\n\n\treturn theThing;\n}', + options: [ + { blankLine: 'always', prev: 'multiline-expression', next: 'return' }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // break + //---------------------------------------------------------------------- + + { + code: 'while(a){break\n\nfoo()}', + output: 'while(a){break\nfoo()}', + options: [{ blankLine: 'never', prev: 'break', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'switch(a){case 0:break\n\nfoo()}', + output: 'switch(a){case 0:break\nfoo()}', + options: [{ blankLine: 'never', prev: 'break', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'while(a){break\nfoo()}', + output: 'while(a){break\n\nfoo()}', + options: [{ blankLine: 'always', prev: 'break', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'switch(a){case 0:break\nfoo()}', + output: 'switch(a){case 0:break\n\nfoo()}', + options: [{ blankLine: 'always', prev: 'break', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // case + //---------------------------------------------------------------------- + + { + code: 'switch(a){case 0:\nfoo()\n\ndefault:}', + output: 'switch(a){case 0:\nfoo()\ndefault:}', + options: [{ blankLine: 'never', prev: 'case', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'switch(a){case 0:\nfoo()\ndefault:}', + output: 'switch(a){case 0:\nfoo()\n\ndefault:}', + options: [{ blankLine: 'always', prev: 'case', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // class + //---------------------------------------------------------------------- + + { + code: 'class A{}\n\nfoo()', + output: 'class A{}\nfoo()', + options: [{ blankLine: 'never', prev: 'class', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'class A{}\nfoo()', + output: 'class A{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'class', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // const + //---------------------------------------------------------------------- + + { + code: 'const a=1\n\nfoo()', + output: 'const a=1\nfoo()', + options: [{ blankLine: 'never', prev: 'const', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'const a=1\nfoo()', + output: 'const a=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'const', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // type + //---------------------------------------------------------------------- + + { + code: 'type a=number\n\nfoo()', + output: 'type a=number\nfoo()', + options: [{ blankLine: 'never', prev: 'type', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'type a=number\nfoo()', + output: 'type a=number\n\nfoo()', + options: [{ blankLine: 'always', prev: 'type', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // interface + //---------------------------------------------------------------------- + + { + code: 'interface Test{\na:number;\n}\nfoo()', + output: 'interface Test{\na:number;\n}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'interface', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // continue + //---------------------------------------------------------------------- + + { + code: 'while(a){continue\n\nfoo()}', + output: 'while(a){continue\nfoo()}', + options: [{ blankLine: 'never', prev: 'continue', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'while(a){continue\nfoo()}', + output: 'while(a){continue\n\nfoo()}', + options: [{ blankLine: 'always', prev: 'continue', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // debugger + //---------------------------------------------------------------------- + + { + code: 'debugger\n\nfoo()', + output: 'debugger\nfoo()', + options: [{ blankLine: 'never', prev: 'debugger', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'debugger\nfoo()', + output: 'debugger\n\nfoo()', + options: [{ blankLine: 'always', prev: 'debugger', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // default + //---------------------------------------------------------------------- + + { + code: 'switch(a){default:\nfoo()\n\ncase 0:}', + output: 'switch(a){default:\nfoo()\ncase 0:}', + options: [{ blankLine: 'never', prev: 'default', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'switch(a){default:\nfoo()\ncase 0:}', + output: 'switch(a){default:\nfoo()\n\ncase 0:}', + options: [{ blankLine: 'always', prev: 'default', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // do + //---------------------------------------------------------------------- + + { + code: 'do;while(a)\n\nfoo()', + output: 'do;while(a)\nfoo()', + options: [{ blankLine: 'never', prev: 'do', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'do;while(a)\nfoo()', + output: 'do;while(a)\n\nfoo()', + options: [{ blankLine: 'always', prev: 'do', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // export + //---------------------------------------------------------------------- + + { + code: 'export default 1\n\nfoo()', + output: 'export default 1\nfoo()', + options: [{ blankLine: 'never', prev: 'export', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'export let a=1\n\nfoo()', + output: 'export let a=1\nfoo()', + options: [{ blankLine: 'never', prev: 'export', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a = 0;export {a}\n\nfoo()', + output: 'var a = 0;export {a}\nfoo()', + options: [{ blankLine: 'never', prev: 'export', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'export default 1\nfoo()', + output: 'export default 1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'export', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'export let a=1\nfoo()', + output: 'export let a=1\n\nfoo()', + options: [{ blankLine: 'always', prev: 'export', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a = 0;export {a}\nfoo()', + output: 'var a = 0;export {a}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'export', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // for + //---------------------------------------------------------------------- + + { + code: 'for(;;);\n\nfoo()', + output: 'for(;;);\nfoo()', + options: [{ blankLine: 'never', prev: 'for', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'for(a in b);\n\nfoo()', + output: 'for(a in b);\nfoo()', + options: [{ blankLine: 'never', prev: 'for', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'for(a of b);\n\nfoo()', + output: 'for(a of b);\nfoo()', + options: [{ blankLine: 'never', prev: 'for', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'for(;;);\nfoo()', + output: 'for(;;);\n\nfoo()', + options: [{ blankLine: 'always', prev: 'for', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(a in b);\nfoo()', + output: 'for(a in b);\n\nfoo()', + options: [{ blankLine: 'always', prev: 'for', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'for(a of b);\nfoo()', + output: 'for(a of b);\n\nfoo()', + options: [{ blankLine: 'always', prev: 'for', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // function + //---------------------------------------------------------------------- + + { + code: 'function foo(){}\n\nfoo()', + output: 'function foo(){}\nfoo()', + options: [{ blankLine: 'never', prev: 'function', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'function foo(){}\nfoo()', + output: 'function foo(){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'function', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'async function foo(){}\nfoo()', + output: 'async function foo(){}\n\nfoo()', + options: [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'function', next: '*' }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // if + //---------------------------------------------------------------------- + + { + code: 'if(a);\n\nfoo()', + output: 'if(a);\nfoo()', + options: [{ blankLine: 'never', prev: 'if', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'if(a);else;\n\nfoo()', + output: 'if(a);else;\nfoo()', + options: [{ blankLine: 'never', prev: 'if', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'if(a);\nfoo()', + output: 'if(a);\n\nfoo()', + options: [{ blankLine: 'always', prev: 'if', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(a);else;\nfoo()', + output: 'if(a);else;\n\nfoo()', + options: [{ blankLine: 'always', prev: 'if', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // iife + //---------------------------------------------------------------------- + + { + code: '(function(){\n})()\n\nvar a = 2;', + output: '(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'iife', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '+(function(){\n})()\n\nvar a = 2;', + output: '+(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'iife', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '(function(){\n})()\nvar a = 2;', + output: '(function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: '+(function(){\n})()\nvar a = 2;', + output: '+(function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + // Optional chaining + { + code: '(function(){\n})?.()\nvar a = 2;', + output: '(function(){\n})?.()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'void (function(){\n})?.()\nvar a = 2;', + output: 'void (function(){\n})?.()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + + // Sequenced function + { + code: '(1,2,3,function(){\n})()\nvar a = 2;', + output: '(1,2,3,function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'iife', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // import + //---------------------------------------------------------------------- + + { + code: "import a from 'a'\n\nfoo()", + output: "import a from 'a'\nfoo()", + options: [{ blankLine: 'never', prev: 'import', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "import * as a from 'a'\n\nfoo()", + output: "import * as a from 'a'\nfoo()", + options: [{ blankLine: 'never', prev: 'import', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "import {a} from 'a'\n\nfoo()", + output: "import {a} from 'a'\nfoo()", + options: [{ blankLine: 'never', prev: 'import', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "import a from 'a'\nfoo()", + output: "import a from 'a'\n\nfoo()", + options: [{ blankLine: 'always', prev: 'import', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "import * as a from 'a'\nfoo()", + output: "import * as a from 'a'\n\nfoo()", + options: [{ blankLine: 'always', prev: 'import', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "import {a} from 'a'\nfoo()", + output: "import {a} from 'a'\n\nfoo()", + options: [{ blankLine: 'always', prev: 'import', next: '*' }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // let + //---------------------------------------------------------------------- + + { + code: 'let a\n\nfoo()', + output: 'let a\nfoo()', + options: [{ blankLine: 'never', prev: 'let', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'let a\nfoo()', + output: 'let a\n\nfoo()', + options: [{ blankLine: 'always', prev: 'let', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // return + //---------------------------------------------------------------------- + + { + code: 'function foo(){return\n\nfoo()}', + output: 'function foo(){return\nfoo()}', + options: [{ blankLine: 'never', prev: 'return', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'function foo(){return\nfoo()}', + output: 'function foo(){return\n\nfoo()}', + options: [{ blankLine: 'always', prev: 'return', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // switch + //---------------------------------------------------------------------- + + { + code: 'switch(a){}\n\nfoo()', + output: 'switch(a){}\nfoo()', + options: [{ blankLine: 'never', prev: 'switch', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'switch(a){}\nfoo()', + output: 'switch(a){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'switch', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // throw + //---------------------------------------------------------------------- + + { + code: 'throw a\n\nfoo()', + output: 'throw a\nfoo()', + options: [{ blankLine: 'never', prev: 'throw', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'throw a\nfoo()', + output: 'throw a\n\nfoo()', + options: [{ blankLine: 'always', prev: 'throw', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // try + //---------------------------------------------------------------------- + + { + code: 'try{}catch(e){}\n\nfoo()', + output: 'try{}catch(e){}\nfoo()', + options: [{ blankLine: 'never', prev: 'try', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'try{}finally{}\n\nfoo()', + output: 'try{}finally{}\nfoo()', + options: [{ blankLine: 'never', prev: 'try', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'try{}catch(e){}finally{}\n\nfoo()', + output: 'try{}catch(e){}finally{}\nfoo()', + options: [{ blankLine: 'never', prev: 'try', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'try{}catch(e){}\nfoo()', + output: 'try{}catch(e){}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'try', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{}finally{}\nfoo()', + output: 'try{}finally{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'try', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'try{}catch(e){}finally{}\nfoo()', + output: 'try{}catch(e){}finally{}\n\nfoo()', + options: [{ blankLine: 'always', prev: 'try', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // var + //---------------------------------------------------------------------- + + { + code: 'var a\n\nfoo()', + output: 'var a\nfoo()', + options: [{ blankLine: 'never', prev: 'var', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a\nfoo()', + output: 'var a\n\nfoo()', + options: [{ blankLine: 'always', prev: 'var', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // while + //---------------------------------------------------------------------- + + { + code: 'while(a);\n\nfoo()', + output: 'while(a);\nfoo()', + options: [{ blankLine: 'never', prev: 'while', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'while(a);\nfoo()', + output: 'while(a);\n\nfoo()', + options: [{ blankLine: 'always', prev: 'while', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // with + //---------------------------------------------------------------------- + + { + code: 'with(a);\n\nfoo()', + output: 'with(a);\nfoo()', + options: [{ blankLine: 'never', prev: 'with', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'with(a);\nfoo()', + output: 'with(a);\n\nfoo()', + options: [{ blankLine: 'always', prev: 'with', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // multiline-const + //---------------------------------------------------------------------- + + { + code: 'const a={\nb:1,\nc:2\n}\n\nconst d=3', + output: 'const a={\nb:1,\nc:2\n}\nconst d=3', + options: [{ blankLine: 'never', prev: 'multiline-const', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'const a={\nb:1,\nc:2\n}\nconst d=3', + output: 'const a={\nb:1,\nc:2\n}\n\nconst d=3', + options: [{ blankLine: 'always', prev: 'multiline-const', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'const a=1\n\nconst b={\nc:2,\nd:3\n}', + output: 'const a=1\nconst b={\nc:2,\nd:3\n}', + options: [{ blankLine: 'never', prev: '*', next: 'multiline-const' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'const a=1\nconst b={\nc:2,\nd:3\n}', + output: 'const a=1\n\nconst b={\nc:2,\nd:3\n}', + options: [{ blankLine: 'always', prev: '*', next: 'multiline-const' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // multiline-let + //---------------------------------------------------------------------- + + { + code: 'let a={\nb:1,\nc:2\n}\n\nlet d=3', + output: 'let a={\nb:1,\nc:2\n}\nlet d=3', + options: [{ blankLine: 'never', prev: 'multiline-let', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'let a={\nb:1,\nc:2\n}\nlet d=3', + output: 'let a={\nb:1,\nc:2\n}\n\nlet d=3', + options: [{ blankLine: 'always', prev: 'multiline-let', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'let a=1\n\nlet b={\nc:2,\nd:3\n}', + output: 'let a=1\nlet b={\nc:2,\nd:3\n}', + options: [{ blankLine: 'never', prev: '*', next: 'multiline-let' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'let a=1\nlet b={\nc:2,\nd:3\n}', + output: 'let a=1\n\nlet b={\nc:2,\nd:3\n}', + options: [{ blankLine: 'always', prev: '*', next: 'multiline-let' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // multiline-var + //---------------------------------------------------------------------- + + { + code: 'var a={\nb:1,\nc:2\n}\n\nvar d=3', + output: 'var a={\nb:1,\nc:2\n}\nvar d=3', + options: [{ blankLine: 'never', prev: 'multiline-var', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a={\nb:1,\nc:2\n}\nvar d=3', + output: 'var a={\nb:1,\nc:2\n}\n\nvar d=3', + options: [{ blankLine: 'always', prev: 'multiline-var', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a=1\n\nvar b={\nc:2,\nd:3\n}', + output: 'var a=1\nvar b={\nc:2,\nd:3\n}', + options: [{ blankLine: 'never', prev: '*', next: 'multiline-var' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a=1\nvar b={\nc:2,\nd:3\n}', + output: 'var a=1\n\nvar b={\nc:2,\nd:3\n}', + options: [{ blankLine: 'always', prev: '*', next: 'multiline-var' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // single line const + //---------------------------------------------------------------------- + + { + code: 'const a=1\n\nconst b=2', + output: 'const a=1\nconst b=2', + options: [{ blankLine: 'never', prev: 'singleline-const', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'const a=1\nconst b=2', + output: 'const a=1\n\nconst b=2', + options: [{ blankLine: 'always', prev: 'singleline-const', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'const a=1\n\nconst b=2', + output: 'const a=1\nconst b=2', + options: [{ blankLine: 'never', prev: '*', next: 'singleline-const' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'const a=1\nconst b=2', + output: 'const a=1\n\nconst b=2', + options: [{ blankLine: 'always', prev: '*', next: 'singleline-const' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // single line let + //---------------------------------------------------------------------- + + { + code: 'let a=1\n\nlet b=2', + output: 'let a=1\nlet b=2', + options: [{ blankLine: 'never', prev: 'singleline-let', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'let a=1\nlet b=2', + output: 'let a=1\n\nlet b=2', + options: [{ blankLine: 'always', prev: 'singleline-let', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'let a=1\n\nlet b=2', + output: 'let a=1\nlet b=2', + options: [{ blankLine: 'never', prev: '*', next: 'singleline-let' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'let a=1\nlet b=2', + output: 'let a=1\n\nlet b=2', + options: [{ blankLine: 'always', prev: '*', next: 'singleline-let' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // single line var + //---------------------------------------------------------------------- + + { + code: 'var a=1\n\nvar b=2', + output: 'var a=1\nvar b=2', + options: [{ blankLine: 'never', prev: 'singleline-var', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a=1\nvar b=2', + output: 'var a=1\n\nvar b=2', + options: [{ blankLine: 'always', prev: 'singleline-var', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a=1\n\nvar b=2', + output: 'var a=1\nvar b=2', + options: [{ blankLine: 'never', prev: '*', next: 'singleline-var' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a=1\nvar b=2', + output: 'var a=1\n\nvar b=2', + options: [{ blankLine: 'always', prev: '*', next: 'singleline-var' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // Tests from newline-after-var + //---------------------------------------------------------------------- + + // should disallow no line break in "always" mode + { + code: "var greet = 'hello';console.log(greet);", + output: "var greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello';var name = 'world';console.log(greet, name);", + output: + "var greet = 'hello';var name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello', name = 'world';console.log(greet, name);", + output: + "var greet = 'hello', name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + // should disallow no blank line in "always" mode + { + code: "var greet = 'hello';\nconsole.log(greet);", + output: "var greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello'; \nconsole.log(greet);", + output: "var greet = 'hello';\n \nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello'; // inline comment\nconsole.log(greet);", + output: "var greet = 'hello'; // inline comment\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello';\nvar name = 'world';\nconsole.log(greet, name);", + output: + "var greet = 'hello';\nvar name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello', name = 'world';\nconsole.log(greet, name);", + output: + "var greet = 'hello', name = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello',\nname = 'world';\nconsole.log(greet, name);", + output: + "var greet = 'hello',\nname = 'world';\n\nconsole.log(greet, name);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "let greet = 'hello';\nconsole.log(greet);", + output: "let greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "const greet = 'hello';\nconsole.log(greet);", + output: "const greet = 'hello';\n\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "function example() {\nvar greet = 'hello';\nconsole.log(greet);\n}", + output: + "function example() {\nvar greet = 'hello';\n\nconsole.log(greet);\n}", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var f = function() {\nvar greet = 'hello';\nconsole.log(greet);\n};", + output: + "var f = function() {\nvar greet = 'hello';\n\nconsole.log(greet);\n};", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "() => {\nvar greet = 'hello';\nconsole.log(greet);\n}", + output: "() => {\nvar greet = 'hello';\n\nconsole.log(greet);\n}", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + // should disallow blank lines in "never" mode + { + code: "var greet = 'hello';\n\nconsole.log(greet);", + output: "var greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello';\n\n\nconsole.log(greet);", + output: "var greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello';\n\n\n\nconsole.log(greet);", + output: "var greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello'; \n\nconsole.log(greet);", + output: "var greet = 'hello'; \nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello'; // inline comment\n\nconsole.log(greet);", + output: "var greet = 'hello'; // inline comment\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello';\nvar name = 'world';\n\nconsole.log(greet, name);", + output: + "var greet = 'hello';\nvar name = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello', name = 'world';\n\nconsole.log(greet, name);", + output: "var greet = 'hello', name = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello',\nname = 'world';\n\nconsole.log(greet, name);", + output: + "var greet = 'hello',\nname = 'world';\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "var greet = 'hello', // inline comment\nname = 'world'; // inline comment\n\nconsole.log(greet, name);", + output: + "var greet = 'hello', // inline comment\nname = 'world'; // inline comment\nconsole.log(greet, name);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "let greet = 'hello';\n\nconsole.log(greet);", + output: "let greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: "const greet = 'hello';\n\nconsole.log(greet);", + output: "const greet = 'hello';\nconsole.log(greet);", + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + + // should disallow a comment on the next line that's not in turn followed by a blank in "always" mode + { + code: "var greet = 'hello';\n// next-line comment\nconsole.log(greet);", + output: + "var greet = 'hello';\n\n// next-line comment\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello';\n/* block comment\nblock comment */\nconsole.log(greet);", + output: + "var greet = 'hello';\n\n/* block comment\nblock comment */\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello',\nname = 'world';\n// next-line comment\nconsole.log(greet);", + output: + "var greet = 'hello',\nname = 'world';\n\n// next-line comment\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello',\nname = 'world';\n/* block comment\nblock comment */\nconsole.log(greet);", + output: + "var greet = 'hello',\nname = 'world';\n\n/* block comment\nblock comment */\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello';\n// next-line comment\n// second-line comment\nconsole.log(greet);", + output: + "var greet = 'hello';\n\n// next-line comment\n// second-line comment\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: "var greet = 'hello';\n// next-line comment\n/* block comment\nblock comment */\nconsole.log(greet);", + output: + "var greet = 'hello';\n\n// next-line comment\n/* block comment\nblock comment */\nconsole.log(greet);", + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + // https://github.com/eslint/eslint/issues/6834 + { + code: ` +var a = 1 +;(b || c).doSomething() + `, + output: ` +var a = 1 + +;(b || c).doSomething() + `, + options: [ + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: ` +var a = 1 + +;(b || c).doSomething() + `, + output: ` +var a = 1 +;(b || c).doSomething() + `, + options: [ + { blankLine: 'never', prev: ['const', 'let', 'var'], next: '*' }, + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // Tests from newline-before-return + //---------------------------------------------------------------------- + + { + code: 'function a() {\nvar b; return;\n}', + output: 'function a() {\nvar b;\n\n return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b;\nreturn;\n}', + output: 'function a() {\nvar b;\n\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne();\nreturn d;\n}\n}', + output: + 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne();\n\nreturn d;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne(); return d;\n}\n}', + output: + 'function a() {\nif (b) return b;\nelse if (c) return c;\nelse {\ne();\n\n return d;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\n while (b) {\nc();\nreturn;\n}\n}', + output: 'function a() {\n while (b) {\nc();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\ndo {\nc();\nreturn;\n} while (b);\n}', + output: 'function a() {\ndo {\nc();\n\nreturn;\n} while (b);\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++) {\nc();\nreturn;\n}\n}', + output: + 'function a() {\nfor (var b; b < c; b++) {\nc();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nfor (b in c) {\nd();\nreturn;\n}\n}', + output: 'function a() {\nfor (b in c) {\nd();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nfor (b of c) {\nd();\nreturn;\n}\n}', + output: 'function a() {\nfor (b of c) {\nd();\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nif (b) {\nc();\n}\n//comment\nreturn b;\n}', + output: 'function a() {\nif (b) {\nc();\n}\n\n//comment\nreturn b;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\n/*comment\ncomment*/\nif (b) {\nc();\nreturn b;\n} else {\n//comment\n\nreturn d;\n}\n/*multi-line\ncomment*/\nreturn e;\n}', + output: + 'function a() {\n/*comment\ncomment*/\nif (b) {\nc();\n\nreturn b;\n} else {\n//comment\n\nreturn d;\n}\n\n/*multi-line\ncomment*/\nreturn e;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [ + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + ], + }, + { + code: 'function a() {\nif (b) { return; } //comment\nreturn c;\n}', + output: 'function a() {\nif (b) { return; } //comment\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nif (b) { return; } /*multi-line\ncomment*/\nreturn c;\n}', + output: + 'function a() {\nif (b) { return; } /*multi-line\ncomment*/\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nif (b) { return; }\n/*multi-line\ncomment*/ return c;\n}', + output: + 'function a() {\nif (b) { return; }\n\n/*multi-line\ncomment*/ return c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nif (b) { return; } /*multi-line\ncomment*/ return c;\n}', + output: + 'function a() {\nif (b) { return; } /*multi-line\ncomment*/\n\n return c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a;\nreturn;', + output: 'var a;\n\nreturn;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a; return;', + output: 'var a;\n\n return;', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\n{\n//comment\n}\nreturn\n}', + output: 'function a() {\n{\n//comment\n}\n\nreturn\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\n{\n//comment\n} return\n}', + output: 'function a() {\n{\n//comment\n}\n\n return\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar c;\nwhile (b) {\n c = d; //comment\n}\nreturn c;\n}', + output: + 'function a() {\nvar c;\nwhile (b) {\n c = d; //comment\n}\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nfor (var b; b < c; b++) {\nif (d) {\nbreak; //comment\n}\nreturn;\n}\n}', + output: + 'function a() {\nfor (var b; b < c; b++) {\nif (d) {\nbreak; //comment\n}\n\nreturn;\n}\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b; /*multi-line\ncomment*/\nreturn c;\n}', + output: 'function a() {\nvar b; /*multi-line\ncomment*/\n\nreturn c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b;\n/*multi-line\ncomment*/ return c;\n}', + output: 'function a() {\nvar b;\n\n/*multi-line\ncomment*/ return c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b; /*multi-line\ncomment*/ return c;\n}', + output: 'function a() {\nvar b; /*multi-line\ncomment*/\n\n return c;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b;\n//comment\nreturn;\n}', + output: 'function a() {\nvar b;\n\n//comment\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b; //comment\nreturn;\n}', + output: 'function a() {\nvar b; //comment\n\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b;\n/* comment */ return;\n}', + output: 'function a() {\nvar b;\n\n/* comment */ return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b;\n//comment\n/* comment */ return;\n}', + output: 'function a() {\nvar b;\n\n//comment\n/* comment */ return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b; /* comment */ return;\n}', + output: 'function a() {\nvar b; /* comment */\n\n return;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b; /* comment */\nreturn;\n}', + output: 'function a() {\nvar b; /* comment */\n\nreturn;\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b;\nreturn; //comment\n}', + output: 'function a() {\nvar b;\n\nreturn; //comment\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function a() {\nvar b; return; //comment\n}', + output: 'function a() {\nvar b;\n\n return; //comment\n}', + options: [{ blankLine: 'always', prev: '*', next: 'return' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // From JSCS disallowPaddingNewLinesAfterBlocks + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/disallow-padding-newlines-after-blocks.js + //---------------------------------------------------------------------- + + { + code: 'if(true){}\n\nvar a = 2;', + output: 'if(true){}\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'if(true){\nif(true) {}\n\nvar a = 2;}', + output: 'if(true){\nif(true) {}\nvar a = 2;}', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '(function(){\n})()\n\nvar a = 2;', + output: '(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: '+(function(){\n})()\n\nvar a = 2;', + output: '+(function(){\n})()\nvar a = 2;', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'var a = function() {};\n\nvar b = 2;', + output: 'var a = function() {};\nvar b = 2;', + options: [{ blankLine: 'never', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // From JSCS disallowPaddingNewLinesBeforeExport + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/disallow-padding-newlines-before-export.js + //---------------------------------------------------------------------- + + { + code: 'var a = 2;\n\nmodule.exports = a;', + output: 'var a = 2;\nmodule.exports = a;', + options: [{ blankLine: 'never', prev: '*', next: 'exports' }], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // From JSCS disallowPaddingNewLinesBeforeExport + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/disallow-padding-newlines-before-keywords.js + //---------------------------------------------------------------------- + + { + code: 'function x() { var a;\n\nreturn; }', + output: 'function x() { var a;\nreturn; }', + options: [ + { + blankLine: 'never', + prev: '*', + next: ['if', 'for', 'return', 'switch', 'case', 'break', 'throw'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'function x() { var a = true;\n\nif (a) { a = !a; }; }', + output: 'function x() { var a = true;\nif (a) { a = !a; }; }', + options: [ + { + blankLine: 'never', + prev: '*', + next: ['if', 'for', 'return', 'switch', 'case', 'break', 'throw'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'function x() { var a = true;\n\nfor (var i = 0; i < 10; i++) { a = !a; }; }', + output: + 'function x() { var a = true;\nfor (var i = 0; i < 10; i++) { a = !a; }; }', + options: [ + { + blankLine: 'never', + prev: '*', + next: ['if', 'for', 'return', 'switch', 'case', 'break', 'throw'], + }, + ], + errors: [{ messageId: 'unexpectedBlankLine' }], + }, + { + code: 'function x() { var y = true;\n\nswitch ("Oranges") { case "Oranges": y = !y;\n\nbreak;\n\ncase "Apples": y = !y;\n\nbreak; default: y = !y; } }', + output: + 'function x() { var y = true;\nswitch ("Oranges") { case "Oranges": y = !y;\nbreak;\ncase "Apples": y = !y;\nbreak; default: y = !y; } }', + options: [ + { + blankLine: 'never', + prev: '*', + next: ['if', 'for', 'return', 'switch', 'case', 'break', 'throw'], + }, + ], + errors: [ + { messageId: 'unexpectedBlankLine' }, + { messageId: 'unexpectedBlankLine' }, + { messageId: 'unexpectedBlankLine' }, + { messageId: 'unexpectedBlankLine' }, + ], + }, + { + code: 'function x() {try { var a;\n\nthrow 0; } catch (e) { var b = 0;\n\nthrow e; } }', + output: + 'function x() {try { var a;\nthrow 0; } catch (e) { var b = 0;\nthrow e; } }', + options: [ + { + blankLine: 'never', + prev: '*', + next: ['if', 'for', 'return', 'switch', 'case', 'break', 'throw'], + }, + ], + errors: [ + { messageId: 'unexpectedBlankLine' }, + { messageId: 'unexpectedBlankLine' }, + ], + }, + { + code: 'function x(a) { var b = 0;\n\nif (!a) { return false; };\n\nfor (var i = 0; i < b; i++) { if (!a[i]) return false; }\n\nreturn true; }', + output: + 'function x(a) { var b = 0;\nif (!a) { return false; };\nfor (var i = 0; i < b; i++) { if (!a[i]) return false; }\nreturn true; }', + options: [ + { + blankLine: 'never', + prev: '*', + next: ['if', 'for', 'return', 'switch', 'case', 'break', 'throw'], + }, + ], + errors: [ + { messageId: 'unexpectedBlankLine' }, + { messageId: 'unexpectedBlankLine' }, + { messageId: 'unexpectedBlankLine' }, + ], + }, + + //---------------------------------------------------------------------- + // From JSCS requirePaddingNewLinesAfterBlocks + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/require-padding-newlines-after-blocks.js + //---------------------------------------------------------------------- + + { + code: 'if(true){}\nvar a = 2;', + output: 'if(true){}\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a = function() {\n};\nvar b = 2;', + output: 'var a = function() {\n};\n\nvar b = 2;', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'if(true){\nif(true) {}\nvar a = 2;}', + output: 'if(true){\nif(true) {}\n\nvar a = 2;}', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: '(function(){\n})()\nvar a = 2;', + output: '(function(){\n})()\n\nvar a = 2;', + options: [{ blankLine: 'always', prev: 'block-like', next: '*' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'var a = function() {\n};\nvar b = 2;', + output: 'var a = function() {\n};\n\nvar b = 2;', + options: [ + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: '(function(){\n})()\nvar a = 2;', + output: '(function(){\n})()\n\nvar a = 2;', + options: [ + { blankLine: 'always', prev: 'multiline-block-like', next: '*' }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // From JSCS requirePaddingNewLinesBeforeExport + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/require-padding-newlines-before-export.js + //---------------------------------------------------------------------- + + { + code: 'var a = 2;\nmodule.exports = a;', + output: 'var a = 2;\n\nmodule.exports = a;', + options: [{ blankLine: 'always', prev: '*', next: 'exports' }], + errors: [{ messageId: 'expectedBlankLine' }], + }, + + //---------------------------------------------------------------------- + // From JSCS requirePaddingNewlinesBeforeKeywords + // https://github.com/jscs-dev/node-jscs/blob/44f9b86eb0757fd4ca05a81a50450c5f1b25c37b/test/specs/rules/require-padding-newlines-before-keywords.js + //---------------------------------------------------------------------- + + { + code: 'function x() { var a; return; }', + output: 'function x() { var a;\n\n return; }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function x() { var a = true; for (var i = 0; i < 10; i++) { a = !a; }; }', + output: + 'function x() { var a = true;\n\n for (var i = 0; i < 10; i++) { a = !a; }; }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function x() { var y = true; switch ("Oranges") { case "Oranges": y = !y; break; case "Apples": y = !y; break; default: y = !y; } }', + output: + 'function x() { var y = true;\n\n switch ("Oranges") { case "Oranges": y = !y;\n\n break;\n\n case "Apples": y = !y;\n\n break;\n\n default: y = !y; } }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + errors: [ + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + ], + }, + { + code: 'function x() { var a = true; while (!a) { a = !a; }; }', + output: 'function x() { var a = true;\n\n while (!a) { a = !a; }; }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + errors: [{ messageId: 'expectedBlankLine' }], + }, + { + code: 'function x() {try { var a; throw 0; } catch (e) { var b = 0; throw e; } }', + output: + 'function x() {try { var a;\n\n throw 0; } catch (e) { var b = 0;\n\n throw e; } }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + errors: [ + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + ], + }, + { + code: 'function x(a) { var b = 0; if (!a) { return false; }; for (var i = 0; i < b; i++) { if (!a[i]) return false; } return true; }', + output: + 'function x(a) { var b = 0;\n\n if (!a) { return false; };\n\n for (var i = 0; i < b; i++) { if (!a[i]) return false; }\n\n return true; }', + options: [ + { + blankLine: 'always', + prev: '*', + next: [ + 'if', + 'for', + 'return', + 'switch', + 'case', + 'break', + 'throw', + 'while', + 'default', + ], + }, + ], + errors: [ + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + { messageId: 'expectedBlankLine' }, + ], + }, + ], +});