diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 47b978b0f80..2fbd0c3e141 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1504,9 +1504,9 @@ namespace ts { return declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes); case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JsxAttributes: // Interface/Object-types always have their children added to the 'members' of // their container. They are only accessible through an instance of their @@ -2103,8 +2103,9 @@ namespace ts { case SyntaxKind.ConstructorType: return bindFunctionOrConstructorType(node); case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.MappedType: - return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode); + return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); case SyntaxKind.ObjectLiteralExpression: return bindObjectLiteralExpression(node); case SyntaxKind.FunctionExpression: @@ -2162,13 +2163,17 @@ namespace ts { case SyntaxKind.ModuleBlock: return updateStrictModeStatementList((node).statements); + case SyntaxKind.JSDocParameterTag: + if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { + break; + } + // falls through case SyntaxKind.JSDocPropertyTag: - return declareSymbolAndAddToSymbolTable(node as JSDocPropertyTag, - (node as JSDocPropertyTag).isBracketed || ((node as JSDocPropertyTag).typeExpression && (node as JSDocPropertyTag).typeExpression.type.kind === SyntaxKind.JSDocOptionalType) ? - SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property, - SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypeLiteral: - return bindAnonymousTypeWorker(node as JSDocTypeLiteral); + const propTag = node as JSDocPropertyLikeTag; + const flags = propTag.isBracketed || propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? + 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) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ff45ee0b73..9327a7c6f3b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4493,8 +4493,8 @@ namespace ts { if (declaration.kind === SyntaxKind.ExportAssignment) { return links.type = checkExpression((declaration).expression); } - if (isInJavaScriptFile(declaration) && declaration.kind === SyntaxKind.JSDocPropertyTag && (declaration).typeExpression) { - return links.type = getTypeFromTypeNode((declaration).typeExpression.type); + if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.typeExpression) { + return links.type = getTypeFromTypeNode(declaration.typeExpression.type); } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { @@ -5074,20 +5074,9 @@ namespace ts { return unknownType; } - let declaration: JSDocTypedefTag | TypeAliasDeclaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag); - let type: Type; - if (declaration) { - if (declaration.jsDocTypeLiteral) { - type = getTypeFromTypeNode(declaration.jsDocTypeLiteral); - } - else { - type = getTypeFromTypeNode(declaration.typeExpression.type); - } - } - else { - declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); - type = getTypeFromTypeNode(declaration.type); - } + const declaration = findDeclaration( + symbol, d => d.kind === SyntaxKind.JSDocTypedefTag || d.kind === SyntaxKind.TypeAliasDeclaration); + let type = getTypeFromTypeNode(declaration.kind === SyntaxKind.JSDocTypedefTag ? declaration.typeExpression : declaration.type); if (popTypeResolution()) { const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); @@ -7662,9 +7651,12 @@ namespace ts { links.resolvedType = emptyTypeLiteralType; } else { - const type = createObjectType(ObjectFlags.Anonymous, node.symbol); + let type = createObjectType(ObjectFlags.Anonymous, node.symbol); type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); + if (isJSDocTypeLiteral(node) && node.isArrayType) { + type = createArrayType(type); + } links.resolvedType = type; } } @@ -7898,7 +7890,8 @@ namespace ts { case SyntaxKind.ParenthesizedType: case SyntaxKind.JSDocNonNullableType: case SyntaxKind.JSDocOptionalType: - return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocTypeExpression: + return getTypeFromTypeNode((node).type); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: @@ -22646,8 +22639,7 @@ namespace ts { } if (entityName.parent!.kind === SyntaxKind.JSDocParameterTag) { - const parameter = getParameterFromJSDoc(entityName.parent as JSDocParameterTag); - return parameter && parameter.symbol; + return getParameterSymbolFromJSDoc(entityName.parent as JSDocParameterTag); } if (entityName.parent.kind === SyntaxKind.TypeParameter && entityName.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5d6cd9e3bd1..521fce028db 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -59,6 +59,9 @@ namespace ts { * @param node a given node to visit its children * @param cbNode a callback to be invoked for all child nodes * @param cbNodes a callback to be invoked for embedded array + * + * @remarks `forEachChild` must visit the children of a node in the order + * that they appear in the source code. The language service depends on this property to locate nodes by position. */ export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { if (!node || node.kind <= SyntaxKind.LastToken) { @@ -407,9 +410,15 @@ namespace ts { case SyntaxKind.JSDocComment: return visitNodes(cbNode, cbNodes, (node).tags); case SyntaxKind.JSDocParameterTag: - return visitNode(cbNode, (node).preParameterName) || - visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).postParameterName); + case SyntaxKind.JSDocPropertyTag: + if ((node as JSDocPropertyLikeTag).isNameFirst) { + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).typeExpression); + } + else { + return visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).name); + } case SyntaxKind.JSDocReturnTag: return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTypeTag: @@ -419,15 +428,20 @@ namespace ts { case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNode, cbNodes, (node).typeParameters); case SyntaxKind.JSDocTypedefTag: - return visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).name) || - visitNode(cbNode, (node).jsDocTypeLiteral); + if ((node as JSDocTypedefTag).typeExpression && + (node as JSDocTypedefTag).typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + return visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).fullName); + } + else { + return visitNode(cbNode, (node).fullName) || + visitNode(cbNode, (node).typeExpression); + } case SyntaxKind.JSDocTypeLiteral: - return visitNodes(cbNode, cbNodes, (node).jsDocPropertyTags); - case SyntaxKind.JSDocPropertyTag: - return visitNode(cbNode, (node).typeExpression) || - visitNode(cbNode, (node).name); + for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) { + visitNode(cbNode, tag); + } + return; case SyntaxKind.PartiallyEmittedExpression: return visitNode(cbNode, (node).expression); } @@ -1949,14 +1963,18 @@ namespace ts { break; } dotPos = scanner.getStartPos(); - const node: QualifiedName = createNode(SyntaxKind.QualifiedName, entity.pos); - node.left = entity; - node.right = parseRightSideOfDot(allowReservedWords); - entity = finishNode(node); + entity = createQualifiedName(entity, parseRightSideOfDot(allowReservedWords)); } return entity; } + function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { + const node = createNode(SyntaxKind.QualifiedName, entity.pos) as QualifiedName; + node.left = entity; + node.right = name; + return finishNode(node); + } + function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier { // Technically a keyword is valid here as all identifiers and keywords are identifier names. // However, often we'll encounter this in error situations when the identifier or keyword @@ -6163,6 +6181,11 @@ namespace ts { SavingComments, } + const enum PropertyLikeParse { + Property, + Parameter, + } + export function parseJSDocCommentWorker(start: number, length: number): JSDoc { const content = sourceText; start = start || 0; @@ -6342,7 +6365,7 @@ namespace ts { case "arg": case "argument": case "param": - tag = parseParameterOrPropertyTag(atToken, tagName, /*shouldParseParamTag*/ true); + tag = parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter); break; case "return": case "returns": @@ -6465,10 +6488,10 @@ namespace ts { }); } - function parseBracketNameInPropertyAndParamTag(): { name: Identifier, isBracketed: boolean } { - // Looking for something like '[foo]' or 'foo' + function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { + // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' const isBracketed = parseOptional(SyntaxKind.OpenBracketToken); - const name = parseJSDocIdentifierName(/*createIfMissing*/ true); + const name = parseJSDocEntityName(); if (isBracketed) { skipWhitespace(); @@ -6483,33 +6506,72 @@ namespace ts { return { name, isBracketed }; } - function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, shouldParseParamTag: boolean): JSDocPropertyTag | JSDocParameterTag { + function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.ObjectKeyword: + return true; + case SyntaxKind.ArrayType: + return isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); + default: + return isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object"; + } + } + + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse.Parameter): JSDocParameterTag; + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse.Property): JSDocPropertyTag; + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse): JSDocPropertyLikeTag { let typeExpression = tryParseTypeExpression(); + let isNameFirst = !typeExpression; skipWhitespace(); const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); skipWhitespace(); - let preName: Identifier, postName: Identifier; - if (typeExpression) { - postName = name; - } - else { - preName = name; + if (isNameFirst) { typeExpression = tryParseTypeExpression(); } - const result = shouldParseParamTag ? + const result: JSDocPropertyLikeTag = target === PropertyLikeParse.Parameter ? createNode(SyntaxKind.JSDocParameterTag, atToken.pos) : createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name); + if (nestedTypeLiteral) { + typeExpression = nestedTypeLiteral; + isNameFirst = true; + } result.atToken = atToken; result.tagName = tagName; - result.preParameterName = preName; result.typeExpression = typeExpression; - result.postParameterName = postName; - result.name = postName || preName; + result.name = name; + result.isNameFirst = isNameFirst; result.isBracketed = isBracketed; return finishNode(result); + + } + + function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression, name: EntityName) { + if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { + const typeLiteralExpression = createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos()); + let child: JSDocParameterTag | false; + let jsdocTypeLiteral: JSDocTypeLiteral; + const start = scanner.getStartPos(); + let children: JSDocParameterTag[]; + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.Parameter, name))) { + if (!children) { + children = []; + } + children.push(child); + } + if (children) { + jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); + jsdocTypeLiteral.jsDocPropertyTags = children; + if (typeExpression.type.kind === SyntaxKind.ArrayType) { + jsdocTypeLiteral.isArrayType = true; + } + typeLiteralExpression.type = finishNode(jsdocTypeLiteral); + return finishNode(typeLiteralExpression); + } + } } function parseReturnTag(atToken: AtToken, tagName: Identifier): JSDocReturnTag { @@ -6573,69 +6635,44 @@ namespace ts { rightNode = rightNode.body; } } - typedefTag.typeExpression = typeExpression; skipWhitespace(); - if (typeExpression) { - if (isObjectTypeReference(typeExpression.type)) { - typedefTag.jsDocTypeLiteral = scanChildTags(); + typedefTag.typeExpression = typeExpression; + if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { + let child: JSDocTypeTag | JSDocPropertyTag | false; + let jsdocTypeLiteral: JSDocTypeLiteral; + let alreadyHasTypeTag = false; + const start = scanner.getStartPos(); + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.Property))) { + if (!jsdocTypeLiteral) { + jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); + } + if (child.kind === SyntaxKind.JSDocTypeTag) { + if (alreadyHasTypeTag) { + break; + } + else { + jsdocTypeLiteral.jsDocTypeTag = child; + alreadyHasTypeTag = true; + } + } + else { + if (!jsdocTypeLiteral.jsDocPropertyTags) { + jsdocTypeLiteral.jsDocPropertyTags = [] as MutableNodeArray; + } + (jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray).push(child); + } } - if (!typedefTag.jsDocTypeLiteral) { - typedefTag.jsDocTypeLiteral = typeExpression.type; + if (jsdocTypeLiteral) { + if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { + jsdocTypeLiteral.isArrayType = true; + } + typedefTag.typeExpression = finishNode(jsdocTypeLiteral); } } - else { - typedefTag.jsDocTypeLiteral = scanChildTags(); - } return finishNode(typedefTag); - function isObjectTypeReference(node: TypeNode) { - return node.kind === SyntaxKind.ObjectKeyword || - isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object"; - } - - function scanChildTags(): JSDocTypeLiteral { - const jsDocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, scanner.getStartPos()); - let resumePos = scanner.getStartPos(); - let canParseTag = true; - let seenAsterisk = false; - let parentTagTerminated = false; - - while (token() !== SyntaxKind.EndOfFileToken && !parentTagTerminated) { - nextJSDocToken(); - switch (token()) { - case SyntaxKind.AtToken: - if (canParseTag) { - parentTagTerminated = !tryParseChildTag(jsDocTypeLiteral); - if (!parentTagTerminated) { - resumePos = scanner.getStartPos(); - } - } - seenAsterisk = false; - break; - case SyntaxKind.NewLineTrivia: - resumePos = scanner.getStartPos() - 1; - canParseTag = true; - seenAsterisk = false; - break; - case SyntaxKind.AsteriskToken: - if (seenAsterisk) { - canParseTag = false; - } - seenAsterisk = true; - break; - case SyntaxKind.Identifier: - canParseTag = false; - break; - case SyntaxKind.EndOfFileToken: - break; - } - } - scanner.setTextPos(resumePos); - return finishNode(jsDocTypeLiteral); - } - function parseJSDocTypeNameWithNamespace(flags: NodeFlags) { const pos = scanner.getTokenPos(); const typeNameOrNamespaceName = parseJSDocIdentifierName(); @@ -6655,8 +6692,58 @@ namespace ts { } } + function escapedTextsEqual(a: EntityName, b: EntityName): boolean { + while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { + if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { + a = a.left; + b = b.left; + } + else { + return false; + } + } + return a.escapedText === b.escapedText; + } - function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean { + function parseChildParameterOrPropertyTag(target: PropertyLikeParse.Property): JSDocTypeTag | JSDocPropertyTag | false; + function parseChildParameterOrPropertyTag(target: PropertyLikeParse.Parameter, name: EntityName): JSDocParameterTag | false; + function parseChildParameterOrPropertyTag(target: PropertyLikeParse, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + let canParseTag = true; + let seenAsterisk = false; + while (true) { + nextJSDocToken(); + switch (token()) { + case SyntaxKind.AtToken: + if (canParseTag) { + const child = tryParseChildTag(target); + if (child && child.kind === SyntaxKind.JSDocParameterTag && + (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { + return false; + } + return child; + } + seenAsterisk = false; + break; + case SyntaxKind.NewLineTrivia: + canParseTag = true; + seenAsterisk = false; + break; + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + canParseTag = false; + } + seenAsterisk = true; + break; + case SyntaxKind.Identifier: + canParseTag = false; + break; + case SyntaxKind.EndOfFileToken: + return false; + } + } + } + + function tryParseChildTag(target: PropertyLikeParse): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { Debug.assert(token() === SyntaxKind.AtToken); const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); atToken.end = scanner.getTextPos(); @@ -6667,27 +6754,16 @@ namespace ts { if (!tagName) { return false; } - switch (tagName.escapedText) { case "type": - if (parentTag.jsDocTypeTag) { - // already has a @type tag, terminate the parent tag now. - return false; - } - parentTag.jsDocTypeTag = parseTypeTag(atToken, tagName); - return true; + return target === PropertyLikeParse.Property && parseTypeTag(atToken, tagName); case "prop": case "property": - const propertyTag = parseParameterOrPropertyTag(atToken, tagName, /*shouldParseParamTag*/ false) as JSDocPropertyTag; - if (propertyTag) { - if (!parentTag.jsDocPropertyTags) { - parentTag.jsDocPropertyTags = >[]; - } - (parentTag.jsDocPropertyTags as MutableNodeArray).push(propertyTag); - return true; - } - // Error parsing property tag - return false; + return target === PropertyLikeParse.Property && parseParameterOrPropertyTag(atToken, tagName, target); + case "arg": + case "argument": + case "param": + return target === PropertyLikeParse.Parameter && parseParameterOrPropertyTag(atToken, tagName, target); } return false; } @@ -6736,6 +6812,24 @@ namespace ts { return currentToken = scanner.scanJSDocToken(); } + function parseJSDocEntityName(): EntityName { + let entity: EntityName = parseJSDocIdentifierName(/*createIfMissing*/ true); + if (parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + // Note that y[] is accepted as an entity name, but the postfix brackets are not saved for checking. + // Technically usejsdoc.org requires them for specifying a property of a type equivalent to Array<{ x: ...}> + // but it's not worth it to enforce that restriction. + } + while (parseOptional(SyntaxKind.DotToken)) { + const name = parseJSDocIdentifierName(/*createIfMissing*/ true); + if (parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + } + entity = createQualifiedName(entity, name); + } + return entity; + } + function parseJSDocIdentifierName(createIfMissing = false): Identifier { return createJSDocIdentifier(tokenIsIdentifierOrKeyword(token()), createIfMissing); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f076f16f4be..e52c9fb9f5a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -760,6 +760,7 @@ namespace ts { // SyntaxKind.ShorthandPropertyAssignment // SyntaxKind.EnumMember // SyntaxKind.JSDocPropertyTag + // SyntaxKind.JSDocParameterTag export interface VariableLikeDeclaration extends NamedDeclaration { propertyName?: PropertyName; dotDotDotToken?: DotDotDotToken; @@ -2036,7 +2037,7 @@ namespace ts { } // represents a top level: { type } expression in a JSDoc comment. - export interface JSDocTypeExpression extends Node { + export interface JSDocTypeExpression extends TypeNode { kind: SyntaxKind.JSDocTypeExpression; type: TypeNode; } @@ -2125,38 +2126,32 @@ namespace ts { kind: SyntaxKind.JSDocTypedefTag; fullName?: JSDocNamespaceDeclaration | Identifier; name?: Identifier; - typeExpression?: JSDocTypeExpression; - jsDocTypeLiteral?: JSDocTypeLiteral; + typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; } - export interface JSDocPropertyTag extends JSDocTag, TypeElement { + export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { parent: JSDoc; - kind: SyntaxKind.JSDocPropertyTag; - name: Identifier; - /** the parameter name, if provided *before* the type (TypeScript-style) */ - preParameterName?: Identifier; - /** the parameter name, if provided *after* the type (JSDoc-standard) */ - postParameterName?: Identifier; + name: EntityName; typeExpression: JSDocTypeExpression; + /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ + isNameFirst: boolean; isBracketed: boolean; } + export interface JSDocPropertyTag extends JSDocPropertyLikeTag { + kind: SyntaxKind.JSDocPropertyTag; + } + + export interface JSDocParameterTag extends JSDocPropertyLikeTag { + kind: SyntaxKind.JSDocParameterTag; + } + export interface JSDocTypeLiteral extends JSDocType { kind: SyntaxKind.JSDocTypeLiteral; - jsDocPropertyTags?: NodeArray; + jsDocPropertyTags?: ReadonlyArray; jsDocTypeTag?: JSDocTypeTag; - } - - export interface JSDocParameterTag extends JSDocTag { - kind: SyntaxKind.JSDocParameterTag; - /** the parameter name, if provided *before* the type (TypeScript-style) */ - preParameterName?: Identifier; - typeExpression?: JSDocTypeExpression; - /** the parameter name, if provided *after* the type (JSDoc-standard) */ - postParameterName?: Identifier; - /** the parameter name, regardless of the location it was provided */ - name: Identifier; - isBracketed: boolean; + /** If true, then this type literal represents an *array* of its type. */ + isArrayType?: boolean; } export const enum FlowFlags { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d006a2fb6c1..561419d367e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1547,25 +1547,29 @@ namespace ts { export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] | undefined { if (param.name && isIdentifier(param.name)) { const name = param.name.escapedText; - return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && tag.name.escapedText === name) as JSDocParameterTag[]; - } - else { - // TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines - // But multi-line object types aren't supported yet either - return undefined; + return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name) as JSDocParameterTag[]; } + // a binding pattern doesn't have a name, so it's not possible to match it a jsdoc parameter, which is identified by name + return undefined; } /** Does the opposite of `getJSDocParameterTags`: given a JSDoc parameter, finds the parameter corresponding to it. */ - export function getParameterFromJSDoc(node: JSDocParameterTag): ParameterDeclaration | undefined { - const name = node.name.escapedText; - const grandParent = node.parent!.parent!; - Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment); - if (!isFunctionLike(grandParent)) { + export function getParameterSymbolFromJSDoc(node: JSDocParameterTag): Symbol | undefined { + if (node.symbol) { + return node.symbol; + } + if (!isIdentifier(node.name)) { return undefined; } - return find(grandParent.parameters, p => + const name = node.name.escapedText; + Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment); + const func = node.parent!.parent!; + if (!isFunctionLike(func)) { + return undefined; + } + const parameter = find(func.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name); + return parameter && parameter.symbol; } export function getTypeParameterFromJsDoc(node: TypeParameterDeclaration & { parent: JSDocTemplateTag }): TypeParameterDeclaration | undefined { @@ -4057,6 +4061,9 @@ namespace ts { if (!declaration) { return undefined; } + if (isJSDocPropertyLikeTag(declaration) && declaration.name.kind === SyntaxKind.QualifiedName) { + return declaration.name.right; + } if (declaration.kind === SyntaxKind.BinaryExpression) { const expr = declaration as BinaryExpression; switch (getSpecialPropertyAssignmentKind(expr)) { @@ -4715,6 +4722,10 @@ namespace ts { return node.kind === SyntaxKind.JSDocPropertyTag; } + export function isJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { + return node.kind === SyntaxKind.JSDocPropertyTag || node.kind === SyntaxKind.JSDocParameterTag; + } + export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { return node.kind === SyntaxKind.JSDocTypeLiteral; } diff --git a/src/services/classifier.ts b/src/services/classifier.ts index ce676ff7733..dc5d99bc490 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -755,10 +755,10 @@ namespace ts { return; function processJSDocParameterTag(tag: JSDocParameterTag) { - if (tag.preParameterName) { - pushCommentRange(pos, tag.preParameterName.pos - pos); - pushClassification(tag.preParameterName.pos, tag.preParameterName.end - tag.preParameterName.pos, ClassificationType.parameterName); - pos = tag.preParameterName.end; + if (tag.isNameFirst) { + pushCommentRange(pos, tag.name.pos - pos); + pushClassification(tag.name.pos, tag.name.end - tag.name.pos, ClassificationType.parameterName); + pos = tag.name.end; } if (tag.typeExpression) { @@ -767,10 +767,10 @@ namespace ts { pos = tag.typeExpression.end; } - if (tag.postParameterName) { - pushCommentRange(pos, tag.postParameterName.pos - pos); - pushClassification(tag.postParameterName.pos, tag.postParameterName.end - tag.postParameterName.pos, ClassificationType.parameterName); - pos = tag.postParameterName.end; + if (!tag.isNameFirst) { + pushCommentRange(pos, tag.name.pos - pos); + pushClassification(tag.name.pos, tag.name.end - tag.name.pos, ClassificationType.parameterName); + pos = tag.name.end; } } } diff --git a/src/services/completions.ts b/src/services/completions.ts index a4f66252aa8..b49567ef217 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -420,7 +420,7 @@ namespace ts.Completions { if (tag.tagName.pos <= position && position <= tag.tagName.end) { request = { kind: "JsDocTagName" }; } - if (isTagWithTypeExpression(tag) && tag.typeExpression) { + if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ true); if (!currentToken || (!isDeclarationName(currentToken) && diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 75ff52aaf41..ee3ae7c868f 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -120,6 +120,9 @@ namespace ts.JsDoc { } export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] { + if (!isIdentifier(tag.name)) { + return emptyArray; + } const nameThusFar = tag.name.text; const jsdoc = tag.parent; const fn = jsdoc.parent; @@ -129,7 +132,7 @@ namespace ts.JsDoc { if (!isIdentifier(param.name)) return undefined; const name = param.name.text; - if (jsdoc.tags.some(t => t !== tag && isJSDocParameterTag(t) && t.name.escapedText === name) + if (jsdoc.tags.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { return undefined; } diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argSynonymForParamTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argSynonymForParamTag.json index e72852a4f3c..1a49c026e4c 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argSynonymForParamTag.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argSynonymForParamTag.json @@ -28,18 +28,13 @@ "end": 20 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 22, - "end": 27, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 22, "end": 27, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": false, "comment": "Description" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argumentSynonymForParamTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argumentSynonymForParamTag.json index a5f1b1629db..4b463061bb5 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argumentSynonymForParamTag.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.argumentSynonymForParamTag.json @@ -28,18 +28,13 @@ "end": 25 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 27, - "end": 32, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 27, "end": 32, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": false, "comment": "Description" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json index 7d3fab2aad7..15e8b4a5cfb 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json @@ -28,18 +28,13 @@ "end": 22 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 24, - "end": 29, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 24, "end": 29, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": false, "comment": "" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTag1.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTag1.json index 7ca78d669ce..21d94f52e6a 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTag1.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTag1.json @@ -28,18 +28,13 @@ "end": 22 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 24, - "end": 29, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 24, "end": 29, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": false, "comment": "Description text follows" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName1.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName1.json index 103917eafda..18649dcb321 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName1.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName1.json @@ -28,18 +28,13 @@ "end": 22 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 25, - "end": 30, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 25, "end": 30, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": true, "comment": "Description text follows" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName2.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName2.json index b93240d2c40..6bddf6235d5 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName2.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagBracketedName2.json @@ -28,18 +28,13 @@ "end": 22 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 26, - "end": 31, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 26, "end": 31, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": true, "comment": "Description text follows" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json index 3e7956f1424..93f47686e8f 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json @@ -18,12 +18,6 @@ "end": 14, "escapedText": "param" }, - "preParameterName": { - "kind": "Identifier", - "pos": 15, - "end": 20, - "escapedText": "name1" - }, "typeExpression": { "kind": "JSDocTypeExpression", "pos": 21, @@ -40,6 +34,7 @@ "end": 20, "escapedText": "name1" }, + "isNameFirst": true, "isBracketed": false, "comment": "" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType2.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType2.json index d732cd8b85c..e6c48a3f759 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType2.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType2.json @@ -18,12 +18,6 @@ "end": 14, "escapedText": "param" }, - "preParameterName": { - "kind": "Identifier", - "pos": 15, - "end": 20, - "escapedText": "name1" - }, "typeExpression": { "kind": "JSDocTypeExpression", "pos": 21, @@ -40,6 +34,7 @@ "end": 20, "escapedText": "name1" }, + "isNameFirst": true, "isBracketed": false, "comment": "Description" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json index b082a5e57b2..926344175d7 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json @@ -18,18 +18,13 @@ "end": 14, "escapedText": "param" }, - "preParameterName": { - "kind": "Identifier", - "pos": 15, - "end": 18, - "escapedText": "foo" - }, "name": { "kind": "Identifier", "pos": 15, "end": 18, "escapedText": "foo" }, + "isNameFirst": true, "isBracketed": false, "comment": "" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json index 4c14cdc918f..391ee1aac2f 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json @@ -28,18 +28,13 @@ "end": 22 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 24, - "end": 29, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 24, "end": 29, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": false, "comment": "" }, @@ -68,18 +63,13 @@ "end": 48 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 50, - "end": 55, - "escapedText": "name2" - }, "name": { "kind": "Identifier", "pos": 50, "end": 55, "escapedText": "name2" }, + "isNameFirst": false, "isBracketed": false, "comment": "" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json index 801d86196fc..628d54d162d 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json @@ -28,18 +28,13 @@ "end": 22 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 24, - "end": 29, - "escapedText": "name1" - }, "name": { "kind": "Identifier", "pos": 24, "end": 29, "escapedText": "name1" }, + "isNameFirst": false, "isBracketed": false, "comment": "" }, @@ -68,18 +63,13 @@ "end": 44 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 46, - "end": 51, - "escapedText": "name2" - }, "name": { "kind": "Identifier", "pos": 46, "end": 51, "escapedText": "name2" }, + "isNameFirst": false, "isBracketed": false, "comment": "" }, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json index 415411bf4e6..f0e42ae6325 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json @@ -30,7 +30,7 @@ "end": 23, "escapedText": "People" }, - "jsDocTypeLiteral": { + "typeExpression": { "kind": "JSDocTypeLiteral", "pos": 26, "end": 98, @@ -92,18 +92,13 @@ "end": 64 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 66, - "end": 69, - "escapedText": "age" - }, "name": { "kind": "Identifier", "pos": 66, "end": 69, "escapedText": "age" }, + "isNameFirst": false, "isBracketed": false }, { @@ -131,18 +126,13 @@ "end": 91 } }, - "postParameterName": { - "kind": "Identifier", - "pos": 93, - "end": 97, - "escapedText": "name" - }, "name": { "kind": "Identifier", "pos": 93, "end": 97, "escapedText": "name" }, + "isNameFirst": false, "isBracketed": false } ] diff --git a/tests/baselines/reference/jsdocParamTagTypeLiteral.symbols b/tests/baselines/reference/jsdocParamTagTypeLiteral.symbols new file mode 100644 index 00000000000..8005be7cec7 --- /dev/null +++ b/tests/baselines/reference/jsdocParamTagTypeLiteral.symbols @@ -0,0 +1,132 @@ +=== tests/cases/conformance/jsdoc/0.js === +/** + * @param {Object} notSpecial + * @param {string} unrelated - not actually related because it's not notSpecial.unrelated + */ +function normal(notSpecial) { +>normal : Symbol(normal, Decl(0.js, 0, 0)) +>notSpecial : Symbol(notSpecial, Decl(0.js, 4, 16)) + + notSpecial; // should just be 'any' +>notSpecial : Symbol(notSpecial, Decl(0.js, 4, 16)) +} +normal(12); +>normal : Symbol(normal, Decl(0.js, 0, 0)) + +/** + * @param {Object} opts1 doc1 + * @param {string} opts1.x doc2 + * @param {string=} opts1.y doc3 + * @param {string} [opts1.z] doc4 + * @param {string} [opts1.w="hi"] doc5 + */ +function foo1(opts1) { +>foo1 : Symbol(foo1, Decl(0.js, 7, 11)) +>opts1 : Symbol(opts1, Decl(0.js, 16, 14)) + + opts1.x; +>opts1.x : Symbol(x, Decl(0.js, 11, 3)) +>opts1 : Symbol(opts1, Decl(0.js, 16, 14)) +>x : Symbol(x, Decl(0.js, 11, 3)) +} + +foo1({x: 'abc'}); +>foo1 : Symbol(foo1, Decl(0.js, 7, 11)) +>x : Symbol(x, Decl(0.js, 20, 6)) + +/** + * @param {Object[]} opts2 + * @param {string} opts2[].anotherX + * @param {string=} opts2[].anotherY + */ +function foo2(/** @param opts2 bad idea theatre! */opts2) { +>foo2 : Symbol(foo2, Decl(0.js, 20, 17)) +>opts2 : Symbol(opts2, Decl(0.js, 27, 14)) + + opts2[0].anotherX; +>opts2[0].anotherX : Symbol(anotherX, Decl(0.js, 24, 3)) +>opts2 : Symbol(opts2, Decl(0.js, 27, 14)) +>anotherX : Symbol(anotherX, Decl(0.js, 24, 3)) +} + +foo2([{anotherX: "world"}]); +>foo2 : Symbol(foo2, Decl(0.js, 20, 17)) +>anotherX : Symbol(anotherX, Decl(0.js, 31, 7)) + +/** + * @param {object} opts3 + * @param {string} opts3.x + */ +function foo3(opts3) { +>foo3 : Symbol(foo3, Decl(0.js, 31, 28)) +>opts3 : Symbol(opts3, Decl(0.js, 37, 14)) + + opts3.x; +>opts3.x : Symbol(x, Decl(0.js, 35, 3)) +>opts3 : Symbol(opts3, Decl(0.js, 37, 14)) +>x : Symbol(x, Decl(0.js, 35, 3)) +} +foo3({x: 'abc'}); +>foo3 : Symbol(foo3, Decl(0.js, 31, 28)) +>x : Symbol(x, Decl(0.js, 40, 6)) + +/** + * @param {object[]} opts4 + * @param {string} opts4[].x + * @param {string=} opts4[].y + * @param {string} [opts4[].z] + * @param {string} [opts4[].w="hi"] + */ +function foo4(opts4) { +>foo4 : Symbol(foo4, Decl(0.js, 40, 17)) +>opts4 : Symbol(opts4, Decl(0.js, 49, 14)) + + opts4[0].x; +>opts4[0].x : Symbol(x, Decl(0.js, 44, 3)) +>opts4 : Symbol(opts4, Decl(0.js, 49, 14)) +>x : Symbol(x, Decl(0.js, 44, 3)) +} + +foo4([{ x: 'hi' }]); +>foo4 : Symbol(foo4, Decl(0.js, 40, 17)) +>x : Symbol(x, Decl(0.js, 53, 7)) + +/** + * @param {object[]} opts5 - Let's test out some multiple nesting levels + * @param {string} opts5[].help - (This one is just normal) + * @param {object} opts5[].what - Look at us go! Here's the first nest! + * @param {string} opts5[].what.a - (Another normal one) + * @param {Object[]} opts5[].what.bad - Now we're nesting inside a nested type + * @param {string} opts5[].what.bad[].idea - I don't think you can get back out of this level... + * @param {boolean} opts5[].what.bad[].oh - Oh ... that's how you do it. + * @param {number} opts5[].unnest - Here we are almost all the way back at the beginning. + */ +function foo5(opts5) { +>foo5 : Symbol(foo5, Decl(0.js, 53, 20)) +>opts5 : Symbol(opts5, Decl(0.js, 65, 14)) + + opts5[0].what.bad[0].idea; +>opts5[0].what.bad[0].idea : Symbol(idea, Decl(0.js, 61, 3)) +>opts5[0].what.bad : Symbol(bad, Decl(0.js, 60, 3)) +>opts5[0].what : Symbol(what, Decl(0.js, 58, 3)) +>opts5 : Symbol(opts5, Decl(0.js, 65, 14)) +>what : Symbol(what, Decl(0.js, 58, 3)) +>bad : Symbol(bad, Decl(0.js, 60, 3)) +>idea : Symbol(idea, Decl(0.js, 61, 3)) + + opts5[0].unnest; +>opts5[0].unnest : Symbol(unnest, Decl(0.js, 63, 3)) +>opts5 : Symbol(opts5, Decl(0.js, 65, 14)) +>unnest : Symbol(unnest, Decl(0.js, 63, 3)) +} + +foo5([{ help: "help", what: { a: 'a', bad: [{ idea: 'idea', oh: false }] }, unnest: 1 }]); +>foo5 : Symbol(foo5, Decl(0.js, 53, 20)) +>help : Symbol(help, Decl(0.js, 70, 7)) +>what : Symbol(what, Decl(0.js, 70, 21)) +>a : Symbol(a, Decl(0.js, 70, 29)) +>bad : Symbol(bad, Decl(0.js, 70, 37)) +>idea : Symbol(idea, Decl(0.js, 70, 45)) +>oh : Symbol(oh, Decl(0.js, 70, 59)) +>unnest : Symbol(unnest, Decl(0.js, 70, 75)) + diff --git a/tests/baselines/reference/jsdocParamTagTypeLiteral.types b/tests/baselines/reference/jsdocParamTagTypeLiteral.types new file mode 100644 index 00000000000..95adecafc83 --- /dev/null +++ b/tests/baselines/reference/jsdocParamTagTypeLiteral.types @@ -0,0 +1,169 @@ +=== tests/cases/conformance/jsdoc/0.js === +/** + * @param {Object} notSpecial + * @param {string} unrelated - not actually related because it's not notSpecial.unrelated + */ +function normal(notSpecial) { +>normal : (notSpecial: any) => void +>notSpecial : any + + notSpecial; // should just be 'any' +>notSpecial : any +} +normal(12); +>normal(12) : void +>normal : (notSpecial: any) => void +>12 : 12 + +/** + * @param {Object} opts1 doc1 + * @param {string} opts1.x doc2 + * @param {string=} opts1.y doc3 + * @param {string} [opts1.z] doc4 + * @param {string} [opts1.w="hi"] doc5 + */ +function foo1(opts1) { +>foo1 : (opts1: { x: string; y?: string; z?: string; w?: string; }) => void +>opts1 : { x: string; y?: string; z?: string; w?: string; } + + opts1.x; +>opts1.x : string +>opts1 : { x: string; y?: string; z?: string; w?: string; } +>x : string +} + +foo1({x: 'abc'}); +>foo1({x: 'abc'}) : void +>foo1 : (opts1: { x: string; y?: string; z?: string; w?: string; }) => void +>{x: 'abc'} : { x: string; } +>x : string +>'abc' : "abc" + +/** + * @param {Object[]} opts2 + * @param {string} opts2[].anotherX + * @param {string=} opts2[].anotherY + */ +function foo2(/** @param opts2 bad idea theatre! */opts2) { +>foo2 : (opts2: { anotherX: string; anotherY?: string; }[]) => void +>opts2 : { anotherX: string; anotherY?: string; }[] + + opts2[0].anotherX; +>opts2[0].anotherX : string +>opts2[0] : { anotherX: string; anotherY?: string; } +>opts2 : { anotherX: string; anotherY?: string; }[] +>0 : 0 +>anotherX : string +} + +foo2([{anotherX: "world"}]); +>foo2([{anotherX: "world"}]) : void +>foo2 : (opts2: { anotherX: string; anotherY?: string; }[]) => void +>[{anotherX: "world"}] : { anotherX: string; }[] +>{anotherX: "world"} : { anotherX: string; } +>anotherX : string +>"world" : "world" + +/** + * @param {object} opts3 + * @param {string} opts3.x + */ +function foo3(opts3) { +>foo3 : (opts3: { x: string; }) => void +>opts3 : { x: string; } + + opts3.x; +>opts3.x : string +>opts3 : { x: string; } +>x : string +} +foo3({x: 'abc'}); +>foo3({x: 'abc'}) : void +>foo3 : (opts3: { x: string; }) => void +>{x: 'abc'} : { x: string; } +>x : string +>'abc' : "abc" + +/** + * @param {object[]} opts4 + * @param {string} opts4[].x + * @param {string=} opts4[].y + * @param {string} [opts4[].z] + * @param {string} [opts4[].w="hi"] + */ +function foo4(opts4) { +>foo4 : (opts4: { x: string; y?: string; z?: string; w?: string; }[]) => void +>opts4 : { x: string; y?: string; z?: string; w?: string; }[] + + opts4[0].x; +>opts4[0].x : string +>opts4[0] : { x: string; y?: string; z?: string; w?: string; } +>opts4 : { x: string; y?: string; z?: string; w?: string; }[] +>0 : 0 +>x : string +} + +foo4([{ x: 'hi' }]); +>foo4([{ x: 'hi' }]) : void +>foo4 : (opts4: { x: string; y?: string; z?: string; w?: string; }[]) => void +>[{ x: 'hi' }] : { x: string; }[] +>{ x: 'hi' } : { x: string; } +>x : string +>'hi' : "hi" + +/** + * @param {object[]} opts5 - Let's test out some multiple nesting levels + * @param {string} opts5[].help - (This one is just normal) + * @param {object} opts5[].what - Look at us go! Here's the first nest! + * @param {string} opts5[].what.a - (Another normal one) + * @param {Object[]} opts5[].what.bad - Now we're nesting inside a nested type + * @param {string} opts5[].what.bad[].idea - I don't think you can get back out of this level... + * @param {boolean} opts5[].what.bad[].oh - Oh ... that's how you do it. + * @param {number} opts5[].unnest - Here we are almost all the way back at the beginning. + */ +function foo5(opts5) { +>foo5 : (opts5: { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; }[]) => void +>opts5 : { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; }[] + + opts5[0].what.bad[0].idea; +>opts5[0].what.bad[0].idea : string +>opts5[0].what.bad[0] : { idea: string; oh: boolean; } +>opts5[0].what.bad : { idea: string; oh: boolean; }[] +>opts5[0].what : { a: string; bad: { idea: string; oh: boolean; }[]; } +>opts5[0] : { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; } +>opts5 : { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; }[] +>0 : 0 +>what : { a: string; bad: { idea: string; oh: boolean; }[]; } +>bad : { idea: string; oh: boolean; }[] +>0 : 0 +>idea : string + + opts5[0].unnest; +>opts5[0].unnest : number +>opts5[0] : { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; } +>opts5 : { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; }[] +>0 : 0 +>unnest : number +} + +foo5([{ help: "help", what: { a: 'a', bad: [{ idea: 'idea', oh: false }] }, unnest: 1 }]); +>foo5([{ help: "help", what: { a: 'a', bad: [{ idea: 'idea', oh: false }] }, unnest: 1 }]) : void +>foo5 : (opts5: { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; }[]) => void +>[{ help: "help", what: { a: 'a', bad: [{ idea: 'idea', oh: false }] }, unnest: 1 }] : { help: string; what: { a: string; bad: { idea: string; oh: false; }[]; }; unnest: number; }[] +>{ help: "help", what: { a: 'a', bad: [{ idea: 'idea', oh: false }] }, unnest: 1 } : { help: string; what: { a: string; bad: { idea: string; oh: false; }[]; }; unnest: number; } +>help : string +>"help" : "help" +>what : { a: string; bad: { idea: string; oh: false; }[]; } +>{ a: 'a', bad: [{ idea: 'idea', oh: false }] } : { a: string; bad: { idea: string; oh: false; }[]; } +>a : string +>'a' : "a" +>bad : { idea: string; oh: false; }[] +>[{ idea: 'idea', oh: false }] : { idea: string; oh: false; }[] +>{ idea: 'idea', oh: false } : { idea: string; oh: false; } +>idea : string +>'idea' : "idea" +>oh : boolean +>false : false +>unnest : number +>1 : 1 + diff --git a/tests/cases/conformance/jsdoc/jsdocParamTagTypeLiteral.ts b/tests/cases/conformance/jsdoc/jsdocParamTagTypeLiteral.ts new file mode 100644 index 00000000000..bf59ac5664d --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocParamTagTypeLiteral.ts @@ -0,0 +1,78 @@ +// @allowJS: true +// @checkJs: true +// @noEmit: true +// @strict: true +// @suppressOutputPathCheck: true + +// @Filename: 0.js +/** + * @param {Object} notSpecial + * @param {string} unrelated - not actually related because it's not notSpecial.unrelated + */ +function normal(notSpecial) { + notSpecial; // should just be 'any' +} +normal(12); + +/** + * @param {Object} opts1 doc1 + * @param {string} opts1.x doc2 + * @param {string=} opts1.y doc3 + * @param {string} [opts1.z] doc4 + * @param {string} [opts1.w="hi"] doc5 + */ +function foo1(opts1) { + opts1.x; +} + +foo1({x: 'abc'}); + +/** + * @param {Object[]} opts2 + * @param {string} opts2[].anotherX + * @param {string=} opts2[].anotherY + */ +function foo2(/** @param opts2 bad idea theatre! */opts2) { + opts2[0].anotherX; +} + +foo2([{anotherX: "world"}]); + +/** + * @param {object} opts3 + * @param {string} opts3.x + */ +function foo3(opts3) { + opts3.x; +} +foo3({x: 'abc'}); + +/** + * @param {object[]} opts4 + * @param {string} opts4[].x + * @param {string=} opts4[].y + * @param {string} [opts4[].z] + * @param {string} [opts4[].w="hi"] + */ +function foo4(opts4) { + opts4[0].x; +} + +foo4([{ x: 'hi' }]); + +/** + * @param {object[]} opts5 - Let's test out some multiple nesting levels + * @param {string} opts5[].help - (This one is just normal) + * @param {object} opts5[].what - Look at us go! Here's the first nest! + * @param {string} opts5[].what.a - (Another normal one) + * @param {Object[]} opts5[].what.bad - Now we're nesting inside a nested type + * @param {string} opts5[].what.bad[].idea - I don't think you can get back out of this level... + * @param {boolean} opts5[].what.bad[].oh - Oh ... that's how you do it. + * @param {number} opts5[].unnest - Here we are almost all the way back at the beginning. + */ +function foo5(opts5) { + opts5[0].what.bad[0].idea; + opts5[0].unnest; +} + +foo5([{ help: "help", what: { a: 'a', bad: [{ idea: 'idea', oh: false }] }, unnest: 1 }]); diff --git a/tests/cases/fourslash/findAllReferencesJsDocTypeLiteral.ts b/tests/cases/fourslash/findAllReferencesJsDocTypeLiteral.ts new file mode 100644 index 00000000000..6d1b32b1515 --- /dev/null +++ b/tests/cases/fourslash/findAllReferencesJsDocTypeLiteral.ts @@ -0,0 +1,22 @@ +// @allowJs: true +// @checkJs: true +/// + +// @Filename: foo.js +/////** +//// * @param {object} o - very important! +//// * @param {string} o.x - a thing, its ok +//// * @param {number} o.y - another thing +//// * @param {Object} o.nested - very nested +//// * @param {boolean} o.nested.[|great|] - much greatness +//// * @param {number} o.nested.times - twice? probably!?? +//// */ +//// function f(o) { return o.nested.[|great|]; } + +verify.rangesReferenceEachOther(); + +///** +// * @param {object} [|o|] - very important! +// * @param {string} o.x - a thing, its ok +// */ +// function f([|o|]) { return [|o|].x; }