Skip to content

Commit

Permalink
enhancement bcherny#543 -- options
Browse files Browse the repository at this point in the history
Added `usePrefixItems` option, which defaults to false
  • Loading branch information
TheoAnastasiadis committed Jan 25, 2024
1 parent c4d25a0 commit 01470b6
Showing 1 changed file with 191 additions and 185 deletions.
376 changes: 191 additions & 185 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,185 +1,191 @@
import {readFileSync} from 'fs'
import {JSONSchema4} from 'json-schema'
import {Options as $RefOptions} from '@bcherny/json-schema-ref-parser'
import {cloneDeep, 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, log} from './utils'
import {validate} from './validator'
import {isDeepStrictEqual} from 'util'
import {link} from './linker'
import {validateOptions} from './optionValidator'

export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema'

export interface Options {
/**
* [$RefParser](https://github.com/BigstickCarpet/json-schema-ref-parser) Options, used when resolving `$ref`s
*/
$refOptions: $RefOptions
/**
* Default value for additionalProperties, when it is not explicitly set.
*/
additionalProperties: boolean
/**
* Disclaimer comment prepended to the top of each generated file.
*/
bannerComment: string
/**
* Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s.
*/
cwd: string
/**
* Declare external schemas referenced via `$ref`?
*/
declareExternallyReferenced: boolean
/**
* Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)?
*/
enableConstEnums: boolean
/**
* Format code? Set this to `false` to improve performance.
*/
format: boolean
/**
* Ignore maxItems and minItems for `array` types, preventing tuples being generated.
*/
ignoreMinAndMaxItems: boolean
/**
* Maximum number of unioned tuples to emit when representing bounded-size array types,
* before falling back to emitting unbounded arrays. Increase this to improve precision
* of emitted types, decrease it to improve performance, or set it to `-1` to ignore
* `minItems` and `maxItems`.
*/
maxItems: number
/**
* Append all index signatures with `| undefined` so that they are strictly typed.
*
* This is required to be compatible with `strictNullChecks`.
*/
strictIndexSignatures: boolean
/**
* A [Prettier](https://prettier.io/docs/en/options.html) configuration.
*/
style: PrettierOptions
/**
* Generate code for `definitions` that aren't referenced by the schema?
*/
unreachableDefinitions: boolean
/**
* Generate unknown type instead of any
*/
unknownAny: boolean
}

export const DEFAULT_OPTIONS: Options = {
$refOptions: {},
additionalProperties: true, // TODO: default to empty schema (as per spec) instead
bannerComment: `/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/`,
cwd: process.cwd(),
declareExternallyReferenced: true,
enableConstEnums: true,
format: true,
ignoreMinAndMaxItems: false,
maxItems: 20,
strictIndexSignatures: false,
style: {
bracketSpacing: false,
printWidth: 120,
semi: true,
singleQuote: false,
tabWidth: 2,
trailingComma: 'none',
useTabs: false,
},
unreachableDefinitions: false,
unknownAny: true,
}

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}"`)
},
)
const schema = Try<JSONSchema4>(
() => JSON.parse(contents.toString()),
() => {
throw new TypeError(`Error parsing JSON in file "${filename}"`)
},
)
return compile(schema, stripExtension(filename), {cwd: dirname(filename), ...options})
}

export async function compile(schema: JSONSchema4, name: string, options: Partial<Options> = {}): Promise<string> {
validateOptions(options)

const _options = merge({}, DEFAULT_OPTIONS, options)

const start = Date.now()
function time() {
return `(${Date.now() - start}ms)`
}

// normalize options
if (!endsWith(_options.cwd, '/')) {
_options.cwd += '/'
}

// Initial clone to avoid mutating the input
const _schema = cloneDeep(schema)

const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options)
if (process.env.VERBOSE) {
if (isDeepStrictEqual(_schema, dereferencedSchema)) {
log('green', 'dereferencer', time(), '✅ No change')
} else {
log('green', 'dereferencer', time(), '✅ Result:', dereferencedSchema)
}
}

const linked = link(dereferencedSchema)
if (process.env.VERBOSE) {
log('green', 'linker', time(), '✅ No change')
}

const errors = validate(linked, name)
if (errors.length) {
errors.forEach(_ => error(_))
throw new ValidationError()
}
if (process.env.VERBOSE) {
log('green', 'validator', time(), '✅ No change')
}

const normalized = normalize(linked, dereferencedPaths, name, _options)
log('yellow', 'normalizer', time(), '✅ Result:', normalized)

const parsed = parse(normalized, _options)
log('blue', 'parser', time(), '✅ Result:', parsed)

const optimized = optimize(parsed, _options)
log('cyan', 'optimizer', time(), '✅ Result:', optimized)

const generated = generate(optimized, _options)
log('magenta', 'generator', time(), '✅ Result:', generated)

const formatted = format(generated, _options)
log('white', 'formatter', time(), '✅ Result:', formatted)

return formatted
}

