Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

typescript-json-schema

Package Overview
Dependencies
Maintainers
2
Versions
93
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

typescript-json-schema - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

test/programs/comments-override/main.ts

4

package.json
{
"name": "typescript-json-schema",
"version": "0.1.1",
"version": "0.2.0",
"description": "typescript-json-schema generates JSON Schema files from your Typescript sources",

@@ -31,3 +31,3 @@ "main": "typescript-json-schema.js",

"dependencies": {
"typescript": "~1.8.0",
"typescript": "~2.0.3",
"glob": "~7.0.0",

@@ -34,0 +34,0 @@ "yargs": "^4.1.0"

@@ -20,5 +20,5 @@ # typescript-json-schema

* Generate schema from a typescript type: `typescript-json-schema project/directory/**/*.ts fully.qualified.type.to.generate`
* Generate schema from a typescript type: `typescript-json-schema "project/directory/**/*.ts" fully.qualified.type.to.generate`
## Background
Inspired and builds upon [Typson](https://github.com/lbovet/typson/), but typescript-json-schema is compatible with more recent Typescript versions. Also, since it uses the Typescript compiler internally, more advanced scenarios are possible.
Inspired and builds upon [Typson](https://github.com/lbovet/typson/), but typescript-json-schema is compatible with more recent Typescript versions. Also, since it uses the Typescript compiler internally, more advanced scenarios are possible.

@@ -0,0 +0,0 @@ export interface MyObject {

interface MyArray {
[index: number]: string | number;
}

@@ -5,7 +5,7 @@ {

"items": {
"anyOf": [
{"type": "string"},
{"type": "number"}
"type": [
"string",
"number"
]
}
}

@@ -8,2 +8,2 @@ enum Enum {

foo: Enum
}
}

@@ -0,0 +0,0 @@ interface MyObject {

@@ -26,9 +26,5 @@ {

"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
"type": [
"string",
"number"
]

@@ -35,0 +31,0 @@ },

@@ -0,0 +0,0 @@ interface MyObject {

type MyFixedSizeArray = [string, number];

@@ -0,0 +0,0 @@ interface MyObject {

@@ -13,9 +13,5 @@ {

"SubfieldB": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
}
"type": [
"string",
"number"
]

@@ -22,0 +18,0 @@ },

@@ -1,1 +0,12 @@

type MyType = string | number;
// Simple union (generates "type": [...])
type MyType1 = string | number;
// Non-simple union (generates a "oneOf"/"anyOf")
type MyType2 = string | number[];
interface MyObject
{
var1 : MyType1;
var2 : MyType2;
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"oneOf": [
{
"type": "string"
}, {
"type": "number"
"type": "object",
"properties": {
"var1": {
"type": [
"string",
"number"
]
},
"var2": {
"anyOf": [
{
"type": "array",
"items": {
"type": "number"
}
},
{
"type": "string"
}
]
}
]
},
"required": [
"var1",
"var2"
],
"$schema": "http://json-schema.org/draft-04/schema#"
}
import {assert} from "chai";
import {version as TypescriptVersion, CompilerOptions} from "typescript";
import {TJS} from "../typescript-json-schema";

@@ -8,3 +9,3 @@ import {readFileSync} from 'fs';

export function assertSchema(group: string, name: string, type: string, settings?: any) {
export function assertSchema(group: string, name: string, type: string, settings?: any, compilerOptions?: CompilerOptions) {
it(group + " should create correct schema", function() {

@@ -16,3 +17,3 @@ if(!settings) {

const actual = TJS.generateSchema(TJS.getProgramFromFiles([resolve(base + group + "/" + name)]), type, settings);
const actual = TJS.generateSchema(TJS.getProgramFromFiles([resolve(base + group + "/" + name)], compilerOptions), type, settings);

@@ -33,7 +34,7 @@ const file = readFileSync(base + group + "/schema.json", "utf8")

assertSchema("interface-multi", "main.ts", "MyObject");
let settings = TJS.getDefaultArgs();
settings.useRootRef = true;
assertSchema("interface-recursion", "main.ts", "MyObject", settings); // this sample needs rootRef
assertSchema("module-interface-single", "main.ts", "MyObject");

@@ -45,12 +46,23 @@

assertSchema("enums-string", "main.ts", "MyObject");
assertSchema("enums-number", "main.ts", "MyObject");
assertSchema("enums-number-initialized", "main.ts", "Enum");
assertSchema("enums-compiled-compute", "main.ts", "Enum");
assertSchema("enums-mixed", "main.ts", "MyObject");
assertSchema("string-literals", "main.ts", "MyObject");
assertSchema("string-literals-inline", "main.ts", "MyObject");
assertSchema("array-types", "main.ts", "MyArray");
assertSchema("map-types", "main.ts", "MyObject");
assertSchema("namespace", "main.ts", "Type");
assertSchema("type-union", "main.ts", "MyType");
assertSchema("type-union", "main.ts", "MyObject");
assertSchema("type-intersection", "main.ts", "MyObject");
assertSchema("type-aliases", "main.ts", "MyString");
assertSchema("type-aliases-fixed-size-array", "main.ts", "MyFixedSizeArray");
assertSchema("type-aliases-multitype-array", "main.ts", "MyArray");
assertSchema("type-anonymous", "main.ts", "MyObject");
assertSchema("type-primitives", "main.ts", "MyObject");
assertSchema("type-nullable", "main.ts", "MyObject");

@@ -60,2 +72,9 @@ assertSchema("optionals", "main.ts", "MyObject");

assertSchema("comments", "main.ts", "MyObject");
assertSchema("comments-override", "main.ts", "MyObject");
assertSchema("type-union-tagged", "main.ts", "Shape");
assertSchema("strict-null-checks", "main.ts", "MyObject", undefined, {
strictNullChecks: true
});
});

@@ -14,9 +14,8 @@ {

"preserveConstEnums": true,
"suppressImplicitAnyIndexErrors": true,
"sourceMap": true,
"watch": false
},
"filesGlob": [
"include": [
"**/*.ts",
"**/*.tsx"
"typings/**/*.d.ts"
],

@@ -29,14 +28,3 @@ "exclude": [

"compileOnSave": true,
"buildOnSave": false,
"files": [
"test/schema.test.ts",
"typescript-json-schema.ts",
"typings/assertion-error/assertion-error.d.ts",
"typings/chai/chai.d.ts",
"typings/glob/glob.d.ts",
"typings/minimatch/minimatch.d.ts",
"typings/mocha/mocha.d.ts",
"typings/node/node.d.ts",
"typings/typescript/typescript.d.ts"
]
"buildOnSave": false
}

@@ -18,2 +18,3 @@ "use strict";

generateRequired: false,
strictNullChecks: false,
out: undefined

@@ -29,2 +30,6 @@ };

this.reffedDefinitions = {};
this.simpleTypesAllowedProperties = [
"type",
"description"
];
this.allSymbols = allSymbols;

@@ -41,3 +46,3 @@ this.inheritingTypes = inheritingTypes;

});
JsonSchemaGenerator.prototype.copyValidationKeywords = function (comment, to) {
JsonSchemaGenerator.prototype.copyValidationKeywords = function (comment, to, otherAnnotations) {
JsonSchemaGenerator.annotedValidationKeywordPattern.lastIndex = 0;

@@ -72,2 +77,5 @@ var annotation;

}
else {
otherAnnotations[keyword] = true;
}
}

@@ -84,3 +92,3 @@ };

};
JsonSchemaGenerator.prototype.parseCommentsIntoDefinition = function (symbol, definition) {
JsonSchemaGenerator.prototype.parseCommentsIntoDefinition = function (symbol, definition, otherAnnotations) {
if (!symbol) {

@@ -95,48 +103,87 @@ return;

joined = this.copyDescription(joined, definition);
this.copyValidationKeywords(joined, definition);
this.copyValidationKeywords(joined, definition, otherAnnotations);
};
JsonSchemaGenerator.prototype.extractLiteralValue = function (typ) {
if (typ.flags & ts.TypeFlags.EnumLiteral) {
var str = typ.text;
var num = parseFloat(str);
return isNaN(num) ? str : num;
}
else if (typ.flags & ts.TypeFlags.StringLiteral) {
return typ.text;
}
else if (typ.flags & ts.TypeFlags.NumberLiteral) {
return parseFloat(typ.text);
}
else if (typ.flags & ts.TypeFlags.BooleanLiteral) {
return typ.intrinsicName == "true";
}
return undefined;
};
JsonSchemaGenerator.prototype.resolveTupleType = function (propertyType) {
if (!propertyType.getSymbol() && (propertyType.getFlags() & ts.TypeFlags.Reference))
return propertyType.target;
if (!(propertyType.flags & ts.TypeFlags.Tuple))
return null;
return propertyType;
};
JsonSchemaGenerator.prototype.getDefinitionForRootType = function (propertyType, tc, reffedType, definition) {
var _this = this;
var symbol = propertyType.getSymbol();
var propertyTypeString = tc.typeToString(propertyType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
switch (propertyTypeString.toLowerCase()) {
case "string":
definition.type = "string";
break;
case "number":
var isInteger = (definition.type == "integer" || (reffedType && reffedType.getName() == "integer"));
definition.type = isInteger ? "integer" : "number";
break;
case "boolean":
definition.type = "boolean";
break;
case "any":
break;
case "date":
definition.type = "string";
definition.format = "date-time";
break;
default:
if (propertyType.flags & ts.TypeFlags.Tuple) {
var tupleType = propertyType;
var fixedTypes = tupleType.elementTypes.map(function (elType) { return _this.getTypeDefinition(elType, tc); });
definition.type = "array";
definition.items = fixedTypes;
definition.minItems = fixedTypes.length;
definition.additionalItems = {
"anyOf": fixedTypes
};
}
else if (propertyType.flags & ts.TypeFlags.StringLiteral) {
var tupleType = this.resolveTupleType(propertyType);
if (tupleType) {
var elemTypes = tupleType.elementTypes || propertyType.typeArguments;
var fixedTypes = elemTypes.map(function (elType) { return _this.getTypeDefinition(elType, tc); });
definition.type = "array";
definition.items = fixedTypes;
definition.minItems = fixedTypes.length;
definition.additionalItems = {
"anyOf": fixedTypes
};
}
else {
var propertyTypeString = tc.typeToString(propertyType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
switch (propertyTypeString.toLowerCase()) {
case "string":
definition.type = "string";
definition.enum = [propertyType.text];
}
else if (symbol && symbol.getName() == "Array") {
var arrayType = propertyType.typeArguments[0];
definition.type = "array";
definition.items = this.getTypeDefinition(arrayType, tc);
}
else {
console.error("Unsupported type: ", propertyType);
}
break;
case "number":
var isInteger = (definition.type == "integer" || (reffedType && reffedType.getName() == "integer"));
definition.type = isInteger ? "integer" : "number";
break;
case "boolean":
definition.type = "boolean";
break;
case "null":
definition.type = "null";
break;
case "undefined":
definition.type = "undefined";
break;
case "any":
break;
case "date":
definition.type = "string";
definition.format = "date-time";
break;
default:
var value = this.extractLiteralValue(propertyType);
if (value !== undefined) {
definition.type = typeof value;
definition.enum = [value];
}
else if (symbol && symbol.getName() == "Array") {
var arrayType = propertyType.typeArguments[0];
definition.type = "array";
definition.items = this.getTypeDefinition(arrayType, tc);
}
else {
var info = propertyType;
try {
info = JSON.stringify(propertyType);
}
catch (err) { }
console.error("Unsupported type: ", info);
}
}
}

@@ -200,22 +247,45 @@ return definition;

var enumValues = [];
var enumTypes = [];
var addType = function (type) {
if (enumTypes.indexOf(type) == -1)
enumTypes.push(type);
};
enm.members.forEach(function (member) {
var caseLabel = member.name.text;
var initial = member.initializer;
if (initial) {
if (initial.expression) {
var exp = initial.expression;
var text = exp.text;
if (text) {
enumValues.push(text);
var constantValue = tc.getConstantValue(member);
if (constantValue !== undefined) {
enumValues.push(constantValue);
addType(typeof constantValue);
}
else {
var initial = member.initializer;
if (initial) {
if (initial.expression) {
var exp = initial.expression;
var text = exp.text;
if (text) {
enumValues.push(text);
addType("string");
}
else if (exp.kind == ts.SyntaxKind.TrueKeyword || exp.kind == ts.SyntaxKind.FalseKeyword) {
enumValues.push((exp.kind == ts.SyntaxKind.TrueKeyword));
addType("boolean");
}
else {
console.warn("initializer is expression for enum: " + fullName + "." + caseLabel);
}
}
else {
console.warn("initializer is expression for enum: " + fullName + "." + caseLabel);
else if (initial.kind == ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
enumValues.push(initial.getText());
addType("string");
}
else if (initial.kind == ts.SyntaxKind.NullKeyword) {
enumValues.push(null);
addType("null");
}
}
else if (initial.kind && initial.kind == ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
enumValues.push(initial.getText());
}
}
});
definition.type = "string";
if (enumTypes.length)
definition.type = (enumTypes.length == 1) ? enumTypes[0] : enumTypes;
if (enumValues.length > 0) {

@@ -226,2 +296,65 @@ definition["enum"] = enumValues;

};
JsonSchemaGenerator.prototype.getUnionDefinition = function (unionType, prop, tc, unionModifier, definition) {
var enumValues = [];
var simpleTypes = [];
var schemas = [];
var addSimpleType = function (type) {
if (simpleTypes.indexOf(type) == -1)
simpleTypes.push(type);
};
var addEnumValue = function (val) {
if (enumValues.indexOf(val) == -1)
enumValues.push(val);
};
for (var i = 0; i < unionType.types.length; ++i) {
var valueType = unionType.types[i];
var value = this.extractLiteralValue(valueType);
if (value !== undefined) {
addEnumValue(value);
}
else {
var def = this.getTypeDefinition(unionType.types[i], tc);
if (def.type === "undefined") {
if (prop)
prop.mayBeUndefined = true;
}
else {
var keys = Object.keys(def);
if (keys.length == 1 && keys[0] == "type")
addSimpleType(def.type);
else
schemas.push(def);
}
}
}
if (enumValues.length > 0) {
var isOnlyBooleans = enumValues.length == 2 &&
typeof enumValues[0] === "boolean" &&
typeof enumValues[1] === "boolean" &&
enumValues[0] !== enumValues[1];
if (isOnlyBooleans) {
addSimpleType("boolean");
}
else {
var enumSchema = { enum: enumValues };
if (enumValues.every(function (x) { return typeof x == "string"; }))
enumSchema.type = "string";
else if (enumValues.every(function (x) { return typeof x == "number"; }))
enumSchema.type = "number";
else if (enumValues.every(function (x) { return typeof x == "boolean"; }))
enumSchema.type = "boolean";
schemas.push(enumSchema);
}
}
if (simpleTypes.length > 0)
schemas.push({ type: simpleTypes.length == 1 ? simpleTypes[0] : simpleTypes });
if (schemas.length == 1) {
for (var k in schemas[0])
definition[k] = schemas[0][k];
}
else {
definition[unionModifier] = schemas;
}
return definition;
};
JsonSchemaGenerator.prototype.getClassDefinition = function (clazzType, tc, definition) {

@@ -289,3 +422,3 @@ var _this = this;

var requiredProps = props.reduce(function (required, prop) {
if (!(prop.flags & ts.SymbolFlags.Optional)) {
if (!(prop.flags & ts.SymbolFlags.Optional) && !prop.mayBeUndefined) {
required.push(prop.getName());

@@ -301,6 +434,43 @@ }

};
JsonSchemaGenerator.prototype.addSimpleType = function (def, type) {
for (var k in def) {
if (this.simpleTypesAllowedProperties.indexOf(k) == -1)
return false;
}
if (!def.type) {
def.type = type;
}
else if (def.type.push) {
if (!def.type.every(function (val) { return typeof val == "string"; }))
return false;
if (def.type.indexOf('null') == -1)
def.type.push('null');
}
else {
if (typeof def.type != "string")
return false;
if (def.type != 'null')
def.type = [def.type, 'null'];
}
return true;
};
JsonSchemaGenerator.prototype.makeNullable = function (def) {
if (!this.addSimpleType(def, 'null')) {
var union = def.oneOf || def.anyOf;
if (union)
union.push({ type: 'null' });
else {
var subdef = {};
for (var k in def) {
subdef[k] = def[k];
delete def[k];
}
def.anyOf = [subdef, { type: 'null' }];
}
}
return def;
};
JsonSchemaGenerator.prototype.getTypeDefinition = function (typ, tc, asRef, unionModifier, prop, reffedType) {
var _this = this;
if (asRef === void 0) { asRef = this.args.useRef; }
if (unionModifier === void 0) { unionModifier = "oneOf"; }
if (unionModifier === void 0) { unionModifier = "anyOf"; }
var definition = {};

@@ -317,3 +487,3 @@ var returnedDefinition = definition;

}
var asTypeAliasRef = asRef && ((reffedType && this.args.useTypeAliasRef) || isStringEnum);
var asTypeAliasRef = asRef && reffedType && (this.args.useTypeAliasRef || isStringEnum);
if (!asTypeAliasRef) {

@@ -336,4 +506,8 @@ if (isRawType || (typ.getFlags() & ts.TypeFlags.Anonymous)) {

}
this.parseCommentsIntoDefinition(reffedType, definition);
this.parseCommentsIntoDefinition(prop || symbol, returnedDefinition);
var otherAnnotations = {};
this.parseCommentsIntoDefinition(reffedType, definition, otherAnnotations);
if (prop)
this.parseCommentsIntoDefinition(prop, returnedDefinition, otherAnnotations);
else
this.parseCommentsIntoDefinition(symbol, definition, otherAnnotations);
if (!asRef || !this.reffedDefinitions[fullTypeName]) {

@@ -348,14 +522,10 @@ if (asRef) {

if (typ.flags & ts.TypeFlags.Union) {
var unionType = typ;
if (isStringEnum) {
definition.type = "string";
definition.enum = unionType.types.map(function (propType) {
return propType.text;
});
this.getUnionDefinition(typ, prop, tc, unionModifier, definition);
}
else if (typ.flags & ts.TypeFlags.Intersection) {
definition.allOf = [];
var types = typ.types;
for (var i = 0; i < types.length; ++i) {
definition.allOf.push(this.getTypeDefinition(types[i], tc));
}
else {
definition[unionModifier] = unionType.types.map(function (propType) {
return _this.getTypeDefinition(propType, tc);
});
}
}

@@ -365,3 +535,3 @@ else if (isRawType) {

}
else if (node.kind == ts.SyntaxKind.EnumDeclaration) {
else if (node && (node.kind == ts.SyntaxKind.EnumDeclaration || node.kind == ts.SyntaxKind.EnumMember)) {
this.getEnumDefinition(typ, tc, definition);

@@ -372,2 +542,4 @@ }

}
if (otherAnnotations['nullable'])
this.makeNullable(definition);
}

@@ -388,2 +560,12 @@ return returnedDefinition;

};
JsonSchemaGenerator.prototype.getSchemaForSymbols = function (symbols) {
var root = {
"$schema": "http://json-schema.org/draft-04/schema#",
definitions: {}
};
for (var id in symbols) {
root.definitions[id] = this.getTypeDefinition(symbols[id], this.tc, this.args.useRootRef);
}
return root;
};
JsonSchemaGenerator.validationKeywords = [

@@ -397,6 +579,9 @@ "ignore", "description", "type", "minimum", "exclusiveMinimum", "maximum",

}());
function getProgramFromFiles(files) {
function getProgramFromFiles(files, compilerOptions) {
if (compilerOptions === void 0) { compilerOptions = {}; }
var options = {
noEmit: true, emitDecoratorMetadata: true, experimentalDecorators: true, target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
};
for (var k in compilerOptions)
options[k] = compilerOptions[k];
return ts.createProgram(files, options);

@@ -411,4 +596,5 @@ }

var allSymbols_1 = {};
var userSymbols_1 = {};
var inheritingTypes_1 = {};
program.getSourceFiles().forEach(function (sourceFile) {
program.getSourceFiles().forEach(function (sourceFile, sourceFileIdx) {
function inspect(node, tc) {

@@ -423,2 +609,4 @@ if (node.kind == ts.SyntaxKind.ClassDeclaration

allSymbols_1[fullName_1] = nodeType;
if (!sourceFile.hasNoDefaultLib)
userSymbols_1[fullName_1] = nodeType;
var baseTypes = nodeType.getBaseTypes() || [];

@@ -440,3 +628,9 @@ baseTypes.forEach(function (baseType) {

var generator = new JsonSchemaGenerator(allSymbols_1, inheritingTypes_1, tc, args);
var definition = generator.getSchemaForSymbol(fullTypeName);
var definition = void 0;
if (fullTypeName == '*') {
definition = generator.getSchemaForSymbols(userSymbols_1);
}
else {
definition = generator.getSchemaForSymbol(fullTypeName);
}
return definition;

@@ -479,3 +673,5 @@ }

else {
program = TJS.getProgramFromFiles(glob.sync(filePattern));
program = TJS.getProgramFromFiles(glob.sync(filePattern), {
strictNullChecks: args.strictNullChecks
});
}

@@ -518,2 +714,4 @@ var definition = TJS.generateSchema(program, fullTypeName, args);

.describe("required", "Create required array for non-optional properties.")
.boolean("strictNullChecks").default("strictNullChecks", defaultArgs.strictNullChecks)
.describe("strictNullChecks", "(TypeScript 2) Make values non-nullable by default.")
.alias("out", "o")

@@ -531,2 +729,3 @@ .describe("out", "The output file, defaults to using stdout")

generateRequired: args.required,
strictNullChecks: args.strictNullChecks,
out: args.out

@@ -533,0 +732,0 @@ });

@@ -20,2 +20,3 @@ import * as ts from "typescript";

generateRequired: false,
strictNullChecks: false,
out: undefined

@@ -60,6 +61,6 @@ };

*/
private copyValidationKeywords(comment: string, to) {
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;
let annotation: string[];
while ((annotation = JsonSchemaGenerator.annotedValidationKeywordPattern.exec(comment))) {

@@ -96,2 +97,5 @@ const annotationTokens = annotation[0].split(" ");

}
else {
otherAnnotations[keyword] = true;
}
}

@@ -119,3 +123,3 @@ }

private parseCommentsIntoDefinition(symbol: ts.Symbol, definition: any): void {
private parseCommentsIntoDefinition(symbol: ts.Symbol, definition: any, otherAnnotations: {}): void {
if (!symbol) {

@@ -130,55 +134,95 @@ return;

joined = this.copyDescription(joined, definition);
this.copyValidationKeywords(joined, definition);
this.copyValidationKeywords(joined, definition, otherAnnotations);
}
private extractLiteralValue(typ: ts.Type): string|number|boolean {
if (typ.flags & (ts.TypeFlags as any).EnumLiteral) {
let str = (typ as any /*ts.LiteralType*/).text;
let num = parseFloat(str);
return isNaN(num) ? str : num;
} else if (typ.flags & ts.TypeFlags.StringLiteral) {
return (/*<ts.StringLiteralType/ts.LiteralType>*/ 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 as any).intrinsicName == "true";
}
return undefined;
}
/**
* Checks whether a type is a tuple type.
*/
private resolveTupleType(propertyType: ts.Type): ts.TupleTypeNode {
if (!propertyType.getSymbol() && (propertyType.getFlags() & ts.TypeFlags.Reference))
return (propertyType as ts.TypeReference).target as any;
if (!(propertyType.flags & ts.TypeFlags.Tuple))
return null;
return propertyType as any;
}
private getDefinitionForRootType(propertyType: ts.Type, tc: ts.TypeChecker, reffedType: ts.Symbol, definition: any) {
const symbol = propertyType.getSymbol();
const propertyTypeString = tc.typeToString(propertyType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
const tupleType = this.resolveTupleType(propertyType);
switch (propertyTypeString.toLowerCase()) {
case "string":
definition.type = "string";
break;
case "number":
const isInteger = (definition.type == "integer" || (reffedType && reffedType.getName() == "integer"));
definition.type = isInteger ? "integer" : "number";
break;
case "boolean":
definition.type = "boolean";
break;
case "any":
// no type restriction, so that anything will match
break;
case "date":
definition.type = "string";
definition.format = "date-time";
break;
default:
if(propertyType.flags & ts.TypeFlags.Tuple) { // tuple
const tupleType: ts.TupleType = <ts.TupleType>propertyType;
const fixedTypes = tupleType.elementTypes.map(elType => this.getTypeDefinition(elType, tc));
definition.type = "array";
definition.items = fixedTypes;
definition.minItems = fixedTypes.length;
definition.additionalItems = {
"anyOf": fixedTypes
};
} else if (propertyType.flags & ts.TypeFlags.StringLiteral) {
if (tupleType) { // tuple
const elemTypes : ts.Type[] = tupleType.elementTypes || (propertyType as any).typeArguments;
const fixedTypes = elemTypes.map(elType => this.getTypeDefinition(elType, tc));
definition.type = "array";
definition.items = fixedTypes;
definition.minItems = fixedTypes.length;
definition.additionalItems = {
"anyOf": fixedTypes
};
} else {
const propertyTypeString = tc.typeToString(propertyType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
switch (propertyTypeString.toLowerCase()) {
case "string":
definition.type = "string";
definition.enum = [ (<ts.StringLiteralType> propertyType).text ];
} else if (symbol && symbol.getName() == "Array") {
const arrayType = (<ts.TypeReference>propertyType).typeArguments[0];
definition.type = "array";
definition.items = this.getTypeDefinition(arrayType, tc);
} else {
// TODO
console.error("Unsupported type: ", propertyType);
//definition = this.getTypeDefinition(propertyType, tc);
}
break;
case "number":
const isInteger = (definition.type == "integer" || (reffedType && reffedType.getName() == "integer"));
definition.type = isInteger ? "integer" : "number";
break;
case "boolean":
definition.type = "boolean";
break;
case "null":
definition.type = "null";
break;
case "undefined":
definition.type = "undefined";
break;
case "any":
// no type restriction, so that anything will match
break;
case "date":
definition.type = "string";
definition.format = "date-time";
break;
default:
const value = this.extractLiteralValue(propertyType);
if (value !== undefined) {
definition.type = typeof value;
definition.enum = [ value ];
} else if (symbol && symbol.getName() == "Array") {
const arrayType = (<ts.TypeReference>propertyType).typeArguments[0];
definition.type = "array";
definition.items = this.getTypeDefinition(arrayType, tc);
} else {
// Report that type could not be processed
let info : any = propertyType;
try { info = JSON.stringify(propertyType); }
catch(err) {}
console.error("Unsupported type: ", info);
//definition = this.getTypeDefinition(propertyType, tc);
}
}
}
return definition;
}
private getReferencedTypeSymbol(prop: ts.Symbol, tc: ts.TypeChecker) : ts.Symbol {

@@ -201,3 +245,3 @@ const decl = prop.getDeclarations();

const reffedType = this.getReferencedTypeSymbol(prop, tc);
let definition: any = this.getTypeDefinition(propertyType, tc, undefined, undefined, prop, reffedType);

@@ -242,27 +286,45 @@ if (this.args.useTitle) {

const node = clazzType.getSymbol().getDeclarations()[0];
const fullName = tc.typeToString(clazzType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
const fullName = tc.typeToString(clazzType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
const enm = <ts.EnumDeclaration>node;
const values = tc.getIndexTypeOfType(clazzType, ts.IndexKind.String);
var enumValues: string[] = [];
var enumValues: any[] = [];
let enumTypes: string[] = [];
const addType = (type) => {
if (enumTypes.indexOf(type) == -1)
enumTypes.push(type);
};
enm.members.forEach(member => {
const caseLabel = (<ts.Identifier>member.name).text;
// try to extract the enums value; it will probably by a cast expression
let initial = <ts.Expression>member.initializer;
if (initial) {
if ((<any>initial).expression) { // node
const exp = (<any>initial).expression;
const text = (<any>exp).text;
// if it is an expression with a text literal, chances are it is the enum convension:
// CASELABEL = 'literal' as any
if (text) {
enumValues.push(text);
} else {
console.warn("initializer is expression for enum: " + fullName + "." + caseLabel);
const constantValue = tc.getConstantValue(member);
if (constantValue !== undefined) {
enumValues.push(constantValue);
addType(typeof constantValue);
} else {
// try to extract the enums value; it will probably by a cast expression
let initial : ts.Expression = member.initializer;
if (initial) {
if ((<any>initial).expression) { // node
const exp = (<any>initial).expression;
const text = (<any>exp).text;
// if it is an expression with a text literal, chances are it is the enum convension:
// CASELABEL = 'literal' as any
if (text) {
enumValues.push(text);
addType("string");
} else if (exp.kind == ts.SyntaxKind.TrueKeyword || exp.kind == ts.SyntaxKind.FalseKeyword) {
enumValues.push((exp.kind == ts.SyntaxKind.TrueKeyword));
addType("boolean");
} else {
console.warn("initializer is expression for enum: " + fullName + "." + caseLabel);
}
} else if (initial.kind == ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
enumValues.push(initial.getText());
addType("string");
} else if (initial.kind == ts.SyntaxKind.NullKeyword) {
enumValues.push(null);
addType("null");
}
} else if ((<any>initial).kind && (<any>initial).kind == ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
enumValues.push(initial.getText());
}

@@ -272,3 +334,4 @@ }

definition.type = "string";
if (enumTypes.length)
definition.type = (enumTypes.length == 1) ? enumTypes[0] : enumTypes;

@@ -282,2 +345,76 @@ if (enumValues.length > 0) {

private getUnionDefinition(unionType: ts.UnionType, prop: ts.Symbol, tc: ts.TypeChecker, unionModifier: string, definition: any): any {
const enumValues = [];
const simpleTypes = [];
const schemas = [];
const addSimpleType = (type) => {
if (simpleTypes.indexOf(type) == -1)
simpleTypes.push(type);
};
const addEnumValue = (val) => {
if (enumValues.indexOf(val) == -1)
enumValues.push(val);
};
for (let i = 0; i < unionType.types.length; ++i) {
const valueType = unionType.types[i];
const value = this.extractLiteralValue(valueType);
if (value !== undefined) {
addEnumValue(value);
}
else {
const def = this.getTypeDefinition(unionType.types[i], tc);
if (def.type === "undefined") {
if (prop)
(<any>prop).mayBeUndefined = true;
}
else {
const keys = Object.keys(def);
if (keys.length == 1 && keys[0] == "type")
addSimpleType(def.type);
else
schemas.push(def);
}
}
}
if (enumValues.length > 0) {
// If the values are true and false, just add "boolean" as simple type
const isOnlyBooleans = enumValues.length == 2 &&
typeof enumValues[0] === "boolean" &&
typeof enumValues[1] === "boolean" &&
enumValues[0] !== enumValues[1];
if (isOnlyBooleans) {
addSimpleType("boolean");
} else {
const enumSchema : any = { enum: enumValues };
// If all values are of the same primitive type, add a "type" field to the schema
if (enumValues.every((x) => { return typeof x == "string"; }))
enumSchema.type = "string";
else if (enumValues.every((x) => { return typeof x == "number"; }))
enumSchema.type = "number";
else if (enumValues.every((x) => { return typeof x == "boolean"; }))
enumSchema.type = "boolean";
schemas.push(enumSchema);
}
}
if (simpleTypes.length > 0)
schemas.push({ type: simpleTypes.length == 1 ? simpleTypes[0] : simpleTypes });
if (schemas.length == 1) {
for (let k in schemas[0])
definition[k] = schemas[0][k];
}
else {
definition[unionModifier] = schemas;
}
return definition;
}
private getClassDefinition(clazzType: ts.Type, tc: ts.TypeChecker, definition: any): any {

@@ -301,6 +438,6 @@ const node = clazzType.getSymbol().getDeclarations()[0];

}
const typ = tc.getTypeAtLocation(indexSignature.type);
const def = this.getTypeDefinition(typ, tc, undefined, "anyOf");
if(isStringIndexed) {

@@ -353,3 +490,3 @@ definition.type = "object";

const requiredProps = props.reduce((required, prop) => {
if (!(prop.flags & ts.SymbolFlags.Optional)) {
if (!(prop.flags & ts.SymbolFlags.Optional) && !(<any>prop).mayBeUndefined) {
required.push(prop.getName());

@@ -366,9 +503,54 @@ }

}
private getTypeDefinition(typ: ts.Type, tc: ts.TypeChecker, asRef = this.args.useRef, unionModifier: string = "oneOf", prop? : ts.Symbol, reffedType?: ts.Symbol): any {
private simpleTypesAllowedProperties = [
"type",
"description"
];
private addSimpleType(def: any, type: string) {
for (let k in def) {
if (this.simpleTypesAllowedProperties.indexOf(k) == -1)
return false;
}
if (!def.type) {
def.type = type;
} else if (def.type.push) {
if (!(<Object[]>def.type).every((val) => { return typeof val == "string"; }))
return false;
if (def.type.indexOf('null') == -1)
def.type.push('null');
} else {
if (typeof def.type != "string")
return false;
if (def.type != 'null')
def.type = [ def.type, 'null' ];
}
return true;
}
private makeNullable(def: any) {
if (!this.addSimpleType(def, 'null')) {
let union = def.oneOf || def.anyOf;
if (union)
union.push({ type: 'null' });
else {
const subdef = {};
for (var k in def) {
subdef[k] = def[k];
delete def[k];
}
def.anyOf = [ subdef, { type: 'null' } ];
}
}
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
let returnedDefinition = definition; // returned definition, may be a $ref
const symbol = typ.getSymbol();
const isRawType = (!symbol || symbol.name == "integer" || symbol.name == "Array" || symbol.name == "Date");

@@ -384,5 +566,5 @@

}
// aliased types must be handled slightly different
const asTypeAliasRef = asRef && ((reffedType && this.args.useTypeAliasRef) || isStringEnum);
const asTypeAliasRef = asRef && reffedType && (this.args.useTypeAliasRef || isStringEnum);
if (!asTypeAliasRef) {

@@ -394,3 +576,3 @@ if (isRawType || (typ.getFlags() & ts.TypeFlags.Anonymous)) {

}
let fullTypeName = "";

@@ -402,3 +584,3 @@ if (asTypeAliasRef) {

}
if (asRef) {

@@ -409,7 +591,13 @@ returnedDefinition = {

}
// Parse comments
this.parseCommentsIntoDefinition(reffedType, definition); // handle comments in the type alias declaration
this.parseCommentsIntoDefinition(prop || symbol, returnedDefinition);
const otherAnnotations = {};
this.parseCommentsIntoDefinition(reffedType, definition, otherAnnotations); // handle comments in the type alias declaration
if (prop)
this.parseCommentsIntoDefinition(prop, returnedDefinition, otherAnnotations);
else
this.parseCommentsIntoDefinition(symbol, definition, otherAnnotations);
// Create the actual definition only if is an inline definition, or
// if it will be a $ref and it is not yet created
if (!asRef || !this.reffedDefinitions[fullTypeName]) {

@@ -422,19 +610,14 @@ if (asRef) { // must be here to prevent recursivity problems

}
const node = symbol ? symbol.getDeclarations()[0] : null;
if (typ.flags & ts.TypeFlags.Union) {
const unionType = <ts.UnionType>typ;
if (isStringEnum) {
definition.type = "string";
definition.enum = unionType.types.map((propType) => {
return (<ts.StringLiteralType> propType).text;
});
} else {
definition[unionModifier] = unionType.types.map((propType) => {
return this.getTypeDefinition(propType, tc);
});
this.getUnionDefinition(typ as ts.UnionType, prop, tc, unionModifier, definition);
} else if (typ.flags & ts.TypeFlags.Intersection) {
definition.allOf = [];
const types = (<ts.IntersectionType> typ).types;
for (let i = 0; i < types.length; ++i) {
definition.allOf.push(this.getTypeDefinition(types[i], tc));
}
} else if (isRawType) {
this.getDefinitionForRootType(typ, tc, reffedType, definition);
} else if (node.kind == ts.SyntaxKind.EnumDeclaration) {
} else if (node && (node.kind == ts.SyntaxKind.EnumDeclaration || node.kind == ts.SyntaxKind.EnumMember)) {
this.getEnumDefinition(typ, tc, definition);

@@ -444,4 +627,7 @@ } else {

}
if (otherAnnotations['nullable'])
this.makeNullable(definition);
}
return returnedDefinition;

@@ -463,12 +649,25 @@ }

}
public getSchemaForSymbols(symbols: { [name: string]: ts.Type }): any {
const root = {
"$schema": "http://json-schema.org/draft-04/schema#",
definitions: {}
};
for (const id in symbols) {
root.definitions[id] = this.getTypeDefinition(symbols[id], this.tc, this.args.useRootRef);
}
return root;
}
}
export function getProgramFromFiles(files: string[]): ts.Program {
export function getProgramFromFiles(files: string[], compilerOptions: ts.CompilerOptions = {}): ts.Program {
// use built-in default options
const options: ts.CompilerOptions = {
const options: ts.CompilerOptions = {
noEmit: true, emitDecoratorMetadata: true, experimentalDecorators: true, target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
};
return ts.createProgram(files, options);
for (const k in compilerOptions)
options[k] = compilerOptions[k];
return ts.createProgram(files, options);
}
export function generateSchema(program: ts.Program, fullTypeName: string, args = getDefaultArgs()) {

@@ -482,11 +681,12 @@ const tc = program.getTypeChecker();

const allSymbols: { [name: string]: ts.Type } = {};
const userSymbols: { [name: string]: ts.Type } = {};
const inheritingTypes: { [baseName: string]: string[] } = {};
program.getSourceFiles().forEach(sourceFile => {
/*console.log(sourceFile.fileName);
program.getSourceFiles().forEach((sourceFile, sourceFileIdx) => {
/*console.log(sourceFile.fileName);
if(sourceFile.fileName.indexOf("main.ts") > -1) {
debugger;
} */
} */
function inspect(node: ts.Node, tc: ts.TypeChecker) {
if (node.kind == ts.SyntaxKind.ClassDeclaration

@@ -499,14 +699,17 @@ || node.kind == ts.SyntaxKind.InterfaceDeclaration

let fullName = tc.getFullyQualifiedName((<any>node).symbol)
// remove file name
// TODO: we probably don't want this eventually,
// TODO: we probably don't want this eventually,
// as same types can occur in different files and will override eachother in allSymbols
// This means atm we can't generate all types in large programs.
fullName = fullName.replace(/".*"\./, "");
allSymbols[fullName] = nodeType;
//if (sourceFileIdx == 0)
if (!sourceFile.hasNoDefaultLib)
userSymbols[fullName] = nodeType;
const baseTypes = nodeType.getBaseTypes() || [];
baseTypes.forEach(baseType => {

@@ -527,4 +730,9 @@ var baseName = tc.typeToString(baseType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);

const generator = new JsonSchemaGenerator(allSymbols, inheritingTypes, tc, args);
let definition = generator.getSchemaForSymbol(fullTypeName);
let definition : any;
if (fullTypeName == '*') { // All types in file(s)
definition = generator.getSchemaForSymbols(userSymbols);
}
else { // Use specific type as root object
definition = generator.getSchemaForSymbol(fullTypeName);
}
return definition;

@@ -548,3 +756,3 @@ } else {

const configObject = result.config;
const configParseResult = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configFileName), {}, configFileName);

@@ -557,6 +765,6 @@ const options = configParseResult.options;

delete options.declaration;
const program = ts.createProgram(configParseResult.fileNames, options);
return program;
//const conf = ts.convertCompilerOptionsFromJson(null, path.dirname(filePattern), "tsconfig.json");

@@ -569,7 +777,9 @@ }

} else {
program = TJS.getProgramFromFiles(glob.sync(filePattern));
program = TJS.getProgramFromFiles(glob.sync(filePattern), {
strictNullChecks: args.strictNullChecks
});
}
const definition = TJS.generateSchema(program, fullTypeName, args);
const json = JSON.stringify(definition, null, 4) + "\n";

@@ -581,3 +791,3 @@ if(args.out) {

}
});
});
} else {

@@ -610,2 +820,4 @@ process.stdout.write(json);

.describe("required", "Create required array for non-optional properties.")
.boolean("strictNullChecks").default("strictNullChecks", defaultArgs.strictNullChecks)
.describe("strictNullChecks", "(TypeScript 2) Make values non-nullable by default.")
.alias("out", "o")

@@ -624,2 +836,3 @@ .describe("out", "The output file, defaults to using stdout")

generateRequired: args.required,
strictNullChecks: args.strictNullChecks,
out: args.out

@@ -640,2 +853,2 @@ });

console.log(JSON.stringify(result));
*/
*/

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc