Skip to content

Commit 248ac82

Browse files
committed
fix(linter/array-type): fix more false negatives
1 parent cc19c8b commit 248ac82

File tree

2 files changed

+438
-0
lines changed

2 files changed

+438
-0
lines changed

crates/oxc_linter/src/rules/typescript/array_type.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,21 @@ impl Rule for ArrayType {
220220
check(param, default_config, readonly_config, ctx);
221221
}
222222
}
223+
AstKind::TSConditionalType(ts_conditional_type) => {
224+
check(&ts_conditional_type.check_type, default_config, readonly_config, ctx);
225+
check(&ts_conditional_type.extends_type, default_config, readonly_config, ctx);
226+
check(&ts_conditional_type.true_type, default_config, readonly_config, ctx);
227+
check(&ts_conditional_type.false_type, default_config, readonly_config, ctx);
228+
}
229+
AstKind::TSIndexedAccessType(ts_indexed_access_type) => {
230+
check(&ts_indexed_access_type.object_type, default_config, readonly_config, ctx);
231+
check(&ts_indexed_access_type.index_type, default_config, readonly_config, ctx);
232+
}
233+
AstKind::TSMappedType(ts_mapped_type) => {
234+
if let Some(type_annotation) = &ts_mapped_type.type_annotation {
235+
check(type_annotation, default_config, readonly_config, ctx);
236+
}
237+
}
223238
_ => {}
224239
}
225240
}
@@ -890,6 +905,71 @@ fn test() {
890905
"let a: readonly Array<number>[] = [[]];",
891906
Some(serde_json::json!([{"default":"generic","readonly":"array"}])),
892907
),
908+
(
909+
"function testFn<T>(param: T) { return param; }
910+
export const test = testFn<string[]>([]);",
911+
Some(serde_json::json!([{"default":"array"}])),
912+
),
913+
(
914+
"function testFn<T>(param: T) { return param; }
915+
export const test = testFn<Array<string>>([]);",
916+
Some(serde_json::json!([{"default":"generic"}])),
917+
),
918+
(
919+
"function testFn<T>(param: T) { return param; }
920+
export const test = testFn<string[]>([]);",
921+
Some(serde_json::json!([{"default":"array-simple"}])),
922+
),
923+
(
924+
"function testFn<T>(param: T) { return param; }
925+
export const test = testFn<Array<{name: string}>>([]);",
926+
Some(serde_json::json!([{"default":"array-simple"}])),
927+
),
928+
(
929+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
930+
export const test = testFn<string[], number[]>([]);",
931+
Some(serde_json::json!([{"default":"array"}])),
932+
),
933+
(
934+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
935+
export const test = testFn<Array<string>, Array<number>>([]);",
936+
Some(serde_json::json!([{"default":"generic"}])),
937+
),
938+
(
939+
"function testFn<T>(param: T) { return param; }
940+
export const test = testFn<readonly string[]>([]);",
941+
Some(serde_json::json!([{"default":"array"}])),
942+
),
943+
(
944+
"function testFn<T>(param: T) { return param; }
945+
export const test = testFn<ReadonlyArray<string>>([]);",
946+
Some(serde_json::json!([{"default":"generic"}])),
947+
),
948+
(
949+
"function testFn<T>(param: T) { return param; }
950+
export const test = testFn<readonly string[]>([]);",
951+
Some(serde_json::json!([{"default":"array-simple"}])),
952+
),
953+
(
954+
"function testFn<T>(param: T) { return param; }
955+
export const test = testFn<ReadonlyArray<{name: string}>>([]);",
956+
Some(serde_json::json!([{"default":"array-simple"}])),
957+
),
958+
(
959+
"function testFn<T>(param: T) { return param; }
960+
export const test = testFn<string>('hello');",
961+
Some(serde_json::json!([{"default":"array"}])),
962+
),
963+
(
964+
"function testFn<T>(param: T) { return param; }
965+
export const test = testFn<{name: string}>({name: 'test'});",
966+
Some(serde_json::json!([{"default":"array-simple"}])),
967+
),
968+
(
969+
"class MyClass<T> { constructor(public value: T) {} }
970+
const instance = new MyClass<number>(42);",
971+
Some(serde_json::json!([{"default":"generic"}])),
972+
),
893973
];
894974

