Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] bugfix: make declareExternallyReferenced option work consistently (fix #311, fix #525) #608

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
50 changes: 34 additions & 16 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
Parent,
SchemaSchema,
SchemaType,
IsExternalSchema,
} from './types/JSONSchema'
import {generateName, log, maybeStripDefault, maybeStripNameHints} from './utils'

Expand Down Expand Up @@ -56,22 +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(
{
[Parent]: schema[Parent],
$id: schema.$id,
additionalProperties: schema.additionalProperties,
allOf: [],
description: schema.description,
required: schema.required,
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 Down Expand Up @@ -116,19 +112,22 @@ 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',
Expand All @@ -151,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 @@ -161,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 @@ -177,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 @@ -185,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 @@ -194,6 +198,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames, options)!,
params: (schema as EnumJSONSchema).enum!.map((_, n) => ({
Expand All @@ -208,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 @@ -216,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 @@ -224,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 @@ -240,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 @@ -251,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 @@ -263,6 +274,7 @@ function parseNonLiteral(
const arrayType: TTuple = {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
maxItems,
minItems,
Expand All @@ -280,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 @@ -290,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 @@ -307,6 +321,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: (schema as EnumJSONSchema).enum!.map(_ => parseLiteral(_, undefined)),
Expand All @@ -323,6 +338,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
maxItems: schema.maxItems,
minItems,
Expand All @@ -338,6 +354,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 @@ -374,6 +391,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,
}
5 changes: 5 additions & 0 deletions src/types/JSONSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface JSONSchema extends JSONSchema4 {
deprecated?: boolean
}

export const IsExternalSchema = Symbol('IsExternalSchema')
export const Parent = Symbol('Parent')

export interface LinkedJSONSchema extends JSONSchema {
Expand Down Expand Up @@ -77,6 +78,10 @@ export interface LinkedJSONSchema extends JSONSchema {
*/
export interface NormalizedJSONSchema extends Omit<LinkedJSONSchema, 'definitions' | 'id'> {
[Parent]: NormalizedJSONSchema | null
/**
* Indicates whether this schema was an external $ref.
*/
[IsExternalSchema]: boolean

additionalItems?: boolean | NormalizedJSONSchema
additionalProperties: boolean | NormalizedJSONSchema
Expand Down
Loading