Skip to content

Commit 6e6d2e5

Browse files
committed
Better typings for Array.map()
1 parent 196c0aa commit 6e6d2e5

23 files changed

+444
-130
lines changed

src/lib/es5.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ interface ReadonlyArray<T> {
11381138
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
11391139
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
11401140
*/
1141-
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[];
1141+
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): { -readonly [P in keyof this]: U };
11421142
/**
11431143
* Returns the elements of an array that meet the condition specified in a callback function.
11441144
* @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
@@ -1308,7 +1308,7 @@ interface Array<T> {
13081308
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
13091309
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
13101310
*/
1311-
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
1311+
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): { -readonly [P in keyof this]: U };
13121312
/**
13131313
* Returns the elements of an array that meet the condition specified in a callback function.
13141314
* @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
tests/cases/compiler/APISample_jsdoc.ts(79,32): error TS2495: Type '{ [x: number]: readonly JSDocParameterTag[]; hasTrailingComma?: readonly JSDocParameterTag[] | undefined; length: readonly JSDocParameterTag[]; toString: readonly JSDocParameterTag[]; toLocaleString: readonly JSDocParameterTag[]; concat: readonly JSDocParameterTag[]; join: readonly JSDocParameterTag[]; slice: readonly JSDocParameterTag[]; indexOf: readonly JSDocParameterTag[]; lastIndexOf: readonly JSDocParameterTag[]; every: readonly JSDocParameterTag[]; some: readonly JSDocParameterTag[]; forEach: readonly JSDocParameterTag[]; map: readonly JSDocParameterTag[]; filter: readonly JSDocParameterTag[]; reduce: readonly JSDocParameterTag[]; reduceRight: readonly JSDocParameterTag[]; pos: readonly JSDocParameterTag[]; end: readonly JSDocParameterTag[]; }' is not an array type or a string type.
2+
3+
4+
==== tests/cases/compiler/node_modules/typescript/index.d.ts (0 errors) ====
5+
declare module "typescript" {
6+
export = ts;
7+
}
8+
9+
==== tests/cases/compiler/APISample_jsdoc.ts (1 errors) ====
10+
/*
11+
* Note: This test is a public API sample. The original sources can be found
12+
* at: https://github.com./YousefED/typescript-json-schema
13+
* https://github.com./vega/ts-json-schema-generator
14+
* Please log a "breaking change" issue for any API breaking change affecting this issue
15+
*/
16+
17+
declare var console: any;
18+
19+
import * as ts from "typescript";
20+
21+
// excerpted from https://github.com./YousefED/typescript-json-schema
22+
// (converted from a method and modified; for example, `this: any` to compensate, among other changes)
23+
function parseCommentsIntoDefinition(this: any,
24+
symbol: ts.Symbol,
25+
definition: {description?: string, [s: string]: string | undefined},
26+
otherAnnotations: { [s: string]: true}): void {
27+
if (!symbol) {
28+
return;
29+
}
30+
31+
// the comments for a symbol
32+
let comments = symbol.getDocumentationComment(undefined);
33+
34+
if (comments.length) {
35+
definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n")).join("");
36+
}
37+
38+
// jsdocs are separate from comments
39+
const jsdocs = symbol.getJsDocTags();
40+
jsdocs.forEach(doc => {
41+
// if we have @TJS-... annotations, we have to parse them
42+
const { name, text } = doc;
43+
if (this.userValidationKeywords[name]) {
44+
definition[name] = this.parseValue(text);
45+
} else {
46+
// special annotations
47+
otherAnnotations[doc.name] = true;
48+
}
49+
});
50+
}
51+
52+
53+
// excerpted from https://github.com./vega/ts-json-schema-generator
54+
export interface Annotations {
55+
[name: string]: any;
56+
}
57+
function getAnnotations(this: any, node: ts.Node): Annotations | undefined {
58+
const symbol: ts.Symbol = (node as any).symbol;
59+
if (!symbol) {
60+
return undefined;
61+
}
62+
63+
const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
64+
if (!jsDocTags || !jsDocTags.length) {
65+
return undefined;
66+
}
67+
68+
const annotations: Annotations = jsDocTags.reduce((result: Annotations, jsDocTag: ts.JSDocTagInfo) => {
69+
const value = this.parseJsDocTag(jsDocTag);
70+
if (value !== undefined) {
71+
result[jsDocTag.name] = value;
72+
}
73+
74+
return result;
75+
}, {});
76+
return Object.keys(annotations).length ? annotations : undefined;
77+
}
78+
79+
// these examples are artificial and mostly nonsensical
80+
function parseSpecificTags(node: ts.Node) {
81+
if (node.kind === ts.SyntaxKind.Parameter) {
82+
return ts.getJSDocParameterTags(node as ts.ParameterDeclaration);
83+
}
84+
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
85+
const func = node as ts.FunctionDeclaration;
86+
if (ts.hasJSDocParameterTags(func)) {
87+
const flat: ts.JSDocTag[] = [];
88+
for (const tags of func.parameters.map(ts.getJSDocParameterTags)) {
89+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90+
!!! error TS2495: Type '{ [x: number]: readonly JSDocParameterTag[]; hasTrailingComma?: readonly JSDocParameterTag[] | undefined; length: readonly JSDocParameterTag[]; toString: readonly JSDocParameterTag[]; toLocaleString: readonly JSDocParameterTag[]; concat: readonly JSDocParameterTag[]; join: readonly JSDocParameterTag[]; slice: readonly JSDocParameterTag[]; indexOf: readonly JSDocParameterTag[]; lastIndexOf: readonly JSDocParameterTag[]; every: readonly JSDocParameterTag[]; some: readonly JSDocParameterTag[]; forEach: readonly JSDocParameterTag[]; map: readonly JSDocParameterTag[]; filter: readonly JSDocParameterTag[]; reduce: readonly JSDocParameterTag[]; reduceRight: readonly JSDocParameterTag[]; pos: readonly JSDocParameterTag[]; end: readonly JSDocParameterTag[]; }' is not an array type or a string type.
91+
if (tags) flat.push(...tags);
92+
}
93+
return flat;
94+
}
95+
}
96+
}
97+
98+
function getReturnTypeFromJSDoc(node: ts.Node) {
99+
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
100+
return ts.getJSDocReturnType(node);
101+
}
102+
let type = ts.getJSDocType(node);
103+
if (type && type.kind === ts.SyntaxKind.FunctionType) {
104+
return (type as ts.FunctionTypeNode).type;
105+
}
106+
}
107+
108+
function getAllTags(node: ts.Node) {
109+
ts.getJSDocTags(node);
110+
}
111+
112+
function getSomeOtherTags(node: ts.Node) {
113+
const tags: (ts.JSDocTag | undefined)[] = [];
114+
tags.push(ts.getJSDocAugmentsTag(node));
115+
tags.push(ts.getJSDocClassTag(node));
116+
tags.push(ts.getJSDocReturnTag(node));
117+
const type = ts.getJSDocTypeTag(node);
118+
if (type) {
119+
tags.push(type);
120+
}
121+
tags.push(ts.getJSDocTemplateTag(node));
122+
return tags;
123+
}
124+

