Merge pull request #17352 from Microsoft/jsdoc-param-type-literals

Parse jsdoc `@param` type literals
This commit is contained in:
Nathan Shively-Sanders
2017-07-26 15:17:31 -07:00
committed by GitHub
24 changed files with 693 additions and 267 deletions

View File

@@ -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(<SignatureDeclaration>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(<ObjectLiteralExpression>node);
case SyntaxKind.FunctionExpression:
@@ -2162,13 +2163,17 @@ namespace ts {
case SyntaxKind.ModuleBlock:
return updateStrictModeStatementList((<Block | ModuleBlock>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) {

View File

@@ -4493,8 +4493,8 @@ namespace ts {
if (declaration.kind === SyntaxKind.ExportAssignment) {
return links.type = checkExpression((<ExportAssignment>declaration).expression);
}
if (isInJavaScriptFile(declaration) && declaration.kind === SyntaxKind.JSDocPropertyTag && (<JSDocPropertyTag>declaration).typeExpression) {
return links.type = getTypeFromTypeNode((<JSDocPropertyTag>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<JSDocTypedefTag>(symbol, SyntaxKind.JSDocTypedefTag);
let type: Type;
if (declaration) {
if (declaration.jsDocTypeLiteral) {
type = getTypeFromTypeNode(declaration.jsDocTypeLiteral);
}
else {
type = getTypeFromTypeNode(declaration.typeExpression.type);
}
}
else {
declaration = getDeclarationOfKind<TypeAliasDeclaration>(symbol, SyntaxKind.TypeAliasDeclaration);
type = getTypeFromTypeNode(declaration.type);
}
const declaration = <JSDocTypedefTag | TypeAliasDeclaration>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((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
case SyntaxKind.JSDocTypeExpression:
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression>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) {

View File

@@ -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<T>(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
if (!node || node.kind <= SyntaxKind.LastToken) {
@@ -407,9 +410,15 @@ namespace ts {
case SyntaxKind.JSDocComment:
return visitNodes(cbNode, cbNodes, (<JSDoc>node).tags);
case SyntaxKind.JSDocParameterTag:
return visitNode(cbNode, (<JSDocParameterTag>node).preParameterName) ||
visitNode(cbNode, (<JSDocParameterTag>node).typeExpression) ||
visitNode(cbNode, (<JSDocParameterTag>node).postParameterName);
case SyntaxKind.JSDocPropertyTag:
if ((node as JSDocPropertyLikeTag).isNameFirst) {
return visitNode(cbNode, (<JSDocPropertyLikeTag>node).name) ||
visitNode(cbNode, (<JSDocPropertyLikeTag>node).typeExpression);
}
else {
return visitNode(cbNode, (<JSDocPropertyLikeTag>node).typeExpression) ||
visitNode(cbNode, (<JSDocPropertyLikeTag>node).name);
}
case SyntaxKind.JSDocReturnTag:
return visitNode(cbNode, (<JSDocReturnTag>node).typeExpression);
case SyntaxKind.JSDocTypeTag:
@@ -419,15 +428,20 @@ namespace ts {
case SyntaxKind.JSDocTemplateTag:
return visitNodes(cbNode, cbNodes, (<JSDocTemplateTag>node).typeParameters);
case SyntaxKind.JSDocTypedefTag:
return visitNode(cbNode, (<JSDocTypedefTag>node).typeExpression) ||
visitNode(cbNode, (<JSDocTypedefTag>node).fullName) ||
visitNode(cbNode, (<JSDocTypedefTag>node).name) ||
visitNode(cbNode, (<JSDocTypedefTag>node).jsDocTypeLiteral);
if ((node as JSDocTypedefTag).typeExpression &&
(node as JSDocTypedefTag).typeExpression.kind === SyntaxKind.JSDocTypeExpression) {
return visitNode(cbNode, (<JSDocTypedefTag>node).typeExpression) ||
visitNode(cbNode, (<JSDocTypedefTag>node).fullName);
}
else {
return visitNode(cbNode, (<JSDocTypedefTag>node).fullName) ||
visitNode(cbNode, (<JSDocTypedefTag>node).typeExpression);
}
case SyntaxKind.JSDocTypeLiteral:
return visitNodes(cbNode, cbNodes, (<JSDocTypeLiteral>node).jsDocPropertyTags);
case SyntaxKind.JSDocPropertyTag:
return visitNode(cbNode, (<JSDocPropertyTag>node).typeExpression) ||
visitNode(cbNode, (<JSDocPropertyTag>node).name);
for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) {
visitNode(cbNode, tag);
}
return;
case SyntaxKind.PartiallyEmittedExpression:
return visitNode(cbNode, (<PartiallyEmittedExpression>node).expression);
}
@@ -1949,14 +1963,18 @@ namespace ts {
break;
}
dotPos = scanner.getStartPos();
const node: QualifiedName = <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 ?
<JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos) :
<JSDocPropertyTag>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 = <JSDocTypeExpression>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 = <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 = <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<JSDocPropertyTag>;
}
(jsdocTypeLiteral.jsDocPropertyTags as MutableNodeArray<JSDocPropertyTag>).push(child);
}
}
if (!typedefTag.jsDocTypeLiteral) {
typedefTag.jsDocTypeLiteral = <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 = <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 = <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 = <MutableNodeArray<JSDocPropertyTag>>[];
}
(parentTag.jsDocPropertyTags as MutableNodeArray<JSDocPropertyTag>).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);
}

View File

@@ -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<JSDocPropertyTag>;
jsDocPropertyTags?: ReadonlyArray<JSDocPropertyLikeTag>;
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 {

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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) &&

View File

@@ -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;
}