support @extends in jsdoc

This commit is contained in:
Arthur Ozga 2017-09-22 16:21:31 -07:00
parent 92b7dcf20a
commit b21c46b9b5
9 changed files with 164 additions and 136 deletions

View file

@ -4986,10 +4986,10 @@ namespace ts {
baseType = getReturnTypeOfSignature(constructors[0]);
}
// In a JS file, you can use the @augments jsdoc tag to specify a base type with type parameters
// In a JS file, you can use the @augments and @extends jsdoc tags to specify a base type with type parameters
const valueDecl = type.symbol.valueDeclaration;
if (valueDecl && isInJavaScriptFile(valueDecl)) {
const augTag = getJSDocAugmentsTag(type.symbol.valueDeclaration);
const augTag = getJSDocAugmentsOrExtendsTag(type.symbol.valueDeclaration);
if (augTag) {
baseType = getTypeFromTypeNode(augTag.typeExpression.type);
}

View file

@ -423,8 +423,9 @@ namespace ts {
return visitNode(cbNode, (<JSDocReturnTag>node).typeExpression);
case SyntaxKind.JSDocTypeTag:
return visitNode(cbNode, (<JSDocTypeTag>node).typeExpression);
case SyntaxKind.JSDocAugmentsTag:
return visitNode(cbNode, (<JSDocAugmentsTag>node).typeExpression);
case SyntaxKind.JSDocAugmentsOrExtendsTag:
case SyntaxKind.JSDocExtendsTag:
return visitNode(cbNode, (<JSDocAugmentsOrExtendsTag>node).typeExpression);
case SyntaxKind.JSDocTemplateTag:
return visitNodes(cbNode, cbNodes, (<JSDocTemplateTag>node).typeParameters);
case SyntaxKind.JSDocTypedefTag:
@ -6366,7 +6367,8 @@ namespace ts {
if (tagName) {
switch (tagName.escapedText) {
case "augments":
tag = parseAugmentsTag(atToken, tagName);
case "extends":
tag = parseAugmentsOrExtendsTag(atToken, tagName);
break;
case "class":
case "constructor":
@ -6603,10 +6605,10 @@ namespace ts {
return finishNode(result);
}
function parseAugmentsTag(atToken: AtToken, tagName: Identifier): JSDocAugmentsTag {
function parseAugmentsOrExtendsTag(atToken: AtToken, tagName: Identifier): JSDocAugmentsOrExtendsTag {
const typeExpression = tryParseTypeExpression();
const result = <JSDocAugmentsTag>createNode(SyntaxKind.JSDocAugmentsTag, atToken.pos);
const result = <JSDocAugmentsOrExtendsTag>createNode(SyntaxKind.JSDocAugmentsOrExtendsTag, atToken.pos);
result.atToken = atToken;
result.tagName = tagName;
result.typeExpression = typeExpression;

View file

@ -363,7 +363,8 @@ namespace ts {
JSDocVariadicType,
JSDocComment,
JSDocTag,
JSDocAugmentsTag,
JSDocAugmentsOrExtendsTag,
JSDocExtendsTag,
JSDocClassTag,
JSDocParameterTag,
JSDocReturnTag,
@ -2159,8 +2160,12 @@ namespace ts {
kind: SyntaxKind.JSDocTag;
}
export interface JSDocAugmentsTag extends JSDocTag {
kind: SyntaxKind.JSDocAugmentsTag;
/**
* Note that `@extends` is a synonym of `@augments`.
* Both are covered by this interface.
*/
export interface JSDocAugmentsOrExtendsTag extends JSDocTag {
kind: SyntaxKind.JSDocAugmentsOrExtendsTag;
typeExpression: JSDocTypeExpression;
}

View file

@ -4072,8 +4072,8 @@ namespace ts {
}
/** Gets the JSDoc augments tag for the node if present */
export function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined {
return getFirstJSDocTag(node, SyntaxKind.JSDocAugmentsTag) as JSDocAugmentsTag;
export function getJSDocAugmentsOrExtendsTag(node: Node): JSDocAugmentsOrExtendsTag | undefined {
return getFirstJSDocTag(node, SyntaxKind.JSDocAugmentsOrExtendsTag) as JSDocAugmentsOrExtendsTag;
}
/** Gets the JSDoc class tag for the node if present */
@ -4765,8 +4765,8 @@ namespace ts {
return node.kind === SyntaxKind.JSDocComment;
}
export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag {
return node.kind === SyntaxKind.JSDocAugmentsTag;
export function isJSDocAugmentsOrExtendsTag(node: Node): node is JSDocAugmentsOrExtendsTag {
return node.kind === SyntaxKind.JSDocAugmentsOrExtendsTag;
}
export function isJSDocParameterTag(node: Node): node is JSDocParameterTag {

View file

@ -581,11 +581,11 @@ namespace ts.Completions {
return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters };
type JSDocTagWithTypeExpression = JSDocAugmentsTag | JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
type JSDocTagWithTypeExpression = JSDocAugmentsOrExtendsTag | JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression {
switch (tag.kind) {
case SyntaxKind.JSDocAugmentsTag:
case SyntaxKind.JSDocAugmentsOrExtendsTag:
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
case SyntaxKind.JSDocReturnTag:

View file

@ -101,7 +101,7 @@ function getAllTags(node: ts.Node) {
function getSomeOtherTags(node: ts.Node) {
const tags: (ts.JSDocTag | undefined)[] = [];
tags.push(ts.getJSDocAugmentsTag(node));
tags.push(ts.getJSDocAugmentsOrExtendsTag(node));
tags.push(ts.getJSDocClassTag(node));
tags.push(ts.getJSDocReturnTag(node));
const type = ts.getJSDocTypeTag(node);
@ -200,7 +200,7 @@ function getAllTags(node) {
}
function getSomeOtherTags(node) {
var tags = [];
tags.push(ts.getJSDocAugmentsTag(node));
tags.push(ts.getJSDocAugmentsOrExtendsTag(node));
tags.push(ts.getJSDocClassTag(node));
tags.push(ts.getJSDocReturnTag(node));
var type = ts.getJSDocTypeTag(node);

View file

@ -1,116 +1,116 @@
// @module: commonjs
// @includebuiltfile: typescript_standalone.d.ts
// @strict:true
/*
* Note: This test is a public API sample. The original sources can be found
* at: https://github.com/YousefED/typescript-json-schema
* https://github.com/vega/ts-json-schema-generator
* Please log a "breaking change" issue for any API breaking change affecting this issue
*/
declare var console: any;
import * as ts from "typescript";
// excerpted from https://github.com/YousefED/typescript-json-schema
// (converted from a method and modified; for example, `this: any` to compensate, among other changes)
function parseCommentsIntoDefinition(this: any,
symbol: ts.Symbol,
definition: {description?: string, [s: string]: string | undefined},
otherAnnotations: { [s: string]: true}): void {
if (!symbol) {
return;
}
// the comments for a symbol
let comments = symbol.getDocumentationComment();
if (comments.length) {
definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n")).join("");
}
// jsdocs are separate from comments
const jsdocs = symbol.getJsDocTags();
jsdocs.forEach(doc => {
// if we have @TJS-... annotations, we have to parse them
const { name, text } = doc;
if (this.userValidationKeywords[name]) {
definition[name] = this.parseValue(text);
} else {
// special annotations
otherAnnotations[doc.name] = true;
}
});
}
// excerpted from https://github.com/vega/ts-json-schema-generator
export interface Annotations {
[name: string]: any;
}
function getAnnotations(this: any, node: ts.Node): Annotations | undefined {
const symbol: ts.Symbol = (node as any).symbol;
if (!symbol) {
return undefined;
}
const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
if (!jsDocTags || !jsDocTags.length) {
return undefined;
}
const annotations: Annotations = jsDocTags.reduce((result: Annotations, jsDocTag: ts.JSDocTagInfo) => {
const value = this.parseJsDocTag(jsDocTag);
if (value !== undefined) {
result[jsDocTag.name] = value;
}
return result;
}, {});
return Object.keys(annotations).length ? annotations : undefined;
}
// these examples are artificial and mostly nonsensical
function parseSpecificTags(node: ts.Node) {
if (node.kind === ts.SyntaxKind.Parameter) {
return ts.getJSDocParameterTags(node as ts.ParameterDeclaration);
}
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
const func = node as ts.FunctionDeclaration;
if (ts.hasJSDocParameterTags(func)) {
const flat: ts.JSDocTag[] = [];
for (const tags of func.parameters.map(ts.getJSDocParameterTags)) {
if (tags) flat.push(...tags);
}
return flat;
}
}
}
function getReturnTypeFromJSDoc(node: ts.Node) {
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
return ts.getJSDocReturnType(node);
}
let type = ts.getJSDocType(node);
if (type && type.kind === ts.SyntaxKind.FunctionType) {
return (type as ts.FunctionTypeNode).type;
}
}
function getAllTags(node: ts.Node) {
ts.getJSDocTags(node);
}
function getSomeOtherTags(node: ts.Node) {
const tags: (ts.JSDocTag | undefined)[] = [];
tags.push(ts.getJSDocAugmentsTag(node));
tags.push(ts.getJSDocClassTag(node));
tags.push(ts.getJSDocReturnTag(node));
const type = ts.getJSDocTypeTag(node);
if (type) {
tags.push(type);
}
tags.push(ts.getJSDocTemplateTag(node));
return tags;
}
// @module: commonjs
// @includebuiltfile: typescript_standalone.d.ts
// @strict:true
/*
* Note: This test is a public API sample. The original sources can be found
* at: https://github.com/YousefED/typescript-json-schema
* https://github.com/vega/ts-json-schema-generator
* Please log a "breaking change" issue for any API breaking change affecting this issue
*/
declare var console: any;
import * as ts from "typescript";
// excerpted from https://github.com/YousefED/typescript-json-schema
// (converted from a method and modified; for example, `this: any` to compensate, among other changes)
function parseCommentsIntoDefinition(this: any,
symbol: ts.Symbol,
definition: {description?: string, [s: string]: string | undefined},
otherAnnotations: { [s: string]: true}): void {
if (!symbol) {
return;
}
// the comments for a symbol
let comments = symbol.getDocumentationComment();
if (comments.length) {
definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim().replace(/\r\n/g, "\n")).join("");
}
// jsdocs are separate from comments
const jsdocs = symbol.getJsDocTags();
jsdocs.forEach(doc => {
// if we have @TJS-... annotations, we have to parse them
const { name, text } = doc;
if (this.userValidationKeywords[name]) {
definition[name] = this.parseValue(text);
} else {
// special annotations
otherAnnotations[doc.name] = true;
}
});
}
// excerpted from https://github.com/vega/ts-json-schema-generator
export interface Annotations {
[name: string]: any;
}
function getAnnotations(this: any, node: ts.Node): Annotations | undefined {
const symbol: ts.Symbol = (node as any).symbol;
if (!symbol) {
return undefined;
}
const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
if (!jsDocTags || !jsDocTags.length) {
return undefined;
}
const annotations: Annotations = jsDocTags.reduce((result: Annotations, jsDocTag: ts.JSDocTagInfo) => {
const value = this.parseJsDocTag(jsDocTag);
if (value !== undefined) {
result[jsDocTag.name] = value;
}
return result;
}, {});
return Object.keys(annotations).length ? annotations : undefined;
}
// these examples are artificial and mostly nonsensical
function parseSpecificTags(node: ts.Node) {
if (node.kind === ts.SyntaxKind.Parameter) {
return ts.getJSDocParameterTags(node as ts.ParameterDeclaration);
}
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
const func = node as ts.FunctionDeclaration;
if (ts.hasJSDocParameterTags(func)) {
const flat: ts.JSDocTag[] = [];
for (const tags of func.parameters.map(ts.getJSDocParameterTags)) {
if (tags) flat.push(...tags);
}
return flat;
}
}
}
function getReturnTypeFromJSDoc(node: ts.Node) {
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
return ts.getJSDocReturnType(node);
}
let type = ts.getJSDocType(node);
if (type && type.kind === ts.SyntaxKind.FunctionType) {
return (type as ts.FunctionTypeNode).type;
}
}
function getAllTags(node: ts.Node) {
ts.getJSDocTags(node);
}
function getSomeOtherTags(node: ts.Node) {
const tags: (ts.JSDocTag | undefined)[] = [];
tags.push(ts.getJSDocAugmentsOrExtendsTag(node));
tags.push(ts.getJSDocClassTag(node));
tags.push(ts.getJSDocReturnTag(node));
const type = ts.getJSDocTypeTag(node);
if (type) {
tags.push(type);
}
tags.push(ts.getJSDocTemplateTag(node));
return tags;
}

View file

@ -15,9 +15,8 @@
// @Filename: declarations.d.ts
//// declare class Thing<T> {
//// mine: T;
//// mine: T;
//// }
goTo.marker();
verify.quickInfoIs("(local var) x: string");

View file

@ -0,0 +1,22 @@
///<reference path="fourslash.ts" />
// @allowJs: true
// @Filename: dummy.js
//// /**
//// * @extends {Thing<string>}
//// */
//// class MyStringThing extends Thing {
//// constructor() {
//// var x = this.mine;
//// x/**/;
//// }
//// }
// @Filename: declarations.d.ts
//// declare class Thing<T> {
//// mine: T;
//// }
goTo.marker();
verify.quickInfoIs("(local var) x: string");