Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
12 changes: 7 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like SyntaxKind.JSDocExtendsTag is never assigned anywhere?

return visitNode(cbNode, (<JSDocAugmentsOrExtendsTag>node).typeExpression);
case SyntaxKind.JSDocTemplateTag:
return visitNodes(cbNode, cbNodes, (<JSDocTemplateTag>node).typeParameters);
case SyntaxKind.JSDocTypedefTag:
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 8 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ namespace ts {
JSDocVariadicType,
JSDocComment,
JSDocTag,
JSDocAugmentsTag,
JSDocAugmentsOrExtendsTag,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep JSDocAugmentsTag and just let @extends be a silent synonym for it in the parser. I don't think it's bad, for example, if the roundtrip emit converts @extends to @augment. And that's the worst side-effect I can think of.

JSDocExtendsTag,
JSDocClassTag,
JSDocParameterTag,
JSDocReturnTag,
Expand Down Expand Up @@ -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;
}

Expand Down
8 changes: 4 additions & 4 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is part of the public API now, we should not break that.

return node.kind === SyntaxKind.JSDocAugmentsOrExtendsTag;
}

export function isJSDocParameterTag(node: Node): node is JSDocParameterTag {
Expand Down
4 changes: 2 additions & 2 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/APISample_jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
232 changes: 116 additions & 116 deletions tests/cases/compiler/APISample_jsdoc.ts
Original file line number Diff line number Diff line change
@@ -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;
}
3 changes: 1 addition & 2 deletions tests/cases/fourslash/jsDocAugments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 22 additions & 0 deletions tests/cases/fourslash/jsDocExtends.ts
Original file line number Diff line number Diff line change
@@ -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");