Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional type support #105

Merged
merged 36 commits into from
Jun 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0fb2a1e
Add build and test task
kayahr Jun 1, 2019
2b2665f
Implement crude conditional type node parser
kayahr Jun 1, 2019
a135acf
Fix linter warning
kayahr Jun 1, 2019
8c97e42
Add some tests for conditional type
kayahr Jun 1, 2019
c8db06b
Only unwrap types internally when needed
kayahr Jun 3, 2019
898ef09
Merge branch 'master' into feature/conditional-type
Jun 5, 2019
c7c2aa2
Update schema URLs to draft-07
Jun 5, 2019
b0b71df
Use already existing derefType
Jun 5, 2019
b9a5a5e
Remove accidentally commited tasks.json
Jun 5, 2019
00ed2ba
Merge branch 'master' into feature/conditional-type
Jun 5, 2019
df40298
Merge branch 'master' into feature/conditional-type
Jun 6, 2019
300053a
Merge branch 'master' into feature/conditional-type
Jun 7, 2019
31816a4
Split types "any" and "unknown"
Jun 7, 2019
e0089ed
Add NeverTypeFormatter in case this type is exposed
Jun 7, 2019
10c71ae
Add new classes to index
Jun 7, 2019
81027bc
Move isAssignable check to utility function
Jun 7, 2019
5484887
Better variable names
Jun 7, 2019
b27d0d9
Fix stack overflow in isAssignableTo for circular dependencies
Jun 7, 2019
3fd9004
Simplify condition type node parser
Jun 7, 2019
9d7500a
Add test for Omit
Jun 7, 2019
eac11e3
Simplified conditional type node parser even more
Jun 7, 2019
093f847
Support enums in conditionals types
Jun 7, 2019
3aee97a
Merge branch 'master' into feature/conditional-type
Jun 7, 2019
587f31e
Merge branch 'master' into feature/conditional-type
Jun 7, 2019
ae5fbcc
Combine unions
Jun 7, 2019
5faa296
Deref type
Jun 7, 2019
dcba459
More tests
Jun 7, 2019
a5fc384
Merge branch 'master' into feature/conditional-type
Jun 8, 2019
6d685b0
Improve type narrowing so definition types are kept
Jun 8, 2019
1e761a8
Remove accidentally commited comment
Jun 9, 2019
a07c94a
Merge branch 'master' into feature/conditional-type
Jun 11, 2019
e43afb4
Merge branch 'master' into feature/conditional-type
Jun 21, 2019
a0df82d
Only narrow down result type when type parameter matching the check type
Jun 22, 2019
d7cdb26
Ignore annotations from standard typescript types
Jun 22, 2019
c6d05b5
Merge branch 'master' into feature/conditional-type
Jun 22, 2019
f0cf842
Merge branch 'master' into feature/conditional-type
Jun 23, 2019
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: 4 additions & 0 deletions factory/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { EnumTypeFormatter } from "../src/TypeFormatter/EnumTypeFormatter";
import { IntersectionTypeFormatter } from "../src/TypeFormatter/IntersectionTypeFormatter";
import { LiteralTypeFormatter } from "../src/TypeFormatter/LiteralTypeFormatter";
import { LiteralUnionTypeFormatter } from "../src/TypeFormatter/LiteralUnionTypeFormatter";
import { NeverTypeFormatter } from "../src/TypeFormatter/NeverTypeFormatter";
import { NullTypeFormatter } from "../src/TypeFormatter/NullTypeFormatter";
import { NumberTypeFormatter } from "../src/TypeFormatter/NumberTypeFormatter";
import { ObjectTypeFormatter } from "../src/TypeFormatter/ObjectTypeFormatter";
Expand All @@ -23,6 +24,7 @@ import { StringTypeFormatter } from "../src/TypeFormatter/StringTypeFormatter";
import { TupleTypeFormatter } from "../src/TypeFormatter/TupleTypeFormatter";
import { UndefinedTypeFormatter } from "../src/TypeFormatter/UndefinedTypeFormatter";
import { UnionTypeFormatter } from "../src/TypeFormatter/UnionTypeFormatter";
import { UnknownTypeFormatter } from "../src/TypeFormatter/UnknownTypeFormatter";



