Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions crates/oxc_linter/src/rules/typescript/array_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
_ => {}
}
}
Expand Down Expand Up @@ -890,6 +905,71 @@ fn test() {
"let a: readonly Array<number>[] = [[]];",
Some(serde_json::json!([{"default":"generic","readonly":"array"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<string[]>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<Array<string>>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<string[]>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<Array<{name: string}>>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test = testFn<string[], number[]>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test = testFn<Array<string>, Array<number>>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<readonly string[]>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<ReadonlyArray<string>>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<readonly string[]>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<ReadonlyArray<{name: string}>>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<string>('hello');",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test = testFn<{name: string}>({name: 'test'});",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"class MyClass<T> { constructor(public value: T) {} }
const instance = new MyClass<number>(42);",
Some(serde_json::json!([{"default":"generic"}])),
),
];

let fail = vec![
Expand Down Expand Up @@ -1291,6 +1371,99 @@ export const test2 = testFn<readonly string[]>([]);",
export const test2 = testFn<ReadonlyArray<string>>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test3 = testFn<string[], number[]>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test3 = testFn<Array<string>, Array<number>>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test4 = testFn<Promise<string[]>>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test4 = testFn<Promise<Array<string>>>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test5 = testFn<(string & number)[]>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T>(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<T> { constructor(public value: T) {} }
const instance = new MyClass<number[]>([1, 2, 3]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"class MyClass<T> { constructor(public value: T) {} }
const instance = new MyClass<Array<number>>([1, 2, 3]);",
Some(serde_json::json!([{"default":"array"}])),
),
// Type assertion with generic
(
"const value = {} as Map<string, number[]>;",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"const value = {} as Map<string, Array<number>>;",
Some(serde_json::json!([{"default":"array"}])),
),
(
"interface Container<T> { value: T; }
const container: Container<string[]> = { value: [] };",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"interface Container<T> { value: T; }
const container: Container<Array<string>> = { value: [] };",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test7 = testFn<readonly string[], readonly number[]>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test7 = testFn<ReadonlyArray<string>, ReadonlyArray<number>>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test8 = testFn<string[], Array<number>>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test8 = testFn<Array<string>, number[]>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"type IsArray<T> = T extends any[] ? true : false;",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"type MakeArrays<T> = { [K in keyof T]: T[K][] };",
Some(serde_json::json!([{"default":"generic"}])),
),
];

let fix: Vec<(&str, &str, Option<serde_json::Value>)> = vec![
Expand Down Expand Up @@ -1865,6 +2038,83 @@ export const test2 = testFn<ReadonlyArray<string>>([]);",
export const test2 = testFn<readonly string[]>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
// Multiple type parameters - fix tests
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test3 = testFn<string[], number[]>([]);",
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test3 = testFn<Array<string>, Array<number>>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test3 = testFn<Array<string>, Array<number>>([]);",
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test3 = testFn<string[], number[]>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
// Complex types in generic arguments - fix tests
(
"function testFn<T>(param: T) { return param; }
export const test5 = testFn<(string & number)[]>([]);",
"function testFn<T>(param: T) { return param; }
export const test5 = testFn<Array<string & number>>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test5 = testFn<(() => void)[]>([]);",
"function testFn<T>(param: T) { return param; }
export const test5 = testFn<Array<() => 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<T> { constructor(public value: T) {} }
const instance = new MyClass<number[]>([1, 2, 3]);",
"class MyClass<T> { constructor(public value: T) {} }
const instance = new MyClass<Array<number>>([1, 2, 3]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"class MyClass<T> { constructor(public value: T) {} }
const instance = new MyClass<Array<number>>([1, 2, 3]);",
"class MyClass<T> { constructor(public value: T) {} }
const instance = new MyClass<number[]>([1, 2, 3]);",
Some(serde_json::json!([{"default":"array"}])),
),
// Readonly arrays in multiple type parameters - fix tests
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test7 = testFn<readonly string[], readonly number[]>([]);",
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test7 = testFn<ReadonlyArray<string>, ReadonlyArray<number>>([]);",
Some(serde_json::json!([{"default":"generic"}])),
),
(
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test7 = testFn<ReadonlyArray<string>, ReadonlyArray<number>>([]);",
"function testFn<T, U>(param1: T, param2: U) { return [param1, param2]; }
export const test7 = testFn<readonly string[], readonly number[]>([]);",
Some(serde_json::json!([{"default":"array"}])),
),
// array-simple with simple types in generics
(
"function testFn<T>(param: T) { return param; }
export const test9 = testFn<Array<string>>([]);",
"function testFn<T>(param: T) { return param; }
export const test9 = testFn<string[]>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
(
"function testFn<T>(param: T) { return param; }
export const test9 = testFn<ReadonlyArray<number>>([]);",
"function testFn<T>(param: T) { return param; }
export const test9 = testFn<readonly number[]>([]);",
Some(serde_json::json!([{"default":"array-simple"}])),
),
];

Tester::new(ArrayType::NAME, ArrayType::PLUGIN, pass, fail)
Expand Down
Loading
Loading