diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index fa34718fc4c..91eaf17711e 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -169,6 +169,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | Name | Description | :heavy_check_mark: | :wrench: | :thought_balloon: | | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | ----------------- | | [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :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/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: | | diff --git a/packages/eslint-plugin/docs/rules/comma-spacing.md b/packages/eslint-plugin/docs/rules/comma-spacing.md new file mode 100644 index 00000000000..145e3607726 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/comma-spacing.md @@ -0,0 +1,22 @@ +# Enforces consistent spacing before and after commas (`comma-spacing`) + +## Rule Details + +This rule extends the base [`eslint/comma-spacing`](https://eslint.org/docs/rules/comma-spacing) rule. +It adds support for trailing comma in a types parameters list. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "comma-spacing": "off", + "@typescript-eslint/comma-spacing": ["error"] +} +``` + +## Options + +See [`eslint/comma-spacing` options](https://eslint.org/docs/rules/comma-spacing#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/comma-spacing.md) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 916d9a0238f..e4b69b7764c 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -8,6 +8,8 @@ "@typescript-eslint/ban-types": "error", "brace-style": "off", "@typescript-eslint/brace-style": "error", + "comma-spacing": "off", + "@typescript-eslint/comma-spacing": "error", "@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/consistent-type-definitions": "error", "default-param-last": "off", diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts new file mode 100644 index 00000000000..0651142efcb --- /dev/null +++ b/packages/eslint-plugin/src/rules/comma-spacing.ts @@ -0,0 +1,190 @@ +import { + TSESTree, + AST_TOKEN_TYPES, +} from '@typescript-eslint/experimental-utils'; +import { isClosingParenToken, isCommaToken } from 'eslint-utils'; +import { isTokenOnSameLine, createRule } from '../util'; + +type Options = [ + { + before: boolean; + after: boolean; + }, +]; +type MessageIds = 'unexpected' | 'missing'; + +export default createRule({ + name: 'comma-spacing', + meta: { + type: 'suggestion', + docs: { + description: 'Enforces consistent spacing before and after commas', + category: 'Stylistic Issues', + recommended: false, + extendsBaseRule: true, + }, + fixable: 'whitespace', + schema: [ + { + type: 'object', + properties: { + before: { + type: 'boolean', + default: false, + }, + after: { + type: 'boolean', + default: true, + }, + }, + additionalProperties: false, + }, + ], + messages: { + unexpected: `There should be no space {{loc}} ','.`, + missing: `A space is required {{loc}} ','.`, + }, + }, + defaultOptions: [ + { + before: false, + after: true, + }, + ], + create(context, [{ before: spaceBefore, after: spaceAfter }]) { + const sourceCode = context.getSourceCode(); + const tokensAndComments = sourceCode.tokensAndComments; + const ignoredTokens = new Set(); + + /** + * Adds null elements of the ArrayExpression or ArrayPattern node to the ignore list + * @param node node to evaluate + */ + function addNullElementsToIgnoreList( + node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, + ): void { + let previousToken = sourceCode.getFirstToken(node); + for (const element of node.elements) { + let token: TSESTree.Token | null; + if (element === null) { + token = sourceCode.getTokenAfter(previousToken as TSESTree.Token); + if (token && isCommaToken(token)) { + ignoredTokens.add(token); + } + } else { + token = sourceCode.getTokenAfter(element); + } + + previousToken = token; + } + } + + /** + * Adds type parameters trailing comma token to the ignore list + * @param node node to evaluate + */ + function addTypeParametersTrailingCommaToIgnoreList( + node: TSESTree.TSTypeParameterDeclaration, + ): void { + const param = node.params[node.params.length - 1]; + const afterToken = sourceCode.getTokenAfter(param); + if (afterToken && isCommaToken(afterToken)) { + ignoredTokens.add(afterToken); + } + } + + /** + * Validates the spacing around a comma token. + * @param commaToken The token representing the comma + * @param prevToken The last token before the comma + * @param nextToken The first token after the comma + */ + function validateCommaSpacing( + commaToken: TSESTree.Token, + prevToken: TSESTree.Token | null, + nextToken: TSESTree.Token | null, + ): void { + if ( + prevToken && + isTokenOnSameLine(prevToken, commaToken) && + spaceBefore !== sourceCode.isSpaceBetween(prevToken, commaToken) + ) { + context.report({ + node: commaToken, + data: { + loc: 'before', + }, + messageId: spaceBefore ? 'missing' : 'unexpected', + fix: fixer => + spaceBefore + ? fixer.insertTextBefore(commaToken, ' ') + : fixer.replaceTextRange( + [prevToken.range[1], commaToken.range[0]], + '', + ), + }); + } + + if (nextToken && isClosingParenToken(nextToken)) { + return; + } + + if (!spaceAfter && nextToken && nextToken.type === AST_TOKEN_TYPES.Line) { + return; + } + + if ( + nextToken && + isTokenOnSameLine(commaToken, nextToken) && + spaceAfter !== sourceCode.isSpaceBetween(commaToken, nextToken) + ) { + context.report({ + node: commaToken, + data: { + loc: 'after', + }, + messageId: spaceAfter ? 'missing' : 'unexpected', + fix: fixer => + spaceAfter + ? fixer.insertTextAfter(commaToken, ' ') + : fixer.replaceTextRange( + [commaToken.range[1], nextToken.range[0]], + '', + ), + }); + } + } + + return { + TSTypeParameterDeclaration: addTypeParametersTrailingCommaToIgnoreList, + ArrayExpression: addNullElementsToIgnoreList, + ArrayPattern: addNullElementsToIgnoreList, + + 'Program:exit'(): void { + tokensAndComments.forEach((token, i) => { + if (!isCommaToken(token)) { + return; + } + + if (token.type === AST_TOKEN_TYPES.JSXText) { + return; + } + + const commaToken = token as TSESTree.Token; + const prevToken = tokensAndComments[i - 1] as TSESTree.Token; + const nextToken = tokensAndComments[i + 1] as TSESTree.Token; + + validateCommaSpacing( + commaToken, + isCommaToken(prevToken) || ignoredTokens.has(commaToken) + ? null + : prevToken, + isCommaToken(nextToken) || ignoredTokens.has(commaToken) + ? null + : nextToken, + ); + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 2079ccc4e40..abaa31db3b3 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -7,6 +7,7 @@ import banTypes from './ban-types'; import braceStyle from './brace-style'; import camelcase from './camelcase'; import classNameCasing from './class-name-casing'; +import commaSpacing from './comma-spacing'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; import defaultParamLast from './default-param-last'; @@ -92,6 +93,7 @@ export default { 'brace-style': braceStyle, camelcase: camelcase, 'class-name-casing': classNameCasing, + 'comma-spacing': commaSpacing, 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, 'default-param-last': defaultParamLast, diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts index 3b54d5a61ca..060165a28ed 100644 --- a/packages/eslint-plugin/src/util/astUtils.ts +++ b/packages/eslint-plugin/src/util/astUtils.ts @@ -55,8 +55,8 @@ function isLogicalOrOperator(node: TSESTree.Node): boolean { * Determines whether two adjacent tokens are on the same line */ function isTokenOnSameLine( - left: TSESTree.Token, - right: TSESTree.Token, + left: TSESTree.Token | TSESTree.Comment, + right: TSESTree.Token | TSESTree.Comment, ): boolean { return left.loc.end.line === right.loc.start.line; } diff --git a/packages/eslint-plugin/tests/rules/comma-spacing.test.ts b/packages/eslint-plugin/tests/rules/comma-spacing.test.ts new file mode 100644 index 00000000000..12a6e0219f8 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/comma-spacing.test.ts @@ -0,0 +1,785 @@ +import rule from '../../src/rules/comma-spacing'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('comma-spacing', rule, { + valid: [ + `foo(1, true/* comment */, 'text');`, + `foo(1, true /* comment */, 'text');`, + `foo(1, true/* comment *//* comment */, 'text');`, + `foo(1, true/* comment */ /* comment */, 'text');`, + `foo(1, true, /* comment */ 'text');`, + `foo(1, // comment\n true, /* comment */ 'text');`, + { + code: `foo(1, // comment\n true,/* comment */ 'text');`, + options: [{ before: false, after: false }], + }, + `const a = 1, b = 2;`, + `const foo = [, ];`, + `const foo = [1, ];`, + `const foo = [, 2];`, + `const foo = [1, 2];`, + `const foo = [, , ];`, + `const foo = [1, , ];`, + `const foo = [, 2, ];`, + `const foo = [, , 3];`, + `const foo = [1, 2, ];`, + `const foo = [, 2, 3];`, + `const foo = [1, , 3];`, + `const foo = [1, 2, 3];`, + `const foo = {'foo':'foo', 'baz':'baz'};`, + `const foo = {'foo':'foo', 'baz':\n'baz'};`, + `const foo = {'foo':\n'foo', 'baz':\n'baz'};`, + `function foo(a, b){}`, + `function foo(a, b = 1){}`, + `function foo(a = 1, b, c){}`, + `const foo = (a, b) => {}`, + `const foo = (a=1, b) => {}`, + `const foo = a => a + 2`, + `a, b`, + `const a = (1 + 2, 2)`, + `a(b, c)`, + `new A(b, c)`, + `foo((a), b)`, + `const b = ((1 + 2), 2)`, + `parseInt((a + b), 10)`, + `go.boom((a + b), 10)`, + `go.boom((a + b), 10, (4))`, + `const x = [ (a + c), (b + b) ]`, + `[' , ']`, + '[` , `]', + '`${[1, 2]}`', + `fn(a, b,)`, + `const fn = (a, b,) => {}`, + `const fn = function (a, b,) {}`, + `foo(/,/, 'a')`, + `const x = ',,,,,';`, + `const code = 'var foo = 1, bar = 3;'`, + `['apples', \n 'oranges'];`, + `{x: 'var x,y,z'}`, + { + code: `const foo = {'foo':\n'bar' ,'baz':\n'qur'};`, + options: [{ before: true, after: false }], + }, + { + code: `const a = 1 ,b = 2;`, + options: [{ before: true, after: false }], + }, + { + code: `function foo(a ,b){}`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [,];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [1 ,];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [ ,2];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [1 ,2];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [,,];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [1 , ,];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [ ,2 ,];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [ , ,3];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [1 ,2 ,];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [ ,2 ,3];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [1 , ,3];`, + options: [{ before: true, after: false }], + }, + { + code: `const arr = [1 ,2 ,3];`, + options: [{ before: true, after: false }], + }, + { + code: `const obj = {'foo':'bar' , 'baz':'qur'};`, + options: [{ before: true, after: true }], + }, + { + code: `const a = 1 , b = 2;`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [, ];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [1 , ];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [ , 2];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [1 , 2];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [, , ];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [1 , , ];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [ , 2 , ];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [ , , 3];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [1 , 2 , ];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [, 2 , 3];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [1 , , 3];`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [1 , 2 , 3];`, + options: [{ before: true, after: true }], + }, + { + code: `a , b`, + options: [{ before: true, after: true }], + }, + { + code: `const arr = [,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [ ,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [1,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [,2];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [ ,2];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [1,2];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [,,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [ ,,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [1,,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [,2,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [ ,2,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [,,3];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [1,2,];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [,2,3];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [1,,3];`, + options: [{ before: false, after: false }], + }, + { + code: `const arr = [1,2,3];`, + options: [{ before: false, after: false }], + }, + { + code: `const a = (1 + 2,2)`, + options: [{ before: false, after: false }], + }, + 'const a; console.log(`${a}`, "a");', + `const [a, b] = [1, 2];`, + `const [a, b, ] = [1, 2];`, + `const [a, , b] = [1, 2, 3];`, + `const [ , b] = a;`, + `const [, b] = a;`, + { + code: `,`, + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, + { + code: ` , `, + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, + { + code: `Hello, world`, + options: [{ before: true, after: false }], + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + `const Foo = (foo: T) => {}`, + `function foo() {}`, + `class Foo {}`, + `interface Foo{}`, + ], + + invalid: [ + { + code: `a(b,c)`, + output: `a(b , c)`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 4, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 4, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `new A(b,c)`, + output: `new A(b , c)`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 8, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 8, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const a = 1 ,b = 2;`, + output: `const a = 1, b = 2;`, + errors: [ + { + messageId: 'unexpected', + column: 13, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 13, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [1 , 2];`, + output: `const arr = [1, 2];`, + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: 'const arr = [1 , ];', + output: 'const arr = [1, ];', + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: `const arr = [1 , ];`, + output: `const arr = [1 ,];`, + options: [{ before: true, after: false }], + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [1 ,2];`, + output: `const arr = [1, 2];`, + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: `missing`, + column: 16, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [(1) , 2];`, + output: `const arr = [(1), 2];`, + errors: [ + { + messageId: 'unexpected', + column: 18, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: `const arr = [1, 2];`, + output: `const arr = [1 ,2];`, + options: [{ before: true, after: false }], + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'unexpected', + column: 15, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [1\n , 2];`, + output: `const arr = [1\n ,2];`, + options: [{ before: false, after: false }], + errors: [ + { + messageId: 'unexpected', + column: 3, + line: 2, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [1,\n 2];`, + output: `const arr = [1 ,\n 2];`, + options: [{ before: true, after: false }], + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: `const obj = {'foo':\n'bar', 'baz':\n'qur'};`, + output: `const obj = {'foo':\n'bar' ,'baz':\n'qur'};`, + options: [{ before: true, after: false }], + errors: [ + { + messageId: 'missing', + column: 6, + line: 2, + data: { loc: 'before' }, + }, + { + messageId: 'unexpected', + column: 6, + line: 2, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const obj = {a: 1\n ,b: 2};`, + output: `const obj = {a: 1\n , b: 2};`, + options: [{ before: false, after: true }], + errors: [ + { + messageId: 'missing', + column: 3, + line: 2, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const obj = {a: 1 ,\n b: 2};`, + output: `const obj = {a: 1,\n b: 2};`, + options: [{ before: false, after: false }], + errors: [ + { + messageId: 'unexpected', + column: 19, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: `const arr = [1 ,2];`, + output: `const arr = [1 , 2];`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 16, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [1,2];`, + output: `const arr = [1 , 2];`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const obj = {'foo':\n'bar','baz':\n'qur'};`, + output: `const obj = {'foo':\n'bar' , 'baz':\n'qur'};`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 6, + line: 2, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 6, + line: 2, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const arr = [1 , 2];`, + output: `const arr = [1,2];`, + options: [{ before: false, after: false }], + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `a ,b`, + output: `a, b`, + options: [{ before: false, after: true }], + errors: [ + { + messageId: 'unexpected', + column: 3, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 3, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `function foo(a,b){}`, + output: `function foo(a , b){}`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const foo = (a,b) => {}`, + output: `const foo = (a , b) => {}`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `const foo = (a = 1,b) => {}`, + output: `const foo = (a = 1 , b) => {}`, + options: [{ before: true, after: true }], + errors: [ + { + messageId: 'missing', + column: 19, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 19, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `function foo(a = 1 ,b = 2) {}`, + output: `function foo(a = 1, b = 2) {}`, + options: [{ before: false, after: true }], + errors: [ + { + messageId: 'unexpected', + column: 20, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 20, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `{foo(1 ,2)}`, + output: `{foo(1, 2)}`, + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + errors: [ + { + messageId: 'unexpected', + column: 11, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 11, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `foo(1, true/* comment */ , 'foo');`, + output: `foo(1, true/* comment */, 'foo');`, + errors: [ + { + messageId: 'unexpected', + column: 26, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: `foo(1, true,/* comment */ 'foo');`, + output: `foo(1, true, /* comment */ 'foo');`, + errors: [ + { + messageId: 'missing', + column: 12, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `foo(404,// comment\n true, 'hello');`, + output: `foo(404, // comment\n true, 'hello');`, + errors: [ + { + messageId: 'missing', + column: 8, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `function Foo() {}`, + output: `function Foo() {}`, + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `function Foo() {}`, + output: `function Foo() {}`, + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + { + code: `function Foo() {}`, + output: `function Foo() {}`, + errors: [ + { + messageId: 'unexpected', + column: 16, + line: 1, + data: { loc: 'before' }, + }, + { + messageId: 'missing', + column: 16, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `function Foo() {}`, + output: `function Foo() {}`, + options: [{ before: false, after: false }], + errors: [ + { + messageId: 'unexpected', + column: 15, + line: 1, + data: { loc: 'after' }, + }, + ], + }, + { + code: `function Foo() {}`, + output: `function Foo() {}`, + options: [{ before: true, after: false }], + errors: [ + { + messageId: 'missing', + column: 15, + line: 1, + data: { loc: 'before' }, + }, + ], + }, + ], +});