diff --git a/src/cli.ts b/src/cli.ts index 456f1447..0922834e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import minimist from 'minimist' +import {cloneDeep} from 'lodash' import {readFileSync, writeFileSync, existsSync, lstatSync, readdirSync, mkdirSync} from 'fs' import {glob, isDynamicPattern} from 'tinyglobby' import {join, resolve, dirname} from 'path' @@ -23,6 +24,7 @@ main( 'strictIndexSignatures', 'unknownAny', 'unreachableDefinitions', + 'useTypeImports', ], default: DEFAULT_OPTIONS, string: ['bannerComment', 'cwd'], @@ -101,7 +103,11 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti return [file, await processFile(file, argv)] as const } else { const outputPath = pathTransform(argOut, argIn, file) - return [file, await processFile(file, argv), outputPath] as const + // get argv with cwd corrected. We want to resolve relevant to dir. + const opts = cloneDeep(argv) + const dirPath = resolve(dirname(file)) + opts.cwd = resolve(dirPath) + return [file, await processFile(file, opts), outputPath] as const } }), ) @@ -179,6 +185,9 @@ Boolean values can be set to false using the 'no-' prefix. Root directory for resolving $ref --declareExternallyReferenced Declare external schemas referenced via '$ref'? + --useTypeImports + Access external schemas referenced via '$ref' using 'import type'? This + requires --declareExternallyReferenced=false. --enableConstEnums Prepend enums with 'const'? --inferStringEnumKeysFromValues diff --git a/src/generator.ts b/src/generator.ts index dcee32ff..0ff4f8b6 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -15,6 +15,7 @@ import { T_UNKNOWN, } from './types/AST' import {log, toSafeString} from './utils' +import {basename, dirname, extname, join} from 'path' export function generate(ast: AST, options = DEFAULT_OPTIONS): string { return ( @@ -349,14 +350,36 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { } function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { - return ( - (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + + const lines: string[] = [] + + hasComment(ast) ? lines.push(generateComment(ast.comment, ast.deprecated) + '\n') : lines.push('') + + if (options.useTypeImports) { + for (const param of ast.params) { + if (param.ast.standaloneName && param.referencePath) { + const dir = dirname(param.referencePath) + const nameWithoutExt = basename(param.referencePath, extname(param.referencePath)) + // If we needed to rename due to name clashes, import with the original + // name. + const importInBrackets = + param.ast.originalName && param.ast.originalName !== param.ast.standaloneName + ? `${param.ast.originalName} as ${param.ast.standaloneName}` + : param.ast.standaloneName + const importPath = './' + join(dir, `${nameWithoutExt}.ts`) + lines.push(`import type { ${importInBrackets} } from '${importPath}';`) + } + } + } + + lines.push( `export interface ${toSafeString(ast.standaloneName)} ` + - (ast.superTypes.length > 0 - ? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} ` - : '') + - generateInterface(ast, options) + (ast.superTypes.length > 0 + ? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} ` + : '') + + generateInterface(ast, options), ) + + return lines.join('') } function generateStandaloneType(ast: ASTWithStandaloneName, options: Options): string { diff --git a/src/index.ts b/src/index.ts index 1aa67be0..7f66055a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,10 @@ export interface Options { * Declare external schemas referenced via `$ref`? */ declareExternallyReferenced: boolean + /** + * Use `import type` instead of redeclaring. + */ + useTypeImports: boolean /** * Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? */ @@ -98,6 +102,7 @@ export const DEFAULT_OPTIONS: Options = { */`, cwd: process.cwd(), declareExternallyReferenced: true, + useTypeImports: false, enableConstEnums: true, inferStringEnumKeysFromValues: false, format: true, @@ -135,7 +140,10 @@ function parseAsJSONSchema(filename: string): JSONSchema4 { export async function compile(schema: JSONSchema4, name: string, options: Partial = {}): Promise { validateOptions(options) - const _options = merge({}, DEFAULT_OPTIONS, options) + // useTypeImports implies declareExternallyReferenced = false + const optionOverrides = options.useTypeImports ? {declareExternallyReferenced: false} : {} + + const _options = merge({}, DEFAULT_OPTIONS, options, optionOverrides) const start = Date.now() function time() { @@ -151,6 +159,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia 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') @@ -176,7 +185,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia const normalized = normalize(linked, dereferencedPaths, name, _options) log('yellow', 'normalizer', time(), '✅ Result:', normalized) - const parsed = parse(normalized, _options) + const parsed = parse(normalized, _options, dereferencedPaths) log('blue', 'parser', time(), '✅ Result:', parsed) const optimized = optimize(parsed, _options) diff --git a/src/normalizer.ts b/src/normalizer.ts index 2d0d534d..c2588a8a 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -99,10 +99,6 @@ rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _ if (!schema.$id && !schema.title && dereferencedName) { schema.$id = toSafeString(justName(dereferencedName)) } - - if (dereferencedName) { - dereferencedPaths.delete(schema) - } }) rules.set('Escape closing JSDoc comment', schema => { diff --git a/src/parser.ts b/src/parser.ts index 92acdeb3..4c24353e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -14,7 +14,8 @@ import type { SchemaType, } from './types/JSONSchema' import {Intersection, Types, getRootSchema, isBoolean, isPrimitive} from './types/JSONSchema' -import {generateName, log, maybeStripDefault} from './utils' +import {generateName, generateNameNonUnique, log, maybeStripDefault} from './utils' +import {DereferencedPaths} from './resolver' export type Processed = Map> @@ -23,6 +24,7 @@ export type UsedNames = Set export function parse( schema: NormalizedJSONSchema | JSONSchema4Type, options: Options, + dereferencedPaths: DereferencedPaths, keyName?: string, processed: Processed = new Map(), usedNames = new Set(), @@ -39,10 +41,18 @@ export function parse( const types = schema[Types] if (intersection) { - const ast = parseAsTypeWithCache(intersection, 'ALL_OF', options, keyName, processed, usedNames) as TIntersection + const ast = parseAsTypeWithCache( + intersection, + 'ALL_OF', + options, + dereferencedPaths, + keyName, + processed, + usedNames, + ) as TIntersection types.forEach(type => { - ast.params.push(parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames)) + ast.params.push(parseAsTypeWithCache(schema, type, options, dereferencedPaths, keyName, processed, usedNames)) }) log('blue', 'parser', 'Types:', [...types], 'Input:', schema, 'Output:', ast) @@ -51,7 +61,7 @@ export function parse( if (types.size === 1) { const type = [...types][0] - const ast = parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames) + const ast = parseAsTypeWithCache(schema, type, options, dereferencedPaths, keyName, processed, usedNames) log('blue', 'parser', 'Type:', type, 'Input:', schema, 'Output:', ast) return ast } @@ -63,6 +73,7 @@ function parseAsTypeWithCache( schema: NormalizedJSONSchema, type: SchemaType, options: Options, + dereferencedPaths: DereferencedPaths, keyName?: string, processed: Processed = new Map(), usedNames = new Set(), @@ -86,7 +97,7 @@ function parseAsTypeWithCache( // Update the AST in place. This updates the `processed` cache, as well // as any nodes that directly reference the node. - return Object.assign(ast, parseNonLiteral(schema, type, options, keyName, processed, usedNames)) + return Object.assign(ast, parseNonLiteral(schema, type, options, keyName, processed, usedNames, dereferencedPaths)) } function parseBooleanSchema(schema: boolean, keyName: string | undefined, options: Options): AST { @@ -118,6 +129,7 @@ function parseNonLiteral( keyName: string | undefined, processed: Processed, usedNames: UsedNames, + dereferencedPaths: DereferencedPaths, ): AST { const definitions = getDefinitionsMemoized(getRootSchema(schema as any)) // TODO const keyNameFromDefinition = findKey(definitions, _ => _ === schema) @@ -128,8 +140,9 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), - params: schema.allOf!.map(_ => parse(_, options, undefined, processed, usedNames)), + params: schema.allOf!.map(_ => parse(_, options, dereferencedPaths, undefined, processed, usedNames)), type: 'INTERSECTION', } case 'ANY': @@ -138,6 +151,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), } case 'ANY_OF': @@ -145,8 +159,9 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), - params: schema.anyOf!.map(_ => parse(_, options, undefined, processed, usedNames)), + params: schema.anyOf!.map(_ => parse(_, options, dereferencedPaths, undefined, processed, usedNames)), type: 'UNION', } case 'BOOLEAN': @@ -154,6 +169,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'BOOLEAN', } @@ -163,6 +179,7 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, params: schema.tsType!, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'CUSTOM_TYPE', } @@ -171,6 +188,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options)!, standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames, options)!, params: (schema as EnumJSONSchema).enum!.map((_, n) => ({ ast: parseLiteral(_, undefined), @@ -179,12 +197,13 @@ function parseNonLiteral( type: 'ENUM', } case 'NAMED_SCHEMA': - return newInterface(schema as SchemaSchema, options, processed, usedNames, keyName) + return newInterface(schema as SchemaSchema, options, processed, usedNames, dereferencedPaths, keyName) case 'NEVER': return { comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NEVER', } @@ -193,6 +212,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NULL', } @@ -201,6 +221,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NUMBER', } @@ -208,6 +229,7 @@ function parseNonLiteral( return { comment: schema.description, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'OBJECT', deprecated: schema.deprecated, @@ -217,8 +239,9 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), - params: schema.oneOf!.map(_ => parse(_, options, undefined, processed, usedNames)), + params: schema.oneOf!.map(_ => parse(_, options, dereferencedPaths, undefined, processed, usedNames)), type: 'UNION', } case 'REFERENCE': @@ -228,6 +251,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'STRING', } @@ -242,14 +266,22 @@ function parseNonLiteral( keyName, maxItems, minItems, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), - params: schema.items.map(_ => parse(_, options, undefined, processed, usedNames)), + params: schema.items.map(_ => parse(_, options, dereferencedPaths, undefined, processed, usedNames)), type: 'TUPLE', } if (schema.additionalItems === true) { arrayType.spreadParam = options.unknownAny ? T_UNKNOWN : T_ANY } else if (schema.additionalItems) { - arrayType.spreadParam = parse(schema.additionalItems, options, undefined, processed, usedNames) + arrayType.spreadParam = parse( + schema.additionalItems, + options, + dereferencedPaths, + undefined, + processed, + usedNames, + ) } return arrayType } else { @@ -257,8 +289,16 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), - params: parse(schema.items!, options, `{keyNameFromDefinition}Items`, processed, usedNames), + params: parse( + schema.items!, + options, + dereferencedPaths, + `{keyNameFromDefinition}Items`, + processed, + usedNames, + ), type: 'ARRAY', } } @@ -267,12 +307,13 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: (schema.type as JSONSchema4TypeName[]).map(type => { const member: LinkedJSONSchema = {...omit(schema, '$id', 'description', 'title'), type} maybeStripDefault(member) applySchemaTyping(member) - return parse(member, options, undefined, processed, usedNames) + return parse(member, options, dereferencedPaths, undefined, processed, usedNames) }), type: 'UNION', } @@ -281,12 +322,21 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: (schema as EnumJSONSchema).enum!.map(_ => parseLiteral(_, undefined)), type: 'UNION', } case 'UNNAMED_SCHEMA': - return newInterface(schema as SchemaSchema, options, processed, usedNames, keyName, keyNameFromDefinition) + return newInterface( + schema as SchemaSchema, + options, + processed, + usedNames, + dereferencedPaths, + keyName, + keyNameFromDefinition, + ) case 'UNTYPED_ARRAY': // normalised to not be undefined const minItems = schema.minItems! @@ -303,6 +353,7 @@ function parseNonLiteral( 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, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'TUPLE', } @@ -313,12 +364,25 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, params, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'ARRAY', } } } +function getOriginalName( + schema: NormalizedJSONSchema, + keyNameFromDefinition: string | undefined, + options: Options, +): string | undefined { + const fullName = + options.customName?.(schema, keyNameFromDefinition) || schema.title || schema.$id || keyNameFromDefinition + if (fullName) { + return generateNameNonUnique(fullName) + } +} + /** * Compute a schema name using a series of fallbacks */ @@ -328,8 +392,7 @@ function standaloneName( usedNames: UsedNames, options: Options, ): string | undefined { - const name = - options.customName?.(schema, keyNameFromDefinition) || schema.title || schema.$id || keyNameFromDefinition + const name = getOriginalName(schema, keyNameFromDefinition, options) if (name) { return generateName(name, usedNames) } @@ -340,6 +403,7 @@ function newInterface( options: Options, processed: Processed, usedNames: UsedNames, + dereferencedPaths: DereferencedPaths, keyName?: string, keyNameFromDefinition?: string, ): TInterface { @@ -348,9 +412,10 @@ function newInterface( comment: schema.description, deprecated: schema.deprecated, keyName, - params: parseSchema(schema, options, processed, usedNames, name), + params: parseSchema(schema, options, processed, usedNames, name, dereferencedPaths), + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: name, - superTypes: parseSuperTypes(schema, options, processed, usedNames), + superTypes: parseSuperTypes(schema, options, processed, usedNames, dereferencedPaths), type: 'INTERFACE', } } @@ -360,6 +425,7 @@ function parseSuperTypes( options: Options, processed: Processed, usedNames: UsedNames, + dereferencedPaths: DereferencedPaths, ): TNamedInterface[] { // Type assertion needed because of dereferencing step // TODO: Type it upstream @@ -367,7 +433,7 @@ function parseSuperTypes( if (!superTypes) { return [] } - return superTypes.map(_ => parse(_, options, undefined, processed, usedNames) as TNamedInterface) + return superTypes.map(_ => parse(_, options, dereferencedPaths, undefined, processed, usedNames) as TNamedInterface) } /** @@ -379,14 +445,19 @@ function parseSchema( processed: Processed, usedNames: UsedNames, parentSchemaName: string, + dereferencedPaths: DereferencedPaths, ): TInterfaceParam[] { - let asts: TInterfaceParam[] = map(schema.properties, (value, key: string) => ({ - ast: parse(value, options, key, processed, usedNames), - isPatternProperty: false, - isRequired: includes(schema.required || [], key), - isUnreachableDefinition: false, - keyName: key, - })) + let asts: TInterfaceParam[] = map( + schema.properties, + (value, key: string): TInterfaceParam => ({ + ast: parse(value, options, dereferencedPaths, key, processed, usedNames), + isPatternProperty: false, + isRequired: includes(schema.required || [], key), + isUnreachableDefinition: false, + keyName: key, + referencePath: dereferencedPaths.get(value) ?? null, + }), + ) let singlePatternProperty = false if (schema.patternProperties) { @@ -397,7 +468,7 @@ function parseSchema( asts = asts.concat( map(schema.patternProperties, (value, key: string) => { - const ast = parse(value, options, key, processed, usedNames) + const ast = parse(value, options, dereferencedPaths, key, processed, usedNames) const comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema definition via the \`patternProperty\` "${key.replace('*/', '*\\/')}".` ast.comment = ast.comment ? `${ast.comment}\n\n${comment}` : comment @@ -407,6 +478,7 @@ via the \`patternProperty\` "${key.replace('*/', '*\\/')}".` isRequired: singlePatternProperty || includes(schema.required || [], key), isUnreachableDefinition: false, keyName: singlePatternProperty ? '[k: string]' : key, + referencePath: dereferencedPaths.get(value) ?? null, } }), ) @@ -415,7 +487,7 @@ via the \`patternProperty\` "${key.replace('*/', '*\\/')}".` if (options.unreachableDefinitions) { asts = asts.concat( map(schema.$defs, (value, key: string) => { - const ast = parse(value, options, key, processed, usedNames) + const ast = parse(value, options, dereferencedPaths, key, 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 @@ -425,6 +497,7 @@ via the \`definition\` "${key}".` isRequired: includes(schema.required || [], key), isUnreachableDefinition: true, keyName: key, + referencePath: dereferencedPaths.get(value) ?? null, } }), ) @@ -443,6 +516,7 @@ via the \`definition\` "${key}".` isRequired: true, isUnreachableDefinition: false, keyName: '[k: string]', + referencePath: null, }) case false: @@ -452,11 +526,12 @@ via the \`definition\` "${key}".` // defined via index signatures are already optional default: return asts.concat({ - ast: parse(schema.additionalProperties, options, '[k: string]', processed, usedNames), + ast: parse(schema.additionalProperties, options, dereferencedPaths, '[k: string]', processed, usedNames), isPatternProperty: false, isRequired: true, isUnreachableDefinition: false, keyName: '[k: string]', + referencePath: null, }) } } diff --git a/src/types/AST.ts b/src/types/AST.ts index 4fb05afd..f02f0740 100644 --- a/src/types/AST.ts +++ b/src/types/AST.ts @@ -26,13 +26,14 @@ export interface AbstractAST { comment?: string keyName?: string standaloneName?: string + originalName?: string type: AST_TYPE deprecated?: boolean } export type ASTWithComment = AST & {comment: string} export type ASTWithName = AST & {keyName: string} -export type ASTWithStandaloneName = AST & {standaloneName: string} +export type ASTWithStandaloneName = AST & {standaloneName: string; originalName: string} export function hasComment(ast: AST): ast is ASTWithComment { return ( @@ -62,6 +63,7 @@ export interface TBoolean extends AbstractAST { } export interface TEnum extends AbstractAST { + originalName: string standaloneName: string type: 'ENUM' params: TEnumParam[] @@ -80,6 +82,7 @@ export interface TInterface extends AbstractAST { export interface TNamedInterface extends AbstractAST { standaloneName: string + originalName: string type: 'INTERFACE' params: TInterfaceParam[] superTypes: TNamedInterface[] @@ -95,6 +98,7 @@ export interface TInterfaceParam { isRequired: boolean isPatternProperty: boolean isUnreachableDefinition: boolean + referencePath: string | null } export interface TIntersection extends AbstractAST { diff --git a/src/utils.ts b/src/utils.ts index 320654ff..e77caf3b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -205,12 +205,18 @@ export function toSafeString(string: string) { ) } -export function generateName(from: string, usedNames: Set) { +export function generateNameNonUnique(from: string): string { let name = toSafeString(from) if (!name) { name = 'NoName' } + return name +} + +export function generateName(from: string, usedNames: Set) { + let name = generateNameNonUnique(from) + // increment counter until we find a free name if (usedNames.has(name)) { let counter = 1 diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index 0d8b981b..7e9c1d79 100644 --- a/test/__snapshots__/test/test.ts.md +++ b/test/__snapshots__/test/test.ts.md @@ -449775,3 +449775,73 @@ Generated by [AVA](https://avajs.dev). g?: number;␊ }␊ ` + +## type imports + +> Snapshot 1 + + 'test/resources/mirror-dir/out/inner-dir/ReferencedTypeInInnerDir.d.ts' + +> Snapshot 2 + + `/* 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.␊ + */␊ + ␊ + export interface ExampleSchema {␊ + firstName: string;␊ + lastName: string;␊ + [k: string]: unknown;␊ + }␊ + ` + +> Snapshot 3 + + 'test/resources/mirror-dir/out/ReferencedType.d.ts' + +> Snapshot 4 + + `/* 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.␊ + */␊ + ␊ + export interface ExampleSchema {␊ + firstName: string;␊ + lastName: string;␊ + /**␊ + * Age in years␊ + */␊ + age?: number;␊ + height?: number;␊ + favoriteFoods?: unknown[];␊ + likesDogs?: boolean;␊ + [k: string]: unknown;␊ + }␊ + ` + +> Snapshot 5 + + 'test/resources/mirror-dir/out/ReferencingType.d.ts' + +> Snapshot 6 + + `/* 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.␊ + */␊ + ␊ + import type {ExampleSchema} from "./ReferencedType.ts";␊ + import type {ExampleSchema as ExampleSchema1} from "./inner-dir/ReferencedTypeInInnerDir.ts";␊ + export interface Referencing {␊ + foo: ExampleSchema;␊ + bar: ExampleSchema1;␊ + }␊ + ` diff --git a/test/__snapshots__/test/test.ts.snap b/test/__snapshots__/test/test.ts.snap index 79cd4741..85125b89 100644 Binary files a/test/__snapshots__/test/test.ts.snap and b/test/__snapshots__/test/test.ts.snap differ diff --git a/test/resources/mirror-dir/ReferencedType.json b/test/resources/mirror-dir/ReferencedType.json new file mode 100644 index 00000000..aed64a92 --- /dev/null +++ b/test/resources/mirror-dir/ReferencedType.json @@ -0,0 +1,28 @@ +{ + "id": "http://dummy.com/api/example-schema", + "title": "Example Schema", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "description": "Age in years", + "type": "integer", + "minimum": 0 + }, + "height": { + "type": "number" + }, + "favoriteFoods": { + "type": "array" + }, + "likesDogs": { + "type": "boolean" + } + }, + "required": ["firstName", "lastName"] +} \ No newline at end of file diff --git a/test/resources/mirror-dir/ReferencingType.json b/test/resources/mirror-dir/ReferencingType.json new file mode 100644 index 00000000..e74c6b07 --- /dev/null +++ b/test/resources/mirror-dir/ReferencingType.json @@ -0,0 +1,14 @@ +{ + "title": "Referencing", + "type": "object", + "properties": { + "foo": { + "$ref": "ReferencedType.json" + }, + "bar": { + "$ref": "./inner-dir/ReferencedTypeInInnerDir.json" + } + }, + "required": ["foo", "bar"], + "additionalProperties": false +} diff --git a/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json b/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json new file mode 100644 index 00000000..00fb601c --- /dev/null +++ b/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json @@ -0,0 +1,14 @@ +{ + "id": "http://dummy.com/api/example-schema", + "title": "Example Schema", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + }, + "required": ["firstName", "lastName"] +} diff --git a/test/testCLI.ts b/test/testCLI.ts index 327c1004..70ca8ac4 100644 --- a/test/testCLI.ts +++ b/test/testCLI.ts @@ -137,6 +137,15 @@ export function run() { }) rimraf.sync('./test/resources/MultiSchema2/out') }) + + test('type imports', t => { + execSync('node dist/src/cli.js --useTypeImports -i ./test/resources/mirror-dir -o ./test/resources/mirror-dir/out') + getPaths('./test/resources/mirror-dir/out').forEach(file => { + t.snapshot(file) + t.snapshot(readFileSync(file, 'utf-8')) + unlinkSync(file) + }) + }) } function getPaths(path: string, paths: string[] = []) {