895975
let fail = vec![
@@ -1291,6 +1371,99 @@ export const test2 = testFn<readonly string[]>([]);",
12911371
export const test2 = testFn<ReadonlyArray<string>>([]);",
12921372
Some(serde_json::json!([{"default":"array"}])),
12931373
),
1374+
(
1375+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
1376+
export const test3 = testFn<string[], number[]>([]);",
1377+
Some(serde_json::json!([{"default":"generic"}])),
1378+
),
1379+
(
1380+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
1381+
export const test3 = testFn<Array<string>, Array<number>>([]);",
1382+
Some(serde_json::json!([{"default":"array"}])),
1383+
),
1384+
(
1385+
"function testFn<T>(param: T) { return param; }
1386+
export const test4 = testFn<Promise<string[]>>([]);",
1387+
Some(serde_json::json!([{"default":"generic"}])),
1388+
),
1389+
(
1390+
"function testFn<T>(param: T) { return param; }
1391+
export const test4 = testFn<Promise<Array<string>>>([]);",
1392+
Some(serde_json::json!([{"default":"array"}])),
1393+
),
1394+
(
1395+
"function testFn<T>(param: T) { return param; }
1396+
export const test5 = testFn<(string & number)[]>([]);",
1397+
Some(serde_json::json!([{"default":"array-simple"}])),
1398+
),
1399+
(
1400+
"function testFn<T>(param: T) { return param; }
1401+
export const test5 = testFn<(() => void)[]>([]);",
1402+
Some(serde_json::json!([{"default":"array-simple"}])),
1403+
),
1404+
// Array of arrays in generic arguments
1405+
// Note: When checking types in generic arguments, the rule checks recursively,
1406+
// so string[][] will trigger errors for both the outer and inner array types.
1407+
// This is different from checking a standalone type annotation where only the
1408+
// outermost type is checked.
1409+
// Class generic instantiation
1410+
(
1411+
"class MyClass<T> { constructor(public value: T) {} }
1412+
const instance = new MyClass<number[]>([1, 2, 3]);",
1413+
Some(serde_json::json!([{"default":"generic"}])),
1414+
),
1415+
(
1416+
"class MyClass<T> { constructor(public value: T) {} }
1417+
const instance = new MyClass<Array<number>>([1, 2, 3]);",
1418+
Some(serde_json::json!([{"default":"array"}])),
1419+
),
1420+
// Type assertion with generic
1421+
(
1422+
"const value = {} as Map<string, number[]>;",
1423+
Some(serde_json::json!([{"default":"generic"}])),
1424+
),
1425+
(
1426+
"const value = {} as Map<string, Array<number>>;",
1427+
Some(serde_json::json!([{"default":"array"}])),
1428+
),
1429+
(
1430+
"interface Container<T> { value: T; }
1431+
const container: Container<string[]> = { value: [] };",
1432+
Some(serde_json::json!([{"default":"generic"}])),
1433+
),
1434+
(
1435+
"interface Container<T> { value: T; }
1436+
const container: Container<Array<string>> = { value: [] };",
1437+
Some(serde_json::json!([{"default":"array"}])),
1438+
),
1439+
(
1440+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
1441+
export const test7 = testFn<readonly string[], readonly number[]>([]);",
1442+
Some(serde_json::json!([{"default":"generic"}])),
1443+
),
1444+
(
1445+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
1446+
export const test7 = testFn<ReadonlyArray<string>, ReadonlyArray<number>>([]);",
1447+
Some(serde_json::json!([{"default":"array"}])),
1448+
),
1449+
(
1450+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
1451+
export const test8 = testFn<string[], Array<number>>([]);",
1452+
Some(serde_json::json!([{"default":"array"}])),
1453+
),
1454+
(
1455+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
1456+
export const test8 = testFn<Array<string>, number[]>([]);",
1457+
Some(serde_json::json!([{"default":"generic"}])),
1458+
),
1459+
(
1460+
"type IsArray<T> = T extends any[] ? true : false;",
1461+
Some(serde_json::json!([{"default":"generic"}])),
1462+
),
1463+
(
1464+
"type MakeArrays<T> = { [K in keyof T]: T[K][] };",
1465+
Some(serde_json::json!([{"default":"generic"}])),
1466+
),
12941467
];
12951468

