support @extends in jsdoc
This commit is contained in:
parent
92b7dcf20a
commit
b21c46b9b5
9 changed files with 164 additions and 136 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,8 @@
|
|||
|
||||
// @Filename: declarations.d.ts
|
||||
//// declare class Thing<T> {
|
||||
//// mine: T;
|
||||
//// mine: T;
|
||||
//// }
|
||||
|
||||
goTo.marker();
|
||||
verify.quickInfoIs("(local var) x: string");
|
||||
|
||||
|
|
22
tests/cases/fourslash/jsDocExtends.ts
Normal file
22
tests/cases/fourslash/jsDocExtends.ts
Normal 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");
|
Loading…
Reference in a new issue