Expand All @@ -39,7 +41,9 @@ export function createFormatter(config: Config): TypeFormatter {
.addTypeFormatter(new NullTypeFormatter())

.addTypeFormatter(new AnyTypeFormatter())
.addTypeFormatter(new NeverTypeFormatter())
.addTypeFormatter(new UndefinedTypeFormatter())
.addTypeFormatter(new UnknownTypeFormatter())

.addTypeFormatter(new LiteralTypeFormatter())
.addTypeFormatter(new EnumTypeFormatter())
Expand Down
7 changes: 6 additions & 1 deletion factory/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { ArrayNodeParser } from "../src/NodeParser/ArrayNodeParser";
import { BooleanLiteralNodeParser } from "../src/NodeParser/BooleanLiteralNodeParser";
import { BooleanTypeNodeParser } from "../src/NodeParser/BooleanTypeNodeParser";
import { CallExpressionParser } from "../src/NodeParser/CallExpressionParser";
import { ConditionalTypeNodeParser } from "../src/NodeParser/ConditionalTypeNodeParser";
import { EnumNodeParser } from "../src/NodeParser/EnumNodeParser";
import { ExpressionWithTypeArgumentsNodeParser } from "../src/NodeParser/ExpressionWithTypeArgumentsNodeParser";
import { IndexedAccessTypeNodeParser } from "../src/NodeParser/IndexedAccessTypeNodeParser";
import { InterfaceAndClassNodeParser } from "../src/NodeParser/InterfaceAndClassNodeParser";
import { IntersectionNodeParser } from "../src/NodeParser/IntersectionNodeParser";
import { LiteralNodeParser } from "../src/NodeParser/LiteralNodeParser";
import { MappedTypeNodeParser } from "../src/NodeParser/MappedTypeNodeParser";
import { NeverTypeNodeParser } from "../src/NodeParser/NeverTypeNodeParser";
import { NullLiteralNodeParser } from "../src/NodeParser/NullLiteralNodeParser";
import { NumberLiteralNodeParser } from "../src/NodeParser/NumberLiteralNodeParser";
import { NumberTypeNodeParser } from "../src/NodeParser/NumberTypeNodeParser";
Expand All @@ -37,11 +39,11 @@ import { TypeOperatorNodeParser } from "../src/NodeParser/TypeOperatorNodeParser
import { TypeReferenceNodeParser } from "../src/NodeParser/TypeReferenceNodeParser";
import { UndefinedTypeNodeParser } from "../src/NodeParser/UndefinedTypeNodeParser";
import { UnionNodeParser } from "../src/NodeParser/UnionNodeParser";
import { UnknownTypeNodeParser } from "../src/NodeParser/UnknownTypeNodeParser";
import { SubNodeParser } from "../src/SubNodeParser";
import { TopRefNodeParser } from "../src/TopRefNodeParser";
import { FunctionNodeParser } from "./../src/NodeParser/FunctionNodeParser";


export function createParser(program: ts.Program, config: Config): NodeParser {
const typeChecker = program.getTypeChecker();
const chainNodeParser = new ChainNodeParser(typeChecker, []);
Expand Down Expand Up @@ -72,7 +74,9 @@ export function createParser(program: ts.Program, config: Config): NodeParser {
.addNodeParser(new NumberTypeNodeParser())
.addNodeParser(new BooleanTypeNodeParser())
.addNodeParser(new AnyTypeNodeParser())
.addNodeParser(new UnknownTypeNodeParser())
.addNodeParser(new UndefinedTypeNodeParser())
.addNodeParser(new NeverTypeNodeParser())
.addNodeParser(new ObjectTypeNodeParser())

.addNodeParser(new StringLiteralNodeParser())
Expand All @@ -92,6 +96,7 @@ export function createParser(program: ts.Program, config: Config): NodeParser {
.addNodeParser(new IndexedAccessTypeNodeParser(chainNodeParser))
.addNodeParser(new TypeofNodeParser(typeChecker, chainNodeParser))
.addNodeParser(new MappedTypeNodeParser(chainNodeParser))
.addNodeParser(new ConditionalTypeNodeParser(typeChecker, chainNodeParser))
.addNodeParser(new TypeOperatorNodeParser(chainNodeParser))

.addNodeParser(new UnionNodeParser(typeChecker, chainNodeParser))
Expand Down
5 changes: 5 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ export * from "./src/SubTypeFormatter";
export * from "./src/ChainTypeFormatter";
export * from "./src/CircularReferenceTypeFormatter";
export * from "./src/TypeFormatter/AnyTypeFormatter";
export * from "./src/TypeFormatter/UnknownTypeFormatter";
export * from "./src/TypeFormatter/NullTypeFormatter";
export * from "./src/TypeFormatter/UndefinedTypeFormatter";
export * from "./src/TypeFormatter/NeverTypeFormatter";
export * from "./src/TypeFormatter/BooleanTypeFormatter";
export * from "./src/TypeFormatter/NumberTypeFormatter";
export * from "./src/TypeFormatter/StringTypeFormatter";
Expand All @@ -71,9 +73,11 @@ export * from "./src/ExposeNodeParser";
export * from "./src/TopRefNodeParser";
export * from "./src/CircularReferenceNodeParser";
export * from "./src/NodeParser/AnyTypeNodeParser";
export * from "./src/NodeParser/UnknownTypeNodeParser";
export * from "./src/NodeParser/LiteralNodeParser";
export * from "./src/NodeParser/NullLiteralNodeParser";
export * from "./src/NodeParser/UndefinedTypeNodeParser";
export * from "./src/NodeParser/NeverTypeNodeParser";
export * from "./src/NodeParser/NumberLiteralNodeParser";
export * from "./src/NodeParser/StringLiteralNodeParser";
export * from "./src/NodeParser/BooleanLiteralNodeParser";
Expand All @@ -93,6 +97,7 @@ export * from "./src/NodeParser/UnionNodeParser";
export * from "./src/NodeParser/TupleNodeParser";
export * from "./src/NodeParser/AnnotatedNodeParser";
export * from "./src/NodeParser/CallExpressionParser";
export * from "./src/NodeParser/ConditionalTypeNodeParser";
export * from "./src/NodeParser/PrefixUnaryExpressionNodeParser";

export * from "./src/SchemaGenerator";
Expand Down
3 changes: 3 additions & 0 deletions src/NodeParser/AnnotatedNodeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export class AnnotatedNodeParser implements SubNodeParser {

public createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
const baseType = this.childNodeParser.createType(node, context, reference);
if (node.getSourceFile().fileName.match(/[\/\\]typescript[\/\\]lib[\/\\]lib\.[^/\\]+\.d\.ts$/i)) {
return baseType;
}
const annotatedNode = this.getAnnotatedNode(node);
const annotations = this.annotationsReader.getAnnotations(annotatedNode);
const nullable = this.annotationsReader instanceof ExtendedAnnotationsReader ?
Expand Down
2 changes: 1 addition & 1 deletion src/NodeParser/AnyTypeNodeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BaseType } from "../Type/BaseType";

export class AnyTypeNodeParser implements SubNodeParser {
public supportsNode(node: ts.KeywordTypeNode): boolean {
return node.kind === ts.SyntaxKind.AnyKeyword || node.kind === ts.SyntaxKind.UnknownKeyword;
return node.kind === ts.SyntaxKind.AnyKeyword;
}
public createType(node: ts.KeywordTypeNode, context: Context): BaseType {
return new AnyType();
Expand Down
50 changes: 50 additions & 0 deletions src/NodeParser/ConditionalTypeNodeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as ts from "typescript";
import { Context, NodeParser } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { BaseType } from "../Type/BaseType";
import { isAssignableTo } from "../Utils/isAssignableTo";
import { narrowType } from "../Utils/narrowType";

export class ConditionalTypeNodeParser implements SubNodeParser {
public constructor(
private typeChecker: ts.TypeChecker,
private childNodeParser: NodeParser,
) {}

public supportsNode(node: ts.ConditionalTypeNode): boolean {
return node.kind === ts.SyntaxKind.ConditionalType;
}

public createType(node: ts.ConditionalTypeNode, context: Context): BaseType {
const checkType = this.childNodeParser.createType(node.checkType, context);
const extendsType = this.childNodeParser.createType(node.extendsType, context);
const result = isAssignableTo(extendsType, checkType);
const tsResultType = result ? node.trueType : node.falseType;
const resultType = this.childNodeParser.createType(tsResultType, context);

// If result type is the same type parameter as the check type then narrow down the result type
const checkTypeParameterName = this.getTypeParameterName(node.checkType);
const resultTypeParameterName = this.getTypeParameterName(tsResultType);
if (resultTypeParameterName != null && resultTypeParameterName === checkTypeParameterName) {
return narrowType(resultType, type => isAssignableTo(extendsType, type) === result);
}

return resultType;
}

/**
* Returns the type parameter name of the given type node if any.
*
* @param node - The type node for which to return the type parameter name.
* @return The type parameter name or null if specified type node is not a type parameter.
*/
private getTypeParameterName(node: ts.TypeNode): string | null {
if (ts.isTypeReferenceNode(node)) {
const typeSymbol = this.typeChecker.getSymbolAtLocation(node.typeName)!;
if (typeSymbol.flags & ts.SymbolFlags.TypeParameter) {
return typeSymbol.name;
}
}
return null;
}
}
14 changes: 14 additions & 0 deletions src/NodeParser/NeverTypeNodeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as ts from "typescript";
import { Context } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { BaseType } from "../Type/BaseType";
import { NeverType } from "../Type/NeverType";

export class NeverTypeNodeParser implements SubNodeParser {
public supportsNode(node: ts.KeywordTypeNode): boolean {
return node.kind === ts.SyntaxKind.NeverKeyword;
}
public createType(node: ts.KeywordTypeNode, context: Context): BaseType {
return new NeverType();
}
}
14 changes: 14 additions & 0 deletions src/NodeParser/UnknownTypeNodeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as ts from "typescript";
import { Context } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { BaseType } from "../Type/BaseType";
import { UnknownType } from "../Type/UnknownType";

export class UnknownTypeNodeParser implements SubNodeParser {
public supportsNode(node: ts.KeywordTypeNode): boolean {
return node.kind === ts.SyntaxKind.UnknownKeyword;
}
public createType(node: ts.KeywordTypeNode, context: Context): BaseType {
return new UnknownType();
}
}
9 changes: 9 additions & 0 deletions src/Type/EnumType.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { BaseType } from "./BaseType";
import { LiteralType } from "./LiteralType";
import { NullType } from "./NullType";

export type EnumValue = string|boolean|number|null;

export class EnumType extends BaseType {
private types: BaseType[];

public constructor(
private id: string,
private values: EnumValue[],
) {
super();
this.types = values.map(value => value == null ? new NullType() : new LiteralType(value));
}

public getId(): string {
Expand All @@ -17,4 +22,8 @@ export class EnumType extends BaseType {
public getValues(): EnumValue[] {
return this.values;
}

public getTypes(): BaseType[] {
return this.types;
}
}
7 changes: 7 additions & 0 deletions src/Type/NeverType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BaseType } from "./BaseType";

export class NeverType extends BaseType {
public getId(): string {
return "never";
}
}
7 changes: 7 additions & 0 deletions src/Type/UnknownType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BaseType } from "./BaseType";

export class UnknownType extends BaseType {
public getId(): string {
return "unknown";
}
}
16 changes: 16 additions & 0 deletions src/TypeFormatter/NeverTypeFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { BaseType } from "../Type/BaseType";
import { NeverType } from "../Type/NeverType";

export class NeverTypeFormatter implements SubTypeFormatter {
public supportsType(type: NeverType): boolean {
return type instanceof NeverType;
}
public getDefinition(type: NeverType): Definition {
return {not: {}};
}
public getChildren(type: NeverType): BaseType[] {
return [];
}
}
3 changes: 2 additions & 1 deletion src/TypeFormatter/ObjectTypeFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { UndefinedType } from "../Type/UndefinedType";
import { UnionType } from "../Type/UnionType";
import { TypeFormatter } from "../TypeFormatter";
import { getAllOfDefinitionReducer } from "../Utils/allOfDefinition";
import { derefType } from "../Utils/derefType";
import { StringMap } from "../Utils/StringMap";

export class ObjectTypeFormatter implements SubTypeFormatter {
Expand Down Expand Up @@ -75,7 +76,7 @@ export class ObjectTypeFormatter implements SubTypeFormatter {
}

private prepareObjectProperty(property: ObjectProperty): ObjectProperty {
const propType = property.getType();
const propType = derefType(property.getType());
if (propType instanceof UndefinedType) {
return new ObjectProperty(property.getName(), new UndefinedType(), false);
} else if (!(propType instanceof UnionType)) {
Expand Down
16 changes: 16 additions & 0 deletions src/TypeFormatter/UnknownTypeFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { BaseType } from "../Type/BaseType";
import { UnknownType } from "../Type/UnknownType";

export class UnknownTypeFormatter implements SubTypeFormatter {
public supportsType(type: UnknownType): boolean {
return type instanceof UnknownType;
}
public getDefinition(type: UnknownType): Definition {
return {};
}
public getChildren(type: UnknownType): BaseType[] {
return [];
}
}
Loading