diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d1c782d6155fa..d95001b104106 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 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; @@ -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: @@ -301,9 +302,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; } } @@ -456,8 +454,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. - 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! } @@ -628,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; @@ -709,11 +691,9 @@ namespace ts { case SyntaxKind.CallExpression: bindCallExpressionFlow(node); break; - case SyntaxKind.JSDocComment: - 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: @@ -728,6 +708,7 @@ namespace ts { bindEachChild(node); break; } + bindJSDoc(node); } function isNarrowingExpression(expr: Expression): boolean { @@ -1379,24 +1360,10 @@ namespace ts { } } - function bindJSDocComment(node: JSDoc) { - forEachChild(node, n => { - if (n.kind !== SyntaxKind.JSDocTypedefTag) { - bind(n); - } - }); - } - - function bindJSDocTypedefTag(node: JSDocTypedefTag) { - 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); - }); + function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) { + if (node.fullName) { + setParentPointers(node, node.fullName); + } } function bindCallExpressionFlow(node: CallExpression) { @@ -1456,6 +1423,7 @@ namespace ts { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.JSDocFunctionType: case SyntaxKind.FunctionType: case SyntaxKind.ConstructSignature: @@ -1545,6 +1513,7 @@ namespace ts { case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.IndexSignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: @@ -1555,6 +1524,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 @@ -1650,7 +1621,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 } // @@ -1752,21 +1723,34 @@ 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.typedef, 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 }; + parent = typeAlias; + bind(typeAlias.typeExpression); + if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { + parent = typeAlias.parent; + bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + } + else { + bind(typeAlias.fullName); + } } 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 @@ -1946,7 +1930,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)) bindJSDocTypedefTagIfAny(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 @@ -1978,26 +1961,21 @@ namespace ts { } else if (!skipTransformFlagAggregation && (node.transformFlags & TransformFlags.HasComputedFlags) === 0) { subtreeTransformFlags |= computeTransformFlagsForNode(node, 0); + bindJSDoc(node); } inStrictMode = saveInStrictMode; } - function bindJSDocTypedefTagIfAny(node: Node) { - if (!hasJSDocNodes(node)) { - return; - } - - for (const jsDoc of node.jsDoc) { - if (!jsDoc.tags) { - continue; + function bindJSDoc(node: Node) { + if (hasJSDocNodes(node)) { + if (isInJavaScriptFile(node)) { + for (const j of node.jsDoc) { + bind(j); + } } - - for (const tag of jsDoc.tags) { - if (tag.kind === SyntaxKind.JSDocTypedefTag) { - const savedParent = parent; - parent = jsDoc; - bind(tag); - parent = savedParent; + else { + for (const j of node.jsDoc) { + setParentPointers(node, j); } } } @@ -2036,10 +2014,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 @@ -2141,8 +2119,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: @@ -2205,6 +2184,9 @@ namespace ts { return updateStrictModeStatementList((node).statements); case SyntaxKind.JSDocParameterTag: + if (node.parent.kind === SyntaxKind.JSDocSignature) { + return bindParameter(node as JSDocParameterTag); + } if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { break; } @@ -2215,13 +2197,9 @@ namespace ts { SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property; return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypedefTag: { - const { fullName } = node as JSDocTypedefTag; - if (!fullName || fullName.kind === SyntaxKind.Identifier) { - (delayedTypedefs || (delayedTypedefs = [])).push({ typedef: node as JSDocTypedefTag, container, lastContainer, blockScopeContainer, parent }); - } - break; - } + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag); } } @@ -2622,7 +2600,10 @@ namespace ts { } } - function bindParameter(node: ParameterDeclaration) { + 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) @@ -2630,7 +2611,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); @@ -2690,18 +2671,24 @@ 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.kind === SyntaxKind.InferType) { + if (isJSDocTemplateTag(node.parent)) { + const container = find((node.parent.parent as JSDoc).tags, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent); + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); + } + 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) { @@ -3804,6 +3791,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/checker.ts b/src/compiler/checker.ts index d0780406f1186..4c9a98951fae8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1577,9 +1577,11 @@ 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) { - 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)); + } } } @@ -2089,7 +2091,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; @@ -2144,25 +2146,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) { - if (isJSDocTypeReference(name.parent) && isJSDocTag(name.parent.parent.parent)) { - const host = getJSDocHost(name.parent.parent.parent as JSDocTag); - if (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); - } + function resolveEntityNameFromJSSpecialAssignment(name: Identifier, meaning: SymbolFlags) { + if (isJSDocTypeReference(name.parent)) { + const host = getJSDocHost(name.parent); + 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); } @@ -3606,8 +3617,10 @@ namespace ts { } function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { - const parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); - Debug.assert(!!parameterDeclaration || isTransientSymbol(parameterSymbol)); + let parameterDeclaration: ParameterDeclaration | JSDocParameterTag = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { + parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + } let parameterType = getTypeOfSymbol(parameterSymbol); if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { @@ -3620,8 +3633,8 @@ namespace ts { const dotDotDotToken = isRest ? 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); @@ -4026,8 +4039,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: @@ -4802,7 +4816,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; } @@ -4837,7 +4851,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)) @@ -5139,6 +5153,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); @@ -5168,9 +5184,11 @@ 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 || node.kind === SyntaxKind.JSDocTypedefTag) { - const declaration = 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) { result = appendTypeParameters(result, typeParameters); @@ -5466,9 +5484,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 = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType; @@ -6036,7 +6054,7 @@ namespace ts { } function createSignature( - declaration: SignatureDeclaration, + declaration: SignatureDeclaration | JSDocSignature, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[], @@ -7046,8 +7064,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; } @@ -7067,6 +7085,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); @@ -7142,7 +7168,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[] = []; @@ -7165,6 +7191,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); @@ -7178,13 +7205,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 || + const isOptionalParameter = isOptionalJSDocParameterTag(param) || + param.initializer || param.questionToken || param.dotDotDotToken || + iife && parameters.length > iife.arguments.length && !type || isUntypedSignatureInJSFile || isJSDocOptionalParameter(param); if (!isOptionalParameter) { @@ -7220,8 +7248,8 @@ namespace ts { * 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); @@ -7240,9 +7268,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; @@ -7656,11 +7684,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) { @@ -7675,7 +7703,7 @@ namespace ts { return createTypeReference(type, typeArguments); } return checkNoTypeArguments(node, symbol) ? type : unknownType; - } + } function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[]): Type { const type = getDeclaredTypeOfSymbol(symbol); @@ -7702,18 +7730,18 @@ namespace ts { 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) { @@ -7828,7 +7856,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); @@ -8396,8 +8424,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, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } @@ -8506,8 +8535,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, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } @@ -8816,7 +8846,7 @@ namespace ts { const type = createObjectType(ObjectFlags.Mapped, node.symbol); type.declaration = node; type.aliasSymbol = getAliasSymbolForTypeNode(node); - type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); + 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. @@ -8924,7 +8954,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 = getTypeArgumentsForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isPossiblyReferencedInConditionalType(tp, node)); const root: ConditionalRoot = { @@ -8937,7 +8968,7 @@ namespace ts { inferTypeParameters: getInferTypeParameters(node), outerTypeParameters, instantiations: undefined, - aliasSymbol: getAliasSymbolForTypeNode(node), + aliasSymbol, aliasTypeArguments }; links.resolvedType = getConditionalType(root, /*mapper*/ undefined); @@ -9041,7 +9072,7 @@ namespace ts { else { let type = createObjectType(ObjectFlags.Anonymous, node.symbol); type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } @@ -9052,11 +9083,10 @@ namespace ts { } function getAliasSymbolForTypeNode(node: TypeNode) { - return (node.parent.kind === SyntaxKind.TypeAliasDeclaration || node.parent.kind === SyntaxKind.JSDocTypedefTag) ? getSymbolOfNode(node.parent) : undefined; + return isTypeAlias(node.parent) ? getSymbolOfNode(node.parent) : undefined; } - function getAliasTypeArgumentsForTypeNode(node: TypeNode) { - const symbol = getAliasSymbolForTypeNode(node); + function getTypeArgumentsForAliasSymbol(symbol: Symbol) { return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; } @@ -9314,6 +9344,7 @@ namespace ts { case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); case SyntaxKind.TypeOperator: return getTypeFromTypeOperatorNode(node); @@ -9991,8 +10022,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 { @@ -21761,8 +21792,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 @@ -22297,7 +22329,7 @@ namespace ts { } } - function checkJSDocTypedefTag(node: JSDocTypedefTag) { + function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { 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); @@ -25212,7 +25244,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: @@ -26423,9 +26456,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); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index bbd4edc8507fd..0b9a0142496aa 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).typeExpression); + 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) { @@ -6331,8 +6340,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 { @@ -6386,7 +6396,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 */` @@ -6503,8 +6513,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); @@ -6516,7 +6525,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, indent); break; default: tag = parseUnknownTag(atToken, tagName); @@ -6531,8 +6543,11 @@ namespace ts { // a badly malformed tag should not be added to the list of tags return; } - tag.comment = parseTagComments(indent + tag.end - tag.pos); - addTag(tag); + if (!tag.comment) { + // some tags, like typedef and callback, have already parsed their comments earlier + tag.comment = parseTagComments(indent + tag.end - tag.pos); + } + return tag; } function parseTagComments(indent: number): string | undefined { @@ -6605,6 +6620,9 @@ namespace ts { } function addTag(tag: JSDocTag): void { + if (!tag) { + return; + } if (!tags) { tags = [tag]; tagsPos = tag.pos; @@ -6665,9 +6683,9 @@ 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); const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name, target); @@ -6771,27 +6789,17 @@ 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(); 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.comment = parseTagComments(indent); typedefTag.typeExpression = typeExpression; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { @@ -6826,23 +6834,69 @@ namespace ts { } return finishNode(typedefTag); + } - function parseJSDocTypeNameWithNamespace(flags: NodeFlags) { - const pos = scanner.getTokenPos(); - const typeNameOrNamespaceName = parseJSDocIdentifierName(); + 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; - jsDocNamespaceNode.name = typeNameOrNamespaceName; - jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace); - return finishNode(jsDocNamespaceNode); + 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; + } - if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) { - typeNameOrNamespaceName.isInJSDocNamespace = true; + 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(); + 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); + } + const returnTag = tryParse(() => { + if (token() === SyntaxKind.AtToken) { + nextJSDocToken(); + const tag = parseTag(indent); + if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { + return tag as JSDocReturnTag; + } + } + }); + 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; } - return typeNameOrNamespaceName; } } @@ -6872,6 +6926,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; } @@ -6920,12 +6975,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 f2a6f001a21a6..7892b11eaea0e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -413,9 +413,11 @@ namespace ts { JSDocVariadicType, JSDocComment, JSDocTypeLiteral, + JSDocSignature, JSDocTag, JSDocAugmentsTag, JSDocClassTag, + JSDocCallbackTag, JSDocParameterTag, JSDocReturnTag, JSDocTypeTag, @@ -2053,7 +2055,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 | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature; export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; @@ -2387,6 +2389,21 @@ namespace ts { typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; } + export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration { + parent: JSDoc; + kind: SyntaxKind.JSDocCallbackTag; + fullName?: JSDocNamespaceDeclaration | Identifier; + name?: Identifier; + typeExpression: JSDocSignature; + } + + export interface JSDocSignature extends JSDocType, Declaration { + kind: SyntaxKind.JSDocSignature; + typeParameters?: ReadonlyArray; + parameters: ReadonlyArray; + type: JSDocReturnTag | undefined; + } + export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; name: EntityName; @@ -4032,7 +4049,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 e10d40a70ba16..7339907547a57 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -520,6 +520,9 @@ namespace ts { case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocSignature: return true; default: assertTypeIsNever(node); @@ -561,14 +564,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 @@ -1803,6 +1799,14 @@ 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; + } + + 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) && @@ -1826,6 +1830,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 +1913,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) || @@ -1918,18 +1924,12 @@ namespace ts { return decl && isFunctionLike(decl) ? decl : undefined; } - export function getJSDocHost(node: JSDocTag): HasJSDoc { - while (node.parent.kind === SyntaxKind.JSDocTypeLiteral) { - if (node.parent.parent.kind === SyntaxKind.JSDocTypedefTag) { - 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 { @@ -1938,13 +1938,14 @@ 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 { + 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 { @@ -2998,8 +2999,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 { + // callback tags do not currently support this parameters + if (signature.parameters.length && !isJSDocSignature(signature)) { const thisParameter = signature.parameters[0]; if (parameterIsThisKeyword(thisParameter)) { return thisParameter; @@ -3085,7 +3087,10 @@ 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 { + export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined { + if (isJSDocSignature(node)) { + return node.type && node.type.typeExpression && node.type.typeExpression.type; + } return node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined); } @@ -3093,15 +3098,30 @@ 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 | JSDocTypedefTag) { - return isJSDocTypedefTag(node) - ? getJSDocTypeParameterDeclarations(node) - : node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); + 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; + 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); } - export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters | JSDocTypedefTag) { - const templateTag = getJSDocTemplateTag(node); - return templateTag && templateTag.typeParameters; + export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) { + 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; + } + } } /** @@ -4638,6 +4658,8 @@ namespace ts { return undefined; } } + case SyntaxKind.JSDocCallbackTag: + return (declaration as JSDocCallbackTag).name; case SyntaxKind.JSDocTypedefTag: return getNameOfJSDocTypedef(declaration as JSDocTypedefTag); case SyntaxKind.ExportAssignment: { @@ -5475,6 +5497,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 @@ -5636,6 +5666,7 @@ namespace ts { switch (kind) { case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: case SyntaxKind.FunctionType: @@ -6104,6 +6135,7 @@ namespace ts { || kind === SyntaxKind.TypeParameter || kind === SyntaxKind.VariableDeclaration || kind === SyntaxKind.JSDocTypedefTag + || kind === SyntaxKind.JSDocCallbackTag || kind === SyntaxKind.JSDocPropertyTag; } @@ -6254,7 +6286,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/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 5b411bedde421..5d7e019c28da0 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -93,7 +93,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; } @@ -152,7 +152,7 @@ namespace ts.codefix { break; case SyntaxKind.TypeParameter: - const typeParameters = (parent.parent).typeParameters; + 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); @@ -315,4 +315,4 @@ namespace ts.codefix { } } } -} \ No newline at end of file +} diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index ca0dafbc74cb4..c8958ad3d2f57 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); } @@ -98,6 +101,7 @@ namespace ts.JsDoc { case SyntaxKind.JSDocTypeTag: return withNode((tag as JSDocTypeTag).typeExpression); case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocPropertyTag: case SyntaxKind.JSDocParameterTag: const { name } = tag as JSDocTypedefTag | JSDocPropertyTag | JSDocParameterTag; 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/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 354c200217943..4c5d7ff6f0961 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) && 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) && - node.typeParameters !== undefined && - node.typeParameters.length > 0; + getEffectiveTypeParameterDeclarations(node) && + getEffectiveTypeParameterDeclarations(node).length > 0; } function isInGenericContext(node: Node) { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 430dc2196e0f1..399b7184e626e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -276,7 +276,7 @@ namespace ts { } export function getContainerNode(node: Node): Declaration { - 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. @@ -315,7 +315,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); @@ -346,8 +349,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/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/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 67bd6cbe9801b..1cf0b12522b86 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -349,22 +349,24 @@ declare namespace ts { JSDocVariadicType = 284, JSDocComment = 285, JSDocTypeLiteral = 286, - JSDocTag = 287, - JSDocAugmentsTag = 288, - JSDocClassTag = 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, + JSDocSignature = 287, + JSDocTag = 288, + JSDocAugmentsTag = 289, + JSDocClassTag = 290, + JSDocCallbackTag = 291, + JSDocParameterTag = 292, + JSDocReturnTag = 293, + JSDocTypeTag = 294, + JSDocTemplateTag = 295, + JSDocTypedefTag = 296, + JSDocPropertyTag = 297, + SyntaxList = 298, + NotEmittedStatement = 299, + PartiallyEmittedExpression = 300, + CommaListExpression = 301, + MergeDeclarationMarker = 302, + EndOfDeclarationMarker = 303, + Count = 304, FirstAssignment = 58, LastAssignment = 70, FirstCompoundAssignment = 59, @@ -391,9 +393,9 @@ declare namespace ts { LastBinaryOperator = 70, FirstNode = 145, FirstJSDocNode = 277, - LastJSDocNode = 295, - FirstJSDocTagNode = 287, - LastJSDocTagNode = 295 + LastJSDocNode = 297, + FirstJSDocTagNode = 288, + LastJSDocTagNode = 297 } enum NodeFlags { None = 0, @@ -1280,7 +1282,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 | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; @@ -1537,6 +1539,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; @@ -2283,7 +2298,7 @@ declare namespace ts { Construct = 1 } interface Signature { - declaration?: SignatureDeclaration; + declaration?: SignatureDeclaration | JSDocSignature; typeParameters?: TypeParameter[]; parameters: Symbol[]; } @@ -3362,6 +3377,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 ef435632a3667..35669b096e39b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -349,22 +349,24 @@ declare namespace ts { JSDocVariadicType = 284, JSDocComment = 285, JSDocTypeLiteral = 286, - JSDocTag = 287, - JSDocAugmentsTag = 288, - JSDocClassTag = 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, + JSDocSignature = 287, + JSDocTag = 288, + JSDocAugmentsTag = 289, + JSDocClassTag = 290, + JSDocCallbackTag = 291, + JSDocParameterTag = 292, + JSDocReturnTag = 293, + JSDocTypeTag = 294, + JSDocTemplateTag = 295, + JSDocTypedefTag = 296, + JSDocPropertyTag = 297, + SyntaxList = 298, + NotEmittedStatement = 299, + PartiallyEmittedExpression = 300, + CommaListExpression = 301, + MergeDeclarationMarker = 302, + EndOfDeclarationMarker = 303, + Count = 304, FirstAssignment = 58, LastAssignment = 70, FirstCompoundAssignment = 59, @@ -391,9 +393,9 @@ declare namespace ts { LastBinaryOperator = 70, FirstNode = 145, FirstJSDocNode = 277, - LastJSDocNode = 295, - FirstJSDocTagNode = 287, - LastJSDocTagNode = 295 + LastJSDocNode = 297, + FirstJSDocTagNode = 288, + LastJSDocTagNode = 297 } enum NodeFlags { None = 0, @@ -1280,7 +1282,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 | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; name?: Identifier; @@ -1537,6 +1539,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; @@ -2283,7 +2298,7 @@ declare namespace ts { Construct = 1 } interface Signature { - declaration?: SignatureDeclaration; + declaration?: SignatureDeclaration | JSDocSignature; typeParameters?: TypeParameter[]; parameters: Symbol[]; } @@ -3362,6 +3377,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/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 new file mode 100644 index 0000000000000..b52ac8f92c8ab --- /dev/null +++ b/tests/baselines/reference/callbackTag1.symbols @@ -0,0 +1,28 @@ +=== tests/cases/conformance/jsdoc/cb.js === +/** @callback Sid + * @param {string} s + * @returns {string} What were you expecting + */ +var x = 1 +>x : Symbol(x, Decl(cb.js, 4, 3)) + +/** @type {Sid} smallId */ +var sid = s => s + "!"; +>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, 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 + * @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 new file mode 100644 index 0000000000000..2f1e64c8edcb4 --- /dev/null +++ b/tests/baselines/reference/callbackTag1.types @@ -0,0 +1,34 @@ +=== tests/cases/conformance/jsdoc/cb.js === +/** @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 : Sid +>s => s + "!" : (s: string) => string +>s : string +>s + "!" : string +>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 + */ + 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..5a13438ebbe9a --- /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 : Id +>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 : SharedId +>this : this +>id : SharedId + } +} +/** @type {SharedId} */ +var outside = n => n + 1; +>outside : SharedId +>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/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..836897d0f308d --- /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 : Miracle +>() => "!" : () => string +>"!" : "!" + + 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/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/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 787d9ab106bb1..14407a5a0c9d4 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/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/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 new file mode 100644 index 0000000000000..63a90e74ff88d --- /dev/null +++ b/tests/cases/conformance/jsdoc/callbackTag1.ts @@ -0,0 +1,22 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: cb.js + +/** @callback Sid + * @param {string} s + * @returns {string} What were you expecting + */ +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 + */ diff --git a/tests/cases/conformance/jsdoc/callbackTag2.ts b/tests/cases/conformance/jsdoc/callbackTag2.ts new file mode 100644 index 0000000000000..34e71b55bcd71 --- /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 - "Prince Noctis Lucius Caelum" + * @return {"cecil" | "zidane"} + */ 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 = () => "!"; + 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' +} 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 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} */ 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; + }, +}; 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/server/jsdocCallbackTag.ts b/tests/cases/fourslash/server/jsdocCallbackTag.ts new file mode 100644 index 0000000000000..700419cd6824e --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCallbackTag.ts @@ -0,0 +1,54 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsdocCallbackTag.js +//// /** +//// * @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 {FooHa/*8*/ndler} callback +//// */ +//// var t/*1*/; +//// +//// /** +//// * @callback FooHandler2 - What, another one? +//// * @param {string=} eventName - it keeps happening +//// * @param {string} [eventName2] - i WARNED you dog +//// */ +//// /** +//// * @type {FooH/*3*/andler2} callback +//// */ +//// var t2/*2*/; +//// t(/*4*/"!", /*5*/12, /*6*/false); + +goTo.marker("1"); +verify.quickInfoIs("var t: FooHandler"); +goTo.marker("2"); +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"); +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" }] +}); diff --git a/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocCallbackTagNavigateTo.ts new file mode 100644 index 0000000000000..2beaf99ed7073 --- /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": "script", + "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 }); 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");