Skip to content

Commit

Permalink
bugfix: make declareExternallyReferenced option work consistently
Browse files Browse the repository at this point in the history
  • Loading branch information
bcherny committed Jun 25, 2024
1 parent 31993de commit 542eca0
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 60 deletions.
25 changes: 15 additions & 10 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ function declareEnums(ast: AST, options: Options, processed = new Set<AST>()): s
}

processed.add(ast)
let type = ''

if (ast.isExternalSchema && !options.declareExternallyReferenced) {
return ''
}

switch (ast.type) {
case 'ENUM':
Expand All @@ -46,7 +49,7 @@ function declareEnums(ast: AST, options: Options, processed = new Set<AST>()): s
case 'INTERSECTION':
return ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
case 'TUPLE':
type = ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
let type = ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
if (ast.spreadParam) {
type += declareEnums(ast.spreadParam, options, processed)
}
Expand All @@ -66,15 +69,17 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string,
processed.add(ast)
let type = ''

if (ast.isExternalSchema && !options.declareExternallyReferenced) {
return ''
}

switch (ast.type) {
case 'ARRAY':
type = declareNamedInterfaces((ast as TArray).params, options, rootASTName, processed)
break
case 'INTERFACE':
type = [
hasStandaloneName(ast) &&
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
generateStandaloneInterface(ast, options),
hasStandaloneName(ast) && generateStandaloneInterface(ast, options),
getSuperTypesAndParams(ast)
.map(ast => declareNamedInterfaces(ast, options, rootASTName, processed))
.filter(Boolean)
Expand Down Expand Up @@ -108,6 +113,10 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc

processed.add(ast)

if (ast.isExternalSchema && !options.declareExternallyReferenced) {
return ''
}

switch (ast.type) {
case 'ARRAY':
return [
Expand All @@ -120,11 +129,7 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc
return ''
case 'INTERFACE':
return getSuperTypesAndParams(ast)
.map(
ast =>
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
declareNamedTypes(ast, options, rootASTName, processed),
)
.map(ast => declareNamedTypes(ast, options, rootASTName, processed))
.filter(Boolean)
.join('\n')
case 'INTERSECTION':
Expand Down
27 changes: 22 additions & 5 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
import {IsExternalSchema, JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
import {Options} from './'
import {DereferencedPaths} from './resolver'
Expand Down Expand Up @@ -73,6 +73,27 @@ rules.set('Transform id to $id', (schema, fileName) => {
}
})

rules.set(
'Add an ExternalRef flag to anything that needs it',
(schema, _fileName, _options, _key, dereferencedPaths) => {
if (!isSchemaLike(schema)) {
return
}

// Top-level schema
if (!schema[Parent]) {
return
}

const dereferencedName = dereferencedPaths.get(schema)
Object.defineProperty(schema, IsExternalSchema, {
enumerable: false,
value: dereferencedName && !dereferencedName.startsWith('#'),
writable: false,
})
},
)

rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _key, dereferencedPaths) => {
if (!isSchemaLike(schema)) {
return
Expand All @@ -95,10 +116,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 => {
Expand Down
59 changes: 41 additions & 18 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
JSONSchemaWithDefinitions,
SchemaSchema,
SchemaType,
IsExternalSchema,
NormalizedJSONSchema,
Parent,
} from './types/JSONSchema'
import {generateName, log, maybeStripDefault, maybeStripNameHints} from './utils'

Expand All @@ -31,7 +34,7 @@ export type Processed = Map<LinkedJSONSchema, Map<SchemaType, AST>>
export type UsedNames = Set<string>

export function parse(
schema: LinkedJSONSchema | JSONSchema4Type,
schema: NormalizedJSONSchema | JSONSchema4Type,
options: Options,
keyName?: string,
processed: Processed = new Map(),
Expand All @@ -54,19 +57,17 @@ export function parse(

// Be careful to first process the intersection before processing its params,
// so that it gets first pick for standalone name.
const ast = parseAsTypeWithCache(
{
$id: schema.$id,
allOf: [],
description: schema.description,
title: schema.title,
},
'ALL_OF',
options,
keyName,
processed,
usedNames,
) as TIntersection
const allOf: NormalizedJSONSchema = {
[IsExternalSchema]: schema[IsExternalSchema],
[Parent]: schema[Parent],
$id: schema.$id,
allOf: [],
description: schema.description,
title: schema.title,
additionalProperties: schema.additionalProperties,
required: schema.required,
}
const ast = parseAsTypeWithCache(allOf, 'ALL_OF', options, keyName, processed, usedNames) as TIntersection

ast.params = types.map(type =>
// We hoist description (for comment) and id/title (for standaloneName)
Expand All @@ -79,7 +80,7 @@ export function parse(
}

function parseAsTypeWithCache(
schema: LinkedJSONSchema,
schema: NormalizedJSONSchema,
type: SchemaType,
options: Options,
keyName?: string,
Expand Down Expand Up @@ -111,27 +112,30 @@ function parseAsTypeWithCache(
function parseBooleanSchema(schema: boolean, keyName: string | undefined, options: Options): AST {
if (schema) {
return {
isExternalSchema: false,
keyName,
type: options.unknownAny ? 'UNKNOWN' : 'ANY',
}
}

return {
isExternalSchema: false,
keyName,
type: 'NEVER',
}
}

function parseLiteral(schema: JSONSchema4Type, keyName: string | undefined): AST {
return {
isExternalSchema: false,
keyName,
params: schema,
type: 'LITERAL',
}
}

function parseNonLiteral(
schema: LinkedJSONSchema,
schema: NormalizedJSONSchema,
type: SchemaType,
options: Options,
keyName: string | undefined,
Expand All @@ -146,6 +150,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.allOf!.map(_ => parse(_, options, undefined, processed, usedNames)),
Expand All @@ -156,13 +161,15 @@ function parseNonLiteral(
...(options.unknownAny ? T_UNKNOWN : T_ANY),
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
}
case 'ANY_OF':
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.anyOf!.map(_ => parse(_, options, undefined, processed, usedNames)),
Expand All @@ -172,6 +179,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: 'BOOLEAN',
Expand All @@ -180,6 +188,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
params: schema.tsType!,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
Expand All @@ -189,9 +198,10 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames, options)!,
params: schema.enum!.map((_, n) => ({
params: schema.enum!.map((_: any, n: number) => ({
ast: parseLiteral(_, undefined),
keyName: schema.tsEnumNames![n],
})),
Expand All @@ -203,6 +213,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: 'NEVER',
Expand All @@ -211,6 +222,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: 'NULL',
Expand All @@ -219,13 +231,15 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: 'NUMBER',
}
case 'OBJECT':
return {
comment: schema.description,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: 'OBJECT',
Expand All @@ -235,6 +249,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.oneOf!.map(_ => parse(_, options, undefined, processed, usedNames)),
Expand All @@ -246,6 +261,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: 'STRING',
Expand All @@ -258,6 +274,7 @@ function parseNonLiteral(
const arrayType: TTuple = {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
maxItems,
minItems,
Expand All @@ -275,6 +292,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: parse(schema.items!, options, `{keyNameFromDefinition}Items`, processed, usedNames),
Expand All @@ -285,6 +303,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: (schema.type as JSONSchema4TypeName[]).map(type => {
Expand All @@ -297,9 +316,10 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.enum!.map(_ => parseLiteral(_, undefined)),
params: schema.enum!.map((_: any) => parseLiteral(_, undefined)),
type: 'UNION',
}
case 'UNNAMED_SCHEMA':
Expand All @@ -313,6 +333,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
maxItems: schema.maxItems,
minItems,
Expand All @@ -328,6 +349,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
Expand Down Expand Up @@ -364,6 +386,7 @@ function newInterface(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
params: parseSchema(schema, options, processed, usedNames, name),
standaloneName: name,
Expand Down
5 changes: 5 additions & 0 deletions src/types/AST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type AST =

export interface AbstractAST {
comment?: string
isExternalSchema: boolean
keyName?: string
standaloneName?: string
type: AST_TYPE
Expand Down Expand Up @@ -154,18 +155,22 @@ export interface TCustomType extends AbstractAST {

export const T_ANY: TAny = {
type: 'ANY',
isExternalSchema: false,
}

export const T_ANY_ADDITIONAL_PROPERTIES: TAny & ASTWithName = {
keyName: '[k: string]',
type: 'ANY',
isExternalSchema: false,
}

export const T_UNKNOWN: TUnknown = {
type: 'UNKNOWN',
isExternalSchema: false,
}

export const T_UNKNOWN_ADDITIONAL_PROPERTIES: TUnknown & ASTWithName = {
keyName: '[k: string]',
type: 'UNKNOWN',
isExternalSchema: false,
}
Loading

0 comments on commit 542eca0

Please sign in to comment.