tests/baselines/reference/arityAndOrderCompatibility01.errors.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(15,12): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
2+
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(16,5): error TS2461: Type 'StrNum' is not an array type.
23
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(17,5): error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type.
34
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(18,5): error TS2741: Property '2' is missing in type '[string, number]' but required in type '[number, number, number]'.
45
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(19,5): error TS2741: Property '2' is missing in type 'StrNum' but required in type '[number, number, number]'.
@@ -28,7 +29,7 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(31,5): error
2829
tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error TS2322: Type '{ 0: string; 1: number; length: 2; }' is not assignable to type '[number, string]'.
2930

3031

31-
==== tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts (17 errors) ====
32+
==== tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts (18 errors) ====
3233
interface StrNum extends Array<string|number> {
3334
0: string;
3435
1: number;
@@ -47,6 +48,8 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error
4748
~
4849
!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
4950
var [d, e, f] = y;
51+
~~~~~~~~~
52+
!!! error TS2461: Type 'StrNum' is not an array type.
5053
var [g, h, i] = z;
5154
~~~~~~~~~
5255
!!! error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type.

tests/baselines/reference/arityAndOrderCompatibility01.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ var [a, b, c] = x;
3636
>x : [string, number]
3737

3838
var [d, e, f] = y;
39-
>d : string
40-
>e : number
41-
>f : string | number
39+
>d : any
40+
>e : any
41+
>f : any
4242
>y : StrNum
4343

4444
var [g, h, i] = z;

tests/baselines/reference/arrayOfSubtypeIsAssignableToReadonlyArray.errors.txt

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(13,1): error TS2322: Type 'A[]' is not assignable to type 'readonly B[]'.
22
Property 'b' is missing in type 'A' but required in type 'B'.
3+
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(15,1): error TS2322: Type 'C<A>' is not assignable to type 'readonly A[]'.
4+
The types of 'map(...).length' are incompatible between these types.
5+
Type 'U' is not assignable to type 'number'.
6+
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(16,1): error TS2322: Type 'C<B>' is not assignable to type 'readonly A[]'.
7+
The types of 'map(...).length' are incompatible between these types.
8+
Type 'U' is not assignable to type 'number'.
9+
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(17,1): error TS2322: Type 'C<B>' is not assignable to type 'readonly B[]'.
10+
The types returned by 'map(...)' are incompatible between these types.
11+
Type '{ [x: number]: U; c: U; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }' is not assignable to type 'U[]'.
312
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error TS2322: Type 'C<A>' is not assignable to type 'readonly B[]'.
413
The types returned by 'concat(...)' are incompatible between these types.
514
Type 'A[]' is not assignable to type 'B[]'.
615
Type 'A' is not assignable to type 'B'.
716

817

9-
==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (2 errors) ====
18+
==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (5 errors) ====
1019
class A { a }
1120
class B extends A { b }
1221
class C<T> extends Array<T> { c }
@@ -26,8 +35,20 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error T
2635
!!! related TS2728 tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts:2:21: 'b' is declared here.
2736

2837
rra = cra;
38+
~~~
39+
!!! error TS2322: Type 'C<A>' is not assignable to type 'readonly A[]'.
40+
!!! error TS2322: The types of 'map(...).length' are incompatible between these types.
41+
!!! error TS2322: Type 'U' is not assignable to type 'number'.
2942
rra = crb; // OK, C<B> is assignable to ReadonlyArray<A>
43+
~~~
44+
!!! error TS2322: Type 'C<B>' is not assignable to type 'readonly A[]'.
45+
!!! error TS2322: The types of 'map(...).length' are incompatible between these types.
46+
!!! error TS2322: Type 'U' is not assignable to type 'number'.
3047
rrb = crb;
48+
~~~
49+
!!! error TS2322: Type 'C<B>' is not assignable to type 'readonly B[]'.
50+
!!! error TS2322: The types returned by 'map(...)' are incompatible between these types.
51+
!!! error TS2322: Type '{ [x: number]: U; c: U; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }' is not assignable to type 'U[]'.
3152
rrb = cra; // error: 'A' is not assignable to 'B'
3253
~~~
3354
!!! error TS2322: Type 'C<A>' is not assignable to type 'readonly B[]'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
tests/cases/compiler/bestChoiceType.ts(3,23): error TS2349: This expression is not callable.
2+
Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
3+
tests/cases/compiler/bestChoiceType.ts(10,15): error TS2349: This expression is not callable.
4+
Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
5+
tests/cases/compiler/bestChoiceType.ts(16,15): error TS2349: This expression is not callable.
6+
Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
7+
8+
9+
==== tests/cases/compiler/bestChoiceType.ts (3 errors) ====
10+
// Repro from #10041
11+
12+
(''.match(/ /) || []).map(s => s.toLowerCase());
13+
~~~
14+
!!! error TS2349: This expression is not callable.
15+
!!! error TS2349: Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
16+
17+
// Similar cases
18+
19+
function f1() {
20+
let x = ''.match(/ /);
21+
let y = x || [];
22+
let z = y.map(s => s.toLowerCase());
23+
~~~
24+
!!! error TS2349: This expression is not callable.
25+
!!! error TS2349: Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
26+
}
27+
28+
function f2() {
29+
let x = ''.match(/ /);
30+
let y = x ? x : [];
31+
let z = y.map(s => s.toLowerCase());
32+
~~~
33+
!!! error TS2349: This expression is not callable.
34+
!!! error TS2349: Each member of the union type '(<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => { [x: number]: U; index?: U | undefined; input?: U | undefined; length: U; toString: U; toLocaleString: U; pop: U; push: U; concat: U; join: U; reverse: U; shift: U; slice: U; sort: U; splice: U; unshift: U; indexOf: U; lastIndexOf: U; every: U; some: U; forEach: U; map: U; filter: U; reduce: U; reduceRight: U; }) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.
35+
}
36+

0 commit comments

Comments
 (0)