From ea76fcc5c12f360d25e7722ff51d4a4c72e7ed90 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 10 Jan 2017 22:48:41 -0800 Subject: [PATCH 1/8] Upgrade deps --- package.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 34144110..a0ea3906 100644 --- a/package.json +++ b/package.json @@ -32,22 +32,21 @@ "dependencies": { "glob": "~7.1.1", "json-stable-stringify": "^1.0.1", - "typescript": "~2.0.10", - "yargs": "^6.4.0" + "typescript": "~2.1.4", + "yargs": "^6.6.0" }, "devDependencies": { - "@types/ajv": "0.0.4", "@types/assertion-error": "^1.0.30", "@types/chai": "^3.4.34", "@types/glob": "^5.0.30", "@types/json-stable-stringify": "^1.0.29", - "@types/mocha": "^2.2.33", - "@types/node": "^6.0.51", - "ajv": "^4.9.0", + "@types/mocha": "^2.2.37", + "@types/node": "^7.0.0", + "ajv": "^4.10.4", "chai": "^3.5.0", - "mocha": "^3.1.2", - "source-map-support": "^0.4.6", - "tslint": "^4.0.2" + "mocha": "^3.2.0", + "source-map-support": "^0.4.8", + "tslint": "^4.3.1" }, "scripts": { "test": "npm run build && mocha -t 5000 --require source-map-support/register test", From 4fc7e6083c4e0f01603a7c2840232d667a5e73ad Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Thu, 12 Jan 2017 10:12:09 -0800 Subject: [PATCH 2/8] Use modifier and object flags --- typescript-json-schema.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/typescript-json-schema.ts b/typescript-json-schema.ts index 1073107e..d0b5fab7 100644 --- a/typescript-json-schema.ts +++ b/typescript-json-schema.ts @@ -144,10 +144,10 @@ export class JsonSchemaGenerator { * Checks whether a type is a tuple type. */ private resolveTupleType(propertyType: ts.Type): ts.TupleTypeNode { - if (!propertyType.getSymbol() && (propertyType.getFlags() & ts.TypeFlags.Reference)) { + if (!propertyType.getSymbol() && (propertyType.getFlags() & ts.TypeFlags.Object && (propertyType).objectFlags & ts.ObjectFlags.Reference)) { return (propertyType as ts.TypeReference).target as any; } - if (!(propertyType.flags & ts.TypeFlags.Tuple)) { + if (!(propertyType.getFlags() & ts.TypeFlags.Object && (propertyType).objectFlags & ts.ObjectFlags.Tuple)) { return null; } return propertyType as any; @@ -418,6 +418,8 @@ export class JsonSchemaGenerator { const props = tc.getPropertiesOfType(clazzType); const fullName = tc.typeToString(clazzType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType); + const modifierFlags = ts.getCombinedModifierFlags(node); + if(props.length === 0 && clazz.members && clazz.members.length === 1 && clazz.members[0].kind === ts.SyntaxKind.IndexSignature) { // for case "array-types" const indexSignature = clazz.members[0]; @@ -442,7 +444,7 @@ export class JsonSchemaGenerator { definition.items = def; } return definition; - } else if (clazz.flags & ts.NodeFlags.Abstract) { + } else if (modifierFlags & ts.ModifierFlags.Abstract) { const oneOf = this.inheritingTypes[fullName].map((typename) => { return this.getTypeDefinition(this.allSymbols[typename], tc); }); @@ -566,7 +568,7 @@ export class JsonSchemaGenerator { // aliased types must be handled slightly different const asTypeAliasRef = asRef && reffedType && (this.args.useTypeAliasRef || isStringEnum); if (!asTypeAliasRef) { - if (isRawType || (typ.getFlags() & ts.TypeFlags.Anonymous)) { + if (isRawType || typ.getFlags() & ts.TypeFlags.Object && (typ).objectFlags & ts.ObjectFlags.Anonymous) { asRef = false; // raw types and inline types cannot be reffed, // unless we are handling a type alias } From fa2880db03d288bb40ac020e7ade9917502de76e Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Thu, 12 Jan 2017 10:13:56 -0800 Subject: [PATCH 3/8] Travis on node 7, tslint --- .travis.yml | 4 ++-- tslint.json | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 959df9b1..4d80225b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js node_js: - - "4.1" - - "4.0" + - "4" + - "7" script: - npm run lint - npm run test diff --git a/tslint.json b/tslint.json index 500e14ae..9941ef44 100644 --- a/tslint.json +++ b/tslint.json @@ -8,14 +8,11 @@ "curly": true, "forin": true, "label-position": true, - "label-undefined": true, "no-arg": true, "no-any": false, "jsdoc-format": true, "semicolon": [true, "always"], - "no-duplicate-key": true, "no-duplicate-variable": true, - "no-unreachable": true, "no-consecutive-blank-lines": false, "no-console": [ true, @@ -32,7 +29,6 @@ "no-string-literal": false, "no-trailing-whitespace": true, "no-unused-expression": true, - "no-unused-variable": true, "no-use-before-declare": true, "one-line": [ true, From 5c26a6e2e7c5530cdfc2e733e4697deca450991e Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Thu, 12 Jan 2017 18:59:17 -0800 Subject: [PATCH 4/8] =?UTF-8?q?Typescript=20now=20parses=20jsdoc=20by=20it?= =?UTF-8?q?self=20so=20we=20don=E2=80=99t=20have=20to=20extract=20them=20m?= =?UTF-8?q?anually=20any=20more.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript-json-schema.ts | 113 ++++++++++++-------------------------- 1 file changed, 35 insertions(+), 78 deletions(-) diff --git a/typescript-json-schema.ts b/typescript-json-schema.ts index d0b5fab7..7ca7b704 100644 --- a/typescript-json-schema.ts +++ b/typescript-json-schema.ts @@ -28,9 +28,6 @@ export class JsonSchemaGenerator { "pattern", "minItems", "maxItems", "uniqueItems", "default", "additionalProperties", "enum"]; - private static annotedValidationKeywordPattern = /@[a-z.-]+\s*[^@]+/gi; - // private static primitiveTypes = ["string", "number", "boolean", "any"]; - private allSymbols: { [name: string]: ts.Type }; private inheritingTypes: { [baseName: string]: string[] }; private tc: ts.TypeChecker; @@ -46,95 +43,55 @@ export class JsonSchemaGenerator { public get ReffedDefinitions(): { [key: string]: any } { return this.reffedDefinitions; } - /** - * (source: Typson) - * Extracts the schema validation keywords stored in a comment and register them as properties. - * A validation keyword starts by a @. It has a name and a value. Several keywords may occur. - * - * @param comment {string} the full comment. - * @param to {object} the destination variable. - */ - private copyValidationKeywords(comment: string, to: {}, otherAnnotations: {}) { - JsonSchemaGenerator.annotedValidationKeywordPattern.lastIndex = 0; - // TODO: to improve the use of the exec method: it could make the tokenization - let annotation: string[]; - while ((annotation = JsonSchemaGenerator.annotedValidationKeywordPattern.exec(comment))) { - const annotationTokens = annotation[0].split(" "); - let keyword: string = annotationTokens[0].slice(1); - const path = keyword.split("."); - let context: string = null; - - // TODO: paths etc. originate from Typson, not supported atm. - if (path.length > 1) { - context = path[0]; - keyword = path[1]; - } - - keyword = keyword.replace("TJS-", ""); - - // case sensitive check inside the dictionary - if (JsonSchemaGenerator.validationKeywords.indexOf(keyword) >= 0 || JsonSchemaGenerator.validationKeywords.indexOf("TJS-" + keyword) >= 0) { - let value: string = annotationTokens.length > 1 ? annotationTokens.slice(1).join(" ") : ""; - value = value.replace(/^\s+|\s+$/gm, ""); // trim all whitepsace characters, including newlines - try { - value = JSON.parse(value); - } catch (e) { } - if (context) { - if (!to[context]) { - to[context] = {}; - } - to[context][keyword] = value; - } else { - to[keyword] = value; - } - } else { - otherAnnotations[keyword] = true; - } - } - } /** - * (source: Typson) - * Extracts the description part of a comment and register it in the description property. - * The description is supposed to start at first position and may be delimited by @. - * - * @param comment {string} the full comment. - * @param to {object} the destination variable or definition. - * @returns {string} the full comment minus the beginning description part. + * Try to parse a value and returns the string if it fails. */ - private copyDescription(comment: string, to: {description: string}): string { - const delimiter = "@"; - const delimiterIndex = comment.indexOf(delimiter); - const description = comment.slice(0, delimiterIndex < 0 ? comment.length : delimiterIndex); - if (description.length > 0) { - to.description = description.replace(/\s+$/g, ""); - } - return delimiterIndex < 0 ? "" : comment.slice(delimiterIndex); + private parseValue(value: string) { + if (value === "false") { + return false; + } + if (value === "true") { + return true; + } + let num = parseFloat(value); + return isNaN(num) ? value : num; } - private parseCommentsIntoDefinition(symbol: ts.Symbol, definition: any, otherAnnotations: {}): void { + private parseCommentsIntoDefinition(symbol: ts.Symbol, definition: {description: string}, otherAnnotations: {}): void { if (!symbol) { return; } - const comments: ts.SymbolDisplayPart[] = symbol.getDocumentationComment(); - if (!comments || !comments.length) { - return; + + // the comments for a symbol + let comments = symbol.getDocumentationComment(); + + if (comments.length) { + definition.description = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim()).join(""); } - let joined = comments.map(comment => comment.kind === "lineBreak" ? comment.text : comment.text.trim()).join(""); - joined = this.copyDescription(joined, definition); - this.copyValidationKeywords(joined, definition, otherAnnotations); + + // jsdocs are separate from comments + const jsdocs = symbol.getJsDocTags(); + jsdocs.forEach(doc => { + if (JsonSchemaGenerator.validationKeywords.indexOf(doc.name) > 0 || JsonSchemaGenerator.validationKeywords.indexOf("TJS-" + doc.name) >= 0) { + definition[doc.name] = this.parseValue(doc.text); + } else { + // special annotations + otherAnnotations[doc.name] = true; + } + }); } - private extractLiteralValue(typ: ts.Type): string|number|boolean { - if (typ.flags & (ts.TypeFlags as any).EnumLiteral) { - let str = (typ as any /*ts.LiteralType*/).text; + private extractLiteralValue(typ: ts.Type): string | number | boolean { + if (typ.flags & ts.TypeFlags.EnumLiteral) { + let str = (typ).text; let num = parseFloat(str); return isNaN(num) ? str : num; } else if (typ.flags & ts.TypeFlags.StringLiteral) { - return (/**/ typ as any).text; - } else if (typ.flags & (ts.TypeFlags as any).NumberLiteral) { - return parseFloat((typ as any).text); - } else if (typ.flags & (ts.TypeFlags as any).BooleanLiteral) { + return (typ).text; + } else if (typ.flags & ts.TypeFlags.NumberLiteral) { + return parseFloat((typ).text); + } else if (typ.flags & ts.TypeFlags.BooleanLiteral) { return (typ as any).intrinsicName === "true"; } return undefined; From c0d57b15d0209d08217989992f62c047ee3bf9f5 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sat, 14 Jan 2017 20:31:42 -0800 Subject: [PATCH 5/8] Use objects instead of arrays for faster lookups. --- typescript-json-schema.ts | 40 ++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/typescript-json-schema.ts b/typescript-json-schema.ts index 7ca7b704..5090159f 100644 --- a/typescript-json-schema.ts +++ b/typescript-json-schema.ts @@ -22,11 +22,29 @@ export function getDefaultArgs() { } export class JsonSchemaGenerator { - private static validationKeywords = [ - "ignore", "description", "type", "minimum", "exclusiveMinimum", "maximum", - "exclusiveMaximum", "multipleOf", "minLength", "maxLength", "format", - "pattern", "minItems", "maxItems", "uniqueItems", "default", - "additionalProperties", "enum"]; + /** + * JSDoc keywords that should be used to annotate the JSON schema. + */ + private static validationKeywords = { + ignore: true, + description: true, + type: true, + minimum: true, + exclusiveMinimum: true, + maximum: true, + exclusiveMaximum: true, + multipleOf: true, + minLength: true, + maxLength: true, + format: true, + pattern: true, + minItems: true, + maxItems: true, + uniqueItems: true, + default: true, + additionalProperties: true, + enum: true + }; private allSymbols: { [name: string]: ts.Type }; private inheritingTypes: { [baseName: string]: string[] }; @@ -73,7 +91,7 @@ export class JsonSchemaGenerator { // jsdocs are separate from comments const jsdocs = symbol.getJsDocTags(); jsdocs.forEach(doc => { - if (JsonSchemaGenerator.validationKeywords.indexOf(doc.name) > 0 || JsonSchemaGenerator.validationKeywords.indexOf("TJS-" + doc.name) >= 0) { + if (JsonSchemaGenerator.validationKeywords[doc.name] || JsonSchemaGenerator.validationKeywords["TJS-" + doc.name]) { definition[doc.name] = this.parseValue(doc.text); } else { // special annotations @@ -453,14 +471,14 @@ export class JsonSchemaGenerator { } } - private simpleTypesAllowedProperties = [ - "type", - "description" - ]; + private simpleTypesAllowedProperties = { + type: true, + description: true + }; private addSimpleType(def: any, type: string) { for (let k in def) { - if (this.simpleTypesAllowedProperties.indexOf(k) === -1) { + if (!this.simpleTypesAllowedProperties[k]) { return false; } } From 8be3ade9ac1b92992e8c8a2bead4b5f80767ca51 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sat, 14 Jan 2017 20:32:06 -0800 Subject: [PATCH 6/8] Add type for definitons to get rid of a bunch of anys --- typescript-json-schema.ts | 75 +++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/typescript-json-schema.ts b/typescript-json-schema.ts index 5090159f..65733978 100644 --- a/typescript-json-schema.ts +++ b/typescript-json-schema.ts @@ -21,6 +21,30 @@ export function getDefaultArgs() { }; } +export type Definition = { + $ref?: string, + description?: string, + allOf?: Definition[], + oneOf?: Definition[], + anyOf?: Definition[], + title?: string, + type?: string | string[], + definitions?: {[key: string]: any}, + format?: string, + items?: Definition, + minItems?: number, + additionalItems?: { + anyOf: Definition + }, + enum?: string[] | Definition[], + default?: string | number | boolean | Object, + additionalProperties?: Definition, + required?: string[], + propertyOrder?: string[], + properties?: {}, + defaultProperties?: string[], +}; + export class JsonSchemaGenerator { /** * JSDoc keywords that should be used to annotate the JSON schema. @@ -50,7 +74,7 @@ export class JsonSchemaGenerator { private inheritingTypes: { [baseName: string]: string[] }; private tc: ts.TypeChecker; - private reffedDefinitions: { [key: string]: any } = {}; + private reffedDefinitions: { [key: string]: Definition } = {}; constructor(allSymbols: { [name: string]: ts.Type }, inheritingTypes: { [baseName: string]: string[] }, tc: ts.TypeChecker, private args = getDefaultArgs()) { this.allSymbols = allSymbols; @@ -58,7 +82,7 @@ export class JsonSchemaGenerator { this.tc = tc; } - public get ReffedDefinitions(): { [key: string]: any } { + public get ReffedDefinitions(): { [key: string]: Definition } { return this.reffedDefinitions; } @@ -76,7 +100,10 @@ export class JsonSchemaGenerator { return isNaN(num) ? value : num; } - private parseCommentsIntoDefinition(symbol: ts.Symbol, definition: {description: string}, otherAnnotations: {}): void { + /** + * Parse the comments of a symbol into the definition and other annotations. + */ + private parseCommentsIntoDefinition(symbol: ts.Symbol, definition: {description?: string}, otherAnnotations: {}): void { if (!symbol) { return; } @@ -128,7 +155,7 @@ export class JsonSchemaGenerator { return propertyType as any; } - private getDefinitionForRootType(propertyType: ts.Type, tc: ts.TypeChecker, reffedType: ts.Symbol, definition: any) { + private getDefinitionForRootType(propertyType: ts.Type, tc: ts.TypeChecker, reffedType: ts.Symbol, definition: Definition) { const symbol = propertyType.getSymbol(); const tupleType = this.resolveTupleType(propertyType); @@ -210,7 +237,7 @@ export class JsonSchemaGenerator { const reffedType = this.getReferencedTypeSymbol(prop, tc); - let definition: any = this.getTypeDefinition(propertyType, tc, undefined, undefined, prop, reffedType); + let definition = this.getTypeDefinition(propertyType, tc, undefined, undefined, prop, reffedType); if (this.args.useTitle) { definition.title = propertyName; } @@ -233,7 +260,7 @@ export class JsonSchemaGenerator { vm.runInNewContext("sandboxvar=" + initial.getText(), sandbox); initial = sandbox.sandboxvar; - if (initial === null || typeof (initial) === "string" || typeof (initial) === "number" || typeof (initial) === "boolean" || Object.prototype.toString.call(initial) === "[object Array]") { + if (initial === null || typeof initial === "string" || typeof initial === "number" || typeof initial === "boolean" || Object.prototype.toString.call(initial) === "[object Array]") { definition.default = initial; } else if (initial) { console.warn("unknown initializer for property " + propertyName + ": " + initial); @@ -247,12 +274,12 @@ export class JsonSchemaGenerator { return definition; } - private getEnumDefinition(clazzType: ts.Type, tc: ts.TypeChecker, definition: any): any { + private getEnumDefinition(clazzType: ts.Type, tc: ts.TypeChecker, definition: Definition): Definition { const node = clazzType.getSymbol().getDeclarations()[0]; const fullName = tc.typeToString(clazzType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType); const enm = node; - var enumValues: any[] = []; + var enumValues: Definition[] = []; let enumTypes: string[] = []; const addType = (type: string) => { @@ -301,16 +328,16 @@ export class JsonSchemaGenerator { } if (enumValues.length > 0) { - definition["enum"] = enumValues.sort(); + definition.enum = enumValues.sort(); } return definition; } - private getUnionDefinition(unionType: ts.UnionType, prop: ts.Symbol, tc: ts.TypeChecker, unionModifier: string, definition: any): any { + private getUnionDefinition(unionType: ts.UnionType, prop: ts.Symbol, tc: ts.TypeChecker, unionModifier: string, definition: Definition) { const enumValues: (string | number | boolean)[] = []; const simpleTypes: string[] = []; - const schemas: any[] = []; + const schemas: Definition[] = []; const addSimpleType = (type: string) => { if (simpleTypes.indexOf(type) === -1) { @@ -338,7 +365,11 @@ export class JsonSchemaGenerator { } else { const keys = Object.keys(def); if (keys.length === 1 && keys[0] === "type") { - addSimpleType(def.type); + if (typeof def.type !== "string") { + console.error("Expected only a simple type."); + } else { + addSimpleType(def.type); + } } else { schemas.push(def); } @@ -356,7 +387,7 @@ export class JsonSchemaGenerator { if (isOnlyBooleans) { addSimpleType("boolean"); } else { - const enumSchema: any = { enum: enumValues.sort() }; + const enumSchema: Definition = { enum: enumValues.sort() }; // If all values are of the same primitive type, add a "type" field to the schema if (enumValues.every((x) => { return typeof x === "string"; })) { @@ -387,7 +418,7 @@ export class JsonSchemaGenerator { return definition; } - private getClassDefinition(clazzType: ts.Type, tc: ts.TypeChecker, definition: any): any { + private getClassDefinition(clazzType: ts.Type, tc: ts.TypeChecker, definition: Definition): Definition { const node = clazzType.getSymbol().getDeclarations()[0]; const clazz = node; const props = tc.getPropertiesOfType(clazzType); @@ -476,7 +507,7 @@ export class JsonSchemaGenerator { description: true }; - private addSimpleType(def: any, type: string) { + private addSimpleType(def: Definition, type: string) { for (let k in def) { if (!this.simpleTypesAllowedProperties[k]) { return false; @@ -485,7 +516,7 @@ export class JsonSchemaGenerator { if (!def.type) { def.type = type; - } else if (def.type.push) { + } else if (typeof def.type !== "string") { if (!(def.type).every((val) => { return typeof val === "string"; })) { return false; } @@ -505,7 +536,7 @@ export class JsonSchemaGenerator { return true; } - private makeNullable(def: any) { + private makeNullable(def: Definition) { if (!this.addSimpleType(def, "null")) { let union = def.oneOf || def.anyOf; if (union) { @@ -524,8 +555,8 @@ export class JsonSchemaGenerator { return def; } - private getTypeDefinition(typ: ts.Type, tc: ts.TypeChecker, asRef = this.args.useRef, unionModifier: string = "anyOf", prop?: ts.Symbol, reffedType?: ts.Symbol): any { - const definition: any = {}; // real definition + private getTypeDefinition(typ: ts.Type, tc: ts.TypeChecker, asRef = this.args.useRef, unionModifier: string = "anyOf", prop?: ts.Symbol, reffedType?: ts.Symbol): Definition { + const definition: Definition = {}; // real definition let returnedDefinition = definition; // returned definition, may be a $ref const symbol = typ.getSymbol(); @@ -605,7 +636,7 @@ export class JsonSchemaGenerator { return returnedDefinition; } - public getSchemaForSymbol(symbolName: string, includeReffedDefinitions: boolean = true): any { + public getSchemaForSymbol(symbolName: string, includeReffedDefinitions: boolean = true): Definition { if(!this.allSymbols[symbolName]) { throw `type ${symbolName} not found`; } @@ -619,7 +650,7 @@ export class JsonSchemaGenerator { return def; } - public getSchemaForSymbols(symbols: { [name: string]: ts.Type }): any { + public getSchemaForSymbols(symbols: { [name: string]: ts.Type }): Definition { const root = { "$schema": "http://json-schema.org/draft-04/schema#", definitions: {} @@ -698,7 +729,7 @@ export function generateSchema(program: ts.Program, fullTypeName: string, args = }); const generator = new JsonSchemaGenerator(allSymbols, inheritingTypes, typeChecker, args); - let definition: any; + let definition: Definition; if (fullTypeName === "*") { // All types in file(s) definition = generator.getSchemaForSymbols(userSymbols); } else { // Use specific type as root object From 191e7dad35c6b6f98492237178b403d2eb0b6ed1 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sat, 14 Jan 2017 22:33:48 -0800 Subject: [PATCH 7/8] Fix type primitives test case. Problem was that {} does not have declarations and https://github.com/Microsoft/TypeScript/issues/13498 --- test/programs/type-primitives/main.ts | 2 +- typescript-json-schema.ts | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/test/programs/type-primitives/main.ts b/test/programs/type-primitives/main.ts index d9d4c097..ca75bfa9 100644 --- a/test/programs/type-primitives/main.ts +++ b/test/programs/type-primitives/main.ts @@ -7,7 +7,7 @@ class MyObject { number1: number = 1; - /** @type integer */ + /** @TJS-type integer */ integer1: number = 1; integer2: integer = 1; diff --git a/typescript-json-schema.ts b/typescript-json-schema.ts index 65733978..298f5db4 100644 --- a/typescript-json-schema.ts +++ b/typescript-json-schema.ts @@ -118,8 +118,10 @@ export class JsonSchemaGenerator { // jsdocs are separate from comments const jsdocs = symbol.getJsDocTags(); jsdocs.forEach(doc => { - if (JsonSchemaGenerator.validationKeywords[doc.name] || JsonSchemaGenerator.validationKeywords["TJS-" + doc.name]) { - definition[doc.name] = this.parseValue(doc.text); + // if we have @TJS-... annotations, we have to parse them + const [name, text] = doc.name === "TJS" ? /^-([\w]+)\s([\w]+)/g.exec(doc.text).slice(1,3) : [doc.name, doc.text]; + if (JsonSchemaGenerator.validationKeywords[name]) { + definition[name] = this.parseValue(text); } else { // special annotations otherAnnotations[doc.name] = true; @@ -429,20 +431,20 @@ export class JsonSchemaGenerator { if(props.length === 0 && clazz.members && clazz.members.length === 1 && clazz.members[0].kind === ts.SyntaxKind.IndexSignature) { // for case "array-types" const indexSignature = clazz.members[0]; - if(indexSignature.parameters.length !== 1) { + if (indexSignature.parameters.length !== 1) { throw "Not supported: IndexSignatureDeclaration parameters.length != 1"; } const indexSymbol: ts.Symbol = (indexSignature.parameters[0]).symbol; const indexType = tc.getTypeOfSymbolAtLocation(indexSymbol, node); const isStringIndexed = (indexType.flags === ts.TypeFlags.String); - if(indexType.flags !== ts.TypeFlags.Number && !isStringIndexed) { + if (indexType.flags !== ts.TypeFlags.Number && !isStringIndexed) { throw "Not supported: IndexSignatureDeclaration with index symbol other than a number or a string"; } const typ = tc.getTypeAtLocation(indexSignature.type); const def = this.getTypeDefinition(typ, tc, undefined, "anyOf"); - if(isStringIndexed) { + if (isStringIndexed) { definition.type = "object"; definition.additionalProperties = def; } else { @@ -611,7 +613,7 @@ export class JsonSchemaGenerator { definition.title = fullTypeName; } } - const node = symbol ? symbol.getDeclarations()[0] : null; + const node = symbol && symbol.getDeclarations() !== undefined ? symbol.getDeclarations()[0] : null; if (typ.flags & ts.TypeFlags.Union) { this.getUnionDefinition(typ as ts.UnionType, prop, tc, unionModifier, definition); } else if (typ.flags & ts.TypeFlags.Intersection) { @@ -624,6 +626,10 @@ export class JsonSchemaGenerator { this.getDefinitionForRootType(typ, tc, reffedType, definition); } else if (node && (node.kind === ts.SyntaxKind.EnumDeclaration || node.kind === ts.SyntaxKind.EnumMember)) { this.getEnumDefinition(typ, tc, definition); + } else if (symbol && symbol.flags & ts.SymbolFlags.TypeLiteral && Object.keys(symbol.members).length === 0) { + // {} is TypeLiteral with no members. Need special case because it doesn't have declarations. + definition.type = "object"; + definition.properties = {}; } else { this.getClassDefinition(typ, tc, definition); } From 628a7ca647cde7929dcabc49dc7c2efe94217225 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sat, 14 Jan 2017 22:39:40 -0800 Subject: [PATCH 8/8] Add test cases for string liters | string, which should be string. --- test/programs/string-literals-inline/main.ts | 1 + test/programs/string-literals-inline/schema.json | 6 +++++- test/programs/string-literals/main.ts | 1 + test/programs/string-literals/schema.json | 6 +++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/programs/string-literals-inline/main.ts b/test/programs/string-literals-inline/main.ts index a0702573..0487d776 100644 --- a/test/programs/string-literals-inline/main.ts +++ b/test/programs/string-literals-inline/main.ts @@ -1,3 +1,4 @@ class MyObject { foo: "ok" | "fail" | "abort"; + bar: "ok" | "fail" | "abort" | string; } diff --git a/test/programs/string-literals-inline/schema.json b/test/programs/string-literals-inline/schema.json index fc7c52c2..1869434d 100644 --- a/test/programs/string-literals-inline/schema.json +++ b/test/programs/string-literals-inline/schema.json @@ -8,10 +8,14 @@ "fail", "ok" ] + }, + "bar": { + "type": "string" } }, "required": [ - "foo" + "foo", + "bar" ], "$schema": "http://json-schema.org/draft-04/schema#" } \ No newline at end of file diff --git a/test/programs/string-literals/main.ts b/test/programs/string-literals/main.ts index 09c0898c..c8d2aa1b 100644 --- a/test/programs/string-literals/main.ts +++ b/test/programs/string-literals/main.ts @@ -2,4 +2,5 @@ type result = "ok" | "fail" | "abort"; class MyObject { foo: result; + bar: result | string; } diff --git a/test/programs/string-literals/schema.json b/test/programs/string-literals/schema.json index 331fa087..a6352f35 100644 --- a/test/programs/string-literals/schema.json +++ b/test/programs/string-literals/schema.json @@ -3,10 +3,14 @@ "properties": { "foo": { "$ref": "#/definitions/result" + }, + "bar": { + "type": "string" } }, "required": [ - "foo" + "foo", + "bar" ], "definitions": { "result": {