Skip to content

Commit b724360

Browse files
committed
bugfix: make declareExternallyReferenced option work consistently
1 parent 74d6126 commit b724360

File tree

7 files changed

+85
-51
lines changed

7 files changed

+85
-51
lines changed

src/generator.ts

+15-10
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ function declareEnums(ast: AST, options: Options, processed = new Set<AST>()): s
3535
}
3636

3737
processed.add(ast)
38-
let type = ''
38+
39+
if (ast.isExternalSchema && !options.declareExternallyReferenced) {
40+
return ''
41+
}
3942

4043
switch (ast.type) {
4144
case 'ENUM':
@@ -46,7 +49,7 @@ function declareEnums(ast: AST, options: Options, processed = new Set<AST>()): s
4649
case 'INTERSECTION':
4750
return ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
4851
case 'TUPLE':
49-
type = ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
52+
let type = ast.params.reduce((prev, ast) => prev + declareEnums(ast, options, processed), '')
5053
if (ast.spreadParam) {
5154
type += declareEnums(ast.spreadParam, options, processed)
5255
}
@@ -66,15 +69,17 @@ function declareNamedInterfaces(ast: AST, options: Options, rootASTName: string,
6669
processed.add(ast)
6770
let type = ''
6871

72+
if (ast.isExternalSchema && !options.declareExternallyReferenced) {
73+
return ''
74+
}
75+
6976
switch (ast.type) {
7077
case 'ARRAY':
7178
type = declareNamedInterfaces((ast as TArray).params, options, rootASTName, processed)
7279
break
7380
case 'INTERFACE':
7481
type = [
75-
hasStandaloneName(ast) &&
76-
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
77-
generateStandaloneInterface(ast, options),
82+
hasStandaloneName(ast) && generateStandaloneInterface(ast, options),
7883
getSuperTypesAndParams(ast)
7984
.map(ast => declareNamedInterfaces(ast, options, rootASTName, processed))
8085
.filter(Boolean)
@@ -108,6 +113,10 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc
108113

109114
processed.add(ast)
110115

116+
if (ast.isExternalSchema && !options.declareExternallyReferenced) {
117+
return ''
118+
}
119+
111120
switch (ast.type) {
112121
case 'ARRAY':
113122
return [
@@ -120,11 +129,7 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc
120129
return ''
121130
case 'INTERFACE':
122131
return getSuperTypesAndParams(ast)
123-
.map(
124-
ast =>
125-
(ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
126-
declareNamedTypes(ast, options, rootASTName, processed),
127-
)
132+
.map(ast => declareNamedTypes(ast, options, rootASTName, processed))
128133
.filter(Boolean)
129134
.join('\n')
130135
case 'INTERSECTION':

src/normalizer.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
1+
import {IsExternalSchema, JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
22
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
33
import {Options} from './'
44
import {DereferencedPaths} from './resolver'
@@ -73,6 +73,27 @@ rules.set('Transform id to $id', (schema, fileName) => {
7373
}
7474
})
7575

76+
rules.set(
77+
'Add an ExternalRef flag to anything that needs it',
78+
(schema, _fileName, _options, _key, dereferencedPaths) => {
79+
if (!isSchemaLike(schema)) {
80+
return
81+
}
82+
83+
// Top-level schema
84+
if (!schema[Parent]) {
85+
return
86+
}
87+
88+
const dereferencedName = dereferencedPaths.get(schema)
89+
Object.defineProperty(schema, IsExternalSchema, {
90+
enumerable: false,
91+
value: dereferencedName && !dereferencedName.startsWith('#'),
92+
writable: false,
93+
})
94+
},
95+
)
96+
7697
rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _key, dereferencedPaths) => {
7798
if (!isSchemaLike(schema)) {
7899
return
@@ -95,10 +116,6 @@ rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _
95116
if (!schema.$id && !schema.title && dereferencedName) {
96117
schema.$id = toSafeString(justName(dereferencedName))
97118
}
98-
99-
if (dereferencedName) {
100-
dereferencedPaths.delete(schema)
101-
}
102119
})
103120

104121
rules.set('Escape closing JSDoc comment', schema => {

src/parser.ts

+34-16
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
Parent,
2626
SchemaSchema,
2727
SchemaType,
28+
IsExternalSchema,
2829
} from './types/JSONSchema'
2930
import {generateName, log, maybeStripDefault, maybeStripNameHints} from './utils'
3031

@@ -56,22 +57,17 @@ export function parse(
5657

5758
// Be careful to first process the intersection before processing its params,
5859
// so that it gets first pick for standalone name.
59-
const ast = parseAsTypeWithCache(
60-
{
61-
[Parent]: schema[Parent],
62-
$id: schema.$id,
63-
additionalProperties: schema.additionalProperties,
64-
allOf: [],
65-
description: schema.description,
66-
required: schema.required,
67-
title: schema.title,
68-
},
69-
'ALL_OF',
70-
options,
71-
keyName,
72-
processed,
73-
usedNames,
74-
) as TIntersection
60+
const allOf: NormalizedJSONSchema = {
61+
[IsExternalSchema]: schema[IsExternalSchema],
62+
[Parent]: schema[Parent],
63+
$id: schema.$id,
64+
allOf: [],
65+
description: schema.description,
66+
title: schema.title,
67+
additionalProperties: schema.additionalProperties,
68+
required: schema.required,
69+
}
70+
const ast = parseAsTypeWithCache(allOf, 'ALL_OF', options, keyName, processed, usedNames) as TIntersection
7571

7672
ast.params = types.map(type =>
7773
// We hoist description (for comment) and id/title (for standaloneName)
@@ -116,19 +112,22 @@ function parseAsTypeWithCache(
116112
function parseBooleanSchema(schema: boolean, keyName: string | undefined, options: Options): AST {
117113
if (schema) {
118114
return {
115+
isExternalSchema: false,
119116
keyName,
120117
type: options.unknownAny ? 'UNKNOWN' : 'ANY',
121118
}
122119
}
123120

124121
return {
122+
isExternalSchema: false,
125123
keyName,
126124
type: 'NEVER',
127125
}
128126
}
129127

130128
function parseLiteral(schema: JSONSchema4Type, keyName: string | undefined): AST {
131129
return {
130+
isExternalSchema: false,
132131
keyName,
133132
params: schema,
134133
type: 'LITERAL',
@@ -151,6 +150,7 @@ function parseNonLiteral(
151150
return {
152151
comment: schema.description,
153152
deprecated: schema.deprecated,
153+
isExternalSchema: schema[IsExternalSchema],
154154
keyName,
155155
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
156156
params: schema.allOf!.map(_ => parse(_, options, undefined, processed, usedNames)),
@@ -161,13 +161,15 @@ function parseNonLiteral(
161161
...(options.unknownAny ? T_UNKNOWN : T_ANY),
162162
comment: schema.description,
163163
deprecated: schema.deprecated,
164+
isExternalSchema: schema[IsExternalSchema],
164165
keyName,
165166
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
166167
}
167168
case 'ANY_OF':
168169
return {
169170
comment: schema.description,
170171
deprecated: schema.deprecated,
172+
isExternalSchema: schema[IsExternalSchema],
171173
keyName,
172174
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
173175
params: schema.anyOf!.map(_ => parse(_, options, undefined, processed, usedNames)),
@@ -177,6 +179,7 @@ function parseNonLiteral(
177179
return {
178180
comment: schema.description,
179181
deprecated: schema.deprecated,
182+
isExternalSchema: schema[IsExternalSchema],
180183
keyName,
181184
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
182185
type: 'BOOLEAN',
@@ -185,6 +188,7 @@ function parseNonLiteral(
185188
return {
186189
comment: schema.description,
187190
deprecated: schema.deprecated,
191+
isExternalSchema: schema[IsExternalSchema],
188192
keyName,
189193
params: schema.tsType!,
190194
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
@@ -194,6 +198,7 @@ function parseNonLiteral(
194198
return {
195199
comment: schema.description,
196200
deprecated: schema.deprecated,
201+
isExternalSchema: schema[IsExternalSchema],
197202
keyName,
198203
standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames, options)!,
199204
params: (schema as EnumJSONSchema).enum!.map((_, n) => ({
@@ -208,6 +213,7 @@ function parseNonLiteral(
208213
return {
209214
comment: schema.description,
210215
deprecated: schema.deprecated,
216+
isExternalSchema: schema[IsExternalSchema],
211217
keyName,
212218
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
213219
type: 'NEVER',
@@ -216,6 +222,7 @@ function parseNonLiteral(
216222
return {
217223
comment: schema.description,
218224
deprecated: schema.deprecated,
225+
isExternalSchema: schema[IsExternalSchema],
219226
keyName,
220227
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
221228
type: 'NULL',
@@ -224,13 +231,15 @@ function parseNonLiteral(
224231
return {
225232
comment: schema.description,
226233
deprecated: schema.deprecated,
234+
isExternalSchema: schema[IsExternalSchema],
227235
keyName,
228236
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
229237
type: 'NUMBER',
230238
}
231239
case 'OBJECT':
232240
return {
233241
comment: schema.description,
242+
isExternalSchema: schema[IsExternalSchema],
234243
keyName,
235244
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
236245
type: 'OBJECT',
@@ -240,6 +249,7 @@ function parseNonLiteral(
240249
return {
241250
comment: schema.description,
242251
deprecated: schema.deprecated,
252+
isExternalSchema: schema[IsExternalSchema],
243253
keyName,
244254
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
245255
params: schema.oneOf!.map(_ => parse(_, options, undefined, processed, usedNames)),
@@ -251,6 +261,7 @@ function parseNonLiteral(
251261
return {
252262
comment: schema.description,
253263
deprecated: schema.deprecated,
264+
isExternalSchema: schema[IsExternalSchema],
254265
keyName,
255266
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
256267
type: 'STRING',
@@ -263,6 +274,7 @@ function parseNonLiteral(
263274
const arrayType: TTuple = {
264275
comment: schema.description,
265276
deprecated: schema.deprecated,
277+
isExternalSchema: schema[IsExternalSchema],
266278
keyName,
267279
maxItems,
268280
minItems,
@@ -280,6 +292,7 @@ function parseNonLiteral(
280292
return {
281293
comment: schema.description,
282294
deprecated: schema.deprecated,
295+
isExternalSchema: schema[IsExternalSchema],
283296
keyName,
284297
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
285298
params: parse(schema.items!, options, `{keyNameFromDefinition}Items`, processed, usedNames),
@@ -290,6 +303,7 @@ function parseNonLiteral(
290303
return {
291304
comment: schema.description,
292305
deprecated: schema.deprecated,
306+
isExternalSchema: schema[IsExternalSchema],
293307
keyName,
294308
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
295309
params: (schema.type as JSONSchema4TypeName[]).map(type => {
@@ -307,6 +321,7 @@ function parseNonLiteral(
307321
return {
308322
comment: schema.description,
309323
deprecated: schema.deprecated,
324+
isExternalSchema: schema[IsExternalSchema],
310325
keyName,
311326
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
312327
params: (schema as EnumJSONSchema).enum!.map(_ => parseLiteral(_, undefined)),
@@ -323,6 +338,7 @@ function parseNonLiteral(
323338
return {
324339
comment: schema.description,
325340
deprecated: schema.deprecated,
341+
isExternalSchema: schema[IsExternalSchema],
326342
keyName,
327343
maxItems: schema.maxItems,
328344
minItems,
@@ -338,6 +354,7 @@ function parseNonLiteral(
338354
return {
339355
comment: schema.description,
340356
deprecated: schema.deprecated,
357+
isExternalSchema: schema[IsExternalSchema],
341358
keyName,
342359
params,
343360
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
@@ -374,6 +391,7 @@ function newInterface(
374391
return {
375392
comment: schema.description,
376393
deprecated: schema.deprecated,
394+
isExternalSchema: schema[IsExternalSchema],
377395
keyName,
378396
params: parseSchema(schema, options, processed, usedNames, name),
379397
standaloneName: name,

src/types/AST.ts

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type AST =
2424

2525
export interface AbstractAST {
2626
comment?: string
27+
isExternalSchema: boolean
2728
keyName?: string
2829
standaloneName?: string
2930
type: AST_TYPE
@@ -154,18 +155,22 @@ export interface TCustomType extends AbstractAST {
154155

155156
export const T_ANY: TAny = {
156157
type: 'ANY',
158+
isExternalSchema: false,
157159
}
158160

159161
export const T_ANY_ADDITIONAL_PROPERTIES: TAny & ASTWithName = {
160162
keyName: '[k: string]',
161163
type: 'ANY',
164+
isExternalSchema: false,
162165
}
163166

164167
export const T_UNKNOWN: TUnknown = {
165168
type: 'UNKNOWN',
169+
isExternalSchema: false,
166170
}
167171

168172
export const T_UNKNOWN_ADDITIONAL_PROPERTIES: TUnknown & ASTWithName = {
169173
keyName: '[k: string]',
170174
type: 'UNKNOWN',
175+
isExternalSchema: false,
171176
}

src/types/JSONSchema.ts

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface JSONSchema extends JSONSchema4 {
4040
deprecated?: boolean
4141
}
4242

43+
export const IsExternalSchema = Symbol('IsExternalSchema')
4344
export const Parent = Symbol('Parent')
4445

4546
export interface LinkedJSONSchema extends JSONSchema {
@@ -77,6 +78,10 @@ export interface LinkedJSONSchema extends JSONSchema {
7778
*/
7879
export interface NormalizedJSONSchema extends Omit<LinkedJSONSchema, 'definitions' | 'id'> {
7980
[Parent]: NormalizedJSONSchema | null
81+
/**
82+
* Indicates whether this schema was an external $ref.
83+
*/
84+
[IsExternalSchema]: boolean
8085

8186
additionalItems?: boolean | NormalizedJSONSchema
8287
additionalProperties: boolean | NormalizedJSONSchema

0 commit comments

Comments
 (0)