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' },
+ },
+ ],
+ },
+ ],
+});