Skip to content

Improve contextual types using jsdoc tags #16346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 9, 2017
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
168 changes: 84 additions & 84 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

51 changes: 48 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,11 @@ namespace ts {
return getFirstJSDocTag(node, SyntaxKind.JSDocReturnTag) as JSDocReturnTag;
}

export function getJSDocReturnType(node: Node): JSDocType {
const returnTag = getJSDocReturnTag(node);
return returnTag && returnTag.typeExpression && returnTag.typeExpression.type;
}

export function getJSDocTemplateTag(node: Node): JSDocTemplateTag {
return getFirstJSDocTag(node, SyntaxKind.JSDocTemplateTag) as JSDocTemplateTag;
}
Expand Down Expand Up @@ -2615,14 +2620,19 @@ namespace ts {
});
}

/** Get the type annotaion for the value parameter. */
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode {
function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined {
if (accessor && accessor.parameters.length > 0) {
const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]);
return accessor.parameters[hasThis ? 1 : 0].type;
return accessor.parameters[hasThis ? 1 : 0];
}
}

/** Get the type annotation for the value parameter. */
export function getSetAccessorTypeAnnotationNode(accessor: SetAccessorDeclaration): TypeNode {
const parameter = getSetAccessorValueParameter(accessor);
return parameter && parameter.type;
}