12961469
let fix: Vec<(&str, &str, Option<serde_json::Value>)> = vec![
@@ -1865,6 +2038,83 @@ export const test2 = testFn<ReadonlyArray<string>>([]);",
18652038
export const test2 = testFn<readonly string[]>([]);",
18662039
Some(serde_json::json!([{"default":"array"}])),
18672040
),
2041+
// Multiple type parameters - fix tests
2042+
(
2043+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2044+
export const test3 = testFn<string[], number[]>([]);",
2045+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2046+
export const test3 = testFn<Array<string>, Array<number>>([]);",
2047+
Some(serde_json::json!([{"default":"generic"}])),
2048+
),
2049+
(
2050+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2051+
export const test3 = testFn<Array<string>, Array<number>>([]);",
2052+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2053+
export const test3 = testFn<string[], number[]>([]);",
2054+
Some(serde_json::json!([{"default":"array"}])),
2055+
),
2056+
// Complex types in generic arguments - fix tests
2057+
(
2058+
"function testFn<T>(param: T) { return param; }
2059+
export const test5 = testFn<(string & number)[]>([]);",
2060+
"function testFn<T>(param: T) { return param; }
2061+
export const test5 = testFn<Array<string & number>>([]);",
2062+
Some(serde_json::json!([{"default":"array-simple"}])),
2063+
),
2064+
(
2065+
"function testFn<T>(param: T) { return param; }
2066+
export const test5 = testFn<(() => void)[]>([]);",
2067+
"function testFn<T>(param: T) { return param; }
2068+
export const test5 = testFn<Array<() => void>>([]);",
2069+
Some(serde_json::json!([{"default":"array-simple"}])),
2070+
),
2071+
// Note: Nested arrays in generic arguments are checked recursively,
2072+
// so fixes are applied at each level independently
2073+
// Class generic instantiation - fix tests
2074+
(
2075+
"class MyClass<T> { constructor(public value: T) {} }
2076+
const instance = new MyClass<number[]>([1, 2, 3]);",
2077+
"class MyClass<T> { constructor(public value: T) {} }
2078+
const instance = new MyClass<Array<number>>([1, 2, 3]);",
2079+
Some(serde_json::json!([{"default":"generic"}])),
2080+
),
2081+
(
2082+
"class MyClass<T> { constructor(public value: T) {} }
2083+
const instance = new MyClass<Array<number>>([1, 2, 3]);",
2084+
"class MyClass<T> { constructor(public value: T) {} }
2085+
const instance = new MyClass<number[]>([1, 2, 3]);",
2086+
Some(serde_json::json!([{"default":"array"}])),
2087+
),
2088+
// Readonly arrays in multiple type parameters - fix tests
2089+
(
2090+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2091+
export const test7 = testFn<readonly string[], readonly number[]>([]);",
2092+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2093+
export const test7 = testFn<ReadonlyArray<string>, ReadonlyArray<number>>([]);",
2094+
Some(serde_json::json!([{"default":"generic"}])),
2095+
),
2096+
(
2097+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2098+
export const test7 = testFn<ReadonlyArray<string>, ReadonlyArray<number>>([]);",
2099+
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
2100+
export const test7 = testFn<readonly string[], readonly number[]>([]);",
2101+
Some(serde_json::json!([{"default":"array"}])),
2102+
),
2103+
// array-simple with simple types in generics
2104+
(
2105+
"function testFn<T>(param: T) { return param; }
2106+
export const test9 = testFn<Array<string>>([]);",
2107+
"function testFn<T>(param: T) { return param; }
2108+
export const test9 = testFn<string[]>([]);",
2109+
Some(serde_json::json!([{"default":"array-simple"}])),
2110+
),
2111+
(
2112+
"function testFn<T>(param: T) { return param; }
2113+
export const test9 = testFn<ReadonlyArray<number>>([]);",
2114+
"function testFn<T>(param: T) { return param; }
2115+
export const test9 = testFn<readonly number[]>([]);",
2116+
Some(serde_json::json!([{"default":"array-simple"}])),
2117+
),
18682118
];
18692119

18702120
Tester::new(ArrayType::NAME, ArrayType::PLUGIN, pass, fail)

0 commit comments

Comments
 (0)