diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6957be484f3..881ad81cb2e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -404,6 +404,13 @@ namespace ts { // This node will now be set as the parent of all of its children as we recurse into them. parent = node; + // Binding of JsDocComment should be done before the current block scope container changes. + // because the scope of JsDocComment should not be affected by whether the current node is a + // container or not. + if (isInJavaScriptFile(node) && node.jsDocComment) { + bind(node.jsDocComment); + } + // Depending on what kind of node this is, we may have to adjust the current container // and block-container. If the current node is a container, then it is automatically // considered the current block-container as well. Also, for containers that we know @@ -468,10 +475,6 @@ namespace ts { labelStack = labelIndexMap = implicitLabels = undefined; } - if (isInJavaScriptFile(node) && node.jsDocComment) { - bind(node.jsDocComment); - } - bindReachableStatement(node); if (currentReachabilityState === Reachability.Reachable && isFunctionLikeKind(kind) && nodeIsPresent((node).body)) { @@ -857,7 +860,7 @@ namespace ts { // their container in the tree. To accomplish this, we simply add their declared // symbol to the 'locals' of the container. These symbols can then be found as // the type checker walks up the containers, checking them for matching names. - return declareSymbol(container.locals, undefined, node, symbolFlags, symbolExcludes); + return declareSymbol(container.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } @@ -1343,6 +1346,7 @@ namespace ts { return bindClassLikeDeclaration(node); case SyntaxKind.InterfaceDeclaration: return bindBlockScopedDeclaration(node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); + case SyntaxKind.JSDocTypedefTag: case SyntaxKind.TypeAliasDeclaration: return bindBlockScopedDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); case SyntaxKind.EnumDeclaration: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9509c286236..ff416ec2cc5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3403,7 +3403,7 @@ namespace ts { return links.declaredType; } - function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { + function getDeclaredTypeOfTypeAlias(symbol: Symbol, node?: Node): Type { const links = getSymbolLinks(symbol); if (!links.declaredType) { // Note that we use the links object as the target here because the symbol object is used as the unique @@ -3411,8 +3411,18 @@ namespace ts { if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { return unknownType; } - const declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); - let type = getTypeFromTypeNode(declaration.type); + + let typeNode: TypeNode; + let name: Identifier; + if (node && (node.flags & NodeFlags.JavaScriptFile)) { + const declaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag); + typeNode = declaration.typeExpression.type; + } + if (!typeNode) { + const declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); + typeNode = declaration.type; + } + let type = getTypeFromTypeNode(typeNode); if (popTypeResolution()) { links.typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); if (links.typeParameters) { @@ -3424,7 +3434,7 @@ namespace ts { } else { type = unknownType; - error(declaration.name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); + error(name, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } links.declaredType = type; } @@ -3462,13 +3472,13 @@ namespace ts { return links.declaredType; } - function getDeclaredTypeOfSymbol(symbol: Symbol): Type { + function getDeclaredTypeOfSymbol(symbol: Symbol, node?: Node): Type { Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0); if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { return getDeclaredTypeOfClassOrInterface(symbol); } if (symbol.flags & SymbolFlags.TypeAlias) { - return getDeclaredTypeOfTypeAlias(symbol); + return getDeclaredTypeOfTypeAlias(symbol, node); } if (symbol.flags & SymbolFlags.Enum) { return getDeclaredTypeOfEnum(symbol); @@ -4564,7 +4574,7 @@ namespace ts { // references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the // declared type. Instantiations are cached using the type identities of the type arguments as the key. function getTypeFromTypeAliasReference(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol): Type { - const type = getDeclaredTypeOfSymbol(symbol); + const type = getDeclaredTypeOfSymbol(symbol, node); const links = getSymbolLinks(symbol); const typeParameters = links.typeParameters; if (typeParameters) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d7c2fac2710..c887a9c7112 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -811,6 +811,10 @@ "category": "Error", "code": 1249 }, + "'{0}' tag cannot be used independently as a top level JSDoc tag.": { + "category": "Error", + "code": 1250 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", "code": 1300 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b7c011631ee..91f225931d2 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -401,6 +401,8 @@ namespace ts { return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNodes, (node).typeParameters); + case SyntaxKind.JSDocTypedefTag: + return visitNode(cbNode, (node).typeExpression); } } @@ -886,7 +888,7 @@ namespace ts { /** Invokes the provided callback then unconditionally restores the parser to the state it * was in immediately prior to invoking the callback. The result of invoking the callback - * is returned from this function. + * is returned from this function. */ function lookAhead(callback: () => T): T { return speculationHelper(callback, /*isLookAhead*/ true); @@ -2068,11 +2070,11 @@ namespace ts { } function fillSignature( - returnToken: SyntaxKind, - yieldContext: boolean, - awaitContext: boolean, - requireCompleteParameterList: boolean, - signature: SignatureDeclaration): void { + returnToken: SyntaxKind, + yieldContext: boolean, + awaitContext: boolean, + requireCompleteParameterList: boolean, + signature: SignatureDeclaration): void { const returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken; signature.typeParameters = parseTypeParameters(); @@ -3341,8 +3343,8 @@ namespace ts { if (sourceFile.languageVariant !== LanguageVariant.JSX) { return false; } - // We are in JSX context and the token is part of JSXElement. - // Fall through + // We are in JSX context and the token is part of JSXElement. + // Fall through default: return true; } @@ -4044,9 +4046,9 @@ namespace ts { const isAsync = !!(node.flags & NodeFlags.Async); node.name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : - isGenerator ? doInYieldContext(parseOptionalIdentifier) : - isAsync ? doInAwaitContext(parseOptionalIdentifier) : - parseOptionalIdentifier(); + isGenerator ? doInYieldContext(parseOptionalIdentifier) : + isAsync ? doInAwaitContext(parseOptionalIdentifier) : + parseOptionalIdentifier(); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlock(/*allowYield*/ isGenerator, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false); @@ -4984,7 +4986,7 @@ namespace ts { if (token === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { // We need to ensure that any subsequent modifiers appear on the same line - // so that when 'const' is a standalone declaration, we don't issue an error. + // so that when 'const' is a standalone declaration, we don't issue an error. if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { break; } @@ -5247,7 +5249,7 @@ namespace ts { node.decorators = decorators; setModifiers(node, modifiers); if (token === SyntaxKind.GlobalKeyword) { - // parse 'global' as name of global scope augmentation + // parse 'global' as name of global scope augmentation node.name = parseIdentifier(); node.flags |= NodeFlags.GlobalAugmentation; } @@ -5829,7 +5831,7 @@ namespace ts { } function checkForEmptyTypeArgumentList(typeArguments: NodeArray) { - if (parseDiagnostics.length === 0 && typeArguments && typeArguments.length === 0) { + if (parseDiagnostics.length === 0 && typeArguments && typeArguments.length === 0) { const start = typeArguments.pos - "<".length; const end = skipTrivia(sourceText, typeArguments.end) + ">".length; return parseErrorAtPosition(start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); @@ -5990,6 +5992,7 @@ namespace ts { Debug.assert(end <= content.length); let tags: NodeArray; + let currentParentJSDocDeclaration: Declaration; let result: JSDocComment; @@ -6097,6 +6100,11 @@ namespace ts { return handleTemplateTag(atToken, tagName); case "type": return handleTypeTag(atToken, tagName); + case "typedef": + return handleTypedefTag(atToken, tagName); + case "property": + case "prop": + return handlePropertyTag(atToken, tagName); } } @@ -6204,6 +6212,56 @@ namespace ts { return finishNode(result); } + function handlePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag { + if (!currentParentJSDocDeclaration) { + parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_cannot_be_used_independently_as_a_top_level_JSDoc_tag, tagName.text); + return undefined; + } + + const typeExpression = tryParseTypeExpression(); + skipWhitespace(); + const name = parseJSDocIdentifier(); + if (!name) { + parseErrorAtPosition(scanner.getStartPos(), /*length*/ 0, Diagnostics.Identifier_expected); + return undefined; + } + + const result = createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + result.atToken = atToken; + result.tagName = tagName; + result.name = name; + result.typeExpression = typeExpression; + return finishNode(result); + } + + function handleTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag { + const typeExpression = tryParseTypeExpression(); + skipWhitespace(); + const name = parseJSDocIdentifier(); + if (!name) { + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + return undefined; + } + + const result = createNode(SyntaxKind.JSDocTypedefTag, atToken.pos); + result.atToken = atToken; + result.tagName = tagName; + 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; + // } + // } + // } + + return result; + } + function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag { if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) { parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 546d1631945..a0cc8d054e1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -342,6 +342,10 @@ namespace ts { JSDocReturnTag, JSDocTypeTag, JSDocTemplateTag, + JSDocTypedefTag, + JSDocPropertyTag, + JSDocTypedefDeclaration, + JSDocTypeLiteral, // Synthesized list SyntaxList, @@ -1510,6 +1514,29 @@ namespace ts { typeExpression: JSDocTypeExpression; } + // @kind(SyntaxKind.JSDocTypedefTag) + export interface JSDocTypedefTag extends JSDocTag, Declaration { + name: Identifier; + typeExpression: JSDocTypeExpression; + } + + // @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 { + members: NodeArray; + } + // @kind(SyntaxKind.JSDocParameterTag) export interface JSDocParameterTag extends JSDocTag { preParameterName?: Identifier; @@ -2094,7 +2121,7 @@ namespace ts { jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt. - superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing + superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing } export const enum TypeFlags {