diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index baf7f747539..3c26dedea64 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2170,10 +2170,11 @@ namespace ts { } // falls through case SyntaxKind.JSDocPropertyTag: - 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); + 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 c69cdaee8c2..b7e08ea41a0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5074,8 +5074,8 @@ namespace ts { return unknownType; } - const declaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag) || - getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); + 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()) { @@ -22610,8 +22610,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 799f1333730..a0bd6c02c51 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -411,7 +411,7 @@ namespace ts { return visitNodes(cbNode, cbNodes, (node).tags); case SyntaxKind.JSDocParameterTag: case SyntaxKind.JSDocPropertyTag: - if ((node as JSDocPropertyLikeTag).isParameterNameFirst) { + if ((node as JSDocPropertyLikeTag).isNameFirst) { return visitNode(cbNode, (node).fullName) || visitNode(cbNode, (node).typeExpression); } @@ -431,12 +431,10 @@ namespace ts { 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); + visitNode(cbNode, (node).fullName); } else { return visitNode(cbNode, (node).fullName) || - visitNode(cbNode, (node).name) || visitNode(cbNode, (node).typeExpression); } case SyntaxKind.JSDocTypeLiteral: @@ -6520,7 +6518,27 @@ namespace ts { const result: JSDocPropertyLikeTag = target ? createNode(SyntaxKind.JSDocParameterTag, atToken.pos) : createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, fullName); + if (nestedTypeLiteral) { + typeExpression = nestedTypeLiteral; + } + result.atToken = atToken; + result.tagName = tagName; + result.typeExpression = typeExpression; + if (typeExpression) { + result.type = typeExpression.type; + } + result.fullName = postName || preName; + result.name = ts.isIdentifier(result.fullName) ? result.fullName : result.fullName.right; + result.isNameFirst = !!nestedTypeLiteral || (postName ? false : !!preName); + result.isBracketed = isBracketed; + return finishNode(result); + + } + + function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression, fullName: EntityName) { if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { + const typeLiteralExpression = createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos()); let child: JSDocPropertyLikeTag | false; let jsdocTypeLiteral: JSDocTypeLiteral; const start = scanner.getStartPos(); @@ -6535,21 +6553,10 @@ namespace ts { if (typeExpression.type.kind === SyntaxKind.ArrayType) { jsdocTypeLiteral.isArrayType = true; } - typeExpression.type = finishNode(jsdocTypeLiteral); + typeLiteralExpression.type = finishNode(jsdocTypeLiteral); + return finishNode(typeLiteralExpression); } } - result.atToken = atToken; - result.tagName = tagName; - result.typeExpression = typeExpression; - 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 { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 52e9a62e6a9..f608684a136 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2135,7 +2135,7 @@ namespace ts { name: Identifier; typeExpression: JSDocTypeExpression; /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ - isParameterNameFirst: boolean; + isNameFirst: boolean; isBracketed: boolean; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 983ff8012bd..f20a2d422fd 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1552,19 +1552,19 @@ 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; + export function getParameterSymbolFromJSDoc(node: JSDocParameterTag): Symbol | undefined { + if (node.symbol) { + return node.symbol; } const name = node.name.text; - const grandParent = node.parent!.parent!; Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment); - if (!isFunctionLike(grandParent)) { + const func = node.parent!.parent!; + if (!isFunctionLike(func)) { return undefined; } - return find(grandParent.parameters, p => + const parameter = find(func.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.text === name); + return parameter && parameter.symbol; } export function getTypeParameterFromJsDoc(node: TypeParameterDeclaration & { parent: JSDocTemplateTag }): TypeParameterDeclaration | undefined { diff --git a/src/services/classifier.ts b/src/services/classifier.ts index 8acce0a01fa..7c61f249dcb 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -755,7 +755,7 @@ namespace ts { return; function processJSDocParameterTag(tag: JSDocParameterTag) { - if (tag.isParameterNameFirst) { + 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; @@ -767,7 +767,7 @@ namespace ts { pos = tag.typeExpression.end; } - if (!tag.isParameterNameFirst) { + 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/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; }