diff --git a/packages/eslint-plugin/src/rules/init-declarations.ts b/packages/eslint-plugin/src/rules/init-declarations.ts index fabbe73898a5..4debac89cb26 100644 --- a/packages/eslint-plugin/src/rules/init-declarations.ts +++ b/packages/eslint-plugin/src/rules/init-declarations.ts @@ -28,7 +28,51 @@ export default createRule({ }, defaultOptions: ['always'], create(context, [mode]) { - const rules = baseRule.create(context); + // Make a custom context to adjust the the loc of reports where the base + // rule's behavior is a bit too aggressive with TS-specific syntax (namely, + // type annotations). + function getBaseContextOverride(): typeof context { + const reportOverride: typeof context.report = descriptor => { + if ('node' in descriptor && descriptor.loc == null) { + const { node, ...rest } = descriptor; + // We only want to special case the report loc when reporting on + // variables declarations that are not initialized. Declarations that + // _are_ initialized get reported by the base rule due to a setting to + // prohibit initializing variables entirely, in which case underlining + // the whole node including the type annotation and initializer is + // appropriate. + if ( + node.type === AST_NODE_TYPES.VariableDeclarator && + node.init == null + ) { + context.report({ + ...rest, + loc: getReportLoc(node), + }); + return; + } + } + + context.report(descriptor); + }; + + // `return { ...context, report: reportOverride }` isn't safe because the + // `context` object has some getters that need to be preserved. + // + // `return new Proxy(context, ...)` doesn't work because `context` has + // non-configurable properties that throw when constructing a Proxy. + // + // So, we'll just use Proxy on a dummy object and use the `get` trap to + // proxy `context`'s properties. + return new Proxy({} as typeof context, { + get: (target, prop, receiver): unknown => + prop === 'report' + ? reportOverride + : Reflect.get(context, prop, receiver), + }); + } + + const rules = baseRule.create(getBaseContextOverride()); return { 'VariableDeclaration:exit'(node: TSESTree.VariableDeclaration): void { @@ -65,3 +109,26 @@ export default createRule({ } }, }); + +/** + * When reporting an uninitialized variable declarator, get the loc excluding + * the type annotation. + */ +function getReportLoc( + node: TSESTree.VariableDeclarator, +): TSESTree.SourceLocation { + const start: TSESTree.Position = structuredClone(node.loc.start); + const end: TSESTree.Position = { + line: node.loc.start.line, + // `if (id.type === AST_NODE_TYPES.Identifier)` is a condition for + // reporting in the base rule (as opposed to things like destructuring + // assignment), so the type assertion should always be valid. + column: + node.loc.start.column + (node.id as TSESTree.Identifier).name.length, + }; + + return { + start, + end, + }; +} diff --git a/packages/eslint-plugin/tests/rules/init-declarations.test.ts b/packages/eslint-plugin/tests/rules/init-declarations.test.ts index f284cd101853..2667377b2a7e 100644 --- a/packages/eslint-plugin/tests/rules/init-declarations.test.ts +++ b/packages/eslint-plugin/tests/rules/init-declarations.test.ts @@ -1,5 +1,4 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import rule from '../../src/rules/init-declarations'; @@ -381,7 +380,10 @@ declare namespace myLib1 { { messageId: 'initialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 8, }, ], }, @@ -392,7 +394,10 @@ declare namespace myLib1 { { messageId: 'initialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 23, + endLine: 1, + endColumn: 26, }, ], }, @@ -407,12 +412,18 @@ var foo, { messageId: 'initialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 2, + column: 5, + endLine: 2, + endColumn: 8, }, { messageId: 'initialized', data: { idName: 'baz' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 4, + column: 3, + endLine: 4, + endColumn: 6, }, ], }, @@ -428,7 +439,10 @@ function foo() { { messageId: 'initialized', data: { idName: 'bar' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 4, + column: 7, + endLine: 4, + endColumn: 10, }, ], }, @@ -444,7 +458,10 @@ function foo() { { messageId: 'initialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 7, + endLine: 3, + endColumn: 10, }, ], }, @@ -455,7 +472,10 @@ function foo() { { messageId: 'initialized', data: { idName: 'a' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, }, ], }, @@ -475,7 +495,10 @@ function foo() { { messageId: 'initialized', data: { idName: 'b' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 4, + column: 5, + endLine: 4, + endColumn: 6, }, ], }, @@ -492,12 +515,18 @@ function foo() { { messageId: 'initialized', data: { idName: 'a' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 7, + endLine: 3, + endColumn: 8, }, { messageId: 'initialized', data: { idName: 'c' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 5, + column: 7, + endLine: 5, + endColumn: 8, }, ], }, @@ -508,7 +537,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 20, }, ], }, @@ -519,7 +551,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 15, }, ], }, @@ -534,12 +569,18 @@ var foo, { messageId: 'notInitialized', data: { idName: 'bar' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 3, + endLine: 3, + endColumn: 10, }, { messageId: 'notInitialized', data: { idName: 'baz' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 4, + column: 3, + endLine: 4, + endColumn: 10, }, ], }, @@ -555,8 +596,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'bar' }, - - type: AST_NODE_TYPES.VariableDeclarator, + line: 4, + column: 7, + endLine: 4, + endColumn: 16, }, ], }, @@ -567,7 +610,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'a' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 10, }, ], }, @@ -586,7 +632,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'a' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 7, + endLine: 3, + endColumn: 16, }, ], }, @@ -603,7 +652,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'c' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 5, + column: 7, + endLine: 5, + endColumn: 12, }, ], }, @@ -614,7 +666,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'i' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 10, + endLine: 1, + endColumn: 15, }, ], }, @@ -628,7 +683,10 @@ for (var foo in []) { { messageId: 'notInitialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 2, + column: 10, + endLine: 2, + endColumn: 13, }, ], }, @@ -642,7 +700,10 @@ for (var foo of []) { { messageId: 'notInitialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 2, + column: 10, + endLine: 2, + endColumn: 13, }, ], }, @@ -657,7 +718,10 @@ function foo() { { messageId: 'initialized', data: { idName: 'bar' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 7, + endLine: 3, + endColumn: 10, }, ], }, @@ -670,7 +734,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'arr' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 34, }, ], }, @@ -681,7 +748,10 @@ function foo() { { messageId: 'notInitialized', data: { idName: 'arr' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 33, }, ], }, @@ -698,7 +768,10 @@ const class1 = class NAME { { messageId: 'notInitialized', data: { idName: 'name1' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 4, + column: 9, + endLine: 4, + endColumn: 32, }, ], }, @@ -709,7 +782,10 @@ const class1 = class NAME { { messageId: 'initialized', data: { idName: 'arr' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 5, + endLine: 1, + endColumn: 8, }, ], }, @@ -720,7 +796,10 @@ const class1 = class NAME { { messageId: 'notInitialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 1, + column: 13, + endLine: 1, + endColumn: 32, }, ], }, @@ -735,7 +814,10 @@ namespace myLib { { messageId: 'initialized', data: { idName: 'numberOfGreetings' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 7, + endLine: 3, + endColumn: 24, }, ], }, @@ -750,7 +832,10 @@ namespace myLib { { messageId: 'notInitialized', data: { idName: 'numberOfGreetings' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 7, + endLine: 3, + endColumn: 36, }, ], }, @@ -771,17 +856,26 @@ namespace myLib1 { { messageId: 'initialized', data: { idName: 'foo' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 3, + column: 9, + endLine: 3, + endColumn: 12, }, { messageId: 'initialized', data: { idName: 'bar' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 5, + column: 9, + endLine: 5, + endColumn: 12, }, { messageId: 'initialized', data: { idName: 'baz' }, - type: AST_NODE_TYPES.VariableDeclarator, + line: 7, + column: 11, + endLine: 7, + endColumn: 14, }, ], },