export class ValidationError extends Error {}
import {readFileSync} from 'fs'
import {JSONSchema4} from 'json-schema'
import {Options as $RefOptions} from '@bcherny/json-schema-ref-parser'
import {cloneDeep, 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, log} from './utils'
import {validate} from './validator'
import {isDeepStrictEqual} from 'util'
import {link} from './linker'
import {validateOptions} from './optionValidator'

export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema'

export interface Options {
/**
* [$RefParser](https://github.com/BigstickCarpet/json-schema-ref-parser) Options, used when resolving `$ref`s
*/
$refOptions: $RefOptions
/**
* Default value for additionalProperties, when it is not explicitly set.
*/
additionalProperties: boolean
/**
* Disclaimer comment prepended to the top of each generated file.
*/
bannerComment: string
/**
* Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s.
*/
cwd: string
/**
* Declare external schemas referenced via `$ref`?
*/
declareExternallyReferenced: boolean
/**
* Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)?
*/
enableConstEnums: boolean
/**
* Format code? Set this to `false` to improve performance.
*/
format: boolean
/**
* Ignore maxItems and minItems for `array` types, preventing tuples being generated.
*/
ignoreMinAndMaxItems: boolean
/**
* Maximum number of unioned tuples to emit when representing bounded-size array types,
* before falling back to emitting unbounded arrays. Increase this to improve precision
* of emitted types, decrease it to improve performance, or set it to `-1` to ignore
* `minItems` and `maxItems`.
*/
maxItems: number
/**
* Use `prefixItems` and `items` instead of `items` and `additionalItems` for tuple validation. This change was introduced in the 2020-12 Release Notes of the
* JSON Schema specification. [Read more about this](https://json-schema.org/draft/2020-12/release-notes#2020-12-release-notes)
*/
usePrefixItems: boolean
/**
* Append all index signatures with `| undefined` so that they are strictly typed.
*
* This is required to be compatible with `strictNullChecks`.
*/
strictIndexSignatures: boolean
/**
* A [Prettier](https://prettier.io/docs/en/options.html) configuration.
*/
style: PrettierOptions
/**
* Generate code for `definitions` that aren't referenced by the schema?
*/
unreachableDefinitions: boolean
/**
* Generate unknown type instead of any
*/
unknownAny: boolean
}

export const DEFAULT_OPTIONS: Options = {
$refOptions: {},
additionalProperties: true, // TODO: default to empty schema (as per spec) instead
bannerComment: `/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/`,
cwd: process.cwd(),
declareExternallyReferenced: true,
enableConstEnums: true,
format: true,
ignoreMinAndMaxItems: false,
maxItems: 20,
usePrefixItems: false,
strictIndexSignatures: false,
style: {
bracketSpacing: false,
printWidth: 120,
semi: true,
singleQuote: false,
tabWidth: 2,
trailingComma: 'none',
useTabs: false,
},
unreachableDefinitions: false,
unknownAny: true,
}

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}"`)
},
)
const schema = Try<JSONSchema4>(
() => JSON.parse(contents.toString()),
() => {
throw new TypeError(`Error parsing JSON in file "${filename}"`)
},
)
return compile(schema, stripExtension(filename), {cwd: dirname(filename), ...options})
}

export async function compile(schema: JSONSchema4, name: string, options: Partial<Options> = {}): Promise<string> {
validateOptions(options)

const _options = merge({}, DEFAULT_OPTIONS, options)

const start = Date.now()
function time() {
return `(${Date.now() - start}ms)`
}

// normalize options
if (!endsWith(_options.cwd, '/')) {
_options.cwd += '/'
}

// Initial clone to avoid mutating the input
const _schema = cloneDeep(schema)

const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options)
if (process.env.VERBOSE) {
if (isDeepStrictEqual(_schema, dereferencedSchema)) {
log('green', 'dereferencer', time(), '✅ No change')
} else {
log('green', 'dereferencer', time(), '✅ Result:', dereferencedSchema)
}
}

const linked = link(dereferencedSchema)
if (process.env.VERBOSE) {
log('green', 'linker', time(), '✅ No change')
}

const errors = validate(linked, name)
if (errors.length) {
errors.forEach(_ => error(_))
throw new ValidationError()
}
if (process.env.VERBOSE) {
log('green', 'validator', time(), '✅ No change')
}

const normalized = normalize(linked, dereferencedPaths, name, _options)
log('yellow', 'normalizer', time(), '✅ Result:', normalized)

const parsed = parse(normalized, _options)
log('blue', 'parser', time(), '✅ Result:', parsed)

const optimized = optimize(parsed, _options)
log('cyan', 'optimizer', time(), '✅ Result:', optimized)

const generated = generate(optimized, _options)
log('magenta', 'generator', time(), '✅ Result:', generated)

const formatted = format(generated, _options)
log('white', 'formatter', time(), '✅ Result:', formatted)

return formatted
}

export class ValidationError extends Error {}

0 comments on commit 01470b6

Please sign in to comment.