From 3d87e088a162120409da8517a7264e29400c86f7 Mon Sep 17 00:00:00 2001 From: Adam Sajko Date: Wed, 12 Nov 2025 11:41:09 +0100 Subject: [PATCH] feat(eslint-plugin): [require-explicit-array-types] new rule --- .../rules/require-explicit-array-types.mdx | 58 ++++++ .../eslint-plugin/src/configs/eslintrc/all.ts | 1 + .../eslint-plugin/src/configs/flat/all.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/require-explicit-array-types.ts | 55 +++++ .../require-explicit-array-types.shot | 29 +++ .../require-explicit-array-types.test.ts | 188 ++++++++++++++++++ .../require-explicit-array-types.shot | 10 + 8 files changed, 344 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/require-explicit-array-types.mdx create mode 100644 packages/eslint-plugin/src/rules/require-explicit-array-types.ts create mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-explicit-array-types.shot create mode 100644 packages/eslint-plugin/tests/rules/require-explicit-array-types.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/require-explicit-array-types.shot diff --git a/packages/eslint-plugin/docs/rules/require-explicit-array-types.mdx b/packages/eslint-plugin/docs/rules/require-explicit-array-types.mdx new file mode 100644 index 00000000000..0f687793d8a --- /dev/null +++ b/packages/eslint-plugin/docs/rules/require-explicit-array-types.mdx @@ -0,0 +1,58 @@ +--- +description: 'Require explicit type annotations for empty arrays assigned to variables.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/require-explicit-array-types** for documentation. + +This rule requires explicit type annotations for empty arrays assigned to variables. When assigning an empty array to a variable, TypeScript cannot infer the element type. The inferred type depends on your TypeScript configuration: + +- **With `noImplicitAny: false`** (default): Empty arrays are inferred as `any[]`, which allows any type to be pushed but loses type safety. +- **With `noImplicitAny: true`** (strict mode): Empty arrays are inferred as `never[]`, which prevents adding any elements and causes type errors. However, TypeScript still allows `const arr = []` without a type annotation; errors only appear when you try to use the array. + +This rule requires explicit type annotations for empty arrays at declaration time to ensure type safety. If you intentionally want `never[]`, explicitly type it: `const arr: never[] = []`. + +## Examples + + + + +```ts +const arr = []; +const items = []; +let data = []; +var list = []; +``` + + + + +```ts +const arr: string[] = []; +const items: number[] = []; +let data: boolean[] = []; +var list: any[] = []; + +// If you intentionally want never[], you can be explicit about it +// Placeholder that will be replaced later +const placeholder: never[] = []; + +// Non-empty arrays don't require explicit types +const numbers = [1, 2, 3]; +const strings = ['a', 'b']; + +// Type assertions are also acceptable +const typedArr = [] as string[]; +const typedItems = [] as number[]; +``` + + + + +## When Not To Use It + +If you prefer to rely on TypeScript's type inference for empty arrays, or if you consistently use type assertions instead of type annotations, you can turn this rule off. diff --git a/packages/eslint-plugin/src/configs/eslintrc/all.ts b/packages/eslint-plugin/src/configs/eslintrc/all.ts index 78c8420e264..352e7cbe64d 100644 --- a/packages/eslint-plugin/src/configs/eslintrc/all.ts +++ b/packages/eslint-plugin/src/configs/eslintrc/all.ts @@ -151,6 +151,7 @@ export = { '@typescript-eslint/require-array-sort-compare': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-explicit-array-types': 'error', '@typescript-eslint/restrict-plus-operands': 'error', '@typescript-eslint/restrict-template-expressions': 'error', 'no-return-await': 'off', diff --git a/packages/eslint-plugin/src/configs/flat/all.ts b/packages/eslint-plugin/src/configs/flat/all.ts index 43792419ab3..24eebc6a8c0 100644 --- a/packages/eslint-plugin/src/configs/flat/all.ts +++ b/packages/eslint-plugin/src/configs/flat/all.ts @@ -165,6 +165,7 @@ export default ( '@typescript-eslint/require-array-sort-compare': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-explicit-array-types': 'error', '@typescript-eslint/restrict-plus-operands': 'error', '@typescript-eslint/restrict-template-expressions': 'error', 'no-return-await': 'off', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 9ee5903aa5f..b92385d604f 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -120,6 +120,7 @@ import preferTsExpectError from './prefer-ts-expect-error'; import promiseFunctionAsync from './promise-function-async'; import relatedGetterSetterPairs from './related-getter-setter-pairs'; import requireArraySortCompare from './require-array-sort-compare'; +import requireExplicitArrayTypes from './require-explicit-array-types'; import requireAwait from './require-await'; import restrictPlusOperands from './restrict-plus-operands'; import restrictTemplateExpressions from './restrict-template-expressions'; @@ -255,6 +256,7 @@ const rules = { 'promise-function-async': promiseFunctionAsync, 'related-getter-setter-pairs': relatedGetterSetterPairs, 'require-array-sort-compare': requireArraySortCompare, + 'require-explicit-array-types': requireExplicitArrayTypes, 'require-await': requireAwait, 'restrict-plus-operands': restrictPlusOperands, 'restrict-template-expressions': restrictTemplateExpressions, diff --git a/packages/eslint-plugin/src/rules/require-explicit-array-types.ts b/packages/eslint-plugin/src/rules/require-explicit-array-types.ts new file mode 100644 index 00000000000..d336a50434c --- /dev/null +++ b/packages/eslint-plugin/src/rules/require-explicit-array-types.ts @@ -0,0 +1,55 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule } from '../util'; + +export type Options = []; +export type MessageIds = 'missingTypeAnnotation'; + +export default createRule({ + name: 'require-explicit-array-types', + meta: { + type: 'problem', + docs: { + description: + 'Require explicit type annotations for empty arrays assigned to variables', + }, + messages: { + missingTypeAnnotation: + 'Empty array assigned to variable must have an explicit type annotation. Use `{{kind}} {{name}}: Type[] = []` instead.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + return { + VariableDeclarator(node) { + const id = node.id; + if (id.type !== AST_NODE_TYPES.Identifier) { + return; + } + + if ( + node.init && + node.init.type === AST_NODE_TYPES.ArrayExpression && + node.init.elements.length === 0 && + !id.typeAnnotation + ) { + const parent = node.parent; + const kind = + parent.type === AST_NODE_TYPES.VariableDeclaration + ? parent.kind + : 'const'; + + context.report({ + node, + messageId: 'missingTypeAnnotation', + data: { + name: id.name, + kind, + }, + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-explicit-array-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-explicit-array-types.shot new file mode 100644 index 00000000000..329435ed5e3 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-explicit-array-types.shot @@ -0,0 +1,29 @@ +Incorrect + +const arr = []; + ~~~~~~~~ Empty array assigned to variable must have an explicit type annotation. Use `const arr: Type[] = []` instead. +const items = []; + ~~~~~~~~~~ Empty array assigned to variable must have an explicit type annotation. Use `const items: Type[] = []` instead. +let data = []; + ~~~~~~~~~ Empty array assigned to variable must have an explicit type annotation. Use `let data: Type[] = []` instead. +var list = []; + ~~~~~~~~~ Empty array assigned to variable must have an explicit type annotation. Use `var list: Type[] = []` instead. + +Correct + +const arr: string[] = []; +const items: number[] = []; +let data: boolean[] = []; +var list: any[] = []; + +// If you intentionally want never[], you can be explicit about it +// Placeholder that will be replaced later +const placeholder: never[] = []; + +// Non-empty arrays don't require explicit types +const numbers = [1, 2, 3]; +const strings = ['a', 'b']; + +// Type assertions are also acceptable +const typedArr = [] as string[]; +const typedItems = [] as number[]; diff --git a/packages/eslint-plugin/tests/rules/require-explicit-array-types.test.ts b/packages/eslint-plugin/tests/rules/require-explicit-array-types.test.ts new file mode 100644 index 00000000000..ffb1fa27f42 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/require-explicit-array-types.test.ts @@ -0,0 +1,188 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/require-explicit-array-types'; + +const ruleTester = new RuleTester(); + +ruleTester.run('require-explicit-array-types', rule, { + valid: [ + // With type annotations + 'const arr: string[] = [];', + 'const arr: number[] = [];', + 'const arr: boolean[] = [];', + 'const arr: any[] = [];', + 'const arr: unknown[] = [];', + 'const arr: Array = [];', + 'const arr: Array = [];', + 'const arr: (string | number)[] = [];', + 'let arr: string[] = [];', + 'var arr: string[] = [];', + + // Non-empty arrays (should not trigger) + 'const arr = [1, 2, 3];', + 'const arr = ["a", "b"];', + + // Arrays with type annotation and elements + 'const arr: number[] = [1, 2, 3];', + 'const arr: string[] = ["a", "b"];', + + // Non-array assignments + 'const x = 5;', + 'const y = "hello";', + 'const z = null;', + 'const w = undefined;', + + // Array with type assertion + 'const arr = [] as string[];', + 'const arr = [] as number[];', + + // Explicit never[] type (intentional placeholder) + 'const placeholder: never[] = [];', + ], + invalid: [ + { + code: 'const arr = [];', + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'arr', + kind: 'const', + }, + }, + ], + }, + { + code: 'let arr = [];', + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'arr', + kind: 'let', + }, + }, + ], + }, + { + code: 'var arr = [];', + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'arr', + kind: 'var', + }, + }, + ], + }, + { + code: ` + const items = []; + const data = []; + `, + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'items', + kind: 'const', + }, + }, + { + messageId: 'missingTypeAnnotation', + data: { + name: 'data', + kind: 'const', + }, + }, + ], + }, + { + code: ` + const arr = []; + arr.push(1); + `, + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'arr', + kind: 'const', + }, + }, + ], + }, + { + code: ` + function test() { + const local = []; + } + `, + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'local', + kind: 'const', + }, + }, + ], + }, + { + code: ` + if (true) { + const arr = []; + } + `, + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'arr', + kind: 'const', + }, + }, + ], + }, + { + code: ` + for (let i = 0; i < 10; i++) { + const items = []; + } + `, + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'items', + kind: 'const', + }, + }, + ], + }, + { + code: ` + const a = []; + const b: number[] = []; + const c = []; + `, + errors: [ + { + messageId: 'missingTypeAnnotation', + data: { + name: 'a', + kind: 'const', + }, + }, + { + messageId: 'missingTypeAnnotation', + data: { + name: 'c', + kind: 'const', + }, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/require-explicit-array-types.shot b/packages/eslint-plugin/tests/schema-snapshots/require-explicit-array-types.shot new file mode 100644 index 00000000000..cdd9f837585 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/require-explicit-array-types.shot @@ -0,0 +1,10 @@ + +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];