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
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
@@ -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':
@@ -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)
}
@@ -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)
@@ -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 [
@@ -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':
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'
@@ -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
@@ -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 => {
50 changes: 34 additions & 16 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import {
Parent,
SchemaSchema,
SchemaType,
IsExternalSchema,
} from './types/JSONSchema'
import {generateName, log, maybeStripDefault, maybeStripNameHints} from './utils'

@@ -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)
@@ -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',
@@ -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)),
@@ -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)),
@@ -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',
@@ -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),
@@ -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) => ({
@@ -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',
@@ -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',
@@ -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',
@@ -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)),
@@ -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',
@@ -263,6 +274,7 @@ function parseNonLiteral(
const arrayType: TTuple = {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
maxItems,
minItems,
@@ -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),
@@ -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 => {
@@ -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)),
@@ -323,6 +338,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
maxItems: schema.maxItems,
minItems,
@@ -338,6 +354,7 @@ function parseNonLiteral(
return {
comment: schema.description,
deprecated: schema.deprecated,
isExternalSchema: schema[IsExternalSchema],
keyName,
params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
@@ -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,
5 changes: 5 additions & 0 deletions src/types/AST.ts
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ export type AST =

export interface AbstractAST {
comment?: string
isExternalSchema: boolean
keyName?: string
standaloneName?: string
type: AST_TYPE
@@ -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
@@ -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 {
@@ -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
Loading
Oops, something went wrong.