diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b7852a64d04de..1ff0128794072 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -199,7 +199,7 @@ namespace ts { // other kinds of value declarations take precedence over modules symbol.valueDeclaration = node; } - } + } } // Should not be called on a declaration with a computed property name, @@ -226,6 +226,7 @@ namespace ts { return "__constructor"; case SyntaxKind.FunctionType: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocCallbackType: return "__call"; case SyntaxKind.ConstructorType: case SyntaxKind.ConstructSignature: @@ -264,6 +265,17 @@ namespace ts { let functionType = node.parent; let index = indexOf(functionType.parameters, node); return "p" + index; + case SyntaxKind.JSDocParameterTag: + // JSDocParameterTag can be used for either function parameter type notation + // or in @callback tag declaration. We only create new symbol when the JSDocParameterTag + // is part of a @callback tag declaration. + Debug.assert(node.parent.kind === SyntaxKind.JSDocCallbackType, "Should not bind JSDocParameterTag if it is not part of a JSDocCallbackTag"); + const preOrPostParameterName =(node).preParameterName || (node).postParameterName; + if (preOrPostParameterName) { + return preOrPostParameterName.text; + } + let jsDocCallbackType = node.parent; + return "p" + indexOf(jsDocCallbackType.parameterTags, node); case SyntaxKind.JSDocTypedefTag: const parentNode = node.parent && node.parent.parent; let nameFromParentNode: string; @@ -1199,6 +1211,7 @@ namespace ts { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocCallbackType: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: case SyntaxKind.FunctionType: @@ -1289,6 +1302,7 @@ namespace ts { return declareSymbol(container.symbol.members, container.symbol, node, symbolFlags, symbolExcludes); case SyntaxKind.FunctionType: + case SyntaxKind.JSDocCallbackType: case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: @@ -1403,12 +1417,12 @@ namespace ts { } } - function bindFunctionOrConstructorType(node: SignatureDeclaration): void { + function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocCallbackType): void { // For a given function symbol "<...>(...) => T" we want to generate a symbol identical // to the one we would get for: { <...>(...): T } // // We do that by making an anonymous type literal symbol, and then setting the function - // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable + // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable // from an actual type literal symbol you would have gotten had you used the long form. const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)); addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); @@ -1820,7 +1834,8 @@ namespace ts { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.JSDocFunctionType: - return bindFunctionOrConstructorType(node); + case SyntaxKind.JSDocCallbackType: + return bindFunctionOrConstructorType(node); case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocRecordType: @@ -1846,8 +1861,13 @@ namespace ts { case SyntaxKind.InterfaceDeclaration: return bindBlockScopedDeclaration(node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.TypeAliasDeclaration: return bindBlockScopedDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + case SyntaxKind.JSDocParameterTag: + return container.kind === SyntaxKind.JSDocCallbackType + ? bindJSDocParamTag(node) + : undefined; case SyntaxKind.EnumDeclaration: return bindEnumDeclaration(node); case SyntaxKind.ModuleDeclaration: @@ -2113,6 +2133,10 @@ namespace ts { } } + function bindJSDocParamTag(node: JSDocParameterTag) { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); + } + function bindParameter(node: ParameterDeclaration) { if (!isDeclarationFile(file) && !isInAmbientContext(node) && diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6427b05c37aec..3aaa00d4b65d1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2391,7 +2391,7 @@ namespace ts { } function buildParameterDisplay(p: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - const parameterNode = p.valueDeclaration; + const parameterNode = p.valueDeclaration; if (isRestParameter(parameterNode)) { writePunctuation(writer, SyntaxKind.DotDotDotToken); } @@ -3178,6 +3178,14 @@ namespace ts { type = checkExpressionCached((declaration.parent).right); } } + else if (declaration.kind == SyntaxKind.JSDocParameterTag) { + if ((declaration).typeExpression) { + const jsDocType = (declaration).typeExpression.type; + if (jsDocType) { + type = getTypeFromTypeNode(jsDocType); + } + } + } if (type === undefined) { type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); @@ -3653,18 +3661,23 @@ namespace ts { } let type: Type; - let declaration: JSDocTypedefTag | TypeAliasDeclaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag); - if (declaration) { - if (declaration.jsDocTypeLiteral) { - type = getTypeFromTypeNode(declaration.jsDocTypeLiteral); + let declaration: Declaration; + if (declaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag)) { + const jsDocTypedefTag = declaration; + if (jsDocTypedefTag.jsDocTypeLiteral) { + type = getTypeFromTypeNode(jsDocTypedefTag.jsDocTypeLiteral); } - else { - type = getTypeFromTypeNode(declaration.typeExpression.type); + else if (jsDocTypedefTag.typeExpression) { + type = getTypeFromTypeNode(jsDocTypedefTag.typeExpression.type); } } + else if (declaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocCallbackTag)) { + const jsDocCallbackTag = declaration; + type = getTypeFromTypeNode(jsDocCallbackTag.type); + } else { - declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); - type = getTypeFromTypeNode(declaration.type); + declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); + type = getTypeFromTypeNode((declaration).type); } if (popTypeResolution()) { @@ -3906,7 +3919,7 @@ namespace ts { resolveObjectTypeMembers(type, source, typeParameters, typeArguments); } - function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[], + function createSignature(declaration: SignatureDeclaration | JSDocCallbackType, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[], resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasStringLiterals: boolean): Signature { const sig = new Signature(checker); sig.declaration = declaration; @@ -4421,26 +4434,35 @@ namespace ts { return result; } + function isJSDocOptionalParameterTag(node: JSDocParameterTag) { + if (!node) { + return false; + } + + if (node.isBracketed) { + return true; + } + if (node.typeExpression && node.typeExpression.type) { + return node.typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } + return false; + } + function isJSDocOptionalParameter(node: ParameterDeclaration) { if (node.flags & NodeFlags.JavaScriptFile) { if (node.type && node.type.kind === SyntaxKind.JSDocOptionalType) { return true; } - const paramTag = getCorrespondingJSDocParameterTag(node); - if (paramTag) { - if (paramTag.isBracketed) { - return true; - } - - if (paramTag.typeExpression) { - return paramTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType; - } - } + return isJSDocOptionalParameterTag(getCorrespondingJSDocParameterTag(node)); } } - function isOptionalParameter(node: ParameterDeclaration) { + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (isJSDocParameterTag(node)) { + return isJSDocOptionalParameterTag(node); + } + if (hasQuestionToken(node) || isJSDocOptionalParameter(node)) { return true; } @@ -4474,7 +4496,7 @@ namespace ts { } } - function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature { + function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocCallbackType): Signature { const links = getNodeLinks(declaration); if (!links.resolvedSignature) { const parameters: Symbol[] = []; @@ -4487,8 +4509,9 @@ namespace ts { // If this is a JSDoc construct signature, then skip the first parameter in the // parameter list. The first parameter represents the return type of the construct // signature. - for (let i = isJSConstructSignature ? 1 : 0, n = declaration.parameters.length; i < n; i++) { - const param = declaration.parameters[i]; + const parameterNodes = isJSDocCallbackType(declaration) ? declaration.parameterTags : declaration.parameters; + for (let i = isJSConstructSignature ? 1 : 0, n = parameterNodes.length; i < n; i++) { + const param = parameterNodes[i]; let paramSymbol = param.symbol; // Include parameter symbol instead of property symbol in the signature @@ -4504,18 +4527,30 @@ namespace ts { parameters.push(paramSymbol); } - if (param.type && param.type.kind === SyntaxKind.StringLiteralType) { - hasStringLiterals = true; - } - - if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { - if (minArgumentCount < 0) { - minArgumentCount = i - (hasThisParameter ? 1 : 0); + if (isJSDocParameterTag(param)) { + if (isJSDocOptionalParameterTag(param)) { + if (minArgumentCount < 0) { + minArgumentCount = i - (hasThisParameter ? 1 : 0); + } + } + else { + minArgumentCount = -1; } } else { - // If we see any required parameters, it means the prior ones were not in fact optional. - minArgumentCount = -1; + if (param.type && param.type.kind === SyntaxKind.StringLiteralType) { + hasStringLiterals = true; + } + + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + if (minArgumentCount < 0) { + minArgumentCount = i - (hasThisParameter ? 1 : 0); + } + } + else { + // If we see any required parameters, it means the prior ones were not in fact optional. + minArgumentCount = -1; + } } } @@ -4531,7 +4566,7 @@ namespace ts { } if (minArgumentCount < 0) { - minArgumentCount = declaration.parameters.length - (hasThisParameter ? 1 : 0); + minArgumentCount = parameterNodes.length - (hasThisParameter ? 1 : 0); } if (isJSConstructSignature) { minArgumentCount--; @@ -4540,11 +4575,14 @@ namespace ts { const classType = declaration.kind === SyntaxKind.Constructor ? getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; - const typeParameters = classType ? classType.localTypeParameters : - declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : + let typeParameters: TypeParameter[]; + if (!isJSDocCallbackType(declaration)) { + typeParameters = classType ? classType.localTypeParameters : + declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : getTypeParametersFromJSDocTemplate(declaration); - const returnType = getSignatureReturnTypeFromDeclaration(declaration, minArgumentCount, isJSConstructSignature, classType); - const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? + } + const returnType = isJSDocCallbackType(declaration) ? voidType : getSignatureReturnTypeFromDeclaration(declaration, minArgumentCount, isJSConstructSignature, classType); + const typePredicate = !isJSDocCallbackType(declaration) && declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) : undefined; @@ -4603,6 +4641,7 @@ namespace ts { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocCallbackType: // 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). @@ -4612,7 +4651,7 @@ namespace ts { break; } } - result.push(getSignatureFromDeclaration(node)); + result.push(getSignatureFromDeclaration(node)); } } return result; @@ -5357,6 +5396,7 @@ namespace ts { case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: case SyntaxKind.JSDocRecordType: + case SyntaxKind.JSDocCallbackType: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); // This function assumes that an identifier or qualified name is a type expression // Callers should first ensure this by calling isTypeNode diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dc2e114977f2c..bae59cdffb6dc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -409,6 +409,11 @@ namespace ts { case SyntaxKind.JSDocPropertyTag: return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).name); + case SyntaxKind.JSDocCallbackTag: + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).type); + case SyntaxKind.JSDocCallbackType: + return visitNodes(cbNodes, (node).parameterTags); } } @@ -6207,6 +6212,8 @@ namespace ts { return handleTypeTag(atToken, tagName); case "typedef": return handleTypedefTag(atToken, tagName); + case "callback": + return handleCallbackTag(atToken, tagName); } } @@ -6341,13 +6348,15 @@ namespace ts { typedefTag.name = parseJSDocIdentifierName(); typedefTag.typeExpression = typeExpression; + const jsDocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, scanner.getStartPos()); if (typeExpression) { if (typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { const jsDocTypeReference = typeExpression.type; if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) { const name = jsDocTypeReference.name; if (name.text === "Object") { - typedefTag.jsDocTypeLiteral = scanChildTags(); + scanChildTags(tryParseChildTag, jsDocTypeLiteral); + typedefTag.jsDocTypeLiteral = finishNode(jsDocTypeLiteral); } } } @@ -6356,78 +6365,111 @@ namespace ts { } } else { - typedefTag.jsDocTypeLiteral = scanChildTags(); + scanChildTags(tryParseChildTag, jsDocTypeLiteral); + typedefTag.jsDocTypeLiteral = finishNode(jsDocTypeLiteral); } return finishNode(typedefTag); - function scanChildTags(): JSDocTypeLiteral { - const jsDocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, scanner.getStartPos()); - let resumePos = scanner.getStartPos(); - let canParseTag = true; - let seenAsterisk = false; - let parentTagTerminated = false; + function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean { + Debug.assert(token === SyntaxKind.AtToken); + const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); + atToken.end = scanner.getTextPos(); + nextJSDocToken(); - while (token !== SyntaxKind.EndOfFileToken && !parentTagTerminated) { - nextJSDocToken(); - switch (token) { - case SyntaxKind.AtToken: - if (canParseTag) { - parentTagTerminated = !tryParseChildTag(jsDocTypeLiteral); - } - seenAsterisk = false; - break; - case SyntaxKind.NewLineTrivia: - resumePos = scanner.getStartPos() - 1; - canParseTag = true; - seenAsterisk = false; - break; - case SyntaxKind.AsteriskToken: - if (seenAsterisk) { - canParseTag = false; - } - seenAsterisk = true; - break; - case SyntaxKind.Identifier: - canParseTag = false; - case SyntaxKind.EndOfFileToken: - break; - } + const tagName = parseJSDocIdentifierName(); + if (!tagName) { + return false; } - scanner.setTextPos(resumePos); - return finishNode(jsDocTypeLiteral); + + switch (tagName.text) { + case "type": + if (parentTag.jsDocTypeTag) { + // already has a @type tag, terminate the parent tag now. + return false; + } + parentTag.jsDocTypeTag = handleTypeTag(atToken, tagName); + return true; + case "prop": + case "property": + if (!parentTag.jsDocPropertyTags) { + parentTag.jsDocPropertyTags = >[]; + } + const propertyTag = handlePropertyTag(atToken, tagName); + parentTag.jsDocPropertyTags.push(propertyTag); + return true; + } + return false; } } - function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean { - Debug.assert(token === SyntaxKind.AtToken); - const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); - atToken.end = scanner.getTextPos(); - nextJSDocToken(); + function handleCallbackTag(atToken: Node, tagName: Identifier): JSDocCallbackTag { + const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos); + callbackTag.atToken = atToken; + callbackTag.tagName = tagName; + callbackTag.name = parseJSDocIdentifierName(); + const callbackTypeNode = createNode(SyntaxKind.JSDocCallbackType, scanner.getStartPos()); + scanChildTags(tryParseChildTag, callbackTypeNode); + callbackTag.type = finishNode(callbackTypeNode); + return finishNode(callbackTag); + + function tryParseChildTag(parentTag: JSDocCallbackType) { + Debug.assert(token === SyntaxKind.AtToken); + const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); + atToken.end = scanner.getTextPos(); + nextJSDocToken(); - const tagName = parseJSDocIdentifierName(); - if (!tagName) { + const tagName = parseJSDocIdentifierName(); + if (!tagName) { + return false; + } + + switch (tagName.text) { + case "param": + (parentTag.parameterTags || (parentTag.parameterTags = >[])).push(handleParamTag(atToken, tagName)); + return true; + } return false; } + } - switch (tagName.text) { - case "type": - if (parentTag.jsDocTypeTag) { - // already has a @type tag, terminate the parent tag now. - return false; - } - parentTag.jsDocTypeTag = handleTypeTag(atToken, tagName); - return true; - case "prop": - case "property": - if (!parentTag.jsDocPropertyTags) { - parentTag.jsDocPropertyTags = >[]; - } - const propertyTag = handlePropertyTag(atToken, tagName); - parentTag.jsDocPropertyTags.push(propertyTag); - return true; + function scanChildTags(tryParseChildTag: (parentTag: T) => boolean, parentTag: T): T { + let resumePos = scanner.getStartPos(); + let canParseTag = true; + let seenAsterisk = false; + let parentTagTerminated = false; + + while (token !== SyntaxKind.EndOfFileToken && !parentTagTerminated) { + nextJSDocToken(); + switch (token) { + case SyntaxKind.AtToken: + if (canParseTag) { + parentTagTerminated = !tryParseChildTag(parentTag); + if (!parentTagTerminated) { + resumePos = scanner.getStartPos(); + } + } + seenAsterisk = false; + break; + case SyntaxKind.NewLineTrivia: + resumePos = scanner.getStartPos() - 1; + canParseTag = true; + seenAsterisk = false; + break; + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + canParseTag = false; + } + seenAsterisk = true; + break; + case SyntaxKind.Identifier: + canParseTag = false; + case SyntaxKind.EndOfFileToken: + break; + } } - return false; + scanner.setTextPos(resumePos); + return; } function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3a3a937ade77b..c21dc79f491be 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -337,6 +337,7 @@ namespace ts { JSDocVariadicType, JSDocConstructorType, JSDocThisType, + JSDocCallbackType, JSDocComment, JSDocTag, JSDocParameterTag, @@ -344,6 +345,7 @@ namespace ts { JSDocTypeTag, JSDocTemplateTag, JSDocTypedefTag, + JSDocCallbackTag, JSDocPropertyTag, JSDocTypeLiteral, @@ -1528,6 +1530,15 @@ namespace ts { jsDocTypeLiteral?: JSDocTypeLiteral; } + export interface JSDocCallbackTag extends JSDocTag, Declaration { + name: Identifier; + type: JSDocCallbackType; + } + + export interface JSDocCallbackType extends JSDocType, Declaration { + parameterTags?: NodeArray; + } + // @kind(SyntaxKind.JSDocPropertyTag) export interface JSDocPropertyTag extends JSDocTag, TypeElement { name: Identifier; @@ -1541,7 +1552,7 @@ namespace ts { } // @kind(SyntaxKind.JSDocParameterTag) - export interface JSDocParameterTag extends JSDocTag { + export interface JSDocParameterTag extends JSDocTag, Declaration { preParameterName?: Identifier; typeExpression?: JSDocTypeExpression; postParameterName?: Identifier; @@ -2397,7 +2408,7 @@ namespace ts { } export interface Signature { - declaration: SignatureDeclaration; // Originating declaration + declaration: SignatureDeclaration | JSDocCallbackType; // 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 6f3a1d6d813d4..de8d440e31500 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1463,26 +1463,50 @@ namespace ts { return undefined; } - export function hasRestParameter(s: SignatureDeclaration): boolean { - return isRestParameter(lastOrUndefined(s.parameters)); + export function hasRestParameter(s: SignatureDeclaration | JSDocCallbackType): boolean { + if (isJSDocCallbackType(s)) { + return isRestParameter(lastOrUndefined(s.parameterTags)); + } + else { + return isRestParameter(lastOrUndefined(s.parameters)); + } + } + + export function isJSDocCallbackType(node: Node): node is JSDocCallbackType { + return node && node.kind === SyntaxKind.JSDocCallbackType; + } + + export function isJSDocParameterTag(node: Node): node is JSDocParameterTag { + return node && node.kind === SyntaxKind.JSDocParameterTag; } export function hasDeclaredRestParameter(s: SignatureDeclaration): boolean { return isDeclaredRestParam(lastOrUndefined(s.parameters)); } - export function isRestParameter(node: ParameterDeclaration) { - if (node && (node.flags & NodeFlags.JavaScriptFile)) { - if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType) { - return true; + export function isRestParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (isJSDocParameterTag(node)) { + if (node.typeExpression && node.typeExpression.type) { + return node.typeExpression.type.kind === SyntaxKind.JSDocVariadicType; } + } + else { + if (node && (node.flags & NodeFlags.JavaScriptFile)) { + if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType) { + return true; + } - const paramTag = getCorrespondingJSDocParameterTag(node); - if (paramTag && paramTag.typeExpression) { - return paramTag.typeExpression.type.kind === SyntaxKind.JSDocVariadicType; + // This is different kind of jsDoc parameter tag. Here is the address the case when + // the parameter declaration is a normal one (in the code), while the type of the + // parameter may be found in the jsdoc comment. + const paramTag = getCorrespondingJSDocParameterTag(node); + if (paramTag && paramTag.typeExpression) { + return paramTag.typeExpression.type.kind === SyntaxKind.JSDocVariadicType; + } } + return isDeclaredRestParam(node); } - return isDeclaredRestParam(node); + return false; } export function isDeclaredRestParam(node: ParameterDeclaration) { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 022c538e76278..50ec031674cea 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -221,7 +221,7 @@ namespace ts.NavigationBar { if (node.jsDocComments) { for (const jsDocComment of node.jsDocComments) { for (const tag of jsDocComment.tags) { - if (tag.kind === SyntaxKind.JSDocTypedefTag) { + if (tag.kind === SyntaxKind.JSDocTypedefTag || tag.kind === SyntaxKind.JSDocCallbackTag) { addLeafNode(tag); } } @@ -460,6 +460,7 @@ namespace ts.NavigationBar { case SyntaxKind.SourceFile: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: return true; case SyntaxKind.Constructor: @@ -583,6 +584,7 @@ namespace ts.NavigationBar { return ts.ScriptElementKind.functionElement; case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: return ScriptElementKind.typeElement; default: diff --git a/tests/cases/fourslash/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts new file mode 100644 index 0000000000000..7118aeadceb79 --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -0,0 +1,31 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsdocCallbackTag.js + +//// /** +//// * @callback FooHandler +//// * @param {string} eventName +//// * @param eventName2 {number | string} +//// * @param eventName3 +//// */ +//// /** +//// * @type {FooHandler} callback +//// */ +//// var t/*1*/; +//// +//// /** +//// * @callback FooHandler2 +//// * @param {string=} eventName +//// * @param {string} [eventName2] +//// */ +//// /** +//// * @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"); \ No newline at end of file diff --git a/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts new file mode 100644 index 0000000000000..8d8e0ac1c8bb9 --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts @@ -0,0 +1,33 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsDocCallback.js + +//// /** +//// * @callback FooCallback +//// * @param {string} eventName +//// */ +//// /** @type {FooCallback} */ +//// var t; + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "FooCallback", + "kind": "type" + }, + { + "text": "t", + "kind": "var" + } + ] + }, + { + "text": "FooCallback", + "kind": "type", + "indent": 1 + } +]); \ No newline at end of file diff --git a/tests/cases/fourslash/server/jsdocCallbackTagRename01.ts b/tests/cases/fourslash/server/jsdocCallbackTagRename01.ts new file mode 100644 index 0000000000000..4d68c502dad17 --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTagRename01.ts @@ -0,0 +1,15 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsDocCallback.js +//// +//// /** +//// * @callback [|FooCallback|] +//// * @param {string} eventName +//// */ +//// +//// /** @type {/*1*/[|FooCallback|]} */ +//// var t; + +goTo.marker('1'); +verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); \ No newline at end of file