diff --git a/packages/eslint-plugin/docs/rules/array-type.md b/packages/eslint-plugin/docs/rules/array-type.md index 8906f0c9a4c..275b4fbd617 100644 --- a/packages/eslint-plugin/docs/rules/array-type.md +++ b/packages/eslint-plugin/docs/rules/array-type.md @@ -8,13 +8,26 @@ This rule aims to standardise usage of array types within your codebase. ## Options -This rule accepts one option - a single string +```ts +type ArrayOption = 'array' | 'generic' | 'array-simple'; +type Options = { + default: ArrayOption; + readonly?: ArrayOption; +}; + +const defaultOptions: Options = { + default: 'array', +}; +``` + +The rule accepts an options object with the following properties: + +- `default` - sets the array type expected for mutable cases. +- `readonly` - sets the array type expected for readonly arrays. If this is omitted, then the value for `default` will be used. -- `"array"` enforces use of `T[]` for all types `T`. -- `"generic"` enforces use of `Array` for all types `T`. -- `"array-simple"` enforces use of `T[]` if `T` is a simple type. +Each property can be set to one of three strings: `'array' | 'generic' | 'array-simple'`. -Without providing an option, by default the rule will enforce `"array"`. +The default config will enforce that all mutable and readonly arrays use the `'array'` syntax. ### `"array"` diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 02559789b5e..ee0b35fdde8 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -73,13 +73,20 @@ function typeNeedsParentheses(node: TSESTree.Node): boolean { } export type OptionString = 'array' | 'generic' | 'array-simple'; -type Options = [OptionString]; +type Options = [ + { + default: OptionString; + readonly?: OptionString; + } +]; type MessageIds = | 'errorStringGeneric' | 'errorStringGenericSimple' | 'errorStringArray' | 'errorStringArraySimple'; +const arrayOption = { enum: ['array', 'generic', 'array-simple'] }; + export default util.createRule({ name: 'array-type', meta: { @@ -102,14 +109,32 @@ export default util.createRule({ }, schema: [ { - enum: ['array', 'generic', 'array-simple'], + type: 'object', + properties: { + default: arrayOption, + readonly: arrayOption, + }, }, ], }, - defaultOptions: ['array'], - create(context, [option]) { + defaultOptions: [ + { + default: 'array', + }, + ], + create(context, [options]) { const sourceCode = context.getSourceCode(); + 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. @@ -143,22 +168,36 @@ export default util.createRule({ } return { - TSArrayType(node) { + TSArrayType(node: TSESTree.TSArrayType) { if ( - option === 'array' || - (option === 'array-simple' && isSimpleType(node.elementType)) + isArrayOption || + (isArraySimpleOption && isSimpleType(node.elementType)) ) { return; } - const messageId = - option === 'generic' - ? 'errorStringGeneric' - : 'errorStringGenericSimple'; 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'; + + if ( + (isReadonlyGeneric && !isReadonly) || + (isReadonlyArray && isReadonly) + ) { + return; + } + + const messageId = + defaultOption === 'generic' + ? 'errorStringGeneric' + : 'errorStringGenericSimple'; const typeOpNode = isReadonly ? node.parent! : null; context.report({ @@ -201,23 +240,32 @@ export default util.createRule({ }, }); }, + TSTypeReference(node: TSESTree.TSTypeReference) { if ( - option === 'generic' || + isGenericOption || node.typeName.type !== AST_NODE_TYPES.Identifier ) { return; } - if (!['Array', 'ReadonlyArray'].includes(node.typeName.name)) { + + const isReadonlyArrayType = node.typeName.name === 'ReadonlyArray'; + const isArrayType = node.typeName.name === 'Array'; + + if ( + !(isArrayType || isReadonlyArrayType) || + (readonlyOption === 'generic' && isReadonlyArrayType) || + (defaultOption === 'generic' && !isReadonlyArrayType) + ) { return; } - const messageId = - option === 'array' ? 'errorStringArray' : 'errorStringArraySimple'; - const isReadonly = node.typeName.name === 'ReadonlyArray'; - const readonlyPrefix = isReadonly ? 'readonly ' : ''; - + const readonlyPrefix = isReadonlyArrayType ? 'readonly ' : ''; const typeParams = node.typeParameters && node.typeParameters.params; + const messageId = + defaultOption === 'array' + ? 'errorStringArray' + : 'errorStringArraySimple'; if (!typeParams || typeParams.length === 0) { // Create an 'any' array @@ -231,12 +279,13 @@ export default util.createRule({ return fixer.replaceText(node, `${readonlyPrefix}any[]`); }, }); + return; } if ( typeParams.length !== 1 || - (option === 'array-simple' && !isSimpleType(typeParams[0])) + (defaultOption === '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 5e90bf07301..32de7e32c89 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -11,175 +11,201 @@ ruleTester.run('array-type', rule, { valid: [ { code: 'let a: readonly any[] = []', - options: ['array'], + options: [{ default: 'array' }], }, { code: 'let a = new Array()', - options: ['array'], + options: [{ default: 'array' }], }, { code: 'let a: string[] = []', - options: ['array'], + options: [{ default: 'array' }], }, { code: 'let a: (string | number)[] = []', - options: ['array'], + options: [{ default: 'array' }], }, { code: 'let a: ({ foo: Bar[] })[] = []', - options: ['array'], + options: [{ default: 'array' }], }, { code: 'let a: Array = []', - options: ['generic'], + options: [{ default: 'generic' }], }, { - code: 'let a: Array = []', - options: ['generic'], + code: 'let a: Array = []', + options: [{ default: 'generic' }], }, { code: 'let a: Array<{ foo: Array }> = []', - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `let fooVar: Array;`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `function foo (a: Array): Array {}`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `let yy: number[][] = [[4, 5], [6]];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { code: `function fooFunction(foo: Array>) { return foo.map(e => e.foo); }`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { code: `function bazFunction(baz: Arr>) { return baz.map(e => e.baz); }`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { code: `let fooVar: Array<(c: number) => number>;`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { - code: `type fooUnion = Array;`, - options: ['array-simple'], + code: `type fooUnion = Array;`, + options: [{ default: 'array-simple' }], }, { code: `type fooIntersection = Array;`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { code: `namespace fooName { type BarType = { bar: string }; type BazType = Arr; }`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { code: `interface FooInterface { '.bar': {baz: string[];}; }`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], }, { code: `let yy: number[][] = [[4, 5], [6]];`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `let ya = [[1, "2"]] as[number, string][];`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `function barFunction(bar: ArrayClass[]) { return bar.map(e => e.bar); }`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `function bazFunction(baz: Arr>) { return baz.map(e => e.baz); }`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `let barVar: ((c: number) => number)[];`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `type barUnion = (string|number|boolean)[];`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `type barIntersection = (string & number)[];`, - options: ['array'], + options: [{ default: 'array' }], }, { code: `interface FooInterface { '.bar': {baz: string[];}; }`, - options: ['array'], + options: [{ default: 'array' }], }, { // https://github.com/typescript-eslint/typescript-eslint/issues/172 code: 'type Unwrap = T extends (infer E)[] ? E : T', - options: ['array'], + options: [{ default: 'array' }], }, { code: `let z: Array = [3, "4"];`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `let xx: Array> = [[1, 2], [3]];`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `type Arr = Array;`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `function fooFunction(foo: Array>) { return foo.map(e => e.foo); }`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `function bazFunction(baz: Arr>) { return baz.map(e => e.baz); }`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `let fooVar: Array<(c: number) => number>;`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `type fooUnion = Array;`, - options: ['generic'], + options: [{ default: 'generic' }], }, { code: `type fooIntersection = Array;`, - options: ['generic'], + options: [{ default: 'generic' }], }, { // https://github.com/typescript-eslint/typescript-eslint/issues/172 code: 'type Unwrap = T extends Array ? E : T', - options: ['generic'], + options: [{ default: 'generic' }], + }, + + // readonly + { + code: 'let a: string[] = []', + 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: Array = []`, + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: `let a: readonly number[] = []`, + options: [{ default: 'generic', readonly: 'array' }], + }, + { + code: `let a: readonly Array[] = [[]]`, + options: [{ default: 'generic', readonly: 'array' }], }, ], invalid: [ { code: 'let a: Array = []', output: 'let a: string[] = []', - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -192,7 +218,7 @@ ruleTester.run('array-type', rule, { { code: 'let a: Array = []', output: 'let a: (string | number)[] = []', - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -205,7 +231,7 @@ ruleTester.run('array-type', rule, { { code: 'let a: ({ foo: Array })[] = []', output: 'let a: ({ foo: Bar[] })[] = []', - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -218,7 +244,7 @@ ruleTester.run('array-type', rule, { { code: 'let a: string[] = []', output: 'let a: Array = []', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -231,7 +257,7 @@ ruleTester.run('array-type', rule, { { code: 'let a: (string | number)[] = []', output: 'let a: Array = []', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -244,7 +270,7 @@ ruleTester.run('array-type', rule, { { code: 'let a: Array<{ foo: Bar[] }> = []', output: 'let a: Array<{ foo: Array }> = []', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -257,7 +283,7 @@ ruleTester.run('array-type', rule, { { code: 'let a: Array<{ foo: Foo | Bar[] }> = []', output: 'let a: Array<{ foo: Foo | Array }> = []', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -270,6 +296,7 @@ ruleTester.run('array-type', rule, { { code: 'function foo (a: Array): Array {}', output: 'function foo (a: Bar[]): Bar[] {}', + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -288,7 +315,7 @@ ruleTester.run('array-type', rule, { { code: `let x: Array = [undefined] as undefined[];`, output: `let x: undefined[] = [undefined] as undefined[];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -301,7 +328,7 @@ ruleTester.run('array-type', rule, { { code: `let xx: Array = [];`, output: `let xx: object[] = [];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -314,7 +341,7 @@ ruleTester.run('array-type', rule, { { code: `let y: string[] = >["2"];`, output: `let y: string[] = ["2"];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -327,7 +354,7 @@ ruleTester.run('array-type', rule, { { code: `let z: Array = [3, "4"];`, output: `let z: any[] = [3, "4"];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -340,7 +367,7 @@ ruleTester.run('array-type', rule, { { code: `let ya = [[1, "2"]] as[number, string][];`, output: `let ya = [[1, "2"]] as Array<[number, string]>;`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -353,7 +380,7 @@ ruleTester.run('array-type', rule, { { code: `type Arr = Array;`, output: `type Arr = T[];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -368,7 +395,7 @@ ruleTester.run('array-type', rule, { let yyyy: Arr>[]> = [[[["2"]]]];`, output: `// Ignore user defined aliases let yyyy: Arr>>> = [[[["2"]]]];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -391,7 +418,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, baz: Arr; xyz: this[]; }`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -408,7 +435,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, output: `function barFunction(bar: Array>) { return bar.map(e => e.bar); }`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -421,7 +448,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let barVar: ((c: number) => number)[];`, output: `let barVar: Array<(c: number) => number>;`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -434,7 +461,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `type barUnion = (string|number|boolean)[];`, output: `type barUnion = Array;`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -447,7 +474,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `type barIntersection = (string & number)[];`, output: `type barIntersection = Array;`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -460,7 +487,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let v: Array = [{ bar: "bar" }];`, output: `let v: fooName.BarType[] = [{ bar: "bar" }];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -473,7 +500,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let w: fooName.BazType[] = [["baz"]];`, output: `let w: Array> = [["baz"]];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', @@ -486,7 +513,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let x: Array = [undefined] as undefined[];`, output: `let x: undefined[] = [undefined] as undefined[];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -499,7 +526,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let y: string[] = >["2"];`, output: `let y: string[] = ["2"];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -512,7 +539,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let z: Array = [3, "4"];`, output: `let z: any[] = [3, "4"];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -525,7 +552,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `type Arr = Array;`, output: `type Arr = T[];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -540,7 +567,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, let yyyy: Arr>[]> = [[[["2"]]]];`, output: `// Ignore user defined aliases let yyyy: Arr[][]> = [[[["2"]]]];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -561,7 +588,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, bar: T[]; baz: Arr; }`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -578,7 +605,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, output: `function fooFunction(foo: ArrayClass[]) { return foo.map(e => e.foo); }`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -591,7 +618,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, { code: `let fooVar: Array<(c: number) => number>;`, output: `let fooVar: ((c: number) => number)[];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -604,7 +631,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, { code: `type fooUnion = Array;`, output: `type fooUnion = (string|number|boolean)[];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -617,7 +644,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, { code: `type fooIntersection = Array;`, output: `type fooIntersection = (string & number)[];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -629,7 +656,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, }, { code: `let fooVar: Array[];`, - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -641,7 +668,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, }, { code: `let fooVar: Array[];`, - options: ['array-simple'], + options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', @@ -654,7 +681,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, { code: `let x: Array = [1] as number[];`, output: `let x: Array = [1] as Array;`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -667,7 +694,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, { code: `let y: string[] = >["2"];`, output: `let y: Array = >["2"];`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -680,7 +707,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, { code: `let ya = [[1, "2"]] as[number, string][];`, output: `let ya = [[1, "2"]] as Array<[number, string]>;`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -695,7 +722,7 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, let yyyy: Arr>[]> = [[[["2"]]]];`, output: `// Ignore user defined aliases let yyyy: Arr>>> = [[[["2"]]]];`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -716,7 +743,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, bar: Array; baz: Arr; }`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -733,7 +760,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, output: `function barFunction(bar: Array>) { return bar.map(e => e.bar); }`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -746,7 +773,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `let barVar: ((c: number) => number)[];`, output: `let barVar: Array<(c: number) => number>;`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -759,7 +786,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `type barUnion = (string|number|boolean)[];`, output: `type barUnion = Array;`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -772,7 +799,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: `type barIntersection = (string & number)[];`, output: `type barIntersection = Array;`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -789,7 +816,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, output: `interface FooInterface { '.bar': {baz: Array;}; }`, - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -803,7 +830,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, // https://github.com/typescript-eslint/typescript-eslint/issues/172 code: 'type Unwrap = T extends Array ? E : T', output: 'type Unwrap = T extends (infer E)[] ? E : T', - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -817,7 +844,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, // https://github.com/typescript-eslint/typescript-eslint/issues/172 code: 'type Unwrap = T extends (infer E)[] ? E : T', output: 'type Unwrap = T extends Array ? E : T', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -832,7 +859,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: 'const x: readonly number[] = [];', output: 'const x: ReadonlyArray = [];', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -845,7 +872,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: 'const x: readonly (number | string | boolean)[] = [];', output: 'const x: ReadonlyArray = [];', - options: ['generic'], + options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', @@ -858,7 +885,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: 'const x: ReadonlyArray = [];', output: 'const x: readonly number[] = [];', - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -871,7 +898,7 @@ let yyyy: Arr>>> = [[[["2"]]]];`, { code: 'const x: ReadonlyArray = [];', output: 'const x: readonly (number | string | boolean)[] = [];', - options: ['array'], + options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', @@ -881,6 +908,32 @@ let yyyy: Arr>>> = [[[["2"]]]];`, }, ], }, + { + 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, + }, + ], + }, ], }); @@ -893,16 +946,20 @@ describe('array-type (nested)', () => { describe('should deeply fix correctly', () => { function testOutput( - option: OptionString, + defaultOption: OptionString, code: string, output: string, + readonlyOption?: OptionString, ): void { it(code, () => { const result = linter.verifyAndFix( code, { rules: { - 'array-type': ['error', option], + 'array-type': [ + 2, + { default: defaultOption, readonly: readonlyOption }, + ], }, parser: '@typescript-eslint/parser', }, @@ -912,7 +969,6 @@ describe('array-type (nested)', () => { ); expect(result.messages).toHaveLength(0); - expect(result.fixed).toBeTruthy(); expect(result.output).toBe(output); }); } @@ -985,5 +1041,35 @@ 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[][] = []`, + `let a: readonly Array[] = []`, + 'array', + ); }); });