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

feat: support property defaults with constant and enum refs (#336) #600

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
26 changes: 26 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,32 @@ class MyObject {
```


## [default-properties-ref](./test/programs/default-properties-ref)

```ts

const defaultBooleanFalse = false;
const defaultBooleanTrue = true;
const defaultFloat = 12.3;
const defaultInteger = 123;
const defaultString = "test"

enum FruitEnum {
Apple = 'apple',
Orange = 'orange'
}

class MyObject {
propBooleanFalse: boolean = defaultBooleanFalse;
propBooleanTrue: boolean = defaultBooleanTrue;
propFloat: number = defaultFloat;
propInteger: number = defaultInteger;
propString: string = defaultString;
propEnum: FruitEnum = FruitEnum.Apple;
}
```


## [enums-compiled-compute](./test/programs/enums-compiled-compute)

```ts
Expand Down
20 changes: 20 additions & 0 deletions test/programs/default-properties-ref/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

const defaultBooleanFalse = false;
const defaultBooleanTrue = true;
const defaultFloat = 12.3;
const defaultInteger = 123;
const defaultString = "test"

enum FruitEnum {
Apple = 'apple',
Orange = 'orange'
}

class MyObject {
propBooleanFalse: boolean = defaultBooleanFalse;
propBooleanTrue: boolean = defaultBooleanTrue;
propFloat: number = defaultFloat;
propInteger: number = defaultInteger;
propString: string = defaultString;
propEnum: FruitEnum = FruitEnum.Apple;
}
48 changes: 48 additions & 0 deletions test/programs/default-properties-ref/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"FruitEnum": {
"enum": [
"apple",
"orange"
],
"type": "string"
}
},
"properties": {
"propBooleanFalse": {
"type": "boolean",
"default": false
},
"propBooleanTrue": {
"type": "boolean",
"default": true
},
"propEnum": {
"$ref": "#/definitions/FruitEnum",
"default": "apple"
},
"propFloat": {
"type": "number",
"default": 12.3
},
"propInteger": {
"type": "number",
"default": 123
},
"propString": {
"type": "string",
"default": "test"
}
},
"required": [
"propBooleanFalse",
"propBooleanTrue",
"propEnum",
"propFloat",
"propInteger",
"propString"
],
"type": "object"
}
1 change: 1 addition & 0 deletions test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ describe("schema", () => {
assertSchema("ignored-required", "MyObject");

assertSchema("default-properties", "MyObject");
assertSchema("default-properties-ref", "MyObject");

// not supported yet #116
// assertSchema("interface-extra-props", "MyObject");
Expand Down
95 changes: 72 additions & 23 deletions typescript-json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,55 @@ export class JsonSchemaGenerator {
return undefined;
}

private getInitializerValue(initializer?: ts.Expression | ts.LiteralToken): any {
let val;
if (initializer === undefined) {
return;
}
switch (initializer.kind) {
case ts.SyntaxKind.NumericLiteral:
const txt = initializer.getText();
if (txt.includes(".")) {
val = Number.parseFloat(initializer.getText());
} else {
val = Number.parseInt(initializer.getText());
}
break;
case ts.SyntaxKind.StringLiteral:
val = (initializer as ts.StringLiteral).text;
break;
case ts.SyntaxKind.FalseKeyword:
val = false;
break;
case ts.SyntaxKind.TrueKeyword:
val = true;
break;
}
return val;
}

private getDeclarationValue(declaration?: ts.Declaration): any {
let val: any;
if (declaration === undefined) {
return;
}
switch (declaration.kind) {
case ts.SyntaxKind.VariableDeclaration:
val = this.getInitializerValue((declaration as ts.VariableDeclaration).initializer);
break;
case ts.SyntaxKind.EnumDeclaration:
const enumDecl = declaration as ts.EnumDeclaration;
val = enumDecl.members.reduce((prev, curr) => {
const v = this.getInitializerValue(curr.initializer);
prev[curr.name.getText()] = v;
return prev;
}, {} as { [k: string]: any });

break;
}
return val;
}

private getDefinitionForProperty(prop: ts.Symbol, node: ts.Node): Definition | null {
if (prop.flags & ts.SymbolFlags.Method) {
return null;
Expand Down Expand Up @@ -847,31 +896,30 @@ export class JsonSchemaGenerator {
initial = initial.expression;
}

if ((<any>initial).expression) {
// node
console.warn("initializer is expression for property " + propertyName);
} else if ((<any>initial).kind && (<any>initial).kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
definition.default = initial.getText();
} else {
try {
const sandbox = { sandboxvar: null as any };
vm.runInNewContext("sandboxvar=" + initial.getText(), sandbox);
try {
const sandbox: Record<string, any> = { sandboxvar: null as any };
// Put user symbols into sandbox
Object.entries(this.userSymbols)
.filter(([_, sym]) => sym.valueDeclaration)
.forEach(([name, sym]) => {
sandbox[name] = this.getDeclarationValue(sym.valueDeclaration);
});
vm.runInNewContext("sandboxvar=" + initial.getText(), sandbox);

const val = sandbox.sandboxvar;
if (
val === null ||
typeof val === "string" ||
typeof val === "number" ||
typeof val === "boolean" ||
Object.prototype.toString.call(val) === "[object Array]"
) {
definition.default = val;
} else if (val) {
console.warn("unknown initializer for property " + propertyName + ": " + val);
}
} catch (e) {
console.warn("exception evaluating initializer for property " + propertyName);
const val = sandbox.sandboxvar;
if (
val === null ||
typeof val === "string" ||
typeof val === "number" ||
typeof val === "boolean" ||
Object.prototype.toString.call(val) === "[object Array]"
) {
definition.default = val;
} else if (val) {
console.warn("unknown initializer for property " + propertyName + ": " + val);
}
} catch (e) {
console.warn("exception evaluating initializer for property " + propertyName);
}
}

Expand Down Expand Up @@ -1700,6 +1748,7 @@ export function buildGenerator(

function inspect(node: ts.Node, tc: ts.TypeChecker) {
if (
node.kind === ts.SyntaxKind.VariableDeclaration ||
node.kind === ts.SyntaxKind.ClassDeclaration ||
node.kind === ts.SyntaxKind.InterfaceDeclaration ||
node.kind === ts.SyntaxKind.EnumDeclaration ||
Expand Down
Loading