Skip to content

Commit

Permalink
Fill in conversion implementations for remaining typescript types (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
hoodmane authored May 6, 2024
1 parent 2b35f7c commit b689246
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 10 deletions.
80 changes: 70 additions & 10 deletions sphinx_js/js/convertType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ import { ReadonlySymbolToType } from "./redirectPrivateAliases.js";
*
* Most visitor nodes should be similar to the implementation of getTypeString
* on the same type.
*
* TODO: implement the remaining not implemented cases and add test coverage.
*/
class TypeConverter implements TypeVisitor<Type> {
private readonly basePath: string;
Expand Down Expand Up @@ -98,11 +96,17 @@ class TypeConverter implements TypeVisitor<Type> {
}

conditional(type: ConditionalType): Type {
throw new Error("Not implemented");
return [
...this.convert(type.checkType, TypeContext.conditionalCheck),
" extends ",
...this.convert(type.extendsType, TypeContext.conditionalExtends),
" ? ",
...this.convert(type.trueType, TypeContext.conditionalTrue),
" : ",
...this.convert(type.falseType, TypeContext.conditionalFalse),
];
}
indexedAccess(type: IndexedAccessType): Type {
// TODO: switch to correct impl
return ["<TODO: not implemented indexedAccess>"];
return [
...this.convert(type.objectType, TypeContext.indexedObject),
"[",
Expand All @@ -111,7 +115,13 @@ class TypeConverter implements TypeVisitor<Type> {
];
}
inferred(type: InferredType): Type {
throw new Error("Not implemented");
if (type.constraint) {
return [
`infer ${type.name} extends `,
...this.convert(type.constraint, TypeContext.inferredConstraint),
];
}
return [`infer ${type.name}`];
}
intersection(type: IntersectionType): Type {
const result: Type = [];
Expand All @@ -132,10 +142,48 @@ class TypeConverter implements TypeVisitor<Type> {
return [JSON.stringify(type.value)];
}
mapped(type: MappedType): Type {
throw new Error("Not implemented");
const read = {
"+": "readonly ",
"-": "-readonly ",
"": "",
}[type.readonlyModifier ?? ""];

const opt = {
"+": "?",
"-": "-?",
"": "",
}[type.optionalModifier ?? ""];

const parts: Type = [
"{ ",
read,
"[",
type.parameter,
" in ",
...this.convert(type.parameterType, TypeContext.mappedParameter),
];

if (type.nameType) {
parts.push(
" as ",
...this.convert(type.nameType, TypeContext.mappedName),
);
}

parts.push(
"]",
opt,
": ",
...this.convert(type.templateType, TypeContext.mappedTemplate),
" }",
);
return parts;
}
optional(type: OptionalType): Type {
throw new Error("Not implemented");
return [
...this.convert(type.elementType, TypeContext.optionalElement),
"?",
];
}
predicate(type: PredicateType): Type {
// Consider using typedoc's representation for this instead of this custom
Expand Down Expand Up @@ -281,10 +329,22 @@ class TypeConverter implements TypeVisitor<Type> {
throw new Error("Not implemented");
}
rest(type: RestType): Type {
throw new Error("Not implemented");
return ["...", ...this.convert(type.elementType, TypeContext.restElement)];
}
templateLiteral(type: TemplateLiteralType): Type {
throw new Error("Not implemented");
return [
"`",
type.head,
...type.tail.flatMap(([type, text]) => {
return [
"${",
...this.convert(type, TypeContext.templateLiteralElement),
"}",
text,
];
}),
"`",
];
}
tuple(type: TupleType): Type {
const result: Type = [];
Expand Down
18 changes: 18 additions & 0 deletions tests/test_typedoc_analysis/source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,21 @@ export class HasHiddenTypeMember {
*/
hasHiddenType: number;
}

export let restType: [...number[]];

export let indexedAccessType: FunctionInterface["length"];

export type ConditionalType<T> = T extends A ? 1 : 2;

export type InferredType<T> = T extends Promise<infer S> ? S : T;

export type keys = "A" | "B" | "C";

export type MappedType1 = { [property in keys]: number };
export type MappedType2 = { -readonly [property in keys]?: number };
export type MappedType3 = { readonly [property in keys]-?: number };

export type TemplateLiteral = `${number}: ${string}`;

export type OptionalType = [number?];
28 changes: 28 additions & 0 deletions tests/test_typedoc_analysis/test_typedoc_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,3 +663,31 @@ def test_hidden_type_top_level(self):
def test_hidden_type_member(self):
obj = self.analyzer.get_object(["HasHiddenTypeMember"])
assert obj.members[0].type == []

def test_rest_type(self):
obj = self.analyzer.get_object(["restType"])
assert join_type(obj.type) == "[...number[]]"

def test_indexed_access_type(self):
obj = self.analyzer.get_object(["indexedAccessType"])
assert join_type(obj.type) == 'FunctionInterface["length"]'

def test_conditional_type(self):
obj = self.analyzer.get_object(["ConditionalType"])
assert join_type(obj.type) == "T extends A ? 1 : 2"

def test_inferred_type(self):
obj = self.analyzer.get_object(["InferredType"])
assert join_type(obj.type) == "T extends Promise<infer S> ? S : T"

def test_mapped_type(self):
obj = self.analyzer.get_object(["MappedType1"])
assert join_type(obj.type) == "{ [property in keys]: number }"
obj = self.analyzer.get_object(["MappedType2"])
assert join_type(obj.type) == "{ -readonly [property in keys]?: number }"
obj = self.analyzer.get_object(["MappedType3"])
assert join_type(obj.type) == "{ readonly [property in keys]-?: number }"

def test_template_literal(self):
obj = self.analyzer.get_object(["TemplateLiteral"])
assert join_type(obj.type) == "`${number}: ${string}`"

0 comments on commit b689246

Please sign in to comment.