Skip to content

Commit

Permalink
fix: Resolve type parameters in concrete subclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Dec 26, 2020
1 parent f364c1f commit 85cd06d
Show file tree
Hide file tree
Showing 24 changed files with 789 additions and 155 deletions.
120 changes: 110 additions & 10 deletions src/lib/converter/factories/signature.ts
Expand Up @@ -3,6 +3,7 @@ import * as assert from "assert";
import {
DeclarationReflection,
ParameterReflection,
PredicateType,
Reflection,
ReflectionFlag,
ReflectionKind,
Expand All @@ -12,6 +13,7 @@ import {
import { Context } from "../context";
import { ConverterEvents } from "../converter-events";
import { convertDefaultValue } from "../convert-expression";
import { removeUndefined } from "../utils/reflections";

export function createSignature(
context: Context,
Expand Down Expand Up @@ -58,10 +60,15 @@ export function createSignature(
declaration?.parameters
);

sigRef.type = context.converter.convertType(
context,
declaration?.type ?? signature.getReturnType()
);
const predicate = context.checker.getTypePredicateOfSignature(signature);
if (predicate) {
sigRef.type = convertPredicate(predicate, context.withScope(sigRef));
} else {
sigRef.type = context.converter.convertType(
context.withScope(sigRef),
signature.getReturnType()
);
}

context.registerReflection(sigRef, undefined);
context.trigger(
Expand Down Expand Up @@ -90,16 +97,54 @@ function convertParameters(

paramRefl.type = context.converter.convertType(
context.withScope(paramRefl),
parameterNodes?.[i].type ?? getTypeOfSymbol(param, context)
context.checker.getTypeOfSymbolAtLocation(param, declaration)
);

if (declaration.questionToken) {
paramRefl.type = removeUndefined(paramRefl.type);
}

paramRefl.defaultValue = convertDefaultValue(parameterNodes?.[i]);
paramRefl.setFlag(ReflectionFlag.Optional, !!declaration.questionToken);
paramRefl.setFlag(ReflectionFlag.Rest, !!declaration.dotDotDotToken);
return paramRefl;
});
}

export function convertParameterNodes(
context: Context,
sigRef: SignatureReflection,
parameters: readonly ts.ParameterDeclaration[]
) {
return parameters.map((param) => {
const paramRefl = new ParameterReflection(
/__\d+/.test(param.name.getText())
? "__namedParameters"
: param.name.getText(),
ReflectionKind.Parameter,
sigRef
);
context.registerReflection(
paramRefl,
context.getSymbolAtLocation(param)
);

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

if (param.questionToken) {
paramRefl.type = removeUndefined(paramRefl.type);
}

paramRefl.defaultValue = convertDefaultValue(param);
paramRefl.setFlag(ReflectionFlag.Optional, !!param.questionToken);
paramRefl.setFlag(ReflectionFlag.Rest, !!param.dotDotDotToken);
return paramRefl;
});
}

function convertTypeParameters(
context: Context,
parent: Reflection,
Expand Down Expand Up @@ -128,9 +173,64 @@ function convertTypeParameters(
});
}

function getTypeOfSymbol(symbol: ts.Symbol, context: Context) {
const decl = symbol.getDeclarations()?.[0];
return decl
? context.checker.getTypeOfSymbolAtLocation(symbol, decl)
: context.checker.getDeclaredTypeOfSymbol(symbol);
export function convertTypeParameterNodes(
context: Context,
parent: Reflection,
parameters: readonly ts.TypeParameterDeclaration[] | undefined
) {
return parameters?.map((param) => {
const constraint = param.constraint
? context.converter.convertType(context, param.constraint)
: void 0;
const defaultType = param.default
? context.converter.convertType(context, param.default)
: void 0;
const paramRefl = new TypeParameterReflection(
param.name.text,
constraint,
defaultType,
parent
);
context.registerReflection(paramRefl, undefined);
context.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, paramRefl);

return paramRefl;
});
}

function convertPredicate(
predicate: ts.TypePredicate,
context: Context
): PredicateType {
let name: string;
switch (predicate.kind) {
case ts.TypePredicateKind.This:
case ts.TypePredicateKind.AssertsThis:
name = "this";
break;
case ts.TypePredicateKind.Identifier:
case ts.TypePredicateKind.AssertsIdentifier:
name = predicate.parameterName;
break;
}

let asserts: boolean;
switch (predicate.kind) {
case ts.TypePredicateKind.This:
case ts.TypePredicateKind.Identifier:
asserts = false;
break;
case ts.TypePredicateKind.AssertsThis:
case ts.TypePredicateKind.AssertsIdentifier:
asserts = true;
break;
}

return new PredicateType(
name,
asserts,
predicate.type
? context.converter.convertType(context, predicate.type)
: void 0
);
}
6 changes: 4 additions & 2 deletions src/lib/converter/plugins/CommentPlugin.ts
Expand Up @@ -142,10 +142,12 @@ export class CommentPlugin extends ConverterComponent {
}

if (comment.hasTag("event")) {
reflection.kind = ReflectionKind.Event;
if (reflection.kindOf(ReflectionKind.CallSignature)) {
reflection.parent?.setFlag(ReflectionFlag.Public);
if (reflection.parent) {
reflection.parent.kind = ReflectionKind.Event;
}
}
reflection.kind = ReflectionKind.Event;
comment.removeTags("event");
}

Expand Down
101 changes: 79 additions & 22 deletions src/lib/converter/types.ts
Expand Up @@ -21,13 +21,19 @@ import {
UnionType,
UnknownType,
MappedType,
SignatureReflection,
} from "../models";
import { TemplateLiteralType } from "../models/types/template-literal";
import { zip } from "../utils/array";
import { Context } from "./context";
import { ConverterEvents } from "./converter-events";
import { createSignature } from "./factories/signature";
import {
convertParameterNodes,
convertTypeParameterNodes,
createSignature,
} from "./factories/signature";
import { convertSymbol } from "./symbols";
import { removeUndefined } from "./utils/reflections";

export interface TypeConverter<
TNode extends ts.TypeNode = ts.TypeNode,
Expand All @@ -49,6 +55,7 @@ export function loadConverters() {
arrayConverter,
conditionalConverter,
exprWithTypeArgsConverter,
functionTypeConverter,
indexedAccessConverter,
inferredConverter,
intersectionConverter,
Expand Down Expand Up @@ -206,6 +213,71 @@ const exprWithTypeArgsConverter: TypeConverter<
convertType: requestBugReport,
};

const functionTypeConverter: TypeConverter<ts.FunctionTypeNode, ts.Type> = {
kind: [ts.SyntaxKind.FunctionType],
convert(context, node) {
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 = convertType(signatureCtx, node.type);
signature.parameters = convertParameterNodes(
signatureCtx,
signature,
node.parameters
);
signature.typeParameters = convertTypeParameterNodes(
signatureCtx,
reflection,
node.typeParameters
);

return new ReflectionType(reflection);
},
convertType(context, type) {
if (!type.symbol) {
return new IntrinsicType("Function");
}

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

reflection.signatures = [
createSignature(
context.withScope(reflection),
ReflectionKind.CallSignature,
type.getCallSignatures()[0]
),
];

return new ReflectionType(reflection);
},
};

const indexedAccessConverter: TypeConverter<
ts.IndexedAccessTypeNode,
ts.IndexedAccessType
Expand Down Expand Up @@ -314,10 +386,8 @@ const predicateConverter: TypeConverter<ts.TypePredicateNode, ts.Type> = {

// This is a horrible thing... we're going to want to split this into converters
// for different types at some point.
const typeLiteralConverter: TypeConverter<
ts.TypeLiteralNode | ts.FunctionTypeNode
> = {
kind: [ts.SyntaxKind.TypeLiteral, ts.SyntaxKind.FunctionType],
const typeLiteralConverter: TypeConverter<ts.TypeLiteralNode> = {
kind: [ts.SyntaxKind.TypeLiteral],
convert(context, node) {
const symbol = context.getSymbolAtLocation(node) ?? node.symbol;
const type = context.getTypeAtLocation(node);
Expand Down Expand Up @@ -452,9 +522,10 @@ const referenceConverter: TypeConverter<
context.resolveAliasedSymbol(symbol),
context.project
);
ref.typeArguments = type.aliasTypeArguments?.map((ref) =>
convertType(context, ref)
);
ref.typeArguments = (type.aliasSymbol
? type.aliasTypeArguments
: type.typeArguments
)?.map((ref) => convertType(context, ref));
return ref;
},
};
Expand Down Expand Up @@ -759,17 +830,3 @@ function kindToModifier(
return undefined;
}
}

function removeUndefined(type: Type) {
if (type instanceof UnionType) {
const types = type.types.filter(
(t) => !t.equals(new IntrinsicType("undefined"))
);
if (types.length === 1) {
return types[0];
}
type.types = types;
return type;
}
return type;
}
15 changes: 15 additions & 0 deletions src/lib/converter/utils/reflections.ts
@@ -0,0 +1,15 @@
import { IntrinsicType, Type, UnionType } from "../../models";

export function removeUndefined(type: Type) {
if (type instanceof UnionType) {
const types = type.types.filter(
(t) => !t.equals(new IntrinsicType("undefined"))
);
if (types.length === 1) {
return types[0];
}
type.types = types;
return type;
}
return type;
}
10 changes: 9 additions & 1 deletion src/lib/ts-internal.ts
Expand Up @@ -5,7 +5,15 @@ import * as ts from "typescript";
*/
declare module "typescript" {
interface Node {
// https://github.com/Microsoft/TypeScript/blob/v2.1.4/src/compiler/types.ts#L497
// https://github.com/microsoft/TypeScript/blob/v4.1.3/src/compiler/types.ts#L847
symbol?: ts.Symbol;
}

interface TypeChecker {
// https://github.com/microsoft/TypeScript/blob/v4.1.3/src/compiler/types.ts#L4145
// https://github.com/microsoft/TypeScript/issues/42118
getTypePredicateOfSignature(
signature: ts.Signature
): ts.TypePredicate | undefined;
}
}
7 changes: 7 additions & 0 deletions src/test/converter/alias/specs.json
Expand Up @@ -109,6 +109,13 @@
"type": {
"type": "reference",
"id": 30,
"typeArguments": [
{
"type": "reference",
"id": 29,
"name": "ExampleParam"
}
],
"name": "Example"
},
"defaultValue": "makeExample()"
Expand Down

0 comments on commit 85cd06d

Please sign in to comment.