Socket
Socket
Sign inDemoInstall

json-schema-to-typescript

Package Overview
Dependencies
Maintainers
1
Versions
114
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-schema-to-typescript - npm Package Compare versions

Comparing version 7.1.0 to 8.0.0

.eslintrc.json

4

CHANGELOG.md
# Changelog
## 8.0.0
- e144890 Improve generated output when mixing nulls and unions (#261)
## 7.1.0

@@ -4,0 +8,0 @@

289

dist/src/generator.js

@@ -10,3 +10,3 @@ "use strict";

if (options === void 0) { options = index_1.DEFAULT_OPTIONS; }
return [
return ([
options.bannerComment,

@@ -18,4 +18,3 @@ declareNamedTypes(ast, options, ast.standaloneName),

.filter(Boolean)
.join('\n\n')
+ '\n'; // trailing newline
.join('\n\n') + '\n'); // trailing newline
}

@@ -43,5 +42,3 @@ exports.generate = generate;

case 'INTERFACE':
type = getSuperTypesAndParams(ast).reduce(function (prev, ast) {
return prev + declareEnums(ast, options, processed);
}, '');
type = getSuperTypesAndParams(ast).reduce(function (prev, ast) { return prev + declareEnums(ast, options, processed); }, '');
break;

@@ -66,7 +63,12 @@ default:

type = [
AST_1.hasStandaloneName(ast) && (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && generateStandaloneInterface(ast, options),
getSuperTypesAndParams(ast).map(function (ast) {
return declareNamedInterfaces(ast, options, rootASTName, processed);
}).filter(Boolean).join('\n')
].filter(Boolean).join('\n');
AST_1.hasStandaloneName(ast) &&
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
generateStandaloneInterface(ast, options),
getSuperTypesAndParams(ast)
.map(function (ast) { return declareNamedInterfaces(ast, options, rootASTName, processed); })
.filter(Boolean)
.join('\n')
]
.filter(Boolean)
.join('\n');
break;

@@ -76,3 +78,6 @@ case 'INTERSECTION':

case 'UNION':
type = ast.params.map(function (_) { return declareNamedInterfaces(_, options, rootASTName, processed); }).filter(Boolean).join('\n');
type = ast.params
.map(function (_) { return declareNamedInterfaces(_, options, rootASTName, processed); })
.filter(Boolean)
.join('\n');
if (ast.type === 'TUPLE' && ast.spreadParam) {

@@ -99,3 +104,5 @@ type += declareNamedInterfaces(ast.spreadParam, options, rootASTName, processed);

AST_1.hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined
].filter(Boolean).join('\n');
]
.filter(Boolean)
.join('\n');
break;

@@ -106,6 +113,9 @@ case 'ENUM':

case 'INTERFACE':
type = getSuperTypesAndParams(ast).map(function (ast) {
return (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && declareNamedTypes(ast, options, rootASTName, processed);
type = getSuperTypesAndParams(ast)
.map(function (ast) {
return (ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
declareNamedTypes(ast, options, rootASTName, processed);
})
.filter(Boolean).join('\n');
.filter(Boolean)
.join('\n');
break;

@@ -117,5 +127,12 @@ case 'INTERSECTION':

AST_1.hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined,
ast.params.map(function (ast) { return declareNamedTypes(ast, options, rootASTName, processed); }).filter(Boolean).join('\n'),
('spreadParam' in ast && ast.spreadParam) ? declareNamedTypes(ast.spreadParam, options, rootASTName, processed) : undefined
].filter(Boolean).join('\n');
ast.params
.map(function (ast) { return declareNamedTypes(ast, options, rootASTName, processed); })
.filter(Boolean)
.join('\n'),
'spreadParam' in ast && ast.spreadParam
? declareNamedTypes(ast.spreadParam, options, rootASTName, processed)
: undefined
]
.filter(Boolean)
.join('\n');
break;

@@ -142,85 +159,98 @@ default:

switch (ast.type) {
case 'ANY': return 'any';
case 'ARRAY': return (function () {
var type = generateType(ast.params, options);
return type.endsWith('"') ? '(' + type + ')[]' : type + '[]';
})();
case 'BOOLEAN': return 'boolean';
case 'INTERFACE': return generateInterface(ast, options);
case 'INTERSECTION': return generateSetOperation(ast, options);
case 'LITERAL': return JSON.stringify(ast.params);
case 'NUMBER': return 'number';
case 'NULL': return 'null';
case 'OBJECT': return 'object';
case 'REFERENCE': return ast.params;
case 'STRING': return 'string';
case 'TUPLE': return (function () {
var minItems = ast.minItems;
var maxItems = ast.maxItems || -1;
var spreadParam = ast.spreadParam;
var astParams = ast.params.slice();
if (minItems > 0 && minItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
if (maxItems < 0) {
// no max items and no spread param, so just spread any
spreadParam = AST_1.T_ANY;
case 'ANY':
return 'any';
case 'ARRAY':
return (function () {
var type = generateType(ast.params, options);
return type.endsWith('"') ? '(' + type + ')[]' : type + '[]';
})();
case 'BOOLEAN':
return 'boolean';
case 'INTERFACE':
return generateInterface(ast, options);
case 'INTERSECTION':
return generateSetOperation(ast, options);
case 'LITERAL':
return JSON.stringify(ast.params);
case 'NUMBER':
return 'number';
case 'NULL':
return 'null';
case 'OBJECT':
return 'object';
case 'REFERENCE':
return ast.params;
case 'STRING':
return 'string';
case 'TUPLE':
return (function () {
var minItems = ast.minItems;
var maxItems = ast.maxItems || -1;
var spreadParam = ast.spreadParam;
var astParams = ast.params.slice();
if (minItems > 0 && minItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
if (maxItems < 0) {
// no max items and no spread param, so just spread any
spreadParam = AST_1.T_ANY;
}
}
}
if (maxItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
// fill the tuple with any elements
for (var i = astParams.length; i < maxItems; i += 1) {
astParams.push(AST_1.T_ANY);
if (maxItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
// fill the tuple with any elements
for (var i = astParams.length; i < maxItems; i += 1) {
astParams.push(AST_1.T_ANY);
}
}
}
function addSpreadParam(params) {
if (spreadParam) {
var spread = '...(' + generateType(spreadParam, options) + ')[]';
params.push(spread);
function addSpreadParam(params) {
if (spreadParam) {
var spread = '...(' + generateType(spreadParam, options) + ')[]';
params.push(spread);
}
return params;
}
return params;
}
function paramsToString(params) {
return '[' + params.join(', ') + ']';
}
var paramsList = astParams
.map(function (param) { return generateType(param, options); });
if (paramsList.length > minItems) {
/*
if there are more items than the min, we return a union of tuples instead of
using the optional element operator. This is done because it is more typesafe.
// optional element operator
type A = [string, string?, string?]
const a: A = ['a', undefined, 'c'] // no error
// union of tuples
type B = [string] | [string, string] | [string, string, string]
const b: B = ['a', undefined, 'c'] // TS error
*/
var cumulativeParamsList = paramsList.slice(0, minItems);
var typesToUnion = [];
if (cumulativeParamsList.length > 0) {
// actually has minItems, so add the initial state
typesToUnion.push(paramsToString(cumulativeParamsList));
function paramsToString(params) {
return '[' + params.join(', ') + ']';
}
else {
// no minItems means it's acceptable to have an empty tuple type
typesToUnion.push(paramsToString([]));
}
for (var i = minItems; i < paramsList.length; i += 1) {
cumulativeParamsList.push(paramsList[i]);
if (i === paramsList.length - 1) {
// only the last item in the union should have the spread parameter
addSpreadParam(cumulativeParamsList);
var paramsList = astParams.map(function (param) { return generateType(param, options); });
if (paramsList.length > minItems) {
/*
if there are more items than the min, we return a union of tuples instead of
using the optional element operator. This is done because it is more typesafe.
// optional element operator
type A = [string, string?, string?]
const a: A = ['a', undefined, 'c'] // no error
// union of tuples
type B = [string] | [string, string] | [string, string, string]
const b: B = ['a', undefined, 'c'] // TS error
*/
var cumulativeParamsList = paramsList.slice(0, minItems);
var typesToUnion = [];
if (cumulativeParamsList.length > 0) {
// actually has minItems, so add the initial state
typesToUnion.push(paramsToString(cumulativeParamsList));
}
typesToUnion.push(paramsToString(cumulativeParamsList));
else {
// no minItems means it's acceptable to have an empty tuple type
typesToUnion.push(paramsToString([]));
}
for (var i = minItems; i < paramsList.length; i += 1) {
cumulativeParamsList.push(paramsList[i]);
if (i === paramsList.length - 1) {
// only the last item in the union should have the spread parameter
addSpreadParam(cumulativeParamsList);
}
typesToUnion.push(paramsToString(cumulativeParamsList));
}
return typesToUnion.join('|');
}
return typesToUnion.join('|');
}
// no max items so only need to return one type
return paramsToString(addSpreadParam(paramsList));
})();
case 'UNION': return generateSetOperation(ast, options);
case 'CUSTOM_TYPE': return ast.params;
// no max items so only need to return one type
return paramsToString(addSpreadParam(paramsList));
})();
case 'UNION':
return generateSetOperation(ast, options);
case 'CUSTOM_TYPE':
return ast.params;
}

@@ -237,5 +267,5 @@ }

function generateInterface(ast, options) {
return "{"
+ '\n'
+ ast.params
return ("{" +
'\n' +
ast.params
.filter(function (_) { return !_.isPatternProperty && !_.isUnreachableDefinition; })

@@ -248,45 +278,42 @@ .map(function (_a) {

var isRequired = _a[0], keyName = _a[1], ast = _a[2], type = _a[3];
return (AST_1.hasComment(ast) && !ast.standaloneName ? generateComment(ast.comment) + '\n' : '')
+ escapeKeyName(keyName)
+ (isRequired ? '' : '?')
+ ': '
+ (AST_1.hasStandaloneName(ast) ? utils_1.toSafeString(type) : type);
return (AST_1.hasComment(ast) && !ast.standaloneName ? generateComment(ast.comment) + '\n' : '') +
escapeKeyName(keyName) +
(isRequired ? '' : '?') +
': ' +
(AST_1.hasStandaloneName(ast) ? utils_1.toSafeString(type) : type);
})
.join('\n')
+ '\n'
+ '}';
.join('\n') +
'\n' +
'}');
}
function generateComment(comment) {
return [
'/**'
].concat(comment.split('\n').map(function (_) { return ' * ' + _; }), [
' */'
]).join('\n');
return ['/**'].concat(comment.split('\n').map(function (_) { return ' * ' + _; }), [' */']).join('\n');
}
function generateStandaloneEnum(ast, options) {
return (AST_1.hasComment(ast) ? generateComment(ast.comment) + '\n' : '')
+ 'export ' + (options.enableConstEnums ? 'const ' : '') + ("enum " + utils_1.toSafeString(ast.standaloneName) + " {")
+ '\n'
+ ast.params.map(function (_a) {
return ((AST_1.hasComment(ast) ? generateComment(ast.comment) + '\n' : '') +
'export ' +
(options.enableConstEnums ? 'const ' : '') +
("enum " + utils_1.toSafeString(ast.standaloneName) + " {") +
'\n' +
ast.params.map(function (_a) {
var ast = _a.ast, keyName = _a.keyName;
return keyName + ' = ' + generateType(ast, options);
})
.join(',\n')
+ '\n'
+ '}';
}).join(',\n') +
'\n' +
'}');
}
function generateStandaloneInterface(ast, options) {
return (AST_1.hasComment(ast) ? generateComment(ast.comment) + '\n' : '')
+ ("export interface " + utils_1.toSafeString(ast.standaloneName) + " ")
+ (ast.superTypes.length > 0 ? "extends " + ast.superTypes.map(function (superType) { return utils_1.toSafeString(superType.standaloneName); }).join(', ') + " " : '')
+ generateInterface(ast, options);
return ((AST_1.hasComment(ast) ? generateComment(ast.comment) + '\n' : '') +
("export interface " + utils_1.toSafeString(ast.standaloneName) + " ") +
(ast.superTypes.length > 0
? "extends " + ast.superTypes.map(function (superType) { return utils_1.toSafeString(superType.standaloneName); }).join(', ') + " "
: '') +
generateInterface(ast, options));
}
function generateStandaloneType(ast, options) {
return (AST_1.hasComment(ast) ? generateComment(ast.comment) + '\n' : '')
+ ("export type " + utils_1.toSafeString(ast.standaloneName) + " = " + generateType(lodash_1.omit(ast, 'standaloneName') /* TODO */, options));
return ((AST_1.hasComment(ast) ? generateComment(ast.comment) + '\n' : '') +
("export type " + utils_1.toSafeString(ast.standaloneName) + " = " + generateType(lodash_1.omit(ast, 'standaloneName') /* TODO */, options)));
}
function escapeKeyName(keyName) {
if (keyName.length
&& /[A-Za-z_$]/.test(keyName.charAt(0))
&& /^[\w$]+$/.test(keyName)) {
if (keyName.length && /[A-Za-z_$]/.test(keyName.charAt(0)) && /^[\w$]+$/.test(keyName)) {
return keyName;

@@ -300,6 +327,4 @@ }

function getSuperTypesAndParams(ast) {
return ast.params
.map(function (param) { return param.ast; })
.concat(ast.superTypes);
return ast.params.map(function (param) { return param.ast; }).concat(ast.superTypes);
}
//# sourceMappingURL=generator.js.map

@@ -93,4 +93,8 @@ "use strict";

if (options === void 0) { options = exports.DEFAULT_OPTIONS; }
var contents = utils_1.Try(function () { return fs_1.readFileSync(filename); }, function () { throw new ReferenceError("Unable to read file \"" + filename + "\""); });
var schema = utils_1.Try(function () { return JSON.parse(contents.toString()); }, function () { throw new TypeError("Error parsing JSON in file \"" + filename + "\""); });
var contents = utils_1.Try(function () { return fs_1.readFileSync(filename); }, function () {
throw new ReferenceError("Unable to read file \"" + filename + "\"");
});
var schema = utils_1.Try(function () { return JSON.parse(contents.toString()); }, function () {
throw new TypeError("Error parsing JSON in file \"" + filename + "\"");
});
return compile(schema, utils_1.stripExtension(filename), __assign({ cwd: path_1.dirname(filename) }, options));

@@ -110,3 +114,3 @@ }

errors.forEach(function (_) { return utils_1.error(_); });
throw new ValidationError;
throw new ValidationError();
}

@@ -122,4 +126,4 @@ // normalize options

return [4 /*yield*/, resolver_1.dereference(normalizer_1.normalize(schema, name), _options)];
case 1: return [2 /*return*/, _a.apply(void 0, [_b.apply(void 0, [_c.apply(void 0, [_d.apply(void 0, [_e.sent(), _options])]),
_options]), _options])];
case 1: return [2 /*return*/, _a.apply(void 0, [_b.apply(void 0, [_c.apply(void 0, [_d.apply(void 0, [_e.sent(), _options])]), _options]),
_options])];
}

@@ -126,0 +130,0 @@ });

@@ -17,2 +17,10 @@ "use strict";

}
rules.set('Remove `type=["null"]` if `enum=[null]`', function (schema) {
if (Array.isArray(schema.enum) &&
schema.enum.some(function (e) { return e === null; }) &&
Array.isArray(schema.type) &&
schema.type.includes('null')) {
schema.type = schema.type.filter(function (type) { return type !== 'null'; });
}
});
rules.set('Destructure unary types', function (schema) {

@@ -35,5 +43,3 @@ if (schema.type && Array.isArray(schema.type) && schema.type.length === 1) {

rules.set('Default additionalProperties to true', function (schema) {
if (!('additionalProperties' in schema) &&
isObjectType(schema) &&
schema.patternProperties === undefined) {
if (!('additionalProperties' in schema) && isObjectType(schema) && schema.patternProperties === undefined) {
schema.additionalProperties = true;

@@ -40,0 +46,0 @@ }

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

return Object.assign(ast, {
params: ast.params.map(function (_) {
return Object.assign(_, { ast: optimize(_.ast, processed) });
})
params: ast.params.map(function (_) { return Object.assign(_, { ast: optimize(_.ast, processed) }); })
});

@@ -31,5 +29,3 @@ case 'INTERSECTION':

// [A, B, B] -> [A, B]
ast.params = lodash_1.uniqBy(ast.params, function (_) {
return _.type + "------" + stringify(_.params);
});
ast.params = lodash_1.uniqBy(ast.params, function (_) { return _.type + "------" + stringify(_.params); });
return Object.assign(ast, {

@@ -36,0 +32,0 @@ params: ast.params.map(function (_) { return optimize(_, processed); })

@@ -180,3 +180,5 @@ "use strict";

keyName: keyName,
params: schema.type.map(function (_) { return parse({ type: _ }, options, rootSchema, undefined, true, processed, usedNames); }),
params: schema.type.map(function (_) {
return parse(__assign({}, schema, { type: _ }), options, rootSchema, undefined, true, processed, usedNames);
}),
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),

@@ -279,3 +281,3 @@ type: 'UNION'

// value definition, we can validate against that.
singlePatternProperty = (!schema.additionalProperties && Object.keys(schema.patternProperties).length === 1);
singlePatternProperty = !schema.additionalProperties && Object.keys(schema.patternProperties).length === 1;
asts = asts.concat(lodash_1.map(schema.patternProperties, function (value, key) {

@@ -285,3 +287,3 @@ var ast = parse(value, options, rootSchema, key, true, processed, usedNames);

ast.comment = ast.comment ? ast.comment + "\n\n" + comment : comment;
return ({
return {
ast: ast,

@@ -292,3 +294,3 @@ isPatternProperty: !singlePatternProperty,

keyName: singlePatternProperty ? '[k: string]' : key
});
};
}));

@@ -295,0 +297,0 @@ }

@@ -47,3 +47,3 @@ "use strict";

utils_1.log(cli_color_1.whiteBright.bgGreen('resolver'), schema, cwd);
parser = new $RefParser;
parser = new $RefParser();
return [2 /*return*/, parser.dereference(cwd, schema, $refOptions)];

@@ -50,0 +50,0 @@ });

@@ -16,2 +16,6 @@ "use strict";

return 'ONE_OF';
if (Array.isArray(schema.type))
return 'UNION';
if (schema.type === 'null')
return 'NULL';
if (schema.items)

@@ -25,9 +29,11 @@ return 'TYPED_ARRAY';

return 'REFERENCE';
if (Array.isArray(schema.type))
return 'UNION';
switch (schema.type) {
case 'string': return 'STRING';
case 'number': return 'NUMBER';
case 'integer': return 'NUMBER';
case 'boolean': return 'BOOLEAN';
case 'string':
return 'STRING';
case 'number':
return 'NUMBER';
case 'integer':
return 'NUMBER';
case 'boolean':
return 'BOOLEAN';
case 'object':

@@ -38,10 +44,14 @@ if (!schema.properties && !lodash_1.isPlainObject(schema)) {

break;
case 'array': return 'UNTYPED_ARRAY';
case 'null': return 'NULL';
case 'any': return 'ANY';
case 'array':
return 'UNTYPED_ARRAY';
case 'any':
return 'ANY';
}
switch (typeof schema.default) {
case 'boolean': return 'BOOLEAN';
case 'number': return 'NUMBER';
case 'string': return 'STRING';
case 'boolean':
return 'BOOLEAN';
case 'number':
return 'NUMBER';
case 'string':
return 'STRING';
}

@@ -48,0 +58,0 @@ if (schema.id)

@@ -133,3 +133,5 @@ "use strict";

// technically you can put definitions on any key
Object.keys(schema).filter(function (key) { return !BLACKLISTED_KEYS.has(key); }).forEach(function (key) {
Object.keys(schema)
.filter(function (key) { return !BLACKLISTED_KEYS.has(key); })
.forEach(function (key) {
var child = schema[key];

@@ -136,0 +138,0 @@ if (child && typeof child === 'object') {

{
"name": "json-schema-to-typescript",
"version": "7.1.0",
"version": "8.0.0",
"description": "compile json schema to typescript typings",

@@ -18,6 +18,7 @@ "main": "dist/src/index.js",

"clean": "shx rm -rf dist",
"lint": "tslint -c tslint.json src/*.ts",
"lint": "eslint src/*.ts test/*.ts",
"tdd": "concurrently -r -p '' -k 'npm run watch' 'npm run watch:test'",
"test": "ava --verbose",
"pretest": "npm run build",
"test": "ava --serial --verbose",
"prepublishOnly": "npm test",
"pretest": "npm run build && npm run lint",
"watch": "tsc -w",

@@ -57,3 +58,3 @@ "watch:test": "ava -w"

"mz": "^2.7.0",
"prettier": "^1.18.2",
"prettier": "^1.19.1",
"stdin": "0.0.1"

@@ -66,2 +67,4 @@ },

"@types/mz": "0.0.32",
"@typescript-eslint/eslint-plugin": "^2.9.0",
"@typescript-eslint/parser": "^2.9.0",
"ava": "^1.2.1",

@@ -71,5 +74,7 @@ "browserify": "^16.2.3",

"concurrently": "^4.1.0",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-prettier": "^3.1.1",
"shx": "^0.3.2",
"tsify": "^4.0.1",
"tslint": "^5.13.1",
"typescript": "^3.3.3333"

@@ -76,0 +81,0 @@ },

#!/usr/bin/env node
import { whiteBright } from 'cli-color'
import { JSONSchema4 } from 'json-schema'
import {whiteBright} from 'cli-color'
import {JSONSchema4} from 'json-schema'
import minimist = require('minimist')
import { readFile, writeFile } from 'mz/fs'
import { resolve } from 'path'
import {readFile, writeFile} from 'mz/fs'
import {resolve} from 'path'
import stdin = require('stdin')
import { compile, Options } from './index'
import {compile, Options} from './index'
main(minimist(process.argv.slice(2), {
alias: {
help: ['h'],
input: ['i'],
output: ['o']
}
}))
main(
minimist(process.argv.slice(2), {
alias: {
help: ['h'],
input: ['i'],
output: ['o']
}
})
)
async function main(argv: minimist.ParsedArgs) {
if (argv.help) {

@@ -37,3 +38,2 @@ printHelp()

}
}

@@ -64,3 +64,3 @@

process.stdout.write(
`
`
${pkg.name} ${pkg.version}

@@ -67,0 +67,0 @@ Usage: json2ts [--input, -i] [IN_FILE] [--output, -o] [OUT_FILE] [OPTIONS]

@@ -1,6 +0,6 @@

import { format as prettify } from 'prettier'
import { Options } from './'
import {format as prettify} from 'prettier'
import {Options} from './'
export function format(code: string, options: Options): string {
return prettify(code, { parser: 'typescript', ...options.style })
return prettify(code, {parser: 'typescript', ...options.style})
}

@@ -1,28 +0,33 @@

import { whiteBright } from 'cli-color'
import { omit } from 'lodash'
import { DEFAULT_OPTIONS, Options } from './index'
import {whiteBright} from 'cli-color'
import {omit} from 'lodash'
import {DEFAULT_OPTIONS, Options} from './index'
import {
AST, ASTWithStandaloneName, hasComment, hasStandaloneName, T_ANY, TArray, TEnum, TInterface, TIntersection,
TNamedInterface, TUnion
AST,
ASTWithStandaloneName,
hasComment,
hasStandaloneName,
T_ANY,
TArray,
TEnum,
TInterface,
TIntersection,
TNamedInterface,
TUnion
} from './types/AST'
import { log, toSafeString } from './utils'
import {log, toSafeString} from './utils'
export function generate(ast: AST, options = DEFAULT_OPTIONS): string {
return [
options.bannerComment,
declareNamedTypes(ast, options, ast.standaloneName!),
declareNamedInterfaces(ast, options, ast.standaloneName!),
declareEnums(ast, options)
]
.filter(Boolean)
.join('\n\n')
+ '\n' // trailing newline
return (
[
options.bannerComment,
declareNamedTypes(ast, options, ast.standaloneName!),
declareNamedInterfaces(ast, options, ast.standaloneName!),
declareEnums(ast, options)
]
.filter(Boolean)
.join('\n\n') + '\n'
) // trailing newline
}
function declareEnums(
ast: AST,
options: Options,
processed = new Set<AST>()
): string {
function declareEnums(ast: AST, options: Options, processed = new Set<AST>()): string {
if (processed.has(ast)) {

@@ -48,5 +53,3 @@ return ''

case 'INTERFACE':
type = getSuperTypesAndParams(ast).reduce((prev, ast) =>
prev + declareEnums(ast, options, processed),
'')
type = getSuperTypesAndParams(ast).reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
break

@@ -60,9 +63,3 @@ default:

function declareNamedInterfaces(
ast: AST,
options: Options,
rootASTName: string,
processed = new Set<AST>()
): string {
function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string, processed = new Set<AST>()): string {
if (processed.has(ast)) {

@@ -81,7 +78,12 @@ return ''

type = [
hasStandaloneName(ast) && (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && generateStandaloneInterface(ast, options),
getSuperTypesAndParams(ast).map(ast =>
declareNamedInterfaces(ast, options, rootASTName, processed)
).filter(Boolean).join('\n')
].filter(Boolean).join('\n')
hasStandaloneName(ast) &&
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
generateStandaloneInterface(ast, options),
getSuperTypesAndParams(ast)
.map(ast => declareNamedInterfaces(ast, options, rootASTName, processed))
.filter(Boolean)
.join('\n')
]
.filter(Boolean)
.join('\n')
break

@@ -91,3 +93,6 @@ case 'INTERSECTION':

case 'UNION':
type = ast.params.map(_ => declareNamedInterfaces(_, options, rootASTName, processed)).filter(Boolean).join('\n')
type = ast.params
.map(_ => declareNamedInterfaces(_, options, rootASTName, processed))
.filter(Boolean)
.join('\n')
if (ast.type === 'TUPLE' && ast.spreadParam) {

@@ -104,9 +109,3 @@ type += declareNamedInterfaces(ast.spreadParam, options, rootASTName, processed)

function declareNamedTypes(
ast: AST,
options: Options,
rootASTName: string,
processed = new Set<AST>()
): string {
function declareNamedTypes(ast: AST, options: Options, rootASTName: string, processed = new Set<AST>()): string {
if (processed.has(ast)) {

@@ -124,3 +123,5 @@ return ''

hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined
].filter(Boolean).join('\n')
]
.filter(Boolean)
.join('\n')
break

@@ -131,5 +132,10 @@ case 'ENUM':

case 'INTERFACE':
type = getSuperTypesAndParams(ast).map(ast =>
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) && declareNamedTypes(ast, options, rootASTName, processed))
.filter(Boolean).join('\n')
type = getSuperTypesAndParams(ast)
.map(
ast =>
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
declareNamedTypes(ast, options, rootASTName, processed)
)
.filter(Boolean)
.join('\n')
break

@@ -141,5 +147,12 @@ case 'INTERSECTION':

hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined,
ast.params.map(ast => declareNamedTypes(ast, options, rootASTName, processed)).filter(Boolean).join('\n'),
('spreadParam' in ast && ast.spreadParam) ? declareNamedTypes(ast.spreadParam, options, rootASTName, processed) : undefined
].filter(Boolean).join('\n')
ast.params
.map(ast => declareNamedTypes(ast, options, rootASTName, processed))
.filter(Boolean)
.join('\n'),
'spreadParam' in ast && ast.spreadParam
? declareNamedTypes(ast.spreadParam, options, rootASTName, processed)
: undefined
]
.filter(Boolean)
.join('\n')
break

@@ -173,54 +186,65 @@ default:

switch (ast.type) {
case 'ANY': return 'any'
case 'ARRAY': return (() => {
const type = generateType(ast.params, options)
return type.endsWith('"') ? '(' + type + ')[]' : type + '[]'
})()
case 'BOOLEAN': return 'boolean'
case 'INTERFACE': return generateInterface(ast, options)
case 'INTERSECTION': return generateSetOperation(ast, options)
case 'LITERAL': return JSON.stringify(ast.params)
case 'NUMBER': return 'number'
case 'NULL': return 'null'
case 'OBJECT': return 'object'
case 'REFERENCE': return ast.params
case 'STRING': return 'string'
case 'TUPLE': return (() => {
const minItems = ast.minItems
const maxItems = ast.maxItems || -1
case 'ANY':
return 'any'
case 'ARRAY':
return (() => {
const type = generateType(ast.params, options)
return type.endsWith('"') ? '(' + type + ')[]' : type + '[]'
})()
case 'BOOLEAN':
return 'boolean'
case 'INTERFACE':
return generateInterface(ast, options)
case 'INTERSECTION':
return generateSetOperation(ast, options)
case 'LITERAL':
return JSON.stringify(ast.params)
case 'NUMBER':
return 'number'
case 'NULL':
return 'null'
case 'OBJECT':
return 'object'
case 'REFERENCE':
return ast.params
case 'STRING':
return 'string'
case 'TUPLE':
return (() => {
const minItems = ast.minItems
const maxItems = ast.maxItems || -1
let spreadParam = ast.spreadParam
const astParams = [...ast.params]
if (minItems > 0 && minItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
if (maxItems < 0) {
// no max items and no spread param, so just spread any
spreadParam = T_ANY
let spreadParam = ast.spreadParam
const astParams = [...ast.params]
if (minItems > 0 && minItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
if (maxItems < 0) {
// no max items and no spread param, so just spread any
spreadParam = T_ANY
}
}
}
if (maxItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
// fill the tuple with any elements
for (let i = astParams.length; i < maxItems; i += 1) {
astParams.push(T_ANY)
if (maxItems > astParams.length && ast.spreadParam === undefined) {
// this is a valid state, and JSONSchema doesn't care about the item type
// fill the tuple with any elements
for (let i = astParams.length; i < maxItems; i += 1) {
astParams.push(T_ANY)
}
}
}
function addSpreadParam(params: string[]): string[] {
if (spreadParam) {
const spread = '...(' + generateType(spreadParam, options) + ')[]'
params.push(spread)
function addSpreadParam(params: string[]): string[] {
if (spreadParam) {
const spread = '...(' + generateType(spreadParam, options) + ')[]'
params.push(spread)
}
return params
}
return params
}
function paramsToString(params: string[]): string {
return '[' + params.join(', ') + ']'
}
function paramsToString(params: string[]): string {
return '[' + params.join(', ') + ']'
}
const paramsList = astParams
.map(param => generateType(param, options))
const paramsList = astParams.map(param => generateType(param, options))
if (paramsList.length > minItems) {
/*
if (paramsList.length > minItems) {
/*
if there are more items than the min, we return a union of tuples instead of

@@ -238,32 +262,34 @@ using the optional element operator. This is done because it is more typesafe.

const cumulativeParamsList: string[] = paramsList.slice(0, minItems)
const typesToUnion: string[] = []
const cumulativeParamsList: string[] = paramsList.slice(0, minItems)
const typesToUnion: string[] = []
if (cumulativeParamsList.length > 0) {
// actually has minItems, so add the initial state
typesToUnion.push(paramsToString(cumulativeParamsList))
} else {
// no minItems means it's acceptable to have an empty tuple type
typesToUnion.push(paramsToString([]))
}
if (cumulativeParamsList.length > 0) {
// actually has minItems, so add the initial state
typesToUnion.push(paramsToString(cumulativeParamsList))
} else {
// no minItems means it's acceptable to have an empty tuple type
typesToUnion.push(paramsToString([]))
}
for (let i = minItems; i < paramsList.length; i += 1) {
cumulativeParamsList.push(paramsList[i])
for (let i = minItems; i < paramsList.length; i += 1) {
cumulativeParamsList.push(paramsList[i])
if (i === paramsList.length - 1) {
// only the last item in the union should have the spread parameter
addSpreadParam(cumulativeParamsList)
if (i === paramsList.length - 1) {
// only the last item in the union should have the spread parameter
addSpreadParam(cumulativeParamsList)
}
typesToUnion.push(paramsToString(cumulativeParamsList))
}
typesToUnion.push(paramsToString(cumulativeParamsList))
return typesToUnion.join('|')
}
return typesToUnion.join('|')
}
// no max items so only need to return one type
return paramsToString(addSpreadParam(paramsList))
})()
case 'UNION': return generateSetOperation(ast, options)
case 'CUSTOM_TYPE': return ast.params
// no max items so only need to return one type
return paramsToString(addSpreadParam(paramsList))
})()
case 'UNION':
return generateSetOperation(ast, options)
case 'CUSTOM_TYPE':
return ast.params
}

@@ -281,61 +307,66 @@ }

function generateInterface(
ast: TInterface,
options: Options
): string {
return `{`
+ '\n'
+ ast.params
function generateInterface(ast: TInterface, options: Options): string {
return (
`{` +
'\n' +
ast.params
.filter(_ => !_.isPatternProperty && !_.isUnreachableDefinition)
.map(({ isRequired, keyName, ast }) => [isRequired, keyName, ast, generateType(ast, options)] as [boolean, string, AST, string])
.map(([isRequired, keyName, ast, type]) =>
(hasComment(ast) && !ast.standaloneName ? generateComment(ast.comment) + '\n' : '')
+ escapeKeyName(keyName)
+ (isRequired ? '' : '?')
+ ': '
+ (hasStandaloneName(ast) ? toSafeString(type) : type)
.map(
({isRequired, keyName, ast}) =>
[isRequired, keyName, ast, generateType(ast, options)] as [boolean, string, AST, string]
)
.join('\n')
+ '\n'
+ '}'
.map(
([isRequired, keyName, ast, type]) =>
(hasComment(ast) && !ast.standaloneName ? generateComment(ast.comment) + '\n' : '') +
escapeKeyName(keyName) +
(isRequired ? '' : '?') +
': ' +
(hasStandaloneName(ast) ? toSafeString(type) : type)
)
.join('\n') +
'\n' +
'}'
)
}
function generateComment(comment: string): string {
return [
'/**',
...comment.split('\n').map(_ => ' * ' + _),
' */'
].join('\n')
return ['/**', ...comment.split('\n').map(_ => ' * ' + _), ' */'].join('\n')
}
function generateStandaloneEnum(ast: TEnum, options: Options): string {
return (hasComment(ast) ? generateComment(ast.comment) + '\n' : '')
+ 'export ' + (options.enableConstEnums ? 'const ' : '') + `enum ${toSafeString(ast.standaloneName)} {`
+ '\n'
+ ast.params.map(({ ast, keyName }) =>
keyName + ' = ' + generateType(ast, options)
)
.join(',\n')
+ '\n'
+ '}'
return (
(hasComment(ast) ? generateComment(ast.comment) + '\n' : '') +
'export ' +
(options.enableConstEnums ? 'const ' : '') +
`enum ${toSafeString(ast.standaloneName)} {` +
'\n' +
ast.params.map(({ast, keyName}) => keyName + ' = ' + generateType(ast, options)).join(',\n') +
'\n' +
'}'
)
}
function generateStandaloneInterface(ast: TNamedInterface, options: Options): string {
return (hasComment(ast) ? generateComment(ast.comment) + '\n' : '')
+ `export interface ${toSafeString(ast.standaloneName)} `
+ (ast.superTypes.length > 0 ? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} ` : '')
+ generateInterface(ast, options)
return (
(hasComment(ast) ? generateComment(ast.comment) + '\n' : '') +
`export interface ${toSafeString(ast.standaloneName)} ` +
(ast.superTypes.length > 0
? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} `
: '') +
generateInterface(ast, options)
)
}
function generateStandaloneType(ast: ASTWithStandaloneName, options: Options): string {
return (hasComment(ast) ? generateComment(ast.comment) + '\n' : '')
+ `export type ${toSafeString(ast.standaloneName)} = ${generateType(omit<AST>(ast, 'standaloneName') as AST /* TODO */, options)}`
return (
(hasComment(ast) ? generateComment(ast.comment) + '\n' : '') +
`export type ${toSafeString(ast.standaloneName)} = ${generateType(
omit<AST>(ast, 'standaloneName') as AST /* TODO */,
options
)}`
)
}
function escapeKeyName(keyName: string): string {
if (
keyName.length
&& /[A-Za-z_$]/.test(keyName.charAt(0))
&& /^[\w$]+$/.test(keyName)
) {
if (keyName.length && /[A-Za-z_$]/.test(keyName.charAt(0)) && /^[\w$]+$/.test(keyName)) {
return keyName

@@ -350,5 +381,3 @@ }

function getSuperTypesAndParams(ast: TInterface): AST[] {
return ast.params
.map(param => param.ast)
.concat(ast.superTypes)
return ast.params.map(param => param.ast).concat(ast.superTypes)
}

@@ -1,17 +0,17 @@

import { readFileSync } from 'fs'
import { JSONSchema4 } from 'json-schema'
import { Options as $RefOptions } from 'json-schema-ref-parser'
import { endsWith, merge } from 'lodash'
import { dirname } from 'path'
import { Options as PrettierOptions } from 'prettier'
import { format } from './formatter'
import { generate } from './generator'
import { normalize } from './normalizer'
import { optimize } from './optimizer'
import { parse } from './parser'
import { dereference } from './resolver'
import { error, stripExtension, Try } from './utils'
import { validate } from './validator'
import {readFileSync} from 'fs'
import {JSONSchema4} from 'json-schema'
import {Options as $RefOptions} from 'json-schema-ref-parser'
import {endsWith, merge} from 'lodash'
import {dirname} from 'path'
import {Options as PrettierOptions} from 'prettier'
import {format} from './formatter'
import {generate} from './generator'
import {normalize} from './normalizer'
import {optimize} from './optimizer'
import {parse} from './parser'
import {dereference} from './resolver'
import {error, stripExtension, Try} from './utils'
import {validate} from './validator'
export { EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema } from './types/JSONSchema'
export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema'

@@ -79,27 +79,19 @@ export interface Options {

export function compileFromFile(
filename: string,
options: Partial<Options> = DEFAULT_OPTIONS
): Promise<string> {
export function compileFromFile(filename: string, options: Partial<Options> = DEFAULT_OPTIONS): Promise<string> {
const contents = Try(
() => readFileSync(filename),
() => { throw new ReferenceError(`Unable to read file "${filename}"`) }
() => {
throw new ReferenceError(`Unable to read file "${filename}"`)
}
)
const schema = Try<JSONSchema4>(
() => JSON.parse(contents.toString()),
() => { throw new TypeError(`Error parsing JSON in file "${filename}"`) }
() => {
throw new TypeError(`Error parsing JSON in file "${filename}"`)
}
)
return compile(
schema,
stripExtension(filename),
{ cwd: dirname(filename), ...options }
)
return compile(schema, stripExtension(filename), {cwd: dirname(filename), ...options})
}
export async function compile(
schema: JSONSchema4,
name: string,
options: Partial<Options> = {}
): Promise<string> {
export async function compile(schema: JSONSchema4, name: string, options: Partial<Options> = {}): Promise<string> {
const _options = merge({}, DEFAULT_OPTIONS, options)

@@ -110,3 +102,3 @@

errors.forEach(_ => error(_))
throw new ValidationError
throw new ValidationError()
}

@@ -119,10 +111,8 @@

return format(generate(
optimize(
parse(await dereference(normalize(schema, name), _options), _options)
),
return format(
generate(optimize(parse(await dereference(normalize(schema, name), _options), _options)), _options),
_options
), _options)
)
}
export class ValidationError extends Error { }
export class ValidationError extends Error {}

@@ -1,6 +0,6 @@

import { whiteBright } from 'cli-color'
import {whiteBright} from 'cli-color'
import stringify = require('json-stringify-safe')
import { cloneDeep } from 'lodash'
import { JSONSchema, JSONSchemaTypeName, NormalizedJSONSchema } from './types/JSONSchema'
import { escapeBlockComment, justName, log, toSafeString, traverse } from './utils'
import {cloneDeep} from 'lodash'
import {JSONSchema, JSONSchemaTypeName, NormalizedJSONSchema} from './types/JSONSchema'
import {escapeBlockComment, justName, log, toSafeString, traverse} from './utils'

@@ -20,2 +20,13 @@ type Rule = (schema: JSONSchema, rootSchema: JSONSchema, fileName?: string) => void

rules.set('Remove `type=["null"]` if `enum=[null]`', schema => {
if (
Array.isArray(schema.enum) &&
schema.enum.some(e => e === null) &&
Array.isArray(schema.type) &&
schema.type.includes('null')
) {
schema.type = schema.type.filter(type => type !== 'null')
}
})
rules.set('Destructure unary types', schema => {

@@ -27,3 +38,3 @@ if (schema.type && Array.isArray(schema.type) && schema.type.length === 1) {

rules.set('Add empty `required` property if none is defined', (schema) => {
rules.set('Add empty `required` property if none is defined', schema => {
if (!('required' in schema) && isObjectType(schema)) {

@@ -34,3 +45,3 @@ schema.required = []

rules.set('Transform `required`=false to `required`=[]', (schema) => {
rules.set('Transform `required`=false to `required`=[]', schema => {
if (schema.required === false) {

@@ -42,6 +53,4 @@ schema.required = []

// TODO: default to empty schema (as per spec) instead
rules.set('Default additionalProperties to true', (schema) => {
if (!('additionalProperties' in schema) &&
isObjectType(schema) &&
schema.patternProperties === undefined) {
rules.set('Default additionalProperties to true', schema => {
if (!('additionalProperties' in schema) && isObjectType(schema) && schema.patternProperties === undefined) {
schema.additionalProperties = true

@@ -61,3 +70,3 @@ }

rules.set('Normalise schema.minItems', (schema) => {
rules.set('Normalise schema.minItems', schema => {
// make sure we only add the props onto array types

@@ -99,3 +108,3 @@ if (isArrayType(schema)) {

rules.forEach((rule, key) => {
traverse(_schema, (schema) => rule(schema, _schema, filename))
traverse(_schema, schema => rule(schema, _schema, filename))
log(whiteBright.bgYellow('normalizer'), `Applied rule: "${key}"`)

@@ -102,0 +111,0 @@ })

@@ -1,9 +0,8 @@

import { whiteBright } from 'cli-color'
import {whiteBright} from 'cli-color'
import stringify = require('json-stringify-safe')
import { uniqBy } from 'lodash'
import { AST, T_ANY } from './types/AST'
import { log } from './utils'
import {uniqBy} from 'lodash'
import {AST, T_ANY} from './types/AST'
import {log} from './utils'
export function optimize(ast: AST, processed = new Map<AST, AST>()): AST {
log(whiteBright.bgCyan('optimizer'), ast, processed.has(ast) ? '(FROM CACHE)' : '')

@@ -20,9 +19,6 @@

return Object.assign(ast, {
params: ast.params.map(_ =>
Object.assign(_, { ast: optimize(_.ast, processed) })
)
params: ast.params.map(_ => Object.assign(_, {ast: optimize(_.ast, processed)}))
})
case 'INTERSECTION':
case 'UNION':
// [A, B, C, Any] -> Any

@@ -35,5 +31,3 @@ if (ast.params.some(_ => _.type === 'ANY')) {

// [A, B, B] -> [A, B]
ast.params = uniqBy(ast.params, _ =>
`${_.type}------${stringify((_ as any).params)}`
)
ast.params = uniqBy(ast.params, _ => `${_.type}------${stringify((_ as any).params)}`)

@@ -40,0 +34,0 @@ return Object.assign(ast, {

@@ -1,10 +0,19 @@

import { whiteBright } from 'cli-color'
import { JSONSchema4Type, JSONSchema4TypeName } from 'json-schema'
import { findKey, includes, isPlainObject, map } from 'lodash'
import { format } from 'util'
import { Options } from './'
import { typeOfSchema } from './typeOfSchema'
import { AST, hasStandaloneName, T_ANY, T_ANY_ADDITIONAL_PROPERTIES, TInterface, TInterfaceParam, TNamedInterface, TTuple } from './types/AST'
import { JSONSchema, JSONSchemaWithDefinitions, SchemaSchema } from './types/JSONSchema'
import { generateName, log } from './utils'
import {whiteBright} from 'cli-color'
import {JSONSchema4Type, JSONSchema4TypeName} from 'json-schema'
import {findKey, includes, isPlainObject, map} from 'lodash'
import {format} from 'util'
import {Options} from './'
import {typeOfSchema} from './typeOfSchema'
import {
AST,
hasStandaloneName,
T_ANY,
T_ANY_ADDITIONAL_PROPERTIES,
TInterface,
TInterfaceParam,
TNamedInterface,
TTuple
} from './types/AST'
import {JSONSchema, JSONSchemaWithDefinitions, SchemaSchema} from './types/JSONSchema'
import {generateName, log} from './utils'

@@ -24,3 +33,2 @@ export type Processed = Map<JSONSchema | JSONSchema4Type, AST>

): AST {
// If we've seen this node before, return it.

@@ -37,3 +45,3 @@ if (processed.has(schema)) {

// TODO: Investigate alternative approaches (lazy-computing nodes, etc.)
let ast = {} as AST
const ast = {} as AST
processed.set(schema, ast)

@@ -43,3 +51,12 @@ const set = (_ast: AST) => Object.assign(ast, _ast)

return isSchema
? parseNonLiteral(schema as SchemaSchema, options, rootSchema, keyName, keyNameFromDefinition, set, processed, usedNames)
? parseNonLiteral(
schema as SchemaSchema,
options,
rootSchema,
keyName,
keyNameFromDefinition,
set,
processed,
usedNames
)
: parseLiteral(schema, keyName, keyNameFromDefinition, set)

@@ -72,3 +89,2 @@ }

) {
log(whiteBright.bgBlue('parser'), schema, '<-' + typeOfSchema(schema), processed.has(schema) ? '(FROM CACHE)' : '')

@@ -185,3 +201,11 @@

} else if (schema.additionalItems) {
arrayType.spreadParam = parse(schema.additionalItems, options, rootSchema, undefined, true, processed, usedNames)
arrayType.spreadParam = parse(
schema.additionalItems,
options,
rootSchema,
undefined,
true,
processed,
usedNames
)
}

@@ -203,3 +227,5 @@ return set(arrayType)

keyName,
params: (schema.type as JSONSchema4TypeName[]).map(_ => parse({ type: _ }, options, rootSchema, undefined, true, processed, usedNames)),
params: (schema.type as JSONSchema4TypeName[]).map(_ =>
parse({...schema, type: _}, options, rootSchema, undefined, true, processed, usedNames)
),
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),

@@ -217,3 +243,5 @@ type: 'UNION'

case 'UNNAMED_SCHEMA':
return set(newInterface(schema as SchemaSchema, options, rootSchema, processed, usedNames, keyName, keyNameFromDefinition))
return set(
newInterface(schema as SchemaSchema, options, rootSchema, processed, usedNames, keyName, keyNameFromDefinition)
)
case 'UNTYPED_ARRAY':

@@ -252,8 +280,4 @@ // normalised to not be undefined

*/
function standaloneName(
schema: JSONSchema,
keyNameFromDefinition: string | undefined,
usedNames: UsedNames
) {
let name = schema.title || schema.id || keyNameFromDefinition
function standaloneName(schema: JSONSchema, keyNameFromDefinition: string | undefined, usedNames: UsedNames) {
const name = schema.title || schema.id || keyNameFromDefinition
if (name) {

@@ -273,3 +297,3 @@ return generateName(name, usedNames)

): TInterface {
let name = standaloneName(schema, keyNameFromDefinition, usedNames)!
const name = standaloneName(schema, keyNameFromDefinition, usedNames)!
return {

@@ -329,3 +353,2 @@ comment: schema.description,

): TInterfaceParam[] {
let asts: TInterfaceParam[] = map(schema.properties, (value, key: string) => ({

@@ -344,35 +367,37 @@ ast: parse(value, options, rootSchema, key, true, processed, usedNames),

// value definition, we can validate against that.
singlePatternProperty = (
!schema.additionalProperties && Object.keys(schema.patternProperties).length === 1
)
singlePatternProperty = !schema.additionalProperties && Object.keys(schema.patternProperties).length === 1
asts = asts.concat(map(schema.patternProperties, (value, key: string) => {
let ast = parse(value, options, rootSchema, key, true, processed, usedNames)
let comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema definition
asts = asts.concat(
map(schema.patternProperties, (value, key: string) => {
const ast = parse(value, options, rootSchema, key, true, processed, usedNames)
const comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema definition
via the \`patternProperty\` "${key}".`
ast.comment = ast.comment ? `${ast.comment}\n\n${comment}` : comment
return ({
ast,
isPatternProperty: !singlePatternProperty,
isRequired: singlePatternProperty || includes(schema.required || [], key),
isUnreachableDefinition: false,
keyName: singlePatternProperty ? '[k: string]' : key
ast.comment = ast.comment ? `${ast.comment}\n\n${comment}` : comment
return {
ast,
isPatternProperty: !singlePatternProperty,
isRequired: singlePatternProperty || includes(schema.required || [], key),
isUnreachableDefinition: false,
keyName: singlePatternProperty ? '[k: string]' : key
}
})
}))
)
}
if (options.unreachableDefinitions) {
asts = asts.concat(map(schema.definitions, (value, key: string) => {
let ast = parse(value, options, rootSchema, key, true, processed, usedNames)
let comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema
asts = asts.concat(
map(schema.definitions, (value, key: string) => {
const ast = parse(value, options, rootSchema, key, true, processed, usedNames)
const comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema
via the \`definition\` "${key}".`
ast.comment = ast.comment ? `${ast.comment}\n\n${comment}` : comment
return {
ast,
isPatternProperty: false,
isRequired: includes(schema.required || [], key),
isUnreachableDefinition: true,
keyName: key
}
}))
ast.comment = ast.comment ? `${ast.comment}\n\n${comment}` : comment
return {
ast,
isPatternProperty: false,
isRequired: includes(schema.required || [], key),
isUnreachableDefinition: true,
keyName: key
}
})
)
}

@@ -411,3 +436,3 @@

type Definitions = { [k: string]: JSONSchema }
type Definitions = {[k: string]: JSONSchema}

@@ -417,7 +442,3 @@ /**

*/
function getDefinitions(
schema: JSONSchema,
isSchema = true,
processed = new Set<JSONSchema>()
): Definitions {
function getDefinitions(schema: JSONSchema, isSchema = true, processed = new Set<JSONSchema>()): Definitions {
if (processed.has(schema)) {

@@ -428,6 +449,9 @@ return {}

if (Array.isArray(schema)) {
return schema.reduce((prev, cur) => ({
...prev,
...getDefinitions(cur, false, processed)
}), {})
return schema.reduce(
(prev, cur) => ({
...prev,
...getDefinitions(cur, false, processed)
}),
{}
)
}

@@ -437,6 +461,9 @@ if (isPlainObject(schema)) {

...(isSchema && hasDefinitions(schema) ? schema.definitions : {}),
...Object.keys(schema).reduce<Definitions>((prev, cur) => ({
...prev,
...getDefinitions(schema[cur], false, processed)
}), {})
...Object.keys(schema).reduce<Definitions>(
(prev, cur) => ({
...prev,
...getDefinitions(schema[cur], false, processed)
}),
{}
)
}

@@ -443,0 +470,0 @@ }

@@ -1,10 +0,13 @@

import { whiteBright } from 'cli-color'
import {whiteBright} from 'cli-color'
import $RefParser = require('json-schema-ref-parser')
import { JSONSchema } from './types/JSONSchema'
import { log } from './utils'
import {JSONSchema} from './types/JSONSchema'
import {log} from './utils'
export async function dereference(schema: JSONSchema, {cwd, $refOptions}: {cwd: string, $refOptions: $RefParser.Options}): Promise<JSONSchema> {
export async function dereference(
schema: JSONSchema,
{cwd, $refOptions}: {cwd: string; $refOptions: $RefParser.Options}
): Promise<JSONSchema> {
log(whiteBright.bgGreen('resolver'), schema, cwd)
const parser = new $RefParser
const parser = new $RefParser()
return parser.dereference(cwd, schema, $refOptions)
}

@@ -1,3 +0,3 @@

import { isPlainObject } from 'lodash'
import { JSONSchema, SCHEMA_TYPE } from './types/JSONSchema'
import {isPlainObject} from 'lodash'
import {JSONSchema, SCHEMA_TYPE} from './types/JSONSchema'

@@ -12,2 +12,4 @@ /**

if (schema.oneOf) return 'ONE_OF'
if (Array.isArray(schema.type)) return 'UNION'
if (schema.type === 'null') return 'NULL'
if (schema.items) return 'TYPED_ARRAY'

@@ -17,8 +19,11 @@ if (schema.enum && schema.tsEnumNames) return 'NAMED_ENUM'

if (schema.$ref) return 'REFERENCE'
if (Array.isArray(schema.type)) return 'UNION'
switch (schema.type) {
case 'string': return 'STRING'
case 'number': return 'NUMBER'
case 'integer': return 'NUMBER'
case 'boolean': return 'BOOLEAN'
case 'string':
return 'STRING'
case 'number':
return 'NUMBER'
case 'integer':
return 'NUMBER'
case 'boolean':
return 'BOOLEAN'
case 'object':

@@ -29,11 +34,15 @@ if (!schema.properties && !isPlainObject(schema)) {

break
case 'array': return 'UNTYPED_ARRAY'
case 'null': return 'NULL'
case 'any': return 'ANY'
case 'array':
return 'UNTYPED_ARRAY'
case 'any':
return 'ANY'
}
switch (typeof schema.default) {
case 'boolean': return 'BOOLEAN'
case 'number': return 'NUMBER'
case 'string': return 'STRING'
case 'boolean':
return 'BOOLEAN'
case 'number':
return 'NUMBER'
case 'string':
return 'STRING'
}

@@ -40,0 +49,0 @@ if (schema.id) return 'NAMED_SCHEMA'

@@ -1,5 +0,5 @@

import { whiteBright } from 'cli-color'
import { deburr, isPlainObject, mapValues, trim, upperFirst } from 'lodash'
import { basename, extname } from 'path'
import { JSONSchema } from './types/JSONSchema'
import {whiteBright} from 'cli-color'
import {deburr, isPlainObject, mapValues, trim, upperFirst} from 'lodash'
import {basename, extname} from 'path'
import {JSONSchema} from './types/JSONSchema'

@@ -18,4 +18,4 @@ // TODO: pull out into a separate package

*/
export function dft<T, U>(object: { [k: string]: any }, cb: (value: U, key: string) => T): void {
for (let key in object) {
export function dft<T, U>(object: {[k: string]: any}, cb: (value: U, key: string) => T): void {
for (const key in object) {
if (!object.hasOwnProperty(key)) continue

@@ -27,20 +27,19 @@ if (isPlainObject(object[key])) dft(object[key], cb)

export function mapDeep(
object: object,
fn: (value: object, key?: string) => object,
key?: string
): object {
return fn(mapValues(object, (_: unknown, key) => {
if (isPlainObject(_)) {
return mapDeep(_ as object, fn, key)
} else if (Array.isArray(_)) {
return _.map(item => {
if (isPlainObject(item)) {
return mapDeep(item as object, fn, key)
}
return item
})
}
return _
}), key)
export function mapDeep(object: object, fn: (value: object, key?: string) => object, key?: string): object {
return fn(
mapValues(object, (_: unknown, key) => {
if (isPlainObject(_)) {
return mapDeep(_ as object, fn, key)
} else if (Array.isArray(_)) {
return _.map(item => {
if (isPlainObject(item)) {
return mapDeep(item as object, fn, key)
}
return item
})
}
return _
}),
key
)
}

@@ -83,6 +82,3 @@

])
function traverseObjectKeys(
obj: Record<string, JSONSchema>,
callback: (schema: JSONSchema) => void
) {
function traverseObjectKeys(obj: Record<string, JSONSchema>, callback: (schema: JSONSchema) => void) {
Object.keys(obj).forEach(k => {

@@ -140,8 +136,10 @@ if (obj[k] && typeof obj[k] === 'object' && !Array.isArray(obj[k])) {

// technically you can put definitions on any key
Object.keys(schema).filter(key => !BLACKLISTED_KEYS.has(key)).forEach(key => {
const child = schema[key]
if (child && typeof child === 'object') {
traverseObjectKeys(child, callback)
}
})
Object.keys(schema)
.filter(key => !BLACKLISTED_KEYS.has(key))
.forEach(key => {
const child = schema[key]
if (child && typeof child === 'object') {
traverseObjectKeys(child, callback)
}
})
}

@@ -175,14 +173,15 @@

deburr(string)
// replace chars which are not valid for typescript identifiers with whitespace
.replace(/(^\s*[^a-zA-Z_$])|([^a-zA-Z_$\d])/g, ' ')
// uppercase leading underscores followed by lowercase
.replace(/^_[a-z]/g, match => match.toUpperCase())
// remove non-leading underscores followed by lowercase (convert snake_case)
.replace(/_[a-z]/g, match => match.substr(1, match.length).toUpperCase())
// uppercase letters after digits, dollars
.replace(/([\d$]+[a-zA-Z])/g, match => match.toUpperCase())
// uppercase first letter after whitespace
.replace(/\s+([a-zA-Z])/g, match => trim(match.toUpperCase()))
// remove remaining whitespace
.replace(/\s/g, ''))
// replace chars which are not valid for typescript identifiers with whitespace
.replace(/(^\s*[^a-zA-Z_$])|([^a-zA-Z_$\d])/g, ' ')
// uppercase leading underscores followed by lowercase
.replace(/^_[a-z]/g, match => match.toUpperCase())
// remove non-leading underscores followed by lowercase (convert snake_case)
.replace(/_[a-z]/g, match => match.substr(1, match.length).toUpperCase())
// uppercase letters after digits, dollars
.replace(/([\d$]+[a-zA-Z])/g, match => match.toUpperCase())
// uppercase first letter after whitespace
.replace(/\s+([a-zA-Z])/g, match => trim(match.toUpperCase()))
// remove remaining whitespace
.replace(/\s/g, '')
)
}

@@ -189,0 +188,0 @@

@@ -1,3 +0,3 @@

import { JSONSchema } from './types/JSONSchema'
import { mapDeep } from './utils'
import {JSONSchema} from './types/JSONSchema'
import {mapDeep} from './utils'

@@ -4,0 +4,0 @@ type Rule = (schema: JSONSchema) => boolean | void

Sorry, the diff of this file is too big to display

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