diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 881ad81cb2e..b3f163bbef1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -744,6 +744,7 @@ namespace ts { case SyntaxKind.EnumDeclaration: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocRecordType: return ContainerFlags.IsContainer; @@ -832,6 +833,7 @@ namespace ts { case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.JSDocRecordType: + case SyntaxKind.JSDocTypeLiteral: // Interface/Object-types always have their children added to the 'members' of // their container. They are only accessible through an instance of their // container, and are never in scope otherwise (even inside the body of the @@ -1294,6 +1296,7 @@ namespace ts { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.JSDocRecordMember: + case SyntaxKind.JSDocPropertyTag: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | ((node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: @@ -1326,6 +1329,7 @@ namespace ts { case SyntaxKind.JSDocFunctionType: return bindFunctionOrConstructorType(node); case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocRecordType: return bindAnonymousDeclaration(node, SymbolFlags.TypeLiteral, "__type"); case SyntaxKind.ObjectLiteralExpression: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ff416ec2cc5..e66e5e6a9d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3412,17 +3412,14 @@ namespace ts { return unknownType; } - let typeNode: TypeNode; - let name: Identifier; + let declaration: JSDocTypedefTag | TypeAliasDeclaration; if (node && (node.flags & NodeFlags.JavaScriptFile)) { - const declaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag); - typeNode = declaration.typeExpression.type; + declaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag); } - if (!typeNode) { - const declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); - typeNode = declaration.type; + if (!declaration) { + declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); } - let type = getTypeFromTypeNode(typeNode); + let type = getTypeFromTypeNode(declaration.type); if (popTypeResolution()) { links.typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); if (links.typeParameters) { @@ -3434,7 +3431,7 @@ namespace ts { } else { type = unknownType; - error(name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); + error(declaration.name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } links.declaredType = type; } @@ -5067,6 +5064,7 @@ namespace ts { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: case SyntaxKind.JSDocRecordType: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 91f225931d2..5de1369ee09 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -402,7 +402,15 @@ namespace ts { case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNodes, (node).typeParameters); case SyntaxKind.JSDocTypedefTag: - return visitNode(cbNode, (node).typeExpression); + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).type); + case SyntaxKind.JSDocTypeLiteral: + return visitNodes(cbNodes, (node).members); + case SyntaxKind.JSDocPropertyTag: + return visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).type); } } @@ -5992,7 +6000,8 @@ namespace ts { Debug.assert(end <= content.length); let tags: NodeArray; - let currentParentJSDocDeclaration: Declaration; + let currentParentJSDocTag: JSDocParentTag; + let currentParentJSDocTagEnd: number; let result: JSDocComment; @@ -6050,6 +6059,10 @@ namespace ts { nextJSDocToken(); } + if (currentParentJSDocTag) { + finishCurrentParentTag(); + } + result = createJSDocComment(); }); @@ -6073,6 +6086,40 @@ namespace ts { } } + function finishCurrentParentTag(): void { + if (!currentParentJSDocTag) { + return; + } + + if (currentParentJSDocTag.kind === SyntaxKind.JSDocTypedefTag) { + const typedefTag = currentParentJSDocTag; + if (typedefTag.jsDocTypeTag) { + if (typedefTag.jsDocTypeTag.typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { + const typeTagtype = typedefTag.jsDocTypeTag.typeExpression.type; + if ((typeTagtype.name.kind !== SyntaxKind.Identifier) || + (typeTagtype.name).text !== "Object") { + typedefTag.type = typedefTag.jsDocTypeTag.typeExpression.type; + } + } + else { + typedefTag.type = typedefTag.jsDocTypeTag.typeExpression.type; + } + } + if (!typedefTag.type) { + const tagType = createNode(SyntaxKind.JSDocTypeLiteral, currentParentJSDocTag.pos); + if (typedefTag.jsDocPropertyTags){ + tagType.members = >[]; + addRange(tagType.members, typedefTag.jsDocPropertyTags); + } + typedefTag.type = finishNode(tagType, currentParentJSDocTagEnd); + } + } + + addTag(finishNode(currentParentJSDocTag, currentParentJSDocTagEnd)); + currentParentJSDocTag = undefined; + currentParentJSDocTagEnd = undefined; + } + function parseTag(): void { Debug.assert(token === SyntaxKind.AtToken); const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); @@ -6085,22 +6132,30 @@ namespace ts { } const tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName); - addTag(tag); + if (!currentParentJSDocTag) { + addTag(tag); + } } function handleTag(atToken: Node, tagName: Identifier): JSDocTag { if (tagName) { switch (tagName.text) { case "param": + finishCurrentParentTag(); return handleParamTag(atToken, tagName); case "return": case "returns": + finishCurrentParentTag(); return handleReturnTag(atToken, tagName); case "template": + finishCurrentParentTag(); return handleTemplateTag(atToken, tagName); case "type": + // @typedef tag is allowed to have one @type tag, therefore seeing + // a @type tag may not indicate the end of the current parent tag. return handleTypeTag(atToken, tagName); case "typedef": + finishCurrentParentTag(); return handleTypedefTag(atToken, tagName); case "property": case "prop": @@ -6130,6 +6185,25 @@ namespace ts { } } + function addToCurrentParentTag(tag: JSDocTag): void { + if (!currentParentJSDocTag) { + return; + } + switch (tag.kind) { + case SyntaxKind.JSDocPropertyTag: + if (!currentParentJSDocTag.jsDocPropertyTags) { + currentParentJSDocTag.jsDocPropertyTags = >[]; + } + currentParentJSDocTag.jsDocPropertyTags.push(tag); + break; + case SyntaxKind.JSDocTypeTag: + if (!currentParentJSDocTag.jsDocTypeTag) { + currentParentJSDocTag.jsDocTypeTag = tag; + } + break; + } + } + function tryParseTypeExpression(): JSDocTypeExpression { if (token !== SyntaxKind.OpenBraceToken) { return undefined; @@ -6205,15 +6279,33 @@ namespace ts { parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); } - const result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); + let result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.typeExpression = tryParseTypeExpression(); - return finishNode(result); + result = finishNode(result); + + let typeTagPartOfParentTag = false; + if (currentParentJSDocTag && currentParentJSDocTag.kind === SyntaxKind.JSDocTypedefTag) { + const parentTag = currentParentJSDocTag; + if (!parentTag.typeExpression && !parentTag.jsDocTypeTag) { + typeTagPartOfParentTag = true; + parentTag.jsDocTypeTag = result; + currentParentJSDocTagEnd = scanner.getStartPos(); + } + } + if (!typeTagPartOfParentTag) { + // If this @type tag is not part of the current parent tag, then + // it denotes the end of the current parent tag. + finishCurrentParentTag(); + return result; + } + + return undefined; } function handlePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag { - if (!currentParentJSDocDeclaration) { + if (!currentParentJSDocTag) { parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_cannot_be_used_independently_as_a_top_level_JSDoc_tag, tagName.text); return undefined; } @@ -6226,12 +6318,17 @@ namespace ts { return undefined; } - const result = createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + let result = createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.name = name; result.typeExpression = typeExpression; - return finishNode(result); + result.type = typeExpression.type; + result = finishNode(result); + + addToCurrentParentTag(result); + currentParentJSDocTagEnd = scanner.getStartPos(); + return undefined; } function handleTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag { @@ -6249,17 +6346,26 @@ namespace ts { result.name = name; result.typeExpression = typeExpression; - // if (typeExpression && typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { - // const jsDocTypeReference = typeExpression.type; - // if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) { - // const name = jsDocTypeReference.name; - // if (name.text === "Object") { - // currentParentJSDocDeclaration = declaration; - // } - // } - // } + if (typeExpression && typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { + const jsDocTypeReference = typeExpression.type; + if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) { + const name = jsDocTypeReference.name; + if (name.text === "Object") { + currentParentJSDocTag = result; + } + } + } + else if (!typeExpression) { + currentParentJSDocTag = result; + } - return result; + if (!currentParentJSDocTag) { + result.type = result.typeExpression.type; + return finishNode(result); + } + + currentParentJSDocTagEnd = scanner.getStartPos(); + return undefined; } function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a0cc8d054e1..31c2c8eb068 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -344,7 +344,6 @@ namespace ts { JSDocTemplateTag, JSDocTypedefTag, JSDocPropertyTag, - JSDocTypedefDeclaration, JSDocTypeLiteral, // Synthesized list @@ -616,6 +615,7 @@ namespace ts { // SyntaxKind.PropertyAssignment // SyntaxKind.ShorthandPropertyAssignment // SyntaxKind.EnumMember + // SyntaxKind.JSDocPropertyTag export interface VariableLikeDeclaration extends Declaration { propertyName?: PropertyName; dotDotDotToken?: Node; @@ -1515,25 +1515,26 @@ namespace ts { } // @kind(SyntaxKind.JSDocTypedefTag) - export interface JSDocTypedefTag extends JSDocTag, Declaration { + export interface JSDocTypedefTag extends JSDocTag, Declaration, JSDocParentTag { name: Identifier; - typeExpression: JSDocTypeExpression; + typeExpression?: JSDocTypeExpression; + type: JSDocType; + } + + export interface JSDocParentTag extends JSDocTag { + jsDocPropertyTags?: NodeArray; + jsDocTypeTag?: JSDocTypeTag; } // @kind(SyntaxKind.JSDocPropertyTag) export interface JSDocPropertyTag extends JSDocTag, TypeElement { name: Identifier; typeExpression: JSDocTypeExpression; - } - - // @kind(SyntaxKind.JSDocTypedefDeclaration) - export interface JSDocTypedefDeclaration extends Declaration { - name: Identifier; type: TypeNode; } // @kind(SyntaxKind.JSDocTypeLiteral) - export interface JSDocTypeLiteral extends TypeNode { + export interface JSDocTypeLiteral extends JSDocType { members: NodeArray; } diff --git a/tests/cases/fourslash/server/jsdocCompletions.ts b/tests/cases/fourslash/server/jsdocCompletions.ts new file mode 100644 index 00000000000..c8dc1bbe481 --- /dev/null +++ b/tests/cases/fourslash/server/jsdocCompletions.ts @@ -0,0 +1,15 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: jsdocCompletion_typedef.js +//// /** +//// * @typedef {Object} Person +//// * @property {string} personName +//// * @type {Person} +//// */ +//// var x1; +//// x1/**/ + +goTo.marker(); +edit.insert('.'); +verify.memberListContains('personName'); \ No newline at end of file