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 6.1.3 to 7.0.0

5

CHANGELOG.md
# Changelog
## 7.0.0
- b9c4bcb Add support for `additionalItems` for tuple types
- c5f4f03 Add support for `minItems` and `maxItems`
## 6.1.0

@@ -4,0 +9,0 @@

2

dist/src/cli.js

@@ -39,2 +39,3 @@ #!/usr/bin/env node

Object.defineProperty(exports, "__esModule", { value: true });
var cli_color_1 = require("cli-color");
var minimist = require("minimist");

@@ -45,3 +46,2 @@ var fs_1 = require("mz/fs");

var index_1 = require("./index");
var cli_color_1 = require("cli-color");
main(minimist(process.argv.slice(2), {

@@ -48,0 +48,0 @@ alias: {

@@ -35,3 +35,7 @@ "use strict";

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

@@ -70,2 +74,5 @@ type = getSuperTypesAndParams(ast).reduce(function (prev, ast) {

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

@@ -105,3 +112,4 @@ default:

AST_1.hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined,
ast.params.map(function (ast) { return declareNamedTypes(ast, options, rootASTName, processed); }).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');

@@ -136,5 +144,69 @@ break;

case 'STRING': return 'string';
case 'TUPLE': return '['
+ ast.params.map(function (_) { return generateType(_, options); }).join(', ')
+ ']';
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);
}
}
function addSpreadParam(params) {
if (spreadParam) {
var spread = '...(' + generateType(spreadParam, options) + ')[]';
params.push(spread);
}
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));
}
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('|');
}
// no max items so only need to return one type
return paramsToString(addSpreadParam(paramsList));
})();
case 'UNION': return generateSetOperation(ast, options);

@@ -141,0 +213,0 @@ case 'CUSTOM_TYPE': return ast.params;

import { JSONSchema4 } from 'json-schema';
import { Options as $RefOptions } from 'json-schema-ref-parser';
import { Options as PrettierOptions } from 'prettier';
import { Options as $RefOptions } from 'json-schema-ref-parser';
export { EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema } from './types/JSONSchema';

@@ -5,0 +5,0 @@ export interface Options {

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

exports.DEFAULT_OPTIONS = {
$refOptions: {},
bannerComment: "/* tslint:disable */\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n* and run json-schema-to-typescript to regenerate this file.\n*/",

@@ -88,4 +89,3 @@ cwd: process.cwd(),

},
unreachableDefinitions: false,
$refOptions: {}
unreachableDefinitions: false
};

