diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 7c55b54aca6..3a504fe7d83 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -218,6 +218,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_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 | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: | diff --git a/packages/eslint-plugin/docs/rules/object-curly-spacing.md b/packages/eslint-plugin/docs/rules/object-curly-spacing.md new file mode 100644 index 00000000000..44bd35afb59 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/object-curly-spacing.md @@ -0,0 +1,22 @@ +# Enforce consistent spacing inside braces (`object-curly-spacing`) + +## Rule Details + +This rule extends the base [`eslint/object-curly-spacing`](https://eslint.org/docs/rules/object-curly-spacing) rule. +It adds support for TypeScript's object types. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "object-curly-spacing": "off", + "@typescript-eslint/object-curly-spacing": ["error"] +} +``` + +## Options + +See [`eslint/object-curly-spacing` options](https://eslint.org/docs/rules/object-curly-spacing#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/object-curly-spacing.md) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 36cd690afa0..98afc4ae3a4 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -113,6 +113,8 @@ export = { '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', + 'object-curly-spacing': 'off', + '@typescript-eslint/object-curly-spacing': '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 1be014e46c5..ffa70e57a29 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -79,6 +79,7 @@ import noUseBeforeDefine from './no-use-before-define'; 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 preferAsConst from './prefer-as-const'; import preferEnumInitializers from './prefer-enum-initializers'; import preferForOf from './prefer-for-of'; @@ -195,6 +196,7 @@ export default { 'no-useless-constructor': noUselessConstructor, 'no-var-requires': noVarRequires, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, + 'object-curly-spacing': objectCurlySpacing, 'prefer-as-const': preferAsConst, 'prefer-enum-initializers': preferEnumInitializers, 'prefer-for-of': preferForOf, diff --git a/packages/eslint-plugin/src/rules/object-curly-spacing.ts b/packages/eslint-plugin/src/rules/object-curly-spacing.ts new file mode 100644 index 00000000000..581e7ac1555 --- /dev/null +++ b/packages/eslint-plugin/src/rules/object-curly-spacing.ts @@ -0,0 +1,267 @@ +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/object-curly-spacing'; +import { + createRule, + InferMessageIdsTypeFromRule, + InferOptionsTypeFromRule, + isClosingBraceToken, + isClosingBracketToken, + isTokenOnSameLine, +} from '../util'; + +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'object-curly-spacing', + meta: { + ...baseRule.meta, + docs: { + description: 'Enforce consistent spacing inside braces', + category: 'Stylistic Issues', + recommended: false, + extendsBaseRule: true, + }, + }, + defaultOptions: ['never'], + create(context) { + const spaced = context.options[0] === 'always'; + const sourceCode = context.getSourceCode(); + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param option The option to exclude. + * @returns Whether or not the property is excluded. + */ + function isOptionSet( + option: 'arraysInObjects' | 'objectsInObjects', + ): boolean { + return context.options[1] + ? context.options[1][option] === !spaced + : false; + } + + const options = { + spaced, + arraysInObjectsException: isOptionSet('arraysInObjects'), + objectsInObjectsException: isOptionSet('objectsInObjects'), + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportNoBeginningSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + const nextToken = context + .getSourceCode() + .getTokenAfter(token, { includeComments: true })!; + + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: 'unexpectedSpaceAfter', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.removeRange([token.range[1], nextToken.range[0]]); + }, + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportNoEndingSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + const previousToken = context + .getSourceCode() + .getTokenBefore(token, { includeComments: true })!; + + context.report({ + node, + loc: { start: previousToken.loc.end, end: token.loc.start }, + messageId: 'unexpectedSpaceBefore', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.removeRange([previousToken.range[1], token.range[0]]); + }, + }); + } + + /** + * Reports that there should be a space after the first token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportRequiredBeginningSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + context.report({ + node, + loc: token.loc, + messageId: 'requireSpaceAfter', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextAfter(token, ' '); + }, + }); + } + + /** + * Reports that there should be a space before the last token + * @param node The node to report in the event of an error. + * @param token The token to use for the report. + */ + function reportRequiredEndingSpace( + node: TSESTree.TSTypeLiteral, + token: TSESTree.Token, + ): void { + context.report({ + node, + loc: token.loc, + messageId: 'requireSpaceBefore', + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextBefore(token, ' '); + }, + }); + } + + /** + * Determines if spacing in curly braces is valid. + * @param node The AST node to check. + * @param first The first token to check (should be the opening brace) + * @param second The second token to check (should be first after the opening brace) + * @param penultimate The penultimate token to check (should be last before closing brace) + * @param last The last token to check (should be closing brace) + */ + function validateBraceSpacing( + node: TSESTree.TSTypeLiteral, + first: TSESTree.Token, + second: TSESTree.Token | TSESTree.Comment, + penultimate: TSESTree.Token | TSESTree.Comment, + last: TSESTree.Token, + ): void { + if (isTokenOnSameLine(first, second)) { + const firstSpaced = sourceCode.isSpaceBetween!(first, second); + const secondType = sourceCode.getNodeByRangeIndex(second.range[0])! + .type; + + const openingCurlyBraceMustBeSpaced = + options.arraysInObjectsException && + secondType === AST_NODE_TYPES.TSIndexSignature + ? !options.spaced + : options.spaced; + + if (openingCurlyBraceMustBeSpaced && !firstSpaced) { + reportRequiredBeginningSpace(node, first); + } + if ( + !openingCurlyBraceMustBeSpaced && + firstSpaced && + second.type !== AST_TOKEN_TYPES.Line + ) { + reportNoBeginningSpace(node, first); + } + } + + if (isTokenOnSameLine(penultimate, last)) { + const shouldCheckPenultimate = + (options.arraysInObjectsException && + isClosingBracketToken(penultimate)) || + (options.objectsInObjectsException && + isClosingBraceToken(penultimate)); + const penultimateType = + shouldCheckPenultimate && + sourceCode.getNodeByRangeIndex(penultimate.range[0])!.type; + + const closingCurlyBraceMustBeSpaced = + (options.arraysInObjectsException && + penultimateType === AST_NODE_TYPES.TSTupleType) || + (options.objectsInObjectsException && + penultimateType === AST_NODE_TYPES.TSTypeLiteral) + ? !options.spaced + : options.spaced; + + const lastSpaced = sourceCode.isSpaceBetween!(penultimate, last); + + if (closingCurlyBraceMustBeSpaced && !lastSpaced) { + reportRequiredEndingSpace(node, last); + } + if (!closingCurlyBraceMustBeSpaced && lastSpaced) { + reportNoEndingSpace(node, last); + } + } + } + + /** + * Gets '}' token of an object node. + * + * Because the last token of object patterns might be a type annotation, + * this traverses tokens preceded by the last property, then returns the + * first '}' token. + * @param node The node to get. This node is an + * ObjectExpression or an ObjectPattern. And this node has one or + * more properties. + * @returns '}' token. + */ + function getClosingBraceOfObject( + node: TSESTree.TSTypeLiteral, + ): TSESTree.Token | null { + const lastProperty = node.members[node.members.length - 1]; + + return sourceCode.getTokenAfter(lastProperty, isClosingBraceToken); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + const rules = baseRule.create(context); + return { + ...rules, + TSTypeLiteral(node: TSESTree.TSTypeLiteral): void { + if (node.members.length === 0) { + return; + } + + const first = sourceCode.getFirstToken(node)!; + const last = getClosingBraceOfObject(node)!; + const second = sourceCode.getTokenAfter(first, { + includeComments: true, + })!; + const penultimate = sourceCode.getTokenBefore(last, { + includeComments: true, + })!; + + validateBraceSpacing(node, first, second, penultimate, last); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts b/packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts new file mode 100644 index 00000000000..5c28cbef7ed --- /dev/null +++ b/packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts @@ -0,0 +1,1955 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the position of braces, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import rule from '../../src/rules/object-curly-spacing'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('object-curly-spacing', rule, { + valid: [ + // always - object literals + { code: 'var obj = { foo: bar, baz: qux };', options: ['always'] }, + { + code: 'var obj = { foo: { bar: quxx }, baz: qux };', + options: ['always'], + }, + { code: 'var obj = {\nfoo: bar,\nbaz: qux\n};', options: ['always'] }, + { code: 'var obj = { /**/foo:bar/**/ };', options: ['always'] }, + { code: 'var obj = { //\nfoo:bar };', options: ['always'] }, + + // always - destructuring + { + code: 'var { x } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x,y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx,y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx,y\n} = z', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { /**/x/**/ } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { //\nx } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x = 10, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { x: { z }, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\ny,\n} = x', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { y, } = x', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { y: x } = x', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + + // always - import / export + { + code: "import door from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import * as door from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { door } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\ndoor } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { /**/door/**/ } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { //\ndoor } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { door } from 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { house, mouse } from 'caravan'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import house, { mouse } from 'caravan'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import door, { house, mouse } from 'caravan'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = 0;export { door }', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import 'room'", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { bar as x } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { x, } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\nx,\n} from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { x, } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {\nx,\n} from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { /**/x/**/ } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { //\nx } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var x = 1;\nexport { /**/x/**/ };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var x = 1;\nexport { //\nx };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // always - empty object + { code: 'var foo = {};', options: ['always'] }, + + // always - objectsInObjects + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }};", + options: ['always', { objectsInObjects: false }], + }, + { + code: 'var a = { noop: function () {} };', + options: ['always', { objectsInObjects: false }], + }, + { + code: 'var { y: { z }} = x', + options: ['always', { objectsInObjects: false }], + parserOptions: { ecmaVersion: 6 }, + }, + + // always - arraysInObjects + { + code: "var obj = { 'foo': [ 1, 2 ]};", + options: ['always', { arraysInObjects: false }], + }, + { + code: 'var a = { thingInList: list[0] };', + options: ['always', { arraysInObjects: false }], + }, + + // always - arraysInObjects, objectsInObjects + { + code: "var obj = { 'qux': [ 1, 2 ], 'foo': { 'bar': 1, 'baz': 2 }};", + options: ['always', { arraysInObjects: false, objectsInObjects: false }], + }, + + // always - arraysInObjects, objectsInObjects (reverse) + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }, 'qux': [ 1, 2 ]};", + options: ['always', { arraysInObjects: false, objectsInObjects: false }], + }, + + // never + { code: 'var obj = {foo: bar,\nbaz: qux\n};', options: ['never'] }, + { code: 'var obj = {\nfoo: bar,\nbaz: qux};', options: ['never'] }, + + // never - object literals + { code: 'var obj = {foo: bar, baz: qux};', options: ['never'] }, + { code: 'var obj = {foo: {bar: quxx}, baz: qux};', options: ['never'] }, + { code: 'var obj = {foo: {\nbar: quxx}, baz: qux\n};', options: ['never'] }, + { code: 'var obj = {foo: {\nbar: quxx\n}, baz: qux};', options: ['never'] }, + { code: 'var obj = {\nfoo: bar,\nbaz: qux\n};', options: ['never'] }, + { code: 'var obj = {foo: bar, baz: qux /* */};', options: ['never'] }, + { code: 'var obj = {/* */ foo: bar, baz: qux};', options: ['never'] }, + { code: 'var obj = {//\n foo: bar};', options: ['never'] }, + { + code: 'var obj = { // line comment exception\n foo: bar};', + options: ['never'], + }, + + // never - destructuring + { + code: 'var {x} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x,y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx,y\n} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x = 10} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x = 10, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {x: {z}, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\nx: {z\n}, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {\ny,\n} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {y,} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {y:x} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {/* */ y} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {y /* */} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {//\n y} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var { // line comment exception\n y} = x', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + + // never - import / export + { + code: "import door from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import * as door from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {/* */ door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {/* */ door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {door /* */} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {door /* */} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {//\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {//\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = foo;\nexport {//\n door}', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import { // line comment exception\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export { // line comment exception\n door} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = foo; export { // line comment exception\n door}', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\ndoor} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {\ndoor\n} from 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {house,mouse} from 'caravan'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {house, mouse} from 'caravan'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var door = 0;export {door}', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import 'room'", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import x, {bar} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import x, {bar, baz} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {bar as y} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {x,} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import {\nx,\n} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {x,} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {\nx,\n} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // never - empty object + { code: 'var foo = {};', options: ['never'] }, + + // never - objectsInObjects + { + code: "var obj = {'foo': {'bar': 1, 'baz': 2} };", + options: ['never', { objectsInObjects: true }], + }, + + /* + * https://github.com/eslint/eslint/issues/3658 + * Empty cases. + */ + { code: 'var {} = foo;', parserOptions: { ecmaVersion: 6 } }, + { code: 'var [] = foo;', parserOptions: { ecmaVersion: 6 } }, + { code: 'var {a: {}} = foo;', parserOptions: { ecmaVersion: 6 } }, + { code: 'var {a: []} = foo;', parserOptions: { ecmaVersion: 6 } }, + { + code: "import {} from 'foo';", + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {} from 'foo';", + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export {};', + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'var {} = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var [] = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {a: {}} = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {a: []} = foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "import {} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "export {} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'export {};', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + + // https://github.com/eslint/eslint/issues/6940 + { + code: 'function foo ({a, b}: Props) {\n}', + options: ['never'], + }, + + // default - object literal types + { + code: 'const x:{}', + }, + { + code: 'const x:{ }', + }, + { + code: 'const x:{f: number}', + }, + { + code: 'const x:{ // line-comment\nf: number\n}', + }, + { + code: 'const x:{// line-comment\nf: number\n}', + }, + { + code: 'const x:{/* inline-comment */f: number/* inline-comment */}', + }, + { + code: 'const x:{\nf: number\n}', + }, + { + code: 'const x:{f: {g: number}}', + }, + { + code: 'const x:{f: [number]}', + }, + { + code: 'const x:{[key: string]: value}', + }, + { + code: 'const x:{[key: string]: [number]}', + }, + + // never - object literal types + { + code: 'const x:{f: {g: number} }', + options: ['never', { objectsInObjects: true }], + }, + { + code: 'const x:{f: {g: number}}', + options: ['never', { objectsInObjects: false }], + }, + { + code: 'const x:{f: () => {g: number} }', + options: ['never', { objectsInObjects: true }], + }, + { + code: 'const x:{f: () => {g: number}}', + options: ['never', { objectsInObjects: false }], + }, + { + code: 'const x:{f: [number] }', + options: ['never', { arraysInObjects: true }], + }, + { + code: 'const x:{f: [ number ]}', + options: ['never', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: value}', + options: ['never', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: value}', + options: ['never', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: [number] }', + options: ['never', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: [number]}', + options: ['never', { arraysInObjects: false }], + }, + + // always - object literal types + { + code: 'const x:{}', + options: ['always'], + }, + { + code: 'const x:{ }', + options: ['always'], + }, + { + code: 'const x:{ f: number }', + options: ['always'], + }, + { + code: 'const x:{ // line-comment\nf: number\n}', + options: ['always'], + }, + { + code: 'const x:{ /* inline-comment */ f: number /* inline-comment */ }', + options: ['always'], + }, + { + code: 'const x:{\nf: number\n}', + options: ['always'], + }, + { + code: 'const x:{ f: [number] }', + options: ['always'], + }, + + // always - objectsInObjects + { + code: 'const x:{ f: { g: number } }', + options: ['always', { objectsInObjects: true }], + }, + { + code: 'const x:{ f: { g: number }}', + options: ['always', { objectsInObjects: false }], + }, + { + code: 'const x:{ f: () => { g: number } }', + options: ['always', { objectsInObjects: true }], + }, + { + code: 'const x:{ f: () => { g: number }}', + options: ['always', { objectsInObjects: false }], + }, + + // always - arraysInObjects + { + code: 'const x:{ f: [number] }', + options: ['always', { arraysInObjects: true }], + }, + { + code: 'const x:{ f: [ number ]}', + options: ['always', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: value }', + options: ['always', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: value }', + options: ['always', { arraysInObjects: false }], + }, + { + code: 'const x:{ [key: string]: [number] }', + options: ['always', { arraysInObjects: true }], + }, + { + code: 'const x:{[key: string]: [number]}', + options: ['always', { arraysInObjects: false }], + }, + ], + + invalid: [ + { + code: "import {bar} from 'foo.js';", + output: "import { bar } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "import { bar as y} from 'foo.js';", + output: "import { bar as y } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "import {bar as y} from 'foo.js';", + output: "import { bar as y } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "import { bar} from 'foo.js';", + output: "import { bar } from 'foo.js';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "import x, { bar} from 'foo';", + output: "import x, { bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "import x, { bar/* */} from 'foo';", + output: "import x, { bar/* */ } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 21, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "import x, {/* */bar } from 'foo';", + output: "import x, { /* */bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "import x, {//\n bar } from 'foo';", + output: "import x, { //\n bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "import x, { bar, baz} from 'foo';", + output: "import x, { bar, baz } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 21, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "import x, {bar} from 'foo';", + output: "import x, { bar } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "import x, {bar, baz} from 'foo';", + output: "import x, { bar, baz } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 20, + endLine: 1, + endColumn: 21, + }, + ], + }, + { + code: "import {bar,} from 'foo';", + output: "import { bar, } from 'foo';", + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "import { bar, } from 'foo';", + output: "import {bar,} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "import { /* */ bar, /* */ } from 'foo';", + output: "import {/* */ bar, /* */} from 'foo';", + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ImportDeclaration, + line: 1, + column: 26, + endLine: 1, + endColumn: 27, + }, + ], + }, + { + code: 'var bar = 0;\nexport {bar};', + output: 'var bar = 0;\nexport { bar };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 12, + }, + ], + }, + { + code: 'var bar = 0;\nexport {/* */ bar /* */};', + output: 'var bar = 0;\nexport { /* */ bar /* */ };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 24, + endLine: 2, + endColumn: 25, + }, + ], + }, + { + code: 'var bar = 0;\nexport {//\n bar };', + output: 'var bar = 0;\nexport { //\n bar };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + ], + }, + { + code: 'var bar = 0;\nexport { /* */ bar /* */ };', + output: 'var bar = 0;\nexport {/* */ bar /* */};', + options: ['never'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 9, + endLine: 2, + endColumn: 10, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ExportNamedDeclaration, + line: 2, + column: 25, + endLine: 2, + endColumn: 26, + }, + ], + }, + + // always - arraysInObjects + { + code: "var obj = { 'foo': [ 1, 2 ] };", + output: "var obj = { 'foo': [ 1, 2 ]};", + options: ['always', { arraysInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 28, + endLine: 1, + endColumn: 29, + }, + ], + }, + { + code: "var obj = { 'foo': [ 1, 2 ] , 'bar': [ 'baz', 'qux' ] };", + output: "var obj = { 'foo': [ 1, 2 ] , 'bar': [ 'baz', 'qux' ]};", + options: ['always', { arraysInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 54, + endLine: 1, + endColumn: 55, + }, + ], + }, + + // always-objectsInObjects + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 } };", + output: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }};", + options: ['always', { objectsInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 42, + endLine: 1, + endColumn: 43, + }, + ], + }, + { + code: "var obj = { 'foo': [ 1, 2 ] , 'bar': { 'baz': 1, 'qux': 2 } };", + output: "var obj = { 'foo': [ 1, 2 ] , 'bar': { 'baz': 1, 'qux': 2 }};", + options: ['always', { objectsInObjects: false }], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 60, + endLine: 1, + endColumn: 61, + }, + ], + }, + + // always-destructuring trailing comma + { + code: 'var { a,} = x;', + output: 'var { a, } = x;', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: 'var {a, } = x;', + output: 'var {a,} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: 'var {a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: 'var { a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: 'var { a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 8, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: 'var { a:b } = x;', + output: 'var {a:b} = x;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 9, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 12, + endLine: 1, + endColumn: 16, + }, + ], + }, + + // never-objectsInObjects + { + code: "var obj = {'foo': {'bar': 1, 'baz': 2}};", + output: "var obj = {'foo': {'bar': 1, 'baz': 2} };", + options: ['never', { objectsInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 39, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: "var obj = {'foo': [1, 2] , 'bar': {'baz': 1, 'qux': 2}};", + output: "var obj = {'foo': [1, 2] , 'bar': {'baz': 1, 'qux': 2} };", + options: ['never', { objectsInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 55, + endLine: 1, + endColumn: 56, + }, + ], + }, + + // always & never + { + code: 'var obj = {foo: bar, baz: qux};', + output: 'var obj = { foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux };', + output: 'var obj = { foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var obj = {/* */foo: bar, baz: qux };', + output: 'var obj = { /* */foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var obj = {//\n foo: bar };', + output: 'var obj = { //\n foo: bar };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux};', + output: 'var obj = { foo: bar, baz: qux };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 31, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux/* */};', + output: 'var obj = { foo: bar, baz: qux/* */ };', + options: ['always'], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 31, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 14, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 32, + endLine: 1, + endColumn: 33, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux };', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 30, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: 'var obj = {foo: bar, baz: qux /* */ };', + output: 'var obj = {foo: bar, baz: qux /* */};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux};', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: 'var obj = { foo: bar, baz: qux};', + output: 'var obj = {foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: 'var obj = { /* */ foo: bar, baz: qux};', + output: 'var obj = {/* */ foo: bar, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: 'var obj = { // line comment exception\n foo: bar };', + output: 'var obj = { // line comment exception\n foo: bar};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 2, + column: 10, + endLine: 2, + endColumn: 11, + }, + ], + }, + { + code: 'var obj = { foo: { bar: quxx}, baz: qux};', + output: 'var obj = {foo: {bar: quxx}, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: 'var obj = {foo: {bar: quxx }, baz: qux };', + output: 'var obj = {foo: {bar: quxx}, baz: qux};', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 27, + endLine: 1, + endColumn: 28, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 39, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: 'export const thing = {value: 1 };', + output: 'export const thing = { value: 1 };', + options: ['always'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 22, + endLine: 1, + endColumn: 23, + }, + ], + }, + + // destructuring + { + code: 'var {x, y} = y', + output: 'var { x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: 'var { x, y} = y', + output: 'var { x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var { x, y/* */} = y', + output: 'var { x, y/* */ } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: 'var {/* */x, y } = y', + output: 'var { /* */x, y } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: 'var {//\n x } = y', + output: 'var { //\n x } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: 'var { x, y } = y', + output: 'var {x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var {x, y } = y', + output: 'var {x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: 'var {x, y/* */ } = y', + output: 'var {x, y/* */} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: 'var { /* */x, y} = y', + output: 'var {/* */x, y} = y', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'unexpectedSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: 'var { x=10} = y', + output: 'var { x=10 } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: 'var {x=10 } = y', + output: 'var { x=10 } = y', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'requireSpaceAfter', + data: { token: '{' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + + // never - arraysInObjects + { + code: "var obj = {'foo': [1, 2]};", + output: "var obj = {'foo': [1, 2] };", + options: ['never', { arraysInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + ], + }, + { + code: "var obj = {'foo': [1, 2] , 'bar': ['baz', 'qux']};", + output: "var obj = {'foo': [1, 2] , 'bar': ['baz', 'qux'] };", + options: ['never', { arraysInObjects: true }], + errors: [ + { + messageId: 'requireSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectExpression, + line: 1, + column: 49, + endLine: 1, + endColumn: 50, + }, + ], + }, + + // https://github.com/eslint/eslint/issues/6940 + { + code: 'function foo ({a, b }: Props) {\n}', + output: 'function foo ({a, b}: Props) {\n}', + options: ['never'], + errors: [ + { + messageId: 'unexpectedSpaceBefore', + data: { token: '}' }, + type: AST_NODE_TYPES.ObjectPattern, + line: 1, + column: 20, + endLine: 1, + endColumn: 21, + }, + ], + }, + + // object literal types + { + code: 'type x = { f: number }', + output: 'type x = {f: number}', + errors: [ + { messageId: 'unexpectedSpaceAfter' }, + { messageId: 'unexpectedSpaceBefore' }, + ], + }, + { + code: 'type x = { f: number}', + output: 'type x = {f: number}', + errors: [{ messageId: 'unexpectedSpaceAfter' }], + }, + { + code: 'type x = {f: number }', + output: 'type x = {f: number}', + errors: [{ messageId: 'unexpectedSpaceBefore' }], + }, + { + code: 'type x = {f: number}', + output: 'type x = { f: number }', + options: ['always'], + errors: [ + { messageId: 'requireSpaceAfter' }, + { messageId: 'requireSpaceBefore' }, + ], + }, + { + code: 'type x = {f: number }', + output: 'type x = { f: number }', + options: ['always'], + errors: [{ messageId: 'requireSpaceAfter' }], + }, + { + code: 'type x = { f: number}', + output: 'type x = { f: number }', + options: ['always'], + errors: [{ messageId: 'requireSpaceBefore' }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ca726d6fb83..49e48541f87 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -830,3 +830,28 @@ declare module 'eslint/lib/rules/prefer-const' { >; export = rule; } + +declare module 'eslint/lib/rules/object-curly-spacing' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + | 'requireSpaceBefore' + | 'requireSpaceAfter' + | 'unexpectedSpaceBefore' + | 'unexpectedSpaceAfter', + [ + 'always' | 'never', + { + arraysInObjects?: boolean; + objectsInObjects?: boolean; + }?, + ], + { + ObjectPattern(node: TSESTree.ObjectPattern): void; + ObjectExpression(node: TSESTree.ObjectExpression): void; + ImportDeclaration(node: TSESTree.ImportDeclaration): void; + ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void; + } + >; + export = rule; +}