diff --git a/packages/eslint-plugin/docs/rules/array-type.md b/packages/eslint-plugin/docs/rules/array-type.md index 2ed521c293a..75e2b1c6d65 100644 --- a/packages/eslint-plugin/docs/rules/array-type.md +++ b/packages/eslint-plugin/docs/rules/array-type.md @@ -92,6 +92,25 @@ const e: string[] = ['a', 'b']; const f: readonly string[] = ['a', 'b']; ``` +## Combination matrix + +This matrix lists all possible option combinations and their expected results for different types of Arrays. + +| defaultOption | readonlyOption | Array with simple type | Array with non simple type | Readonly array with simple type | Readonly array with non simple type | +| -------------- | -------------- | ---------------------- | -------------------------- | ------------------------------- | ----------------------------------- | +| `array` | | `number[]` | `(Foo & Bar)[]` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `array` | `array` | `number[]` | `(Foo & Bar)[]` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `array` | `array-simple` | `number[]` | `(Foo & Bar)[]` | `readonly number[]` | `ReadonlyArray` | +| `array` | `generic` | `number[]` | `(Foo & Bar)[]` | `ReadonlyArray` | `ReadonlyArray` | +| `array-simple` | | `number[]` | `Array` | `readonly number[]` | `ReadonlyArray` | +| `array-simple` | `array` | `number[]` | `Array` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `array-simple` | `array-simple` | `number[]` | `Array` | `readonly number[]` | `ReadonlyArray` | +| `array-simple` | `generic` | `number[]` | `Array` | `ReadonlyArray` | `ReadonlyArray` | +| `generic` | | `Array` | `Array` | `ReadonlyArray` | `ReadonlyArray` | +| `generic` | `array` | `Array` | `Array` | `readonly number[]` | `readonly (Foo & Bar)[]` | +| `generic` | `array-simple` | `Array` | `Array` | `readonly number[]` | `ReadonlyArray` | +| `generic` | `generic` | `Array` | `Array` | `ReadonlyArray` | `ReadonlyArray` | + ## Related to - TSLint: [array-type](https://palantir.github.io/tslint/rules/array-type/) diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 0a33d48957c..c77e2fa59fd 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -1,6 +1,5 @@ import { AST_NODE_TYPES, - AST_TOKEN_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; @@ -129,31 +128,6 @@ export default util.createRule({ const defaultOption = options.default; const readonlyOption = options.readonly ?? defaultOption; - const isArraySimpleOption = - defaultOption === 'array-simple' && readonlyOption === 'array-simple'; - const isArrayOption = - defaultOption === 'array' && readonlyOption === 'array'; - const isGenericOption = - defaultOption === 'generic' && readonlyOption === 'generic'; - - /** - * Check if whitespace is needed before this node - * @param node the node to be evaluated. - */ - function requireWhitespaceBefore(node: TSESTree.Node): boolean { - const prevToken = sourceCode.getTokenBefore(node); - if (!prevToken) { - return false; - } - - const nextToken = sourceCode.getTokenAfter(prevToken); - if (nextToken && sourceCode.isSpaceBetweenTokens(prevToken, nextToken)) { - return false; - } - - return prevToken.type === AST_TOKEN_TYPES.Identifier; - } - /** * @param node the node to be evaluated. */ @@ -169,121 +143,80 @@ export default util.createRule({ return 'T'; } - /** - * @param node the node to be evaluated - */ - function getTypeOpNodeRange( - node: TSESTree.Node | null, - ): [number, number] | undefined { - if (!node) { - return undefined; - } - - const firstToken = sourceCode.getFirstToken(node)!; - const nextToken = sourceCode.getTokenAfter(firstToken)!; - return [firstToken.range[0], nextToken.range[0]]; - } - return { TSArrayType(node): void { - if ( - isArrayOption || - (isArraySimpleOption && isSimpleType(node.elementType)) - ) { - return; - } - const isReadonly = node.parent && node.parent.type === AST_NODE_TYPES.TSTypeOperator && node.parent.operator === 'readonly'; - const isReadonlyGeneric = - readonlyOption === 'generic' && defaultOption !== 'generic'; - - const isReadonlyArray = - readonlyOption !== 'generic' && defaultOption === 'generic'; + const currentOption = isReadonly ? readonlyOption : defaultOption; if ( - (isReadonlyGeneric && !isReadonly) || - (isReadonlyArray && isReadonly) + currentOption === 'array' || + (currentOption === 'array-simple' && isSimpleType(node.elementType)) ) { return; } const messageId = - defaultOption === 'generic' + currentOption === 'generic' ? 'errorStringGeneric' : 'errorStringGenericSimple'; - const typeOpNode = isReadonly ? node.parent! : null; + const errorNode = isReadonly ? node.parent! : node; context.report({ - node: isReadonly ? node.parent! : node, + node: errorNode, messageId, data: { type: getMessageType(node.elementType), }, fix(fixer) { - const toFix = [ - fixer.replaceTextRange([node.range[1] - 2, node.range[1]], '>'), - ]; - const startText = requireWhitespaceBefore(node); - const typeOpNodeRange = getTypeOpNodeRange(typeOpNode); + const typeNode = + node.elementType.type === AST_NODE_TYPES.TSParenthesizedType + ? node.elementType.typeAnnotation + : node.elementType; - if (typeOpNodeRange) { - toFix.unshift(fixer.removeRange(typeOpNodeRange)); - } else { - toFix.push( - fixer.insertTextBefore(node, `${startText ? ' ' : ''}`), - ); - } + const arrayType = isReadonly ? 'ReadonlyArray' : 'Array'; - toFix.push( - fixer.insertTextBefore( - node, - `${isReadonly ? 'Readonly' : ''}Array<`, + return [ + fixer.replaceTextRange( + [errorNode.range[0], typeNode.range[0]], + `${arrayType}<`, ), - ); - - if (node.elementType.type === AST_NODE_TYPES.TSParenthesizedType) { - const first = sourceCode.getFirstToken(node.elementType); - const last = sourceCode.getLastToken(node.elementType); - if (!first || !last) { - return null; - } - - toFix.push(fixer.remove(first)); - toFix.push(fixer.remove(last)); - } - - return toFix; + fixer.replaceTextRange( + [typeNode.range[1], errorNode.range[1]], + '>', + ), + ]; }, }); }, TSTypeReference(node): void { if ( - isGenericOption || - node.typeName.type !== AST_NODE_TYPES.Identifier + node.typeName.type !== AST_NODE_TYPES.Identifier || + !( + node.typeName.name === 'Array' || + node.typeName.name === 'ReadonlyArray' + ) ) { return; } const isReadonlyArrayType = node.typeName.name === 'ReadonlyArray'; - const isArrayType = node.typeName.name === 'Array'; + const currentOption = isReadonlyArrayType + ? readonlyOption + : defaultOption; - if ( - !(isArrayType || isReadonlyArrayType) || - (readonlyOption === 'generic' && isReadonlyArrayType) || - (defaultOption === 'generic' && !isReadonlyArrayType) - ) { + if (currentOption === 'generic') { return; } const readonlyPrefix = isReadonlyArrayType ? 'readonly ' : ''; const typeParams = node.typeParameters?.params; const messageId = - defaultOption === 'array' + currentOption === 'array' ? 'errorStringArray' : 'errorStringArraySimple'; @@ -305,7 +238,7 @@ export default util.createRule({ if ( typeParams.length !== 1 || - (defaultOption === 'array-simple' && !isSimpleType(typeParams[0])) + (currentOption === 'array-simple' && !isSimpleType(typeParams[0])) ) { return; } diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index ec6ad4ccf8f..73c1f517d68 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -9,28 +9,137 @@ const ruleTester = new RuleTester({ ruleTester.run('array-type', rule, { valid: [ + // Base cases from https://github.com/typescript-eslint/typescript-eslint/issues/2323#issuecomment-663977655 { - code: 'let a: readonly any[] = [];', + code: 'let a: number[] = [];', options: [{ default: 'array' }], }, { - code: 'let a = new Array();', + code: 'let a: (string | number)[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: string[] = [];', + code: 'let a: readonly number[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: (string | number)[] = [];', + code: 'let a: readonly (string | number)[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: { foo: Bar[] }[] = [];', - options: [{ default: 'array' }], + code: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], }, { - code: 'let a: Array = [];', + code: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + }, + { + code: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', options: [{ default: 'generic' }], }, { @@ -38,13 +147,71 @@ ruleTester.run('array-type', rule, { options: [{ default: 'generic' }], }, { - code: 'let a: Array<{ foo: Array }> = [];', + code: 'let a: ReadonlyArray = [];', options: [{ default: 'generic' }], }, { - code: 'let fooVar: Array;', + code: 'let a: ReadonlyArray = [];', options: [{ default: 'generic' }], }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + { + code: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + { + code: 'let a: readonly number[] = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + { + code: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + }, + // End of base cases + + { + code: 'let a = new Array();', + options: [{ default: 'array' }], + }, + { + code: 'let a: { foo: Bar[] }[] = [];', + options: [{ default: 'array' }], + }, { code: 'function foo(a: Array): Array {}', options: [{ default: 'generic' }], @@ -147,10 +314,6 @@ interface FooInterface { code: 'type Unwrap = T extends (infer E)[] ? E : T;', options: [{ default: 'array' }], }, - { - code: "let z: Array = [3, '4'];", - options: [{ default: 'generic' }], - }, { code: 'let xx: Array> = [[1, 2], [3]];', options: [{ default: 'generic' }], @@ -193,53 +356,558 @@ function bazFunction(baz: Arr>) { options: [{ default: 'generic' }], }, - // readonly + // nested readonly { - code: 'let a: string[] = [];', + code: 'let a: ReadonlyArray = [[]];', options: [{ default: 'array', readonly: 'generic' }], }, + { + code: 'let a: readonly Array[] = [[]];', + options: [{ default: 'generic', readonly: 'array' }], + }, + ], + invalid: [ + // Base cases from https://github.com/typescript-eslint/typescript-eslint/issues/2323#issuecomment-663977655 + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, { code: 'let a: ReadonlyArray = [];', - options: [{ default: 'array', readonly: 'generic' }], + output: 'let a: readonly number[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', + options: [{ default: 'array-simple', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: Array = [];', + output: 'let a: number[] = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringArraySimple', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'array-simple', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: ReadonlyArray = [[]];', - options: [{ default: 'array', readonly: 'generic' }], + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: Array = [];', + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: readonly number[] = [];', + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], }, { - code: 'let a: readonly Array[] = [[]];', + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly (string | number)[] = [];', options: [{ default: 'generic', readonly: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], }, - ], - invalid: [ { - code: 'let a: Array = [];', - output: 'let a: string[] = [];', - options: [{ default: 'array' }], + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], errors: [ { - messageId: 'errorStringArray', - data: { type: 'string' }, + messageId: 'errorStringGeneric', + data: { type: 'number' }, line: 1, column: 8, }, ], }, { - code: 'let a: Array = [];', - output: 'let a: (string | number)[] = [];', - options: [{ default: 'array' }], + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], errors: [ { - messageId: 'errorStringArray', + messageId: 'errorStringGeneric', data: { type: 'T' }, line: 1, column: 8, @@ -247,26 +915,39 @@ function bazFunction(baz: Arr>) { ], }, { - code: 'let a: { foo: Array }[] = [];', - output: 'let a: { foo: Bar[] }[] = [];', - options: [{ default: 'array' }], + code: 'let a: ReadonlyArray = [];', + output: 'let a: readonly number[] = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], errors: [ { - messageId: 'errorStringArray', - data: { type: 'Bar' }, + messageId: 'errorStringArraySimple', + data: { type: 'number' }, line: 1, - column: 15, + column: 8, }, ], }, { - code: 'let a: string[] = [];', - output: 'let a: Array = [];', - options: [{ default: 'generic' }], + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'array-simple' }], + errors: [ + { + messageId: 'errorStringGenericSimple', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: number[] = [];', + output: 'let a: Array = [];', + options: [{ default: 'generic', readonly: 'generic' }], errors: [ { messageId: 'errorStringGeneric', - data: { type: 'string' }, + data: { type: 'number' }, line: 1, column: 8, }, @@ -275,7 +956,33 @@ function bazFunction(baz: Arr>) { { code: 'let a: (string | number)[] = [];', output: 'let a: Array = [];', - options: [{ default: 'generic' }], + options: [{ default: 'generic', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'T' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly number[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], + errors: [ + { + messageId: 'errorStringGeneric', + data: { type: 'number' }, + line: 1, + column: 8, + }, + ], + }, + { + code: 'let a: readonly (string | number)[] = [];', + output: 'let a: ReadonlyArray = [];', + options: [{ default: 'generic', readonly: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -285,6 +992,21 @@ function bazFunction(baz: Arr>) { }, ], }, + // End of base cases + + { + code: 'let a: { foo: Array }[] = [];', + output: 'let a: { foo: Bar[] }[] = [];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'Bar' }, + line: 1, + column: 15, + }, + ], + }, { code: 'let a: Array<{ foo: Bar[] }> = [];', output: 'let a: Array<{ foo: Array }> = [];', @@ -330,25 +1052,6 @@ function bazFunction(baz: Arr>) { }, ], }, - { - code: 'let a: Array<>[] = [];', - output: 'let a: any[][] = [];', - options: [{ default: 'array-simple' }], - errors: [ - { - messageId: 'errorStringGenericSimple', - data: { type: 'T' }, - line: 1, - column: 8, - }, - { - messageId: 'errorStringArraySimple', - data: { type: 'any' }, - line: 1, - column: 8, - }, - ], - }, { code: 'let x: Array = [undefined] as undefined[];', output: 'let x: undefined[] = [undefined] as undefined[];', @@ -362,19 +1065,6 @@ function bazFunction(baz: Arr>) { }, ], }, - { - code: 'let xx: Array = [];', - output: 'let xx: object[] = [];', - options: [{ default: 'array-simple' }], - errors: [ - { - messageId: 'errorStringArraySimple', - data: { type: 'object' }, - line: 1, - column: 9, - }, - ], - }, { code: "let y: string[] = >['2'];", output: "let y: string[] = ['2'];", @@ -716,28 +1406,53 @@ function fooFunction(foo: ArrayClass[]) { ], }, { - code: 'let fooVar: Array[];', - output: 'let fooVar: any[][];', + code: 'let x: Array;', + output: 'let x: any[];', options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'any' }, line: 1, - column: 13, + column: 8, + }, + ], + }, + { + code: 'let x: Array<>;', + output: 'let x: any[];', + options: [{ default: 'array' }], + errors: [ + { + messageId: 'errorStringArray', + data: { type: 'any' }, + line: 1, + column: 8, }, ], }, { - code: 'let fooVar: Array[];', - output: 'let fooVar: any[][];', + code: 'let x: Array;', + output: 'let x: any[];', options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', data: { type: 'any' }, line: 1, - column: 13, + column: 8, + }, + ], + }, + { + code: 'let x: Array<>;', + output: 'let x: any[];', + options: [{ default: 'array-simple' }], + errors: [ + { + messageId: 'errorStringArraySimple', + line: 1, + column: 8, }, ], }, @@ -933,86 +1648,6 @@ interface FooInterface { }, ], }, - - // readonly tests - { - code: 'const x: readonly number[] = [];', - output: 'const x: ReadonlyArray = [];', - options: [{ default: 'generic' }], - errors: [ - { - messageId: 'errorStringGeneric', - data: { type: 'number' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: readonly (number | string | boolean)[] = [];', - output: 'const x: ReadonlyArray = [];', - options: [{ default: 'generic' }], - errors: [ - { - messageId: 'errorStringGeneric', - data: { type: 'T' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: ReadonlyArray = [];', - output: 'const x: readonly number[] = [];', - options: [{ default: 'array' }], - errors: [ - { - messageId: 'errorStringArray', - data: { type: 'number' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: ReadonlyArray = [];', - output: 'const x: readonly (number | string | boolean)[] = [];', - options: [{ default: 'array' }], - errors: [ - { - messageId: 'errorStringArray', - data: { type: 'T' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: readonly number[] = [];', - output: 'const x: ReadonlyArray = [];', - options: [{ default: 'array', readonly: 'generic' }], - errors: [ - { - messageId: 'errorStringGenericSimple', - data: { type: 'number' }, - line: 1, - column: 10, - }, - ], - }, - { - code: 'const x: readonly number[][] = [];', - output: 'const x: readonly Array[] = [];', - options: [{ default: 'generic', readonly: 'array' }], - errors: [ - { - messageId: 'errorStringGeneric', - data: { type: 'number' }, - line: 1, - column: 19, - }, - ], - }, ], }); @@ -1093,6 +1728,39 @@ class Foo extends Bar implements Baz { 'let yy: number[][] = [[4, 5], [6]];', 'let yy: Array> = [[4, 5], [6]];', ); + testOutput('array', 'let a: Array<>[] = [];', 'let a: any[][] = [];'); + testOutput('array', 'let a: Array = [];', 'let a: any[][] = [];'); + testOutput( + 'array', + 'let a: Array[] = [];', + 'let a: any[][][] = [];', + ); + + testOutput( + 'generic', + 'let a: Array<>[] = [];', + 'let a: Array> = [];', + ); + testOutput( + 'generic', + 'let a: Array = [];', + 'let a: Array> = [];', + ); + testOutput( + 'generic', + 'let a: Array[] = [];', + 'let a: Array>> = [];', + ); + testOutput( + 'generic', + 'let a: Array[] = [];', + 'let a: Array> = [];', + ); + testOutput( + 'generic', + 'let a: Array[] = [];', + 'let a: Array>> = [];', + ); // readonly testOutput( @@ -1120,30 +1788,12 @@ class Foo extends Bar implements Baz { 'let x: ReadonlyArray', 'let x: readonly (readonly number[])[]', ); - testOutput( - 'array', - 'let x: ReadonlyArray', - 'let x: ReadonlyArray', - 'generic', - ); - testOutput( - 'array', - 'let a: string[] = []', - 'let a: string[] = []', - 'generic', - ); testOutput( 'array', 'let a: readonly number[][] = []', 'let a: ReadonlyArray = []', 'generic', ); - testOutput( - 'generic', - 'let a: string[] = []', - 'let a: Array = []', - 'array', - ); testOutput( 'generic', 'let a: readonly number[][] = []',