diff --git a/crates/oxc_linter/src/rules/typescript/array_type.rs b/crates/oxc_linter/src/rules/typescript/array_type.rs index 15772a17e4f40..547292d287066 100644 --- a/crates/oxc_linter/src/rules/typescript/array_type.rs +++ b/crates/oxc_linter/src/rules/typescript/array_type.rs @@ -220,6 +220,21 @@ impl Rule for ArrayType { check(param, default_config, readonly_config, ctx); } } + AstKind::TSConditionalType(ts_conditional_type) => { + check(&ts_conditional_type.check_type, default_config, readonly_config, ctx); + check(&ts_conditional_type.extends_type, default_config, readonly_config, ctx); + check(&ts_conditional_type.true_type, default_config, readonly_config, ctx); + check(&ts_conditional_type.false_type, default_config, readonly_config, ctx); + } + AstKind::TSIndexedAccessType(ts_indexed_access_type) => { + check(&ts_indexed_access_type.object_type, default_config, readonly_config, ctx); + check(&ts_indexed_access_type.index_type, default_config, readonly_config, ctx); + } + AstKind::TSMappedType(ts_mapped_type) => { + if let Some(type_annotation) = &ts_mapped_type.type_annotation { + check(type_annotation, default_config, readonly_config, ctx); + } + } _ => {} } } @@ -890,6 +905,71 @@ fn test() { "let a: readonly Array[] = [[]];", Some(serde_json::json!([{"default":"generic","readonly":"array"}])), ), + ( + "function testFn(param: T) { return param; } +export const test = testFn([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn>([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test = testFn([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test = testFn, Array>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn>([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn('hello');", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test = testFn<{name: string}>({name: 'test'});", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass(42);", + Some(serde_json::json!([{"default":"generic"}])), + ), ]; let fail = vec![ @@ -1291,6 +1371,99 @@ export const test2 = testFn([]);", export const test2 = testFn>([]);", Some(serde_json::json!([{"default":"array"}])), ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test3 = testFn([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test3 = testFn, Array>([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test4 = testFn>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test4 = testFn>>([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test5 = testFn<(string & number)[]>([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test5 = testFn<(() => void)[]>([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + // Array of arrays in generic arguments + // Note: When checking types in generic arguments, the rule checks recursively, + // so string[][] will trigger errors for both the outer and inner array types. + // This is different from checking a standalone type annotation where only the + // outermost type is checked. + // Class generic instantiation + ( + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass([1, 2, 3]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass>([1, 2, 3]);", + Some(serde_json::json!([{"default":"array"}])), + ), + // Type assertion with generic + ( + "const value = {} as Map;", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "const value = {} as Map>;", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "interface Container { value: T; } +const container: Container = { value: [] };", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "interface Container { value: T; } +const container: Container> = { value: [] };", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test7 = testFn([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test7 = testFn, ReadonlyArray>([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test8 = testFn>([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test8 = testFn, number[]>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "type IsArray = T extends any[] ? true : false;", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "type MakeArrays = { [K in keyof T]: T[K][] };", + Some(serde_json::json!([{"default":"generic"}])), + ), ]; let fix: Vec<(&str, &str, Option)> = vec![ @@ -1865,6 +2038,83 @@ export const test2 = testFn>([]);", export const test2 = testFn([]);", Some(serde_json::json!([{"default":"array"}])), ), + // Multiple type parameters - fix tests + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test3 = testFn([]);", + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test3 = testFn, Array>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test3 = testFn, Array>([]);", + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test3 = testFn([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + // Complex types in generic arguments - fix tests + ( + "function testFn(param: T) { return param; } +export const test5 = testFn<(string & number)[]>([]);", + "function testFn(param: T) { return param; } +export const test5 = testFn>([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test5 = testFn<(() => void)[]>([]);", + "function testFn(param: T) { return param; } +export const test5 = testFn void>>([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + // Note: Nested arrays in generic arguments are checked recursively, + // so fixes are applied at each level independently + // Class generic instantiation - fix tests + ( + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass([1, 2, 3]);", + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass>([1, 2, 3]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass>([1, 2, 3]);", + "class MyClass { constructor(public value: T) {} } +const instance = new MyClass([1, 2, 3]);", + Some(serde_json::json!([{"default":"array"}])), + ), + // Readonly arrays in multiple type parameters - fix tests + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test7 = testFn([]);", + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test7 = testFn, ReadonlyArray>([]);", + Some(serde_json::json!([{"default":"generic"}])), + ), + ( + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test7 = testFn, ReadonlyArray>([]);", + "function testFn(param1: T, param2: U) { return [param1, param2]; } +export const test7 = testFn([]);", + Some(serde_json::json!([{"default":"array"}])), + ), + // array-simple with simple types in generics + ( + "function testFn(param: T) { return param; } +export const test9 = testFn>([]);", + "function testFn(param: T) { return param; } +export const test9 = testFn([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), + ( + "function testFn(param: T) { return param; } +export const test9 = testFn>([]);", + "function testFn(param: T) { return param; } +export const test9 = testFn([]);", + Some(serde_json::json!([{"default":"array-simple"}])), + ), ]; Tester::new(ArrayType::NAME, ArrayType::PLUGIN, pass, fail) diff --git a/crates/oxc_linter/src/snapshots/typescript_array_type.snap b/crates/oxc_linter/src/snapshots/typescript_array_type.snap index 79f347487120a..eab2c40805bdb 100644 --- a/crates/oxc_linter/src/snapshots/typescript_array_type.snap +++ b/crates/oxc_linter/src/snapshots/typescript_array_type.snap @@ -734,3 +734,191 @@ source: crates/oxc_linter/src/tester.rs · ───────────────────── ╰──── help: Replace `ReadonlyArray` with `readonly string[]`. + + ⚠ typescript-eslint(array-type): Array type using 'string[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:29] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test3 = testFn([]); + · ──────── + ╰──── + help: Replace `string[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:39] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test3 = testFn([]); + · ──────── + ╰──── + help: Replace `number[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'string[]' instead. + ╭─[array_type.ts:2:29] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test3 = testFn, Array>([]); + · ───────────── + ╰──── + help: Replace `Array` with `string[]`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. + ╭─[array_type.ts:2:44] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test3 = testFn, Array>([]); + · ───────────── + ╰──── + help: Replace `Array` with `number[]`. + + ⚠ typescript-eslint(array-type): Array type using 'string[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:37] + 1 │ function testFn(param: T) { return param; } + 2 │ export const test4 = testFn>([]); + · ──────── + ╰──── + help: Replace `string[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'string[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:37] + 1 │ function testFn(param: T) { return param; } + 2 │ export const test4 = testFn>([]); + · ──────── + ╰──── + help: Replace `string[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'string[]' instead. + ╭─[array_type.ts:2:37] + 1 │ function testFn(param: T) { return param; } + 2 │ export const test4 = testFn>>([]); + · ───────────── + ╰──── + help: Replace `Array` with `string[]`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'string[]' instead. + ╭─[array_type.ts:2:37] + 1 │ function testFn(param: T) { return param; } + 2 │ export const test4 = testFn>>([]); + · ───────────── + ╰──── + help: Replace `Array` with `string[]`. + + ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. + ╭─[array_type.ts:2:29] + 1 │ function testFn(param: T) { return param; } + 2 │ export const test5 = testFn<(string & number)[]>([]); + · ─────────────────── + ╰──── + help: Replace `(string & number)[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden for non-simple types. Use 'Array' instead. + ╭─[array_type.ts:2:29] + 1 │ function testFn(param: T) { return param; } + 2 │ export const test5 = testFn<(() => void)[]>([]); + · ────────────── + ╰──── + help: Replace `(() => void)[]` with `Array<() => void>`. + + ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:30] + 1 │ class MyClass { constructor(public value: T) {} } + 2 │ const instance = new MyClass([1, 2, 3]); + · ──────── + ╰──── + help: Replace `number[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. + ╭─[array_type.ts:2:30] + 1 │ class MyClass { constructor(public value: T) {} } + 2 │ const instance = new MyClass>([1, 2, 3]); + · ───────────── + ╰──── + help: Replace `Array` with `number[]`. + + ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:1:33] + 1 │ const value = {} as Map; + · ──────── + ╰──── + help: Replace `number[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. + ╭─[array_type.ts:1:33] + 1 │ const value = {} as Map>; + · ───────────── + ╰──── + help: Replace `Array` with `number[]`. + + ⚠ typescript-eslint(array-type): Array type using 'string[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:28] + 1 │ interface Container { value: T; } + 2 │ const container: Container = { value: [] }; + · ──────── + ╰──── + help: Replace `string[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'string[]' instead. + ╭─[array_type.ts:2:28] + 1 │ interface Container { value: T; } + 2 │ const container: Container> = { value: [] }; + · ───────────── + ╰──── + help: Replace `Array` with `string[]`. + + ⚠ typescript-eslint(array-type): Array type using 'readonly string[]' is forbidden. Use 'ReadonlyArray' instead. + ╭─[array_type.ts:2:29] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test7 = testFn([]); + · ───────────────── + ╰──── + help: Replace `readonly string[]` with `ReadonlyArray`. + + ⚠ typescript-eslint(array-type): Array type using 'readonly number[]' is forbidden. Use 'ReadonlyArray' instead. + ╭─[array_type.ts:2:48] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test7 = testFn([]); + · ───────────────── + ╰──── + help: Replace `readonly number[]` with `ReadonlyArray`. + + ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly string[]' instead. + ╭─[array_type.ts:2:29] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test7 = testFn, ReadonlyArray>([]); + · ───────────────────── + ╰──── + help: Replace `ReadonlyArray` with `readonly string[]`. + + ⚠ typescript-eslint(array-type): Array type using 'ReadonlyArray' is forbidden. Use 'readonly number[]' instead. + ╭─[array_type.ts:2:52] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test7 = testFn, ReadonlyArray>([]); + · ───────────────────── + ╰──── + help: Replace `ReadonlyArray` with `readonly number[]`. + + ⚠ typescript-eslint(array-type): Array type using 'Array' is forbidden. Use 'number[]' instead. + ╭─[array_type.ts:2:39] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test8 = testFn>([]); + · ───────────── + ╰──── + help: Replace `Array` with `number[]`. + + ⚠ typescript-eslint(array-type): Array type using 'number[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:2:44] + 1 │ function testFn(param1: T, param2: U) { return [param1, param2]; } + 2 │ export const test8 = testFn, number[]>([]); + · ──────── + ╰──── + help: Replace `number[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'any[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:1:29] + 1 │ type IsArray = T extends any[] ? true : false; + · ───── + ╰──── + help: Replace `any[]` with `Array`. + + ⚠ typescript-eslint(array-type): Array type using 'T[]' is forbidden. Use 'Array' instead. + ╭─[array_type.ts:1:40] + 1 │ type MakeArrays = { [K in keyof T]: T[K][] }; + · ────── + ╰──── + help: Replace `T[K][]` with `Array`.