export function getThisParameter(signature: SignatureDeclaration): ParameterDeclaration | undefined {
if (signature.parameters.length) {
const thisParameter = signature.parameters[0];
Expand Down Expand Up @@ -2701,6 +2711,41 @@ namespace ts {
};
}

/**
* Gets the effective type annotation of a variable, parameter, or property. If the node was
* parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveTypeAnnotationNode(node: VariableLikeDeclaration): TypeNode {
if (node.type) {
return node.type;
}
if (node.flags & NodeFlags.JavaScriptFile) {
return getJSDocType(node);
}
}

/**
* Gets the effective return type annotation of a signature. If the node was parsed in a
* JavaScript file, gets the return type annotation from JSDoc.
*/
export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode {
if (node.type) {
return node.type;
}
if (node.flags & NodeFlags.JavaScriptFile) {
return getJSDocReturnType(node);
}
}

/**
* Gets the effective type annotation of the value parameter of a set accessor. If the node
* was parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveSetAccessorTypeAnnotationNode(node: SetAccessorDeclaration): TypeNode {
const parameter = getSetAccessorValueParameter(node);
return parameter && getEffectiveTypeAnnotationNode(parameter);
}

export function emitNewLineBeforeLeadingComments(lineMap: number[], writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) {
emitNewLineBeforeLeadingCommentsOfPosition(lineMap, writer, node.pos, leadingComments);
}
Expand Down
25 changes: 25 additions & 0 deletions tests/baselines/reference/checkJsdocReturnTag2.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
tests/cases/conformance/jsdoc/returns.js(6,5): error TS2322: Type '5' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/returns.js(13,5): error TS2322: Type 'true | 5' is not assignable to type 'string | number'.
Type 'true' is not assignable to type 'string | number'.


==== tests/cases/conformance/jsdoc/returns.js (2 errors) ====
// @ts-check
/**
* @returns {string} This comment is not currently exposed
*/
function f() {
return 5;
~~~~~~~~~
!!! error TS2322: Type '5' is not assignable to type 'string'.
}

/**
* @returns {string | number} This comment is not currently exposed
*/
function f1() {
return 5 || true;
~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'true | 5' is not assignable to type 'string | number'.
!!! error TS2322: Type 'true' is not assignable to type 'string | number'.
}
16 changes: 8 additions & 8 deletions tests/baselines/reference/checkJsdocTypeTag1.types
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ x(1);
/** @type {function (number)} */
const x1 = (a) => a + 1;
>x1 : (arg0: number) => any
>(a) => a + 1 : (a: any) => any
>a : any
>a + 1 : any
>a : any
>(a) => a + 1 : (a: number) => number
>a : number
>a + 1 : number
>a : number
>1 : 1

x1(0);
Expand All @@ -75,10 +75,10 @@ x1(0);
/** @type {function (number): number} */
const x2 = (a) => a + 1;
>x2 : (arg0: number) => number
>(a) => a + 1 : (a: any) => any
>a : any
>a + 1 : any
>a : any
>(a) => a + 1 : (a: number) => number
>a : number
>a + 1 : number
>a : number
>1 : 1

x2(0);
Expand Down
20 changes: 13 additions & 7 deletions tests/baselines/reference/checkJsdocTypeTag2.errors.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
tests/cases/conformance/jsdoc/0.js(3,5): error TS2322: Type 'true' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/0.js(6,5): error TS2322: Type '"hello"' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/0.js(10,4): error TS2345: Argument of type '"string"' is not assignable to parameter of type 'number'.
tests/cases/conformance/jsdoc/0.js(13,7): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/conformance/jsdoc/0.js(17,1): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/conformance/jsdoc/0.js(20,21): error TS2339: Property 'concat' does not exist on type 'number'.
tests/cases/conformance/jsdoc/0.js(24,7): error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'.
Type 'number' is not assignable to type 'string'.


==== tests/cases/conformance/jsdoc/0.js (6 errors) ====
Expand All @@ -26,8 +27,6 @@ tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-s

/** @type {function (number): number} */
const x2 = (a) => a + 1;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.

/** @type {string} */
var a;
Expand All @@ -36,7 +35,14 @@ tests/cases/conformance/jsdoc/0.js(20,7): error TS2451: Cannot redeclare block-s
!!! error TS2322: Type 'number' is not assignable to type 'string'.

/** @type {function (number): number} */
const x2 = (a) => a.concat("hi");
const x3 = (a) => a.concat("hi");
~~~~~~
!!! error TS2339: Property 'concat' does not exist on type 'number'.
x3(0);

/** @type {function (number): string} */
const x4 = (a) => a + 1;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
x2(0);
!!! error TS2322: Type '(a: number) => number' is not assignable to type '(arg0: number) => string'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
x4(0);
15 changes: 11 additions & 4 deletions tests/baselines/reference/checkJsdocTypeTag2.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ var a;
a = x2(0);

/** @type {function (number): number} */
const x2 = (a) => a.concat("hi");
x2(0);
const x3 = (a) => a.concat("hi");
x3(0);

/** @type {function (number): string} */
const x4 = (a) => a + 1;
x4(0);

//// [0.js]
// @ts-check
Expand All @@ -36,5 +40,8 @@ var x2 = function (a) { return a + 1; };
var a;
a = x2(0);
/** @type {function (number): number} */
var x2 = function (a) { return a.concat("hi"); };
x2(0);
var x3 = function (a) { return a.concat("hi"); };
x3(0);
/** @type {function (number): string} */
var x4 = function (a) { return a + 1; };
x4(0);
48 changes: 48 additions & 0 deletions tests/baselines/reference/contextualTypeFromJSDoc.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
=== tests/cases/conformance/types/contextualTypes/jsdoc/index.js ===
/** @type {Array<[string, {x?:number, y?:number}]>} */
const arr = [
>arr : Symbol(arr, Decl(index.js, 1, 5))

['a', { x: 1 }],
>x : Symbol(x, Decl(index.js, 2, 11))

['b', { y: 2 }]
>y : Symbol(y, Decl(index.js, 3, 11))

];

/** @return {Array<[string, {x?:number, y?:number}]>} */
function f() {
>f : Symbol(f, Decl(index.js, 4, 2))

return [
['a', { x: 1 }],
>x : Symbol(x, Decl(index.js, 9, 15))

['b', { y: 2 }]
>y : Symbol(y, Decl(index.js, 10, 15))

];
}

class C {
>C : Symbol(C, Decl(index.js, 12, 1))

/** @param {Array<[string, {x?:number, y?:number}]>} value */
set x(value) { }
>x : Symbol(C.x, Decl(index.js, 14, 9), Decl(index.js, 16, 20))
>value : Symbol(value, Decl(index.js, 16, 10))

get x() {
>x : Symbol(C.x, Decl(index.js, 14, 9), Decl(index.js, 16, 20))

return [
['a', { x: 1 }],
>x : Symbol(x, Decl(index.js, 19, 19))

['b', { y: 2 }]
>y : Symbol(y, Decl(index.js, 20, 19))

];
}
}
77 changes: 77 additions & 0 deletions tests/baselines/reference/contextualTypeFromJSDoc.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
=== tests/cases/conformance/types/contextualTypes/jsdoc/index.js ===
/** @type {Array<[string, {x?:number, y?:number}]>} */
const arr = [
>arr : [string, { x?: number; y?: number; }][]
>[ ['a', { x: 1 }], ['b', { y: 2 }]] : ([string, { x: number; }] | [string, { y: number; }])[]

['a', { x: 1 }],
>['a', { x: 1 }] : [string, { x: number; }]
>'a' : "a"
>{ x: 1 } : { x: number; }
>x : number
>1 : 1

['b', { y: 2 }]
>['b', { y: 2 }] : [string, { y: number; }]
>'b' : "b"
>{ y: 2 } : { y: number; }
>y : number
>2 : 2

];

/** @return {Array<[string, {x?:number, y?:number}]>} */
function f() {
>f : () => [string, { x?: number; y?: number; }][]

return [
>[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ([string, { x: number; }] | [string, { y: number; }])[]

['a', { x: 1 }],
>['a', { x: 1 }] : [string, { x: number; }]
>'a' : "a"
>{ x: 1 } : { x: number; }
>x : number
>1 : 1

['b', { y: 2 }]
>['b', { y: 2 }] : [string, { y: number; }]
>'b' : "b"
>{ y: 2 } : { y: number; }
>y : number
>2 : 2

];
}

class C {
>C : C

/** @param {Array<[string, {x?:number, y?:number}]>} value */
set x(value) { }
>x : [string, { x?: number; y?: number; }][]
>value : [string, { x?: number; y?: number; }][]

get x() {
>x : [string, { x?: number; y?: number; }][]

return [
>[ ['a', { x: 1 }], ['b', { y: 2 }] ] : ([string, { x: number; }] | [string, { y: number; }])[]

['a', { x: 1 }],
>['a', { x: 1 }] : [string, { x: number; }]
>'a' : "a"
>{ x: 1 } : { x: number; }
>x : number
>1 : 1

['b', { y: 2 }]
>['b', { y: 2 }] : [string, { y: number; }]
>'b' : "b"
>{ y: 2 } : { y: number; }
>y : number
>2 : 2

];
}
}
8 changes: 6 additions & 2 deletions tests/cases/conformance/jsdoc/checkJsdocTypeTag2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@ var a;
a = x2(0);

/** @type {function (number): number} */
const x2 = (a) => a.concat("hi");
x2(0);
const x3 = (a) => a.concat("hi");
x3(0);

/** @type {function (number): string} */
const x4 = (a) => a + 1;
x4(0);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: index.js
// @target: esnext

/** @type {Array<[string, {x?:number, y?:number}]>} */
const arr = [
['a', { x: 1 }],
['b', { y: 2 }]
];

/** @return {Array<[string, {x?:number, y?:number}]>} */
function f() {
return [
['a', { x: 1 }],
['b', { y: 2 }]
];
}

class C {
/** @param {Array<[string, {x?:number, y?:number}]>} value */
set x(value) { }
get x() {
return [
['a', { x: 1 }],
['b', { y: 2 }]
];
}
}