Skip to content

Commit

Permalink
feat: Support for JSDoc types
Browse files Browse the repository at this point in the history
Closes #1214
Closes #1437
  • Loading branch information
Gerrit0 committed Dec 31, 2020
1 parent 3079c89 commit 1153735
Show file tree
Hide file tree
Showing 10 changed files with 838 additions and 43 deletions.
18 changes: 16 additions & 2 deletions src/lib/converter/converter.ts
Expand Up @@ -157,12 +157,18 @@ export class Converter extends ChildableComponent<
return project;
}

/** @internal */
convertSymbol(context: Context, symbol: ts.Symbol) {
convertSymbol(context, symbol);
}

/**
* Convert the given TypeScript type into its TypeDoc type reflection.
*
* @param context The context object describing the current state the converter is in.
* @param referenceTarget The target to be used to attempt to resolve reference types
* @returns The TypeDoc type reflection representing the given node and type.
* @internal
*/
convertType(
context: Context,
Expand All @@ -171,6 +177,7 @@ export class Converter extends ChildableComponent<
return convertType(context, node);
}

/** @internal */
getNodesForSymbol(symbol: ts.Symbol, kind: ReflectionKind) {
const wantedKinds: ts.SyntaxKind[] = {
[ReflectionKind.Project]: [ts.SyntaxKind.SourceFile],
Expand All @@ -187,11 +194,15 @@ export class Converter extends ChildableComponent<
ts.SyntaxKind.VariableDeclaration,
],
[ReflectionKind.Class]: [ts.SyntaxKind.ClassDeclaration],
[ReflectionKind.Interface]: [ts.SyntaxKind.InterfaceDeclaration],
[ReflectionKind.Interface]: [
ts.SyntaxKind.InterfaceDeclaration,
ts.SyntaxKind.JSDocTypedefTag,
],
[ReflectionKind.Constructor]: [ts.SyntaxKind.Constructor],
[ReflectionKind.Property]: [
ts.SyntaxKind.PropertyDeclaration,
ts.SyntaxKind.PropertySignature,
ts.SyntaxKind.JSDocPropertyTag,
],
[ReflectionKind.Method]: [
ts.SyntaxKind.MethodDeclaration,
Expand Down Expand Up @@ -223,7 +234,10 @@ export class Converter extends ChildableComponent<
[ReflectionKind.ObjectLiteral]: [
ts.SyntaxKind.ObjectLiteralExpression,
],
[ReflectionKind.TypeAlias]: [ts.SyntaxKind.TypeAliasDeclaration],
[ReflectionKind.TypeAlias]: [
ts.SyntaxKind.TypeAliasDeclaration,
ts.SyntaxKind.JSDocTypedefTag,
],
[ReflectionKind.Event]: [], /// this needs to go away
[ReflectionKind.Reference]: [
ts.SyntaxKind.NamespaceExport,
Expand Down
13 changes: 13 additions & 0 deletions src/lib/converter/factories/comment.ts
Expand Up @@ -95,6 +95,19 @@ function getJSDocCommentRanges(node: ts.Node, text: string): ts.CommentRange[] {
* @returns The raw comment string or undefined if no comment could be found.
*/
export function getRawComment(node: ts.Node): string | undefined {
// This happens if we are converting a JS project that has @typedef "interfaces"
// with an @property tag, a @typedef type alias, a callback with parameters, etc.
if (
ts.isJSDocTypedefTag(node) ||
ts.isJSDocPropertyTag(node) ||
ts.isJSDocParameterTag(node) ||
ts.isJSDocCallbackTag(node)
) {
// Also strip off leading dashes:
// @property {string} name - docs
return node.comment?.replace(/^\s*-\s*/, "");
}

if (
node.parent &&
node.parent.kind === ts.SyntaxKind.VariableDeclarationList
Expand Down
54 changes: 42 additions & 12 deletions src/lib/converter/factories/signature.ts
Expand Up @@ -89,34 +89,52 @@ function convertParameters(
) {
return parameters.map((param, i) => {
const declaration = param.valueDeclaration;
assert(declaration && ts.isParameter(declaration));
assert(
declaration &&
(ts.isParameter(declaration) ||
ts.isJSDocParameterTag(declaration))
);
const paramRefl = new ParameterReflection(
/__\d+/.test(param.name) ? "__namedParameters" : param.name,
ReflectionKind.Parameter,
sigRef
);
context.registerReflection(paramRefl, param);
context.trigger(
ConverterEvents.CREATE_PARAMETER,
paramRefl,
declaration
);

paramRefl.type = context.converter.convertType(
context.withScope(paramRefl),
context.checker.getTypeOfSymbolAtLocation(param, declaration)
);

if (declaration.questionToken) {
const isOptional = ts.isParameter(declaration)
? !!declaration.questionToken
: declaration.isBracketed;
if (isOptional) {
paramRefl.type = removeUndefined(paramRefl.type);
}

paramRefl.defaultValue = convertDefaultValue(parameterNodes?.[i]);
paramRefl.setFlag(ReflectionFlag.Optional, !!declaration.questionToken);
paramRefl.setFlag(ReflectionFlag.Rest, !!declaration.dotDotDotToken);
paramRefl.setFlag(ReflectionFlag.Optional, isOptional);
paramRefl.setFlag(
ReflectionFlag.Rest,
ts.isParameter(declaration)
? !!declaration.dotDotDotToken
: !!declaration.typeExpression &&
ts.isJSDocVariadicType(declaration.typeExpression.type)
);
return paramRefl;
});
}

export function convertParameterNodes(
context: Context,
sigRef: SignatureReflection,
parameters: readonly ts.ParameterDeclaration[]
parameters: readonly (ts.JSDocParameterTag | ts.ParameterDeclaration)[]
) {
return parameters.map((param) => {
const paramRefl = new ParameterReflection(
Expand All @@ -133,16 +151,25 @@ export function convertParameterNodes(

paramRefl.type = context.converter.convertType(
context.withScope(paramRefl),
param.type
ts.isParameter(param) ? param.type : param.typeExpression?.type
);

if (param.questionToken) {
const isOptional = ts.isParameter(param)
? !!param.questionToken
: param.isBracketed;
if (isOptional) {
paramRefl.type = removeUndefined(paramRefl.type);
}

paramRefl.defaultValue = convertDefaultValue(param);
paramRefl.setFlag(ReflectionFlag.Optional, !!param.questionToken);
paramRefl.setFlag(ReflectionFlag.Rest, !!param.dotDotDotToken);
paramRefl.setFlag(ReflectionFlag.Optional, isOptional);
paramRefl.setFlag(
ReflectionFlag.Rest,
ts.isParameter(param)
? !!param.dotDotDotToken
: !!param.typeExpression &&
ts.isJSDocVariadicType(param.typeExpression.type)
);
return paramRefl;
});
}
Expand Down Expand Up @@ -177,7 +204,6 @@ function convertTypeParameters(

export function convertTypeParameterNodes(
context: Context,
parent: Reflection,
parameters: readonly ts.TypeParameterDeclaration[] | undefined
) {
return parameters?.map((param) => {
Expand All @@ -191,10 +217,14 @@ export function convertTypeParameterNodes(
param.name.text,
constraint,
defaultType,
parent
context.scope
);
context.registerReflection(paramRefl, undefined);
context.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, paramRefl);
context.trigger(
ConverterEvents.CREATE_TYPE_PARAMETER,
paramRefl,
param
);

return paramRefl;
});
Expand Down
146 changes: 146 additions & 0 deletions src/lib/converter/jsdoc.ts
@@ -0,0 +1,146 @@
// Converter functions for JSDoc defined types
// @typedef
// @callback

import { ok } from "assert";
import * as ts from "typescript";
import {
DeclarationReflection,
IntrinsicType,
ReflectionKind,
ReflectionType,
SignatureReflection,
} from "../models";
import { flatMap } from "../utils/array";
import { Context } from "./context";
import { ConverterEvents } from "./converter-events";
import {
convertParameterNodes,
convertTypeParameterNodes,
} from "./factories/signature";

export function convertJsDocTypedef(
context: Context,
symbol: ts.Symbol,
declaration: ts.JSDocTypedefTag,
nameOverride?: string
) {
if (
declaration.typeExpression &&
ts.isJSDocTypeLiteral(declaration.typeExpression)
) {
convertJsDocInterface(context, declaration, symbol, nameOverride);
return;
}

const reflection = context.createDeclarationReflection(
ReflectionKind.TypeAlias,
symbol,
nameOverride
);

reflection.type = context.converter.convertType(
context.withScope(reflection),
declaration.typeExpression?.type
);

convertTemplateParameters(
context.withScope(reflection),
declaration.parent
);
}

export function convertJsDocCallback(
context: Context,
symbol: ts.Symbol,
declaration: ts.JSDocCallbackTag,
nameOverride?: string
) {
const alias = context.createDeclarationReflection(
ReflectionKind.TypeAlias,
symbol,
nameOverride
);
const ac = context.withScope(alias);

alias.type = convertJsDocSignature(ac, declaration.typeExpression);
convertTemplateParameters(ac, declaration.parent);
}

function convertJsDocInterface(
context: Context,
declaration: ts.JSDocTypedefTag,
symbol: ts.Symbol,
nameOverride?: string
) {
const reflection = context.createDeclarationReflection(
ReflectionKind.Interface,
symbol,
nameOverride
);
const rc = context.withScope(reflection);

const type = context.checker.getDeclaredTypeOfSymbol(symbol);
for (const s of type.getProperties()) {
context.converter.convertSymbol(rc, s);
}

convertTemplateParameters(rc, declaration.parent);
}

function convertJsDocSignature(context: Context, node: ts.JSDocSignature) {
const symbol = context.getSymbolAtLocation(node) ?? node.symbol;
const type = context.getTypeAtLocation(node);
if (!symbol || !type) {
return new IntrinsicType("Function");
}

const reflection = new DeclarationReflection(
"__type",
ReflectionKind.TypeLiteral,
context.scope
);
context.registerReflection(reflection, symbol);
context.trigger(ConverterEvents.CREATE_DECLARATION, reflection, node);

const signature = new SignatureReflection(
"__type",
ReflectionKind.CallSignature,
reflection
);
context.registerReflection(signature, void 0);
const signatureCtx = context.withScope(signature);

reflection.signatures = [signature];
signature.type = context.converter.convertType(
signatureCtx,
node.type?.typeExpression?.type
);
signature.parameters = convertParameterNodes(
signatureCtx,
signature,
node.parameters
);
signature.typeParameters = convertTemplateParameterNodes(
context.withScope(reflection),
node.typeParameters
);

return new ReflectionType(reflection);
}

function convertTemplateParameters(context: Context, node: ts.JSDoc) {
ok(context.scope instanceof DeclarationReflection);
context.scope.typeParameters = convertTemplateParameterNodes(
context,
node.tags?.filter(ts.isJSDocTemplateTag)
);
}

function convertTemplateParameterNodes(
context: Context,
nodes: readonly ts.JSDocTemplateTag[] | undefined
) {
const params = flatMap(nodes ?? [], (tag) => tag.typeParameters);
return convertTypeParameterNodes(context, params);
}
8 changes: 7 additions & 1 deletion src/lib/converter/plugins/CommentPlugin.ts
Expand Up @@ -174,8 +174,14 @@ export class CommentPlugin extends ConverterComponent {
private onCreateTypeParameter(
_context: Context,
reflection: TypeParameterReflection,
_node?: ts.Node
node?: ts.Node
) {
if (node && ts.isJSDocTemplateTag(node.parent)) {
if (node.parent.comment) {
reflection.comment = new Comment(node.parent.comment);
}
}

const comment = reflection.parent && reflection.parent.comment;
if (comment) {
let tag = comment.getTag("typeparam", reflection.name);
Expand Down

0 comments on commit 1153735

Please sign in to comment.