Better types from jsdoc param tags

This commit is contained in:
Ron Buckton 2017-06-06 18:10:00 -07:00
parent 70c1c57493
commit 471e680ef0
4 changed files with 541 additions and 37 deletions

View file

@ -6814,37 +6814,53 @@ namespace ts {
return undefined;
}
function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName) {
function resolveTypeReferenceName(node: TypeReferenceType, typeReferenceName: EntityNameExpression | EntityName) {
if (!typeReferenceName) {
return unknownSymbol;
}
return resolveEntityName(typeReferenceName, SymbolFlags.Type) || unknownSymbol;
const meaning = node.kind === SyntaxKind.JSDocTypeReference
? SymbolFlags.Type | SymbolFlags.Value
: SymbolFlags.Type;
return resolveEntityName(typeReferenceName, meaning) || unknownSymbol;
}
function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) {
const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced.
let fallbackType: Type = unknownType;
while (true) {
if (symbol === unknownSymbol) {
return fallbackType;
}
if (symbol === unknownSymbol) {
return unknownType;
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments);
}
if (symbol.flags & SymbolFlags.TypeAlias) {
return getTypeFromTypeAliasReference(node, symbol, typeArguments);
}
if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) {
// A JSDocTypeReference may have resolved to a value (as opposed to a type). If
// the value has a construct signature, we use the return type of the construct
// signature as the type; otherwise, the type of this reference is just the type
// of the value we resolved to.
if (symbol.flags & SymbolFlags.Function && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
return getInferredClassType(symbol);
}
fallbackType = getTypeOfSymbol(symbol);
// Try to use the symbol of the type (if present) to get a better type on the
// next pass.
symbol = fallbackType.symbol || unknownSymbol;
continue;
}
return getTypeFromNonGenericTypeReference(node, symbol);
}
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments);
}
if (symbol.flags & SymbolFlags.TypeAlias) {
return getTypeFromTypeAliasReference(node, symbol, typeArguments);
}
if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) {
// A JSDocTypeReference may have resolved to a value (as opposed to a type). In
// that case, the type of this reference is just the type of the value we resolved
// to.
return getTypeOfSymbol(symbol);
}
return getTypeFromNonGenericTypeReference(node, symbol);
}
function getPrimitiveTypeFromJSDocTypeReference(node: JSDocTypeReference): Type {
@ -6888,21 +6904,10 @@ namespace ts {
let symbol: Symbol;
let type: Type;
if (node.kind === SyntaxKind.JSDocTypeReference) {
type = getPrimitiveTypeFromJSDocTypeReference(<JSDocTypeReference>node);
if (!type) {
const typeReferenceName = getTypeReferenceName(node);
symbol = resolveTypeReferenceName(typeReferenceName);
type = getTypeReferenceType(node, symbol);
}
type = getPrimitiveTypeFromJSDocTypeReference(node);
}
else {
// We only support expressions that are simple qualified names. For other expressions this produces undefined.
const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference
? (<TypeReferenceNode>node).typeName
: isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)
? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression
: undefined;
symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol;
if (!type) {
symbol = resolveTypeReferenceName(node, getTypeReferenceName(node));
type = getTypeReferenceType(node, symbol);
}
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the
@ -19367,8 +19372,8 @@ namespace ts {
function checkFunctionDeclaration(node: FunctionDeclaration): void {
if (produceDiagnostics) {
checkFunctionOrMethodDeclaration(node) || checkGrammarForGenerator(node);
checkFunctionOrMethodDeclaration(node);
checkGrammarForGenerator(node);
checkCollisionWithCapturedSuperVariable(node, node.name);
checkCollisionWithCapturedThisVariable(node, node.name);
checkCollisionWithCapturedNewTargetVariable(node, node.name);

View file

@ -0,0 +1,184 @@
=== tests/cases/conformance/salsa/node.d.ts ===
declare function require(id: string): any;
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>id : Symbol(id, Decl(node.d.ts, 0, 25))
declare var module: any, exports: any;
>module : Symbol(module, Decl(node.d.ts, 1, 11))
>exports : Symbol(exports, Decl(node.d.ts, 1, 24))
=== tests/cases/conformance/salsa/a-ext.js ===
exports.A = function () {
>exports : Symbol(A, Decl(a-ext.js, 0, 0))
>A : Symbol(A, Decl(a-ext.js, 0, 0))
this.x = 1;
>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
};
=== tests/cases/conformance/salsa/a.js ===
const { A } = require("./a-ext");
>A : Symbol(A, Decl(a.js, 0, 7))
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>"./a-ext" : Symbol("tests/cases/conformance/salsa/a-ext", Decl(a-ext.js, 0, 0))
/** @param {A} p */
function a(p) { p.x; }
>a : Symbol(a, Decl(a.js, 0, 33))
>p : Symbol(p, Decl(a.js, 3, 11))
>p.x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
>p : Symbol(p, Decl(a.js, 3, 11))
>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
=== tests/cases/conformance/salsa/b-ext.js ===
exports.B = class {
>exports : Symbol(B, Decl(b-ext.js, 0, 0))
>B : Symbol(B, Decl(b-ext.js, 0, 0))
constructor() {
this.x = 1;
>this.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
>this : Symbol((Anonymous class), Decl(b-ext.js, 0, 11))
>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
}
};
=== tests/cases/conformance/salsa/b.js ===
const { B } = require("./b-ext");
>B : Symbol(B, Decl(b.js, 0, 7))
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>"./b-ext" : Symbol("tests/cases/conformance/salsa/b-ext", Decl(b-ext.js, 0, 0))
/** @param {B} p */
function b(p) { p.x; }
>b : Symbol(b, Decl(b.js, 0, 33))
>p : Symbol(p, Decl(b.js, 3, 11))
>p.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
>p : Symbol(p, Decl(b.js, 3, 11))
>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
=== tests/cases/conformance/salsa/c-ext.js ===
export function C() {
>C : Symbol(C, Decl(c-ext.js, 0, 0))
this.x = 1;
>x : Symbol(C.x, Decl(c-ext.js, 0, 21))
}
=== tests/cases/conformance/salsa/c.js ===
const { C } = require("./c-ext");
>C : Symbol(C, Decl(c.js, 0, 7))
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>"./c-ext" : Symbol("tests/cases/conformance/salsa/c-ext", Decl(c-ext.js, 0, 0))
/** @param {C} p */
function c(p) { p.x; }
>c : Symbol(c, Decl(c.js, 0, 33))
>p : Symbol(p, Decl(c.js, 3, 11))
>p.x : Symbol(C.x, Decl(c-ext.js, 0, 21))
>p : Symbol(p, Decl(c.js, 3, 11))
>x : Symbol(C.x, Decl(c-ext.js, 0, 21))
=== tests/cases/conformance/salsa/d-ext.js ===
export var D = function() {
>D : Symbol(D, Decl(d-ext.js, 0, 10))
this.x = 1;
>x : Symbol(D.x, Decl(d-ext.js, 0, 27))
};
=== tests/cases/conformance/salsa/d.js ===
const { D } = require("./d-ext");
>D : Symbol(D, Decl(d.js, 0, 7))
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>"./d-ext" : Symbol("tests/cases/conformance/salsa/d-ext", Decl(d-ext.js, 0, 0))
/** @param {D} p */
function d(p) { p.x; }
>d : Symbol(d, Decl(d.js, 0, 33))
>p : Symbol(p, Decl(d.js, 3, 11))
>p.x : Symbol(D.x, Decl(d-ext.js, 0, 27))
>p : Symbol(p, Decl(d.js, 3, 11))
>x : Symbol(D.x, Decl(d-ext.js, 0, 27))
=== tests/cases/conformance/salsa/e-ext.js ===
export class E {
>E : Symbol(E, Decl(e-ext.js, 0, 0))
constructor() {
this.x = 1;
>this.x : Symbol(E.x, Decl(e-ext.js, 1, 19))
>this : Symbol(E, Decl(e-ext.js, 0, 0))
>x : Symbol(E.x, Decl(e-ext.js, 1, 19))
}
}
=== tests/cases/conformance/salsa/e.js ===
const { E } = require("./e-ext");
>E : Symbol(E, Decl(e.js, 0, 7))
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>"./e-ext" : Symbol("tests/cases/conformance/salsa/e-ext", Decl(e-ext.js, 0, 0))
/** @param {E} p */
function e(p) { p.x; }
>e : Symbol(e, Decl(e.js, 0, 33))
>p : Symbol(p, Decl(e.js, 3, 11))
>p.x : Symbol(E.x, Decl(e-ext.js, 1, 19))
>p : Symbol(p, Decl(e.js, 3, 11))
>x : Symbol(E.x, Decl(e-ext.js, 1, 19))
=== tests/cases/conformance/salsa/f.js ===
var F = function () {
>F : Symbol(F, Decl(f.js, 0, 3))
this.x = 1;
>x : Symbol(F.x, Decl(f.js, 0, 21))
};
/** @param {F} p */
function f(p) { p.x; }
>f : Symbol(f, Decl(f.js, 2, 2))
>p : Symbol(p, Decl(f.js, 5, 11))
>p.x : Symbol(F.x, Decl(f.js, 0, 21))
>p : Symbol(p, Decl(f.js, 5, 11))
>x : Symbol(F.x, Decl(f.js, 0, 21))
=== tests/cases/conformance/salsa/g.js ===
function G() {
>G : Symbol(G, Decl(g.js, 0, 0))
this.x = 1;
>x : Symbol(G.x, Decl(g.js, 0, 14))
}
/** @param {G} p */
function g(p) { p.x; }
>g : Symbol(g, Decl(g.js, 2, 1))
>p : Symbol(p, Decl(g.js, 5, 11))
>p.x : Symbol(G.x, Decl(g.js, 0, 14))
>p : Symbol(p, Decl(g.js, 5, 11))
>x : Symbol(G.x, Decl(g.js, 0, 14))
=== tests/cases/conformance/salsa/h.js ===
class H {
>H : Symbol(H, Decl(h.js, 0, 0))
constructor() {
this.x = 1;
>this.x : Symbol(H.x, Decl(h.js, 1, 19))
>this : Symbol(H, Decl(h.js, 0, 0))
>x : Symbol(H.x, Decl(h.js, 1, 19))
}
}
/** @param {H} p */
function h(p) { p.x; }
>h : Symbol(h, Decl(h.js, 4, 1))
>p : Symbol(p, Decl(h.js, 7, 11))
>p.x : Symbol(H.x, Decl(h.js, 1, 19))
>p : Symbol(p, Decl(h.js, 7, 11))
>x : Symbol(H.x, Decl(h.js, 1, 19))

View file

@ -0,0 +1,223 @@
=== tests/cases/conformance/salsa/node.d.ts ===
declare function require(id: string): any;
>require : (id: string) => any
>id : string
declare var module: any, exports: any;
>module : any
>exports : any
=== tests/cases/conformance/salsa/a-ext.js ===
exports.A = function () {
>exports.A = function () { this.x = 1;} : () => void
>exports.A : any
>exports : any
>A : any
>function () { this.x = 1;} : () => void
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1
};
=== tests/cases/conformance/salsa/a.js ===
const { A } = require("./a-ext");
>A : () => void
>require("./a-ext") : typeof "tests/cases/conformance/salsa/a-ext"
>require : (id: string) => any
>"./a-ext" : "./a-ext"
/** @param {A} p */
function a(p) { p.x; }
>a : (p: { x: number; }) => void
>p : { x: number; }
>p.x : number
>p : { x: number; }
>x : number
=== tests/cases/conformance/salsa/b-ext.js ===
exports.B = class {
>exports.B = class { constructor() { this.x = 1; }} : typeof (Anonymous class)
>exports.B : any
>exports : any
>B : any
>class { constructor() { this.x = 1; }} : typeof (Anonymous class)
constructor() {
this.x = 1;
>this.x = 1 : 1
>this.x : number
>this : this
>x : number
>1 : 1
}
};
=== tests/cases/conformance/salsa/b.js ===
const { B } = require("./b-ext");
>B : typeof (Anonymous class)
>require("./b-ext") : typeof "tests/cases/conformance/salsa/b-ext"
>require : (id: string) => any
>"./b-ext" : "./b-ext"
/** @param {B} p */
function b(p) { p.x; }
>b : (p: (Anonymous class)) => void
>p : (Anonymous class)
>p.x : number
>p : (Anonymous class)
>x : number
=== tests/cases/conformance/salsa/c-ext.js ===
export function C() {
>C : () => void
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1
}
=== tests/cases/conformance/salsa/c.js ===
const { C } = require("./c-ext");
>C : () => void
>require("./c-ext") : typeof "tests/cases/conformance/salsa/c-ext"
>require : (id: string) => any
>"./c-ext" : "./c-ext"
/** @param {C} p */
function c(p) { p.x; }
>c : (p: { x: number; }) => void
>p : { x: number; }
>p.x : number
>p : { x: number; }
>x : number
=== tests/cases/conformance/salsa/d-ext.js ===
export var D = function() {
>D : () => void
>function() { this.x = 1;} : () => void
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1
};
=== tests/cases/conformance/salsa/d.js ===
const { D } = require("./d-ext");
>D : () => void
>require("./d-ext") : typeof "tests/cases/conformance/salsa/d-ext"
>require : (id: string) => any
>"./d-ext" : "./d-ext"
/** @param {D} p */
function d(p) { p.x; }
>d : (p: { x: number; }) => void
>p : { x: number; }
>p.x : number
>p : { x: number; }
>x : number
=== tests/cases/conformance/salsa/e-ext.js ===
export class E {
>E : E
constructor() {
this.x = 1;
>this.x = 1 : 1
>this.x : number
>this : this
>x : number
>1 : 1
}
}
=== tests/cases/conformance/salsa/e.js ===
const { E } = require("./e-ext");
>E : typeof E
>require("./e-ext") : typeof "tests/cases/conformance/salsa/e-ext"
>require : (id: string) => any
>"./e-ext" : "./e-ext"
/** @param {E} p */
function e(p) { p.x; }
>e : (p: E) => void
>p : E
>p.x : number
>p : E
>x : number
=== tests/cases/conformance/salsa/f.js ===
var F = function () {
>F : () => void
>function () { this.x = 1;} : () => void
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1
};
/** @param {F} p */
function f(p) { p.x; }
>f : (p: { x: number; }) => void
>p : { x: number; }
>p.x : number
>p : { x: number; }
>x : number
=== tests/cases/conformance/salsa/g.js ===
function G() {
>G : () => void
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1
}
/** @param {G} p */
function g(p) { p.x; }
>g : (p: { x: number; }) => void
>p : { x: number; }
>p.x : number
>p : { x: number; }
>x : number
=== tests/cases/conformance/salsa/h.js ===
class H {
>H : H
constructor() {
this.x = 1;
>this.x = 1 : 1
>this.x : number
>this : this
>x : number
>1 : 1
}
}
/** @param {H} p */
function h(p) { p.x; }
>h : (p: H) => void
>p : H
>p.x : number
>p : H
>x : number

View file

@ -0,0 +1,92 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @module: commonjs
// @filename: node.d.ts
declare function require(id: string): any;
declare var module: any, exports: any;
// @filename: a-ext.js
exports.A = function () {
this.x = 1;
};
// @filename: a.js
const { A } = require("./a-ext");
/** @param {A} p */
function a(p) { p.x; }
// @filename: b-ext.js
exports.B = class {
constructor() {
this.x = 1;
}
};
// @filename: b.js
const { B } = require("./b-ext");
/** @param {B} p */
function b(p) { p.x; }
// @filename: c-ext.js
export function C() {
this.x = 1;
}
// @filename: c.js
const { C } = require("./c-ext");
/** @param {C} p */
function c(p) { p.x; }
// @filename: d-ext.js
export var D = function() {
this.x = 1;
};
// @filename: d.js
const { D } = require("./d-ext");
/** @param {D} p */
function d(p) { p.x; }
// @filename: e-ext.js
export class E {
constructor() {
this.x = 1;
}
}
// @filename: e.js
const { E } = require("./e-ext");
/** @param {E} p */
function e(p) { p.x; }
// @filename: f.js
var F = function () {
this.x = 1;
};
/** @param {F} p */
function f(p) { p.x; }
// @filename: g.js
function G() {
this.x = 1;
}
/** @param {G} p */
function g(p) { p.x; }
// @filename: h.js
class H {
constructor() {
this.x = 1;
}
}
/** @param {H} p */
function h(p) { p.x; }