From 78f37493f07478b5af96b1fe1996acee91b379f4 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 26 Nov 2022 07:52:02 +0200 Subject: [PATCH 1/4] fix/issue6063-keyword-spacing-unexpected-space-before --- .../src/rules/keyword-spacing.ts | 7 +- .../tests/rules/keyword-spacing.test.ts | 106 +++++++++++++++++- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/keyword-spacing.ts b/packages/eslint-plugin/src/rules/keyword-spacing.ts index 32fb2aaeb71..280ca50c896 100644 --- a/packages/eslint-plugin/src/rules/keyword-spacing.ts +++ b/packages/eslint-plugin/src/rules/keyword-spacing.ts @@ -1,5 +1,5 @@ import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import * as util from '../util'; import { getESLintCoreRule } from '../util/getESLintCoreRule'; @@ -56,6 +56,11 @@ export default util.createRule({ ): void { const typeToken = sourceCode.getFirstToken(node, { skip: 1 })!; const punctuatorToken = sourceCode.getTokenAfter(typeToken)!; + if ( + node.specifiers?.[0]?.type === AST_NODE_TYPES.ImportDefaultSpecifier + ) { + return; + } const spacesBetweenTypeAndPunctuator = punctuatorToken.range[0] - typeToken.range[1]; if (after && spacesBetweenTypeAndPunctuator === 0) { diff --git a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts index b8a1a72d2ca..d8fd54071e9 100644 --- a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts @@ -116,12 +116,82 @@ ruleTester.run('keyword-spacing', rule, { }, { code: 'import type { foo } from "foo";', - options: [{ overrides: { as: {} } }], + options: [BOTH], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, }, { code: "import type * as Foo from 'foo'", - options: [{ overrides: { as: {} } }], + options: [BOTH], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import type{SavedQueries} from './SavedQueries.js';", + options: [ + { + before: true, + after: false, + overrides: { + from: { after: true }, + import: { after: true }, + export: { after: true }, + }, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import type{SavedQueries} from'./SavedQueries.js';", + options: [ + { + before: true, + after: false, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import type http from 'node:http';", + options: [ + { + before: true, + after: false, + overrides: { + from: { after: true }, + import: { after: true }, + export: { after: true }, + }, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: "import type http from'node:http';", + options: [ + { + before: true, + after: false, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'import type {} from "foo";', + options: [BOTH], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'import type { foo1, foo2 } from "foo";', + options: [BOTH], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'import type { foo1 as _foo1, foo2 as _foo2 } from "foo";', + options: [BOTH], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'import type { foo as bar } from "foo";', + options: [BOTH], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, }, ], @@ -190,5 +260,37 @@ ruleTester.run('keyword-spacing', rule, { parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [{ messageId: 'unexpectedBefore', data: { value: '*' } }], }, + { + code: "import type {SavedQueries} from './SavedQueries.js';", + output: "import type{SavedQueries} from './SavedQueries.js';", + options: [ + { + before: true, + after: false, + overrides: { + from: { after: true }, + import: { after: true }, + export: { after: true }, + }, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ messageId: 'unexpectedBefore', data: { value: '{' } }], + }, + { + code: "import type {SavedQueries} from './SavedQueries.js';", + output: "import type{SavedQueries} from'./SavedQueries.js';", + options: [ + { + before: true, + after: false, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [ + { messageId: 'unexpectedBefore', data: { value: '{' } }, + { messageId: 'unexpectedAfter', data: { value: 'from' } }, + ], + }, ], }); From 91c18ff5d0220a6985947a6b606bb5bc41922643 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 26 Nov 2022 08:01:10 +0200 Subject: [PATCH 2/4] remove some redundancy --- packages/eslint-plugin/tests/rules/keyword-spacing.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts index d8fd54071e9..006bcab1341 100644 --- a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts @@ -132,8 +132,6 @@ ruleTester.run('keyword-spacing', rule, { after: false, overrides: { from: { after: true }, - import: { after: true }, - export: { after: true }, }, }, ], @@ -157,8 +155,6 @@ ruleTester.run('keyword-spacing', rule, { after: false, overrides: { from: { after: true }, - import: { after: true }, - export: { after: true }, }, }, ], @@ -269,8 +265,6 @@ ruleTester.run('keyword-spacing', rule, { after: false, overrides: { from: { after: true }, - import: { after: true }, - export: { after: true }, }, }, ], From ac5f9bd35286a48236d4dbd4b2cd84956916c4c7 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sun, 27 Nov 2022 20:12:39 +0200 Subject: [PATCH 3/4] Add overrides for type token --- .../src/rules/keyword-spacing.ts | 48 ++++++++++++++----- .../tests/rules/keyword-spacing.test.ts | 42 +++++++++++++--- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/packages/eslint-plugin/src/rules/keyword-spacing.ts b/packages/eslint-plugin/src/rules/keyword-spacing.ts index 280ca50c896..aa09fb3d1b8 100644 --- a/packages/eslint-plugin/src/rules/keyword-spacing.ts +++ b/packages/eslint-plugin/src/rules/keyword-spacing.ts @@ -9,6 +9,25 @@ const baseRule = getESLintCoreRule('keyword-spacing'); export type Options = util.InferOptionsTypeFromRule; export type MessageIds = util.InferMessageIdsTypeFromRule; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const baseSchema = Array.isArray(baseRule.meta.schema) + ? baseRule.meta.schema[0] + : baseRule.meta.schema; +const schema = util.deepMerge( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- https://github.com/microsoft/TypeScript/issues/17002 + baseSchema, + { + properties: { + overrides: { + properties: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + type: baseSchema.properties.overrides.properties.import, + }, + }, + }, + }, +); + export default util.createRule({ name: 'keyword-spacing', meta: { @@ -20,12 +39,12 @@ export default util.createRule({ }, fixable: 'whitespace', hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, + schema: [schema], messages: baseRule.meta.messages, }, defaultOptions: [{}], - create(context, [{ after }]) { + create(context, [{ after, overrides }]) { const sourceCode = context.getSourceCode(); const baseRules = baseRule.create(context); return { @@ -54,6 +73,7 @@ export default util.createRule({ 'ImportDeclaration[importKind=type]'( node: TSESTree.ImportDeclaration, ): void { + const { type: typeOptionOverride = {} } = overrides ?? {}; const typeToken = sourceCode.getFirstToken(node, { skip: 1 })!; const punctuatorToken = sourceCode.getTokenAfter(typeToken)!; if ( @@ -63,21 +83,27 @@ export default util.createRule({ } const spacesBetweenTypeAndPunctuator = punctuatorToken.range[0] - typeToken.range[1]; - if (after && spacesBetweenTypeAndPunctuator === 0) { + if ( + (typeOptionOverride.after ?? after) === true && + spacesBetweenTypeAndPunctuator === 0 + ) { context.report({ - loc: punctuatorToken.loc, - messageId: 'expectedBefore', - data: { value: punctuatorToken.value }, + loc: typeToken.loc, + messageId: 'expectedAfter', + data: { value: 'type' }, fix(fixer) { - return fixer.insertTextBefore(punctuatorToken, ' '); + return fixer.insertTextAfter(typeToken, ' '); }, }); } - if (!after && spacesBetweenTypeAndPunctuator > 0) { + if ( + (typeOptionOverride.after ?? after) === false && + spacesBetweenTypeAndPunctuator > 0 + ) { context.report({ - loc: punctuatorToken.loc, - messageId: 'unexpectedBefore', - data: { value: punctuatorToken.value }, + loc: typeToken.loc, + messageId: 'unexpectedAfter', + data: { value: 'type' }, fix(fixer) { return fixer.removeRange([ typeToken.range[1], diff --git a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts index 006bcab1341..82249405086 100644 --- a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts @@ -124,6 +124,36 @@ ruleTester.run('keyword-spacing', rule, { options: [BOTH], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, }, + { + code: 'import type { SavedQueries } from "./SavedQueries.js";', + options: [ + { + before: true, + after: false, + overrides: { + else: { after: true }, + return: { after: true }, + try: { after: true }, + catch: { after: false }, + case: { after: true }, + const: { after: true }, + throw: { after: true }, + let: { after: true }, + do: { after: true }, + of: { after: true }, + as: { after: true }, + finally: { after: true }, + from: { after: true }, + import: { after: true }, + export: { after: true }, + default: { after: true }, + // The new option: + type: { after: true }, + }, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, { code: "import type{SavedQueries} from './SavedQueries.js';", options: [ @@ -233,28 +263,28 @@ ruleTester.run('keyword-spacing', rule, { output: 'import type { foo } from "foo";', options: [{ after: true, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'expectedBefore', data: { value: '{' } }], + errors: [{ messageId: 'expectedAfter', data: { value: 'type' } }], }, { code: 'import type { foo } from"foo";', output: 'import type{ foo } from"foo";', options: [{ after: false, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'unexpectedBefore', data: { value: '{' } }], + errors: [{ messageId: 'unexpectedAfter', data: { value: 'type' } }], }, { code: 'import type* as foo from "foo";', output: 'import type * as foo from "foo";', options: [{ after: true, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'expectedBefore', data: { value: '*' } }], + errors: [{ messageId: 'expectedAfter', data: { value: 'type' } }], }, { code: 'import type * as foo from"foo";', output: 'import type* as foo from"foo";', options: [{ after: false, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'unexpectedBefore', data: { value: '*' } }], + errors: [{ messageId: 'unexpectedAfter', data: { value: 'type' } }], }, { code: "import type {SavedQueries} from './SavedQueries.js';", @@ -269,7 +299,7 @@ ruleTester.run('keyword-spacing', rule, { }, ], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'unexpectedBefore', data: { value: '{' } }], + errors: [{ messageId: 'unexpectedAfter', data: { value: 'type' } }], }, { code: "import type {SavedQueries} from './SavedQueries.js';", @@ -282,7 +312,7 @@ ruleTester.run('keyword-spacing', rule, { ], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ - { messageId: 'unexpectedBefore', data: { value: '{' } }, + { messageId: 'unexpectedAfter', data: { value: 'type' } }, { messageId: 'unexpectedAfter', data: { value: 'from' } }, ], }, From e8d5efc79d69e7ccd5255cf319d052ea37cc31e0 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Thu, 1 Dec 2022 21:02:40 +0200 Subject: [PATCH 4/4] test --- .../tests/rules/keyword-spacing.test.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts index 82249405086..58c740fbd5c 100644 --- a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts @@ -154,6 +154,20 @@ ruleTester.run('keyword-spacing', rule, { ], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, }, + { + // Space after import is not configurable from option since it's invalid syntax with import type + code: 'import type { SavedQueries } from "./SavedQueries.js";', + options: [ + { + before: true, + after: true, + overrides: { + import: { after: false }, + }, + }, + ], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, { code: "import type{SavedQueries} from './SavedQueries.js';", options: [ @@ -263,28 +277,28 @@ ruleTester.run('keyword-spacing', rule, { output: 'import type { foo } from "foo";', options: [{ after: true, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'expectedAfter', data: { value: 'type' } }], + errors: expectedAfter('type'), }, { code: 'import type { foo } from"foo";', output: 'import type{ foo } from"foo";', options: [{ after: false, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'unexpectedAfter', data: { value: 'type' } }], + errors: unexpectedAfter('type'), }, { code: 'import type* as foo from "foo";', output: 'import type * as foo from "foo";', options: [{ after: true, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'expectedAfter', data: { value: 'type' } }], + errors: expectedAfter('type'), }, { code: 'import type * as foo from"foo";', output: 'import type* as foo from"foo";', options: [{ after: false, before: true }], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'unexpectedAfter', data: { value: 'type' } }], + errors: unexpectedAfter('type'), }, { code: "import type {SavedQueries} from './SavedQueries.js';", @@ -299,7 +313,7 @@ ruleTester.run('keyword-spacing', rule, { }, ], parserOptions: { ecmaVersion: 6, sourceType: 'module' }, - errors: [{ messageId: 'unexpectedAfter', data: { value: 'type' } }], + errors: unexpectedAfter('type'), }, { code: "import type {SavedQueries} from './SavedQueries.js';",