From bc95fd2added211d710750972c3c3daf16f95bdd Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 12:58:51 -0700 Subject: [PATCH 01/25] wip of tests --- src/cli.ts | 1 + src/generator.ts | 13 ++++++++- src/index.ts | 8 ++++++ test/resources/mirror-dir/ReferencedType.json | 28 +++++++++++++++++++ .../resources/mirror-dir/ReferencingType.json | 14 ++++++++++ .../inner-dir/ReferencedTypeInInnerDir.json | 14 ++++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test/resources/mirror-dir/ReferencedType.json create mode 100644 test/resources/mirror-dir/ReferencingType.json create mode 100644 test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json diff --git a/src/cli.ts b/src/cli.ts index 456f1447..134cd01e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -23,6 +23,7 @@ main( 'strictIndexSignatures', 'unknownAny', 'unreachableDefinitions', + 'mirrorDir', ], default: DEFAULT_OPTIONS, string: ['bannerComment', 'cwd'], diff --git a/src/generator.ts b/src/generator.ts index dcee32ff..90b3862b 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -73,7 +73,7 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string, case 'INTERFACE': type = [ hasStandaloneName(ast) && - (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && + (ast.standaloneName === rootASTName || options.declareExternallyReferenced || options.mirrorDir) && generateStandaloneInterface(ast, options), getSuperTypesAndParams(ast) .map(ast => declareNamedInterfaces(ast, options, rootASTName, processed)) @@ -349,6 +349,17 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { } function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { + if (options.mirrorDir) { + return ( + (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + + `import type { ${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, ast.deprecated) + '\n' : '') + `export interface ${toSafeString(ast.standaloneName)} ` + diff --git a/src/index.ts b/src/index.ts index 1aa67be0..d6c96150 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,8 @@ export interface Options { * Declare external schemas referenced via `$ref`? */ declareExternallyReferenced: boolean + mirrorDir: boolean + outDir: string | undefined /** * Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? */ @@ -114,6 +116,8 @@ export const DEFAULT_OPTIONS: Options = { useTabs: false, }, unreachableDefinitions: false, + mirrorDir: false, + outDir: undefined, unknownAny: true, } @@ -137,6 +141,10 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia const _options = merge({}, DEFAULT_OPTIONS, options) + if (_options.mirrorDir && !_options.outDir) { + throw new Error('if you pass mirrorDir: true, you must also pass outDir') + } + const start = Date.now() function time() { return `(${Date.now() - start}ms)` 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"] +} From e8bfbec89fd233a3b682829ba0203320383b00e7 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 14:34:00 -0700 Subject: [PATCH 02/25] god what a faff --- fooOrig.ts | 28 ++++++++++++ src/generator.ts | 30 ++++++++----- src/index.ts | 9 +++- src/normalizer.ts | 6 +-- src/parser.ts | 97 +++++++++++++++++++++++++++++++----------- src/resolver.ts | 5 ++- src/types/AST.ts | 1 + test/testNormalizer.ts | 2 +- 8 files changed, 134 insertions(+), 44 deletions(-) create mode 100644 fooOrig.ts diff --git a/fooOrig.ts b/fooOrig.ts new file mode 100644 index 00000000..77536fab --- /dev/null +++ b/fooOrig.ts @@ -0,0 +1,28 @@ +/* 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 Referencing { + foo: ExampleSchema; + bar: ExampleSchema1; +} +export interface ExampleSchema { + firstName: string; + lastName: string; + /** + * Age in years + */ + age?: number; + height?: number; + favoriteFoods?: unknown[]; + likesDogs?: boolean; + [k: string]: unknown; +} +export interface ExampleSchema1 { + firstName: string; + lastName: string; + [k: string]: unknown; +} diff --git a/src/generator.ts b/src/generator.ts index 90b3862b..d1317fce 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -15,8 +15,11 @@ import { T_UNKNOWN, } from './types/AST' import {log, toSafeString} from './utils' +import {DereferencedPaths} from './resolver' -export function generate(ast: AST, options = DEFAULT_OPTIONS): string { +export function generate(ast: AST, dereferencedPaths: DereferencedPaths, options = DEFAULT_OPTIONS): string { + console.log(`XXX dereferencedPaths`, dereferencedPaths) + console.log(`XXX ast`, ast) return ( [ options.bannerComment, @@ -108,6 +111,8 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc processed.add(ast) + // if (options.mirrorDir && ast. + switch (ast.type) { case 'ARRAY': return [ @@ -349,19 +354,24 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { } function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { + console.log(`XXX ast`, ast) + const lines: string[] = [] + + if (hasComment(ast)) { + lines.push(generateComment(ast.comment, ast.deprecated)) + } + if (options.mirrorDir) { - return ( - (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + - `import type { ${toSafeString(ast.standaloneName)} } ` + - (ast.superTypes.length > 0 - ? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} ` - : '') + - generateInterface(ast, options) - ) + for (const param of ast.params) { + if (param.ast.standaloneName && param.referencePath) { + lines.push( + `import type ${toSafeString(param.ast.standaloneName)} from '${param.referencePath!.replace(/json$/, 'ts')}';`, + ) + } + } } return ( - (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 ? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} ` diff --git a/src/index.ts b/src/index.ts index d6c96150..d6fdaac1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -159,6 +159,11 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia const _schema = cloneDeep(schema) const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options) + for (const key of dereferencedPaths) { + console.log(key, dereferencedPaths.get(key)) + } + console.log(`XXX dereferencedPaths`, dereferencedPaths) + console.log(`XXX dereferencedSchema`, dereferencedSchema) if (process.env.VERBOSE) { if (isDeepStrictEqual(_schema, dereferencedSchema)) { log('green', 'dereferencer', time(), '✅ No change') @@ -184,13 +189,13 @@ 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) log('cyan', 'optimizer', time(), '✅ Result:', optimized) - const generated = generate(optimized, _options) + const generated = generate(optimized, dereferencedPaths, _options) log('magenta', 'generator', time(), '✅ Result:', generated) const formatted = await format(generated, _options) diff --git a/src/normalizer.ts b/src/normalizer.ts index 2d0d534d..e47104c4 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -100,9 +100,9 @@ rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _ schema.$id = toSafeString(justName(dereferencedName)) } - if (dereferencedName) { - dereferencedPaths.delete(schema) - } + // if (dereferencedName) { + // dereferencedPaths.delete(schema) + // } }) rules.set('Escape closing JSDoc comment', schema => { diff --git a/src/parser.ts b/src/parser.ts index 92acdeb3..8dfb25ab 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -15,6 +15,7 @@ import type { } from './types/JSONSchema' import {Intersection, Types, getRootSchema, isBoolean, isPrimitive} from './types/JSONSchema' import {generateName, 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) @@ -129,7 +141,7 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, 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': @@ -146,7 +158,7 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, 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': @@ -179,7 +191,7 @@ 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, @@ -218,7 +230,7 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, 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': @@ -243,13 +255,20 @@ function parseNonLiteral( maxItems, minItems, 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 { @@ -258,7 +277,14 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, 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', } } @@ -272,7 +298,7 @@ function parseNonLiteral( 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', } @@ -286,7 +312,15 @@ function parseNonLiteral( 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! @@ -340,6 +374,7 @@ function newInterface( options: Options, processed: Processed, usedNames: UsedNames, + dereferencedPaths: DereferencedPaths, keyName?: string, keyNameFromDefinition?: string, ): TInterface { @@ -348,9 +383,9 @@ function newInterface( comment: schema.description, deprecated: schema.deprecated, keyName, - params: parseSchema(schema, options, processed, usedNames, name), + params: parseSchema(schema, options, processed, usedNames, name, dereferencedPaths), standaloneName: name, - superTypes: parseSuperTypes(schema, options, processed, usedNames), + superTypes: parseSuperTypes(schema, options, processed, usedNames, dereferencedPaths), type: 'INTERFACE', } } @@ -360,6 +395,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 +403,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 +415,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 +438,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 +448,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 +457,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 +467,7 @@ via the \`definition\` "${key}".` isRequired: includes(schema.required || [], key), isUnreachableDefinition: true, keyName: key, + referencePath: dereferencedPaths.get(value) ?? null, } }), ) @@ -443,6 +486,7 @@ via the \`definition\` "${key}".` isRequired: true, isUnreachableDefinition: false, keyName: '[k: string]', + referencePath: null, }) case false: @@ -452,11 +496,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/resolver.ts b/src/resolver.ts index 94dc5a0e..e47887a8 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -2,7 +2,7 @@ import {$RefParser, ParserOptions as $RefOptions} from '@apidevtools/json-schema import {JSONSchema} from './types/JSONSchema' import {log} from './utils' -export type DereferencedPaths = WeakMap +export type DereferencedPaths = Map export async function dereference( schema: JSONSchema, @@ -10,12 +10,13 @@ export async function dereference( ): Promise<{dereferencedPaths: DereferencedPaths; dereferencedSchema: JSONSchema}> { log('green', 'dereferencer', 'Dereferencing input schema:', cwd, schema) const parser = new $RefParser() - const dereferencedPaths: DereferencedPaths = new WeakMap() + const dereferencedPaths: DereferencedPaths = new Map() const dereferencedSchema = (await parser.dereference(cwd, schema, { ...$refOptions, dereference: { ...$refOptions.dereference, onDereference($ref: string, schema: JSONSchema) { + console.log(`XXX $ref`, $ref) dereferencedPaths.set(schema, $ref) }, }, diff --git a/src/types/AST.ts b/src/types/AST.ts index 4fb05afd..cae6ed06 100644 --- a/src/types/AST.ts +++ b/src/types/AST.ts @@ -95,6 +95,7 @@ export interface TInterfaceParam { isRequired: boolean isPatternProperty: boolean isUnreachableDefinition: boolean + referencePath: string | null } export interface TIntersection extends AbstractAST { diff --git a/test/testNormalizer.ts b/test/testNormalizer.ts index 55fa6ca9..d640b784 100644 --- a/test/testNormalizer.ts +++ b/test/testNormalizer.ts @@ -21,7 +21,7 @@ export function run() { .map(_ => [_, require(_)] as [string, JSONTestCase]) .forEach(([filename, json]: [string, JSONTestCase]) => { test(json.name, t => { - const normalized = normalize(link(json.in), new WeakMap(), filename, json.options ?? DEFAULT_OPTIONS) + const normalized = normalize(link(json.in), new Map(), filename, json.options ?? DEFAULT_OPTIONS) t.deepEqual(json.out, normalized) }) }) From 4b6c5422cf7bd73572b3ff16047ba39eaf1f2e05 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 17:50:58 -0700 Subject: [PATCH 03/25] can run with this: node dist/src/cli.js --mirrorDir --declareExternallyReferenced false --outDir=qwer -i test/resources/mirror-dir/ReferencingType.json --cwd test/resources/mirror-dir -o foo.ts --- foo.ts | 13 +++++++++++++ src/generator.ts | 46 ++++++++++++++++++++-------------------------- src/index.ts | 2 ++ src/normalizer.ts | 17 +++++++++++++++++ 4 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 foo.ts diff --git a/foo.ts b/foo.ts new file mode 100644 index 00000000..61c7b34c --- /dev/null +++ b/foo.ts @@ -0,0 +1,13 @@ +/* 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 {ExampleSchema1} from './inner-dir/ReferencedTypeInInnerDir.ts' +export interface Referencing { + foo: ExampleSchema + bar: ExampleSchema1 +} diff --git a/src/generator.ts b/src/generator.ts index d1317fce..396b8f83 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -20,16 +20,12 @@ import {DereferencedPaths} from './resolver' export function generate(ast: AST, dereferencedPaths: DereferencedPaths, options = DEFAULT_OPTIONS): string { console.log(`XXX dereferencedPaths`, dereferencedPaths) console.log(`XXX ast`, ast) - return ( - [ - options.bannerComment, - declareNamedTypes(ast, options, ast.standaloneName!), - declareNamedInterfaces(ast, options, ast.standaloneName!), - declareEnums(ast, options), - ] - .filter(Boolean) - .join('\n\n') + '\n' - ) // trailing newline + + const namedTypes = declareNamedTypes(ast, options, ast.standaloneName!) + const namedInterfaces = declareNamedInterfaces(ast, options, ast.standaloneName!) + const enums = declareEnums(ast, options) + + return [options.bannerComment, namedTypes, namedInterfaces, enums].filter(Boolean).join('\n\n') + '\n' // trailing newline } function declareEnums(ast: AST, options: Options, processed = new Set()): string { @@ -74,17 +70,15 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string, type = declareNamedInterfaces((ast as TArray).params, options, rootASTName, processed) break case 'INTERFACE': - type = [ + const topLevelAst = hasStandaloneName(ast) && - (ast.standaloneName === rootASTName || options.declareExternallyReferenced || options.mirrorDir) && - generateStandaloneInterface(ast, options), - getSuperTypesAndParams(ast) - .map(ast => declareNamedInterfaces(ast, options, rootASTName, processed)) - .filter(Boolean) - .join('\n'), - ] + (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && + generateStandaloneInterface(ast, options) + const superTypesAndParams = getSuperTypesAndParams(ast) + .map(ast => declareNamedInterfaces(ast, options, rootASTName, processed)) .filter(Boolean) .join('\n') + type = [topLevelAst, superTypesAndParams].filter(Boolean).join('\n') break case 'INTERSECTION': case 'TUPLE': @@ -111,8 +105,6 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc processed.add(ast) - // if (options.mirrorDir && ast. - switch (ast.type) { case 'ARRAY': return [ @@ -365,19 +357,21 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st for (const param of ast.params) { if (param.ast.standaloneName && param.referencePath) { lines.push( - `import type ${toSafeString(param.ast.standaloneName)} from '${param.referencePath!.replace(/json$/, 'ts')}';`, + `import type { ${toSafeString(param.ast.standaloneName)} } from '${param.referencePath!.replace(/json$/, 'ts')}';`, ) } } } - return ( + 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('\n') + '\n' } function generateStandaloneType(ast: ASTWithStandaloneName, options: Options): string { diff --git a/src/index.ts b/src/index.ts index d6fdaac1..70316403 100644 --- a/src/index.ts +++ b/src/index.ts @@ -172,6 +172,8 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia } } + debugger + const linked = link(dereferencedSchema) if (process.env.VERBOSE) { log('green', 'linker', time(), '✅ No change') diff --git a/src/normalizer.ts b/src/normalizer.ts index e47104c4..f3ce7533 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -77,6 +77,23 @@ rules.set('Transform id to $id', (schema, fileName) => { } }) +rules.set('Add $refPath to schema', (schema, _fileName, _options, _key, dereferencedPaths) => { + if (!isSchemaLike(schema)) { + return + } + + if (schema.$refPath) { + return + } + + // We'll infer from $id and title downstream + // TODO: Normalize upstream + const refPath = dereferencedPaths.get(schema) + if (refPath) { + schema.$refPath = refPath + } +}) + rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _key, dereferencedPaths) => { if (!isSchemaLike(schema)) { return From b20858048cf65476c33e71d36db5c5304358bcfc Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 20:06:10 -0700 Subject: [PATCH 04/25] wip --- foo.ts | 13 ------------- src/cli.ts | 6 +++++- 2 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 foo.ts diff --git a/foo.ts b/foo.ts deleted file mode 100644 index 61c7b34c..00000000 --- a/foo.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 {ExampleSchema1} from './inner-dir/ReferencedTypeInInnerDir.ts' -export interface Referencing { - foo: ExampleSchema - bar: ExampleSchema1 -} diff --git a/src/cli.ts b/src/cli.ts index 134cd01e..1315e752 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' @@ -102,7 +103,10 @@ 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) + opts.cwd = resolve(dirname(outputPath)) + return [file, await processFile(file, opts), outputPath] as const } }), ) From 1b1c108ca57fdde7eaf946545683df20fa68e052 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 20:14:39 -0700 Subject: [PATCH 05/25] works, kindof? --- src/cli.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index 1315e752..38f25435 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -104,8 +104,11 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti } else { const outputPath = pathTransform(argOut, argIn, file) // get argv with cwd corrected. We want to resolve relevant to dir. + debugger const opts = cloneDeep(argv) - opts.cwd = resolve(dirname(outputPath)) + const dirPath = resolve(dirname(file)) + opts.cwd = resolve(dirPath) + console.log(`XXX opts`, opts) return [file, await processFile(file, opts), outputPath] as const } }), From 6d91bfd46fe4a3863502ef3ab16a450e004ea4f3 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 20:59:15 -0700 Subject: [PATCH 06/25] slowly, slowly --- src/cli.ts | 4 +++- src/generator.ts | 4 ++++ src/index.ts | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index 38f25435..d290437d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -114,9 +114,11 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti }), ) + const ext = argv.outFileExt ?? '.d.ts' + // careful to do this serially results.forEach(([file, result, outputPath]) => - outputResult(result, outputPath ? `${outputPath}/${justName(file)}.d.ts` : undefined), + outputResult(result, outputPath ? `${outputPath}/${justName(file)}${ext}` : undefined), ) } diff --git a/src/generator.ts b/src/generator.ts index 396b8f83..d5ad5fb1 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -363,6 +363,10 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st } } + if (lines.length > 0) { + lines.push('\n') + } + lines.push( `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 diff --git a/src/index.ts b/src/index.ts index 70316403..c66fe15c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,7 @@ export interface Options { declareExternallyReferenced: boolean mirrorDir: boolean outDir: string | undefined + outFileExt: string | undefined /** * Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? */ @@ -118,6 +119,7 @@ export const DEFAULT_OPTIONS: Options = { unreachableDefinitions: false, mirrorDir: false, outDir: undefined, + outFileExt: undefined, unknownAny: true, } From 4797cfe2852564297ef71fe770100d8b7a17cec6 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 21:16:27 -0700 Subject: [PATCH 07/25] wip --- src/cli.ts | 3 ++- src/generator.ts | 2 +- src/index.ts | 15 +++++---------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index d290437d..68c8dbb8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -114,7 +114,8 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti }), ) - const ext = argv.outFileExt ?? '.d.ts' + const ext = argv.useTypeImports ? '.ts' : '.d.ts' + console.log(`XXX ext`, ext) // careful to do this serially results.forEach(([file, result, outputPath]) => diff --git a/src/generator.ts b/src/generator.ts index d5ad5fb1..75bc725c 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -353,7 +353,7 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st lines.push(generateComment(ast.comment, ast.deprecated)) } - if (options.mirrorDir) { + if (options.useTypeImports) { for (const param of ast.params) { if (param.ast.standaloneName && param.referencePath) { lines.push( diff --git a/src/index.ts b/src/index.ts index c66fe15c..f356e10f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,9 +44,10 @@ export interface Options { * Declare external schemas referenced via `$ref`? */ declareExternallyReferenced: boolean - mirrorDir: boolean - outDir: string | undefined - outFileExt: string | undefined + /** + * Use `import type` instead of redeclaring. + */ + useTypeImports: boolean /** * Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? */ @@ -117,9 +118,7 @@ export const DEFAULT_OPTIONS: Options = { useTabs: false, }, unreachableDefinitions: false, - mirrorDir: false, - outDir: undefined, - outFileExt: undefined, + useTypeImports: false, unknownAny: true, } @@ -143,10 +142,6 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia const _options = merge({}, DEFAULT_OPTIONS, options) - if (_options.mirrorDir && !_options.outDir) { - throw new Error('if you pass mirrorDir: true, you must also pass outDir') - } - const start = Date.now() function time() { return `(${Date.now() - start}ms)` From e77b219c5c30451611ee55eb4e4ef3c305dbe2b2 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 21:37:26 -0700 Subject: [PATCH 08/25] works with types --- src/generator.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index 75bc725c..00fa66d6 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' import {DereferencedPaths} from './resolver' export function generate(ast: AST, dereferencedPaths: DereferencedPaths, options = DEFAULT_OPTIONS): string { @@ -356,9 +357,10 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st if (options.useTypeImports) { for (const param of ast.params) { if (param.ast.standaloneName && param.referencePath) { - lines.push( - `import type { ${toSafeString(param.ast.standaloneName)} } from '${param.referencePath!.replace(/json$/, 'ts')}';`, - ) + const dir = dirname(param.referencePath) + const nameWithoutExt = basename(param.referencePath, extname(param.referencePath)) + const importPath = './' + join(dir, `${nameWithoutExt}.ts`) + lines.push(`import type { ${toSafeString(param.ast.standaloneName)} } from '${importPath}';`) } } } From 47c4c68599633970640a3ca3ffc3442a616cbf9c Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 21:41:05 -0700 Subject: [PATCH 09/25] change title --- .../mirror-dir/inner-dir/ReferencedTypeInInnerDir.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json b/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json index 00fb601c..336b4120 100644 --- a/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json +++ b/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json @@ -1,6 +1,6 @@ { "id": "http://dummy.com/api/example-schema", - "title": "Example Schema", + "title": "Referenced Type In Inner Dir", "type": "object", "properties": { "firstName": { From e88ad99deac77867b3565d0e5a58a9ae0ae59856 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 21:48:22 -0700 Subject: [PATCH 10/25] rm logs --- src/cli.ts | 2 -- src/generator.ts | 7 +------ src/index.ts | 5 ++--- src/resolver.ts | 1 - 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 68c8dbb8..d531e250 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -108,14 +108,12 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti const opts = cloneDeep(argv) const dirPath = resolve(dirname(file)) opts.cwd = resolve(dirPath) - console.log(`XXX opts`, opts) return [file, await processFile(file, opts), outputPath] as const } }), ) const ext = argv.useTypeImports ? '.ts' : '.d.ts' - console.log(`XXX ext`, ext) // careful to do this serially results.forEach(([file, result, outputPath]) => diff --git a/src/generator.ts b/src/generator.ts index 00fa66d6..8e6f0910 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -16,12 +16,8 @@ import { } from './types/AST' import {log, toSafeString} from './utils' import {basename, dirname, extname, join} from 'path' -import {DereferencedPaths} from './resolver' - -export function generate(ast: AST, dereferencedPaths: DereferencedPaths, options = DEFAULT_OPTIONS): string { - console.log(`XXX dereferencedPaths`, dereferencedPaths) - console.log(`XXX ast`, ast) +export function generate(ast: AST, options = DEFAULT_OPTIONS): string { const namedTypes = declareNamedTypes(ast, options, ast.standaloneName!) const namedInterfaces = declareNamedInterfaces(ast, options, ast.standaloneName!) const enums = declareEnums(ast, options) @@ -347,7 +343,6 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { } function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { - console.log(`XXX ast`, ast) const lines: string[] = [] if (hasComment(ast)) { diff --git a/src/index.ts b/src/index.ts index f356e10f..b5df1185 100644 --- a/src/index.ts +++ b/src/index.ts @@ -159,8 +159,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia for (const key of dereferencedPaths) { console.log(key, dereferencedPaths.get(key)) } - console.log(`XXX dereferencedPaths`, dereferencedPaths) - console.log(`XXX dereferencedSchema`, dereferencedSchema) + if (process.env.VERBOSE) { if (isDeepStrictEqual(_schema, dereferencedSchema)) { log('green', 'dereferencer', time(), '✅ No change') @@ -194,7 +193,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia const optimized = optimize(parsed, _options) log('cyan', 'optimizer', time(), '✅ Result:', optimized) - const generated = generate(optimized, dereferencedPaths, _options) + const generated = generate(optimized, _options) log('magenta', 'generator', time(), '✅ Result:', generated) const formatted = await format(generated, _options) diff --git a/src/resolver.ts b/src/resolver.ts index e47887a8..3313669c 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -16,7 +16,6 @@ export async function dereference( dereference: { ...$refOptions.dereference, onDereference($ref: string, schema: JSONSchema) { - console.log(`XXX $ref`, $ref) dereferencedPaths.set(schema, $ref) }, }, From faed3645c43cfa49f322e9dbe9e78be90534c825 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 22:00:09 -0700 Subject: [PATCH 11/25] undo a couple things --- src/generator.ts | 4 ---- src/index.ts | 3 --- src/normalizer.ts | 6 +++--- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index 8e6f0910..3f88514a 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -360,10 +360,6 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st } } - if (lines.length > 0) { - lines.push('\n') - } - lines.push( `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 diff --git a/src/index.ts b/src/index.ts index b5df1185..be7bf913 100644 --- a/src/index.ts +++ b/src/index.ts @@ -156,9 +156,6 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia const _schema = cloneDeep(schema) const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options) - for (const key of dereferencedPaths) { - console.log(key, dereferencedPaths.get(key)) - } if (process.env.VERBOSE) { if (isDeepStrictEqual(_schema, dereferencedSchema)) { diff --git a/src/normalizer.ts b/src/normalizer.ts index f3ce7533..943d2e6d 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -117,9 +117,9 @@ rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _ schema.$id = toSafeString(justName(dereferencedName)) } - // if (dereferencedName) { - // dereferencedPaths.delete(schema) - // } + if (dereferencedName) { + dereferencedPaths.delete(schema) + } }) rules.set('Escape closing JSDoc comment', schema => { From bbf36262e9f0d0e4f8a397b4fdd532e2093fedb5 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 22:01:28 -0700 Subject: [PATCH 12/25] rm the thing --- fooOrig.ts | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 fooOrig.ts diff --git a/fooOrig.ts b/fooOrig.ts deleted file mode 100644 index 77536fab..00000000 --- a/fooOrig.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 Referencing { - foo: ExampleSchema; - bar: ExampleSchema1; -} -export interface ExampleSchema { - firstName: string; - lastName: string; - /** - * Age in years - */ - age?: number; - height?: number; - favoriteFoods?: unknown[]; - likesDogs?: boolean; - [k: string]: unknown; -} -export interface ExampleSchema1 { - firstName: string; - lastName: string; - [k: string]: unknown; -} From 62c08486f8c99022a279820e15ea676f69c13002 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sat, 31 May 2025 22:10:05 -0700 Subject: [PATCH 13/25] fix a line maybe --- src/generator.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index 3f88514a..7277cca0 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -345,9 +345,7 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { const lines: string[] = [] - if (hasComment(ast)) { - lines.push(generateComment(ast.comment, ast.deprecated)) - } + hasComment(ast) ? lines.push(generateComment(ast.comment, ast.deprecated)) : lines.push('') if (options.useTypeImports) { for (const param of ast.params) { From d1be289a5b7a3dcf5dd3212ce50306468d39e404 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 12:06:35 -0700 Subject: [PATCH 14/25] works with node dist/src/cli.js --useTypeImports --declareExternallyReferenced false -i test/resources/mirror-dir/ -o mirrorDir --- src/cli.ts | 1 - src/generator.ts | 17 ++++++++- src/index.ts | 2 -- src/normalizer.ts | 6 ++-- src/parser.ts | 36 +++++++++++++++++-- src/types/AST.ts | 5 ++- src/utils.ts | 8 ++++- .../inner-dir/ReferencedTypeInInnerDir.json | 2 +- 8 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index d531e250..696559b3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -104,7 +104,6 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti } else { const outputPath = pathTransform(argOut, argIn, file) // get argv with cwd corrected. We want to resolve relevant to dir. - debugger const opts = cloneDeep(argv) const dirPath = resolve(dirname(file)) opts.cwd = resolve(dirPath) diff --git a/src/generator.ts b/src/generator.ts index 7277cca0..911e28e8 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -59,6 +59,8 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string, return '' } + debugger + processed.add(ast) let type = '' @@ -190,6 +192,8 @@ function generateRawType(ast: AST, options: Options): string { return ast.params case 'STRING': return 'string' + case 'ENUM': + return 'enum' case 'TUPLE': return (() => { const minItems = ast.minItems @@ -349,15 +353,26 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st if (options.useTypeImports) { for (const param of ast.params) { + console.log(`XXX ast.standaloneName`, ast.standaloneName) + console.log(`XXX param`, param) + debugger 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 { ${toSafeString(param.ast.standaloneName)} } from '${importPath}';`) + lines.push(`import type { ${importInBrackets} } from '${importPath}';`) } } } + console.log(`XXX lines`, lines) + lines.push( `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 diff --git a/src/index.ts b/src/index.ts index be7bf913..5d9673da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -165,8 +165,6 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia } } - debugger - const linked = link(dereferencedSchema) if (process.env.VERBOSE) { log('green', 'linker', time(), '✅ No change') diff --git a/src/normalizer.ts b/src/normalizer.ts index 943d2e6d..f3ce7533 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -117,9 +117,9 @@ rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _ schema.$id = toSafeString(justName(dereferencedName)) } - if (dereferencedName) { - dereferencedPaths.delete(schema) - } + // if (dereferencedName) { + // dereferencedPaths.delete(schema) + // } }) rules.set('Escape closing JSDoc comment', schema => { diff --git a/src/parser.ts b/src/parser.ts index 8dfb25ab..3902110e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -14,7 +14,7 @@ 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> @@ -140,6 +140,7 @@ 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, dereferencedPaths, undefined, processed, usedNames)), type: 'INTERSECTION', @@ -150,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': @@ -157,6 +159,7 @@ 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, dereferencedPaths, undefined, processed, usedNames)), type: 'UNION', @@ -166,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', } @@ -175,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', } @@ -183,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), @@ -197,6 +203,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NEVER', } @@ -205,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', } @@ -213,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', } @@ -220,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, @@ -229,6 +239,7 @@ 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, dereferencedPaths, undefined, processed, usedNames)), type: 'UNION', @@ -240,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', } @@ -254,6 +266,7 @@ function parseNonLiteral( keyName, maxItems, minItems, + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: schema.items.map(_ => parse(_, options, dereferencedPaths, undefined, processed, usedNames)), type: 'TUPLE', @@ -276,6 +289,7 @@ 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!, @@ -293,6 +307,7 @@ 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} @@ -307,6 +322,7 @@ 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', @@ -337,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', } @@ -347,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 */ @@ -362,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) } @@ -384,6 +413,7 @@ function newInterface( deprecated: schema.deprecated, keyName, params: parseSchema(schema, options, processed, usedNames, name, dereferencedPaths), + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), standaloneName: name, superTypes: parseSuperTypes(schema, options, processed, usedNames, dereferencedPaths), type: 'INTERFACE', diff --git a/src/types/AST.ts b/src/types/AST.ts index cae6ed06..7c6a2917 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 | null} 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 | undefined standaloneName: string type: 'ENUM' params: TEnumParam[] @@ -80,6 +82,7 @@ export interface TInterface extends AbstractAST { export interface TNamedInterface extends AbstractAST { standaloneName: string + originalName: string | undefined type: 'INTERFACE' params: TInterfaceParam[] superTypes: TNamedInterface[] 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/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json b/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json index 336b4120..00fb601c 100644 --- a/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json +++ b/test/resources/mirror-dir/inner-dir/ReferencedTypeInInnerDir.json @@ -1,6 +1,6 @@ { "id": "http://dummy.com/api/example-schema", - "title": "Referenced Type In Inner Dir", + "title": "Example Schema", "type": "object", "properties": { "firstName": { From abbdbb7b2bfac78b9846c2c58fff2524f438479d Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 12:08:42 -0700 Subject: [PATCH 15/25] rm logs and debuggers --- src/generator.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index 911e28e8..45955ac0 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -59,8 +59,6 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string, return '' } - debugger - processed.add(ast) let type = '' @@ -353,9 +351,6 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st if (options.useTypeImports) { for (const param of ast.params) { - console.log(`XXX ast.standaloneName`, ast.standaloneName) - console.log(`XXX param`, param) - debugger if (param.ast.standaloneName && param.referencePath) { const dir = dirname(param.referencePath) const nameWithoutExt = basename(param.referencePath, extname(param.referencePath)) @@ -371,8 +366,6 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st } } - console.log(`XXX lines`, lines) - lines.push( `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 From e8615023adcc399017b226422e53695bb296ab24 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 12:16:58 -0700 Subject: [PATCH 16/25] progress! three tests broken --- src/generator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index 45955ac0..7d54d636 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -347,7 +347,7 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { const lines: string[] = [] - hasComment(ast) ? lines.push(generateComment(ast.comment, ast.deprecated)) : lines.push('') + hasComment(ast) ? lines.push(generateComment(ast.comment, ast.deprecated) + '\n') : lines.push('') if (options.useTypeImports) { for (const param of ast.params) { @@ -374,7 +374,7 @@ function generateStandaloneInterface(ast: TNamedInterface, options: Options): st generateInterface(ast, options), ) - return lines.join('\n') + '\n' + return lines.join('') } function generateStandaloneType(ast: ASTWithStandaloneName, options: Options): string { From e8f5d70eedabb3bdcc02004989b2ac9b2b1448b8 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 15:46:13 -0700 Subject: [PATCH 17/25] fix thing --- src/generator.ts | 2 -- src/parser.ts | 2 +- src/types/AST.ts | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index 7d54d636..27d60600 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -190,8 +190,6 @@ function generateRawType(ast: AST, options: Options): string { return ast.params case 'STRING': return 'string' - case 'ENUM': - return 'enum' case 'TUPLE': return (() => { const minItems = ast.minItems diff --git a/src/parser.ts b/src/parser.ts index 3902110e..4c24353e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -188,7 +188,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options), + originalName: getOriginalName(schema, keyNameFromDefinition ?? keyName, options)!, standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames, options)!, params: (schema as EnumJSONSchema).enum!.map((_, n) => ({ ast: parseLiteral(_, undefined), diff --git a/src/types/AST.ts b/src/types/AST.ts index 7c6a2917..f02f0740 100644 --- a/src/types/AST.ts +++ b/src/types/AST.ts @@ -33,7 +33,7 @@ export interface AbstractAST { export type ASTWithComment = AST & {comment: string} export type ASTWithName = AST & {keyName: string} -export type ASTWithStandaloneName = AST & {standaloneName: string; originalName: string | null} +export type ASTWithStandaloneName = AST & {standaloneName: string; originalName: string} export function hasComment(ast: AST): ast is ASTWithComment { return ( @@ -63,7 +63,7 @@ export interface TBoolean extends AbstractAST { } export interface TEnum extends AbstractAST { - originalName: string | undefined + originalName: string standaloneName: string type: 'ENUM' params: TEnumParam[] @@ -82,7 +82,7 @@ export interface TInterface extends AbstractAST { export interface TNamedInterface extends AbstractAST { standaloneName: string - originalName: string | undefined + originalName: string type: 'INTERFACE' params: TInterfaceParam[] superTypes: TNamedInterface[] From 9b17ecbf58a7d8dbe14912c8be03fda5152e39b7 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 16:05:42 -0700 Subject: [PATCH 18/25] fix the thing maybe --- src/typesOfSchema.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/typesOfSchema.ts b/src/typesOfSchema.ts index 2d8b31be..b7701e4d 100644 --- a/src/typesOfSchema.ts +++ b/src/typesOfSchema.ts @@ -36,7 +36,9 @@ const matchers: Record boolean> = { return 'allOf' in schema }, ANY(schema) { - if (Object.keys(schema).length === 0) { + // We add a single key. Remove that one. + const nativeKeys = Object.keys(schema).filter(key => key !== '$refPath') + if (nativeKeys.length === 0) { // The empty schema {} validates any value // @see https://json-schema.org/draft-07/json-schema-core.html#rfc.section.4.3.1 return true From 92e05fa7bc4bf0564aabbeece39e31d9679ecf60 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 16:14:57 -0700 Subject: [PATCH 19/25] add a test --- test/__snapshots__/test/test.ts.md | 70 +++++++++++++++++++++++++++ test/__snapshots__/test/test.ts.snap | Bin 1589732 -> 1589872 bytes test/testCLI.ts | 11 +++++ 3 files changed, 81 insertions(+) diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index 0d8b981b..45724b55 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.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.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.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 79cd47418900736fa22864ed818963518ca89390..de700fb10c2be82df2b0ea584f66fd6632226429 100644 GIT binary patch delta 2295 zcmV79g?LYU{l9Pinf>Q~Jg0|wVDz+JGi_R5B`v_y$aZnIY8_3JdeSbC^D=IS?d^WRGzVmd9b9XAt2_#QpqIW3f0gJ!!fa*t%q2<@;rhd z4;xo2i#}x=zTi)*)!Iu%Kv1Y`56}oHL_)Q-YzEP0<#_->Ab&cnMizfcHh4jwMk7`D zf;=S~{cOmcF19SqNgDs`tw_W2JVUT}(Q(aYE?=x!dB!goG@FjV25eTI@h3{WL^95_ zXiZwPp8J^DjPtWhCXr?Z#?Ot^mx>yxqo!za(0`KIZD~l*x+_{#ktKys_#I=QU}VWV zg?X4jH_k_omw*4-*wq}ZPwGa*AFq7{sQ5%}Do%2$c5i_3y_(FPTD_+^b#SR(>uHCz zI%qX}h%s8I0bNvk=#N@0TB0`hNqe_2F=Bw{q_AAS#~v~>!4t;F99*gScw{qpIqg6D2u5kjP9fxEeUPTM^TaOfJTSbfK zLh8O%n7R@@#nG>a_ue)34eJA&IdvPU<_dwz+JXqu<&x z=hr&>w}Co$j^e>5A;-bQWK24(gp}mI_c;Lc6MuC9B|>8U#jD|$+p$xfWj`RZhc?`# zm6@oom*#29QdhC_JouU3LY(|!lCG^41|K3FLx2SLy{CWxUnom}Ej?jMKQCsO`ZC(m zkBJkKuiky%Hv23oXQkj1>2MvBH;8#TrTPS3tPKT=6cE2^kX=8&F~`ciTolg`I0;H@ zCx6ZC2LQ^CRD@Da2_zisO?%JJEYXy|R`=~YM}(9E2q9gJhx7t~bWUwZuZ<-;kijfN zpuXC~syZb+ixco9#C*OA;CWF+c&PFe@3%od4PJ=yXfP~Iz>pBFS?y^d4D09->a0w> zzigmKs5|-k!Y{`h@dd9fDX~mlw%u8a*?-ec3A+@mx$7s}nEnQ!|Dhu2ajPA#3eHwG zsa*M`p=q;C?feP3>>Fu6547lQ24n-ZS}n2U@j7rh`&-D{-&F3S(qa?2|hsnfRRoDU_1ww>SlI)yaKG?*y>l*;kmr6mzAL|;ag@Y}AYM$UD z^>29;9CzI0BngC5%1e=-t{VpyDSt>F&y&9ZPkyg>l9wj|{FE@Pb_Xf2V;Q8n;z%Z!;NK!St~M=$9EFo|yvU{5Bt-QZ;_KtG zGQ<4?omYvHi1~HpFI8pYjgVDPYk3N4k$mmrp;a-gVpulA9`2cYWH1dxKYw7~o8-;R zFMu~cs?D4K839JGa|WO}?*2s$%wSJyxChY^ChgQuZeUT8y=!RjvBU z65&i>ETX7U)zEA*J9?!R)FQ)v92aoK0<~B$s>j)hLPi7^D_*F@i+|H_D_Q9Xg}ETT zRs`n5Y6GDYaLzAV^l_BcQ8nmv{`jm>6U()}3pzX#}lTcKYT z^s{GvsQ6zd59PvLfE%+7_>`gkA#e(xskhq11FKDZU5KpR_BG-G#-z~3zSq`TddqVK zWflQtX4R(5x=a?C5Pv(M(!i90?*YL_N_nX*_>Ls_5%8lQ7qE>VL}pI1#q9+Qq<>Gv zNQ%O=S6W8L!_d)%ROOCr!zFUkO$X`NaFLBYWQM^)wa)3k+1vsegcdX25bxz!~<;MjlLV zX+xuHI%IPpX~FA?ZXie0`Q{VHlc@gPo>svi+B7+Xnk~ zGjRGMNs_nhEo|~j*K{Pz?gGqisEyeXENU4mdt@)24j_b00;@Xs_|KX@Ga2$WV92?A z3^DDU2t|Gc6!}>xiZJ!bHhLcedOT*)khRw2+^Vgl+(&{Y!G-y-r(9~bwlZCaAx*jw RxjIkS@(=gozeTsf-T;H&W)T1Y delta 2154 zcmV-w2$lEngkj`^VKPBhK_F9ZVQ_P3Z*(AbGYSI$$Yp;-l`Cb3TPdb9*youimWJcB z{hC*qs|`WtKL2oKkupmG7=eC;egTDk0)>79g?JCFUXJFBTD3Oy4dGjh(f|6hlF%Q$*gyMx5;|P*~guI3K9iX8VVXHNkc_J4<#KX z{{j;9NKm4A?atV)_YF(c3SZs;H-MXB$j*&*?={uhX8)g~;d>(9+O!BQ*4Si^NHZrsI5N%hUA#n20 zezmgbQ?~AN{1z)rQ#&BF)M(08Sv%t$#)qe@fPQPM=02Rrs7d zCF}ii%$+`R9OxzWfBEiA!}5$FTD(ZVmNSEBF-U&?*cz&7GLUGpt1V5sLatr$dM`E2#V(UAb@bcnFn)FE zzYWm4_dFha0&)yYg!^QYN=QlWdtXU_exWa*Sbs>=zj!nLvZtKtH2VRbJ*43dsmw%t zy5ATv-ku6}Ad6atr21-e ztLlRAtk1!d5cBz_1kbBF!b6lNf4`0LX|O_+&j-WC91IE3n$@0GhM^0dfZo-)_m^$( z1oY=$U)W{E5nuG$k`l|*r`w$^#h!LS*nfp!&4V!6#`KQ_`ky+2u3GI_RdBkp3FXSK zjck{0YFAH*W#4f7RY{BPQ6L+j)oSr2kJW+8`MkBf&s)fSbkW2fp=>KVB;Sb(P!v(4 zHo{+$n08)ABYdo0xWMqs(TY?r4jB*c<8~cT+d-(tcTYov$mPNn;uO#AK`(l)0e?O9 za{~*GX0IJs&5g^e;DY38o`3u;@#K$+CwX}i!A}T7Yj@xRJC21b9a~Yw4PK3; zy)d_gClT1`FQd>2>@oXm3-hjUC5~irasI92<7(Tn@KHD}#|wOtO@ddi!M;8|Ei*hg zGFg=ze&9L zMQ`5x&j>Jin=t^aF!wLK;Gr-8H~SYVR_Mx#a|A5R3KxN)1NZ%k4BC?6Y($1;lbrlj zT+o#ZhcOlGiUqo|V6q|80GAztg9C!9kgJO<9H}JvOCrhddXq#*JobU>D9Va15|}pg z(A=SE{ctpz(v-1DVQE5>LVsE0If>qqN#qAp$D(_URL*=Pai*s?XRhPe1)@)Uc5qX- zCc%0gJRu)8$Oj|vhJHN(5do0%YUK`Apy4AY3}|)c3zf3hOWB`}XfcH&r&{&3CBnJL zSVT}Gs-fAWcJxXs=tYJ@6&Fy&0=-x;sjKWnF(aaj6)*JS#aVePS%2vWjkzGaRz&8* zY6GD=Eu3bPUF^D31@^y6*#BH%Ul#14JI#+fbf@B`vDs85-B+OhlZ5^c75ZgCKYQlK zivM*yC|}+M_=;`7mlX9+C8zMEdaI2+u-e$y1 { + execSync( + 'node dist/src/cli.js --useTypeImports --declareExternallyReferenced false -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[] = []) { From 9b37b7337317dff2e710bde6d37d66a104ebd307 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 16:44:21 -0700 Subject: [PATCH 20/25] undo some changes --- src/cli.ts | 2 +- src/generator.ts | 14 ++++++++------ src/normalizer.ts | 6 ------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 696559b3..96038f43 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -24,7 +24,7 @@ main( 'strictIndexSignatures', 'unknownAny', 'unreachableDefinitions', - 'mirrorDir', + 'useTypeImports', ], default: DEFAULT_OPTIONS, string: ['bannerComment', 'cwd'], diff --git a/src/generator.ts b/src/generator.ts index 27d60600..a871d4b5 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -67,15 +67,17 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string, type = declareNamedInterfaces((ast as TArray).params, options, rootASTName, processed) break case 'INTERFACE': - const topLevelAst = + type = [ hasStandaloneName(ast) && - (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && - generateStandaloneInterface(ast, options) - const superTypesAndParams = getSuperTypesAndParams(ast) - .map(ast => declareNamedInterfaces(ast, options, rootASTName, processed)) + (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') - type = [topLevelAst, superTypesAndParams].filter(Boolean).join('\n') break case 'INTERSECTION': case 'TUPLE': diff --git a/src/normalizer.ts b/src/normalizer.ts index f3ce7533..55896143 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -86,8 +86,6 @@ rules.set('Add $refPath to schema', (schema, _fileName, _options, _key, derefere return } - // We'll infer from $id and title downstream - // TODO: Normalize upstream const refPath = dereferencedPaths.get(schema) if (refPath) { schema.$refPath = refPath @@ -116,10 +114,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 => { From 2bc6a18d6c44d1b7f7214ba89171c41a2ef5e7ce Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 1 Jun 2025 16:51:42 -0700 Subject: [PATCH 21/25] cleanup --- src/resolver.ts | 4 ++-- test/testNormalizer.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/resolver.ts b/src/resolver.ts index 3313669c..94dc5a0e 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -2,7 +2,7 @@ import {$RefParser, ParserOptions as $RefOptions} from '@apidevtools/json-schema import {JSONSchema} from './types/JSONSchema' import {log} from './utils' -export type DereferencedPaths = Map +export type DereferencedPaths = WeakMap export async function dereference( schema: JSONSchema, @@ -10,7 +10,7 @@ export async function dereference( ): Promise<{dereferencedPaths: DereferencedPaths; dereferencedSchema: JSONSchema}> { log('green', 'dereferencer', 'Dereferencing input schema:', cwd, schema) const parser = new $RefParser() - const dereferencedPaths: DereferencedPaths = new Map() + const dereferencedPaths: DereferencedPaths = new WeakMap() const dereferencedSchema = (await parser.dereference(cwd, schema, { ...$refOptions, dereference: { diff --git a/test/testNormalizer.ts b/test/testNormalizer.ts index d640b784..55fa6ca9 100644 --- a/test/testNormalizer.ts +++ b/test/testNormalizer.ts @@ -21,7 +21,7 @@ export function run() { .map(_ => [_, require(_)] as [string, JSONTestCase]) .forEach(([filename, json]: [string, JSONTestCase]) => { test(json.name, t => { - const normalized = normalize(link(json.in), new Map(), filename, json.options ?? DEFAULT_OPTIONS) + const normalized = normalize(link(json.in), new WeakMap(), filename, json.options ?? DEFAULT_OPTIONS) t.deepEqual(json.out, normalized) }) }) From beca685f9c7ac8947ee0f16edf6a6bbe628ff63a Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Mon, 2 Jun 2025 11:07:24 -0700 Subject: [PATCH 22/25] clean up options a bit --- src/cli.ts | 3 +++ src/index.ts | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 96038f43..aed548eb 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -187,6 +187,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/index.ts b/src/index.ts index 5d9673da..7f66055a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,6 +102,7 @@ export const DEFAULT_OPTIONS: Options = { */`, cwd: process.cwd(), declareExternallyReferenced: true, + useTypeImports: false, enableConstEnums: true, inferStringEnumKeysFromValues: false, format: true, @@ -118,7 +119,6 @@ export const DEFAULT_OPTIONS: Options = { useTabs: false, }, unreachableDefinitions: false, - useTypeImports: false, unknownAny: true, } @@ -140,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() { From 26d458401d41459b572157a15bb66af9e5c9e63b Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Mon, 2 Jun 2025 11:10:43 -0700 Subject: [PATCH 23/25] revert another thing --- src/cli.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index aed548eb..0922834e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -112,11 +112,9 @@ async function processDir(argIn: string, argOut: string | undefined, argv: Parti }), ) - const ext = argv.useTypeImports ? '.ts' : '.d.ts' - // careful to do this serially results.forEach(([file, result, outputPath]) => - outputResult(result, outputPath ? `${outputPath}/${justName(file)}${ext}` : undefined), + outputResult(result, outputPath ? `${outputPath}/${justName(file)}.d.ts` : undefined), ) } From db3f5102b59141bbce51cc5389abc16eca6f38e2 Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Mon, 2 Jun 2025 11:16:23 -0700 Subject: [PATCH 24/25] update test and snapshot --- src/generator.ts | 15 ++++++++++----- test/__snapshots__/test/test.ts.md | 6 +++--- test/__snapshots__/test/test.ts.snap | Bin 1589872 -> 1589878 bytes test/testCLI.ts | 4 +--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/generator.ts b/src/generator.ts index a871d4b5..0ff4f8b6 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -18,11 +18,16 @@ import {log, toSafeString} from './utils' import {basename, dirname, extname, join} from 'path' export function generate(ast: AST, options = DEFAULT_OPTIONS): string { - const namedTypes = declareNamedTypes(ast, options, ast.standaloneName!) - const namedInterfaces = declareNamedInterfaces(ast, options, ast.standaloneName!) - const enums = declareEnums(ast, options) - - return [options.bannerComment, namedTypes, namedInterfaces, enums].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()): string { diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index 45724b55..7e9c1d79 100644 --- a/test/__snapshots__/test/test.ts.md +++ b/test/__snapshots__/test/test.ts.md @@ -449780,7 +449780,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 - 'test/resources/mirror-dir/out/inner-dir/ReferencedTypeInInnerDir.ts' + 'test/resources/mirror-dir/out/inner-dir/ReferencedTypeInInnerDir.d.ts' > Snapshot 2 @@ -449800,7 +449800,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 3 - 'test/resources/mirror-dir/out/ReferencedType.ts' + 'test/resources/mirror-dir/out/ReferencedType.d.ts' > Snapshot 4 @@ -449827,7 +449827,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 5 - 'test/resources/mirror-dir/out/ReferencingType.ts' + 'test/resources/mirror-dir/out/ReferencingType.d.ts' > Snapshot 6 diff --git a/test/__snapshots__/test/test.ts.snap b/test/__snapshots__/test/test.ts.snap index de700fb10c2be82df2b0ea584f66fd6632226429..85125b897b5944781033d49d2f18778e64a84e89 100644 GIT binary patch delta 2283 zcmV_tx7%~5P9 z>COK@t7U8bEJqOQfii_M0fjOGg)#$$G6aP(1%)yOg)#?)G6;n-357BWg)$3;G7N<> z4TUleg)$F?G7yC_5rr}mg)$R`G8Ba}6@@Yug)$elG8peUf8-o=kDTo&k&Wf}d~vWs zLWqroWTIs6cF*2qZ^zk(eP4TO>Lpeq{#Dit_+H zQB1&18onqwe^9W&Ukaq!F?UTbZuHxrRdI%o!tu8)&3?FM>)DXn6r(lB@^r_Vne}yl|Q?Sv`hTQ33%hH^<@y~9J zH7w3E1dA6P*KF$Y#hMjo{DMKV$q1~&X2ltQti%f><5Y{*q&4e>kD1LlKh0z^)-1>P zxsm#6UL*CmDOw!#pJsMj8WObTiWX&LN$wMV$LK2&m(^U>qwzt;CON9&Qg z5z)tMe_sJAK2@8FlbovD?_+$wBD1Gb?P^XHT&h;O+EJwnTFo9|jOHsq7gZkl!$yOa zsLg!R-pNgj2;ey>EZ6U{hl|!r*8v%>sO@>g{s6Uo`CT_f&Ga49ph3CJP)R^^Hyk`z zLKTuQnH~zcy*-RA`jO@is$>ZE?O}4e6gNytf9`7bXf-UPF_}2`Nkqpt!T{bxYpIY! z58JNCqPqF5TsiA#1+^~LP9e1GknF=NXr6xSk|J#@XyIZ=-M4d7SE8pl`t|VsyT*ZG zePENEQ;|As4$$_DiaLzO9c{~^3IgKg?8j5?dB!o|*c^KPzGYCaIWkS?S`@R)d6TQmsU~P*}z!*Uqfw;IP^sYN@K}MJ;U~Pyn{A>YhqsQnfJ%nb8{t{ z6T)MV{(Q&iN7JMSXtJ#=P1dAbyYlsRVw#I>E?eg4x4OjnwZ{Ifq4wS5c<@QcaWF9% zlTIrk#kuc&4gmc`T|kMDn1As~_+=+@f2yW4f1L5LX^jYVQ~V6m}t#%PYYpKLyu8=dE)(L9X&>!$=4TtIpT;fcx_3D zW$Li)&T7P-c1qZ#V9h;0-p2Gde*pat6+w?$?RZsiwz5g(%C8Pgn{8@ar{uD4q-`ppIsUi9S``#pPW_|&@`B81&fBeq~Fnp6U0L^jt zFS6ia-$R?73neR5WyLuP7GZ^rQQy*b-I5H-lHqJXhI*Zz{FPi#l?w+U70i+as5ANroT*toDX zsY$LZ@`6NfKoYs$f5aY;sG`U8!y^6A^_;$2 zjX^{LdY5PWv`a9KON9wqz$Zc)mN4X=K^C9MUAS4dY#$PORb<5 z84jYjfJ+vr#e!iq%1#tABDh%cLM>jLg9ry-e;<|`2-Rs})$9D?nmvlZ z{wIL_4<+_R!CtdR*>TtGk-TZF*Q1i|N1*>bK>ynk{i2|sKJ!Dx|0;PX7w!Vwh;6{9 z4D}CzQ}|4^(Ig&NP2%f9WbHO@5)UvYg(mjBrq<9Ko+~J`04Ou7Hf7divdDzk1(gP- z6nqy5eyo(2f7*g?OM)K)Kl)J)+xX$w%!#+Sy@Y}E@2eO|k(>5P%jjenI=q~y+>vd# zL{7TtARQYnvayGZq3L>)YJUZ2pJt-=>0{jNYEG&CPg4C)3qZLHJnu=?N}goYah*7@ zhO;f4UQao?R{*+i#bZ0qK-HWQ*yjc;#{`^V&#Y&`e`HG=7#-6g8+_Qn0AQT~V4YGC zEE-sOjdvdA9+_@B5PerJG$*vsECbosGsvDWzgLPsPWNq*^= z4nXZ5WkBt=+Nd4Frk1j{uL1~L$q+)|09@ta$A8uYn#z%6O&r4!)83sR$*+JUKPyEN zrbO9x?_)raCoCqi(iop#o?BT?xQ~QRf(xy%rwq!L_C8ghAx*t8cC|(L@(+9VV@bWi F-T?8XXp{f| delta 2277 zcmV z4TUleg)$F?G7yC_5rr}mg)$R`G8Ba}6@@Yug)$elG8peUe{c@ENBoEq*%-&?i-Q#s zLSiJO5G8xJd-f)KJI+4rJCjHhh$0OI4V0vzqM(P8j)H=o3JH27DABxj=il4s*&z$F zO>(z4JD&Z&Z~mG6=YKq>hk0Q1v|cl9T3;nCz|-XSUVBD^MxnC3MWREJS7y+xJP*(p z#RS~6;fs<3e<8<%p6@w8>+(F0!Ivm9tP5G| z7;RLZv5R@Iq^BVu=D||QB;N|v&<(>ev7W7mSiAB(f*=nYS1XG?WgEWWPpj41OGQ9X zsB90=2q{EDwY6*p(Prg&06`!+tVR}pN;Y^wpGG59fB1qtB^&*0$ek{>EX_$8|LmGHe+8)cL~SZga;kQ3fbqSW%${1kr#W?Ssb1@8hqXFrHG7CLTBreCRD0-;S}j_l zHup(;w=gkcfaj#JT))R2E?zHP0c5zSw&xN11Jw5A_uLFMGjL3U2IVqCB>~ahbntK) z)kwl*dMM=fcQLl;N18jRmLu4=kIC&)+%PG*f2-N!wXl%JWa8Y%F&$qG19(%dr9uvU zY`Y$d>gKm{<*cJsG=Hvf0-+s;WFKBd3-nu$6lq&Ui|0b>zEzmI5EHTDhb z1DoWWiq&CrfVQVp)L}I4Xj>Ll5D+hCKb~^W6UBgIbKv=VmO;Jd$TXpAQNl9w9f0Vm zf4UG6iEU!s(MW1uT1DmdCdT6b2I^?Up)W2_8cR;-XC8n%$0af z2#-Pf^G#zAPm?~N$+ogIS(kF{%GW!oX)d<8Y?-6q+A`7`|m)o&Zf1PDNAhU-y+@zJ6sIQmiY0FYqvGY9mnchO2 z{9=->trZ3zA{|431oyqCfB;`8OMopsVM;$QW|;ai+R~4S6Oymqecv|wEGcKD;1lU^ z9g{bRc{!!}1YWES1&b6AziN<0kKk5q(GP6;F&f9y?r z&(AE;l)qN@?K?+=lmiGMU5tnH0)TW*ZAh<;B|DJ8EJL8a+Qh0lB|M80@Fc{1z6#)Z zQAK#D@)Ym4K|T#$i1KJKEKR_W5UpA5X(0^j=n?9yOuWBrphu`X`TD{y#~kqmuPrID zOkK9!S&P}zP6@jdthwta+nD|af1v-NBIt3e9j^+`RyL_z`K6(0vrX;%3AyYWX+ICN z=xqjM1GQQ$vE=bOa5?*1$lKpk?xWKt_VA&tY@dFoE_|HBju}|`yH8mWbYEq-dA;3M^Kc@!LX+~gz)gj32(k)N&` z2No$v9?z4%08f6ec#@YVe*yfIFsyb5DX?Q1q|&hwRNN5NNZM0#OX`Wj&VCvCmS^tq zzcw}R3RmJtCYRvfA~~)$ErT3|lXASsrP(Az^%~;qiIRx%NrW#WyH zRZwer3Tlyj?c_A+5hlQI&Fa(S;+GB!2>u{H8WZq{L(I*;Y(h(P;wHBAwY%)7~r4`g7 z!+snWaK!?(STL%`*@;3%1Q#n_sKtxZa4T8q2!**Iy;cO~f5U16p*k(BW|LoBx5qKq z{{*oAp~Ai_*lYGUKkk}6mN$*fW?a(!81%mf=zm+GUl#PUXMU*oUnURb!d-wHvkmx^ zq5dIo3ZJRB+Qb8^O?+L5tljoC;sM5_(8j*k)>?YYa|LA<0cB>@rp&rb7MTz`pwhsU zg6{#rM@o6Ae=YcqB=`~VqaPQrjUPm2PO`=A1q`HrPsK=z!n9XfM#sa@(S=myj%>pv za?(u)>DX|QjXh+HOxK%K`>R0vv{SXu0ONK~b1LV0^hrh?*NFpb zINQSM^@O8)5up2KGPVm0RL!Y?eP+ONOu!lT%tjtee{N|*qiZ^3gAW@R0IX90tP?7N zMFT4@^UlNEBhyU>qVKAO=9m_m6(IX&4%rjt_bc%yiGQPz_%8wRFXR;ez&|`}>lFrx zZd>^<5N1!Roy;NWL^6DRmTO@cnvR2=rf#zRmucGu`*t&M`XWh^x9lzQOV@NH%zf*5ur(t)$#Xf+oR*`LL&4YPPmAU5Ft~x)HfLPuTJg_u{`rx53^3)09_% diff --git a/test/testCLI.ts b/test/testCLI.ts index 66a9fbcc..70ca8ac4 100644 --- a/test/testCLI.ts +++ b/test/testCLI.ts @@ -139,9 +139,7 @@ export function run() { }) test('type imports', t => { - execSync( - 'node dist/src/cli.js --useTypeImports --declareExternallyReferenced false -i ./test/resources/mirror-dir -o ./test/resources/mirror-dir/out', - ) + 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')) From adac127b19bcae4745918c44abe1368b51b65d9b Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Mon, 2 Jun 2025 12:44:19 -0700 Subject: [PATCH 25/25] rm $refPath thing --- src/normalizer.ts | 15 --------------- src/typesOfSchema.ts | 4 +--- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/normalizer.ts b/src/normalizer.ts index 55896143..c2588a8a 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -77,21 +77,6 @@ rules.set('Transform id to $id', (schema, fileName) => { } }) -rules.set('Add $refPath to schema', (schema, _fileName, _options, _key, dereferencedPaths) => { - if (!isSchemaLike(schema)) { - return - } - - if (schema.$refPath) { - return - } - - const refPath = dereferencedPaths.get(schema) - if (refPath) { - schema.$refPath = refPath - } -}) - rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _key, dereferencedPaths) => { if (!isSchemaLike(schema)) { return diff --git a/src/typesOfSchema.ts b/src/typesOfSchema.ts index b7701e4d..2d8b31be 100644 --- a/src/typesOfSchema.ts +++ b/src/typesOfSchema.ts @@ -36,9 +36,7 @@ const matchers: Record boolean> = { return 'allOf' in schema }, ANY(schema) { - // We add a single key. Remove that one. - const nativeKeys = Object.keys(schema).filter(key => key !== '$refPath') - if (nativeKeys.length === 0) { + if (Object.keys(schema).length === 0) { // The empty schema {} validates any value // @see https://json-schema.org/draft-07/json-schema-core.html#rfc.section.4.3.1 return true