diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 0b58f080fe2..baf7f747539 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 @@ -2104,8 +2104,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: @@ -2163,13 +2164,16 @@ 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) ? + return declareSymbolAndAddToSymbolTable(node as JSDocPropertyLikeTag, + (node as JSDocPropertyLikeTag).isBracketed || ((node as JSDocPropertyLikeTag).typeExpression && (node as JSDocPropertyLikeTag).typeExpression.type.kind === SyntaxKind.JSDocOptionalType) ? SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property, SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypeLiteral: - return bindAnonymousTypeWorker(node as JSDocTypeLiteral); 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 c2f40c169dc..c69cdaee8c2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag) || + getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); + let type = getTypeFromTypeNode(declaration.kind === SyntaxKind.JSDocTypedefTag ? declaration.typeExpression : declaration.type); if (popTypeResolution()) { const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); @@ -7654,9 +7643,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; } } @@ -7890,7 +7882,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: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0be7734968f..c67e27150aa 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).isParameterNameFirst) { + return visitNode(cbNode, (node).fullName) || + visitNode(cbNode, (node).typeExpression); + } + else { + return visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).fullName); + } case SyntaxKind.JSDocReturnTag: return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTypeTag: @@ -419,15 +428,19 @@ 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) || + visitNode(cbNode, (node).name); + } + else { + return visitNode(cbNode, (node).fullName) || + visitNode(cbNode, (node).name) || + 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); case SyntaxKind.PartiallyEmittedExpression: return visitNode(cbNode, (node).expression); } @@ -6457,10 +6470,10 @@ namespace ts { }); } - function parseBracketNameInPropertyAndParamTag(): { name: Identifier, isBracketed: boolean } { - // Looking for something like '[foo]' or 'foo' + function parseBracketNameInPropertyAndParamTag(): { fullName: 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 fullName = parseJSDocEntityName(/*createIfMissing*/ true); if (isBracketed) { skipWhitespace(); @@ -6472,36 +6485,66 @@ namespace ts { parseExpected(SyntaxKind.CloseBracketToken); } - return { name, isBracketed }; + return { fullName, isBracketed }; } - function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, shouldParseParamTag: boolean): JSDocPropertyTag | JSDocParameterTag { + function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { + return node.kind === SyntaxKind.ObjectKeyword || + isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.text === "Object" || + node.kind === SyntaxKind.ArrayType && isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); + } + + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, shouldParseParamTag: true): JSDocParameterTag; + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, shouldParseParamTag: false): JSDocPropertyTag; + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, shouldParseParamTag: boolean): JSDocPropertyLikeTag { let typeExpression = tryParseTypeExpression(); skipWhitespace(); - const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); + const { fullName, isBracketed } = parseBracketNameInPropertyAndParamTag(); skipWhitespace(); - let preName: Identifier, postName: Identifier; + let preName: EntityName, postName: EntityName; if (typeExpression) { - postName = name; + postName = fullName; } else { - preName = name; + preName = fullName; typeExpression = tryParseTypeExpression(); } - const result = shouldParseParamTag ? + const result: JSDocPropertyLikeTag = shouldParseParamTag ? createNode(SyntaxKind.JSDocParameterTag, atToken.pos) : createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { + let child: JSDocPropertyLikeTag | false; + let jsdocTypeLiteral: JSDocTypeLiteral; + const start = scanner.getStartPos(); + while (child = tryParse(() => parseChildParameterOrPropertyTag(/*shouldParseParamTag*/ true, fullName))) { + if (!jsdocTypeLiteral) { + jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); + jsdocTypeLiteral.jsDocPropertyTags = [] as MutableNodeArray; + } + (jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray).push(child as JSDocPropertyTag); + } + if (jsdocTypeLiteral) { + if (typeExpression.type.kind === SyntaxKind.ArrayType) { + jsdocTypeLiteral.isArrayType = true; + } + typeExpression.type = finishNode(jsdocTypeLiteral); + } + } result.atToken = atToken; result.tagName = tagName; - result.preParameterName = preName; result.typeExpression = typeExpression; - result.postParameterName = postName; - result.name = postName || preName; + if (typeExpression) { + result.type = typeExpression.type; + } + result.fullName = postName || preName; + result.name = ts.isIdentifier(result.fullName) ? result.fullName : result.fullName.right; + result.isParameterNameFirst = postName ? false : !!preName; result.isBracketed = isBracketed; return finishNode(result); + } function parseReturnTag(atToken: AtToken, tagName: Identifier): JSDocReturnTag { @@ -6565,68 +6608,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(/*shouldParseParamTag*/ false))) { + 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.text === "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(); @@ -6647,8 +6666,61 @@ namespace ts { } } + function textsEqual(parent: EntityName, name: EntityName): boolean { + while (!ts.isIdentifier(parent) || !ts.isIdentifier(name)) { + if (!ts.isIdentifier(parent) && !ts.isIdentifier(name) && parent.right.text === name.right.text) { + parent = parent.left; + name = name.left; + } + else { + return false; + } + } + return parent.text === name.text; + } - function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean { + function parseChildParameterOrPropertyTag(shouldParseParamTag: false): JSDocTypeTag | JSDocPropertyTag | false; + function parseChildParameterOrPropertyTag(shouldParseParamTag: true, fullName: EntityName): JSDocPropertyTag | JSDocParameterTag | false; + function parseChildParameterOrPropertyTag(shouldParseParamTag: boolean, fullName?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + let resumePos = scanner.getStartPos(); + let canParseTag = true; + let seenAsterisk = false; + while (token() !== SyntaxKind.EndOfFileToken) { + nextJSDocToken(); + switch (token()) { + case SyntaxKind.AtToken: + if (canParseTag) { + const child = tryParseChildTag(shouldParseParamTag); + if (child && child.kind === SyntaxKind.JSDocParameterTag && + (ts.isIdentifier(child.fullName) || !textsEqual(fullName, child.fullName.left))) { + break; + } + return child; + } + 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); + } + + function tryParseChildTag(shouldParseParamTag: boolean, alreadyHasTypeTag?: boolean): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { Debug.assert(token() === SyntaxKind.AtToken); const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); atToken.end = scanner.getTextPos(); @@ -6659,27 +6731,16 @@ namespace ts { if (!tagName) { return false; } - switch (tagName.text) { 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 !alreadyHasTypeTag && !shouldParseParamTag && 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 !shouldParseParamTag && parseParameterOrPropertyTag(atToken, tagName, /*shouldParseParamTag*/ false); + case "arg": + case "argument": + case "param": + return shouldParseParamTag && parseParameterOrPropertyTag(atToken, tagName, /*shouldParseParamTag*/ true); } return false; } @@ -6728,6 +6789,26 @@ namespace ts { return currentToken = scanner.scanJSDocToken(); } + function parseJSDocEntityName(createIfMissing = false): EntityName { + let entity: EntityName = parseJSDocIdentifierName(createIfMissing); + 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 node: QualifiedName = createNode(SyntaxKind.QualifiedName, entity.pos) as QualifiedName; + node.left = entity; + node.right = parseJSDocIdentifierName(createIfMissing); + if (parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + } + entity = finishNode(node); + } + 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 8431269046c..52e9a62e6a9 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,33 @@ 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, VariableLikeDeclaration { parent: JSDoc; - kind: SyntaxKind.JSDocPropertyTag; + fullName?: EntityName; 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; typeExpression: JSDocTypeExpression; + /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ + isParameterNameFirst: 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?: NodeArray; 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 e001ec43f62..983ff8012bd 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1553,6 +1553,10 @@ namespace ts { /** Does the opposite of `getJSDocParameterTags`: given a JSDoc parameter, finds the parameter corresponding to it. */ export function getParameterFromJSDoc(node: JSDocParameterTag): ParameterDeclaration | undefined { + if (!isIdentifier(node.fullName)) { + // `@param {T} obj.prop` is not a top-level param, so it doesn't map to a top-level parameter + return undefined; + } const name = node.name.text; const grandParent = node.parent!.parent!; Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment); diff --git a/src/services/classifier.ts b/src/services/classifier.ts index 146a513ffc5..8acce0a01fa 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.isParameterNameFirst) { + 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.isParameterNameFirst) { + 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 d7f0701caf2..84e7b375ff0 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 070b96101e2..464d251d4c2 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -120,7 +120,7 @@ namespace ts.JsDoc { } export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] { - const nameThusFar = unescapeLeadingUnderscores(tag.name.text); + const nameThusFar = isIdentifier(tag.fullName) ? unescapeLeadingUnderscores(tag.name.text) : undefined; const jsdoc = tag.parent; const fn = jsdoc.parent; if (!ts.isFunctionLike(fn)) return []; @@ -129,7 +129,7 @@ namespace ts.JsDoc { if (!isIdentifier(param.name)) return undefined; const name = unescapeLeadingUnderscores(param.name.text); - if (jsdoc.tags.some(t => t !== tag && isJSDocParameterTag(t) && t.name.text === name) + if (jsdoc.tags.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.fullName) && t.name.text === name) || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { return undefined; }