From f9f91571264a352fc483dbb36b568438b2aa685c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 10:16:36 -0700 Subject: [PATCH 01/49] Add initial tests --- tests/cases/conformance/jsdoc/callbackTag1.ts | 12 +++++++ .../fourslash/server/jsdocCallbackTag.ts | 31 +++++++++++++++++ .../server/jsdocCallbackTagNavigateTo.ts | 33 +++++++++++++++++++ .../server/jsdocCallbackTagRename01.ts | 15 +++++++++ 4 files changed, 91 insertions(+) create mode 100644 tests/cases/conformance/jsdoc/callbackTag1.ts create mode 100644 tests/cases/fourslash/server/jsdocCallbackTag.ts create mode 100644 tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts create mode 100644 tests/cases/fourslash/server/jsdocCallbackTagRename01.ts diff --git a/tests/cases/conformance/jsdoc/callbackTag1.ts b/tests/cases/conformance/jsdoc/callbackTag1.ts new file mode 100644 index 0000000000000..e97cd23921bcd --- /dev/null +++ b/tests/cases/conformance/jsdoc/callbackTag1.ts @@ -0,0 +1,12 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: cb.js + +/** @callback Sid + * @param {string} s + * @returns {string} What were you expecting + */ + +/** @type {Sid} smallId */ +var sid = s => s + "!"; diff --git a/tests/cases/fourslash/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts new file mode 100644 index 0000000000000..d0d944cc2778d --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -0,0 +1,31 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsdocCallbackTag.js + +//// /** +//// * @callback FooHandler +//// * @param {string} eventName - So many words +//// * @param eventName2 {number | string} - Silence is golden +//// * @param eventName3 - Osterreich mos def +//// */ +//// /** +//// * @type {FooHandler} callback +//// */ +//// var t/*1*/; +//// +//// /** +//// * @callback FooHandler2 - What, another one? +//// * @param {string=} eventName - it keeps happening +//// * @param {string} [eventName2] - i WARNED you dog +//// */ +//// /** +//// * @type {FooHandler2} callback +//// */ +//// var t2/*2*/; + + +goTo.marker("1"); +verify.quickInfoIs("var t: (eventName: string, eventName2: number | string, eventName3: any) => void"); +goTo.marker("2"); +verify.quickInfoIs("var t2: (eventName?: string, eventName2?: string) => void"); diff --git a/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts new file mode 100644 index 0000000000000..e17192e65b7e6 --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts @@ -0,0 +1,33 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsDocCallback.js + +//// /** +//// * @callback FooCallback +//// * @param {string} eventName - What even is the navigation bar? +//// */ +//// /** @type {FooCallback} */ +//// var t; + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "FooCallback", + "kind": "type" + }, + { + "text": "t", + "kind": "var" + } + ] + }, + { + "text": "FooCallback", + "kind": "type", + "indent": 1 + } +]) diff --git a/tests/cases/fourslash/server/jsdocCallbackTagRename01.ts b/tests/cases/fourslash/server/jsdocCallbackTagRename01.ts new file mode 100644 index 0000000000000..bb7dd32cfc28d --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTagRename01.ts @@ -0,0 +1,15 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsDocCallback.js +//// +//// /** +//// * @callback [|FooCallback|] +//// * @param {string} eventName - Rename should work +//// */ +//// +//// /** @type {/*1*/[|FooCallback|]} */ +//// var t; + +const ranges = test.ranges(); +verify.renameLocations(ranges[0], { findInStrings: false, findInComments: true, ranges }); From 5fab72fae42209efb9e491537a11561447c1e0cd Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 10:16:47 -0700 Subject: [PATCH 02/49] Add types --- src/compiler/types.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3608b6b5c3232..d71aa8404e262 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -411,9 +411,11 @@ namespace ts { JSDocVariadicType, JSDocComment, JSDocTypeLiteral, + JSDocSignature, JSDocTag, JSDocAugmentsTag, JSDocClassTag, + JSDocCallbackTag, JSDocParameterTag, JSDocReturnTag, JSDocTypeTag, @@ -822,6 +824,7 @@ namespace ts { | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType + | JSDocSignature // TODO: Different from JSDocFunctionType?????? | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration @@ -2385,6 +2388,19 @@ namespace ts { typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; } + export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration { + parent: JSDoc; + kind: SyntaxKind.JSDocCallbackTag; + fullName?: JSDocNamespaceDeclaration | Identifier; + name?: Identifier; // TODO: Not sure whether this rigamarole is needed for callback...but probably! + signature: JSDocSignature; + } + + // TODO: name it JSDocSignatureDeclaration? Could just try to reuse JSDocTypeLiteral + export interface JSDocSignature extends JSDocType, SignatureDeclarationBase { + kind: SyntaxKind.JSDocSignature; + } + export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; name: EntityName; From 37076b3536dedd71d067f74ac46d5744a3db57c0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 10:30:21 -0700 Subject: [PATCH 03/49] Half of parsing (builds but does not pass tests) --- src/compiler/checker.ts | 1 + src/compiler/parser.ts | 50 +++++++++++++++++++++++++++------------ src/compiler/utilities.ts | 1 + 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d825b0d971758..c034f12b2b712 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22340,6 +22340,7 @@ namespace ts { break; case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 32e7ca19dd313..9f60d2ee85ef7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -489,6 +489,15 @@ namespace ts { return visitNode(cbNode, (node).fullName) || visitNode(cbNode, (node).typeExpression); } + case SyntaxKind.JSDocCallbackTag: + return visitNode(cbNode, (node as JSDocCallbackTag).fullName) || + visitNode(cbNode, (node as JSDocCallbackTag).signature); + case SyntaxKind.JSDocSignature: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, (node).typeParameters) || + visitNodes(cbNode, cbNodes, (node).parameters) || + visitNode(cbNode, (node).type); case SyntaxKind.JSDocTypeLiteral: if ((node as JSDocTypeLiteral).jsDocPropertyTags) { for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) { @@ -6457,6 +6466,9 @@ namespace ts { case "typedef": tag = parseTypedefTag(atToken, tagName); break; + case "callback": + tag = parseCallbackTag(atToken, tagName); + break; default: tag = parseUnknownTag(atToken, tagName); break; @@ -6710,6 +6722,14 @@ namespace ts { return finishNode(tag); } + function parseCallbackTag(atToken: AtToken, tagName: Identifier): JSDocCallbackTag { + const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag; + callbackTag.atToken = atToken; + callbackTag.tagName = tagName; + callbackTag.fullName = parseJSDocTypeNameWithNamespace(/*flags*/ 0); + return callbackTag; + } + function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag { const typeExpression = tryParseTypeExpression(); skipWhitespace(); @@ -6765,24 +6785,24 @@ namespace ts { } return finishNode(typedefTag); + } - function parseJSDocTypeNameWithNamespace(flags: NodeFlags) { - const pos = scanner.getTokenPos(); - const typeNameOrNamespaceName = parseJSDocIdentifierName(); - - if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) { - const jsDocNamespaceNode = createNode(SyntaxKind.ModuleDeclaration, pos); - jsDocNamespaceNode.flags |= flags; - jsDocNamespaceNode.name = typeNameOrNamespaceName; - jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace); - return finishNode(jsDocNamespaceNode); - } + function parseJSDocTypeNameWithNamespace(flags: NodeFlags) { + const pos = scanner.getTokenPos(); + const typeNameOrNamespaceName = parseJSDocIdentifierName(); + + if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) { + const jsDocNamespaceNode = createNode(SyntaxKind.ModuleDeclaration, pos); + jsDocNamespaceNode.flags |= flags; + jsDocNamespaceNode.name = typeNameOrNamespaceName; + jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace); + return finishNode(jsDocNamespaceNode); + } - if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) { - typeNameOrNamespaceName.isInJSDocNamespace = true; - } - return typeNameOrNamespaceName; + if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) { + typeNameOrNamespaceName.isInJSDocNamespace = true; } + return typeNameOrNamespaceName; } function escapedTextsEqual(a: EntityName, b: EntityName): boolean { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 04db855d4f8c8..b98582e982ac3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -509,6 +509,7 @@ namespace ts { export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters { switch (node.kind) { case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.MethodSignature: case SyntaxKind.IndexSignature: From 248cd06ad3bef71f87722ee3d40e2b9f5d034188 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 11:34:15 -0700 Subject: [PATCH 04/49] Parsing done; types are uglier; doesn't crash but doesn't pass --- src/compiler/checker.ts | 1 - src/compiler/parser.ts | 52 +++++++++++++++++++++++++-------------- src/compiler/types.ts | 8 +++--- src/compiler/utilities.ts | 1 - 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c034f12b2b712..d825b0d971758 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22340,7 +22340,6 @@ namespace ts { break; case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9f60d2ee85ef7..dd197f7ab265e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6726,8 +6726,31 @@ namespace ts { const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag; callbackTag.atToken = atToken; callbackTag.tagName = tagName; - callbackTag.fullName = parseJSDocTypeNameWithNamespace(/*flags*/ 0); - return callbackTag; + callbackTag.fullName = parseJSDocTypeNameWithNamespace(); + callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName); + skipWhitespace(); + + let child: JSDocParameterTag | false; + const start = scanner.getStartPos(); + const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.Parameter) as JSDocParameterTag)) { + // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); + jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); + } + callbackTag.signature = finishNode(jsdocSignature); + return finishNode(callbackTag); + } + + function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { + if (fullName) { + let rightNode = fullName; + while (true) { + if (ts.isIdentifier(rightNode) || !rightNode.body) { + return ts.isIdentifier(rightNode) ? rightNode : rightNode.name; + } + rightNode = rightNode.body; + } + } } function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag { @@ -6737,19 +6760,8 @@ namespace ts { const typedefTag = createNode(SyntaxKind.JSDocTypedefTag, atToken.pos); typedefTag.atToken = atToken; typedefTag.tagName = tagName; - typedefTag.fullName = parseJSDocTypeNameWithNamespace(/*flags*/ 0); - if (typedefTag.fullName) { - let rightNode = typedefTag.fullName; - while (true) { - if (rightNode.kind === SyntaxKind.Identifier || !rightNode.body) { - // if node is identifier - use it as name - // otherwise use name of the rightmost part that we were able to parse - typedefTag.name = rightNode.kind === SyntaxKind.Identifier ? rightNode : rightNode.name; - break; - } - rightNode = rightNode.body; - } - } + typedefTag.fullName = parseJSDocTypeNameWithNamespace(); + typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); skipWhitespace(); typedefTag.typeExpression = typeExpression; @@ -6787,19 +6799,21 @@ namespace ts { return finishNode(typedefTag); } - function parseJSDocTypeNameWithNamespace(flags: NodeFlags) { + function parseJSDocTypeNameWithNamespace(nested?: boolean) { const pos = scanner.getTokenPos(); const typeNameOrNamespaceName = parseJSDocIdentifierName(); if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) { const jsDocNamespaceNode = createNode(SyntaxKind.ModuleDeclaration, pos); - jsDocNamespaceNode.flags |= flags; + if (nested) { + jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace; + } jsDocNamespaceNode.name = typeNameOrNamespaceName; - jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace); + jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true); return finishNode(jsDocNamespaceNode); } - if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) { + if (typeNameOrNamespaceName && nested) { typeNameOrNamespaceName.isInJSDocNamespace = true; } return typeNameOrNamespaceName; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d71aa8404e262..0458dc3642077 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -824,7 +824,6 @@ namespace ts { | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType - | JSDocSignature // TODO: Different from JSDocFunctionType?????? | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration @@ -2396,9 +2395,12 @@ namespace ts { signature: JSDocSignature; } - // TODO: name it JSDocSignatureDeclaration? Could just try to reuse JSDocTypeLiteral - export interface JSDocSignature extends JSDocType, SignatureDeclarationBase { + // TODO: Could just try to reuse JSDocTypeLiteral + export interface JSDocSignature extends JSDocType, Declaration { kind: SyntaxKind.JSDocSignature; + typeParameters?: ReadonlyArray; + parameters: ReadonlyArray; + type: JSDocReturnTag | undefined; } export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b98582e982ac3..04db855d4f8c8 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -509,7 +509,6 @@ namespace ts { export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters { switch (node.kind) { case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.MethodSignature: case SyntaxKind.IndexSignature: From 2ce53ba4f0873cbfc550d3f3eaecfba2e891d2f5 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 13:27:31 -0700 Subject: [PATCH 05/49] Bind callback tag Builds but tests still don't pass --- src/compiler/binder.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d470a9d778509..297e0d50e9eb7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -118,7 +118,7 @@ namespace ts { let thisParentContainer: Node; // Container one level up let blockScopeContainer: Node; let lastContainer: Node; - let delayedTypedefs: { typedef: JSDocTypedefTag, container: Node, lastContainer: Node, blockScopeContainer: Node, parent: Node }[]; + let delayedTypedefs: { typeAlias: JSDocTypedefTag | JSDocCallbackTag, container: Node, lastContainer: Node, blockScopeContainer: Node, parent: Node }[]; let seenThisKeyword: boolean; // state used by control flow analysis @@ -273,6 +273,7 @@ namespace ts { return InternalSymbolName.Constructor; case SyntaxKind.FunctionType: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: return InternalSymbolName.Call; case SyntaxKind.ConstructorType: case SyntaxKind.ConstructSignature: @@ -1452,6 +1453,7 @@ namespace ts { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.JSDocFunctionType: case SyntaxKind.FunctionType: case SyntaxKind.ConstructSignature: @@ -1541,6 +1543,7 @@ namespace ts { case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.IndexSignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: @@ -1646,7 +1649,7 @@ namespace ts { return state; } - function bindFunctionOrConstructorType(node: SignatureDeclaration): void { + function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { // For a given function symbol "<...>(...) => T" we want to generate a symbol identical // to the one we would get for: { <...>(...): T } // @@ -1757,7 +1760,7 @@ namespace ts { const saveParent = parent; for (const delay of delayedTypedefs) { ({ container, lastContainer, blockScopeContainer, parent } = delay); - bindBlockScopedDeclaration(delay.typedef, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + bindBlockScopedDeclaration(delay.typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); } container = saveContainer; lastContainer = saveLastContainer; @@ -2100,6 +2103,7 @@ namespace ts { case SyntaxKind.TypeParameter: return bindTypeParameter(node as TypeParameterDeclaration); case SyntaxKind.Parameter: + case SyntaxKind.JSDocParameterTag: return bindParameter(node); case SyntaxKind.VariableDeclaration: return bindVariableDeclarationOrBindingElement(node); @@ -2137,8 +2141,9 @@ namespace ts { return bindPropertyOrMethodOrAccessor(node, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); case SyntaxKind.FunctionType: case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructorType: - return bindFunctionOrConstructorType(node); + return bindFunctionOrConstructorType(node); case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.MappedType: @@ -2211,10 +2216,11 @@ namespace ts { SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property; return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypedefTag: { + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: { const { fullName } = node as JSDocTypedefTag; if (!fullName || fullName.kind === SyntaxKind.Identifier) { - (delayedTypedefs || (delayedTypedefs = [])).push({ typedef: node as JSDocTypedefTag, container, lastContainer, blockScopeContainer, parent }); + (delayedTypedefs || (delayedTypedefs = [])).push({ typeAlias: node as JSDocTypedefTag | JSDocCallbackTag, container, lastContainer, blockScopeContainer, parent }); } break; } @@ -2611,7 +2617,7 @@ namespace ts { } } - function bindParameter(node: ParameterDeclaration) { + function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) @@ -2619,7 +2625,7 @@ namespace ts { } if (isBindingPattern(node.name)) { - bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + node.parent.parameters.indexOf(node) as __String); + bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String); } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); From 8991502766fa1162989af2dec3e9bfc1cc9461d4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 15:03:04 -0700 Subject: [PATCH 06/49] Only bind param tags inside callback tags --- src/compiler/binder.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 297e0d50e9eb7..338aaeca7b95d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2618,6 +2618,9 @@ namespace ts { } function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { + return; + } if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) From 7737c7c590cfe8fae51f58178b1bdccb5c34879f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 15:30:00 -0700 Subject: [PATCH 07/49] Fix binding switch to only handle param tags once --- src/compiler/binder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 338aaeca7b95d..d189c69e2bb10 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2103,7 +2103,6 @@ namespace ts { case SyntaxKind.TypeParameter: return bindTypeParameter(node as TypeParameterDeclaration); case SyntaxKind.Parameter: - case SyntaxKind.JSDocParameterTag: return bindParameter(node); case SyntaxKind.VariableDeclaration: return bindVariableDeclarationOrBindingElement(node); @@ -2206,6 +2205,9 @@ namespace ts { return updateStrictModeStatementList((node).statements); case SyntaxKind.JSDocParameterTag: + if (node.parent.kind === SyntaxKind.JSDocCallbackTag) { + return bindParameter(node as JSDocParameterTag); + } if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { break; } From ec7ddf88a4e4bb432d27c9b33c9ee25e5a80c47c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 15:34:26 -0700 Subject: [PATCH 08/49] Checking is 1/3 done or so. Now I'm going to go rename some members to be more uniform. I hate unnnecessary conditionals. --- src/compiler/checker.ts | 21 +++++++++++---------- src/compiler/utilities.ts | 12 ++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d825b0d971758..2ca479492b5e6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5355,9 +5355,9 @@ namespace ts { return unknownType; } - const declaration = find(symbol.declarations, d => - d.kind === SyntaxKind.JSDocTypedefTag || d.kind === SyntaxKind.TypeAliasDeclaration); - const typeNode = declaration.kind === SyntaxKind.JSDocTypedefTag ? declaration.typeExpression : declaration.type; + const declaration = find(symbol.declarations, d => + isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration); + const typeNode = isJSDocTypedefTag(declaration) ? declaration.typeExpression : isJSDocCallbackTag(declaration) ? declaration.signature : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType; @@ -7575,24 +7575,25 @@ namespace ts { */ function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[]): Type { const type = getDeclaredTypeOfSymbol(symbol); + // TODO: call getEffectiveTypeParameterDeclarations here and upgrade it to understand in-comment template tags as type parameters const typeParameters = getSymbolLinks(symbol).typeParameters; if (typeParameters) { const numTypeArguments = length(node.typeArguments); const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { error(node, - minTypeArgumentCount === typeParameters.length - ? Diagnostics.Generic_type_0_requires_1_type_argument_s - : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, - symbolToString(symbol), - minTypeArgumentCount, - typeParameters.length); + minTypeArgumentCount === typeParameters.length + ? Diagnostics.Generic_type_0_requires_1_type_argument_s + : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, + symbolToString(symbol), + minTypeArgumentCount, + typeParameters.length); return unknownType; } return getTypeAliasInstantiation(symbol, typeArguments); } return checkNoTypeArguments(node, symbol) ? type : unknownType; - } + } function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { switch (node.kind) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 04db855d4f8c8..e41502c9e6db7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1772,6 +1772,10 @@ namespace ts { ((node as JSDocFunctionType).parameters[0].name as Identifier).escapedText === "new"; } + export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag { + return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag; + } + function getSourceOfAssignment(node: Node): Node { return isExpressionStatement(node) && node.expression && isBinaryExpression(node.expression) && @@ -5435,6 +5439,14 @@ namespace ts { export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { return node.kind === SyntaxKind.JSDocTypeLiteral; } + + export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag { + return node.kind === SyntaxKind.JSDocCallbackTag; + } + + export function isJSDocSignature(node: Node): node is JSDocSignature { + return node.kind === SyntaxKind.JSDocSignature; + } } // Node tests From 7d2233a00e5c6d794c1de32c03802e8ccce1914c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 15:44:10 -0700 Subject: [PATCH 09/49] Rename typeExpression to type (for some jsdoc) (maybe I'll rename more later) --- src/compiler/checker.ts | 7 +++---- src/compiler/parser.ts | 16 ++++++++-------- src/compiler/types.ts | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ca479492b5e6..2b2e3e00cd014 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5357,9 +5357,8 @@ namespace ts { const declaration = find(symbol.declarations, d => isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration); - const typeNode = isJSDocTypedefTag(declaration) ? declaration.typeExpression : isJSDocCallbackTag(declaration) ? declaration.signature : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. - let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType; + let type = declaration.type ? getTypeFromTypeNode(declaration.type) : unknownType; if (popTypeResolution()) { const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); @@ -22147,7 +22146,7 @@ namespace ts { } function checkJSDocTypedefTag(node: JSDocTypedefTag) { - if (!node.typeExpression) { + if (!node.type) { // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); } @@ -22155,7 +22154,7 @@ namespace ts { if (node.name) { checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); } - checkSourceElement(node.typeExpression); + checkSourceElement(node.type); } function checkJSDocParameterTag(node: JSDocParameterTag) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dd197f7ab265e..d516c3898bad3 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -480,18 +480,18 @@ namespace ts { case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNode, cbNodes, (node).typeParameters); case SyntaxKind.JSDocTypedefTag: - if ((node as JSDocTypedefTag).typeExpression && - (node as JSDocTypedefTag).typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - return visitNode(cbNode, (node).typeExpression) || + if ((node as JSDocTypedefTag).type && + (node as JSDocTypedefTag).type.kind === SyntaxKind.JSDocTypeExpression) { + return visitNode(cbNode, (node).type) || visitNode(cbNode, (node).fullName); } else { return visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).typeExpression); + visitNode(cbNode, (node).type); } case SyntaxKind.JSDocCallbackTag: return visitNode(cbNode, (node as JSDocCallbackTag).fullName) || - visitNode(cbNode, (node as JSDocCallbackTag).signature); + visitNode(cbNode, (node as JSDocCallbackTag).type); case SyntaxKind.JSDocSignature: return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || @@ -6737,7 +6737,7 @@ namespace ts { // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } - callbackTag.signature = finishNode(jsdocSignature); + callbackTag.type = finishNode(jsdocSignature); return finishNode(callbackTag); } @@ -6764,7 +6764,7 @@ namespace ts { typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); skipWhitespace(); - typedefTag.typeExpression = typeExpression; + typedefTag.type = typeExpression; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { let child: JSDocTypeTag | JSDocPropertyTag | false; let jsdocTypeLiteral: JSDocTypeLiteral; @@ -6790,7 +6790,7 @@ namespace ts { if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { jsdocTypeLiteral.isArrayType = true; } - typedefTag.typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + typedefTag.type = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? childTypeTag.typeExpression : finishNode(jsdocTypeLiteral); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0458dc3642077..b0939668d91af 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2384,7 +2384,7 @@ namespace ts { kind: SyntaxKind.JSDocTypedefTag; fullName?: JSDocNamespaceDeclaration | Identifier; name?: Identifier; - typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; + type?: JSDocTypeExpression | JSDocTypeLiteral; } export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration { @@ -2392,7 +2392,7 @@ namespace ts { kind: SyntaxKind.JSDocCallbackTag; fullName?: JSDocNamespaceDeclaration | Identifier; name?: Identifier; // TODO: Not sure whether this rigamarole is needed for callback...but probably! - signature: JSDocSignature; + type: JSDocSignature; } // TODO: Could just try to reuse JSDocTypeLiteral From f41a96b24d44a6b696d39eee9e91ef7f606bea52 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 15:59:12 -0700 Subject: [PATCH 10/49] Rename the rest of typeExpressions Turns out there is a constraint in services such that they all need to be named the same. --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 22 +++++++++++----------- src/compiler/parser.ts | 18 +++++++++--------- src/compiler/types.ts | 6 +++--- src/compiler/utilities.ts | 8 ++++---- src/services/classifier.ts | 12 ++++++------ src/services/completions.ts | 8 ++++---- src/services/jsDoc.ts | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d189c69e2bb10..15925377cd956 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2214,7 +2214,7 @@ namespace ts { // falls through case SyntaxKind.JSDocPropertyTag: const propTag = node as JSDocPropertyLikeTag; - const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? + const flags = propTag.isBracketed || propTag.type && propTag.type.type.kind === SyntaxKind.JSDocOptionalType ? SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property; return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2b2e3e00cd014..8936f5cc1b78a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4708,8 +4708,8 @@ namespace ts { if (declaration.kind === SyntaxKind.ExportAssignment) { return links.type = checkExpression((declaration).expression); } - if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.typeExpression) { - return links.type = getTypeFromTypeNode(declaration.typeExpression.type); + if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.type) { + return links.type = getTypeFromTypeNode(declaration.type.type); } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { @@ -6911,8 +6911,8 @@ namespace ts { return isInJavaScriptFile(node) && ( // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType node.type && node.type.kind === SyntaxKind.JSDocOptionalType - || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => - isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); + || getJSDocParameterTags(node).some(({ isBracketed, type }) => + isBracketed || !!type && type.type.kind === SyntaxKind.JSDocOptionalType)); } function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { @@ -7105,7 +7105,7 @@ namespace ts { const lastParam = lastOrUndefined(declaration.parameters); const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); const lastParamVariadicType = firstDefined(lastParamTags, p => - p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); + p.type && isJSDocVariadicType(p.type.type) ? p.type.type : undefined); const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String); syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType; @@ -15166,7 +15166,7 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: { // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. const tag = isInJavaScriptFile(parent) ? getJSDocTypeTag(parent) : undefined; - return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent); + return tag ? getTypeFromTypeNode(tag.type.type) : getContextualType(parent); } case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent); @@ -20465,7 +20465,7 @@ namespace ts { function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { const tag = isInJavaScriptFile(node) ? getJSDocTypeTag(node) : undefined; if (tag) { - return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); + return checkAssertionWorker(tag, tag.type.type, node.expression, checkMode); } return checkExpression(node.expression, checkMode); } @@ -22147,7 +22147,7 @@ namespace ts { function checkJSDocTypedefTag(node: JSDocTypedefTag) { if (!node.type) { - // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. + // If the node had `@property` tags, `type` would have been set to the first property tag. error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); } @@ -22158,7 +22158,7 @@ namespace ts { } function checkJSDocParameterTag(node: JSDocParameterTag) { - checkSourceElement(node.typeExpression); + checkSourceElement(node.type); if (!getParameterSymbolFromJSDoc(node)) { const decl = getHostSignatureFromJSDoc(node); // don't issue an error for invalid hosts -- just functions -- @@ -22175,8 +22175,8 @@ namespace ts { idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); } else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && - node.typeExpression && node.typeExpression.type && - !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { + node.type && node.type.type && + !isArrayType(getTypeFromTypeNode(node.type.type))) { error(node.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d516c3898bad3..e47908efa32bc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -465,16 +465,16 @@ namespace ts { case SyntaxKind.JSDocPropertyTag: if ((node as JSDocPropertyLikeTag).isNameFirst) { return visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).typeExpression); + visitNode(cbNode, (node).type); } else { - return visitNode(cbNode, (node).typeExpression) || + return visitNode(cbNode, (node).type) || visitNode(cbNode, (node).name); } case SyntaxKind.JSDocReturnTag: - return visitNode(cbNode, (node).typeExpression); + return visitNode(cbNode, (node).type); case SyntaxKind.JSDocTypeTag: - return visitNode(cbNode, (node).typeExpression); + return visitNode(cbNode, (node).type); case SyntaxKind.JSDocAugmentsTag: return visitNode(cbNode, (node).class); case SyntaxKind.JSDocTemplateTag: @@ -6628,7 +6628,7 @@ namespace ts { } result.atToken = atToken; result.tagName = tagName; - result.typeExpression = typeExpression; + result.type = typeExpression; result.name = name; result.isNameFirst = isNameFirst; result.isBracketed = isBracketed; @@ -6668,7 +6668,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocReturnTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; - result.typeExpression = tryParseTypeExpression(); + result.type = tryParseTypeExpression(); return finishNode(result); } @@ -6680,7 +6680,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; - result.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + result.type = parseJSDocTypeExpression(/*mayOmitBraces*/ true); return finishNode(result); } @@ -6790,8 +6790,8 @@ namespace ts { if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { jsdocTypeLiteral.isArrayType = true; } - typedefTag.type = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? - childTypeTag.typeExpression : + typedefTag.type = childTypeTag && childTypeTag.type && !isObjectOrObjectArrayTypeReference(childTypeTag.type.type) ? + childTypeTag.type : finishNode(jsdocTypeLiteral); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b0939668d91af..35e2254c2dca6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2371,12 +2371,12 @@ namespace ts { export interface JSDocReturnTag extends JSDocTag { kind: SyntaxKind.JSDocReturnTag; - typeExpression: JSDocTypeExpression; + type: JSDocTypeExpression; } export interface JSDocTypeTag extends JSDocTag { kind: SyntaxKind.JSDocTypeTag; - typeExpression: JSDocTypeExpression; + type: JSDocTypeExpression; } export interface JSDocTypedefTag extends JSDocTag, NamedDeclaration { @@ -2406,7 +2406,7 @@ namespace ts { export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; name: EntityName; - typeExpression?: JSDocTypeExpression; + type?: JSDocTypeExpression; /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ isNameFirst: boolean; isBracketed: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e41502c9e6db7..9c6e82ff0b05f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4702,7 +4702,7 @@ namespace ts { export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined { // We should have already issued an error if there were multiple type jsdocs, so just use the first one. const tag = getFirstJSDocTag(node, isJSDocTypeTag); - if (tag && tag.typeExpression && tag.typeExpression.type) { + if (tag && tag.type && tag.type.type) { return tag; } return undefined; @@ -4722,10 +4722,10 @@ namespace ts { export function getJSDocType(node: Node): TypeNode | undefined { let tag: JSDocTypeTag | JSDocParameterTag | undefined = getFirstJSDocTag(node, isJSDocTypeTag); if (!tag && isParameter(node)) { - tag = find(getJSDocParameterTags(node), tag => !!tag.typeExpression); + tag = find(getJSDocParameterTags(node), tag => !!tag.type); } - return tag && tag.typeExpression && tag.typeExpression.type; + return tag && tag.type && tag.type.type; } /** @@ -4736,7 +4736,7 @@ namespace ts { */ export function getJSDocReturnType(node: Node): TypeNode | undefined { const returnTag = getJSDocReturnTag(node); - return returnTag && returnTag.typeExpression && returnTag.typeExpression.type; + return returnTag && returnTag.type && returnTag.type.type; } /** Get all JSDoc tags related to a node, including those on parent nodes. */ diff --git a/src/services/classifier.ts b/src/services/classifier.ts index 89c96a45021b9..9b39d3a7c5d34 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -708,10 +708,10 @@ namespace ts { processJSDocTemplateTag(tag); break; case SyntaxKind.JSDocTypeTag: - processElement((tag).typeExpression); + processElement((tag).type); break; case SyntaxKind.JSDocReturnTag: - processElement((tag).typeExpression); + processElement((tag).type); break; } @@ -732,10 +732,10 @@ namespace ts { pos = tag.name.end; } - if (tag.typeExpression) { - pushCommentRange(pos, tag.typeExpression.pos - pos); - processElement(tag.typeExpression); - pos = tag.typeExpression.end; + if (tag.type) { + pushCommentRange(pos, tag.type.pos - pos); + processElement(tag.type); + pos = tag.type.end; } if (!tag.isNameFirst) { diff --git a/src/services/completions.ts b/src/services/completions.ts index f5aba7f99e8f7..0fba04868e78a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -817,14 +817,14 @@ namespace ts.Completions { if (tag.tagName.pos <= position && position <= tag.tagName.end) { return { kind: CompletionDataKind.JsDocTagName }; } - if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + if (isTagWithType(tag) && tag.type && tag.type.kind === SyntaxKind.JSDocTypeExpression) { currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ true); if (!currentToken || (!isDeclarationName(currentToken) && (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || (currentToken.parent).name !== currentToken))) { // Use as type location if inside tag's type expression - insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); + insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.type); } } if (isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { @@ -1002,9 +1002,9 @@ namespace ts.Completions { const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, position, sourceFile, typeChecker); return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer }; - type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; + type JSDocTagWithType = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; - function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { + function isTagWithType(tag: JSDocTag): tag is JSDocTagWithType { switch (tag.kind) { case SyntaxKind.JSDocParameterTag: case SyntaxKind.JSDocPropertyTag: diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index ca0dafbc74cb4..cdf42bf8b0ca7 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -96,7 +96,7 @@ namespace ts.JsDoc { case SyntaxKind.JSDocTemplateTag: return withList((tag as JSDocTemplateTag).typeParameters); case SyntaxKind.JSDocTypeTag: - return withNode((tag as JSDocTypeTag).typeExpression); + return withNode((tag as JSDocTypeTag).type); case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocPropertyTag: case SyntaxKind.JSDocParameterTag: From 64631e580d2f56b5ade44774bd61d4232150cb8c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 16:08:53 -0700 Subject: [PATCH 11/49] Few more checker changes --- src/compiler/checker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8936f5cc1b78a..da4406b7a8cb7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21616,8 +21616,9 @@ namespace ts { switch (d.kind) { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: - // A jsdoc typedef is, by definition, a type alias + // A jsdoc typedef and callback are, by definition, type aliases case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: return DeclarationSpaces.ExportType; case SyntaxKind.ModuleDeclaration: return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated @@ -22145,7 +22146,7 @@ namespace ts { } } - function checkJSDocTypedefTag(node: JSDocTypedefTag) { + function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { if (!node.type) { // If the node had `@property` tags, `type` would have been set to the first property tag. error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); @@ -25051,7 +25052,8 @@ namespace ts { case SyntaxKind.JSDocAugmentsTag: return checkJSDocAugmentsTag(node as JSDocAugmentsTag); case SyntaxKind.JSDocTypedefTag: - return checkJSDocTypedefTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocCallbackTag: + return checkJSDocTypeAliasTag(node as JSDocTypedefTag); case SyntaxKind.JSDocParameterTag: return checkJSDocParameterTag(node as JSDocParameterTag); case SyntaxKind.JSDocFunctionType: From 009c411e80d34cee1eddc83bfbd81e8ec6c908c3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 16:09:03 -0700 Subject: [PATCH 12/49] Revert "Rename the rest of typeExpressions" This reverts commit f41a96b24d44a6b696d39eee9e91ef7f606bea52. --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 22 +++++++++++----------- src/compiler/parser.ts | 18 +++++++++--------- src/compiler/types.ts | 6 +++--- src/compiler/utilities.ts | 8 ++++---- src/services/classifier.ts | 12 ++++++------ src/services/completions.ts | 8 ++++---- src/services/jsDoc.ts | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 15925377cd956..d189c69e2bb10 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2214,7 +2214,7 @@ namespace ts { // falls through case SyntaxKind.JSDocPropertyTag: const propTag = node as JSDocPropertyLikeTag; - const flags = propTag.isBracketed || propTag.type && propTag.type.type.kind === SyntaxKind.JSDocOptionalType ? + const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property; return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da4406b7a8cb7..ec36d0606f97b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4708,8 +4708,8 @@ namespace ts { if (declaration.kind === SyntaxKind.ExportAssignment) { return links.type = checkExpression((declaration).expression); } - if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.type) { - return links.type = getTypeFromTypeNode(declaration.type.type); + if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.typeExpression) { + return links.type = getTypeFromTypeNode(declaration.typeExpression.type); } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { @@ -6911,8 +6911,8 @@ namespace ts { return isInJavaScriptFile(node) && ( // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType node.type && node.type.kind === SyntaxKind.JSDocOptionalType - || getJSDocParameterTags(node).some(({ isBracketed, type }) => - isBracketed || !!type && type.type.kind === SyntaxKind.JSDocOptionalType)); + || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => + isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); } function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { @@ -7105,7 +7105,7 @@ namespace ts { const lastParam = lastOrUndefined(declaration.parameters); const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); const lastParamVariadicType = firstDefined(lastParamTags, p => - p.type && isJSDocVariadicType(p.type.type) ? p.type.type : undefined); + p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String); syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType; @@ -15166,7 +15166,7 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: { // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. const tag = isInJavaScriptFile(parent) ? getJSDocTypeTag(parent) : undefined; - return tag ? getTypeFromTypeNode(tag.type.type) : getContextualType(parent); + return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent); } case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent); @@ -20465,7 +20465,7 @@ namespace ts { function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { const tag = isInJavaScriptFile(node) ? getJSDocTypeTag(node) : undefined; if (tag) { - return checkAssertionWorker(tag, tag.type.type, node.expression, checkMode); + return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); } return checkExpression(node.expression, checkMode); } @@ -22148,7 +22148,7 @@ namespace ts { function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { if (!node.type) { - // If the node had `@property` tags, `type` would have been set to the first property tag. + // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); } @@ -22159,7 +22159,7 @@ namespace ts { } function checkJSDocParameterTag(node: JSDocParameterTag) { - checkSourceElement(node.type); + checkSourceElement(node.typeExpression); if (!getParameterSymbolFromJSDoc(node)) { const decl = getHostSignatureFromJSDoc(node); // don't issue an error for invalid hosts -- just functions -- @@ -22176,8 +22176,8 @@ namespace ts { idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); } else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && - node.type && node.type.type && - !isArrayType(getTypeFromTypeNode(node.type.type))) { + node.typeExpression && node.typeExpression.type && + !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { error(node.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e47908efa32bc..d516c3898bad3 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -465,16 +465,16 @@ namespace ts { case SyntaxKind.JSDocPropertyTag: if ((node as JSDocPropertyLikeTag).isNameFirst) { return visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).type); + visitNode(cbNode, (node).typeExpression); } else { - return visitNode(cbNode, (node).type) || + return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).name); } case SyntaxKind.JSDocReturnTag: - return visitNode(cbNode, (node).type); + return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTypeTag: - return visitNode(cbNode, (node).type); + return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocAugmentsTag: return visitNode(cbNode, (node).class); case SyntaxKind.JSDocTemplateTag: @@ -6628,7 +6628,7 @@ namespace ts { } result.atToken = atToken; result.tagName = tagName; - result.type = typeExpression; + result.typeExpression = typeExpression; result.name = name; result.isNameFirst = isNameFirst; result.isBracketed = isBracketed; @@ -6668,7 +6668,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocReturnTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; - result.type = tryParseTypeExpression(); + result.typeExpression = tryParseTypeExpression(); return finishNode(result); } @@ -6680,7 +6680,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; - result.type = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + result.typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); return finishNode(result); } @@ -6790,8 +6790,8 @@ namespace ts { if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { jsdocTypeLiteral.isArrayType = true; } - typedefTag.type = childTypeTag && childTypeTag.type && !isObjectOrObjectArrayTypeReference(childTypeTag.type.type) ? - childTypeTag.type : + typedefTag.type = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + childTypeTag.typeExpression : finishNode(jsdocTypeLiteral); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 35e2254c2dca6..b0939668d91af 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2371,12 +2371,12 @@ namespace ts { export interface JSDocReturnTag extends JSDocTag { kind: SyntaxKind.JSDocReturnTag; - type: JSDocTypeExpression; + typeExpression: JSDocTypeExpression; } export interface JSDocTypeTag extends JSDocTag { kind: SyntaxKind.JSDocTypeTag; - type: JSDocTypeExpression; + typeExpression: JSDocTypeExpression; } export interface JSDocTypedefTag extends JSDocTag, NamedDeclaration { @@ -2406,7 +2406,7 @@ namespace ts { export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; name: EntityName; - type?: JSDocTypeExpression; + typeExpression?: JSDocTypeExpression; /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ isNameFirst: boolean; isBracketed: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9c6e82ff0b05f..e41502c9e6db7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4702,7 +4702,7 @@ namespace ts { export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined { // We should have already issued an error if there were multiple type jsdocs, so just use the first one. const tag = getFirstJSDocTag(node, isJSDocTypeTag); - if (tag && tag.type && tag.type.type) { + if (tag && tag.typeExpression && tag.typeExpression.type) { return tag; } return undefined; @@ -4722,10 +4722,10 @@ namespace ts { export function getJSDocType(node: Node): TypeNode | undefined { let tag: JSDocTypeTag | JSDocParameterTag | undefined = getFirstJSDocTag(node, isJSDocTypeTag); if (!tag && isParameter(node)) { - tag = find(getJSDocParameterTags(node), tag => !!tag.type); + tag = find(getJSDocParameterTags(node), tag => !!tag.typeExpression); } - return tag && tag.type && tag.type.type; + return tag && tag.typeExpression && tag.typeExpression.type; } /** @@ -4736,7 +4736,7 @@ namespace ts { */ export function getJSDocReturnType(node: Node): TypeNode | undefined { const returnTag = getJSDocReturnTag(node); - return returnTag && returnTag.type && returnTag.type.type; + return returnTag && returnTag.typeExpression && returnTag.typeExpression.type; } /** Get all JSDoc tags related to a node, including those on parent nodes. */ diff --git a/src/services/classifier.ts b/src/services/classifier.ts index 9b39d3a7c5d34..89c96a45021b9 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -708,10 +708,10 @@ namespace ts { processJSDocTemplateTag(tag); break; case SyntaxKind.JSDocTypeTag: - processElement((tag).type); + processElement((tag).typeExpression); break; case SyntaxKind.JSDocReturnTag: - processElement((tag).type); + processElement((tag).typeExpression); break; } @@ -732,10 +732,10 @@ namespace ts { pos = tag.name.end; } - if (tag.type) { - pushCommentRange(pos, tag.type.pos - pos); - processElement(tag.type); - pos = tag.type.end; + if (tag.typeExpression) { + pushCommentRange(pos, tag.typeExpression.pos - pos); + processElement(tag.typeExpression); + pos = tag.typeExpression.end; } if (!tag.isNameFirst) { diff --git a/src/services/completions.ts b/src/services/completions.ts index 0fba04868e78a..f5aba7f99e8f7 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -817,14 +817,14 @@ namespace ts.Completions { if (tag.tagName.pos <= position && position <= tag.tagName.end) { return { kind: CompletionDataKind.JsDocTagName }; } - if (isTagWithType(tag) && tag.type && tag.type.kind === SyntaxKind.JSDocTypeExpression) { + if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ true); if (!currentToken || (!isDeclarationName(currentToken) && (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || (currentToken.parent).name !== currentToken))) { // Use as type location if inside tag's type expression - insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.type); + insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); } } if (isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { @@ -1002,9 +1002,9 @@ namespace ts.Completions { const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, position, sourceFile, typeChecker); return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer }; - type JSDocTagWithType = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; + type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; - function isTagWithType(tag: JSDocTag): tag is JSDocTagWithType { + function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { switch (tag.kind) { case SyntaxKind.JSDocParameterTag: case SyntaxKind.JSDocPropertyTag: diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index cdf42bf8b0ca7..ca0dafbc74cb4 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -96,7 +96,7 @@ namespace ts.JsDoc { case SyntaxKind.JSDocTemplateTag: return withList((tag as JSDocTemplateTag).typeParameters); case SyntaxKind.JSDocTypeTag: - return withNode((tag as JSDocTypeTag).type); + return withNode((tag as JSDocTypeTag).typeExpression); case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocPropertyTag: case SyntaxKind.JSDocParameterTag: From 0de72c2411c5c562ab40046ca8e45ef8084ec55d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 16:09:55 -0700 Subject: [PATCH 13/49] Revert "Rename typeExpression to type (for some jsdoc)" This reverts commit 7d2233a00e5c6d794c1de32c03802e8ccce1914c. --- src/compiler/checker.ts | 7 ++++--- src/compiler/parser.ts | 16 ++++++++-------- src/compiler/types.ts | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ec36d0606f97b..75d5b4f5d7313 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5357,8 +5357,9 @@ namespace ts { const declaration = find(symbol.declarations, d => isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration); + const typeNode = isJSDocTypedefTag(declaration) ? declaration.typeExpression : isJSDocCallbackTag(declaration) ? declaration.signature : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. - let type = declaration.type ? getTypeFromTypeNode(declaration.type) : unknownType; + let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType; if (popTypeResolution()) { const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); @@ -22147,7 +22148,7 @@ namespace ts { } function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { - if (!node.type) { + if (!node.typeExpression) { // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); } @@ -22155,7 +22156,7 @@ namespace ts { if (node.name) { checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); } - checkSourceElement(node.type); + checkSourceElement(node.typeExpression); } function checkJSDocParameterTag(node: JSDocParameterTag) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d516c3898bad3..dd197f7ab265e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -480,18 +480,18 @@ namespace ts { case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNode, cbNodes, (node).typeParameters); case SyntaxKind.JSDocTypedefTag: - if ((node as JSDocTypedefTag).type && - (node as JSDocTypedefTag).type.kind === SyntaxKind.JSDocTypeExpression) { - return visitNode(cbNode, (node).type) || + if ((node as JSDocTypedefTag).typeExpression && + (node as JSDocTypedefTag).typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).fullName); } else { return visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).type); + visitNode(cbNode, (node).typeExpression); } case SyntaxKind.JSDocCallbackTag: return visitNode(cbNode, (node as JSDocCallbackTag).fullName) || - visitNode(cbNode, (node as JSDocCallbackTag).type); + visitNode(cbNode, (node as JSDocCallbackTag).signature); case SyntaxKind.JSDocSignature: return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || @@ -6737,7 +6737,7 @@ namespace ts { // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } - callbackTag.type = finishNode(jsdocSignature); + callbackTag.signature = finishNode(jsdocSignature); return finishNode(callbackTag); } @@ -6764,7 +6764,7 @@ namespace ts { typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); skipWhitespace(); - typedefTag.type = typeExpression; + typedefTag.typeExpression = typeExpression; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { let child: JSDocTypeTag | JSDocPropertyTag | false; let jsdocTypeLiteral: JSDocTypeLiteral; @@ -6790,7 +6790,7 @@ namespace ts { if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { jsdocTypeLiteral.isArrayType = true; } - typedefTag.type = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + typedefTag.typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? childTypeTag.typeExpression : finishNode(jsdocTypeLiteral); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b0939668d91af..0458dc3642077 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2384,7 +2384,7 @@ namespace ts { kind: SyntaxKind.JSDocTypedefTag; fullName?: JSDocNamespaceDeclaration | Identifier; name?: Identifier; - type?: JSDocTypeExpression | JSDocTypeLiteral; + typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; } export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration { @@ -2392,7 +2392,7 @@ namespace ts { kind: SyntaxKind.JSDocCallbackTag; fullName?: JSDocNamespaceDeclaration | Identifier; name?: Identifier; // TODO: Not sure whether this rigamarole is needed for callback...but probably! - type: JSDocSignature; + signature: JSDocSignature; } // TODO: Could just try to reuse JSDocTypeLiteral From 8ae89393b5b7d380828f9f75f9902bb3a6c644a1 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 1 May 2018 16:18:03 -0700 Subject: [PATCH 14/49] Finish undoing typeExpression rename --- src/compiler/checker.ts | 2 +- src/compiler/parser.ts | 4 ++-- src/compiler/types.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75d5b4f5d7313..ea5eca3ef5a09 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5357,7 +5357,7 @@ namespace ts { const declaration = find(symbol.declarations, d => isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration); - const typeNode = isJSDocTypedefTag(declaration) ? declaration.typeExpression : isJSDocCallbackTag(declaration) ? declaration.signature : declaration.type; + const typeNode = isJSDocTypedefTag(declaration) ? declaration.typeExpression : isJSDocCallbackTag(declaration) ? declaration.typeExpression : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dd197f7ab265e..fa1a6ffc1a1b1 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -491,7 +491,7 @@ namespace ts { } case SyntaxKind.JSDocCallbackTag: return visitNode(cbNode, (node as JSDocCallbackTag).fullName) || - visitNode(cbNode, (node as JSDocCallbackTag).signature); + visitNode(cbNode, (node as JSDocCallbackTag).typeExpression); case SyntaxKind.JSDocSignature: return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || @@ -6737,7 +6737,7 @@ namespace ts { // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } - callbackTag.signature = finishNode(jsdocSignature); + callbackTag.typeExpression = finishNode(jsdocSignature); return finishNode(callbackTag); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0458dc3642077..4cccdc1dabebb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2392,7 +2392,7 @@ namespace ts { kind: SyntaxKind.JSDocCallbackTag; fullName?: JSDocNamespaceDeclaration | Identifier; name?: Identifier; // TODO: Not sure whether this rigamarole is needed for callback...but probably! - signature: JSDocSignature; + typeExpression: JSDocSignature; } // TODO: Could just try to reuse JSDocTypeLiteral From a96bdfd2f145eaefc198c878f03d9afc21a96793 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 08:52:28 -0700 Subject: [PATCH 15/49] Rename and improve getTypeParametersForAliasSymbol Plus some other small fixes --- src/compiler/checker.ts | 26 +++++++++++-------- src/compiler/utilities.ts | 4 +++ tests/cases/conformance/jsdoc/callbackTag1.ts | 1 + 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ea5eca3ef5a09..5d06003edffd9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4695,7 +4695,7 @@ namespace ts { if (symbol.flags & SymbolFlags.Prototype) { return links.type = getTypeOfPrototypeProperty(symbol); } - // CommonsJS require/module/exports all have type any. + // CommonsJS require and module both have type any. if (symbol === requireSymbol || symbol === moduleSymbol) { return links.type = anyType; } @@ -5057,6 +5057,7 @@ namespace ts { function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] { let result: TypeParameter[]; for (const node of symbol.declarations) { + // TODO: needs to understand jsdoc typedefs and callbacks if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration) { const declaration = node; @@ -5357,7 +5358,7 @@ namespace ts { const declaration = find(symbol.declarations, d => isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration); - const typeNode = isJSDocTypedefTag(declaration) ? declaration.typeExpression : isJSDocCallbackTag(declaration) ? declaration.typeExpression : declaration.type; + const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType; @@ -8276,8 +8277,9 @@ namespace ts { function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, - getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node)); + aliasSymbol, getTypeParametersForAliasSymbol(aliasSymbol)); } return links.resolvedType; } @@ -8386,8 +8388,9 @@ namespace ts { function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), - getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node)); + aliasSymbol, getTypeParametersForAliasSymbol(aliasSymbol)); } return links.resolvedType; } @@ -8692,7 +8695,7 @@ namespace ts { const type = createObjectType(ObjectFlags.Mapped, node.symbol); type.declaration = node; type.aliasSymbol = getAliasSymbolForTypeNode(node); - type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); + type.aliasTypeArguments = getTypeParametersForAliasSymbol(type.aliasSymbol); links.resolvedType = type; // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. @@ -8800,7 +8803,8 @@ namespace ts { const links = getNodeLinks(node); if (!links.resolvedType) { const checkType = getTypeFromTypeNode(node.checkType); - const aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasTypeArguments = getTypeParametersForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isPossiblyReferencedInConditionalType(tp, node)); const root: ConditionalRoot = { @@ -8813,7 +8817,7 @@ namespace ts { inferTypeParameters: getInferTypeParameters(node), outerTypeParameters, instantiations: undefined, - aliasSymbol: getAliasSymbolForTypeNode(node), + aliasSymbol, aliasTypeArguments }; links.resolvedType = getConditionalType(root, /*mapper*/ undefined); @@ -8917,7 +8921,7 @@ namespace ts { else { let type = createObjectType(ObjectFlags.Anonymous, node.symbol); type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); + type.aliasTypeArguments = getTypeParametersForAliasSymbol(aliasSymbol); if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } @@ -8928,11 +8932,10 @@ namespace ts { } function getAliasSymbolForTypeNode(node: TypeNode) { - return node.parent.kind === SyntaxKind.TypeAliasDeclaration ? getSymbolOfNode(node.parent) : undefined; + return isTypeAlias(node.parent) ? getSymbolOfNode(node.parent) : undefined; } - function getAliasTypeArgumentsForTypeNode(node: TypeNode) { - const symbol = getAliasSymbolForTypeNode(node); + function getTypeParametersForAliasSymbol(symbol: Symbol) { return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; } @@ -9190,6 +9193,7 @@ namespace ts { case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); case SyntaxKind.TypeOperator: return getTypeFromTypeOperatorNode(node); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e41502c9e6db7..d65cccbbdb632 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1776,6 +1776,10 @@ namespace ts { return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag; } + export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration { + return isJSDocTypeAlias(node) || isTypeAliasDeclaration(node); + } + function getSourceOfAssignment(node: Node): Node { return isExpressionStatement(node) && node.expression && isBinaryExpression(node.expression) && diff --git a/tests/cases/conformance/jsdoc/callbackTag1.ts b/tests/cases/conformance/jsdoc/callbackTag1.ts index e97cd23921bcd..a0298954c6efd 100644 --- a/tests/cases/conformance/jsdoc/callbackTag1.ts +++ b/tests/cases/conformance/jsdoc/callbackTag1.ts @@ -7,6 +7,7 @@ * @param {string} s * @returns {string} What were you expecting */ +var x = 1 /** @type {Sid} smallId */ var sid = s => s + "!"; From 6a0a5eb659f13f2ab5ec11bdd8f790394e5e9d11 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 13:58:27 -0700 Subject: [PATCH 16/49] Core checking works, but is flabbergastingly messy I'm serious. --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 78 +++++++++++++++---- src/compiler/parser.ts | 23 ++++-- src/compiler/types.ts | 4 +- src/compiler/utilities.ts | 61 ++++++++++++--- src/services/codefixes/fixUnusedIdentifier.ts | 4 +- src/services/refactors/extractSymbol.ts | 8 +- .../reference/api/tsserverlibrary.d.ts | 59 +++++++++----- tests/baselines/reference/api/typescript.d.ts | 59 +++++++++----- tests/cases/conformance/jsdoc/callbackTag1.ts | 1 + .../fourslash/server/jsdocCallbackTag.ts | 2 +- 11 files changed, 217 insertions(+), 84 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d189c69e2bb10..c892b8dfcfd33 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2205,7 +2205,7 @@ namespace ts { return updateStrictModeStatementList((node).statements); case SyntaxKind.JSDocParameterTag: - if (node.parent.kind === SyntaxKind.JSDocCallbackTag) { + if (node.parent.kind === SyntaxKind.JSDocSignature) { return bindParameter(node as JSDocParameterTag); } if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d06003edffd9..cc089a193a632 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3547,6 +3547,31 @@ namespace ts { function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { const parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (!parameterDeclaration && !(isTransientSymbol(parameterSymbol) && !!parameterSymbol.isRestParameter)) { + const paramTagDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + // now do a whole bunch of stuff with the parameter tag instead + // TODO: Dedupe this! + Debug.assert(!!paramTagDeclaration); + // TODO: Might need to convert jsdoc type literal to a binding name? + Debug.assert(isIdentifier(paramTagDeclaration.name)); + const dotDotDotToken = isRestParameter(paramTagDeclaration) ? createToken(SyntaxKind.DotDotDotToken) : undefined; + const name = paramTagDeclaration.name && isIdentifier(paramTagDeclaration.name) ? + // isIdentifier(paramTagDeclaration.name) ? + setEmitFlags(getSynthesizedClone(paramTagDeclaration.name), EmitFlags.NoAsciiEscaping) : + // cloneQualifiedName(paramTagDeclaration.name) : + symbolName(parameterSymbol); + const questionToken = isOptionalParameter2(paramTagDeclaration) ? createToken(SyntaxKind.QuestionToken) : undefined; + const parameterTypeNode = typeToTypeNodeHelper(getTypeOfSymbol(parameterSymbol), context); + const parameterNode = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + dotDotDotToken, + name, + questionToken, + parameterTypeNode, + /*initializer*/ undefined); + return parameterNode; + } Debug.assert(!!parameterDeclaration || isTransientSymbol(parameterSymbol) && !!parameterSymbol.isRestParameter); let parameterType = getTypeOfSymbol(parameterSymbol); @@ -3586,6 +3611,15 @@ namespace ts { return setEmitFlags(clone, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); } } + + // function cloneQualifiedName(node: QualifiedName): EntityName { + // if (isIdentifier(node.left)) { + // return setEmitFlags(getSynthesizedClone(node.left), EmitFlags.NoAsciiEscaping); + // } + // else { + // return setEmitFlags(createQualifiedName(cloneQualifiedName(node.left), getSynthesizedClone(node.right)), EmitFlags.NoAsciiEscaping); + // } + // } } function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { @@ -4711,6 +4745,9 @@ namespace ts { if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.typeExpression) { return links.type = getTypeFromTypeNode(declaration.typeExpression.type); } + if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && isJSDocSignature(declaration.parent) && !declaration.typeExpression) { + return links.type = unknownType; + } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return unknownType; @@ -5922,7 +5959,7 @@ namespace ts { } function createSignature( - declaration: SignatureDeclaration, + declaration: SignatureDeclaration | JSDocSignature, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[], @@ -7022,7 +7059,7 @@ namespace ts { return typeArguments; } - function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature { + function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { const links = getNodeLinks(declaration); if (!links.resolvedSignature) { const parameters: Symbol[] = []; @@ -7045,6 +7082,7 @@ namespace ts { const param = declaration.parameters[i]; let paramSymbol = param.symbol; + const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; // Include parameter symbol instead of property symbol in the signature if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); @@ -7058,16 +7096,14 @@ namespace ts { parameters.push(paramSymbol); } - if (param.type && param.type.kind === SyntaxKind.LiteralType) { + if (type && type.kind === SyntaxKind.LiteralType) { hasLiteralTypes = true; } // Record a new minimum argument count if this is not an optional parameter - const isOptionalParameter = param.initializer || param.questionToken || param.dotDotDotToken || - iife && parameters.length > iife.arguments.length && !param.type || - isUntypedSignatureInJSFile || - isJSDocOptionalParameter(param); - if (!isOptionalParameter) { + if (!(isOptionalParameter2(param) || + iife && parameters.length > iife.arguments.length && !type || + isUntypedSignatureInJSFile)) { minArgumentCount = parameters.length; } } @@ -7094,14 +7130,26 @@ namespace ts { return links.resolvedSignature; } + // TODO: Decide between this and the Original and B-b-b-b-best? + function isOptionalParameter2(param: ParameterDeclaration | JSDocParameterTag) { + if (isParameter(param)) { + return param.initializer || param.questionToken || param.dotDotDotToken || + isJSDocOptionalParameter(param); + } + else { + const { isBracketed, typeExpression } = param; + return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } + } + /** * A JS function gets a synthetic rest parameter if it references `arguments` AND: * 1. It has no parameters but at least one `@param` with a type that starts with `...` * OR * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` */ - function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration, parameters: Symbol[]): boolean { - if (!containsArgumentsReference(declaration)) { + function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { + if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { return false; } const lastParam = lastOrUndefined(declaration.parameters); @@ -7120,9 +7168,9 @@ namespace ts { return true; } - function getSignatureReturnTypeFromDeclaration(declaration: SignatureDeclaration, isJSConstructSignature: boolean, classType: Type) { + function getSignatureReturnTypeFromDeclaration(declaration: SignatureDeclaration | JSDocSignature, isJSConstructSignature: boolean, classType: Type) { if (isJSConstructSignature) { - return getTypeFromTypeNode(declaration.parameters[0].type); + return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type); } else if (classType) { return classType; @@ -7182,7 +7230,7 @@ namespace ts { for (let i = 0; i < symbol.declarations.length; i++) { const decl = symbol.declarations[i]; const node = isPropertyAccessExpression(decl) ? getAssignedJavascriptInitializer(decl) : decl; - if (!isFunctionLike(node)) continue; + if (!isFunctionLike(node) && !(node && isJSDocSignature(node))) continue; // Don't include signature if node is the implementation of an overloaded function. A node is considered // an implementation node if it has a body and the previous node is of the same kind and immediately // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). @@ -9871,8 +9919,8 @@ namespace ts { function compareTypePredicateRelatedTo( source: TypePredicate, target: TypePredicate, - sourceDeclaration: SignatureDeclaration, - targetDeclaration: SignatureDeclaration, + sourceDeclaration: SignatureDeclaration | JSDocSignature, + targetDeclaration: SignatureDeclaration | JSDocSignature, reportErrors: boolean, errorReporter: ErrorReporter, compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index fa1a6ffc1a1b1..0fddc03652733 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6279,8 +6279,9 @@ namespace ts { } const enum PropertyLikeParse { - Property, - Parameter, + Property = 1 << 0, + Parameter = 1 << 1, + CallbackParameter = 1 << 2, } export function parseJSDocCommentWorker(start: number, length: number): JSDoc { @@ -6616,11 +6617,12 @@ namespace ts { typeExpression = tryParseTypeExpression(); } - const result = target === PropertyLikeParse.Parameter ? - createNode(SyntaxKind.JSDocParameterTag, atToken.pos) : - createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + const result = target === PropertyLikeParse.Property ? + createNode(SyntaxKind.JSDocPropertyTag, atToken.pos) : + createNode(SyntaxKind.JSDocParameterTag, atToken.pos); let comment: string | undefined; if (indent !== undefined) comment = parseTagComments(indent + scanner.getStartPos() - atToken.pos); + // TODO: For nested parsing, CallbackParameter should change to Parameter const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name, target); if (nestedTypeLiteral) { typeExpression = nestedTypeLiteral; @@ -6733,10 +6735,14 @@ namespace ts { let child: JSDocParameterTag | false; const start = scanner.getStartPos(); const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; - while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.Parameter) as JSDocParameterTag)) { + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) { // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } + // const shouldParseReturnTag = true; + // if (shouldParseReturnTag) { + // jsdocSignature.type = parseReturnTag(x, y); + // } callbackTag.typeExpression = finishNode(jsdocSignature); return finishNode(callbackTag); } @@ -6845,6 +6851,7 @@ namespace ts { if (canParseTag) { const child = tryParseChildTag(target); if (child && child.kind === SyntaxKind.JSDocParameterTag && + target !== PropertyLikeParse.CallbackParameter && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { return false; } @@ -6893,12 +6900,12 @@ namespace ts { case "arg": case "argument": case "param": - t = PropertyLikeParse.Parameter; + t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; break; default: return false; } - if (target !== t) { + if (!(target & t)) { return false; } const tag = parseParameterOrPropertyTag(atToken, tagName, target, /*indent*/ undefined); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4cccdc1dabebb..0572106f94649 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2053,7 +2053,7 @@ namespace ts { export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; + export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypeLiteral | JSDocSignature; export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; @@ -3984,7 +3984,7 @@ namespace ts { } export interface Signature { - declaration?: SignatureDeclaration; // Originating declaration + declaration?: SignatureDeclaration | JSDocSignature; // Originating declaration typeParameters?: TypeParameter[]; // Type parameters (undefined if non-generic) parameters: Symbol[]; // Parameters /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d65cccbbdb632..cfc689a7ffb1d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -505,6 +505,38 @@ namespace ts { return false; } + export function getTypeParametersOfDeclaration(node: DeclarationWithTypeParameters): NodeArray | undefined { + switch (node.kind) { + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return node.typeParameters; + case SyntaxKind.JSDocSignature: + case SyntaxKind.JSDocTypeLiteral: + // TODO: Actually go find them! + return undefined; + default: + assertTypeIsNever(node); + return undefined; + } + } + export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters; export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters { switch (node.kind) { @@ -527,6 +559,8 @@ namespace ts { case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocSignature: + case SyntaxKind.JSDocTypeLiteral: return true; default: assertTypeIsNever(node); @@ -1776,7 +1810,7 @@ namespace ts { return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag; } - export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration { + export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration { return isJSDocTypeAlias(node) || isTypeAliasDeclaration(node); } @@ -1915,13 +1949,15 @@ namespace ts { return find(typeParameters, p => p.name.escapedText === name); } - export function hasRestParameter(s: SignatureDeclaration): boolean { - const last = lastOrUndefined(s.parameters); + export function hasRestParameter(s: SignatureDeclaration | JSDocSignature): boolean { + // TODO: This type argument wouldn't be needed if I used NodeArray for jsdoc signatures + const last = lastOrUndefined(s.parameters); return last && isRestParameter(last); } - export function isRestParameter(node: ParameterDeclaration): boolean { - return node.dotDotDotToken !== undefined || node.type && node.type.kind === SyntaxKind.JSDocVariadicType; + export function isRestParameter(node: ParameterDeclaration | JSDocParameterTag): boolean { + const type = isJSDocParameterTag(node) ? (node.typeExpression && node.typeExpression.type) : node.type; + return (node as ParameterDeclaration).dotDotDotToken !== undefined || type && type.kind === SyntaxKind.JSDocVariadicType; } export const enum AssignmentKind { @@ -2968,8 +3004,9 @@ namespace ts { return parameter && parameter.type; } - export function getThisParameter(signature: SignatureDeclaration): ParameterDeclaration | undefined { - if (signature.parameters.length) { + export function getThisParameter(signature: SignatureDeclaration | JSDocSignature): ParameterDeclaration | undefined { + // TODO: Maybe jsdoc signatures should support this parameters? (parameterIsThisKeyword is not going to work there, I think, since jsdoc doesn't have keywords) + if (signature.parameters.length && !isJSDocSignature(signature)) { const thisParameter = signature.parameters[0]; if (parameterIsThisKeyword(thisParameter)) { return thisParameter; @@ -3062,8 +3099,8 @@ namespace ts { * 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 | undefined { - return node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined); + export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined { + return !isJSDocSignature(node) && node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined); } /** @@ -3071,6 +3108,8 @@ namespace ts { * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) { + // TODO: Make getJSDocTypeParameterDeclarations do the right thing + if (isJSDocTypeLiteral(node) || isJSDocSignature(node)) return undefined; return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); } @@ -4694,6 +4733,9 @@ namespace ts { /** Gets the JSDoc return tag for the node if present */ export function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined { + if (isJSDocSignature(node)) { + return node.type; + } return getFirstJSDocTag(node, isJSDocReturnTag); } @@ -5612,6 +5654,7 @@ namespace ts { switch (kind) { case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: case SyntaxKind.FunctionType: diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index fd360c73ea1ad..93c1c1681af78 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -125,7 +125,7 @@ namespace ts.codefix { break; case SyntaxKind.TypeParameter: - const typeParameters = (parent.parent).typeParameters; + const typeParameters = getTypeParametersOfDeclaration(parent.parent); if (typeParameters.length === 1) { const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); @@ -273,4 +273,4 @@ namespace ts.codefix { } } } -} \ No newline at end of file +} diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 5805a9861801e..ffd0bdbcdef0d 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1464,8 +1464,8 @@ namespace ts.refactor.extractSymbol { } // Note that we add the current node's type parameters *after* updating the corresponding scope. - if (isDeclarationWithTypeParameters(curr) && curr.typeParameters) { - for (const typeParameterDecl of curr.typeParameters) { + if (isDeclarationWithTypeParameters(curr) && getTypeParametersOfDeclaration(curr)) { + for (const typeParameterDecl of getTypeParametersOfDeclaration(curr)) { const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; if (allTypeParameterUsages.has(typeParameter.id.toString())) { seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter); @@ -1536,8 +1536,8 @@ namespace ts.refactor.extractSymbol { function hasTypeParameters(node: Node) { return isDeclarationWithTypeParameters(node) && - node.typeParameters !== undefined && - node.typeParameters.length > 0; + getTypeParametersOfDeclaration(node) && + getTypeParametersOfDeclaration(node).length > 0; } function isInGenericContext(node: Node) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 70f99eaa8d59c..3b07c3f0211d0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -347,22 +347,24 @@ declare namespace ts { JSDocVariadicType = 282, JSDocComment = 283, JSDocTypeLiteral = 284, - JSDocTag = 285, - JSDocAugmentsTag = 286, - JSDocClassTag = 287, - JSDocParameterTag = 288, - JSDocReturnTag = 289, - JSDocTypeTag = 290, - JSDocTemplateTag = 291, - JSDocTypedefTag = 292, - JSDocPropertyTag = 293, - SyntaxList = 294, - NotEmittedStatement = 295, - PartiallyEmittedExpression = 296, - CommaListExpression = 297, - MergeDeclarationMarker = 298, - EndOfDeclarationMarker = 299, - Count = 300, + JSDocSignature = 285, + JSDocTag = 286, + JSDocAugmentsTag = 287, + JSDocClassTag = 288, + JSDocCallbackTag = 289, + JSDocParameterTag = 290, + JSDocReturnTag = 291, + JSDocTypeTag = 292, + JSDocTemplateTag = 293, + JSDocTypedefTag = 294, + JSDocPropertyTag = 295, + SyntaxList = 296, + NotEmittedStatement = 297, + PartiallyEmittedExpression = 298, + CommaListExpression = 299, + MergeDeclarationMarker = 300, + EndOfDeclarationMarker = 301, + Count = 302, FirstAssignment = 58, LastAssignment = 70, FirstCompoundAssignment = 59, @@ -389,9 +391,9 @@ declare namespace ts { LastBinaryOperator = 70, FirstNode = 145, FirstJSDocNode = 275, - LastJSDocNode = 293, - FirstJSDocTagNode = 285, - LastJSDocTagNode = 293 + LastJSDocNode = 295, + FirstJSDocTagNode = 286, + LastJSDocTagNode = 295 } enum NodeFlags { None = 0, @@ -1277,7 +1279,7 @@ declare namespace ts { block: Block; } type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypeLiteral | JSDocSignature; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; @@ -1534,6 +1536,19 @@ declare namespace ts { name?: Identifier; typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; } + interface JSDocCallbackTag extends JSDocTag, NamedDeclaration { + parent: JSDoc; + kind: SyntaxKind.JSDocCallbackTag; + fullName?: JSDocNamespaceDeclaration | Identifier; + name?: Identifier; + typeExpression: JSDocSignature; + } + interface JSDocSignature extends JSDocType, Declaration { + kind: SyntaxKind.JSDocSignature; + typeParameters?: ReadonlyArray; + parameters: ReadonlyArray; + type: JSDocReturnTag | undefined; + } interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; name: EntityName; @@ -2251,7 +2266,7 @@ declare namespace ts { Construct = 1 } interface Signature { - declaration?: SignatureDeclaration; + declaration?: SignatureDeclaration | JSDocSignature; typeParameters?: TypeParameter[]; parameters: Symbol[]; } @@ -3298,6 +3313,8 @@ declare namespace ts { function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag; function isJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag; function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral; + function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag; + function isJSDocSignature(node: Node): node is JSDocSignature; } declare namespace ts { /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d54eff0acbdc9..f38008ea77afb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -347,22 +347,24 @@ declare namespace ts { JSDocVariadicType = 282, JSDocComment = 283, JSDocTypeLiteral = 284, - JSDocTag = 285, - JSDocAugmentsTag = 286, - JSDocClassTag = 287, - JSDocParameterTag = 288, - JSDocReturnTag = 289, - JSDocTypeTag = 290, - JSDocTemplateTag = 291, - JSDocTypedefTag = 292, - JSDocPropertyTag = 293, - SyntaxList = 294, - NotEmittedStatement = 295, - PartiallyEmittedExpression = 296, - CommaListExpression = 297, - MergeDeclarationMarker = 298, - EndOfDeclarationMarker = 299, - Count = 300, + JSDocSignature = 285, + JSDocTag = 286, + JSDocAugmentsTag = 287, + JSDocClassTag = 288, + JSDocCallbackTag = 289, + JSDocParameterTag = 290, + JSDocReturnTag = 291, + JSDocTypeTag = 292, + JSDocTemplateTag = 293, + JSDocTypedefTag = 294, + JSDocPropertyTag = 295, + SyntaxList = 296, + NotEmittedStatement = 297, + PartiallyEmittedExpression = 298, + CommaListExpression = 299, + MergeDeclarationMarker = 300, + EndOfDeclarationMarker = 301, + Count = 302, FirstAssignment = 58, LastAssignment = 70, FirstCompoundAssignment = 59, @@ -389,9 +391,9 @@ declare namespace ts { LastBinaryOperator = 70, FirstNode = 145, FirstJSDocNode = 275, - LastJSDocNode = 293, - FirstJSDocTagNode = 285, - LastJSDocTagNode = 293 + LastJSDocNode = 295, + FirstJSDocTagNode = 286, + LastJSDocTagNode = 295 } enum NodeFlags { None = 0, @@ -1277,7 +1279,7 @@ declare namespace ts { block: Block; } type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypeLiteral | JSDocSignature; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; @@ -1534,6 +1536,19 @@ declare namespace ts { name?: Identifier; typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; } + interface JSDocCallbackTag extends JSDocTag, NamedDeclaration { + parent: JSDoc; + kind: SyntaxKind.JSDocCallbackTag; + fullName?: JSDocNamespaceDeclaration | Identifier; + name?: Identifier; + typeExpression: JSDocSignature; + } + interface JSDocSignature extends JSDocType, Declaration { + kind: SyntaxKind.JSDocSignature; + typeParameters?: ReadonlyArray; + parameters: ReadonlyArray; + type: JSDocReturnTag | undefined; + } interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; name: EntityName; @@ -2251,7 +2266,7 @@ declare namespace ts { Construct = 1 } interface Signature { - declaration?: SignatureDeclaration; + declaration?: SignatureDeclaration | JSDocSignature; typeParameters?: TypeParameter[]; parameters: Symbol[]; } @@ -3298,6 +3313,8 @@ declare namespace ts { function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag; function isJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag; function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral; + function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag; + function isJSDocSignature(node: Node): node is JSDocSignature; } declare namespace ts { /** diff --git a/tests/cases/conformance/jsdoc/callbackTag1.ts b/tests/cases/conformance/jsdoc/callbackTag1.ts index a0298954c6efd..10a20e32d4baf 100644 --- a/tests/cases/conformance/jsdoc/callbackTag1.ts +++ b/tests/cases/conformance/jsdoc/callbackTag1.ts @@ -3,6 +3,7 @@ // @checkJs: true // @Filename: cb.js +// TODO: Test nested param types using the nested Object style /** @callback Sid * @param {string} s * @returns {string} What were you expecting diff --git a/tests/cases/fourslash/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts index d0d944cc2778d..a80aa964ce7f3 100644 --- a/tests/cases/fourslash/server/jsdocCallbackTag.ts +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -26,6 +26,6 @@ goTo.marker("1"); -verify.quickInfoIs("var t: (eventName: string, eventName2: number | string, eventName3: any) => void"); +verify.quickInfoIs("var t: (eventName: string, eventName2: string | number, eventName3: any) => void"); goTo.marker("2"); verify.quickInfoIs("var t2: (eventName?: string, eventName2?: string) => void"); From 0003c48321f025f56ac0616f9b89be5e601aedbc Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 14:41:10 -0700 Subject: [PATCH 17/49] Callback return types work now --- src/compiler/parser.ts | 18 ++++++++++++++---- .../baselines/reference/callbackTag1.symbols | 15 +++++++++++++++ tests/baselines/reference/callbackTag1.types | 19 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/callbackTag1.symbols create mode 100644 tests/baselines/reference/callbackTag1.types diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0fddc03652733..d17e24707ac3b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6739,10 +6739,20 @@ namespace ts { // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } - // const shouldParseReturnTag = true; - // if (shouldParseReturnTag) { - // jsdocSignature.type = parseReturnTag(x, y); - // } + const returnTag = tryParse(() => { + if (parseExpectedToken(SyntaxKind.AtToken)) { + const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); + atToken.end = scanner.getTextPos(); + nextToken(); // why is this needed? Doesn't parseExpectedToken advance the token enough? + const name = parseJSDocIdentifierName(); + if (name && (name.escapedText === "return" || name.escapedText === "returns")) { + return parseReturnTag(atToken, name); + } + } + }); + if (returnTag) { + jsdocSignature.type = returnTag; + } callbackTag.typeExpression = finishNode(jsdocSignature); return finishNode(callbackTag); } diff --git a/tests/baselines/reference/callbackTag1.symbols b/tests/baselines/reference/callbackTag1.symbols new file mode 100644 index 0000000000000..5bcc7fc540363 --- /dev/null +++ b/tests/baselines/reference/callbackTag1.symbols @@ -0,0 +1,15 @@ +=== tests/cases/conformance/jsdoc/cb.js === +// TODO: Test nested param types using the nested Object style +/** @callback Sid + * @param {string} s + * @returns {string} What were you expecting + */ +var x = 1 +>x : Symbol(x, Decl(cb.js, 5, 3)) + +/** @type {Sid} smallId */ +var sid = s => s + "!"; +>sid : Symbol(sid, Decl(cb.js, 8, 3)) +>s : Symbol(s, Decl(cb.js, 8, 9)) +>s : Symbol(s, Decl(cb.js, 8, 9)) + diff --git a/tests/baselines/reference/callbackTag1.types b/tests/baselines/reference/callbackTag1.types new file mode 100644 index 0000000000000..47b3394a76dd6 --- /dev/null +++ b/tests/baselines/reference/callbackTag1.types @@ -0,0 +1,19 @@ +=== tests/cases/conformance/jsdoc/cb.js === +// TODO: Test nested param types using the nested Object style +/** @callback Sid + * @param {string} s + * @returns {string} What were you expecting + */ +var x = 1 +>x : number +>1 : 1 + +/** @type {Sid} smallId */ +var sid = s => s + "!"; +>sid : (s: string) => string +>s => s + "!" : (s: string) => string +>s : string +>s + "!" : string +>s : string +>"!" : "!" + From f4ac992e5fcd8e2792c779039f5f9be66c849d83 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 15:11:37 -0700 Subject: [PATCH 18/49] Fix crash in services --- src/compiler/utilities.ts | 2 +- tests/cases/fourslash/server/jsdocCallbackTag.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index cfc689a7ffb1d..f4c0f6e17cafc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6272,7 +6272,7 @@ namespace ts { /** True if node is of a kind that may contain comment text. */ export function isJSDocCommentContainingNode(node: Node): boolean { - return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node) || isJSDocTypeLiteral(node); + return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node) || isJSDocTypeLiteral(node) || isJSDocSignature(node); } // TODO: determine what this does before making it public. diff --git a/tests/cases/fourslash/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts index a80aa964ce7f3..5663224dc6f38 100644 --- a/tests/cases/fourslash/server/jsdocCallbackTag.ts +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -8,6 +8,7 @@ //// * @param {string} eventName - So many words //// * @param eventName2 {number | string} - Silence is golden //// * @param eventName3 - Osterreich mos def +//// * @return {number} - DIVEKICK //// */ //// /** //// * @type {FooHandler} callback @@ -20,12 +21,14 @@ //// * @param {string} [eventName2] - i WARNED you dog //// */ //// /** -//// * @type {FooHandler2} callback +//// * @type {FooH/*3*/andler2} callback //// */ //// var t2/*2*/; goTo.marker("1"); -verify.quickInfoIs("var t: (eventName: string, eventName2: string | number, eventName3: any) => void"); +verify.quickInfoIs("var t: (eventName: string, eventName2: string | number, eventName3: any) => number"); goTo.marker("2"); -verify.quickInfoIs("var t2: (eventName?: string, eventName2?: string) => void"); +verify.quickInfoIs("var t2: (eventName?: string, eventName2?: string) => any"); +goTo.marker("3"); +verify.quickInfoIs("type FooHandler2 = (eventName?: string, eventName2?: string) => any"); From ad7fb6482829792ead56397a337c3221079e1538 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 15:24:12 -0700 Subject: [PATCH 19/49] Make github diff smaller --- src/compiler/parser.ts | 92 +++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d17e24707ac3b..63da0e8a275bc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6724,51 +6724,6 @@ namespace ts { return finishNode(tag); } - function parseCallbackTag(atToken: AtToken, tagName: Identifier): JSDocCallbackTag { - const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag; - callbackTag.atToken = atToken; - callbackTag.tagName = tagName; - callbackTag.fullName = parseJSDocTypeNameWithNamespace(); - callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName); - skipWhitespace(); - - let child: JSDocParameterTag | false; - const start = scanner.getStartPos(); - const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; - while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) { - // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); - jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); - } - const returnTag = tryParse(() => { - if (parseExpectedToken(SyntaxKind.AtToken)) { - const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); - atToken.end = scanner.getTextPos(); - nextToken(); // why is this needed? Doesn't parseExpectedToken advance the token enough? - const name = parseJSDocIdentifierName(); - if (name && (name.escapedText === "return" || name.escapedText === "returns")) { - return parseReturnTag(atToken, name); - } - } - }); - if (returnTag) { - jsdocSignature.type = returnTag; - } - callbackTag.typeExpression = finishNode(jsdocSignature); - return finishNode(callbackTag); - } - - function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { - if (fullName) { - let rightNode = fullName; - while (true) { - if (ts.isIdentifier(rightNode) || !rightNode.body) { - return ts.isIdentifier(rightNode) ? rightNode : rightNode.name; - } - rightNode = rightNode.body; - } - } - } - function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag { const typeExpression = tryParseTypeExpression(); skipWhitespace(); @@ -6815,7 +6770,52 @@ namespace ts { return finishNode(typedefTag); } - function parseJSDocTypeNameWithNamespace(nested?: boolean) { + function parseCallbackTag(atToken: AtToken, tagName: Identifier): JSDocCallbackTag { + const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag; + callbackTag.atToken = atToken; + callbackTag.tagName = tagName; + callbackTag.fullName = parseJSDocTypeNameWithNamespace(); + callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName); + skipWhitespace(); + + let child: JSDocParameterTag | false; + const start = scanner.getStartPos(); + const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) { + // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); + jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); + } + const returnTag = tryParse(() => { + if (parseExpectedToken(SyntaxKind.AtToken)) { + const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); + atToken.end = scanner.getTextPos(); + nextToken(); // why is this needed? Doesn't parseExpectedToken advance the token enough? + const name = parseJSDocIdentifierName(); + if (name && (name.escapedText === "return" || name.escapedText === "returns")) { + return parseReturnTag(atToken, name); + } + } + }); + if (returnTag) { + jsdocSignature.type = returnTag; + } + callbackTag.typeExpression = finishNode(jsdocSignature); + return finishNode(callbackTag); + } + + function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { + if (fullName) { + let rightNode = fullName; + while (true) { + if (ts.isIdentifier(rightNode) || !rightNode.body) { + return ts.isIdentifier(rightNode) ? rightNode : rightNode.name; + } + rightNode = rightNode.body; + } + } + } + + function parseJSDocTypeNameWithNamespace(nested?: boolean) { const pos = scanner.getTokenPos(); const typeNameOrNamespaceName = parseJSDocIdentifierName(); From 2410beeedad81bb6d1ffac0014eb64079ac92204 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 15:29:08 -0700 Subject: [PATCH 20/49] Try to make github diff even smaller --- src/compiler/parser.ts | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 63da0e8a275bc..ffa5cc74b28a4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6770,6 +6770,26 @@ namespace ts { return finishNode(typedefTag); } + function parseJSDocTypeNameWithNamespace(nested?: boolean) { + const pos = scanner.getTokenPos(); + const typeNameOrNamespaceName = parseJSDocIdentifierName(); + + if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) { + const jsDocNamespaceNode = createNode(SyntaxKind.ModuleDeclaration, pos); + if (nested) { + jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace; + } + jsDocNamespaceNode.name = typeNameOrNamespaceName; + jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true); + return finishNode(jsDocNamespaceNode); + } + + if (typeNameOrNamespaceName && nested) { + typeNameOrNamespaceName.isInJSDocNamespace = true; + } + return typeNameOrNamespaceName; + } + function parseCallbackTag(atToken: AtToken, tagName: Identifier): JSDocCallbackTag { const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag; callbackTag.atToken = atToken; @@ -6815,26 +6835,6 @@ namespace ts { } } - function parseJSDocTypeNameWithNamespace(nested?: boolean) { - const pos = scanner.getTokenPos(); - const typeNameOrNamespaceName = parseJSDocIdentifierName(); - - if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) { - const jsDocNamespaceNode = createNode(SyntaxKind.ModuleDeclaration, pos); - if (nested) { - jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace; - } - jsDocNamespaceNode.name = typeNameOrNamespaceName; - jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true); - return finishNode(jsDocNamespaceNode); - } - - if (typeNameOrNamespaceName && nested) { - typeNameOrNamespaceName.isInJSDocNamespace = true; - } - return typeNameOrNamespaceName; - } - function escapedTextsEqual(a: EntityName, b: EntityName): boolean { while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { From 0ae190b71ded063f956708611e19b1af469a57ed Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 2 May 2018 16:16:12 -0700 Subject: [PATCH 21/49] Fix rename for callback tag --- src/compiler/utilities.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f4c0f6e17cafc..98b33743328b0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6122,7 +6122,8 @@ namespace ts { || kind === SyntaxKind.TypeAliasDeclaration || kind === SyntaxKind.TypeParameter || kind === SyntaxKind.VariableDeclaration - || kind === SyntaxKind.JSDocTypedefTag; + || kind === SyntaxKind.JSDocTypedefTag + || kind === SyntaxKind.JSDocCallbackTag; } function isDeclarationStatementKind(kind: SyntaxKind) { From 3ca5bf0076aa83744e9725cb91b0866630f7ffe3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 09:06:14 -0700 Subject: [PATCH 22/49] Fix nav bar for callback tag Also clean up some now-redundant code there to find the name of typedefs. --- src/compiler/utilities.ts | 2 ++ src/services/navigationBar.ts | 25 ++----------------- src/services/utilities.ts | 7 +++--- .../server/jsdocCallbackTagNavigateTo.ts | 4 +-- 4 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 98b33743328b0..511c777085792 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4652,6 +4652,8 @@ namespace ts { return undefined; } } + case SyntaxKind.JSDocCallbackTag: + return (declaration as JSDocCallbackTag).name; case SyntaxKind.JSDocTypedefTag: return getNameOfJSDocTypedef(declaration as JSDocTypedefTag); case SyntaxKind.ExportAssignment: { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index b029796e718ef..3ce18b9100256 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -290,7 +290,7 @@ namespace ts.NavigationBar { if (hasJSDocNodes(node)) { forEach(node.jsDoc, jsDoc => { forEach(jsDoc.tags, tag => { - if (tag.kind === SyntaxKind.JSDocTypedefTag) { + if (isJSDocTypeAlias(tag)) { addLeafNode(tag); } }); @@ -414,8 +414,6 @@ namespace ts.NavigationBar { case SyntaxKind.ArrowFunction: case SyntaxKind.ClassExpression: return getFunctionOrClassName(node); - case SyntaxKind.JSDocTypedefTag: - return getJSDocTypedefTagName(node); default: return undefined; } @@ -460,31 +458,11 @@ namespace ts.NavigationBar { return "()"; case SyntaxKind.IndexSignature: return "[]"; - case SyntaxKind.JSDocTypedefTag: - return getJSDocTypedefTagName(node); default: return ""; } } - function getJSDocTypedefTagName(node: JSDocTypedefTag): string { - if (node.name) { - return node.name.text; - } - else { - const parentNode = node.parent && node.parent.parent; - if (parentNode && parentNode.kind === SyntaxKind.VariableStatement) { - if (parentNode.declarationList.declarations.length > 0) { - const nameIdentifier = parentNode.declarationList.declarations[0].name; - if (nameIdentifier.kind === SyntaxKind.Identifier) { - return nameIdentifier.text; - } - } - } - return ""; - } - } - /** Flattens the NavNode tree to a list, keeping only the top-level items. */ function topLevelItems(root: NavigationBarNode): NavigationBarNode[] { const topLevel: NavigationBarNode[] = []; @@ -511,6 +489,7 @@ namespace ts.NavigationBar { case SyntaxKind.SourceFile: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: return true; case SyntaxKind.Constructor: diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 78f18869b372c..285d055f1665f 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -309,7 +309,10 @@ namespace ts { case SyntaxKind.ClassExpression: return ScriptElementKind.classElement; case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; - case SyntaxKind.TypeAliasDeclaration: return ScriptElementKind.typeElement; + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + return ScriptElementKind.typeElement; case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; case SyntaxKind.VariableDeclaration: return getKindOfVariableDeclaration(node); @@ -340,8 +343,6 @@ namespace ts { case SyntaxKind.ExportSpecifier: case SyntaxKind.NamespaceImport: return ScriptElementKind.alias; - case SyntaxKind.JSDocTypedefTag: - return ScriptElementKind.typeElement; case SyntaxKind.BinaryExpression: const kind = getSpecialPropertyAssignmentKind(node as BinaryExpression); const { right } = node as BinaryExpression; diff --git a/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts index e17192e65b7e6..2beaf99ed7073 100644 --- a/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts +++ b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts @@ -4,7 +4,7 @@ // @Filename: jsDocCallback.js //// /** -//// * @callback FooCallback +//// * @callback FooCallback //// * @param {string} eventName - What even is the navigation bar? //// */ //// /** @type {FooCallback} */ @@ -13,7 +13,7 @@ verify.navigationBar([ { "text": "", - "kind": "module", + "kind": "script", "childItems": [ { "text": "FooCallback", From 819f19dc59de6d74b525e2027794a699f8433745 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 09:52:10 -0700 Subject: [PATCH 23/49] Handle ooorder callback tags Also get rid of redundant typedef name code *in the binder*. It's everywhere! --- src/compiler/binder.ts | 13 ++++++------- tests/cases/conformance/jsdoc/callbackTag1.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c892b8dfcfd33..54b5cd0a0e51d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -298,9 +298,6 @@ namespace ts { const functionType = node.parent; const index = functionType.parameters.indexOf(node as ParameterDeclaration); return "arg" + index as __String; - case SyntaxKind.JSDocTypedefTag: - const name = getNameOfJSDocTypedef(node as JSDocTypedefTag); - return typeof name !== "undefined" ? name.escapedText : undefined; } } @@ -453,6 +450,7 @@ namespace ts { // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation // and this case is specially handled. Module augmentations should only be merged with original module definition // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. + // TODO: Handle callbacks here too if (node.kind === SyntaxKind.JSDocTypedefTag) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefTag(node)) { if (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node)) { @@ -710,7 +708,8 @@ namespace ts { bindJSDocComment(node); break; case SyntaxKind.JSDocTypedefTag: - bindJSDocTypedefTag(node); + case SyntaxKind.JSDocCallbackTag: + bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag); break; // In source files and blocks, bind functions first to match hoisting that occurs at runtime case SyntaxKind.SourceFile: @@ -1378,13 +1377,13 @@ namespace ts { function bindJSDocComment(node: JSDoc) { forEachChild(node, n => { - if (n.kind !== SyntaxKind.JSDocTypedefTag) { + if (!isJSDocTypeAlias(n)) { bind(n); } }); } - function bindJSDocTypedefTag(node: JSDocTypedefTag) { + function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) { forEachChild(node, n => { // if the node has a fullName "A.B.C", that means symbol "C" was already bound // when we visit "fullName"; so when we visit the name "C" as the next child of @@ -1992,7 +1991,7 @@ namespace ts { } for (const tag of jsDoc.tags) { - if (tag.kind === SyntaxKind.JSDocTypedefTag) { + if (isJSDocTypeAlias(tag)) { const savedParent = parent; parent = jsDoc; bind(tag); diff --git a/tests/cases/conformance/jsdoc/callbackTag1.ts b/tests/cases/conformance/jsdoc/callbackTag1.ts index 10a20e32d4baf..510a03850104f 100644 --- a/tests/cases/conformance/jsdoc/callbackTag1.ts +++ b/tests/cases/conformance/jsdoc/callbackTag1.ts @@ -12,3 +12,12 @@ var x = 1 /** @type {Sid} smallId */ var sid = s => s + "!"; + + +/** @type {NoReturn} */ +var noreturn = obj => void obj.title + +/** + * @callback NoReturn + * @param {{ e: number, m: number, title: string }} s - Knee deep, shores, etc + */ From c2d7db7df6e98e2cee4d5ad51635f5b3ff0f42b4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 09:55:07 -0700 Subject: [PATCH 24/49] Add ooorder callback tag test --- tests/baselines/reference/callbackTag1.symbols | 14 ++++++++++++++ tests/baselines/reference/callbackTag1.types | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/baselines/reference/callbackTag1.symbols b/tests/baselines/reference/callbackTag1.symbols index 5bcc7fc540363..e95c48735db83 100644 --- a/tests/baselines/reference/callbackTag1.symbols +++ b/tests/baselines/reference/callbackTag1.symbols @@ -13,3 +13,17 @@ var sid = s => s + "!"; >s : Symbol(s, Decl(cb.js, 8, 9)) >s : Symbol(s, Decl(cb.js, 8, 9)) + +/** @type {NoReturn} */ +var noreturn = obj => void obj.title +>noreturn : Symbol(noreturn, Decl(cb.js, 12, 3)) +>obj : Symbol(obj, Decl(cb.js, 12, 14)) +>obj.title : Symbol(title, Decl(cb.js, 16, 34)) +>obj : Symbol(obj, Decl(cb.js, 12, 14)) +>title : Symbol(title, Decl(cb.js, 16, 34)) + +/** + * @callback NoReturn + * @param {{ e: number, m: number, title: string }} s - Knee deep, shores, etc + */ + diff --git a/tests/baselines/reference/callbackTag1.types b/tests/baselines/reference/callbackTag1.types index 47b3394a76dd6..a1c99e2e7f25f 100644 --- a/tests/baselines/reference/callbackTag1.types +++ b/tests/baselines/reference/callbackTag1.types @@ -17,3 +17,19 @@ var sid = s => s + "!"; >s : string >"!" : "!" + +/** @type {NoReturn} */ +var noreturn = obj => void obj.title +>noreturn : (s: { e: number; m: number; title: string; }) => any +>obj => void obj.title : (obj: { e: number; m: number; title: string; }) => any +>obj : { e: number; m: number; title: string; } +>void obj.title : undefined +>obj.title : string +>obj : { e: number; m: number; title: string; } +>title : string + +/** + * @callback NoReturn + * @param {{ e: number, m: number, title: string }} s - Knee deep, shores, etc + */ + From 7c6d66ed5d967b800965712daedd628a58ca2d5a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 14:14:52 -0700 Subject: [PATCH 25/49] Parse comments for typedef/callback+display param comments --- src/compiler/parser.ts | 19 +++++++++++++------ src/services/jsDoc.ts | 6 +++++- src/services/utilities.ts | 1 + ...sCorrectly.typedefTagWithChildrenTags.json | 2 +- .../fourslash/server/jsdocCallbackTag.ts | 18 +++++++++++++----- .../cases/fourslash/server/jsdocTypedefTag.ts | 9 ++++++--- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ffa5cc74b28a4..3132d5aa50af5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6465,10 +6465,10 @@ namespace ts { tag = parseTypeTag(atToken, tagName); break; case "typedef": - tag = parseTypedefTag(atToken, tagName); + tag = parseTypedefTag(atToken, tagName, indent); break; case "callback": - tag = parseCallbackTag(atToken, tagName); + tag = parseCallbackTag(atToken, tagName, indent); break; default: tag = parseUnknownTag(atToken, tagName); @@ -6483,7 +6483,10 @@ namespace ts { // a badly malformed tag should not be added to the list of tags return; } - tag.comment = parseTagComments(indent + tag.end - tag.pos); + if (!tag.comment) { + // some tags, like typedef and callback, have already parsed their comments earlier + tag.comment = parseTagComments(indent + tag.end - tag.pos); + } addTag(tag); } @@ -6724,7 +6727,7 @@ namespace ts { return finishNode(tag); } - function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag { + function parseTypedefTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocTypedefTag { const typeExpression = tryParseTypeExpression(); skipWhitespace(); @@ -6735,6 +6738,7 @@ namespace ts { typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); skipWhitespace(); + typedefTag.comment = parseTagComments(indent); typedefTag.typeExpression = typeExpression; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { let child: JSDocTypeTag | JSDocPropertyTag | false; @@ -6790,13 +6794,14 @@ namespace ts { return typeNameOrNamespaceName; } - function parseCallbackTag(atToken: AtToken, tagName: Identifier): JSDocCallbackTag { + function parseCallbackTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocCallbackTag { const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag; callbackTag.atToken = atToken; callbackTag.tagName = tagName; callbackTag.fullName = parseJSDocTypeNameWithNamespace(); callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName); skipWhitespace(); + callbackTag.comment = parseTagComments(indent); let child: JSDocParameterTag | false; const start = scanner.getStartPos(); @@ -6812,7 +6817,9 @@ namespace ts { nextToken(); // why is this needed? Doesn't parseExpectedToken advance the token enough? const name = parseJSDocIdentifierName(); if (name && (name.escapedText === "return" || name.escapedText === "returns")) { - return parseReturnTag(atToken, name); + const returnTag = parseReturnTag(atToken, name); + returnTag.comment = parseTagComments(indent); + return returnTag; } } }); diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index ca0dafbc74cb4..2a8cc2245ac0f 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -5,6 +5,7 @@ namespace ts.JsDoc { "author", "argument", "borrows", + "callback", "class", "constant", "constructor", @@ -68,10 +69,12 @@ namespace ts.JsDoc { function getCommentHavingNodes(declaration: Declaration): ReadonlyArray { switch (declaration.kind) { + case SyntaxKind.JSDocParameterTag: case SyntaxKind.JSDocPropertyTag: return [declaration as JSDocPropertyTag]; + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocTypedefTag: - return [(declaration as JSDocTypedefTag).parent]; + return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent]; default: return getJSDocCommentsAndTags(declaration); } @@ -97,6 +100,7 @@ namespace ts.JsDoc { return withList((tag as JSDocTemplateTag).typeParameters); case SyntaxKind.JSDocTypeTag: return withNode((tag as JSDocTypeTag).typeExpression); + // TODO: Handle callback here case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocPropertyTag: case SyntaxKind.JSDocParameterTag: diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 285d055f1665f..d257e86f602aa 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -270,6 +270,7 @@ namespace ts { } export function getContainerNode(node: Node): Declaration { + // TODO: Probably need to handle jsdoccallbacktag too if (node.kind === SyntaxKind.JSDocTypedefTag) { // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json index a4a69fc48a6c4..98a59931ad8a5 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json @@ -32,7 +32,7 @@ }, "typeExpression": { "kind": "JSDocTypeLiteral", - "pos": 26, + "pos": 28, "end": 98, "jsDocPropertyTags": [ { diff --git a/tests/cases/fourslash/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts index 5663224dc6f38..b9a2fcdf3156d 100644 --- a/tests/cases/fourslash/server/jsdocCallbackTag.ts +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -2,16 +2,15 @@ // @allowNonTsExtensions: true // @Filename: jsdocCallbackTag.js - //// /** -//// * @callback FooHandler +//// * @callback FooHandler - A kind of magic //// * @param {string} eventName - So many words //// * @param eventName2 {number | string} - Silence is golden //// * @param eventName3 - Osterreich mos def //// * @return {number} - DIVEKICK //// */ //// /** -//// * @type {FooHandler} callback +//// * @type {FooHa/*8*/ndler} callback //// */ //// var t/*1*/; //// @@ -24,11 +23,20 @@ //// * @type {FooH/*3*/andler2} callback //// */ //// var t2/*2*/; - +//// t(/*4*/"!", /*5*/12, /*6*/false); goTo.marker("1"); verify.quickInfoIs("var t: (eventName: string, eventName2: string | number, eventName3: any) => number"); goTo.marker("2"); verify.quickInfoIs("var t2: (eventName?: string, eventName2?: string) => any"); goTo.marker("3"); -verify.quickInfoIs("type FooHandler2 = (eventName?: string, eventName2?: string) => any"); +verify.quickInfoIs("type FooHandler2 = (eventName?: string, eventName2?: string) => any", "- What, another one?"); +goTo.marker("8"); +verify.quickInfoIs("type FooHandler = (eventName: string, eventName2: string | number, eventName3: any) => number", "- A kind of magic"); +goTo.marker('4'); +verify.currentSignatureHelpIs("t(eventName: string, eventName2: string | number, eventName3: any): number"); +verify.currentParameterHelpArgumentDocCommentIs("- So many words"); +goTo.marker('5'); +verify.currentParameterHelpArgumentDocCommentIs("- Silence is golden"); +goTo.marker('6'); +verify.currentParameterHelpArgumentDocCommentIs("- Osterreich mos def"); diff --git a/tests/cases/fourslash/server/jsdocTypedefTag.ts b/tests/cases/fourslash/server/jsdocTypedefTag.ts index 9e9801148353a..1ab7060456123 100644 --- a/tests/cases/fourslash/server/jsdocTypedefTag.ts +++ b/tests/cases/fourslash/server/jsdocTypedefTag.ts @@ -6,7 +6,7 @@ //// /** @typedef {(string | number)} NumberLike */ //// //// /** -//// * @typedef Animal +//// * @typedef Animal - think Giraffes //// * @type {Object} //// * @property {string} animalName //// * @property {number} animalAge @@ -36,7 +36,7 @@ //// p.personName./*personName*/; //// p.personAge./*personAge*/; //// -//// /** @type {Animal} */ +//// /** @type {/*AnimalType*/Animal} */ //// var a;a./*animal*/; //// a.animalName./*animalName*/; //// a.animalAge./*animalAge*/; @@ -85,4 +85,7 @@ verify.completionListContains('catAge'); goTo.marker('catName'); verify.completionListContains('charAt'); goTo.marker('catAge'); -verify.completionListContains('toExponential'); \ No newline at end of file +verify.completionListContains('toExponential'); + +goTo.marker("AnimalType"); +verify.quickInfoIs("type Animal = {\n animalName: string;\n animalAge: number;\n}", "- think Giraffes"); From 5e02518aca15d2035f0552b066b9cabf9415321e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 14:31:15 -0700 Subject: [PATCH 26/49] Always export callbacks This requires almost no new code since it is basically the same as typedefs --- src/compiler/binder.ts | 5 ++--- .../conformance/jsdoc/callbackCrossModule.ts | 22 +++++++++++++++++++ tests/cases/conformance/jsdoc/callbackTag1.ts | 1 - 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 tests/cases/conformance/jsdoc/callbackCrossModule.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 54b5cd0a0e51d..4c7a391230267 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -450,9 +450,8 @@ namespace ts { // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation // and this case is specially handled. Module augmentations should only be merged with original module definition // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. - // TODO: Handle callbacks here too - if (node.kind === SyntaxKind.JSDocTypedefTag) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. - if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefTag(node)) { + if (isJSDocTypeAlias(node)) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. + if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypeAlias(node)) { if (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node)) { return declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! } diff --git a/tests/cases/conformance/jsdoc/callbackCrossModule.ts b/tests/cases/conformance/jsdoc/callbackCrossModule.ts new file mode 100644 index 0000000000000..a2888f3627fee --- /dev/null +++ b/tests/cases/conformance/jsdoc/callbackCrossModule.ts @@ -0,0 +1,22 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: mod1.js +/** @callback Con - some kind of continuation + * @param {object | undefined} error + * @return {any} I don't even know what this should return + */ +module.exports = C +function C() { + this.p = 1 +} + +// @Filename: use.js +/** @param {import('./mod1').Con} k */ +function f(k) { + if (1 === 2 - 1) { + // I guess basic math works! + } + return k({ ok: true}) +} + diff --git a/tests/cases/conformance/jsdoc/callbackTag1.ts b/tests/cases/conformance/jsdoc/callbackTag1.ts index 510a03850104f..63a90e74ff88d 100644 --- a/tests/cases/conformance/jsdoc/callbackTag1.ts +++ b/tests/cases/conformance/jsdoc/callbackTag1.ts @@ -3,7 +3,6 @@ // @checkJs: true // @Filename: cb.js -// TODO: Test nested param types using the nested Object style /** @callback Sid * @param {string} s * @returns {string} What were you expecting From 12bd83dc924658e1ed1c3f987d0231d6194d4c51 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 14:36:00 -0700 Subject: [PATCH 27/49] Update baselines --- .../reference/callbackCrossModule.symbols | 32 +++++++++++++ .../reference/callbackCrossModule.types | 47 +++++++++++++++++++ .../baselines/reference/callbackTag1.symbols | 19 ++++---- tests/baselines/reference/callbackTag1.types | 1 - 4 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/callbackCrossModule.symbols create mode 100644 tests/baselines/reference/callbackCrossModule.types diff --git a/tests/baselines/reference/callbackCrossModule.symbols b/tests/baselines/reference/callbackCrossModule.symbols new file mode 100644 index 0000000000000..be8175c4a1157 --- /dev/null +++ b/tests/baselines/reference/callbackCrossModule.symbols @@ -0,0 +1,32 @@ +=== tests/cases/conformance/jsdoc/mod1.js === +/** @callback Con - some kind of continuation + * @param {object | undefined} error + * @return {any} I don't even know what this should return + */ +module.exports = C +>module : Symbol(export=, Decl(mod1.js, 0, 0)) +>exports : Symbol(export=, Decl(mod1.js, 0, 0)) +>C : Symbol(C, Decl(mod1.js, 4, 18)) + +function C() { +>C : Symbol(C, Decl(mod1.js, 4, 18)) + + this.p = 1 +>p : Symbol(C.p, Decl(mod1.js, 5, 14)) +} + +=== tests/cases/conformance/jsdoc/use.js === +/** @param {import('./mod1').Con} k */ +function f(k) { +>f : Symbol(f, Decl(use.js, 0, 0)) +>k : Symbol(k, Decl(use.js, 1, 11)) + + if (1 === 2 - 1) { + // I guess basic math works! + } + return k({ ok: true}) +>k : Symbol(k, Decl(use.js, 1, 11)) +>ok : Symbol(ok, Decl(use.js, 5, 14)) +} + + diff --git a/tests/baselines/reference/callbackCrossModule.types b/tests/baselines/reference/callbackCrossModule.types new file mode 100644 index 0000000000000..d1d970c555e7d --- /dev/null +++ b/tests/baselines/reference/callbackCrossModule.types @@ -0,0 +1,47 @@ +=== tests/cases/conformance/jsdoc/mod1.js === +/** @callback Con - some kind of continuation + * @param {object | undefined} error + * @return {any} I don't even know what this should return + */ +module.exports = C +>module.exports = C : typeof C +>module.exports : any +>module : any +>exports : any +>C : typeof C + +function C() { +>C : typeof C + + this.p = 1 +>this.p = 1 : 1 +>this.p : any +>this : any +>p : any +>1 : 1 +} + +=== tests/cases/conformance/jsdoc/use.js === +/** @param {import('./mod1').Con} k */ +function f(k) { +>f : (k: (error: any) => any) => any +>k : (error: any) => any + + if (1 === 2 - 1) { +>1 === 2 - 1 : boolean +>1 : 1 +>2 - 1 : number +>2 : 2 +>1 : 1 + + // I guess basic math works! + } + return k({ ok: true}) +>k({ ok: true}) : any +>k : (error: any) => any +>{ ok: true} : { ok: boolean; } +>ok : boolean +>true : true +} + + diff --git a/tests/baselines/reference/callbackTag1.symbols b/tests/baselines/reference/callbackTag1.symbols index e95c48735db83..b52ac8f92c8ab 100644 --- a/tests/baselines/reference/callbackTag1.symbols +++ b/tests/baselines/reference/callbackTag1.symbols @@ -1,26 +1,25 @@ === tests/cases/conformance/jsdoc/cb.js === -// TODO: Test nested param types using the nested Object style /** @callback Sid * @param {string} s * @returns {string} What were you expecting */ var x = 1 ->x : Symbol(x, Decl(cb.js, 5, 3)) +>x : Symbol(x, Decl(cb.js, 4, 3)) /** @type {Sid} smallId */ var sid = s => s + "!"; ->sid : Symbol(sid, Decl(cb.js, 8, 3)) ->s : Symbol(s, Decl(cb.js, 8, 9)) ->s : Symbol(s, Decl(cb.js, 8, 9)) +>sid : Symbol(sid, Decl(cb.js, 7, 3)) +>s : Symbol(s, Decl(cb.js, 7, 9)) +>s : Symbol(s, Decl(cb.js, 7, 9)) /** @type {NoReturn} */ var noreturn = obj => void obj.title ->noreturn : Symbol(noreturn, Decl(cb.js, 12, 3)) ->obj : Symbol(obj, Decl(cb.js, 12, 14)) ->obj.title : Symbol(title, Decl(cb.js, 16, 34)) ->obj : Symbol(obj, Decl(cb.js, 12, 14)) ->title : Symbol(title, Decl(cb.js, 16, 34)) +>noreturn : Symbol(noreturn, Decl(cb.js, 11, 3)) +>obj : Symbol(obj, Decl(cb.js, 11, 14)) +>obj.title : Symbol(title, Decl(cb.js, 15, 34)) +>obj : Symbol(obj, Decl(cb.js, 11, 14)) +>title : Symbol(title, Decl(cb.js, 15, 34)) /** * @callback NoReturn diff --git a/tests/baselines/reference/callbackTag1.types b/tests/baselines/reference/callbackTag1.types index a1c99e2e7f25f..759f36640115c 100644 --- a/tests/baselines/reference/callbackTag1.types +++ b/tests/baselines/reference/callbackTag1.types @@ -1,5 +1,4 @@ === tests/cases/conformance/jsdoc/cb.js === -// TODO: Test nested param types using the nested Object style /** @callback Sid * @param {string} s * @returns {string} What were you expecting From dafb67d55f8447358b4d95b816de995d94485fe3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 3 May 2018 14:47:00 -0700 Subject: [PATCH 28/49] Fix support for nested namespaced callbacks And add test --- src/compiler/binder.ts | 4 ++-- .../reference/callbackTagNamespace.symbols | 19 +++++++++++++++++ .../reference/callbackTagNamespace.types | 21 +++++++++++++++++++ .../conformance/jsdoc/callbackTagNamespace.ts | 15 +++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/callbackTagNamespace.symbols create mode 100644 tests/baselines/reference/callbackTagNamespace.types create mode 100644 tests/cases/conformance/jsdoc/callbackTagNamespace.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4c7a391230267..6b619082e9def 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2033,10 +2033,10 @@ namespace ts { // current "blockScopeContainer" needs to be set to its immediate namespace parent. if ((node).isInJSDocNamespace) { let parentNode = node.parent; - while (parentNode && parentNode.kind !== SyntaxKind.JSDocTypedefTag) { + while (parentNode && !isJSDocTypeAlias(parentNode)) { parentNode = parentNode.parent; } - bindBlockScopedDeclaration(parentNode, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); break; } // falls through diff --git a/tests/baselines/reference/callbackTagNamespace.symbols b/tests/baselines/reference/callbackTagNamespace.symbols new file mode 100644 index 0000000000000..b8cf7d9588638 --- /dev/null +++ b/tests/baselines/reference/callbackTagNamespace.symbols @@ -0,0 +1,19 @@ +=== tests/cases/conformance/jsdoc/namespaced.js === +/** + * @callback NS.Nested.Inner + * @param {string} space - spaaaaaaaaace + * @param {string} peace - peaaaaaaaaace + * @return {string | number} + */ +var x = 1; +>x : Symbol(x, Decl(namespaced.js, 6, 3)) + +/** @type {NS.Nested.Inner} */ +function f(space, peace) { +>f : Symbol(f, Decl(namespaced.js, 6, 10)) +>space : Symbol(space, Decl(namespaced.js, 8, 11)) +>peace : Symbol(peace, Decl(namespaced.js, 8, 17)) + + return '1' +} + diff --git a/tests/baselines/reference/callbackTagNamespace.types b/tests/baselines/reference/callbackTagNamespace.types new file mode 100644 index 0000000000000..0435320795dcd --- /dev/null +++ b/tests/baselines/reference/callbackTagNamespace.types @@ -0,0 +1,21 @@ +=== tests/cases/conformance/jsdoc/namespaced.js === +/** + * @callback NS.Nested.Inner + * @param {string} space - spaaaaaaaaace + * @param {string} peace - peaaaaaaaaace + * @return {string | number} + */ +var x = 1; +>x : number +>1 : 1 + +/** @type {NS.Nested.Inner} */ +function f(space, peace) { +>f : (space: string, peace: string) => string +>space : string +>peace : string + + return '1' +>'1' : "1" +} + diff --git a/tests/cases/conformance/jsdoc/callbackTagNamespace.ts b/tests/cases/conformance/jsdoc/callbackTagNamespace.ts new file mode 100644 index 0000000000000..ba331e79fc428 --- /dev/null +++ b/tests/cases/conformance/jsdoc/callbackTagNamespace.ts @@ -0,0 +1,15 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: namespaced.js +/** + * @callback NS.Nested.Inner + * @param {string} space - spaaaaaaaaace + * @param {string} peace - peaaaaaaaaace + * @return {string | number} + */ +var x = 1; +/** @type {NS.Nested.Inner} */ +function f(space, peace) { + return '1' +} From 9600db701e5796412be1df7676c6c1209e911752 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 4 May 2018 14:03:19 -0700 Subject: [PATCH 29/49] Callbacks support type parameters 1. Haven't run it with all tests 2. Haven't tested typedef tags yet 3. Still allows shared symbols when on function or class declarations. --- src/compiler/binder.ts | 6 ++- src/compiler/checker.ts | 34 +++++++++++----- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 28 ++++++++----- tests/cases/conformance/jsdoc/callbackTag2.ts | 40 +++++++++++++++++++ 5 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 tests/cases/conformance/jsdoc/callbackTag2.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6b619082e9def..22cfcd0dfea69 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1990,7 +1990,7 @@ namespace ts { } for (const tag of jsDoc.tags) { - if (isJSDocTypeAlias(tag)) { + if (isJSDocTypeAlias(tag) || isJSDocTemplateTag(tag)) { const savedParent = parent; parent = jsDoc; bind(tag); @@ -2202,6 +2202,8 @@ namespace ts { case SyntaxKind.ModuleBlock: return updateStrictModeStatementList((node).statements); + case SyntaxKind.JSDocTemplateTag: + return forEach((node as JSDocTemplateTag).typeParameters, bindTypeParameter); case SyntaxKind.JSDocParameterTag: if (node.parent.kind === SyntaxKind.JSDocSignature) { return bindParameter(node as JSDocParameterTag); @@ -2699,7 +2701,7 @@ namespace ts { } function bindTypeParameter(node: TypeParameterDeclaration) { - if (node.parent.kind === SyntaxKind.InferType) { + if (node.parent && node.parent.kind === SyntaxKind.InferType) { const container = getInferTypeContainer(node.parent); if (container) { if (!container.locals) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cc089a193a632..347c9035f2cd2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1367,6 +1367,16 @@ namespace ts { } } break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + // TODO: These might be stored elsewhere already if we can get the symbol + if (meaning & SymbolFlags.Type) { + const param = find(getEffectiveTypeParameterDeclarations(location as JSDocTypedefTag), param => param.name.escapedText === name); + if (result = getSymbolOfNode(param)) { + break loop; + } + } + break; case SyntaxKind.ExpressionWithTypeArguments: // The type parameters of a class are not in scope in the base class expression. if (lastLocation === (location).expression && (location.parent).token === SyntaxKind.ExtendsKeyword) { @@ -5065,6 +5075,8 @@ namespace ts { case SyntaxKind.ArrowFunction: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.MappedType: case SyntaxKind.ConditionalType: const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); @@ -5094,10 +5106,10 @@ namespace ts { function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] { let result: TypeParameter[]; for (const node of symbol.declarations) { - // TODO: needs to understand jsdoc typedefs and callbacks if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || - node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration) { - const declaration = node; + node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration || + isJSDocTypeAlias(node)) { + const declaration = node; const typeParameters = getEffectiveTypeParameterDeclarations(declaration); if (typeParameters) { result = appendTypeParameters(result, typeParameters); @@ -7122,7 +7134,7 @@ namespace ts { const classType = declaration.kind === SyntaxKind.Constructor ? getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; - const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + const typeParameters = classType ? classType.localTypeParameters : isJSDocSignature(declaration) ? undefined : getTypeParametersFromDeclaration(declaration); const returnType = getSignatureReturnTypeFromDeclaration(declaration, isJSConstructSignature, classType); const hasRestLikeParameter = hasRestParameter(declaration) || isInJavaScriptFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters); links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, /*resolvedTypePredicate*/ undefined, minArgumentCount, hasRestLikeParameter, hasLiteralTypes); @@ -7584,11 +7596,11 @@ namespace ts { const missingAugmentsTag = isJs && node.parent.kind !== SyntaxKind.JSDocAugmentsTag; const diag = minTypeArgumentCount === typeParameters.length ? missingAugmentsTag - ? Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag - : Diagnostics.Generic_type_0_requires_1_type_argument_s - : missingAugmentsTag - ? Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag - : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; + ? Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag + : Diagnostics.Generic_type_0_requires_1_type_argument_s + : missingAugmentsTag + ? Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag + : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); if (!isJs) { @@ -7603,7 +7615,7 @@ namespace ts { return createTypeReference(type, typeArguments); } return checkNoTypeArguments(node, symbol) ? type : unknownType; - } + } function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[]): Type { const type = getDeclaredTypeOfSymbol(symbol); @@ -7757,7 +7769,7 @@ namespace ts { function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) { let constraints: Type[]; - while (node && !isStatement(node)) { + while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { const parent = node.parent; if (parent.kind === SyntaxKind.ConditionalType && node === (parent).trueType) { const constraint = getImpliedConstraint(typeVariable, (parent).checkType, (parent).extendsType); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0572106f94649..3bfa05017d29b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2053,7 +2053,7 @@ namespace ts { export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypeLiteral | JSDocSignature; + export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag; export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 511c777085792..5fd04b9c94cfe 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -527,10 +527,9 @@ namespace ts { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return node.typeParameters; - case SyntaxKind.JSDocSignature: - case SyntaxKind.JSDocTypeLiteral: - // TODO: Actually go find them! - return undefined; + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + return getEffectiveTypeParameterDeclarations(node); default: assertTypeIsNever(node); return undefined; @@ -559,8 +558,8 @@ namespace ts { case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - case SyntaxKind.JSDocSignature: - case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: return true; default: assertTypeIsNever(node); @@ -1930,8 +1929,8 @@ namespace ts { } export function getJSDocHost(node: JSDocTag): HasJSDoc { - while (node.parent.kind === SyntaxKind.JSDocTypeLiteral) { - if (node.parent.parent.kind === SyntaxKind.JSDocTypedefTag) { + while (isJSDocTypeLiteral(node.parent) || isJSDocSignature(node.parent)) { + if (isJSDocTypeAlias(node.parent.parent)) { node = node.parent.parent as JSDocTypedefTag; } else { @@ -3107,9 +3106,16 @@ namespace ts { * Gets the effective type parameters. If the node was parsed in a * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ - export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) { - // TODO: Make getJSDocTypeParameterDeclarations do the right thing - if (isJSDocTypeLiteral(node) || isJSDocSignature(node)) return undefined; + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): NodeArray { + if (isJSDocTypeAlias(node)) { + Debug.assert(node.parent.kind === SyntaxKind.JSDocComment); + const templateTags = flatMap(filter(node.parent.tags, isJSDocTemplateTag), tag => tag.typeParameters) as ReadonlyArray; + const templateTagNodes = templateTags as NodeArray; + templateTagNodes.pos = templateTagNodes.length > 0 ? first(templateTagNodes).pos : node.pos; + templateTagNodes.end = templateTagNodes.length > 0 ? last(templateTagNodes).end : node.end; + templateTagNodes.hasTrailingComma = false; + return templateTagNodes; + } return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); } diff --git a/tests/cases/conformance/jsdoc/callbackTag2.ts b/tests/cases/conformance/jsdoc/callbackTag2.ts new file mode 100644 index 0000000000000..0ce50ca8709e0 --- /dev/null +++ b/tests/cases/conformance/jsdoc/callbackTag2.ts @@ -0,0 +1,40 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: cb.js + +/** @template T + * @callback Id + * @param {T} t + * @returns {T} Maybe just return 120 and cast it? + */ +var x = 1 + +/** @type {Id} I actually wanted to write `const "120"` */ +var one_twenty = s => "120"; + +/** @template S + * @callback SharedId + * @param {S} ego + * @return {S} + */ +class SharedClass { + constructor() { + /** @type {SharedId} */ + this.id; + } +} +/** @type {SharedId} */ +var outside = n => n + 1; + +/** @type {Final<{ fantasy }, { heroes }>} */ +var noreturn = (barts, tidus, noctis) => "cecil" + +/** + * @template V,X + * @callback Final + * @param {V} barts - "Barts" + * @param {X} tidus - Titus + * @param {X & V} noctis - "... Whatever" + * @return {"cecil" | "zidane"} + */ From 0c28218fbc50cacfad7c82b8e6ae44b8a17c234a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 10:10:19 -0700 Subject: [PATCH 30/49] Template tags are now bound correctly --- src/compiler/binder.ts | 22 ++++++- src/compiler/checker.ts | 24 ++++---- src/compiler/utilities.ts | 27 ++++----- .../reference/api/tsserverlibrary.d.ts | 4 +- tests/baselines/reference/api/typescript.d.ts | 4 +- .../reference/callbackTag2.errors.txt | 42 +++++++++++++ .../baselines/reference/callbackTag2.symbols | 52 ++++++++++++++++ tests/baselines/reference/callbackTag2.types | 60 +++++++++++++++++++ .../reference/jsdocTemplateClass.errors.txt | 5 +- .../reference/jsdocTemplateClass.symbols | 41 ++++++------- .../reference/jsdocTemplateClass.types | 3 +- ...sdocTemplateConstructorFunction.errors.txt | 11 ++-- .../jsdocTemplateConstructorFunction.symbols | 53 ++++++++-------- .../jsdocTemplateConstructorFunction.types | 17 +++--- tests/cases/conformance/jsdoc/callbackTag2.ts | 2 +- .../conformance/jsdoc/jsdocTemplateClass.ts | 3 +- .../jsdoc/jsdocTemplateConstructorFunction.ts | 9 ++- 17 files changed, 283 insertions(+), 96 deletions(-) create mode 100644 tests/baselines/reference/callbackTag2.errors.txt create mode 100644 tests/baselines/reference/callbackTag2.symbols create mode 100644 tests/baselines/reference/callbackTag2.types diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 22cfcd0dfea69..49fc73eea52d6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1376,7 +1376,7 @@ namespace ts { function bindJSDocComment(node: JSDoc) { forEachChild(node, n => { - if (!isJSDocTypeAlias(n)) { + if (!isJSDocTypeAlias(n) && !getTypeAliasForJSDocTemplateTag(n, node.tags)) { bind(n); } }); @@ -1552,6 +1552,8 @@ namespace ts { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.MappedType: // All the children of these container types are never visible through another @@ -1990,16 +1992,32 @@ namespace ts { } for (const tag of jsDoc.tags) { - if (isJSDocTypeAlias(tag) || isJSDocTemplateTag(tag)) { + if (isJSDocTypeAlias(tag)) { const savedParent = parent; parent = jsDoc; bind(tag); parent = savedParent; } + const alias = getTypeAliasForJSDocTemplateTag(tag, jsDoc.tags); + if (alias) { + // find typedef or callback and set that to the container (TODO:manually, which is kind of a bad idea) + const savedContainer = container; + const savedParent = parent; + container = alias; + parent = jsDoc; + alias.locals = alias.locals || createSymbolTable(); + bind(tag); + container = savedContainer; + parent = savedParent; + } } } } + function getTypeAliasForJSDocTemplateTag(tag: Node, siblings: NodeArray) { + return isJSDocTemplateTag(tag) && find(siblings, isJSDocTypeAlias); + } + function updateStrictModeStatementList(statements: NodeArray) { if (!inStrictMode) { for (const statement of statements) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 347c9035f2cd2..eaf66e5c71004 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1367,16 +1367,6 @@ namespace ts { } } break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - // TODO: These might be stored elsewhere already if we can get the symbol - if (meaning & SymbolFlags.Type) { - const param = find(getEffectiveTypeParameterDeclarations(location as JSDocTypedefTag), param => param.name.escapedText === name); - if (result = getSymbolOfNode(param)) { - break loop; - } - } - break; case SyntaxKind.ExpressionWithTypeArguments: // The type parameters of a class are not in scope in the base class expression. if (lastLocation === (location).expression && (location.parent).token === SyntaxKind.ExtendsKeyword) { @@ -1581,7 +1571,12 @@ namespace ts { for (const decl of symbol.declarations) { const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; if (decl.kind === SyntaxKind.TypeParameter && parent === container) { - return true; + if (isJSDocTemplateTag(decl.parent)) { + return !find((decl.parent.parent as JSDoc).tags, isJSDocTypeAlias); + } + else { + return true; + } } } @@ -2151,9 +2146,10 @@ namespace ts { * name resolution won't work either. */ function resolveEntityNameFromJSPrototype(name: Identifier, meaning: SymbolFlags) { - if (isJSDocTypeReference(name.parent) && isJSDocTag(name.parent.parent.parent)) { - const host = getJSDocHost(name.parent.parent.parent as JSDocTag); - if (isExpressionStatement(host) && + if (isJSDocTypeReference(name.parent)) { + const host = getJSDocHost(name.parent); + if (host && + isExpressionStatement(host) && isBinaryExpression(host.expression) && getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) { const symbol = getSymbolOfNode(host.expression.left); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5fd04b9c94cfe..2ff837b9a2152 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1928,18 +1928,12 @@ namespace ts { return decl && isFunctionLike(decl) ? decl : undefined; } - export function getJSDocHost(node: JSDocTag): HasJSDoc { - while (isJSDocTypeLiteral(node.parent) || isJSDocSignature(node.parent)) { - if (isJSDocTypeAlias(node.parent.parent)) { - node = node.parent.parent as JSDocTypedefTag; - } - else { - // node.parent.parent is a type expression, child of a parameter type - node = node.parent.parent.parent as JSDocParameterTag; - } + export function getJSDocHost(node: Node): HasJSDoc { + const comment = findAncestor(node.parent, + node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : node.kind === SyntaxKind.JSDocComment); + if (comment) { + return (comment as JSDoc).parent; } - Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment); - return node.parent!.parent!; } export function getTypeParameterFromJsDoc(node: TypeParameterDeclaration & { parent: JSDocTemplateTag }): TypeParameterDeclaration | undefined { @@ -3120,8 +3114,12 @@ namespace ts { } export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) { - const templateTag = getJSDocTemplateTag(node); - return templateTag && templateTag.typeParameters; + const tags = filter(getJSDocTags(node), isJSDocTemplateTag); + for (const tag of tags) { + if (!(tag.parent.kind === SyntaxKind.JSDocComment && find(tag.parent.tags, isJSDocTypeAlias))) { + return tag.typeParameters; + } + } } /** @@ -4804,7 +4802,8 @@ namespace ts { } /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ - function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined { + export function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined { + // TODO: This shouldn't need to be exported, I think return find(getJSDocTags(node), predicate); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3b07c3f0211d0..d37af6f07d344 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1279,7 +1279,7 @@ declare namespace ts { block: Block; } type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypeLiteral | JSDocSignature; + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; @@ -3148,6 +3148,8 @@ declare namespace ts { function getJSDocReturnType(node: Node): TypeNode | undefined; /** Get all JSDoc tags related to a node, including those on parent nodes. */ function getJSDocTags(node: Node): ReadonlyArray; + /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ + function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined; /** Gets all JSDoc tags of a specified kind, or undefined if not present. */ function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): ReadonlyArray; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f38008ea77afb..addb601009c17 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1279,7 +1279,7 @@ declare namespace ts { block: Block; } type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypeLiteral | JSDocSignature; + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; @@ -3148,6 +3148,8 @@ declare namespace ts { function getJSDocReturnType(node: Node): TypeNode | undefined; /** Get all JSDoc tags related to a node, including those on parent nodes. */ function getJSDocTags(node: Node): ReadonlyArray; + /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ + function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined; /** Gets all JSDoc tags of a specified kind, or undefined if not present. */ function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): ReadonlyArray; } diff --git a/tests/baselines/reference/callbackTag2.errors.txt b/tests/baselines/reference/callbackTag2.errors.txt new file mode 100644 index 0000000000000..8be9e90102046 --- /dev/null +++ b/tests/baselines/reference/callbackTag2.errors.txt @@ -0,0 +1,42 @@ +tests/cases/conformance/jsdoc/cb.js(18,29): error TS2304: Cannot find name 'S'. + + +==== tests/cases/conformance/jsdoc/cb.js (1 errors) ==== + /** @template T + * @callback Id + * @param {T} t + * @returns {T} Maybe just return 120 and cast it? + */ + var x = 1 + + /** @type {Id} I actually wanted to write `const "120"` */ + var one_twenty = s => "120"; + + /** @template S + * @callback SharedId + * @param {S} ego + * @return {S} + */ + class SharedClass { + constructor() { + /** @type {SharedId} */ + ~ +!!! error TS2304: Cannot find name 'S'. + this.id; + } + } + /** @type {SharedId} */ + var outside = n => n + 1; + + /** @type {Final<{ fantasy }, { heroes }>} */ + var noreturn = (barts, tidus, noctis) => "cecil" + + /** + * @template V,X + * @callback Final + * @param {V} barts - "Barts" + * @param {X} tidus - Titus + * @param {X & V} noctis - "Prince Noctis Lucius Caelum" + * @return {"cecil" | "zidane"} + */ + \ No newline at end of file diff --git a/tests/baselines/reference/callbackTag2.symbols b/tests/baselines/reference/callbackTag2.symbols new file mode 100644 index 0000000000000..36b810de46566 --- /dev/null +++ b/tests/baselines/reference/callbackTag2.symbols @@ -0,0 +1,52 @@ +=== tests/cases/conformance/jsdoc/cb.js === +/** @template T + * @callback Id + * @param {T} t + * @returns {T} Maybe just return 120 and cast it? + */ +var x = 1 +>x : Symbol(x, Decl(cb.js, 5, 3)) + +/** @type {Id} I actually wanted to write `const "120"` */ +var one_twenty = s => "120"; +>one_twenty : Symbol(one_twenty, Decl(cb.js, 8, 3)) +>s : Symbol(s, Decl(cb.js, 8, 16)) + +/** @template S + * @callback SharedId + * @param {S} ego + * @return {S} + */ +class SharedClass { +>SharedClass : Symbol(SharedClass, Decl(cb.js, 8, 28)) + + constructor() { + /** @type {SharedId} */ + this.id; +>this.id : Symbol(SharedClass.id, Decl(cb.js, 16, 19)) +>this : Symbol(SharedClass, Decl(cb.js, 8, 28)) +>id : Symbol(SharedClass.id, Decl(cb.js, 16, 19)) + } +} +/** @type {SharedId} */ +var outside = n => n + 1; +>outside : Symbol(outside, Decl(cb.js, 22, 3)) +>n : Symbol(n, Decl(cb.js, 22, 13)) +>n : Symbol(n, Decl(cb.js, 22, 13)) + +/** @type {Final<{ fantasy }, { heroes }>} */ +var noreturn = (barts, tidus, noctis) => "cecil" +>noreturn : Symbol(noreturn, Decl(cb.js, 25, 3)) +>barts : Symbol(barts, Decl(cb.js, 25, 16)) +>tidus : Symbol(tidus, Decl(cb.js, 25, 22)) +>noctis : Symbol(noctis, Decl(cb.js, 25, 29)) + +/** + * @template V,X + * @callback Final + * @param {V} barts - "Barts" + * @param {X} tidus - Titus + * @param {X & V} noctis - "Prince Noctis Lucius Caelum" + * @return {"cecil" | "zidane"} + */ + diff --git a/tests/baselines/reference/callbackTag2.types b/tests/baselines/reference/callbackTag2.types new file mode 100644 index 0000000000000..a6825f67524f3 --- /dev/null +++ b/tests/baselines/reference/callbackTag2.types @@ -0,0 +1,60 @@ +=== tests/cases/conformance/jsdoc/cb.js === +/** @template T + * @callback Id + * @param {T} t + * @returns {T} Maybe just return 120 and cast it? + */ +var x = 1 +>x : number +>1 : 1 + +/** @type {Id} I actually wanted to write `const "120"` */ +var one_twenty = s => "120"; +>one_twenty : (t: string) => string +>s => "120" : (s: string) => string +>s : string +>"120" : "120" + +/** @template S + * @callback SharedId + * @param {S} ego + * @return {S} + */ +class SharedClass { +>SharedClass : SharedClass + + constructor() { + /** @type {SharedId} */ + this.id; +>this.id : (ego: any) => any +>this : this +>id : (ego: any) => any + } +} +/** @type {SharedId} */ +var outside = n => n + 1; +>outside : (ego: number) => number +>n => n + 1 : (n: number) => number +>n : number +>n + 1 : number +>n : number +>1 : 1 + +/** @type {Final<{ fantasy }, { heroes }>} */ +var noreturn = (barts, tidus, noctis) => "cecil" +>noreturn : (barts: { fantasy: any; }, tidus: { heroes: any; }, noctis: { heroes: any; } & { fantasy: any; }) => "cecil" | "zidane" +>(barts, tidus, noctis) => "cecil" : (barts: { fantasy: any; }, tidus: { heroes: any; }, noctis: { heroes: any; } & { fantasy: any; }) => "cecil" +>barts : { fantasy: any; } +>tidus : { heroes: any; } +>noctis : { heroes: any; } & { fantasy: any; } +>"cecil" : "cecil" + +/** + * @template V,X + * @callback Final + * @param {V} barts - "Barts" + * @param {X} tidus - Titus + * @param {X & V} noctis - "Prince Noctis Lucius Caelum" + * @return {"cecil" | "zidane"} + */ + diff --git a/tests/baselines/reference/jsdocTemplateClass.errors.txt b/tests/baselines/reference/jsdocTemplateClass.errors.txt index 4598e9f320f6d..402992c34bdc9 100644 --- a/tests/baselines/reference/jsdocTemplateClass.errors.txt +++ b/tests/baselines/reference/jsdocTemplateClass.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/jsdoc/templateTagOnClasses.js(24,1): error TS2322: Type 'boolean' is not assignable to type 'number'. +tests/cases/conformance/jsdoc/templateTagOnClasses.js(25,1): error TS2322: Type 'boolean' is not assignable to type 'number'. ==== tests/cases/conformance/jsdoc/templateTagOnClasses.js (1 errors) ==== @@ -6,6 +6,7 @@ tests/cases/conformance/jsdoc/templateTagOnClasses.js(24,1): error TS2322: Type * @template {T} * @typedef {(t: T) => T} Id */ + /** @template T */ class Foo { /** @typedef {(t: T) => T} Id2 */ /** @param {T} x */ @@ -15,7 +16,7 @@ tests/cases/conformance/jsdoc/templateTagOnClasses.js(24,1): error TS2322: Type /** * * @param {T} x - * @param {Id} y + * @param {Id} y * @param {Id2} alpha * @return {T} */ diff --git a/tests/baselines/reference/jsdocTemplateClass.symbols b/tests/baselines/reference/jsdocTemplateClass.symbols index d80b73bc4ac21..b82cd8038e50b 100644 --- a/tests/baselines/reference/jsdocTemplateClass.symbols +++ b/tests/baselines/reference/jsdocTemplateClass.symbols @@ -3,52 +3,53 @@ * @template {T} * @typedef {(t: T) => T} Id */ +/** @template T */ class Foo { >Foo : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) /** @typedef {(t: T) => T} Id2 */ /** @param {T} x */ constructor (x) { ->x : Symbol(x, Decl(templateTagOnClasses.js, 7, 17)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 8, 17)) this.a = x ->this.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>this.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 8, 21)) >this : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) ->a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) ->x : Symbol(x, Decl(templateTagOnClasses.js, 7, 17)) +>a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 8, 21)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 8, 17)) } /** * * @param {T} x - * @param {Id} y + * @param {Id} y * @param {Id2} alpha * @return {T} */ foo(x, y, alpha) { ->foo : Symbol(Foo.foo, Decl(templateTagOnClasses.js, 9, 5)) ->x : Symbol(x, Decl(templateTagOnClasses.js, 17, 8)) ->y : Symbol(y, Decl(templateTagOnClasses.js, 17, 10)) ->alpha : Symbol(alpha, Decl(templateTagOnClasses.js, 17, 13)) +>foo : Symbol(Foo.foo, Decl(templateTagOnClasses.js, 10, 5)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 18, 8)) +>y : Symbol(y, Decl(templateTagOnClasses.js, 18, 10)) +>alpha : Symbol(alpha, Decl(templateTagOnClasses.js, 18, 13)) return alpha(y(x)) ->alpha : Symbol(alpha, Decl(templateTagOnClasses.js, 17, 13)) ->y : Symbol(y, Decl(templateTagOnClasses.js, 17, 10)) ->x : Symbol(x, Decl(templateTagOnClasses.js, 17, 8)) +>alpha : Symbol(alpha, Decl(templateTagOnClasses.js, 18, 13)) +>y : Symbol(y, Decl(templateTagOnClasses.js, 18, 10)) +>x : Symbol(x, Decl(templateTagOnClasses.js, 18, 8)) } } var f = new Foo(1) ->f : Symbol(f, Decl(templateTagOnClasses.js, 21, 3)) +>f : Symbol(f, Decl(templateTagOnClasses.js, 22, 3)) >Foo : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) var g = new Foo(false) ->g : Symbol(g, Decl(templateTagOnClasses.js, 22, 3)) +>g : Symbol(g, Decl(templateTagOnClasses.js, 23, 3)) >Foo : Symbol(Foo, Decl(templateTagOnClasses.js, 0, 0)) f.a = g.a ->f.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) ->f : Symbol(f, Decl(templateTagOnClasses.js, 21, 3)) ->a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) ->g.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) ->g : Symbol(g, Decl(templateTagOnClasses.js, 22, 3)) ->a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 7, 21)) +>f.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 8, 21)) +>f : Symbol(f, Decl(templateTagOnClasses.js, 22, 3)) +>a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 8, 21)) +>g.a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 8, 21)) +>g : Symbol(g, Decl(templateTagOnClasses.js, 23, 3)) +>a : Symbol(Foo.a, Decl(templateTagOnClasses.js, 8, 21)) diff --git a/tests/baselines/reference/jsdocTemplateClass.types b/tests/baselines/reference/jsdocTemplateClass.types index aa6a685e34fae..9c028a96ff170 100644 --- a/tests/baselines/reference/jsdocTemplateClass.types +++ b/tests/baselines/reference/jsdocTemplateClass.types @@ -3,6 +3,7 @@ * @template {T} * @typedef {(t: T) => T} Id */ +/** @template T */ class Foo { >Foo : Foo @@ -21,7 +22,7 @@ class Foo { /** * * @param {T} x - * @param {Id} y + * @param {Id} y * @param {Id2} alpha * @return {T} */ diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt b/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt index 225d277c4fff8..9e735b780a0fc 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.errors.txt @@ -1,11 +1,14 @@ -tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js(21,1): error TS2322: Type 'false' is not assignable to type 'number'. +tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js(24,1): error TS2322: Type 'false' is not assignable to type 'number'. ==== tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js (1 errors) ==== /** - * @template {T} - * @typedef {(t: T) => T} Id + * @template {U} + * @typedef {(u: U) => U} Id + */ + /** * @param {T} t + * @template {T} */ function Zet(t) { /** @type {T} */ @@ -14,7 +17,7 @@ tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js(21,1): error } /** * @param {T} v - * @param {Id} id + * @param {Id} id */ Zet.prototype.add = function(v, id) { this.u = v || this.t diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols b/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols index 8cf2ee99e57ea..b640d3c1e21e4 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.symbols @@ -1,57 +1,60 @@ === tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js === /** - * @template {T} - * @typedef {(t: T) => T} Id + * @template {U} + * @typedef {(u: U) => U} Id + */ +/** * @param {T} t + * @template {T} */ function Zet(t) { >Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) ->t : Symbol(t, Decl(templateTagOnConstructorFunctions.js, 5, 13)) +>t : Symbol(t, Decl(templateTagOnConstructorFunctions.js, 8, 13)) /** @type {T} */ this.u this.t = t ->t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) ->t : Symbol(t, Decl(templateTagOnConstructorFunctions.js, 5, 13)) +>t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 10, 10)) +>t : Symbol(t, Decl(templateTagOnConstructorFunctions.js, 8, 13)) } /** * @param {T} v - * @param {Id} id + * @param {Id} id */ Zet.prototype.add = function(v, id) { ->Zet.prototype : Symbol(Zet.add, Decl(templateTagOnConstructorFunctions.js, 9, 1)) +>Zet.prototype : Symbol(Zet.add, Decl(templateTagOnConstructorFunctions.js, 12, 1)) >Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) >prototype : Symbol(Function.prototype, Decl(lib.d.ts, --, --)) ->add : Symbol(Zet.add, Decl(templateTagOnConstructorFunctions.js, 9, 1)) ->v : Symbol(v, Decl(templateTagOnConstructorFunctions.js, 14, 29)) ->id : Symbol(id, Decl(templateTagOnConstructorFunctions.js, 14, 31)) +>add : Symbol(Zet.add, Decl(templateTagOnConstructorFunctions.js, 12, 1)) +>v : Symbol(v, Decl(templateTagOnConstructorFunctions.js, 17, 29)) +>id : Symbol(id, Decl(templateTagOnConstructorFunctions.js, 17, 31)) this.u = v || this.t ->this.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>this.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 8, 17), Decl(templateTagOnConstructorFunctions.js, 17, 37)) >this : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) ->u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) ->v : Symbol(v, Decl(templateTagOnConstructorFunctions.js, 14, 29)) ->this.t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) +>u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 8, 17), Decl(templateTagOnConstructorFunctions.js, 17, 37)) +>v : Symbol(v, Decl(templateTagOnConstructorFunctions.js, 17, 29)) +>this.t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 10, 10)) >this : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) ->t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) +>t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 10, 10)) return id(this.u) ->id : Symbol(id, Decl(templateTagOnConstructorFunctions.js, 14, 31)) ->this.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>id : Symbol(id, Decl(templateTagOnConstructorFunctions.js, 17, 31)) +>this.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 8, 17), Decl(templateTagOnConstructorFunctions.js, 17, 37)) >this : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) ->u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 8, 17), Decl(templateTagOnConstructorFunctions.js, 17, 37)) } var z = new Zet(1) ->z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 18, 3)) +>z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 21, 3)) >Zet : Symbol(Zet, Decl(templateTagOnConstructorFunctions.js, 0, 0)) z.t = 2 ->z.t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) ->z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 18, 3)) ->t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 7, 10)) +>z.t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 10, 10)) +>z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 21, 3)) +>t : Symbol(Zet.t, Decl(templateTagOnConstructorFunctions.js, 10, 10)) z.u = false ->z.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) ->z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 18, 3)) ->u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 5, 17), Decl(templateTagOnConstructorFunctions.js, 14, 37)) +>z.u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 8, 17), Decl(templateTagOnConstructorFunctions.js, 17, 37)) +>z : Symbol(z, Decl(templateTagOnConstructorFunctions.js, 21, 3)) +>u : Symbol(Zet.u, Decl(templateTagOnConstructorFunctions.js, 8, 17), Decl(templateTagOnConstructorFunctions.js, 17, 37)) diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction.types b/tests/baselines/reference/jsdocTemplateConstructorFunction.types index f838534aa339f..121cf846a0e39 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction.types +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction.types @@ -1,8 +1,11 @@ === tests/cases/conformance/jsdoc/templateTagOnConstructorFunctions.js === /** - * @template {T} - * @typedef {(t: T) => T} Id + * @template {U} + * @typedef {(u: U) => U} Id + */ +/** * @param {T} t + * @template {T} */ function Zet(t) { >Zet : typeof Zet @@ -23,18 +26,18 @@ function Zet(t) { } /** * @param {T} v - * @param {Id} id + * @param {Id} id */ Zet.prototype.add = function(v, id) { ->Zet.prototype.add = function(v, id) { this.u = v || this.t return id(this.u)} : (v: T, id: (t: T) => T) => T +>Zet.prototype.add = function(v, id) { this.u = v || this.t return id(this.u)} : (v: T, id: (u: T) => T) => T >Zet.prototype.add : any >Zet.prototype : any >Zet : typeof Zet >prototype : any >add : any ->function(v, id) { this.u = v || this.t return id(this.u)} : (v: T, id: (t: T) => T) => T +>function(v, id) { this.u = v || this.t return id(this.u)} : (v: T, id: (u: T) => T) => T >v : T ->id : (t: T) => T +>id : (u: T) => T this.u = v || this.t >this.u = v || this.t : T @@ -49,7 +52,7 @@ Zet.prototype.add = function(v, id) { return id(this.u) >id(this.u) : T ->id : (t: T) => T +>id : (u: T) => T >this.u : T >this : Zet >u : T diff --git a/tests/cases/conformance/jsdoc/callbackTag2.ts b/tests/cases/conformance/jsdoc/callbackTag2.ts index 0ce50ca8709e0..34e71b55bcd71 100644 --- a/tests/cases/conformance/jsdoc/callbackTag2.ts +++ b/tests/cases/conformance/jsdoc/callbackTag2.ts @@ -35,6 +35,6 @@ var noreturn = (barts, tidus, noctis) => "cecil" * @callback Final * @param {V} barts - "Barts" * @param {X} tidus - Titus - * @param {X & V} noctis - "... Whatever" + * @param {X & V} noctis - "Prince Noctis Lucius Caelum" * @return {"cecil" | "zidane"} */ diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts b/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts index 5cd0cde89be12..b13303d6def4f 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateClass.ts @@ -7,6 +7,7 @@ * @template {T} * @typedef {(t: T) => T} Id */ +/** @template T */ class Foo { /** @typedef {(t: T) => T} Id2 */ /** @param {T} x */ @@ -16,7 +17,7 @@ class Foo { /** * * @param {T} x - * @param {Id} y + * @param {Id} y * @param {Id2} alpha * @return {T} */ diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts index dc44afe040a4a..a2a7a48c1cb7c 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction.ts @@ -4,9 +4,12 @@ // @Filename: templateTagOnConstructorFunctions.js /** - * @template {T} - * @typedef {(t: T) => T} Id + * @template {U} + * @typedef {(u: U) => U} Id + */ +/** * @param {T} t + * @template {T} */ function Zet(t) { /** @type {T} */ @@ -15,7 +18,7 @@ function Zet(t) { } /** * @param {T} v - * @param {Id} id + * @param {Id} id */ Zet.prototype.add = function(v, id) { this.u = v || this.t From 07cd96da8827f3b1d8d3e58a9cd5eb79cab0362d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 10:19:51 -0700 Subject: [PATCH 31/49] Test oorder template tags It works. --- .../reference/jsdocTemplateConstructorFunction2.errors.txt | 2 +- .../reference/jsdocTemplateConstructorFunction2.symbols | 2 +- .../baselines/reference/jsdocTemplateConstructorFunction2.types | 2 +- .../conformance/jsdoc/jsdocTemplateConstructorFunction2.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt b/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt index 6a8e45566ecab..cdcc9a056ebab 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.errors.txt @@ -4,8 +4,8 @@ tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js(26,15): error ==== tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js (2 errors) ==== /** - * @template {T} * @param {T} t + * @template {T} */ function Zet(t) { /** @type {T} */ diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols b/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols index c65570f84c4c5..32573a373985b 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.symbols @@ -1,7 +1,7 @@ === tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js === /** - * @template {T} * @param {T} t + * @template {T} */ function Zet(t) { >Zet : Symbol(Zet, Decl(templateTagWithNestedTypeLiteral.js, 0, 0)) diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types index 5737f8caaf9f1..80872e8e0eb1f 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types @@ -1,7 +1,7 @@ === tests/cases/conformance/jsdoc/templateTagWithNestedTypeLiteral.js === /** - * @template {T} * @param {T} t + * @template {T} */ function Zet(t) { >Zet : typeof Zet diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts index a4f107190ff1c..c736962de3974 100644 --- a/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts +++ b/tests/cases/conformance/jsdoc/jsdocTemplateConstructorFunction2.ts @@ -4,8 +4,8 @@ // @Filename: templateTagWithNestedTypeLiteral.js /** - * @template {T} * @param {T} t + * @template {T} */ function Zet(t) { /** @type {T} */ From eb61b8b48d16288f028b5ca17c2e4a523afd53af Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 11:08:32 -0700 Subject: [PATCH 32/49] Parser cleanup --- src/compiler/parser.ts | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 319b00de08955..b9c37239c6623 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6376,7 +6376,7 @@ namespace ts { case SyntaxKind.AtToken: if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { removeTrailingNewlines(comments); - parseTag(indent); + addTag(parseTag(indent)); // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning // for malformed examples like `/** @param {string} x @returns {number} the length */` @@ -6493,8 +6493,7 @@ namespace ts { case "arg": case "argument": case "param": - addTag(parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter, indent)); - return; + return parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter, indent); case "return": case "returns": tag = parseReturnTag(atToken, tagName); @@ -6528,7 +6527,7 @@ namespace ts { // some tags, like typedef and callback, have already parsed their comments earlier tag.comment = parseTagComments(indent + tag.end - tag.pos); } - addTag(tag); + return tag; } function parseTagComments(indent: number): string | undefined { @@ -6601,6 +6600,9 @@ namespace ts { } function addTag(tag: JSDocTag): void { + if (!tag) { + return; + } if (!tags) { tags = [tag]; tagsPos = tag.pos; @@ -6778,8 +6780,8 @@ namespace ts { typedefTag.fullName = parseJSDocTypeNameWithNamespace(); typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName); skipWhitespace(); - typedefTag.comment = parseTagComments(indent); + typedefTag.typeExpression = typeExpression; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { let child: JSDocTypeTag | JSDocPropertyTag | false; @@ -6848,19 +6850,14 @@ namespace ts { const start = scanner.getStartPos(); const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) { - // Debug.assert(child.kind !== SyntaxKind.JSDocTypeTag); jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } const returnTag = tryParse(() => { - if (parseExpectedToken(SyntaxKind.AtToken)) { - const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); - atToken.end = scanner.getTextPos(); - nextToken(); // why is this needed? Doesn't parseExpectedToken advance the token enough? - const name = parseJSDocIdentifierName(); - if (name && (name.escapedText === "return" || name.escapedText === "returns")) { - const returnTag = parseReturnTag(atToken, name); - returnTag.comment = parseTagComments(indent); - return returnTag; + if (token() === SyntaxKind.AtToken) { + nextJSDocToken(); + const tag = parseTag(indent); + if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { + return tag as JSDocReturnTag; } } }); From e6e8ebfb7ae4cacba27998c56aa0414b48a626f6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 11:36:23 -0700 Subject: [PATCH 33/49] Cleanup types and utilities As much as possible, and not as much as I would like. --- src/compiler/types.ts | 3 +- src/compiler/utilities.ts | 46 ++++--------------- src/services/codefixes/fixUnusedIdentifier.ts | 2 +- src/services/refactors/extractSymbol.ts | 8 ++-- .../reference/api/tsserverlibrary.d.ts | 2 - tests/baselines/reference/api/typescript.d.ts | 2 - 6 files changed, 14 insertions(+), 49 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8b852387f6663..2158d6d95f1f5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2391,11 +2391,10 @@ namespace ts { parent: JSDoc; kind: SyntaxKind.JSDocCallbackTag; fullName?: JSDocNamespaceDeclaration | Identifier; - name?: Identifier; // TODO: Not sure whether this rigamarole is needed for callback...but probably! + name?: Identifier; typeExpression: JSDocSignature; } - // TODO: Could just try to reuse JSDocTypeLiteral export interface JSDocSignature extends JSDocType, Declaration { kind: SyntaxKind.JSDocSignature; typeParameters?: ReadonlyArray; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c9e21dd5d6961..872333d64b65b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -498,37 +498,6 @@ namespace ts { return false; } - export function getTypeParametersOfDeclaration(node: DeclarationWithTypeParameters): NodeArray | undefined { - switch (node.kind) { - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTemplateTag: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return node.typeParameters; - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - return getEffectiveTypeParameterDeclarations(node); - default: - assertTypeIsNever(node); - return undefined; - } - } - export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters; export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters { switch (node.kind) { @@ -3024,7 +2993,7 @@ namespace ts { } export function getThisParameter(signature: SignatureDeclaration | JSDocSignature): ParameterDeclaration | undefined { - // TODO: Maybe jsdoc signatures should support this parameters? (parameterIsThisKeyword is not going to work there, I think, since jsdoc doesn't have keywords) + // callback tags do not currently support this parameters if (signature.parameters.length && !isJSDocSignature(signature)) { const thisParameter = signature.parameters[0]; if (parameterIsThisKeyword(thisParameter)) { @@ -3119,14 +3088,17 @@ namespace ts { * JavaScript file, gets the return type annotation from JSDoc. */ export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined { - return !isJSDocSignature(node) && node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined); + if (isJSDocSignature(node)) { + return node.type && node.type.typeExpression && node.type.typeExpression.type; + } + return node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined); } /** * Gets the effective type parameters. If the node was parsed in a * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ - export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): NodeArray { + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) { if (isJSDocTypeAlias(node)) { Debug.assert(node.parent.kind === SyntaxKind.JSDocComment); const templateTags = flatMap(filter(node.parent.tags, isJSDocTemplateTag), tag => tag.typeParameters) as ReadonlyArray; @@ -3143,6 +3115,7 @@ namespace ts { const tags = filter(getJSDocTags(node), isJSDocTemplateTag); for (const tag of tags) { if (!(tag.parent.kind === SyntaxKind.JSDocComment && find(tag.parent.tags, isJSDocTypeAlias))) { + // template tags are only available when a typedef isn't already using them return tag.typeParameters; } } @@ -4765,9 +4738,6 @@ namespace ts { /** Gets the JSDoc return tag for the node if present */ export function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined { - if (isJSDocSignature(node)) { - return node.type; - } return getFirstJSDocTag(node, isJSDocReturnTag); } @@ -4828,7 +4798,7 @@ namespace ts { } /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ - export function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined { + function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined { // TODO: This shouldn't need to be exported, I think return find(getJSDocTags(node), predicate); } diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 93c1c1681af78..dc40948ed1641 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -125,7 +125,7 @@ namespace ts.codefix { break; case SyntaxKind.TypeParameter: - const typeParameters = getTypeParametersOfDeclaration(parent.parent); + const typeParameters = getEffectiveTypeParameterDeclarations(parent.parent); if (typeParameters.length === 1) { const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index ffd0bdbcdef0d..42ea0eabd3a1c 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1464,8 +1464,8 @@ namespace ts.refactor.extractSymbol { } // Note that we add the current node's type parameters *after* updating the corresponding scope. - if (isDeclarationWithTypeParameters(curr) && getTypeParametersOfDeclaration(curr)) { - for (const typeParameterDecl of getTypeParametersOfDeclaration(curr)) { + if (isDeclarationWithTypeParameters(curr) && getEffectiveTypeParameterDeclarations(curr)) { + for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) { const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; if (allTypeParameterUsages.has(typeParameter.id.toString())) { seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter); @@ -1536,8 +1536,8 @@ namespace ts.refactor.extractSymbol { function hasTypeParameters(node: Node) { return isDeclarationWithTypeParameters(node) && - getTypeParametersOfDeclaration(node) && - getTypeParametersOfDeclaration(node).length > 0; + getEffectiveTypeParameterDeclarations(node) && + getEffectiveTypeParameterDeclarations(node).length > 0; } function isInGenericContext(node: Node) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5d076f215a1b3..a4d5e9c7dd6fd 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3167,8 +3167,6 @@ declare namespace ts { function getJSDocReturnType(node: Node): TypeNode | undefined; /** Get all JSDoc tags related to a node, including those on parent nodes. */ function getJSDocTags(node: Node): ReadonlyArray; - /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ - function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined; /** Gets all JSDoc tags of a specified kind, or undefined if not present. */ function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): ReadonlyArray; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 18a5002795b01..6437ddb4ace74 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3167,8 +3167,6 @@ declare namespace ts { function getJSDocReturnType(node: Node): TypeNode | undefined; /** Get all JSDoc tags related to a node, including those on parent nodes. */ function getJSDocTags(node: Node): ReadonlyArray; - /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ - function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined; /** Gets all JSDoc tags of a specified kind, or undefined if not present. */ function getAllJSDocTagsOfKind(node: Node, kind: SyntaxKind): ReadonlyArray; } From 91d95f72085618d00f193a959fc2b3bae26a2152 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 11:41:08 -0700 Subject: [PATCH 34/49] Handle callback more often in services --- src/services/jsDoc.ts | 2 +- src/services/utilities.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 2a8cc2245ac0f..c8958ad3d2f57 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -100,8 +100,8 @@ namespace ts.JsDoc { return withList((tag as JSDocTemplateTag).typeParameters); case SyntaxKind.JSDocTypeTag: return withNode((tag as JSDocTypeTag).typeExpression); - // TODO: Handle callback here case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocPropertyTag: case SyntaxKind.JSDocParameterTag: const { name } = tag as JSDocTypedefTag | JSDocPropertyTag | JSDocParameterTag; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 861846f26d1a5..01fbfe76a2ffc 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -272,8 +272,7 @@ namespace ts { } export function getContainerNode(node: Node): Declaration { - // TODO: Probably need to handle jsdoccallbacktag too - if (node.kind === SyntaxKind.JSDocTypedefTag) { + if (isJSDocTypeAlias(node)) { // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. // Then we get parent again in the loop. From 6973c5dd2e449b2d018bba413a8f3b977a0c875f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 13:31:26 -0700 Subject: [PATCH 35/49] Cleanup of binder and checker --- src/compiler/binder.ts | 8 ++-- src/compiler/checker.ts | 90 +++++++++++++---------------------------- 2 files changed, 33 insertions(+), 65 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8166cb2ca5dae..49c1a13e40383 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1380,6 +1380,7 @@ namespace ts { function bindJSDocComment(node: JSDoc) { forEachChild(node, n => { + // Skip type-alias-related tags, which are bound early. if (!isJSDocTypeAlias(n) && !getTypeAliasForJSDocTemplateTag(n, node.tags)) { bind(n); } @@ -1949,7 +1950,7 @@ namespace ts { // Here the current node is "foo", which is a container, but the scope of "MyType" should // not be inside "foo". Therefore we always bind @typedef before bind the parent node, // and skip binding this tag later when binding all the other jsdoc tags. - if (isInJavaScriptFile(node)) bindJSDocTypedefTagIfAny(node); + if (isInJavaScriptFile(node)) bindJSDocTypeAliasTagsIfAny(node); // First we bind declaration nodes to a symbol if possible. We'll both create a symbol // and then potentially add the symbol to an appropriate symbol table. Possible @@ -1985,7 +1986,7 @@ namespace ts { inStrictMode = saveInStrictMode; } - function bindJSDocTypedefTagIfAny(node: Node) { + function bindJSDocTypeAliasTagsIfAny(node: Node) { if (!hasJSDocNodes(node)) { return; } @@ -2002,9 +2003,10 @@ namespace ts { bind(tag); parent = savedParent; } + // Bind template tags that have a typedef or callback tag in the same comment. + // The typedef/callback tag is the container of the template. const alias = getTypeAliasForJSDocTemplateTag(tag, jsDoc.tags); if (alias) { - // find typedef or callback and set that to the container (TODO:manually, which is kind of a bad idea) const savedContainer = container; const savedParent = parent; container = alias; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 10ab871edd258..a1db7a626e917 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1569,13 +1569,10 @@ namespace ts { function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { for (const decl of symbol.declarations) { - const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; - if (decl.kind === SyntaxKind.TypeParameter && parent === container) { - if (isJSDocTemplateTag(decl.parent)) { - return !find((decl.parent.parent as JSDoc).tags, isJSDocTypeAlias); - } - else { - return true; + if (decl.kind === SyntaxKind.TypeParameter) { + const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; + if (parent === container) { + return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags, isJSDocTypeAlias)); } } } @@ -3565,34 +3562,10 @@ namespace ts { } function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { - const parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + let parameterDeclaration: ParameterDeclaration | JSDocParameterTag = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); if (!parameterDeclaration && !(isTransientSymbol(parameterSymbol) && !!parameterSymbol.isRestParameter)) { - const paramTagDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); - // now do a whole bunch of stuff with the parameter tag instead - // TODO: Dedupe this! - Debug.assert(!!paramTagDeclaration); - // TODO: Might need to convert jsdoc type literal to a binding name? - Debug.assert(isIdentifier(paramTagDeclaration.name)); - const dotDotDotToken = isRestParameter(paramTagDeclaration) ? createToken(SyntaxKind.DotDotDotToken) : undefined; - const name = paramTagDeclaration.name && isIdentifier(paramTagDeclaration.name) ? - // isIdentifier(paramTagDeclaration.name) ? - setEmitFlags(getSynthesizedClone(paramTagDeclaration.name), EmitFlags.NoAsciiEscaping) : - // cloneQualifiedName(paramTagDeclaration.name) : - symbolName(parameterSymbol); - const questionToken = isOptionalParameter2(paramTagDeclaration) ? createToken(SyntaxKind.QuestionToken) : undefined; - const parameterTypeNode = typeToTypeNodeHelper(getTypeOfSymbol(parameterSymbol), context); - const parameterNode = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - dotDotDotToken, - name, - questionToken, - parameterTypeNode, - /*initializer*/ undefined); - return parameterNode; + parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); } - Debug.assert(!!parameterDeclaration || isTransientSymbol(parameterSymbol) && !!parameterSymbol.isRestParameter); - let parameterType = getTypeOfSymbol(parameterSymbol); if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { parameterType = getOptionalType(parameterType); @@ -3603,8 +3576,8 @@ namespace ts { const dotDotDotToken = !parameterDeclaration || isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.DotDotDotToken) : undefined; const name = parameterDeclaration ? parameterDeclaration.name ? - parameterDeclaration.name.kind === SyntaxKind.Identifier ? - setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : + parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : + parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : cloneBindingName(parameterDeclaration.name) : symbolName(parameterSymbol) : symbolName(parameterSymbol); @@ -3630,15 +3603,6 @@ namespace ts { return setEmitFlags(clone, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); } } - - // function cloneQualifiedName(node: QualifiedName): EntityName { - // if (isIdentifier(node.left)) { - // return setEmitFlags(getSynthesizedClone(node.left), EmitFlags.NoAsciiEscaping); - // } - // else { - // return setEmitFlags(createQualifiedName(cloneQualifiedName(node.left), getSynthesizedClone(node.right)), EmitFlags.NoAsciiEscaping); - // } - // } } function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { @@ -6994,8 +6958,8 @@ namespace ts { return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; } - function isOptionalParameter(node: ParameterDeclaration) { - if (hasQuestionToken(node) || isJSDocOptionalParameter(node)) { + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (hasQuestionToken(node) || isOptionalJSDocParameterTag(node) || isJSDocOptionalParameter(node)) { return true; } @@ -7015,6 +6979,14 @@ namespace ts { return false; } + function isOptionalJSDocParameterTag(node: Node): node is JSDocParameterTag { + if (!isJSDocParameterTag(node)) { + return false; + } + const { isBracketed, typeExpression } = node; + return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } + function createTypePredicateFromTypePredicateNode(node: TypePredicateNode): IdentifierTypePredicate | ThisTypePredicate { const { parameterName } = node; const type = getTypeFromTypeNode(node.type); @@ -7132,9 +7104,14 @@ namespace ts { } // Record a new minimum argument count if this is not an optional parameter - if (!(isOptionalParameter2(param) || - iife && parameters.length > iife.arguments.length && !type || - isUntypedSignatureInJSFile)) { + const isOptionalParameter = isOptionalJSDocParameterTag(param) || + param.initializer || param.questionToken || param.dotDotDotToken || + iife && parameters.length > iife.arguments.length && !type || + isUntypedSignatureInJSFile || + isJSDocOptionalParameter(param); + + + if (!isOptionalParameter) { minArgumentCount = parameters.length; } } @@ -7161,18 +7138,6 @@ namespace ts { return links.resolvedSignature; } - // TODO: Decide between this and the Original and B-b-b-b-best? - function isOptionalParameter2(param: ParameterDeclaration | JSDocParameterTag) { - if (isParameter(param)) { - return param.initializer || param.questionToken || param.dotDotDotToken || - isJSDocOptionalParameter(param); - } - else { - const { isBracketed, typeExpression } = param; - return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; - } - } - /** * A JS function gets a synthetic rest parameter if it references `arguments` AND: * 1. It has no parameters but at least one `@param` with a type that starts with `...` @@ -26355,9 +26320,10 @@ namespace ts { return false; } - function isRequiredInitializedParameter(parameter: ParameterDeclaration) { + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag) { return strictNullChecks && !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && parameter.initializer && !hasModifier(parameter, ModifierFlags.ParameterPropertyModifier); } From 52cd1c7cca3c96114c623637687fb525cb81efec Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 14:22:17 -0700 Subject: [PATCH 36/49] More checker cleanup --- src/compiler/checker.ts | 9 ++------- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 4 ++++ tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a1db7a626e917..3a665857b6352 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4732,9 +4732,6 @@ namespace ts { if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.typeExpression) { return links.type = getTypeFromTypeNode(declaration.typeExpression.type); } - if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && isJSDocSignature(declaration.parent) && !declaration.typeExpression) { - return links.type = unknownType; - } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return unknownType; @@ -4750,7 +4747,7 @@ namespace ts { declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) { type = getWidenedTypeFromJSSpecialPropertyDeclarations(symbol); } - else if (isJSDocPropertyTag(declaration) + else if (isJSDocPropertyLikeTag(declaration) || isPropertyAccessExpression(declaration) || isIdentifier(declaration) || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) @@ -7109,8 +7106,6 @@ namespace ts { iife && parameters.length > iife.arguments.length && !type || isUntypedSignatureInJSFile || isJSDocOptionalParameter(param); - - if (!isOptionalParameter) { minArgumentCount = parameters.length; } @@ -7130,7 +7125,7 @@ namespace ts { const classType = declaration.kind === SyntaxKind.Constructor ? getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; - const typeParameters = classType ? classType.localTypeParameters : isJSDocSignature(declaration) ? undefined : getTypeParametersFromDeclaration(declaration); + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); const returnType = getSignatureReturnTypeFromDeclaration(declaration, isJSConstructSignature, classType); const hasRestLikeParameter = hasRestParameter(declaration) || isInJavaScriptFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters); links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, /*resolvedTypePredicate*/ undefined, minArgumentCount, hasRestLikeParameter, hasLiteralTypes); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2158d6d95f1f5..918a34c18b1b3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2053,7 +2053,7 @@ namespace ts { export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag; + export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature; export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 872333d64b65b..bf5658ad27802 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -522,6 +522,7 @@ namespace ts { case SyntaxKind.ArrowFunction: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocSignature: return true; default: assertTypeIsNever(node); @@ -3099,6 +3100,9 @@ namespace ts { * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) { + if (isJSDocSignature(node)) { + return undefined; + } if (isJSDocTypeAlias(node)) { Debug.assert(node.parent.kind === SyntaxKind.JSDocComment); const templateTags = flatMap(filter(node.parent.tags, isJSDocTemplateTag), tag => tag.typeParameters) as ReadonlyArray; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a4d5e9c7dd6fd..68fcd99a54e72 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1280,7 +1280,7 @@ declare namespace ts { block: Block; } type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag; + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6437ddb4ace74..0b2ec76309948 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1280,7 +1280,7 @@ declare namespace ts { block: Block; } type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; - type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag; + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; From 072ae0dfaa4bfe0edf65881d83038339f3a2b6d7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 14:33:38 -0700 Subject: [PATCH 37/49] Remove TODOs and one more cleanup --- src/compiler/checker.ts | 3 +-- src/compiler/parser.ts | 1 - src/compiler/utilities.ts | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3a665857b6352..8df5b1c65e63d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7221,7 +7221,7 @@ namespace ts { for (let i = 0; i < symbol.declarations.length; i++) { const decl = symbol.declarations[i]; const node = isPropertyAccessExpression(decl) ? getAssignedJavascriptInitializer(decl) : decl; - if (!isFunctionLike(node) && !(node && isJSDocSignature(node))) continue; + if (!isFunctionLike(node)) continue; // Don't include signature if node is the implementation of an overloaded function. A node is considered // an implementation node if it has a body and the previous node is of the same kind and immediately // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). @@ -7615,7 +7615,6 @@ namespace ts { */ function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[]): Type { const type = getDeclaredTypeOfSymbol(symbol); - // TODO: call getEffectiveTypeParameterDeclarations here and upgrade it to understand in-comment template tags as type parameters const typeParameters = getSymbolLinks(symbol).typeParameters; if (typeParameters) { const numTypeArguments = length(node.typeArguments); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b9c37239c6623..06944b5e09ccf 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6668,7 +6668,6 @@ namespace ts { createNode(SyntaxKind.JSDocParameterTag, atToken.pos); let comment: string | undefined; if (indent !== undefined) comment = parseTagComments(indent + scanner.getStartPos() - atToken.pos); - // TODO: For nested parsing, CallbackParameter should change to Parameter const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name, target); if (nestedTypeLiteral) { typeExpression = nestedTypeLiteral; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bf5658ad27802..21df22e9ae3d2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1933,7 +1933,6 @@ namespace ts { } export function hasRestParameter(s: SignatureDeclaration | JSDocSignature): boolean { - // TODO: This type argument wouldn't be needed if I used NodeArray for jsdoc signatures const last = lastOrUndefined(s.parameters); return last && isRestParameter(last); } @@ -4803,7 +4802,6 @@ namespace ts { /** Get the first JSDoc tag of a specified kind, or undefined if not present. */ function getFirstJSDocTag(node: Node, predicate: (tag: JSDocTag) => tag is T): T | undefined { - // TODO: This shouldn't need to be exported, I think return find(getJSDocTags(node), predicate); } From 4c010637f744704ef6ed4c42554e389a567e42ed Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 7 May 2018 14:51:47 -0700 Subject: [PATCH 38/49] Support parameter-less callback tags --- src/compiler/parser.ts | 1 + tests/baselines/reference/callbackTag3.symbols | 9 +++++++++ tests/baselines/reference/callbackTag3.types | 11 +++++++++++ tests/cases/conformance/jsdoc/callbackTag3.ts | 10 ++++++++++ 4 files changed, 31 insertions(+) create mode 100644 tests/baselines/reference/callbackTag3.symbols create mode 100644 tests/baselines/reference/callbackTag3.types create mode 100644 tests/cases/conformance/jsdoc/callbackTag3.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 06944b5e09ccf..281443c6a4db7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6848,6 +6848,7 @@ namespace ts { let child: JSDocParameterTag | false; const start = scanner.getStartPos(); const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature; + jsdocSignature.parameters = []; while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) { jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray, child); } diff --git a/tests/baselines/reference/callbackTag3.symbols b/tests/baselines/reference/callbackTag3.symbols new file mode 100644 index 0000000000000..88b53a4cdaa2f --- /dev/null +++ b/tests/baselines/reference/callbackTag3.symbols @@ -0,0 +1,9 @@ +=== tests/cases/conformance/jsdoc/cb.js === +/** @callback Miracle + * @returns {string} What were you expecting + */ +/** @type {Miracle} smallId */ +var sid = () => "!"; +>sid : Symbol(sid, Decl(cb.js, 4, 3)) + + diff --git a/tests/baselines/reference/callbackTag3.types b/tests/baselines/reference/callbackTag3.types new file mode 100644 index 0000000000000..4e9bfd9a0ced0 --- /dev/null +++ b/tests/baselines/reference/callbackTag3.types @@ -0,0 +1,11 @@ +=== tests/cases/conformance/jsdoc/cb.js === +/** @callback Miracle + * @returns {string} What were you expecting + */ +/** @type {Miracle} smallId */ +var sid = () => "!"; +>sid : () => string +>() => "!" : () => string +>"!" : "!" + + diff --git a/tests/cases/conformance/jsdoc/callbackTag3.ts b/tests/cases/conformance/jsdoc/callbackTag3.ts new file mode 100644 index 0000000000000..cb51ed6bb46bf --- /dev/null +++ b/tests/cases/conformance/jsdoc/callbackTag3.ts @@ -0,0 +1,10 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: cb.js +/** @callback Miracle + * @returns {string} What were you expecting + */ +/** @type {Miracle} smallId */ +var sid = () => "!"; + From 831f44f887070e0f27803d711c41a830a0b29c64 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 8 May 2018 10:11:31 -0700 Subject: [PATCH 39/49] Remove extra bind call on template type parameters --- src/compiler/binder.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 49c1a13e40383..89299754fa167 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2226,8 +2226,6 @@ namespace ts { case SyntaxKind.ModuleBlock: return updateStrictModeStatementList((node).statements); - case SyntaxKind.JSDocTemplateTag: - return forEach((node as JSDocTemplateTag).typeParameters, bindTypeParameter); case SyntaxKind.JSDocParameterTag: if (node.parent.kind === SyntaxKind.JSDocSignature) { return bindParameter(node as JSDocParameterTag); From 9ec7f269a4b57c04a1f4ef2929e555e6e5c0d143 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 8 May 2018 13:16:12 -0700 Subject: [PATCH 40/49] Bind template tag containers Doesn't quite work with typedefs, but that's because it's now stricter, without the typedef fixes. I'm going to merge with jsdoc/callback and see how it goes. --- src/compiler/binder.ts | 49 +++++++++---------- src/compiler/checker.ts | 32 +++++++----- src/compiler/utilities.ts | 4 +- .../reference/jsdocTemplateTag2.symbols | 19 +++++++ .../reference/jsdocTemplateTag2.types | 21 ++++++++ .../conformance/jsdoc/jsdocTemplateTag2.ts | 16 ++++++ 6 files changed, 102 insertions(+), 39 deletions(-) create mode 100644 tests/baselines/reference/jsdocTemplateTag2.symbols create mode 100644 tests/baselines/reference/jsdocTemplateTag2.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTemplateTag2.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 89299754fa167..a0004d14b9b27 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -626,22 +626,6 @@ namespace ts { } function bindChildrenWorker(node: Node): void { - // Binding of JsDocComment should be done before the current block scope container changes. - // because the scope of JsDocComment should not be affected by whether the current node is a - // container or not. - if (hasJSDocNodes(node)) { - if (isInJavaScriptFile(node)) { - for (const j of node.jsDoc) { - bind(j); - } - } - else { - for (const j of node.jsDoc) { - setParentPointers(node, j); - } - } - } - if (checkUnreachable(node)) { bindEachChild(node); return; @@ -727,6 +711,23 @@ namespace ts { bindEachChild(node); break; } + // Binding of JsDocComment should be done before the current block scope container changes. + // because the scope of JsDocComment should not be affected by whether the current node is a + // container or not. + if (hasJSDocNodes(node)) { + if (isInJavaScriptFile(node)) { + for (const j of node.jsDoc) { + bind(j); + } + } + else { + for (const j of node.jsDoc) { + setParentPointers(node, j); + } + } + } + + } function isNarrowingExpression(expr: Expression): boolean { @@ -2719,19 +2720,15 @@ namespace ts { } function getInferTypeContainer(node: Node): ConditionalTypeNode { - while (node) { - const parent = node.parent; - if (parent && parent.kind === SyntaxKind.ConditionalType && (parent).extendsType === node) { - return parent; - } - node = parent; - } - return undefined; + const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); + return extendsType && extendsType.parent as ConditionalTypeNode; } function bindTypeParameter(node: TypeParameterDeclaration) { - if (node.parent && node.parent.kind === SyntaxKind.InferType) { - const container = getInferTypeContainer(node.parent); + // TODO: node.parent check *might* not be necessary, although that would mean combining isJSDocTemplateTag with getTypeAliasForJSDocTemplateTag + // and then calling that instead + if (node.parent && (node.parent.kind === SyntaxKind.InferType || isJSDocTemplateTag(node.parent))) { + const container = node.parent.kind === SyntaxKind.InferType ? getInferTypeContainer(node.parent) : getHostSignatureFromJSDoc(node.parent); if (container) { if (!container.locals) { container.locals = createSymbolTable(); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8df5b1c65e63d..15de6ccd03fa4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2083,7 +2083,7 @@ namespace ts { let symbol: Symbol; if (name.kind === SyntaxKind.Identifier) { const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0; - const symbolFromJSPrototype = isInJavaScriptFile(name) ? resolveEntityNameFromJSPrototype(name, meaning) : undefined; + const symbolFromJSPrototype = isInJavaScriptFile(name) ? resolveEntityNameFromJSSpecialAssignment(name, meaning) : undefined; symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true); if (!symbol) { return symbolFromJSPrototype; @@ -2138,26 +2138,34 @@ namespace ts { } /** - * For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. + * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so * name resolution won't work either. + * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. */ - function resolveEntityNameFromJSPrototype(name: Identifier, meaning: SymbolFlags) { + function resolveEntityNameFromJSSpecialAssignment(name: Identifier, meaning: SymbolFlags) { if (isJSDocTypeReference(name.parent)) { const host = getJSDocHost(name.parent); - if (host && - isExpressionStatement(host) && - isBinaryExpression(host.expression) && - getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) { - const symbol = getSymbolOfNode(host.expression.left); - if (symbol) { - const secondaryLocation = symbol.parent.valueDeclaration; - return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); - } + if (host) { + const secondaryLocation = getJSSpecialAssignmentSymbol(getJSDocHost(name.parent.parent.parent as JSDocTag)); + return secondaryLocation && resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); } } } + function getJSSpecialAssignmentSymbol(host: HasJSDoc): Declaration | undefined { + if (isPropertyAssignment(host) && isFunctionLike(host.initializer)) { + const symbol = getSymbolOfNode(host.initializer); + return symbol && symbol.valueDeclaration; + } + else if (isExpressionStatement(host) && + isBinaryExpression(host.expression) && + getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) { + const symbol = getSymbolOfNode(host.expression.left); + return symbol && symbol.parent.valueDeclaration; + } + } + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol { return resolveExternalModuleNameWorker(location, moduleReferenceExpression, Diagnostics.Cannot_find_module_0); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 21df22e9ae3d2..10abad383e2d6 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1826,6 +1826,8 @@ namespace ts { return v && v.initializer; case SyntaxKind.PropertyDeclaration: return (node as PropertyDeclaration).initializer; + case SyntaxKind.PropertyAssignment: + return (node as PropertyAssignment).initializer; } } @@ -1907,7 +1909,7 @@ namespace ts { return parameter && parameter.symbol; } - export function getHostSignatureFromJSDoc(node: JSDocParameterTag): SignatureDeclaration | undefined { + export function getHostSignatureFromJSDoc(node: JSDocTag): SignatureDeclaration | undefined { const host = getJSDocHost(node); const decl = getSourceOfDefaultedAssignment(host) || getSourceOfAssignment(host) || diff --git a/tests/baselines/reference/jsdocTemplateTag2.symbols b/tests/baselines/reference/jsdocTemplateTag2.symbols new file mode 100644 index 0000000000000..407943934ff9f --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag2.symbols @@ -0,0 +1,19 @@ +=== tests/cases/conformance/jsdoc/github17339.js === +var obj = { +>obj : Symbol(obj, Decl(github17339.js, 0, 3)) + + /** + * @template T + * @param {T} a + * @returns {T} + */ + x: function (a) { +>x : Symbol(x, Decl(github17339.js, 0, 11)) +>a : Symbol(a, Decl(github17339.js, 6, 14)) + + return a; +>a : Symbol(a, Decl(github17339.js, 6, 14)) + + }, +}; + diff --git a/tests/baselines/reference/jsdocTemplateTag2.types b/tests/baselines/reference/jsdocTemplateTag2.types new file mode 100644 index 0000000000000..d35724fb9b690 --- /dev/null +++ b/tests/baselines/reference/jsdocTemplateTag2.types @@ -0,0 +1,21 @@ +=== tests/cases/conformance/jsdoc/github17339.js === +var obj = { +>obj : { [x: string]: any; x: (a: T) => T; } +>{ /** * @template T * @param {T} a * @returns {T} */ x: function (a) { return a; },} : { [x: string]: any; x: (a: T) => T; } + + /** + * @template T + * @param {T} a + * @returns {T} + */ + x: function (a) { +>x : (a: T) => T +>function (a) { return a; } : (a: T) => T +>a : T + + return a; +>a : T + + }, +}; + diff --git a/tests/cases/conformance/jsdoc/jsdocTemplateTag2.ts b/tests/cases/conformance/jsdoc/jsdocTemplateTag2.ts new file mode 100644 index 0000000000000..b7e706f124aa4 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTemplateTag2.ts @@ -0,0 +1,16 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @lib: dom,esnext +// @Filename: github17339.js + +var obj = { + /** + * @template T + * @param {T} a + * @returns {T} + */ + x: function (a) { + return a; + }, +}; From 3fed87e260f3bab9c7d2834bfa32aeb1f954a962 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 9 May 2018 09:16:07 -0700 Subject: [PATCH 41/49] Fix fourslash failures --- src/compiler/binder.ts | 24 ++++++++++++------- src/services/codefixes/fixUnusedIdentifier.ts | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a0004d14b9b27..3b9899b7b7bf3 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -626,6 +626,9 @@ namespace ts { } function bindChildrenWorker(node: Node): void { + if (isJSDoc(node)) { + return bindJSDocComment(node); + } if (checkUnreachable(node)) { bindEachChild(node); return; @@ -711,9 +714,6 @@ namespace ts { bindEachChild(node); break; } - // Binding of JsDocComment should be done before the current block scope container changes. - // because the scope of JsDocComment should not be affected by whether the current node is a - // container or not. if (hasJSDocNodes(node)) { if (isInJavaScriptFile(node)) { for (const j of node.jsDoc) { @@ -726,8 +726,6 @@ namespace ts { } } } - - } function isNarrowingExpression(expr: Expression): boolean { @@ -2725,10 +2723,18 @@ namespace ts { } function bindTypeParameter(node: TypeParameterDeclaration) { - // TODO: node.parent check *might* not be necessary, although that would mean combining isJSDocTemplateTag with getTypeAliasForJSDocTemplateTag - // and then calling that instead - if (node.parent && (node.parent.kind === SyntaxKind.InferType || isJSDocTemplateTag(node.parent))) { - const container = node.parent.kind === SyntaxKind.InferType ? getInferTypeContainer(node.parent) : getHostSignatureFromJSDoc(node.parent); + if (isJSDocTemplateTag(node.parent)) { + const container = getTypeAliasForJSDocTemplateTag(node.parent, (node.parent.parent as JSDoc).tags) || getHostSignatureFromJSDoc(node.parent); + if (container) { + Debug.assert(!!container.locals); + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + } + } + else if (node.parent.kind === SyntaxKind.InferType) { + const container = getInferTypeContainer(node.parent); if (container) { if (!container.locals) { container.locals = createSymbolTable(); diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index dc40948ed1641..e2ff057d1fca1 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -66,7 +66,7 @@ namespace ts.codefix { } function getToken(sourceFile: SourceFile, pos: number): Node { - const token = findPrecedingToken(pos, sourceFile); + const token = findPrecedingToken(pos, sourceFile, /*startNode*/ undefined, /*includeJsDoc*/ true); // this handles var ["computed"] = 12; return token.kind === SyntaxKind.CloseBracketToken ? findPrecedingToken(pos - 1, sourceFile) : token; } From 64623f5efc231b37cfb9a71e69f1bb2fccf4910c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 9 May 2018 14:53:06 -0700 Subject: [PATCH 42/49] Stop pre-binding js type aliases Next up, stop pre-binding js type parameters --- src/compiler/binder.ts | 100 +++++++++++++++++++++----------------- src/compiler/utilities.ts | 9 +--- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3b9899b7b7bf3..b283b1284ef1e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -118,7 +118,7 @@ namespace ts { let thisParentContainer: Node; // Container one level up let blockScopeContainer: Node; let lastContainer: Node; - let delayedTypedefs: { typeAlias: JSDocTypedefTag | JSDocCallbackTag, container: Node, lastContainer: Node, blockScopeContainer: Node, parent: Node }[]; + let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag)[]; let seenThisKeyword: boolean; // state used by control flow analysis @@ -188,7 +188,7 @@ namespace ts { thisParentContainer = undefined; blockScopeContainer = undefined; lastContainer = undefined; - delayedTypedefs = undefined; + delayedTypeAliases = undefined; seenThisKeyword = false; currentFlow = undefined; currentBreakTarget = undefined; @@ -627,6 +627,7 @@ namespace ts { function bindChildrenWorker(node: Node): void { if (isJSDoc(node)) { + // TODO: This won't be needed after getting rid of early-jsdoc-if-any binding, because we won't be skipping anything return bindJSDocComment(node); } if (checkUnreachable(node)) { @@ -694,9 +695,6 @@ namespace ts { case SyntaxKind.CallExpression: bindCallExpressionFlow(node); break; - case SyntaxKind.JSDocComment: - bindJSDocComment(node); - break; case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag); @@ -714,18 +712,7 @@ namespace ts { bindEachChild(node); break; } - if (hasJSDocNodes(node)) { - if (isInJavaScriptFile(node)) { - for (const j of node.jsDoc) { - bind(j); - } - } - else { - for (const j of node.jsDoc) { - setParentPointers(node, j); - } - } - } + bindJSDoc(node); } function isNarrowingExpression(expr: Expression): boolean { @@ -1380,22 +1367,17 @@ namespace ts { function bindJSDocComment(node: JSDoc) { forEachChild(node, n => { // Skip type-alias-related tags, which are bound early. - if (!isJSDocTypeAlias(n) && !getTypeAliasForJSDocTemplateTag(n, node.tags)) { + if (!getTypeAliasForJSDocTemplateTag(n, node.tags)) { bind(n); } }); } function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) { - forEachChild(node, n => { - // if the node has a fullName "A.B.C", that means symbol "C" was already bound - // when we visit "fullName"; so when we visit the name "C" as the next child of - // the jsDocTypedefTag, we should skip binding it. - if (node.fullName && n === node.name && node.fullName.kind !== SyntaxKind.Identifier) { - return; - } - bind(n); - }); + if (node.fullName) { + // TODO: Could be not needed? + setParentPointers(node, node.fullName); + } } function bindCallExpressionFlow(node: CallExpression) { @@ -1755,21 +1737,44 @@ namespace ts { } function delayedBindJSDocTypedefTag() { - if (!delayedTypedefs) { + if (!delayedTypeAliases) { return; } const saveContainer = container; const saveLastContainer = lastContainer; const saveBlockScopeContainer = blockScopeContainer; const saveParent = parent; - for (const delay of delayedTypedefs) { - ({ container, lastContainer, blockScopeContainer, parent } = delay); - bindBlockScopedDeclaration(delay.typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + const saveCurrentFlow = currentFlow; + for (const typeAlias of delayedTypeAliases) { + const host = getJSDocHost(typeAlias); + container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; + blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; + currentFlow = { flags: FlowFlags.Start } + if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { + parent = typeAlias.parent; + bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + parent = typeAlias; + bind(typeAlias.typeExpression); + } + else { + parent = typeAlias; + forEachChild(typeAlias, n => { + // if the node has a fullName "A.B.C", that means symbol "C" was already bound + // when we visit "fullName"; so when we visit the name "C" as the next child of + // the jsDocTypedefTag, we should skip binding it. + if (n === typeAlias.name) { + // TODO: I don't think this can ever happen + return; + } + bind(n); + }); + } } container = saveContainer; lastContainer = saveLastContainer; blockScopeContainer = saveBlockScopeContainer; parent = saveParent; + currentFlow = saveCurrentFlow; } // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized @@ -1981,10 +1986,26 @@ namespace ts { } else if (!skipTransformFlagAggregation && (node.transformFlags & TransformFlags.HasComputedFlags) === 0) { subtreeTransformFlags |= computeTransformFlagsForNode(node, 0); + bindJSDoc(node); } inStrictMode = saveInStrictMode; } + function bindJSDoc(node: Node) { + if (hasJSDocNodes(node)) { + if (isInJavaScriptFile(node)) { + for (const j of node.jsDoc) { + bind(j); + } + } + else { + for (const j of node.jsDoc) { + setParentPointers(node, j); + } + } + } + } + function bindJSDocTypeAliasTagsIfAny(node: Node) { if (!hasJSDocNodes(node)) { return; @@ -1996,12 +2017,6 @@ namespace ts { } for (const tag of jsDoc.tags) { - if (isJSDocTypeAlias(tag)) { - const savedParent = parent; - parent = jsDoc; - bind(tag); - parent = savedParent; - } // Bind template tags that have a typedef or callback tag in the same comment. // The typedef/callback tag is the container of the template. const alias = getTypeAliasForJSDocTemplateTag(tag, jsDoc.tags); @@ -2240,13 +2255,8 @@ namespace ts { SymbolFlags.Property; return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: { - const { fullName } = node as JSDocTypedefTag; - if (!fullName || fullName.kind === SyntaxKind.Identifier) { - (delayedTypedefs || (delayedTypedefs = [])).push({ typeAlias: node as JSDocTypedefTag | JSDocCallbackTag, container, lastContainer, blockScopeContainer, parent }); - } - break; - } + case SyntaxKind.JSDocCallbackTag: + return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag); } } @@ -3836,6 +3846,6 @@ namespace ts { */ function setParentPointers(parent: Node, child: Node): void { child.parent = parent; - forEachChild(child, (childsChild) => setParentPointers(child, childsChild)); + forEachChild(child, grandchild => setParentPointers(child, grandchild)); } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 10abad383e2d6..c37522bb01051 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -558,14 +558,7 @@ namespace ts { // Gets the nearest enclosing block scope container that has the provided node // as a descendant, that is not the provided node. export function getEnclosingBlockScopeContainer(node: Node): Node { - let current = node.parent; - while (current) { - if (isBlockScope(current, current.parent)) { - return current; - } - - current = current.parent; - } + return findAncestor(node.parent, current => isBlockScope(current, current.parent)); } // Return display name of an identifier From f44be81b094eb9ddb89f4118336a1fae69e56bb0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 9 May 2018 15:05:52 -0700 Subject: [PATCH 43/49] Further cleanup of delayed js type alias binding --- src/compiler/binder.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b283b1284ef1e..3af0d36e211fb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1750,24 +1750,14 @@ namespace ts { container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; currentFlow = { flags: FlowFlags.Start } + parent = typeAlias; + bind(typeAlias.typeExpression); if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { parent = typeAlias.parent; bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - parent = typeAlias; - bind(typeAlias.typeExpression); } else { - parent = typeAlias; - forEachChild(typeAlias, n => { - // if the node has a fullName "A.B.C", that means symbol "C" was already bound - // when we visit "fullName"; so when we visit the name "C" as the next child of - // the jsDocTypedefTag, we should skip binding it. - if (n === typeAlias.name) { - // TODO: I don't think this can ever happen - return; - } - bind(n); - }); + bind(typeAlias.fullName); } } container = saveContainer; From e59acbc1ff54c807dd9686a3cadc5c3a63276d62 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 9 May 2018 16:29:47 -0700 Subject: [PATCH 44/49] Stop prebinding template tags too This gets rid of prebinding entirely --- src/compiler/binder.ts | 52 ++++-------------------------------------- 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3af0d36e211fb..3a65875834e70 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -626,10 +626,6 @@ namespace ts { } function bindChildrenWorker(node: Node): void { - if (isJSDoc(node)) { - // TODO: This won't be needed after getting rid of early-jsdoc-if-any binding, because we won't be skipping anything - return bindJSDocComment(node); - } if (checkUnreachable(node)) { bindEachChild(node); return; @@ -1364,15 +1360,6 @@ namespace ts { } } - function bindJSDocComment(node: JSDoc) { - forEachChild(node, n => { - // Skip type-alias-related tags, which are bound early. - if (!getTypeAliasForJSDocTemplateTag(n, node.tags)) { - bind(n); - } - }); - } - function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) { if (node.fullName) { // TODO: Could be not needed? @@ -1944,7 +1931,6 @@ namespace ts { // Here the current node is "foo", which is a container, but the scope of "MyType" should // not be inside "foo". Therefore we always bind @typedef before bind the parent node, // and skip binding this tag later when binding all the other jsdoc tags. - if (isInJavaScriptFile(node)) bindJSDocTypeAliasTagsIfAny(node); // First we bind declaration nodes to a symbol if possible. We'll both create a symbol // and then potentially add the symbol to an appropriate symbol table. Possible @@ -1996,38 +1982,6 @@ namespace ts { } } - function bindJSDocTypeAliasTagsIfAny(node: Node) { - if (!hasJSDocNodes(node)) { - return; - } - - for (const jsDoc of node.jsDoc) { - if (!jsDoc.tags) { - continue; - } - - for (const tag of jsDoc.tags) { - // Bind template tags that have a typedef or callback tag in the same comment. - // The typedef/callback tag is the container of the template. - const alias = getTypeAliasForJSDocTemplateTag(tag, jsDoc.tags); - if (alias) { - const savedContainer = container; - const savedParent = parent; - container = alias; - parent = jsDoc; - alias.locals = alias.locals || createSymbolTable(); - bind(tag); - container = savedContainer; - parent = savedParent; - } - } - } - } - - function getTypeAliasForJSDocTemplateTag(tag: Node, siblings: NodeArray) { - return isJSDocTemplateTag(tag) && find(siblings, isJSDocTypeAlias); - } - function updateStrictModeStatementList(statements: NodeArray) { if (!inStrictMode) { for (const statement of statements) { @@ -2724,9 +2678,11 @@ namespace ts { function bindTypeParameter(node: TypeParameterDeclaration) { if (isJSDocTemplateTag(node.parent)) { - const container = getTypeAliasForJSDocTemplateTag(node.parent, (node.parent.parent as JSDoc).tags) || getHostSignatureFromJSDoc(node.parent); + const container = find((node.parent.parent as JSDoc).tags, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); if (container) { - Debug.assert(!!container.locals); + if (!container.locals) { + container.locals = createSymbolTable(); + } declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } else { From 7a4ac26691423025b60e86e68e971d2d009fbb31 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 9 May 2018 16:34:50 -0700 Subject: [PATCH 45/49] Remove TODO --- src/compiler/binder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3a65875834e70..a3641b726f18f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1362,7 +1362,6 @@ namespace ts { function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) { if (node.fullName) { - // TODO: Could be not needed? setParentPointers(node, node.fullName); } } From 0e139164454e6520b0d8932641a0946b94b8d49f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 11 May 2018 13:31:17 -0700 Subject: [PATCH 46/49] Fix lint --- src/compiler/binder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a3641b726f18f..d95001b104106 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1735,7 +1735,7 @@ namespace ts { const host = getJSDocHost(typeAlias); container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; - currentFlow = { flags: FlowFlags.Start } + currentFlow = { flags: FlowFlags.Start }; parent = typeAlias; bind(typeAlias.typeExpression); if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { From 4f61e7192e7a7d4df70c161a96bdb95d318d0fd4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 16 May 2018 14:12:09 -0700 Subject: [PATCH 47/49] Finish merge with use-jsdoc-aliases --- src/compiler/checker.ts | 10 ++++--- .../jsQuickInfoGenerallyAcceptableSize.ts | 6 ++-- ...ParametershasInstantiatedSignatureHelp.tsx | 4 +-- .../fourslash/server/jsdocCallbackTag.ts | 30 +++++++++++++------ 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c2b11aab15247..021509277a3e2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4037,8 +4037,9 @@ namespace ts { function determineIfDeclarationIsVisible() { switch (node.kind) { + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocTypedefTag: - // Top-level jsdoc typedefs are considered exported + // Top-level jsdoc type aliases are considered exported // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); case SyntaxKind.BindingElement: @@ -5181,9 +5182,10 @@ namespace ts { function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] { let result: TypeParameter[]; for (const node of symbol.declarations) { - if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || - node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration || - isJSDocTypeAlias(node)) { + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.ClassDeclaration || + node.kind === SyntaxKind.ClassExpression || + isTypeAlias(node)) { const declaration = node; const typeParameters = getEffectiveTypeParameterDeclarations(declaration); if (typeParameters) { diff --git a/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts b/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts index a6fa8059481c4..8a76a47dc8adb 100644 --- a/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts +++ b/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts @@ -6,7 +6,7 @@ ////// Data table /////** //// @typedef DataTableThing -//// @type {Thing} +//// @type {Object} //// @property {function(TagCollection, Location, string, string, Infotable):void} AddDataTableEntries - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add multiple data table entries. //// @property {function(TagCollection, Location, string, string, Infotable):string} AddDataTableEntry - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add a new data table entry. //// @property {function(TagCollection, Location, string, string, Infotable):void} AddOrUpdateDataTableEntries - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add or update multiple data table entries. @@ -206,7 +206,7 @@ //// * Another thing //// * @type {SomeCallback} //// */ -////var another/*2*/Thing = function(a, b) {} +////var anotherThing/*2*/ = function(a, b) {} verify.quickInfoAt("1", "var doSomething: (dataTable: DataTableThing) => void", "Do something"); -verify.quickInfoAt("2", "var anotherThing: any", "Another thing"); // TODO: #23947 +verify.quickInfoAt("2", "var anotherThing: SomeCallback", "Another thing"); diff --git a/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx b/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx index e317fe338edb8..71859594cea04 100644 --- a/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx +++ b/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx @@ -14,6 +14,6 @@ //// (/>); goTo.marker("1"); -verify.currentSignatureHelpIs("SFC(_props: Record): string"); +verify.signatureHelp({ text: "SFC(_props: Record): string" }); goTo.marker("2"); -verify.currentSignatureHelpIs("SFC(_props: Record): string"); +verify.signatureHelp({ text: "SFC(_props: Record): string" }); diff --git a/tests/cases/fourslash/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts index b9a2fcdf3156d..700419cd6824e 100644 --- a/tests/cases/fourslash/server/jsdocCallbackTag.ts +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -26,17 +26,29 @@ //// t(/*4*/"!", /*5*/12, /*6*/false); goTo.marker("1"); -verify.quickInfoIs("var t: (eventName: string, eventName2: string | number, eventName3: any) => number"); +verify.quickInfoIs("var t: FooHandler"); goTo.marker("2"); -verify.quickInfoIs("var t2: (eventName?: string, eventName2?: string) => any"); +verify.quickInfoIs("var t2: FooHandler2"); goTo.marker("3"); verify.quickInfoIs("type FooHandler2 = (eventName?: string, eventName2?: string) => any", "- What, another one?"); goTo.marker("8"); verify.quickInfoIs("type FooHandler = (eventName: string, eventName2: string | number, eventName3: any) => number", "- A kind of magic"); -goTo.marker('4'); -verify.currentSignatureHelpIs("t(eventName: string, eventName2: string | number, eventName3: any): number"); -verify.currentParameterHelpArgumentDocCommentIs("- So many words"); -goTo.marker('5'); -verify.currentParameterHelpArgumentDocCommentIs("- Silence is golden"); -goTo.marker('6'); -verify.currentParameterHelpArgumentDocCommentIs("- Osterreich mos def"); +verify.signatureHelp({ + marker: '4', + text: "t(eventName: string, eventName2: string | number, eventName3: any): number", + parameterDocComment: "- So many words", + tags: [{ name: "callback", text: "FooHandler - A kind of magic" }, + { name: "type", text: "{FooHandler} callback" }] +}); +verify.signatureHelp({ + marker: '5', + parameterDocComment: "- Silence is golden", + tags: [{ name: "callback", text: "FooHandler - A kind of magic" }, + { name: "type", text: "{FooHandler} callback" }] +}); +verify.signatureHelp({ + marker: '6', + parameterDocComment: "- Osterreich mos def", + tags: [{ name: "callback", text: "FooHandler - A kind of magic" }, + { name: "type", text: "{FooHandler} callback" }] +}); From 75d4d9548bfcf39a07865a5cf326ba8460413b58 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 16 May 2018 14:52:53 -0700 Subject: [PATCH 48/49] Update callback tag baselines --- tests/baselines/reference/callbackTag1.types | 2 +- tests/baselines/reference/callbackTag2.types | 8 ++++---- tests/baselines/reference/callbackTag3.types | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/callbackTag1.types b/tests/baselines/reference/callbackTag1.types index 759f36640115c..2f1e64c8edcb4 100644 --- a/tests/baselines/reference/callbackTag1.types +++ b/tests/baselines/reference/callbackTag1.types @@ -9,7 +9,7 @@ var x = 1 /** @type {Sid} smallId */ var sid = s => s + "!"; ->sid : (s: string) => string +>sid : Sid >s => s + "!" : (s: string) => string >s : string >s + "!" : string diff --git a/tests/baselines/reference/callbackTag2.types b/tests/baselines/reference/callbackTag2.types index a6825f67524f3..5a13438ebbe9a 100644 --- a/tests/baselines/reference/callbackTag2.types +++ b/tests/baselines/reference/callbackTag2.types @@ -10,7 +10,7 @@ var x = 1 /** @type {Id} I actually wanted to write `const "120"` */ var one_twenty = s => "120"; ->one_twenty : (t: string) => string +>one_twenty : Id >s => "120" : (s: string) => string >s : string >"120" : "120" @@ -26,14 +26,14 @@ class SharedClass { constructor() { /** @type {SharedId} */ this.id; ->this.id : (ego: any) => any +>this.id : SharedId >this : this ->id : (ego: any) => any +>id : SharedId } } /** @type {SharedId} */ var outside = n => n + 1; ->outside : (ego: number) => number +>outside : SharedId >n => n + 1 : (n: number) => number >n : number >n + 1 : number diff --git a/tests/baselines/reference/callbackTag3.types b/tests/baselines/reference/callbackTag3.types index 4e9bfd9a0ced0..836897d0f308d 100644 --- a/tests/baselines/reference/callbackTag3.types +++ b/tests/baselines/reference/callbackTag3.types @@ -4,7 +4,7 @@ */ /** @type {Miracle} smallId */ var sid = () => "!"; ->sid : () => string +>sid : Miracle >() => "!" : () => string >"!" : "!" From 3431563870556bc589d3d1d07a73b254e08ceffe Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 17 May 2018 09:05:41 -0700 Subject: [PATCH 49/49] Rename getTypeParametersForAliasSymbol The real fix is *probably* to rename Type.aliasTypeArguments to aliasTypeParameters, but I want to make sure and then put it in a separate PR. --- src/compiler/checker.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e32e42b603c29..4c9a98951fae8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8426,7 +8426,7 @@ namespace ts { if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, - aliasSymbol, getTypeParametersForAliasSymbol(aliasSymbol)); + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } @@ -8537,7 +8537,7 @@ namespace ts { if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), - aliasSymbol, getTypeParametersForAliasSymbol(aliasSymbol)); + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } @@ -8846,7 +8846,7 @@ namespace ts { const type = createObjectType(ObjectFlags.Mapped, node.symbol); type.declaration = node; type.aliasSymbol = getAliasSymbolForTypeNode(node); - type.aliasTypeArguments = getTypeParametersForAliasSymbol(type.aliasSymbol); + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); links.resolvedType = type; // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. @@ -8955,7 +8955,7 @@ namespace ts { if (!links.resolvedType) { const checkType = getTypeFromTypeNode(node.checkType); const aliasSymbol = getAliasSymbolForTypeNode(node); - const aliasTypeArguments = getTypeParametersForAliasSymbol(aliasSymbol); + const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isPossiblyReferencedInConditionalType(tp, node)); const root: ConditionalRoot = { @@ -9072,7 +9072,7 @@ namespace ts { else { let type = createObjectType(ObjectFlags.Anonymous, node.symbol); type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = getTypeParametersForAliasSymbol(aliasSymbol); + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } @@ -9086,7 +9086,7 @@ namespace ts { return isTypeAlias(node.parent) ? getSymbolOfNode(node.parent) : undefined; } - function getTypeParametersForAliasSymbol(symbol: Symbol) { + function getTypeArgumentsForAliasSymbol(symbol: Symbol) { return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; }