@@ -92,0 +92,0 @@ function compileFromFile(filename, options) {

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var cli_color_1 = require("cli-color");
var stringify = require("json-stringify-safe");
var lodash_1 = require("lodash");
var utils_1 = require("./utils");
var stringify = require("json-stringify-safe");
var rules = new Map();
function hasType(schema, type) {
return schema.type === type || (Array.isArray(schema.type) && schema.type.includes(type));
}
function isObjectType(schema) {
return schema.properties !== undefined || hasType(schema, 'object') || hasType(schema, 'any');
}
function isArrayType(schema) {
return schema.items !== undefined || hasType(schema, 'array') || hasType(schema, 'any');
}
rules.set('Destructure unary types', function (schema) {

@@ -12,22 +21,20 @@ if (schema.type && Array.isArray(schema.type) && schema.type.length === 1) {

}
return schema;
});
rules.set('Add empty `required` property if none is defined', function (schema, rootSchema) {
if (stringify(schema) === stringify(rootSchema) && !('required' in schema)) {
rules.set('Add empty `required` property if none is defined', function (schema) {
if (!('required' in schema) && isObjectType(schema)) {
schema.required = [];
}
return schema;
});
rules.set('Transform `required`=false to `required`=[]', function (schema, rootSchema) {
if (stringify(schema) === stringify(rootSchema) && schema.required === false) {
rules.set('Transform `required`=false to `required`=[]', function (schema) {
if (schema.required === false) {
schema.required = [];
}
return schema;
});
// TODO: default to empty schema (as per spec) instead
rules.set('Default additionalProperties to true', function (schema, rootSchema) {
if (stringify(schema) === stringify(rootSchema) && !('additionalProperties' in schema)) {
rules.set('Default additionalProperties to true', function (schema) {
if (!('additionalProperties' in schema) &&
isObjectType(schema) &&
schema.patternProperties === undefined) {
schema.additionalProperties = true;
}
return schema;
});

@@ -38,6 +45,33 @@ rules.set('Default top level `id`', function (schema, rootSchema, fileName) {

}
return schema;
});
rules.set('Escape closing JSDoc Comment', function (schema) {
utils_1.escapeBlockComment(schema);
});
rules.set('Normalise schema.minItems', function (schema) {
// make sure we only add the props onto array types
if (isArrayType(schema)) {
var minItems = schema.minItems;
schema.minItems = typeof minItems === 'number' ? minItems : 0;
}
// cannot normalise maxItems because maxItems = 0 has an actual meaning
});
rules.set('Normalize schema.items', function (schema) {
var maxItems = schema.maxItems, minItems = schema.minItems;
var hasMaxItems = typeof maxItems === 'number' && maxItems >= 0;
var hasMinItems = typeof minItems === 'number' && minItems > 0;
if (schema.items && !Array.isArray(schema.items) && (hasMaxItems || hasMinItems)) {
var items = schema.items;
// create a tuple of length N
var newItems = Array(maxItems || minItems || 0).fill(items);
if (!hasMaxItems) {
// if there is no maximum, then add a spread item to collect the rest
schema.additionalItems = items;
}
schema.items = newItems;
}
if (Array.isArray(schema.items) && hasMaxItems && maxItems < schema.items.length) {
// it's perfectly valid to provide 5 item defs but require maxItems 1
// obviously we shouldn't emit a type for items that aren't expected
schema.items = schema.items.slice(0, maxItems);
}
return schema;

@@ -48,3 +82,3 @@ });

rules.forEach(function (rule, key) {
_schema = utils_1.mapDeep(_schema, function (schema) { return rule(schema, _schema, filename); });
utils_1.traverse(_schema, function (schema) { return rule(schema, _schema, filename); });
utils_1.log(cli_color_1.whiteBright.bgYellow('normalizer'), "Applied rule: \"" + key + "\"");

@@ -51,0 +85,0 @@ });

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var cli_color_1 = require("cli-color");
var stringify = require("json-stringify-safe");
var lodash_1 = require("lodash");
var AST_1 = require("./types/AST");
var utils_1 = require("./utils");
var stringify = require("json-stringify-safe");
function optimize(ast, processed) {

@@ -9,0 +9,0 @@ if (processed === void 0) { processed = new Map(); }

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

var lodash_1 = require("lodash");
var util_1 = require("util");
var typeOfSchema_1 = require("./typeOfSchema");
var AST_1 = require("./types/AST");
var utils_1 = require("./utils");
var util_1 = require("util");
function parse(schema, options, rootSchema, keyName, isSchema, processed, usedNames) {

@@ -145,15 +145,30 @@ if (rootSchema === void 0) { rootSchema = schema; }

if (Array.isArray(schema.items)) {
return set({
// normalised to not be undefined
var minItems_1 = schema.minItems;
var maxItems_1 = schema.maxItems;
var arrayType = {
comment: schema.description,
keyName: keyName,
maxItems: maxItems_1,
minItems: minItems_1,
params: schema.items.map(function (_) { return parse(_, options, rootSchema, undefined, true, processed, usedNames); }),
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),
type: 'TUPLE'
});
};
if (schema.additionalItems === true) {
arrayType.spreadParam = {
type: 'ANY'
};
}
else if (schema.additionalItems) {
arrayType.spreadParam = parse(schema.additionalItems, options, rootSchema, undefined, true, processed, usedNames);
}
return set(arrayType);
}
else {
var params_1 = parse(schema.items, options, rootSchema, undefined, true, processed, usedNames);
return set({
comment: schema.description,
keyName: keyName,
params: parse(schema.items, options, rootSchema, undefined, true, processed, usedNames),
params: params_1,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),

@@ -182,6 +197,24 @@ type: 'ARRAY'

case 'UNTYPED_ARRAY':
// normalised to not be undefined
var minItems = schema.minItems;
var maxItems = typeof schema.maxItems === 'number' ? schema.maxItems : -1;
var params = AST_1.T_ANY;
if (minItems > 0 || maxItems >= 0) {
return set({
comment: schema.description,
keyName: keyName,
maxItems: schema.maxItems,
minItems: minItems,
// create a tuple of length N
params: Array(Math.max(maxItems, minItems) || 0).fill(params),
// if there is no maximum, then add a spread item to collect the rest
spreadParam: maxItems >= 0 ? undefined : params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),
type: 'TUPLE'
});
}
return set({
comment: schema.description,
keyName: keyName,
params: AST_1.T_ANY,
params: params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),

@@ -188,0 +221,0 @@ type: 'ARRAY'

@@ -38,4 +38,4 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
var cli_color_1 = require("cli-color");
var $RefParser = require("json-schema-ref-parser");
var cli_color_1 = require("cli-color");
var utils_1 = require("./utils");

@@ -42,0 +42,0 @@ function dereference(schema, _a) {

@@ -85,2 +85,5 @@ import { JSONSchema4Type } from 'json-schema';

params: AST[];
spreadParam?: AST;
minItems: number;
maxItems?: number;
}

@@ -87,0 +90,0 @@ export interface TUnion extends AbstractAST {

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

import { JSONSchema4 } from 'json-schema';
import { JSONSchema4, JSONSchema4TypeName } from 'json-schema';
export declare type SCHEMA_TYPE = 'ALL_OF' | 'UNNAMED_SCHEMA' | 'ANY' | 'ANY_OF' | 'BOOLEAN' | 'NAMED_ENUM' | 'NAMED_SCHEMA' | 'NULL' | 'NUMBER' | 'STRING' | 'OBJECT' | 'ONE_OF' | 'TYPED_ARRAY' | 'REFERENCE' | 'UNION' | 'UNNAMED_ENUM' | 'UNTYPED_ARRAY' | 'CUSTOM_TYPE';
export declare type JSONSchemaTypeName = JSONSchema4TypeName;
export interface JSONSchema extends JSONSchema4 {

@@ -4,0 +5,0 @@ /**

@@ -10,2 +10,3 @@ import { JSONSchema } from './types/JSONSchema';

export declare function mapDeep(object: object, fn: (value: object, key?: string) => object, key?: string): object;
export declare function traverse(schema: JSONSchema, callback: (schema: JSONSchema) => void): void;
/**

@@ -12,0 +13,0 @@ * Eg. `foo/bar/baz.json` => `baz`

@@ -31,6 +31,112 @@ "use strict";

return fn(lodash_1.mapValues(object, function (_, key) {
return lodash_1.isPlainObject(_) ? mapDeep(_, fn, key) : _;
if (lodash_1.isPlainObject(_)) {
return mapDeep(_, fn, key);
}
else if (Array.isArray(_)) {
return _.map(function (item) {
if (lodash_1.isPlainObject(item)) {
return mapDeep(item, fn, key);
}
return item;
});
}
return _;
}), key);
}
exports.mapDeep = mapDeep;
// keys that shouldn't be traversed by the catchall step
var BLACKLISTED_KEYS = new Set([
'id',
'$schema',
'title',
'description',
'default',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'items',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'additionalProperties',
'definitions',
'properties',
'patternProperties',
'dependencies',
'enum',
'type',
'allOf',
'anyOf',
'oneOf',
'not'
]);
function traverseObjectKeys(obj, callback) {
Object.keys(obj).forEach(function (k) {
if (obj[k] && typeof obj[k] === 'object' && !Array.isArray(obj[k])) {
traverse(obj[k], callback);
}
});
}
function traverseArray(arr, callback) {
arr.forEach(function (i) { return traverse(i, callback); });
}
function traverse(schema, callback) {
callback(schema);
if (schema.anyOf) {
traverseArray(schema.anyOf, callback);
}
if (schema.allOf) {
traverseArray(schema.allOf, callback);
}
if (schema.oneOf) {
traverseArray(schema.oneOf, callback);
}
if (schema.properties) {
traverseObjectKeys(schema.properties, callback);
}
if (schema.patternProperties) {
traverseObjectKeys(schema.patternProperties, callback);
}
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
traverse(schema.additionalProperties, callback);
}
if (schema.items) {
var items = schema.items;
if (Array.isArray(items)) {
traverseArray(items, callback);
}
else {
traverse(items, callback);
}
}
if (schema.additionalItems && typeof schema.additionalItems === 'object') {
traverse(schema.additionalItems, callback);
}
if (schema.dependencies) {
traverseObjectKeys(schema.dependencies, callback);
}
if (schema.definitions) {
traverseObjectKeys(schema.definitions, callback);
}
if (schema.not) {
traverse(schema.not, callback);
}
// technically you can put definitions on any key
Object.keys(schema).filter(function (key) { return !BLACKLISTED_KEYS.has(key); }).forEach(function (key) {
var child = schema[key];
if (child && typeof child === 'object') {
traverseObjectKeys(child, callback);
}
});
}
exports.traverse = traverse;
/**

@@ -37,0 +143,0 @@ * Eg. `foo/bar/baz.json` => `baz`

@@ -15,2 +15,20 @@ "use strict";

});
rules.set('When both maxItems and minItems are present, maxItems >= minItems', function (schema) {
var maxItems = schema.maxItems, minItems = schema.minItems;
if (typeof maxItems === 'number' && typeof minItems === 'number') {
return maxItems >= minItems;
}
});
rules.set('When maxItems exists, maxItems >= 0', function (schema) {
var maxItems = schema.maxItems;
if (typeof maxItems === 'number') {
return maxItems >= 0;
}
});
rules.set('When minItems exists, minItems >= 0', function (schema) {
var minItems = schema.minItems;
if (typeof minItems === 'number') {
return minItems >= 0;
}
});
function validate(schema, filename) {

@@ -17,0 +35,0 @@ var errors = [];

{
"name": "json-schema-to-typescript",
"version": "6.1.3",
"version": "7.0.0",
"description": "compile json schema to typescript typings",

@@ -48,27 +48,27 @@ "main": "dist/src/index.js",

"dependencies": {
"@types/cli-color": "^0.3.29",
"@types/json-schema": "^7.0.3",
"@types/lodash": "^4.14.121",
"@types/minimist": "^1.2.0",
"@types/mz": "0.0.32",
"@types/node": "^11.10.4",
"@types/prettier": "^1.16.1",
"@types/node": ">=4.5.0",
"@types/prettier": "^1.18.0",
"cli-color": "^1.4.0",
"json-schema-ref-parser": "^6.1.0",
"json-schema-ref-parser": "^7.1.0",
"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.11",
"lodash": "^4.17.15",
"minimist": "^1.2.0",
"mz": "^2.7.0",
"prettier": "^1.16.4",
"prettier": "^1.18.2",
"stdin": "0.0.1"
},
"devDependencies": {
"ava": "^1.2.1",
"browserify": "^16.2.3",
"@types/cli-color": "^0.3.29",
"@types/lodash": "^4.14.136",
"@types/minimist": "^1.2.0",
"@types/mz": "0.0.32",
"ava": "^2.2.0",
"browserify": "^16.3.0",
"browserify-shim": "^3.8.14",
"concurrently": "^4.1.0",
"concurrently": "^4.1.1",
"shx": "^0.3.2",
"tsify": "^4.0.1",
"tslint": "^5.13.1",
"typescript": "^3.3.3333"
"tslint": "^5.18.0",
"typescript": "^3.5.3"
},

@@ -75,0 +75,0 @@ "ava": {

@@ -149,2 +149,4 @@ # json-schema-to-typescript [![Build Status][build]](https://circleci.com/gh/bcherny/json-schema-to-typescript) [![npm]](https://www.npmjs.com/package/json-schema-to-typescript) [![mit]](https://opensource.org/licenses/MIT)

- [x] `oneOf` (treated like `anyOf`)
- [x] `maxItems` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L166))
- [x] `minItems` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L165))
- [x] `additionalProperties` of type

@@ -167,4 +169,2 @@ - [x] `patternProperties` (partial support)

- `minimum` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L182))
- `maxItems` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L166))
- `minItems` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L165))
- `maxProperties` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L113))

@@ -171,0 +171,0 @@ - `minProperties` ([eg](https://github.com/tdegrunt/jsonschema/blob/67c0e27ce9542efde0bf43dc1b2a95dd87df43c3/examples/all.js#L112))

#!/usr/bin/env node
import { whiteBright } from 'cli-color'
import { JSONSchema4 } from 'json-schema'

@@ -9,3 +10,2 @@ import minimist = require('minimist')

import { compile, Options } from './index'
import { whiteBright } from 'cli-color'

@@ -12,0 +12,0 @@ main(minimist(process.argv.slice(2), {

@@ -5,3 +5,3 @@ import { whiteBright } from 'cli-color'

import {
AST, ASTWithStandaloneName, hasComment, hasStandaloneName, TArray, TEnum, TInterface, TIntersection,
AST, ASTWithStandaloneName, hasComment, hasStandaloneName, T_ANY, TArray, TEnum, TInterface, TIntersection,
TNamedInterface, TUnion

@@ -43,3 +43,7 @@ } from './types/AST'

case 'TUPLE':
return ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
type = ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
if (ast.spreadParam) {
type += declareEnums(ast.spreadParam, options, processed)
}
break
case 'INTERFACE':

@@ -87,2 +91,5 @@ type = getSuperTypesAndParams(ast).reduce((prev, ast) =>

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

@@ -130,3 +137,4 @@ default:

hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined,
ast.params.map(ast => declareNamedTypes(ast, options, rootASTName, processed)).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')

@@ -153,3 +161,3 @@ break

case 'ARRAY': return (() => {
let type = generateType(ast.params, options)
const type = generateType(ast.params, options)
return type.endsWith('"') ? '(' + type + ')[]' : type + '[]'

@@ -166,5 +174,80 @@ })()

case 'STRING': return 'string'
case 'TUPLE': return '['
+ ast.params.map(_ => generateType(_, options)).join(', ')
+ ']'
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
}
}
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)
}
return params
}
function paramsToString(params: string[]): string {
return '[' + params.join(', ') + ']'
}
const paramsList = astParams
.map(param => 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
*/
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([]))
}
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)
}
typesToUnion.push(paramsToString(cumulativeParamsList))
}
return typesToUnion.join('|')
}
// no max items so only need to return one type
return paramsToString(addSpreadParam(paramsList))
})()
case 'UNION': return generateSetOperation(ast, options)

@@ -171,0 +254,0 @@ case 'CUSTOM_TYPE': return ast.params

import { readFileSync } from 'fs'
import { JSONSchema4 } from 'json-schema'
import { Options as $RefOptions } from 'json-schema-ref-parser'
import { endsWith, merge } from 'lodash'

@@ -14,3 +15,2 @@ import { dirname } from 'path'

import { validate } from './validator'
import { Options as $RefOptions } from 'json-schema-ref-parser'

@@ -51,2 +51,3 @@ export { EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema } from './types/JSONSchema'

export const DEFAULT_OPTIONS: Options = {
$refOptions: {},
bannerComment: `/* tslint:disable */

@@ -70,4 +71,3 @@ /**

},
unreachableDefinitions: false,
$refOptions: {}
unreachableDefinitions: false
}

@@ -74,0 +74,0 @@

import { whiteBright } from 'cli-color'
import stringify = require('json-stringify-safe')
import { cloneDeep } from 'lodash'
import { JSONSchema, NormalizedJSONSchema } from './types/JSONSchema'
import { justName, log, mapDeep, toSafeString, escapeBlockComment } from './utils'
import stringify = require('json-stringify-safe')
import { JSONSchema, JSONSchemaTypeName, NormalizedJSONSchema } from './types/JSONSchema'
import { escapeBlockComment, justName, log, toSafeString, traverse } from './utils'
type Rule = (schema: JSONSchema, rootSchema: JSONSchema, fileName?: string) => JSONSchema
type Rule = (schema: JSONSchema, rootSchema: JSONSchema, fileName?: string) => void
const rules = new Map<string, Rule>()
function hasType(schema: JSONSchema, type: JSONSchemaTypeName) {
return schema.type === type || (Array.isArray(schema.type) && schema.type.includes(type))
}
function isObjectType(schema: JSONSchema) {
return schema.properties !== undefined || hasType(schema, 'object') || hasType(schema, 'any')
}
function isArrayType(schema: JSONSchema) {
return schema.items !== undefined || hasType(schema, 'array') || hasType(schema, 'any')
}
rules.set('Destructure unary types', schema => {

@@ -14,25 +24,23 @@ if (schema.type && Array.isArray(schema.type) && schema.type.length === 1) {

}
return schema
})
rules.set('Add empty `required` property if none is defined', (schema, rootSchema) => {
if (stringify(schema) === stringify(rootSchema) && !('required' in schema)) {
rules.set('Add empty `required` property if none is defined', (schema) => {
if (!('required' in schema) && isObjectType(schema)) {
schema.required = []
}
return schema
})
rules.set('Transform `required`=false to `required`=[]', (schema, rootSchema) => {
if (stringify(schema) === stringify(rootSchema) && schema.required === false) {
rules.set('Transform `required`=false to `required`=[]', (schema) => {
if (schema.required === false) {
schema.required = []
}
return schema
})
// TODO: default to empty schema (as per spec) instead
rules.set('Default additionalProperties to true', (schema, rootSchema) => {
if (stringify(schema) === stringify(rootSchema) && !('additionalProperties' in schema)) {
rules.set('Default additionalProperties to true', (schema) => {
if (!('additionalProperties' in schema) &&
isObjectType(schema) &&
schema.patternProperties === undefined) {
schema.additionalProperties = true
}
return schema
})

@@ -44,3 +52,2 @@

}
return schema
})

@@ -50,2 +57,35 @@

escapeBlockComment(schema)
})
rules.set('Normalise schema.minItems', (schema) => {
// make sure we only add the props onto array types
if (isArrayType(schema)) {
const {minItems} = schema
schema.minItems = typeof minItems === 'number' ? minItems : 0
}
// cannot normalise maxItems because maxItems = 0 has an actual meaning
})
rules.set('Normalize schema.items', schema => {
const {maxItems, minItems} = schema
const hasMaxItems = typeof maxItems === 'number' && maxItems >= 0
const hasMinItems = typeof minItems === 'number' && minItems > 0
if (schema.items && !Array.isArray(schema.items) && (hasMaxItems || hasMinItems)) {
const items = schema.items
// create a tuple of length N
const newItems = Array(maxItems || minItems || 0).fill(items)
if (!hasMaxItems) {
// if there is no maximum, then add a spread item to collect the rest
schema.additionalItems = items
}
schema.items = newItems
}
if (Array.isArray(schema.items) && hasMaxItems && maxItems! < schema.items.length) {
// it's perfectly valid to provide 5 item defs but require maxItems 1
// obviously we shouldn't emit a type for items that aren't expected
schema.items = schema.items.slice(0, maxItems)
}
return schema

@@ -55,5 +95,5 @@ })

export function normalize(schema: JSONSchema, filename?: string): NormalizedJSONSchema {
let _schema = cloneDeep(schema) as NormalizedJSONSchema
const _schema = cloneDeep(schema) as NormalizedJSONSchema
rules.forEach((rule, key) => {
_schema = mapDeep(_schema, schema => rule(schema, _schema, filename)) as NormalizedJSONSchema
traverse(_schema, (schema) => rule(schema, _schema, filename))
log(whiteBright.bgYellow('normalizer'), `Applied rule: "${key}"`)

@@ -60,0 +100,0 @@ })

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 stringify = require('json-stringify-safe')

@@ -7,0 +7,0 @@ export function optimize(ast: AST, processed = new Map<AST, AST>()): AST {

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 } from './types/AST'
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 { format } from 'util'

@@ -164,14 +164,28 @@ export type Processed = Map<JSONSchema | JSONSchema4Type, AST>

if (Array.isArray(schema.items)) {
return set({
// normalised to not be undefined
const minItems = schema.minItems!
const maxItems = schema.maxItems!
const arrayType: TTuple = {
comment: schema.description,
keyName,
maxItems,
minItems,
params: schema.items.map(_ => parse(_, options, rootSchema, undefined, true, processed, usedNames)),
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),
type: 'TUPLE'
})
}
if (schema.additionalItems === true) {
arrayType.spreadParam = {
type: 'ANY'
}
} else if (schema.additionalItems) {
arrayType.spreadParam = parse(schema.additionalItems, options, rootSchema, undefined, true, processed, usedNames)
}
return set(arrayType)
} else {
const params = parse(schema.items!, options, rootSchema, undefined, true, processed, usedNames)
return set({
comment: schema.description,
keyName,
params: parse(schema.items!, options, rootSchema, undefined, true, processed, usedNames),
params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),

@@ -200,6 +214,25 @@ type: 'ARRAY'

case 'UNTYPED_ARRAY':
// normalised to not be undefined
const minItems = schema.minItems!
const maxItems = typeof schema.maxItems === 'number' ? schema.maxItems : -1
const params = T_ANY
if (minItems > 0 || maxItems >= 0) {
return set({
comment: schema.description,
keyName,
maxItems: schema.maxItems,
minItems,
// create a tuple of length N
params: Array(Math.max(maxItems, minItems) || 0).fill(params),
// if there is no maximum, then add a spread item to collect the rest
spreadParam: maxItems >= 0 ? undefined : params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),
type: 'TUPLE'
})
}
return set({
comment: schema.description,
keyName,
params: T_ANY,
params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames),

@@ -298,3 +331,2 @@ type: 'ARRAY'

let singlePatternProperty = false

@@ -301,0 +333,0 @@ if (schema.patternProperties) {

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

import { whiteBright } from 'cli-color'
import $RefParser = require('json-schema-ref-parser')
import { whiteBright } from 'cli-color'
import { JSONSchema } from './types/JSONSchema'

@@ -4,0 +4,0 @@ import { log } from './utils'

@@ -109,2 +109,5 @@ import { JSONSchema4Type } from 'json-schema'

params: AST[]
spreadParam?: AST
minItems: number
maxItems?: number
}

@@ -111,0 +114,0 @@

@@ -1,2 +0,2 @@

import { JSONSchema4 } from 'json-schema'
import { JSONSchema4, JSONSchema4TypeName } from 'json-schema'

@@ -8,2 +8,4 @@ export type SCHEMA_TYPE = 'ALL_OF' | 'UNNAMED_SCHEMA' | 'ANY' | 'ANY_OF'

export type JSONSchemaTypeName = JSONSchema4TypeName
export interface JSONSchema extends JSONSchema4 {

@@ -10,0 +12,0 @@ /**

@@ -31,7 +31,116 @@ import { whiteBright } from 'cli-color'

): object {
return fn(mapValues(object, (_, key) =>
isPlainObject(_) ? mapDeep(_, fn, key) : _
), key)
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)
}
// keys that shouldn't be traversed by the catchall step
const BLACKLISTED_KEYS = new Set([
'id',
'$schema',
'title',
'description',
'default',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'items',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'additionalProperties',
'definitions',
'properties',
'patternProperties',
'dependencies',
'enum',
'type',
'allOf',
'anyOf',
'oneOf',
'not'
])
function traverseObjectKeys(
obj: Record<string, JSONSchema>,
callback: (schema: JSONSchema) => void
) {
Object.keys(obj).forEach(k => {
if (obj[k] && typeof obj[k] === 'object' && !Array.isArray(obj[k])) {
traverse(obj[k], callback)
}
})
}
function traverseArray(arr: JSONSchema[], callback: (schema: JSONSchema) => void) {
arr.forEach(i => traverse(i, callback))
}
export function traverse(schema: JSONSchema, callback: (schema: JSONSchema) => void): void {
callback(schema)
if (schema.anyOf) {
traverseArray(schema.anyOf, callback)
}
if (schema.allOf) {
traverseArray(schema.allOf, callback)
}
if (schema.oneOf) {
traverseArray(schema.oneOf, callback)
}
if (schema.properties) {
traverseObjectKeys(schema.properties, callback)
}
if (schema.patternProperties) {
traverseObjectKeys(schema.patternProperties, callback)
}
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
traverse(schema.additionalProperties, callback)
}
if (schema.items) {
const {items} = schema
if (Array.isArray(items)) {
traverseArray(items, callback)
} else {
traverse(items, callback)
}
}
if (schema.additionalItems && typeof schema.additionalItems === 'object') {
traverse(schema.additionalItems, callback)
}
if (schema.dependencies) {
traverseObjectKeys(schema.dependencies, callback)
}
if (schema.definitions) {
traverseObjectKeys(schema.definitions, callback)
}
if (schema.not) {
traverse(schema.not, callback)
}
// 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)
}
})
}
/**

@@ -38,0 +147,0 @@ * Eg. `foo/bar/baz.json` => `baz`

import { JSONSchema } from './types/JSONSchema'
import { mapDeep } from './utils'
type Rule = (schema: JSONSchema) => false | void
type Rule = (schema: JSONSchema) => boolean | void
const rules = new Map<string, Rule>()

@@ -19,2 +19,23 @@

rules.set('When both maxItems and minItems are present, maxItems >= minItems', schema => {
const {maxItems, minItems} = schema
if (typeof maxItems === 'number' && typeof minItems === 'number') {
return maxItems >= minItems
}
})
rules.set('When maxItems exists, maxItems >= 0', schema => {
const {maxItems} = schema
if (typeof maxItems === 'number') {
return maxItems >= 0
}
})
rules.set('When minItems exists, minItems >= 0', schema => {
const {minItems} = schema
if (typeof minItems === 'number') {
return minItems >= 0
}
})
export function validate(schema: JSONSchema, filename: string): string[] {

@@ -21,0 +42,0 @@ const errors: string[] = []

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