From 106aca2b6f4f383518e981d1a5507bdc8ba4bb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Tue, 17 Oct 2023 09:03:23 +0200 Subject: [PATCH 01/12] fix: Referencer, Name conflicts, Mapped unions --- .../cli/src/metadataGeneration/exceptions.ts | 20 +- .../metadataGeneration/metadataGenerator.ts | 35 +- .../src/metadataGeneration/typeResolver.ts | 986 +++++--- packages/cli/src/utils/validatorUtils.ts | 6 +- tests/fixtures/testModel.ts | 307 ++- .../definitionsGeneration/definitions.spec.ts | 1762 +++++++++++++-- tests/unit/swagger/schemaDetails3.spec.ts | 1979 +++++++++++++++-- 7 files changed, 4439 insertions(+), 656 deletions(-) diff --git a/packages/cli/src/metadataGeneration/exceptions.ts b/packages/cli/src/metadataGeneration/exceptions.ts index 7a24976da..ae10a6441 100644 --- a/packages/cli/src/metadataGeneration/exceptions.ts +++ b/packages/cli/src/metadataGeneration/exceptions.ts @@ -11,7 +11,7 @@ export class GenerateMetadataError extends Error { } export class GenerateMetaDataWarning { - constructor(private message: string, private node: Node | TypeNode, private onlyCurrent = false) {} + constructor(private message: string, private node: Node | TypeNode, private onlyCurrent = false) { } toString() { return `Warning: ${this.message}\n${prettyLocationOfNode(this.node)}\n${prettyTroubleCause(this.node, this.onlyCurrent)}`; @@ -20,19 +20,23 @@ export class GenerateMetaDataWarning { export function prettyLocationOfNode(node: Node | TypeNode) { const sourceFile = node.getSourceFile(); - const token = node.getFirstToken() || node.parent.getFirstToken(); - const start = token ? `:${sourceFile.getLineAndCharacterOfPosition(token.getStart()).line + 1}` : ''; - const end = token ? `:${sourceFile.getLineAndCharacterOfPosition(token.getEnd()).line + 1}` : ''; - const normalizedPath = normalize(`${sourceFile.fileName}${start}${end}`); - return `At: ${normalizedPath}.`; + if (sourceFile) { + const token = node.getFirstToken() || node.parent.getFirstToken(); + const start = token ? `:${sourceFile.getLineAndCharacterOfPosition(token.getStart()).line + 1}` : ''; + const end = token ? `:${sourceFile.getLineAndCharacterOfPosition(token.getEnd()).line + 1}` : ''; + const normalizedPath = normalize(`${sourceFile.fileName}${start}${end}`); + return `At: ${normalizedPath}.`; + } else { + return `At unknown position...`; + } } export function prettyTroubleCause(node: Node | TypeNode, onlyCurrent = false) { let name: string; if (onlyCurrent || !node.parent) { - name = node.pos !== -1 ? node.getText() : (node as any).name.text; + name = node.pos !== -1 ? node.getText() : ((node as any).name?.text || ''); } else { - name = node.parent.pos !== -1 ? node.parent.getText() : (node as any).parent.name.text; + name = node.parent.pos !== -1 ? node.parent.getText() : ((node as any).parent.name?.text || ''); } return `This was caused by '${name}'`; } diff --git a/packages/cli/src/metadataGeneration/metadataGenerator.ts b/packages/cli/src/metadataGeneration/metadataGenerator.ts index 0a36d2ce3..80b1b9eac 100644 --- a/packages/cli/src/metadataGeneration/metadataGenerator.ts +++ b/packages/cli/src/metadataGeneration/metadataGenerator.ts @@ -1,18 +1,19 @@ +import { Config, Tsoa } from '@tsoa/runtime'; import { minimatch } from 'minimatch'; +import { createProgram, forEachChild, isClassDeclaration, type ClassDeclaration, type CompilerOptions, type Program, type TypeChecker } from 'typescript'; +import { getDecorators } from '../utils/decoratorUtils'; import { importClassesFromDirectories } from '../utils/importClassesFromDirectories'; import { ControllerGenerator } from './controllerGenerator'; import { GenerateMetadataError } from './exceptions'; -import { Config, Tsoa } from '@tsoa/runtime'; import { TypeResolver } from './typeResolver'; -import { getDecorators } from '../utils/decoratorUtils'; -import { type TypeChecker, type Program, type ClassDeclaration, type CompilerOptions, createProgram, forEachChild, isClassDeclaration } from 'typescript'; export class MetadataGenerator { public readonly controllerNodes = new Array(); public readonly typeChecker: TypeChecker; private readonly program: Program; private referenceTypeMap: Tsoa.ReferenceTypeMap = {}; - private circularDependencyResolvers = new Array<(referenceTypes: Tsoa.ReferenceTypeMap) => void>(); + private modelDefinitionPosMap: { [name: string]: { fileName: string, pos: number }[] } = {}; + private expressionOrigNameMap: Record = {}; constructor( entryFile: string, @@ -35,7 +36,6 @@ export class MetadataGenerator { this.checkForMethodSignatureDuplicates(controllers); this.checkForPathParamSignatureDuplicates(controllers); - this.circularDependencyResolvers.forEach(c => c(this.referenceTypeMap)); return { controllers, @@ -214,17 +214,34 @@ export class MetadataGenerator { public AddReferenceType(referenceType: Tsoa.ReferenceType) { if (!referenceType.refName) { - return; + throw new Error('no reference type name found'); } - this.referenceTypeMap[decodeURIComponent(referenceType.refName)] = referenceType; + this.referenceTypeMap[referenceType.refName] = referenceType; } public GetReferenceType(refName: string) { return this.referenceTypeMap[refName]; } - public OnFinish(callback: (referenceTypes: Tsoa.ReferenceTypeMap) => void) { - this.circularDependencyResolvers.push(callback); + public CheckModelUnicity(refName: string, positions: { fileName: string, pos: number }[]) { + if (!this.modelDefinitionPosMap[refName]) { + this.modelDefinitionPosMap[refName] = positions; + } else { + let origPositions = this.modelDefinitionPosMap[refName]; + if (!(origPositions.length == positions.length && positions.every(pos => origPositions.find(origPos => pos.pos == origPos.pos && pos.fileName == origPos.fileName)))) { + throw new Error(`Found 2 different model definitions for model ${refName}: orig: ${JSON.stringify(origPositions)}, act: ${JSON.stringify(positions)}`); + } + } + } + + public CheckExpressionUnicity(formattedRefName: string, refName: string) { + if (!this.expressionOrigNameMap[formattedRefName]) { + this.expressionOrigNameMap[formattedRefName] = refName; + } else { + if (this.expressionOrigNameMap[formattedRefName] != refName) { + throw new Error(`Found 2 different type expressions for formatted name "${formattedRefName}": orig: "${this.expressionOrigNameMap[formattedRefName]}", act: "${refName}"`); + } + } } private buildControllers() { diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 785aeac13..a9f623909 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -1,22 +1,25 @@ +import { assertNever, Tsoa } from '@tsoa/runtime'; import * as ts from 'typescript'; -import { getJSDocComment, getJSDocComments, getJSDocTagNames, isExistJSDocTag } from './../utils/jsDocUtils'; +import { safeFromJson } from '../utils/jsonUtils'; import { getDecorators, getNodeFirstDecoratorValue, isDecorator } from './../utils/decoratorUtils'; +import { getJSDocComment, getJSDocComments, getJSDocTagNames, isExistJSDocTag } from './../utils/jsDocUtils'; import { getPropertyValidators } from './../utils/validatorUtils'; import { GenerateMetadataError, GenerateMetaDataWarning } from './exceptions'; +import { getExtensions, getExtensionsFromJSDocComments } from './extension'; import { getInitializerValue } from './initializer-value'; import { MetadataGenerator } from './metadataGenerator'; -import { Tsoa, assertNever } from '@tsoa/runtime'; -import { getExtensions, getExtensionsFromJSDocComments } from './extension'; -import { safeFromJson } from '../utils/jsonUtils'; const localReferenceTypeCache: { [typeName: string]: Tsoa.ReferenceType } = {}; -const inProgressTypes: { [typeName: string]: boolean } = {}; +const inProgressTypes: { [typeName: string]: ((realType: Tsoa.ReferenceType) => void)[] } = {}; type OverrideToken = ts.Token | ts.Token | ts.Token | undefined; type UsableDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration | ts.PropertySignature | ts.TypeAliasDeclaration | ts.EnumMember; type UsableDeclarationWithoutPropertySignature = Exclude; interface Context { - [name: string]: ts.TypeReferenceNode | ts.TypeNode; + [name: string]: { + type: ts.TypeNode, + name: string + } } export class TypeResolver { @@ -25,9 +28,8 @@ export class TypeResolver { private readonly current: MetadataGenerator, private readonly parentNode?: ts.Node, private context: Context = {}, - private readonly referencer?: ts.TypeNode, - private readonly addToRefTypeMap: boolean = true, - ) {} + private readonly referencer?: ts.Type, + ) { } public static clearCache() { Object.keys(localReferenceTypeCache).forEach(key => { @@ -111,13 +113,15 @@ export class TypeResolver { if (ts.isTypeLiteralNode(this.typeNode)) { const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce((res, propertySignature: ts.PropertySignature) => { const type = new TypeResolver(propertySignature.type as ts.TypeNode, this.current, propertySignature, this.context).resolve(); + + let def = TypeResolver.getDefault(propertySignature); const property: Tsoa.Property = { example: this.getNodeExample(propertySignature), - default: getJSDocComment(propertySignature, 'default'), + default: def, description: this.getNodeDescription(propertySignature), format: this.getNodeFormat(propertySignature), name: (propertySignature.name as ts.Identifier).text, - required: !propertySignature.questionToken, + required: !propertySignature.questionToken && def === undefined, type, validators: getPropertyValidators(propertySignature) || {}, deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'), @@ -152,109 +156,196 @@ export class TypeResolver { return { dataType: 'object' }; } - if (ts.isMappedTypeNode(this.typeNode) && this.referencer) { - const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer); - const mappedTypeNode = this.typeNode; - const typeChecker = this.current.typeChecker; - const getDeclaration = (prop: ts.Symbol) => prop.declarations && (prop.declarations[0] as ts.Declaration | undefined); + if (ts.isMappedTypeNode(this.typeNode)) { + let mappedTypeNode = this.typeNode; + const getOneOrigDeclaration = (prop: ts.Symbol): ts.Declaration | undefined => { + if (prop.declarations) { + return prop.declarations[0]; + } + let syntheticOrigin: ts.Symbol = (prop as any).links?.syntheticOrigin; + if (syntheticOrigin && syntheticOrigin.name == prop.name) { //Otherwise losts jsDoc like in intellisense + return syntheticOrigin.declarations?.[0]; + } + return undefined; + } const isIgnored = (prop: ts.Symbol) => { - const declaration = getDeclaration(prop); - return ( - prop.getJsDocTags().find(tag => tag.name === 'ignore') !== undefined || - (declaration !== undefined && !ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration)) - ); + const declaration = getOneOrigDeclaration(prop); + return declaration !== undefined && + ( + getJSDocTagNames(declaration).some(tag => tag == 'ignore') || + ( + !ts.isPropertyDeclaration(declaration) && + !ts.isPropertySignature(declaration) && + !ts.isParameter(declaration) + ) + ); }; - const properties: Tsoa.Property[] = type - .getProperties() - // Ignore methods, getter, setter and @ignored props - .filter(property => isIgnored(property) === false) - // Transform to property - .map(property => { - const propertyType = typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode); - const declaration = getDeclaration(property) as ts.PropertySignature | ts.PropertyDeclaration | ts.ParameterDeclaration | undefined; - - if (declaration && ts.isPropertySignature(declaration)) { - return { ...this.propertyFromSignature(declaration, mappedTypeNode.questionToken), name: property.getName() }; - } else if (declaration && (ts.isPropertyDeclaration(declaration) || ts.isParameter(declaration))) { - return { ...this.propertyFromDeclaration(declaration, mappedTypeNode.questionToken), name: property.getName() }; - } - - // Resolve default value, required and typeNode - let required = false; - const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation)!; - if (mappedTypeNode.questionToken && mappedTypeNode.questionToken.kind === ts.SyntaxKind.MinusToken) { - required = true; - } else if (mappedTypeNode.questionToken && mappedTypeNode.questionToken.kind === ts.SyntaxKind.QuestionToken) { - required = false; - } - // Push property + let calcMappedType = (type: ts.Type): Tsoa.Type => { + if (type.flags & ts.TypeFlags.Union) { //Intersections are not interesting somehow... + let types = (type as ts.UnionType).types; + let resolvedTypes = types.map(calcMappedType); return { - name: property.getName(), - required, - // Mapped types with any amount of indirection (template strings, unions, Exclude<>, etc.) - // don't provide an underlying declaration in the AST, thus we cannot know if the original - // property is deprecated. This matches intellisense's behavior in vscode. - deprecated: false, - type: new TypeResolver(typeNode, this.current, this.typeNode, this.context, this.referencer).resolve(), - validators: {}, + dataType: 'union', + types: resolvedTypes }; - }); + } else if (type.flags & ts.TypeFlags.Object) { + let typeProperties: ts.Symbol[] = type.getProperties(); + const properties: Tsoa.Property[] = typeProperties + // Ignore methods, getter, setter and @ignored props + .filter(property => isIgnored(property) === false) + // Transform to property + .map(property => { + const propertyType = this.current.typeChecker.getTypeOfSymbol(property); + + const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation)!; + let parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc + const type = new TypeResolver(typeNode, this.current, parent, this.context, propertyType).resolve(); + + let required = !(property.flags & ts.SymbolFlags.Optional); + + const comments = property.getDocumentationComment(this.current.typeChecker); + let description = comments.length ? ts.displayPartsToString(comments) : undefined; + + let initializer = (parent as any)?.initializer; + let def = initializer ? getInitializerValue(initializer, this.current.typeChecker) : + parent ? TypeResolver.getDefault(parent) : undefined; + + // Push property + return { + name: property.getName(), + required: required && def === undefined, + deprecated: parent ? ( + isExistJSDocTag(parent, tag => tag.tagName.text === 'deprecated') || + isDecorator(parent, identifier => identifier.text === 'Deprecated') + ) : false, + type, + default: def, + // validators are disjunct via types, so it is now OK. + // if a type not changes while mapping, we need validators + // if a type changes, then the validators will be not relevant + validators: (parent ? getPropertyValidators(parent) : {}) || {}, + description, + format: parent ? this.getNodeFormat(parent) : undefined, + example: parent ? this.getNodeExample(parent) : undefined, + extensions: parent ? this.getNodeExtension(parent) : undefined + }; + }); + + const objectLiteral: Tsoa.NestedObjectLiteralType = { + dataType: 'nestedObjectLiteral', + properties, + }; + let indexInfos = this.current.typeChecker.getIndexInfosOfType(type); + let indexTypes = indexInfos.map(indexInfo => { + const typeNode = this.current.typeChecker.typeToTypeNode(indexInfo.type, undefined, ts.NodeBuilderFlags.NoTruncation)!; + const type = new TypeResolver(typeNode, this.current, mappedTypeNode, this.context, indexInfo.type).resolve(); + return type; + }); + if (indexTypes.length) { + if (indexTypes.length == 1) { + objectLiteral.additionalProperties = indexTypes[0]; + } else { + // { [k: string]: string; } & { [k: number]: number; } + + // A | B is sometimes A type or B type, sometimes optionally accepts both A & B members. + // Most people & TSOA thinks that A | B can be only A or only B. + // So we can accept this merge + + //Every additional property key assumed as string + objectLiteral.additionalProperties = { + dataType: 'union', + types: indexTypes + }; + } + } + return objectLiteral; + } else { + // Known issues & easy to implement: Partial, Partial, ... But I think a programmer not writes types like this + throw new GenerateMetadataError(`Unhandled mapped type has found, flags: ${type.flags}`, this.typeNode); + } + } - const objectLiteral: Tsoa.NestedObjectLiteralType = { - dataType: 'nestedObjectLiteral', - properties, - }; - return objectLiteral; + let referencer = this.getReferencer(); + let result: Tsoa.Type = calcMappedType(referencer); + return result; } - if (ts.isConditionalTypeNode(this.typeNode) && this.referencer && ts.isTypeReferenceNode(this.referencer)) { - const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer); + if (ts.isConditionalTypeNode(this.typeNode)) { + let referencer = this.getReferencer(); + let resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation)! + return new TypeResolver(resolvedNode, this.current, this.typeNode, this.context, referencer).resolve(); + } - if (type.aliasSymbol) { - let declaration = type.aliasSymbol.declarations?.[0] as ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.DeclarationStatement; - if (declaration.name) { - declaration = this.getModelTypeDeclaration(declaration.name as ts.EntityName) as ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.DeclarationStatement; + // keyof + if (ts.isTypeOperatorNode(this.typeNode) && this.typeNode.operator === ts.SyntaxKind.KeyOfKeyword) { + let type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode); + if (type.getFlags() & ts.TypeFlags.Index) { + // in case of generic: keyof T. Not handles all possible cases + let symbol = (type as ts.IndexType).type.getSymbol(); + if (symbol && symbol.getFlags() & ts.TypeFlags.TypeParameter) { + let typeName = symbol.getEscapedName(); + if (this.context[typeName as string]) { + let subResult = new TypeResolver(this.context[typeName as string].type, this.current, this.parentNode, this.context).resolve(); + if (subResult.dataType == 'any') { + return { + dataType: 'union', + types: [ + { dataType: 'string' }, + { dataType: 'double' } + ] + }; + } + let properties = (subResult as Tsoa.RefObjectType).properties?.map(v => v.name); + if (properties) { + return { + dataType: 'enum', + enums: properties + } + } else { + throw new GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, this.context[typeName as string].type); + } + } } - const name = this.getRefTypeName(this.referencer.getText()); - return this.handleCachingAndCircularReferences(name, () => { - if (ts.isTypeAliasDeclaration(declaration)) { - // Note: I don't understand why typescript lose type for `this.referencer` (from above with isTypeReferenceNode()) - return this.getTypeAliasReference(declaration, this.current.typeChecker.typeToString(type), this.referencer as ts.TypeReferenceNode); - } else if (ts.isEnumDeclaration(declaration)) { - return this.getEnumerateType(declaration.name) as Tsoa.RefEnumType; + } else if (type.isUnion()) { + const literals = type.types.filter((t): t is ts.LiteralType => t.isLiteral()); + let literalValues: (string | number)[] = []; + for (let literal of literals) { + if (typeof literal.value == 'number' || typeof literal.value == 'string') { + literalValues.push(literal.value); } else { - throw new GenerateMetadataError( - `Couldn't resolve Conditional to TypeNode. If you think this should be resolvable, please file an Issue. We found an aliasSymbol and it's declaration was of kind ${declaration.kind}`, - this.typeNode, - ); + throw new GenerateMetadataError(`Not handled key Type, maybe ts.PseudoBigInt ${this.current.typeChecker.typeToString(literal)}`, this.typeNode); } - }); - } else if (type.isClassOrInterface()) { - let declaration = type.symbol.declarations?.[0] as ts.InterfaceDeclaration | ts.ClassDeclaration; - if (declaration.name) { - declaration = this.getModelTypeDeclaration(declaration.name) as ts.InterfaceDeclaration | ts.ClassDeclaration; } - const name = this.getRefTypeName(this.referencer.getText()); - return this.handleCachingAndCircularReferences(name, () => this.getModelReference(declaration, this.current.typeChecker.typeToString(type))); - } else { - try { - return new TypeResolver(this.current.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, this.context, this.referencer).resolve(); - } catch { - throw new GenerateMetadataError( - `Couldn't resolve Conditional to TypeNode. If you think this should be resolvable, please file an Issue. The flags on the result of the ConditionalType was ${type.flags}`, - this.typeNode, - ); - } - } - } - // keyof - if (ts.isTypeOperatorNode(this.typeNode) && this.typeNode.operator === ts.SyntaxKind.KeyOfKeyword) { - const type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode); + if (!literals.length && + type.types.length == 3 && + type.types.some(t => t.flags == ts.TypeFlags.String) && + type.types.some(t => t.flags == ts.TypeFlags.Number) && + type.types.some(t => t.flags == ts.TypeFlags.ESSymbol) + ) { + //keyof any + return { + dataType: 'union', + types: [ + { dataType: 'string' }, + { dataType: 'double' } + ] + }; + } - if (type.isUnion()) { - const literals = type.types.filter((t): t is ts.LiteralType => t.isLiteral()).reduce((acc, t: ts.LiteralType) => [...acc, t.value.toString()], []); + if (!literals.length && + type.types.length == 2 && + type.types.some(t => t.flags == ts.TypeFlags.Number) && + type.types.some(t => t.flags == ts.TypeFlags.String)) { + return { + dataType: 'union', + types: [ + { dataType: 'string' }, + { dataType: 'double' } + ] + }; + } // Warn on nonsense (`number`, `typeof Symbol.iterator`) if (type.types.find(t => !t.isLiteral()) !== undefined) { @@ -262,33 +353,45 @@ export class TypeResolver { console.warn(new GenerateMetaDataWarning(`Skipped non-literal type(s) ${problems.join(', ')}`, this.typeNode).toString()); } + let stringMembers = literalValues.filter(v => typeof v == 'string'); + let numberMembers = literalValues.filter(v => typeof v == 'number'); + if (stringMembers.length && numberMembers.length) { + return { + dataType: 'union', + types: [ + { dataType: 'enum', enums: stringMembers }, + { dataType: 'enum', enums: numberMembers }, + ] + } + } return { dataType: 'enum', - enums: literals, + enums: literalValues, }; } else if (type.isLiteral()) { - return { - dataType: 'enum', - enums: [type.value.toString()], - }; + if (typeof type.value == 'number' || typeof type.value == 'string') { + return { + dataType: 'enum', + enums: [type.value], + }; + } else { + throw new GenerateMetadataError(`Not handled indexType, maybe ts.PseudoBigInt ${this.current.typeChecker.typeToString(type)}`, this.typeNode); + } } else if ((type.getFlags() & ts.TypeFlags.Never) !== 0) { throw new GenerateMetadataError(`TypeOperator 'keyof' on node produced a never type`, this.typeNode); - } else { - const warning = new GenerateMetaDataWarning( - `Couldn't resolve keyof reliably, please check the resulting type carefully. - Reason: Type was not a literal or an array of literals.`, - this.typeNode, - ); - - console.warn(warning.toString()); - - try { - return new TypeResolver(this.current.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, this.context, this.referencer).resolve(); - } catch (err) { - const indexedTypeName = this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode.type)); - throw new GenerateMetadataError(`Could not determine the keys on ${indexedTypeName}`, this.typeNode); - } + } else if ((type.getFlags() & ts.TypeFlags.TemplateLiteral) !== 0) { + //Now assumes template literals as string + console.warn(new GenerateMetaDataWarning(`Template literals are assumed as strings`, this.typeNode).toString()); + return { + dataType: 'string', + }; + } else if ((type.getFlags() & ts.TypeFlags.Number) !== 0) { + return { + dataType: 'double' + }; } + const indexedTypeName = this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode.type)); + throw new GenerateMetadataError(`Could not determine the keys on ${indexedTypeName}`, this.typeNode); } // Handle `readonly` arrays @@ -309,7 +412,6 @@ export class TypeResolver { this.current, this.typeNode, this.context, - this.referencer, ).resolve(); } @@ -328,7 +430,7 @@ export class TypeResolver { ); } if (hasType(symbol.valueDeclaration) && symbol.valueDeclaration.type) { - return new TypeResolver(symbol.valueDeclaration.type, this.current, this.typeNode, this.context, this.referencer).resolve(); + return new TypeResolver(symbol.valueDeclaration.type, this.current, this.typeNode, this.context).resolve(); } const declaration = this.current.typeChecker.getTypeOfSymbolAtLocation(symbol, this.typeNode.objectType); try { @@ -336,8 +438,7 @@ export class TypeResolver { this.current.typeChecker.typeToTypeNode(declaration, this.typeNode.objectType, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, - this.context, - this.referencer, + this.context ).resolve(); } catch { throw new GenerateMetadataError( @@ -357,15 +458,16 @@ export class TypeResolver { const isSameTypeQuery = ts.isTypeQueryNode(objectType) && ts.isTypeQueryNode(indexType) && objectType.exprName.getText() === indexType.exprName.getText(); const isSameTypeReference = ts.isTypeReferenceNode(objectType) && ts.isTypeReferenceNode(indexType) && objectType.typeName.getText() === indexType.typeName.getText(); if (isSameTypeQuery || isSameTypeReference) { - const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer || this.typeNode); + const type = this.getReferencer(); const node = this.current.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation)!; return new TypeResolver(node, this.current, this.typeNode, this.context, this.referencer).resolve(); } } if (ts.isTemplateLiteralTypeNode(this.typeNode)) { - const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer || this.typeNode); + const type = this.getReferencer(); if (type.isUnion() && type.types.every((unionElementType): unionElementType is ts.StringLiteralType => unionElementType.isStringLiteral())) { + // `a${'c' | 'd'}b` const stringLiteralEnum: Tsoa.EnumType = { dataType: 'enum', enums: type.types.map((stringLiteralType: ts.StringLiteralType) => stringLiteralType.value), @@ -373,8 +475,8 @@ export class TypeResolver { return stringLiteralEnum; } else { throw new GenerateMetadataError( - `Could not determine the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, - this.typeNode, + `Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, + this.typeNode ); } } @@ -420,31 +522,12 @@ export class TypeResolver { return stringMetaType; } - if (typeReference.typeName.text === 'Record' && typeReference.typeArguments?.length === 2) { - const indexType = new TypeResolver(typeReference.typeArguments[0], this.current, this.parentNode, this.context).resolve(); - if (indexType.dataType === 'string' || indexType.dataType === 'integer' || indexType.dataType === 'double') { - const valueType = new TypeResolver(typeReference.typeArguments[1], this.current, this.parentNode, this.context).resolve(); - - const nestedObjectMetaType: Tsoa.NestedObjectLiteralType = { - additionalProperties: valueType, - dataType: 'nestedObjectLiteral', - properties: [], - }; - return nestedObjectMetaType; - } - } - if (this.context[typeReference.typeName.text]) { - return new TypeResolver(this.context[typeReference.typeName.text], this.current, this.parentNode, this.context).resolve(); + return new TypeResolver(this.context[typeReference.typeName.text].type, this.current, this.parentNode, this.context).resolve(); } } const referenceType = this.getReferenceType(typeReference); - - if (this.addToRefTypeMap) { - this.current.AddReferenceType(referenceType); - } - return referenceType; } @@ -571,41 +654,7 @@ export class TypeResolver { return (type.flags & flag) === flag; } - private getEnumerateType(typeName: ts.EntityName): Tsoa.RefEnumType | undefined { - const enumName = (typeName as ts.Identifier).text; - - const symbol = this.getSymbolAtLocation(typeName); - - // resolve value - let declaredType = (this.current.typeChecker.getDeclaredTypeOfSymbol(symbol)?.symbol || symbol) as ts.Symbol & { parent?: ts.Symbol }; - - // if we are a EnumMember, return parent instead (this happens if a enum has only one entry, not quite sure why though...) - if (this.hasFlag(declaredType, ts.SymbolFlags.EnumMember) && declaredType.parent?.valueDeclaration?.kind === ts.SyntaxKind.EnumDeclaration) { - declaredType = declaredType.parent; - } - - const declarations = declaredType.getDeclarations(); - - if (!declarations) { - return; - } - - let enumNodes = declarations.filter((node): node is ts.EnumDeclaration => { - return ts.isEnumDeclaration(node) && node.name.getText() === enumName; - }); - - if (!enumNodes.length) { - return; - } - - enumNodes = this.getDesignatedModels(enumNodes, enumName); - - if (enumNodes.length > 1) { - throw new GenerateMetadataError(`Multiple matching enum found for enum ${enumName}; please make enum names unique.`); - } - - const enumDeclaration = enumNodes[0]; - + private getEnumerateType(enumDeclaration: ts.EnumDeclaration, enumName: string): Tsoa.RefEnumType { const isNotUndefined = (item: T): item is Exclude => { return item === undefined ? false : true; }; @@ -623,7 +672,17 @@ export class TypeResolver { }; } - private getReferenceType(node: ts.TypeReferenceType, addToRefTypeMap = true): Tsoa.ReferenceType { + private getReferencer(): ts.Type { + if (this.referencer) { + return this.referencer; + } + if (this.typeNode.pos != -1) { + return this.current.typeChecker.getTypeFromTypeNode(this.typeNode); + } + throw new GenerateMetadataError(`Can not succeeded to calculate referencer type.`, this.typeNode); + } + + private static typeReferenceToEntityName(node: ts.TypeReferenceType): ts.EntityName { let type: ts.EntityName; if (ts.isTypeReferenceNode(node)) { type = node.typeName; @@ -632,87 +691,384 @@ export class TypeResolver { } else { throw new GenerateMetadataError(`Can't resolve Reference type.`); } + return type; + } + + //Generates type name for type references + private calcRefTypeName(type: ts.EntityName): string { + let getEntityName = (type: ts.EntityName): string => { + if (ts.isIdentifier(type)) { + return type.text; + } + return `${getEntityName(type.left)}.${type.right.text}`; + } - // Can't invoke getText on Synthetic Nodes - let resolvableName = node.pos !== -1 ? node.getText() : (type as ts.Identifier).text; - if (node.pos === -1 && 'typeArguments' in node && Array.isArray(node.typeArguments)) { - // Add typearguments for Synthetic nodes (e.g. Record<> in TestClassModel.indexedResponse) - const argumentsString = node.typeArguments.map(arg => { - if (ts.isLiteralTypeNode(arg)) { - return `'${String(this.getLiteralValue(arg))}'`; + let name = getEntityName(type); + if (this.context[name]) { //resolve name only interesting if entity is not qualifiedName + name = this.context[name].name; //Not needed to check unicity, because generic parameters are checked previously + } else { + let declarations = this.getModelTypeDeclarations(type); + + //Two possible solutions for recognizing different types: + // - Add declaration positions into type names (In an order). + // - It accepts multiple types with same name, if the code compiles, there would be no conflicts in the type names + // - Clear namespaces from type names. + // - Horrible changes can be in the routes.ts in case of teamwork, + // because source files have paths in the computer where data generation runs. + // - Use fully namespaced names + // - Conflicts can be recognized because of the declarations + // + // The second was implemented, it not changes the usual type name formats. + + let oneDeclaration = declarations[0]; //Every declarations should be in the same namespace hierarchy + let identifiers = name.split('.'); + if (ts.isEnumMember(oneDeclaration)) { + name = identifiers.slice(identifiers.length - 2).join('.'); + } else { + name = identifiers.slice(identifiers.length - 1).join('.'); + } + + let actNode = oneDeclaration.parent; + let isFirst = true; + while (!ts.isSourceFile(actNode)) { + if (!(isFirst && ts.isEnumDeclaration(actNode)) && !ts.isModuleBlock(actNode)) { + if (ts.isModuleDeclaration(actNode)) { + let moduleName = actNode.name.text; + name = `${moduleName}.${name}`; + } else { + throw new GenerateMetadataError(`This node kind is unknown: ${actNode.kind}`, type); + } } - const resolved = this.attemptToResolveKindToPrimitive(arg.kind); - if (resolved.foundMatch === false) return 'any'; - return resolved.resolvedType; - }); - resolvableName += `<${argumentsString.join(', ')}>`; + isFirst = false; + actNode = actNode.parent; + } + + let declarationPositions = declarations.map(declaration => ({ + fileName: declaration.getSourceFile().fileName, + pos: declaration.pos + })); + this.current.CheckModelUnicity(name, declarationPositions); } + return name; + } - const name = this.contextualizedName(resolvableName); + private calcMemberJsDocProperties(arg: ts.PropertySignature): string { + let def = TypeResolver.getDefault(arg); + let isDeprecated = isExistJSDocTag(arg, tag => tag.tagName.text === 'deprecated') || + isDecorator(arg, identifier => identifier.text === 'Deprecated'); - this.typeArgumentsToContext(node, type, this.context); + const symbol = this.getSymbolAtLocation(arg.name as ts.Node); + const comments = symbol ? symbol.getDocumentationComment(this.current.typeChecker) : []; + let description = comments.length ? ts.displayPartsToString(comments) : undefined; - try { - const existingType = localReferenceTypeCache[name]; - if (existingType) { - return existingType; - } + let validators = getPropertyValidators(arg); + let format = this.getNodeFormat(arg); + let example = this.getNodeExample(arg); + let extensions = this.getNodeExtension(arg); + let isIgnored = getJSDocTagNames(arg).some(tag => tag == 'ignore'); - const refEnumType = this.getEnumerateType(type); - if (refEnumType) { - localReferenceTypeCache[name] = refEnumType; - return refEnumType; + let jsonObj = { + default: def, + description, + validators: (validators && Object.keys(validators).length) ? validators : undefined, + format, + example: example !== undefined ? example : undefined, + extensions: extensions.length ? extensions : undefined, + deprecated: isDeprecated ? true : undefined, + ignored: isIgnored ? true : undefined + } + let keys: (keyof typeof jsonObj)[] = Object.keys(jsonObj) as any; + for (let key of keys) { + if (jsonObj[key] === undefined) { + delete jsonObj[key]; } + } + if (Object.keys(jsonObj).length) { + return JSON.stringify(jsonObj); + } + return ''; + } - if (inProgressTypes[name]) { - return this.createCircularDependencyResolver(name); + //Generates type name for type references + private calcTypeName(arg: ts.TypeNode): string { + if (ts.isLiteralTypeNode(arg)) { + let literalValue = this.getLiteralValue(arg); + if (typeof literalValue == 'string') { + return `'${literalValue}'` + } + return `${literalValue}`; + } + const resolved = this.attemptToResolveKindToPrimitive(arg.kind); + if (resolved.foundMatch) { + return resolved.resolvedType; + } + if (ts.isTypeReferenceNode(arg) || ts.isExpressionWithTypeArguments(arg)) { + return this.calcTypeReferenceTypeName(arg); + } else if (ts.isTypeLiteralNode(arg)) { + let members = arg.members.map(member => { + if (ts.isPropertySignature(member)) { + let name = (member.name as ts.Identifier).text; + let typeText = this.calcTypeName(member.type as ts.TypeNode); + return `"${name}"${member.questionToken ? '?' : ''}${this.calcMemberJsDocProperties(member)}: ${typeText}`; + } else if (ts.isIndexSignatureDeclaration(member)) { + let typeText = this.calcTypeName(member.type as ts.TypeNode); + if (member.parameters.length != 1) { + throw new GenerateMetadataError(`Index signature parameters length != 1`, member); + } + let indexType = member.parameters[0]; + if (ts.isParameter(indexType)) { + let indexName = (indexType.name as ts.Identifier).text; + let indexTypeText = this.calcTypeName(indexType.type as ts.TypeNode); + if (indexType.questionToken) { + throw new GenerateMetadataError(`Question token has found for an indexSignature declaration`, indexType); + } + return `["${indexName}": ${indexTypeText}]: ${typeText}`; + } else { + // now we can't reach this part of code + throw new GenerateMetadataError(`indexSignature declaration parameter kind is not SyntaxKind.Parameter`, indexType); + } + } + throw new GenerateMetadataError(`Unhandled member kind has found: ${member.kind}`, member); + }); + return `{${members.join('; ')}}`; + } else if (ts.isArrayTypeNode(arg)) { + let typeName = this.calcTypeName(arg.elementType); + return `${typeName}[]`; + } else if (ts.isIntersectionTypeNode(arg)) { + let memberTypeNames = arg.types.map(type => this.calcTypeName(type)); + return memberTypeNames.join(' & '); + } else if (ts.isUnionTypeNode(arg)) { + let memberTypeNames = arg.types.map(type => this.calcTypeName(type)); + return memberTypeNames.join(' | '); + } else if (ts.isTypeOperatorNode(arg)) { + let subTypeName = this.calcTypeName(arg.type); + let operatorName: string; + if (arg.operator == ts.SyntaxKind.KeyOfKeyword) { + operatorName = 'keyof'; + } else if (arg.operator == ts.SyntaxKind.ReadonlyKeyword) { + operatorName = 'readonly'; + } else { + throw new GenerateMetadataError(`Unknown keyword has found: ${arg.operator}`, arg); } + return `${operatorName} ${subTypeName}`; + } else if (ts.isTypeQueryNode(arg)) { + let subTypeName = this.calcRefTypeName(arg.exprName); + return `typeof ${subTypeName}`; + } else if (ts.isIndexedAccessTypeNode(arg)) { + let objectTypeName = this.calcTypeName(arg.objectType); + let indexTypeName = this.calcTypeName(arg.indexType); + return `${objectTypeName}[${indexTypeName}]`; + } else if (arg.kind == ts.SyntaxKind.UnknownKeyword) { + return 'unknown'; + } else if (arg.kind == ts.SyntaxKind.AnyKeyword) { + return 'any'; + } else if (arg.kind == ts.SyntaxKind.NeverKeyword) { + return 'never'; + } else if (ts.isConditionalTypeNode(arg)) { + let checkTypeName = this.calcTypeName(arg.checkType); + let extendsTypeName = this.calcTypeName(arg.extendsType); + let trueTypeName = this.calcTypeName(arg.trueType); + let falseTypeName = this.calcTypeName(arg.falseType); + return `${checkTypeName} extends ${extendsTypeName} ? ${trueTypeName} : ${falseTypeName}`; + } else if (ts.isParenthesizedTypeNode(arg)) { + let internalTypeName = this.calcTypeName(arg.type); + return `(${internalTypeName})`; //Parentheses are not really interesting. The type name generation adds parentheses for the clarity + } + + const warning = new GenerateMetaDataWarning( + `This kind (${arg.kind}) is unhandled, so the type will be any, and no type conflict checks will made`, + arg, + ); + + console.warn(warning.toString()); + return 'any'; + } + + //Generates type name for type references + private calcTypeReferenceTypeName(node: ts.TypeReferenceType): string { + let type = TypeResolver.typeReferenceToEntityName(node); + let refTypeName = this.calcRefTypeName(type); + if (Array.isArray(node.typeArguments)) { + // Add typeArguments for Synthetic nodes (e.g. Record<> in TestClassModel.indexedResponse) + const argumentsString = node.typeArguments.map(type => this.calcTypeName(type)); + return `${refTypeName}<${argumentsString.join(', ')}>`; + } + return refTypeName; + } - inProgressTypes[name] = true; + private getReferenceType(node: ts.TypeReferenceType, addToRefTypeMap = true): Tsoa.ReferenceType { + let type = TypeResolver.typeReferenceToEntityName(node); - const declaration = this.getModelTypeDeclaration(type); + let name = this.calcTypeReferenceTypeName(node); + let refTypeName = this.getRefTypeName(name); + this.current.CheckExpressionUnicity(refTypeName, name); - let referenceType: Tsoa.ReferenceType; - if (ts.isTypeAliasDeclaration(declaration)) { - referenceType = this.getTypeAliasReference(declaration, name, node, addToRefTypeMap); - } else if (ts.isEnumMember(declaration)) { - referenceType = { - dataType: 'refEnum', - refName: this.getRefTypeName(name), - enums: [this.current.typeChecker.getConstantValue(declaration)!], - enumVarnames: [declaration.name.getText()], - deprecated: isExistJSDocTag(declaration, tag => tag.tagName.text === 'deprecated'), - }; - } else { - referenceType = this.getModelReference(declaration, name); + this.context = this.typeArgumentsToContext(node, type); + + let calcReferenceType = (): Tsoa.ReferenceType => { + try { + const existingType = localReferenceTypeCache[name]; + if (existingType) { + return existingType; + } + + if (inProgressTypes[name]) { + return this.createCircularDependencyResolver(name, refTypeName); + } + + inProgressTypes[name] = []; + + const declarations = this.getModelTypeDeclarations(type); + let referenceTypes: Tsoa.ReferenceType[] = []; + for (let declaration of declarations) { + if (ts.isTypeAliasDeclaration(declaration)) { + let referencer = node.pos != -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined; + referenceTypes.push(this.getTypeAliasReference(declaration, refTypeName, referencer)); + } else if (ts.isEnumDeclaration(declaration)) { + referenceTypes.push(this.getEnumerateType(declaration, refTypeName)); + } else if (ts.isEnumMember(declaration)) { + referenceTypes.push({ + dataType: 'refEnum', + refName: refTypeName, + enums: [this.current.typeChecker.getConstantValue(declaration)!], + enumVarnames: [declaration.name.getText()], + deprecated: isExistJSDocTag(declaration, tag => tag.tagName.text === 'deprecated'), + }); + } else { + referenceTypes.push(this.getModelReference(declaration, refTypeName)); + } + } + let referenceType = TypeResolver.mergeReferenceTypes(referenceTypes); + this.addToLocalReferenceTypeCache(name, referenceType); + return referenceType; + } catch (err) { + // eslint-disable-next-line no-console + console.error(`There was a problem resolving type of '${name}'.`); + throw err; } + } + let result = calcReferenceType(); + if (addToRefTypeMap) { + this.current.AddReferenceType(result); + } + return result; + } - localReferenceTypeCache[name] = referenceType; + private static mergeTwoRefEnumTypes(first: Tsoa.RefEnumType, second: Tsoa.RefEnumType): Tsoa.RefEnumType { + let description = first.description ? + second.description ? `${first.description}\n${second.description}` : first.description : + second.description; - return referenceType; - } catch (err) { - // eslint-disable-next-line no-console - console.error(`There was a problem resolving type of '${name}'.`); - throw err; + let deprecated = first.deprecated || second.deprecated; + + let enums = first.enums ? + second.enums ? [...first.enums, ...second.enums] : first.enums : + second.enums; + + let enumVarnames = first.enumVarnames ? + second.enumVarnames ? [...first.enumVarnames, ...second.enumVarnames] : first.enumVarnames : + second.enumVarnames; + + return { + dataType: 'refEnum', + description, + enums, + enumVarnames, + refName: first.refName, + deprecated + }; + } + + private static mergeTwoRefObjectTypes(first: Tsoa.RefObjectType, second: Tsoa.RefObjectType): Tsoa.RefObjectType { + let description = first.description ? + second.description ? `${first.description}\n${second.description}` : first.description : + second.description; + + let deprecated = first.deprecated || second.deprecated; + let example = first.example || second.example; + + let properties = [ + ...first.properties, + ...second.properties.filter(prop => first.properties.every(firstProp => firstProp.name != prop.name)) + ]; + + let mergeAdditionalTypes = (first: Tsoa.Type, second: Tsoa.Type): Tsoa.Type => { + return { + dataType: 'union', + types: [first, second] + }; + } + + let additionalProperties = first.additionalProperties ? + second.additionalProperties ? + mergeAdditionalTypes(first.additionalProperties, second.additionalProperties) : + first.additionalProperties : + second.additionalProperties; + + let result: Tsoa.RefObjectType = { + dataType: 'refObject', + description, + properties, + additionalProperties, + refName: first.refName, + deprecated, + example + } + + return result; + } + + private static mergeReferenceTypes(referenceTypes: Tsoa.ReferenceType[]): Tsoa.ReferenceType { + if (referenceTypes.length == 1) { + return referenceTypes[0]; + } + if (referenceTypes.every(refType => refType.dataType == 'refEnum')) { + let refEnumTypes = referenceTypes as Tsoa.RefEnumType[]; + let merged = TypeResolver.mergeTwoRefEnumTypes(refEnumTypes[0], refEnumTypes[1]); + for (let i = 2; i < refEnumTypes.length; ++i) { + merged = TypeResolver.mergeTwoRefEnumTypes(merged, refEnumTypes[i]); + } + return merged; + } + if (referenceTypes.every(refType => refType.dataType == 'refObject')) { + let refObjectTypes = referenceTypes as Tsoa.RefObjectType[]; + let merged = TypeResolver.mergeTwoRefObjectTypes(refObjectTypes[0], refObjectTypes[1]); + for (let i = 2; i < refObjectTypes.length; ++i) { + merged = TypeResolver.mergeTwoRefObjectTypes(merged, refObjectTypes[i]); + } + return merged; + } + throw new GenerateMetadataError(`These resolved type merge rules are not defined: ${JSON.stringify(referenceTypes)}`); + } + + private addToLocalReferenceTypeCache(name: string, refType: Tsoa.ReferenceType) { + if (inProgressTypes[name]) { + for (let fn of inProgressTypes[name]) { + fn(refType); + } } + localReferenceTypeCache[name] = refType; + + delete inProgressTypes[name]; } - private getTypeAliasReference(declaration: ts.TypeAliasDeclaration, name: string, referencer: ts.TypeReferenceType, addToRefTypeMap = true): Tsoa.ReferenceType { + private getTypeAliasReference(declaration: ts.TypeAliasDeclaration, refTypeName: string, referencer?: ts.Type): Tsoa.ReferenceType { const example = this.getNodeExample(declaration); - return { + let referenceType: Tsoa.ReferenceType = { dataType: 'refAlias', - default: getJSDocComment(declaration, 'default'), + default: TypeResolver.getDefault(declaration), description: this.getNodeDescription(declaration), - refName: this.getRefTypeName(name), + refName: refTypeName, format: this.getNodeFormat(declaration), - type: new TypeResolver(declaration.type, this.current, declaration, this.context, this.referencer || referencer, addToRefTypeMap).resolve(), + type: new TypeResolver(declaration.type, this.current, declaration, this.context, this.referencer || referencer).resolve(), validators: getPropertyValidators(declaration) || {}, ...(example && { example }), }; + return referenceType; } - private getModelReference(modelType: ts.InterfaceDeclaration | ts.ClassDeclaration, name: string) { + private getModelReference(modelType: ts.InterfaceDeclaration | ts.ClassDeclaration, refTypeName: string) { const example = this.getNodeExample(modelType); const description = this.getNodeDescription(modelType); const deprecated = isExistJSDocTag(modelType, tag => tag.tagName.text === 'deprecated') || isDecorator(modelType, identifier => identifier.text === 'Deprecated'); @@ -732,7 +1088,7 @@ export class TypeResolver { } const type = new TypeResolver(nodeType, this.current).resolve(); const referenceType: Tsoa.ReferenceType = { - refName: this.getRefTypeName(name), + refName: refTypeName, dataType: 'refAlias', description, type, @@ -752,7 +1108,7 @@ export class TypeResolver { dataType: 'refObject', description, properties: inheritedProperties, - refName: this.getRefTypeName(name), + refName: refTypeName, deprecated, ...(example && { example }), }; @@ -762,6 +1118,8 @@ export class TypeResolver { return referenceType; } + //Generates a name from the original type expression. + //This function is not invertable, so it's possible, that 2 type expressions have the same refTypeName. private getRefTypeName(name: string): string { return name .replace(/<|>/g, '_') @@ -773,9 +1131,9 @@ export class TypeResolver { .replace(/\|/g, '-or-') .replace(/\[\]/g, '-Array') .replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__ - .replace(/([a-z]+):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_ + .replace(/([a-z_0-9]+\??):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_ .replace(/;/g, '--') - .replace(/([a-z]+)\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, + .replace(/([a-z\}\)\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, } private attemptToResolveKindToPrimitive = (syntaxKind: ts.SyntaxKind): ResolvesToPrimitive | DoesNotResolveToPrimitive => { @@ -811,62 +1169,17 @@ export class TypeResolver { } }; - private contextualizedName(name: string): string { - return Object.entries(this.context).reduce((acc, [key, entry]) => { - return acc - .replace(new RegExp(`<\\s*([^>]*\\s)*\\s*(${key})(\\s[^>]*)*\\s*>`, 'g'), `<$1${entry.getText()}$3>`) - .replace(new RegExp(`<\\s*([^,]*\\s)*\\s*(${key})(\\s[^,]*)*\\s*,`, 'g'), `<$1${entry.getText()}$3,`) - .replace(new RegExp(`,\\s*([^>]*\\s)*\\s*(${key})(\\s[^>]*)*\\s*>`, 'g'), `,$1${entry.getText()}$3>`) - .replace(new RegExp(`<\\s*([^<]*\\s)*\\s*(${key})(\\s[^<]*)*\\s*<`, 'g'), `<$1${entry.getText()}$3<`); - }, name); - } - - private handleCachingAndCircularReferences(name: string, declarationResolver: () => Tsoa.ReferenceType): Tsoa.ReferenceType { - try { - const existingType = localReferenceTypeCache[name]; - if (existingType) { - return existingType; - } - - if (inProgressTypes[name]) { - return this.createCircularDependencyResolver(name); - } - - inProgressTypes[name] = true; - - const reference = declarationResolver(); - - localReferenceTypeCache[name] = reference; - - this.current.AddReferenceType(reference); - - return reference; - } catch (err) { - // eslint-disable-next-line no-console - console.error(`There was a problem resolving type of '${name}'.`); - throw err; - } - } - - private createCircularDependencyResolver(refName: string) { + private createCircularDependencyResolver(refName: string, refTypeName: string) { const referenceType = { dataType: 'refObject', - refName, + refName: refTypeName, } as Tsoa.ReferenceType; - this.current.OnFinish(referenceTypes => { - const realReferenceType = referenceTypes[refName]; - if (!realReferenceType) { - return; + inProgressTypes[refName].push(realReferenceType => { + for (let key of Object.keys(realReferenceType)) { + (referenceType as any)[key] = (realReferenceType as any)[key]; } - referenceType.description = realReferenceType.description; - if (realReferenceType.dataType === 'refObject' && referenceType.dataType === 'refObject') { - referenceType.properties = realReferenceType.properties; - } - referenceType.dataType = realReferenceType.dataType; - referenceType.refName = realReferenceType.refName; }); - return referenceType; } @@ -883,13 +1196,17 @@ export class TypeResolver { } } - private getModelTypeDeclaration(type: ts.EntityName) { + private getModelTypeDeclarations(type: ts.EntityName) { let typeName: string = type.kind === ts.SyntaxKind.Identifier ? type.text : type.right.text; - const symbol = this.getSymbolAtLocation(type); + let symbol: ts.Symbol | undefined = this.getSymbolAtLocation(type); + if (!symbol && type.kind == ts.SyntaxKind.QualifiedName) { + let fullEnumSymbol = this.getSymbolAtLocation(type.left); + symbol = fullEnumSymbol.exports?.get(typeName as any); + } const declarations = symbol?.getDeclarations(); - if (!declarations) { + if (!symbol || !declarations) { throw new GenerateMetadataError(`No declarations found for referenced type ${typeName}.`); } @@ -913,12 +1230,8 @@ export class TypeResolver { modelTypes = this.getDesignatedModels(modelTypes, typeName); } - if (modelTypes.length > 1) { - const conflicts = modelTypes.map(modelType => modelType.getSourceFile().fileName).join('"; "'); - throw new GenerateMetadataError(`Multiple matching models found for referenced type ${typeName}; please make model names unique. Conflicts found: "${conflicts}".`); - } - return modelTypes[0]; + return modelTypes; } private getSymbolAtLocation(type: ts.Node) { @@ -929,7 +1242,9 @@ export class TypeResolver { private getModelProperties(node: ts.InterfaceDeclaration | ts.ClassDeclaration, overrideToken?: OverrideToken): Tsoa.Property[] { const isIgnored = (e: ts.TypeElement | ts.ClassElement) => { - return isExistJSDocTag(e, tag => tag.tagName.text === 'ignore'); + let ignore = isExistJSDocTag(e, tag => tag.tagName.text === 'ignore'); + ignore = ignore || (e.flags & ts.NodeFlags.ThisNodeHasError) > 0; + return ignore; }; // Interface model @@ -940,7 +1255,6 @@ export class TypeResolver { } const properties: Array = []; - for (const member of node.members) { if (!isIgnored(member) && ts.isPropertyDeclaration(member) && !this.hasStaticModifier(member) && this.hasPublicModifier(member)) { properties.push(member); @@ -958,7 +1272,7 @@ export class TypeResolver { return properties.map(property => this.propertyFromDeclaration(property, overrideToken)); } - private propertyFromSignature(propertySignature: ts.PropertySignature, overrideToken?: OverrideToken) { + private propertyFromSignature(propertySignature: ts.PropertySignature, overrideToken?: OverrideToken): Tsoa.Property { const identifier = propertySignature.name as ts.Identifier; if (!propertySignature.type) { @@ -972,14 +1286,16 @@ export class TypeResolver { required = false; } + let def = TypeResolver.getDefault(propertySignature); + const property: Tsoa.Property = { - default: getJSDocComment(propertySignature, 'default'), + default: def, description: this.getNodeDescription(propertySignature), example: this.getNodeExample(propertySignature), format: this.getNodeFormat(propertySignature), name: identifier.text, - required, - type: new TypeResolver(propertySignature.type, this.current, propertySignature.type.parent, this.context, propertySignature.type).resolve(), + required: required && def === undefined, + type: new TypeResolver(propertySignature.type, this.current, propertySignature.type.parent, this.context).resolve(), validators: getPropertyValidators(propertySignature) || {}, deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'), extensions: this.getNodeExtension(propertySignature), @@ -987,20 +1303,17 @@ export class TypeResolver { return property; } - private propertyFromDeclaration(propertyDeclaration: ts.PropertyDeclaration | ts.ParameterDeclaration, overrideToken?: OverrideToken) { + private propertyFromDeclaration(propertyDeclaration: ts.PropertyDeclaration | ts.ParameterDeclaration, overrideToken?: OverrideToken): Tsoa.Property { const identifier = propertyDeclaration.name as ts.Identifier; let typeNode = propertyDeclaration.type; - if (!typeNode) { - const tsType = this.current.typeChecker.getTypeAtLocation(propertyDeclaration); - typeNode = this.current.typeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.NoTruncation); - } + const tsType = this.current.typeChecker.getTypeAtLocation(propertyDeclaration); - if (!typeNode) { - throw new GenerateMetadataError(`No valid type found for property declaration.`); + if (!typeNode) { // Type is from initializer + typeNode = this.current.typeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.NoTruncation)!; } - const type = new TypeResolver(typeNode, this.current, propertyDeclaration, this.context, typeNode).resolve(); + const type = new TypeResolver(typeNode, this.current, propertyDeclaration, this.context, tsType).resolve(); let required = !propertyDeclaration.questionToken && !propertyDeclaration.initializer; if (overrideToken && overrideToken.kind === ts.SyntaxKind.MinusToken) { @@ -1008,14 +1321,18 @@ export class TypeResolver { } else if (overrideToken && overrideToken.kind === ts.SyntaxKind.QuestionToken) { required = false; } + let def = getInitializerValue(propertyDeclaration.initializer, this.current.typeChecker); + if (def === undefined) { + def = TypeResolver.getDefault(propertyDeclaration); + } const property: Tsoa.Property = { - default: getInitializerValue(propertyDeclaration.initializer, this.current.typeChecker), + default: def, description: this.getNodeDescription(propertyDeclaration), example: this.getNodeExample(propertyDeclaration), format: this.getNodeFormat(propertyDeclaration), name: identifier.text, - required, + required: required && def === undefined, type, validators: getPropertyValidators(propertyDeclaration) || {}, // class properties and constructor parameters may be deprecated either via jsdoc annotation or decorator @@ -1045,21 +1362,22 @@ export class TypeResolver { return undefined; } - private typeArgumentsToContext(type: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments, targetEntity: ts.EntityName, context: Context): Context { - this.context = {}; + private typeArgumentsToContext(type: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments, targetEntity: ts.EntityName): Context { + let newContext: Context = {}; - const declaration = this.getModelTypeDeclaration(targetEntity); - const typeParameters = 'typeParameters' in declaration ? declaration.typeParameters : undefined; + const declaration = this.getModelTypeDeclarations(targetEntity); + const typeParameters = 'typeParameters' in declaration[0] ? declaration[0].typeParameters : undefined; if (typeParameters) { for (let index = 0; index < typeParameters.length; index++) { const typeParameter = typeParameters[index]; const typeArg = type.typeArguments && type.typeArguments[index]; let resolvedType: ts.TypeNode; - + let name: string | undefined; // Argument may be a forward reference from context - if (typeArg && ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName) && context[typeArg.typeName.text]) { - resolvedType = context[typeArg.typeName.text]; + if (typeArg && ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName) && this.context[typeArg.typeName.text]) { + resolvedType = this.context[typeArg.typeName.text].type; + name = this.context[typeArg.typeName.text].name; } else if (typeArg) { resolvedType = typeArg; } else if (typeParameter.default) { @@ -1068,13 +1386,16 @@ export class TypeResolver { throw new GenerateMetadataError(`Could not find a value for type parameter ${typeParameter.name.text}`, type); } - this.context = { - ...this.context, - [typeParameter.name.text]: resolvedType, + newContext = { + ...newContext, + [typeParameter.name.text]: { + type: resolvedType, + name: name || this.calcTypeName(resolvedType) + } }; } } - return context; + return newContext; } private getModelInheritedProperties(modelTypeDeclaration: Exclude): Tsoa.Property[] { @@ -1094,7 +1415,8 @@ export class TypeResolver { const baseEntityName = t.expression as ts.EntityName; // create subContext - const resetCtx = this.typeArgumentsToContext(t, baseEntityName, this.context); + const resetCtx = this.context; + this.context = this.typeArgumentsToContext(t, baseEntityName); const referenceType = this.getReferenceType(t, false); if (referenceType) { @@ -1187,11 +1509,11 @@ export class TypeResolver { return undefined; } - private getNodeFormat(node: UsableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.EnumDeclaration) { + private getNodeFormat(node: ts.Node) { return getJSDocComment(node, 'format'); } - private getNodeExample(node: UsableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.EnumDeclaration) { + private getNodeExample(node: ts.Node) { const exampleJSDoc = getJSDocComment(node, 'example'); if (exampleJSDoc) { return safeFromJson(exampleJSDoc); @@ -1200,7 +1522,7 @@ export class TypeResolver { return getNodeFirstDecoratorValue(node, this.current.typeChecker, dec => dec.text === 'Example'); } - private getNodeExtension(node: UsableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.EnumDeclaration) { + private getNodeExtension(node: ts.Node) { const decorators = this.getDecoratorsByIdentifier(node, 'Extension'); const extensionDecorator = getExtensions(decorators, this.current); @@ -1213,6 +1535,14 @@ export class TypeResolver { private getDecoratorsByIdentifier(node: ts.Node, id: string) { return getDecorators(node, identifier => identifier.text === id); } + + private static getDefault(node: ts.Node) { + let defaultStr = getJSDocComment(node, 'default'); + if (typeof defaultStr == 'string' && defaultStr != 'undefined') { + return JSON.parse(defaultStr); + } + return undefined; + } } interface ResolvesToPrimitive { diff --git a/packages/cli/src/utils/validatorUtils.ts b/packages/cli/src/utils/validatorUtils.ts index 12463a43c..582a0071c 100644 --- a/packages/cli/src/utils/validatorUtils.ts +++ b/packages/cli/src/utils/validatorUtils.ts @@ -1,7 +1,7 @@ -import validator from 'validator'; +import { Tsoa } from '@tsoa/runtime'; import * as ts from 'typescript'; +import validator from 'validator'; import { GenerateMetadataError } from './../metadataGeneration/exceptions'; -import { Tsoa } from '@tsoa/runtime'; import { commentToString, getJSDocTags } from './jsDocUtils'; export function getParameterValidators(parameter: ts.ParameterDeclaration, parameterName: string): Tsoa.Validators { @@ -99,7 +99,7 @@ export function getParameterValidators(parameter: ts.ParameterDeclaration, param }, {} as Tsoa.Validators & { [unknown: string]: { errorMsg: string; value: undefined } }); } -export function getPropertyValidators(property: ts.PropertyDeclaration | ts.TypeAliasDeclaration | ts.PropertySignature | ts.ParameterDeclaration): Tsoa.Validators | undefined { +export function getPropertyValidators(property: ts.Node): Tsoa.Validators | undefined { const tags = getJSDocTags(property, tag => { return getParameterTagSupport().some(value => value === tag.tagName.text); }); diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts index bb5c34785..c597ddbaf 100644 --- a/tests/fixtures/testModel.ts +++ b/tests/fixtures/testModel.ts @@ -209,6 +209,299 @@ export interface TestModel extends Model { extensionComment?: boolean; keyofLiteral?: keyof Items; + + namespaces?: { + simple: NamespaceType; + inNamespace1: Namespace1.NamespaceType; + typeHolder1: Namespace1.TypeHolder; + inModule: Namespace2.Namespace2.NamespaceType; + typeHolder2: Namespace2.TypeHolder; + }; + + defaults?: { + basic: DefaultsClass; + replacedTypes: ReplaceTypes; + /** + * @default undefined + */ + defaultUndefined?: string, + /** + * @default null + */ + defaultNull: string | null, + /** + * @default + * { + * "a": "a", + * "b": 2 + * } + */ + defaultObject: { a: string, b: number }, + }; + + jsDocTypeNames?: { + simple: Partial<{ a: string }>; + commented: Partial<{ + /** comment */ + a: string + }>; + multilineCommented: Partial<{ + /** + * multiline + * comment + */ + a: string + }>; + defaultValue: Partial<{ + /** @default "true" */ + a: string + }>; + deprecated: Partial<{ + /** @deprecated */ + a: string + }>; + validators: Partial<{ + /** @minLength 3 */ + a: string + }>; + examples: Partial<{ + /** @example "example" */ + a: string + }>; + extensions: Partial<{ + /** @extension {"x-key-1": "value-1"} */ + a: string + }>; + ignored: Partial<{ + /** @ignore */ + a: string + }>; + + indexedSimple: Partial<{ [a: string]: string }>; + indexedCommented: Partial<{ + /** comment */ + [a: string]: string + }>; + indexedMultilineCommented: Partial<{ + /** + * multiline + * comment + */ + [a: string]: string + }>; + indexedDefaultValue: Partial<{ + /** @default "true" */ + [a: string]: string + }>; + indexedDeprecated: Partial<{ + /** @deprecated */ + [a: string]: string + }>; + indexedValidators: Partial<{ + /** @minLength 3 */ + [a: string]: string + }>; + indexedExamples: Partial<{ + /** @example "example" */ + [a: string]: string + }>; + indexedExtensions: Partial<{ + /** @extension {"x-key-1": "value-1"} */ + [a: string]: string + }>; + indexedIgnored: Partial<{ + /** @ignore */ + [a: string]: string + }>; + }; + + jsdocMap?: { + omitted: Omit; + partial: Partial; + replacedTypes: ReplaceStringAndNumberTypes; + doubleReplacedTypes: ReplaceStringAndNumberTypes> + postfixed: Postfixed; + values: Values; + typesValues: InternalTypes>; + onlyOneValue: JsDocced['numberValue']; + synonym: JsDoccedSynonym; + synonym2: JsDoccedSynonym2; + }; + + duplicatedDefinitions?: { + interfaces: DuplicatedInterface; + enums: DuplicatedEnum; + enumMember: DuplicatedEnum.C; + namespaceMember: DuplicatedEnum.D; + }; + + mappeds?: { + unionMap: Partial<{ a: string } | { b: number }>; + indexedUnionMap: Partial<{ a: string } | { [b: string]: number }>; + doubleIndexedUnionMap: Partial<{ [a: string]: string } | { [b: string]: number }>; + + intersectionMap: Partial<{ a: string } & { b: number }>; + indexedIntersectionMap: Partial<{ a: string } & { [b: string]: number }>; + doubleIndexedIntersectionMap: Partial<{ [a: string]: string } & { [b: number]: number }>; + parenthesizedMap: Partial<{ a: string } | ({ b: string } & { c: string })>; + parenthesizedMap2: Partial<({ a: string } | { b: string }) & { c: string }>; + }; + + conditionals?: { + simpeConditional: string extends string ? number : boolean; + simpeFalseConditional: string extends number ? number : boolean; + typedConditional: Conditional; + typedFalseConditional: Conditional; + dummyConditional: Dummy>; + dummyFalseConditional: Dummy>; + mappedConditional: Partial; + mappedTypedConditional: Partial>; + } + + typeOperators?: { + keysOfAny: KeysMember; + keysOfInterface: KeysMember; + simple: keyof NestedTypeLiteral; + keyofItem: keyof NestedTypeLiteral['b']; + keyofAnyItem: keyof NestedTypeLiteral['e']; + keyofAny: keyof any; + stringLiterals: keyof Record<'A' | 'B' | 'C', string>; + stringAndNumberLiterals: keyof Record<'A' | 'B' | 3, string>; + keyofEnum: keyof typeof DuplicatedEnum; + numberAndStringKeys: keyof { [3]: string, [4]: string, a: string }; + oneStringKeyInterface: keyof { a: string }; + oneNumberKeyInterface: keyof { [3]: string }; + indexStrings: keyof { [a: string]: string }; + indexNumbers: keyof { [a: number]: string }; + } + + nestedTypes?: { + multiplePartial: Partial>; + separateField: Partial, 'a'>>; + separateField2: Partial, 'a' | 'b'>>; + separateField3: Partial, 'a' | 'b'>>; + } +} + +type SeparateField = { + omitted: Omit, + field: T[Field]; +} + +type KeysMember = { + keys: keyof T; +} + +interface NestedTypeLiteral { + a: string; + b: { + c: string; + d: string; + }, + e: any; +} + +type Dummy = T; + +type Conditional = T extends CheckType ? TrueType : FalseType; + +interface DuplicatedInterface { + a: string; +} + +interface DuplicatedInterface { + a: string; + b: string; +} + +class DuplicatedInterface { + a = 'defaultA'; +} + +enum DuplicatedEnum { + A = 'AA', + B = 'BB' +} + +enum DuplicatedEnum { + C = 'CC' +} + +namespace DuplicatedEnum { + export type D = 'DD'; +} + +interface JsDocced { + /** + * @maxLength 3 + * @default "def" + */ + stringValue: string; + /** + * @isInt + * @default 6 + */ + numberValue: number; +} + +type JsDoccedKeys = keyof JsDocced; +type JsDoccedSynonym = { [key in JsDoccedKeys]: JsDocced[key] }; +type JsDoccedSynonym2 = { [key in keyof JsDocced]: JsDocced[key] }; +type ReplaceTypes = { [K in keyof T]: T[K] extends Type1 ? Type2 : Type1 }; +type ReplaceStringAndNumberTypes = ReplaceTypes; +type Postfixed = { [K in keyof T as `${K & string}${Postfix}`]: T[K] }; +type Values = { [K in keyof T]: { value: T[K] } } +type InternalTypes> = { [K in keyof T]: T[K]['value'] }; + + +class DefaultsClass { + /** + * @default true + */ + boolValue1?: boolean; + /** + * @default false + */ + boolValue2?= true; + boolValue3?= false; + boolValue4?: boolean; +} + +type NamespaceType = string; + +namespace Namespace1 { + export interface NamespaceType { + inFirstNamespace: string; + } + + export interface TypeHolder { + inNamespace1_1: Namespace1.NamespaceType; + inNamespace1_2: NamespaceType; + } +} + +namespace Namespace1 { + export interface NamespaceType { + inFirstNamespace2: string; + } +} + +namespace Namespace2 { + interface NamespaceType { + inSecondNamespace: string; + } + + export module Namespace2 { + export interface NamespaceType { + inModule: string; + other?: NamespaceType; + } + } + + export interface TypeHolder { + inModule: Namespace2.NamespaceType; + inNamespace2: NamespaceType; + } } type Items = { @@ -222,7 +515,7 @@ interface DeprecatedType { } @Deprecated() -class DeprecatedClass {} +class DeprecatedClass { } interface TypeWithDeprecatedProperty { ok: boolean; @@ -268,7 +561,6 @@ const otherIndexedValue = { } as const; export type ForeignIndexedValue = (typeof indexedValue)[keyof typeof otherIndexedValue]; - type Maybe = T | null; export interface TypeAliasModel1 { @@ -415,14 +707,14 @@ export interface TestSubModel2 extends TestSubModel { testSubModel2: boolean; } -export interface HeritageTestModel extends TypeAlias4, Partial> {} +export interface HeritageTestModel extends TypeAlias4, Partial> { } export interface HeritageBaseModel { value: string; } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface HeritageTestModel2 extends HeritageBaseModel {} +export interface HeritageTestModel2 extends HeritageBaseModel { } export interface DefaultTestModel> { t: GenericRequest; @@ -484,7 +776,7 @@ export class ParameterTestModel { public nicknames?: string[]; } -export class ValidateCustomErrorModel {} +export class ValidateCustomErrorModel { } export class ValidateModel { /** @@ -768,8 +1060,8 @@ const ClassIndexTest = { type Names = keyof typeof ClassIndexTest; type ResponseDistribute = T extends Names ? { - [key in T]: Record<(typeof ClassIndexTest)[T][number], U>; - } + [key in T]: Record<(typeof ClassIndexTest)[T][number], U>; + } : never; type IndexRecordAlias = ResponseDistribute; @@ -940,3 +1232,4 @@ type OrderDirection = 'asc' | 'desc'; type OrderOptions = `${keyof E & string}:${OrderDirection}`; type TemplateLiteralString = OrderOptions; + diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index 291e85cf7..451d35c8a 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -1,12 +1,12 @@ -import { expect } from 'chai'; -import 'mocha'; +import { ExtendedSpecConfig } from '@tsoa/cli/cli'; import { MetadataGenerator } from '@tsoa/cli/metadataGeneration/metadataGenerator'; import { SpecGenerator2 } from '@tsoa/cli/swagger/specGenerator2'; import { Swagger } from '@tsoa/runtime'; +import { expect } from 'chai'; +import 'mocha'; +import { versionMajorMinor } from 'typescript'; import { getDefaultOptions } from '../../../fixtures/defaultOptions'; import { TestModel } from '../../../fixtures/testModel'; -import { ExtendedSpecConfig } from '@tsoa/cli/cli'; -import { versionMajorMinor } from 'typescript'; describe('Definition generation', () => { const metadata = new MetadataGenerator('./fixtures/controllers/getController.ts').Generate(); @@ -131,7 +131,7 @@ describe('Definition generation', () => { 'TestSubModelContainerNamespace.InnerNamespace.TestSubModelContainer2', 'TestSubModel2', 'TestSubModelNamespace.TestSubModelNS', - 'TsoaTest.TestModel73', + 'tsoaTest.TsoaTest.TestModel73', ]; expectedModels.forEach(modelName => { getValidatedDefinition(modelName, currentSpec); @@ -190,7 +190,7 @@ describe('Definition generation', () => { it('should generate a correct definition for models in namespaces in modules', () => { allSpecs.forEach(currentSpec => { - const namespaceInModule = getValidatedDefinition('TsoaTest.TestModel73', currentSpec); + const namespaceInModule = getValidatedDefinition('tsoaTest.TsoaTest.TestModel73', currentSpec); expect(namespaceInModule).to.deep.include({ description: undefined, @@ -282,7 +282,7 @@ describe('Definition generation', () => { }, boolValue: (propertyName, propertySchema) => { expect(propertySchema.type).to.eq('boolean', `for property ${propertyName}.type`); - expect(propertySchema.default).to.eq('true', `for property ${propertyName}.default`); + expect(propertySchema.default).to.eq(true, `for property ${propertyName}.default`); expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`); }, boolArray: (propertyName, propertySchema) => { @@ -564,6 +564,7 @@ describe('Definition generation', () => { default: undefined, }, }, + required: ['record-foo', 'record-bar'], type: 'object', default: undefined, example: undefined, @@ -599,6 +600,7 @@ describe('Definition generation', () => { default: undefined, }, }, + required: ["1", "2"], type: 'object', default: undefined, example: undefined, @@ -607,37 +609,53 @@ describe('Definition generation', () => { }); }, stringRecord: (propertyName, propertySchema) => { - expect(propertySchema).to.be.deep.eq({ - properties: {}, + expect(propertySchema.$ref).to.eq('#/definitions/Record_string._data-string__'); + const schema = getValidatedDefinition('Record_string._data-string__', currentSpec); + expect(schema).to.be.deep.eq({ additionalProperties: { properties: { - data: { type: 'string', description: undefined, example: undefined, format: undefined, default: undefined }, + data: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + } }, - required: ['data'], - type: 'object', + required: ["data"], + type: "object" }, - type: 'object', default: undefined, + description: "Construct a type with a set of properties K of type T", example: undefined, format: undefined, - description: undefined, + properties: {}, + type: "object" }); }, numberRecord: (propertyName, propertySchema) => { - expect(propertySchema).to.be.deep.eq({ - properties: {}, + expect(propertySchema.$ref).to.eq('#/definitions/Record_number._data-string__'); + const schema = getValidatedDefinition('Record_number._data-string__', currentSpec); + expect(schema).to.be.deep.eq({ additionalProperties: { properties: { - data: { type: 'string', description: undefined, example: undefined, format: undefined, default: undefined }, + data: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + } }, - required: ['data'], - type: 'object', + required: ["data"], + type: "object" }, - type: 'object', default: undefined, + description: "Construct a type with a set of properties K of type T", example: undefined, format: undefined, - description: undefined, + properties: {}, + type: "object" }); }, modelsObjectIndirect: (propertyName, propertySchema) => { @@ -834,7 +852,7 @@ describe('Definition generation', () => { example: 42, minimum: 42, maximum: 42, - default: '42', + default: 42, }); const dateAliasSchema = getValidatedDefinition('DateAlias', currentSpec); @@ -1119,7 +1137,7 @@ describe('Definition generation', () => { { properties: { list: { - items: { $ref: '#/definitions/ThingContainerWithTitle_string_' }, + items: { type: 'number', format: 'double' }, type: 'array', default: undefined, description: undefined, @@ -1152,6 +1170,7 @@ describe('Definition generation', () => { type: 'string', }, }, + required: ['id'], type: 'object', }); @@ -1169,13 +1188,13 @@ describe('Definition generation', () => { format: undefined, }, indexedResponseObject: { - $ref: '#/definitions/Record_id.any_', + $ref: '#/definitions/Record_id._myProp1-string__', description: undefined, example: undefined, format: undefined, }, indexedType: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined }, - indexedTypeToAlias: { $ref: '#/definitions/IndexedInterfaceAlias', description: undefined, format: undefined, example: undefined }, + indexedTypeToAlias: { $ref: '#/definitions/IndexedInterface', description: undefined, format: undefined, example: undefined }, indexedTypeToClass: { $ref: '#/definitions/IndexedClass', description: undefined, format: undefined, example: undefined }, indexedTypeToInterface: { $ref: '#/definitions/IndexedInterface', description: undefined, format: undefined, example: undefined }, keyInterface: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined, 'x-nullable': false, enum: ['id'] }, @@ -1543,6 +1562,21 @@ describe('Definition generation', () => { default: undefined, }, }, + required: [ + 'lastname:asc', + 'age:asc', + 'weight:asc', + 'human:asc', + 'gender:asc', + 'nicknames:asc', + 'firstname:desc', + 'lastname:desc', + 'age:desc', + 'weight:desc', + 'human:desc', + 'gender:desc', + 'nicknames:desc' + ], type: 'object', description: undefined, example: undefined, @@ -1596,116 +1630,1584 @@ describe('Definition generation', () => { `for property ${propertyName}`, ); }, - }; - - Object.keys(assertionsPerProperty).forEach(aPropertyName => { - const propertySchema = definition.properties![aPropertyName]; - if (!propertySchema) { - throw new Error(`There was no ${aPropertyName} schema generated for the ${currentSpec.specName}`); - } - it(`should produce a valid schema for the ${aPropertyName} property on ${interfaceName} for the ${currentSpec.specName}`, () => { - assertionsPerProperty[aPropertyName](aPropertyName, propertySchema); - }); - }); - - expect(Object.keys(assertionsPerProperty)).to.length( - Object.keys(definition.properties!).length, - `because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`, - ); - }); - }); - - allSpecs.forEach(currentSpec => { - describe(`for spec ${currentSpec.specName}`, () => { - it('should generate a definition description from a model jsdoc comment', () => { - const definition = getValidatedDefinition('TestModel', currentSpec); - expect(definition.description).to.equal('This is a description of a model'); - }); - - it('should generate single first example from jsdoc', () => { - const definition = getValidatedDefinition('TestModel', currentSpec); - if (!definition.example) { - throw new Error('No definition example.'); - } - - expect(definition.example).to.deep.equal({ - boolArray: [true, false], - boolValue: true, - dateValue: '2018-06-25T15:45:00Z', - id: 2, - modelValue: { - id: 3, - email: 'test(at)example.com', - }, - modelsArray: [], - numberArray: [1, 2, 3], - numberArrayReadonly: [1, 2, 3], - numberValue: 1, - optionalString: 'optional string', - strLiteralArr: ['Foo', 'Bar'], - strLiteralVal: 'Foo', - stringArray: ['string one', 'string two'], - stringValue: 'a string', - }); - }); - }); - }); - - allSpecs.forEach(currentSpec => { - describe(`for spec ${currentSpec.specName}`, () => { - it('should generate a default value from jsdoc', () => { - const definition = getValidatedDefinition('TestModel', currentSpec); - if (!definition.properties) { - throw new Error('No definition properties.'); - } - - expect(definition.properties.boolValue.default).to.equal('true'); - }); - }); - }); - }); - - describe('Class-based generation', () => { - allSpecs.forEach(currentSpec => { - const modelName = 'TestClassModel'; - const definition = getValidatedDefinition(modelName, currentSpec); - if (!definition.properties) { - throw new Error('Definition has no properties.'); - } - - const properties = definition.properties; + namespaces: (propertyName, propertySchema) => { + expect(propertySchema).to.deep.eq( + { + properties: { + typeHolder2: { + $ref: "#/definitions/Namespace2.TypeHolder", + description: undefined, + example: undefined, + format: undefined + }, + inModule: { + $ref: "#/definitions/Namespace2.Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + typeHolder1: { + $ref: "#/definitions/Namespace1.TypeHolder", + description: undefined, + example: undefined, + format: undefined + }, + inNamespace1: { + $ref: "#/definitions/Namespace1.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + simple: { + $ref: "#/definitions/NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: [ + "typeHolder2", + "inModule", + "typeHolder1", + "inNamespace1", + "simple" + ], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}` + ); - it('should generate a definition for referenced model', () => { - getValidatedDefinition(modelName, currentSpec); - }); + const typeHolder2Schema = getValidatedDefinition('Namespace2.TypeHolder', currentSpec); + expect(typeHolder2Schema).to.deep.eq( + { + properties: { + inModule: { + $ref: "#/definitions/Namespace2.Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + inNamespace2: { + $ref: "#/definitions/Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inModule", + "inNamespace2"], + type: "object", + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, + description: undefined + }, + `for property ${propertyName}.typeHolder2` + ); - it('should generate a required property from a required property', () => { - const propertyName = 'publicStringProperty'; - if (!properties[propertyName]) { - throw new Error(`Property '${propertyName}' was expected to exist.`); - } + const namespace2_namespace2_namespaceTypeSchema = getValidatedDefinition('Namespace2.Namespace2.NamespaceType', currentSpec); + expect(namespace2_namespace2_namespaceTypeSchema).to.deep.eq( + { + properties: { + inModule: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + other: { + $ref: "#/definitions/Namespace2.Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inModule"], + type: "object", + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + }, + `for property ${propertyName}.typeHolder2.inModule` + ); - expect(definition.required).to.contain(propertyName); - }); + const typeHolderSchema = getValidatedDefinition('Namespace1.TypeHolder', currentSpec); + expect(typeHolderSchema).to.deep.eq( + { + properties: { + inNamespace1_1: + { + $ref: "#/definitions/Namespace1.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + inNamespace1_2: { + $ref: "#/definitions/Namespace1.NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inNamespace1_1", "inNamespace1_2"], + type: "object", + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, + description: undefined + }, + `for property ${propertyName}.typeHolder1` + ); - it('should generate an optional property from an optional property', () => { - const propertyName = 'optionalPublicStringProperty'; - if (!properties[propertyName]) { - throw new Error(`Property '${propertyName}' was expected to exist.`); - } - }); + const namespace1_namespaceTypeSchema = getValidatedDefinition('Namespace1.NamespaceType', currentSpec); + expect(namespace1_namespaceTypeSchema).to.deep.eq( + { + properties: { + inFirstNamespace: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + inFirstNamespace2: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inFirstNamespace", "inFirstNamespace2"], + type: "object", + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + }, + `for property ${propertyName}.typeHolder1.inNamespace1_1` + ); - it('should generate a required property from a required property with no access modifier', () => { - const propertyName = 'stringProperty'; - if (!properties[propertyName]) { - throw new Error(`Property '${propertyName}' was expected to exist.`); - } + const namespace2_namespaceTypeSchema = getValidatedDefinition('Namespace2.NamespaceType', currentSpec); + expect(namespace2_namespaceTypeSchema).to.deep.eq( + { + properties: { + inSecondNamespace: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + }, + required: ["inSecondNamespace"], + type: "object", + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + }, + `for property ${propertyName}.typeHolder2.inNamespace2` + ); - expect(definition.required).to.contain(propertyName); - }); + const namespaceTypeSchema = getValidatedDefinition('NamespaceType', currentSpec); + expect(namespaceTypeSchema).to.deep.eq( + { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.simple` + ); + }, + defaults: (propertyName, propertySchema) => { + expect(propertySchema).to.deep.eq({ + default: undefined, + description: undefined, + example: undefined, + format: undefined, + properties: { + basic: { + $ref: "#/definitions/DefaultsClass", + description: undefined, + example: undefined, + format: undefined + }, + defaultNull: { + default: null, + description: undefined, + example: undefined, + format: undefined, + type: "string", + "x-nullable": true + }, + defaultObject: { + default: { + a: "a", + b: 2 + }, + description: undefined, + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + }, + b: { + default: undefined, + description: undefined, + example: undefined, + format: "double", + type: "number" + } + }, + required: ["b", "a"], + type: "object" + }, + defaultUndefined: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + }, + replacedTypes: { + $ref: "#/definitions/ReplaceTypes_DefaultsClass.boolean.string_", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["replacedTypes", "basic"], + type: "object" + }, + `for property ${propertyName}` + ); - it('should generate a required property from a required constructor var', () => { - const propertyName = 'publicConstructorVar'; + const basicSchema = getValidatedDefinition('DefaultsClass', currentSpec); + expect(basicSchema).to.deep.eq( + { + properties: { + boolValue1: { + type: "boolean", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue2: { + type: "boolean", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue3: { + type: "boolean", + default: false, + description: undefined, + example: undefined, + format: undefined + }, + boolValue4: { + type: "boolean", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + required: undefined, + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + }, + `for property ${propertyName}.basic` + ); + const replacedTypesSchema = getValidatedDefinition('ReplaceTypes_DefaultsClass.boolean.string_', currentSpec); + expect(replacedTypesSchema).to.deep.eq( + { + properties: { + boolValue1: { + type: "string", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue2: { + type: "string", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue3: { + type: "string", + default: false, + description: undefined, + example: undefined, + format: undefined + }, + boolValue4: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: undefined, + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.replacedTypes` + ); + }, + jsDocTypeNames: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.simple?.$ref).to.eq("#/definitions/Partial__a-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.commented?.$ref).to.eq("#/definitions/Partial__a_description-comment_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq("#/definitions/Partial__a_description-multiline%5Cncomment_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.defaultValue?.$ref).to.eq("#/definitions/Partial__a_default-true_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.deprecated?.$ref).to.eq("#/definitions/Partial__a_deprecated-true_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.validators?.$ref).to.eq("#/definitions/Partial__a_validators%3A_minLength%3A_value%3A3___-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.examples?.$ref).to.eq("#/definitions/Partial__a_example-example_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.extensions?.$ref).to.eq("#/definitions/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.ignored?.$ref).to.eq("#/definitions/Partial__a_ignored-true_-string__", `for property ${propertyName}`); + + expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(18, `for property ${propertyName}`); + + const simpleSchema = getValidatedDefinition('Partial__a-string__', currentSpec); + expect(simpleSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.simple` + ); + const commentedSchema = getValidatedDefinition('Partial__a_description-comment_-string__', currentSpec); + expect(commentedSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + description: "comment", + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.commented` + ); + const multilineCommentedSchema = getValidatedDefinition('Partial__a_description-multiline\\ncomment_-string__', currentSpec); + expect(multilineCommentedSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + description: "multiline\ncomment", + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.multilineCommented` + ); + const defaultValueSchema = getValidatedDefinition('Partial__a_default-true_-string__', currentSpec); + expect(defaultValueSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + default: "true", + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.defaultValue` + ); + const deprecatedSchema = getValidatedDefinition('Partial__a_deprecated-true_-string__', currentSpec); + expect(deprecatedSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + "x-deprecated": true, + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.deprecated` + ); + const validatorsSchema = getValidatedDefinition('Partial__a_validators:_minLength:_value:3___-string__', currentSpec); + expect(validatorsSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + minLength: 3, + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.validators` + ); + const examplesSchema = getValidatedDefinition('Partial__a_example-example_-string__', currentSpec); + expect(examplesSchema).to.deep.eq({ + default: undefined, + description: "Make all properties in T optional", + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: "example", + format: undefined, + type: "string" + } + }, + type: "object" + }, + `for property ${propertyName}.examples` + ); + const extensionsSchema = getValidatedDefinition('Partial__a_extensions:[_key-x-key-1.value-value-1_]_-string__', currentSpec); + expect(extensionsSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + "x-key-1": "value-1", + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.extensions` + ); + const ignoredSchema = getValidatedDefinition('Partial__a_ignored-true_-string__', currentSpec); + expect(ignoredSchema).to.deep.eq( + { + properties: { + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.ignored` + ); + const indexedSchema = getValidatedDefinition('Partial__[a-string]:string__', currentSpec); + expect(indexedSchema).to.deep.eq( + { + properties: { + }, + additionalProperties: { + type: "string" + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.indexedSimple` + ); + }, + jsdocMap: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.omitted?.$ref).to.eq("#/definitions/Omit_JsDocced.notRelevant_", `for property ${propertyName}`); + expect(propertySchema?.properties?.partial?.$ref).to.eq("#/definitions/Partial_JsDocced_", `for property ${propertyName}`); + expect(propertySchema?.properties?.replacedTypes?.$ref).to.eq("#/definitions/ReplaceStringAndNumberTypes_JsDocced_", `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleReplacedTypes?.$ref).to.eq("#/definitions/ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__", `for property ${propertyName}`); + expect(propertySchema?.properties?.postfixed?.$ref).to.eq("#/definitions/Postfixed_JsDocced._PostFix_", `for property ${propertyName}`); + expect(propertySchema?.properties?.values?.$ref).to.eq("#/definitions/Values_JsDocced_", `for property ${propertyName}`); + expect(propertySchema?.properties?.typesValues?.$ref).to.eq("#/definitions/InternalTypes_Values_JsDocced__", `for property ${propertyName}`); + expect(propertySchema?.properties?.onlyOneValue).to.deep.eq( + { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}.onlyOneValue` + ); + expect(propertySchema?.properties?.synonym?.$ref).to.eq("#/definitions/JsDoccedSynonym", `for property ${propertyName}`); + expect(propertySchema?.properties?.synonym2?.$ref).to.eq("#/definitions/JsDoccedSynonym2", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(10, `for property ${propertyName}`); + + const omittedSchema = getValidatedDefinition('Omit_JsDocced.notRelevant_', currentSpec); + expect(omittedSchema).to.deep.eq( + { + $ref: "#/definitions/Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__", + description: "Construct a type with the properties of T except for those in type K.", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.omitted` + ); + const omittedSchema2 = getValidatedDefinition('Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__', currentSpec); + expect(omittedSchema2).to.deep.eq({ + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined, + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined + } + }, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.omitted` + ); + const partialSchema = getValidatedDefinition('Partial_JsDocced_', currentSpec); + expect(partialSchema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + format: undefined, + example: undefined, + description: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + example: undefined, + description: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.partial` + ); + const replacedTypesSchema = getValidatedDefinition('ReplaceStringAndNumberTypes_JsDocced_', currentSpec); + expect(replacedTypesSchema).to.deep.eq( + { + $ref: "#/definitions/ReplaceTypes_JsDocced.string.number_", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.replacedTypes` + ); + const replacedTypes2Schema = getValidatedDefinition('ReplaceTypes_JsDocced.string.number_', currentSpec); + expect(replacedTypes2Schema).to.deep.eq({ + properties: { + stringValue: { + type: "number", + format: "double", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + }, + numberValue: { + type: "string", + default: 6, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.replacedTypes` + ); + const doubleReplacedTypesSchema = getValidatedDefinition('ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__', currentSpec); + expect(doubleReplacedTypesSchema).to.deep.eq( + { + $ref: "#/definitions/ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleReplacedTypes` + ); + const doubleReplacedTypes2Schema = getValidatedDefinition('ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_', currentSpec); + expect(doubleReplacedTypes2Schema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined, + }, + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleReplacedTypes` + ); + const postfixedSchema = getValidatedDefinition('Postfixed_JsDocced._PostFix_', currentSpec); + expect(postfixedSchema).to.deep.eq( + { + properties: { + stringValue_PostFix: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + numberValue_PostFix: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + } + }, + required: [ + "stringValue_PostFix", "numberValue_PostFix" + ], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.postfixed` + ); + const valuesSchema = getValidatedDefinition('Values_JsDocced_', currentSpec); + expect(valuesSchema).to.deep.eq( + { + properties: { + stringValue: { + properties: { + value: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["value"], + type: "object", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + properties: { + value: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + required: ["value"], + type: "object", + default: 6, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.values` + ); + const typesValuesSchema = getValidatedDefinition('InternalTypes_Values_JsDocced__', currentSpec); + expect(typesValuesSchema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined, + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.typesValues` + ); + + const synonymSchema = getValidatedDefinition('JsDoccedSynonym', currentSpec); + expect(synonymSchema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + required: ["stringValue", "numberValue"], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.synonym` + ); + const synonym2Schema = getValidatedDefinition('JsDoccedSynonym2', currentSpec); + expect(synonym2Schema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.synonym2` + ); + }, + duplicatedDefinitions: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.interfaces?.$ref).to.eq("#/definitions/DuplicatedInterface", `for property ${propertyName}`); + expect(propertySchema?.properties?.enums?.$ref).to.eq("#/definitions/DuplicatedEnum", `for property ${propertyName}`); + expect(propertySchema?.properties?.enumMember?.$ref).to.eq("#/definitions/DuplicatedEnum.C", `for property ${propertyName}`); + expect(propertySchema?.properties?.namespaceMember?.$ref).to.eq("#/definitions/DuplicatedEnum.D", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); + + const interfacesSchema = getValidatedDefinition('DuplicatedInterface', currentSpec); + expect(interfacesSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + b: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["a", "b"], + type: "object", + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, + description: undefined, + + }, + `for property ${propertyName}.interfaces` + ); + const enumsSchema = getValidatedDefinition('DuplicatedEnum', currentSpec); + expect(enumsSchema).to.deep.eq( + { + enum: ["AA", "BB", "CC"], + type: "string", + description: undefined + }, + `for property ${propertyName}.enums` + ); + const enumMemberSchema = getValidatedDefinition('DuplicatedEnum.C', currentSpec); + expect(enumMemberSchema).to.deep.eq( + { + enum: ["CC"], + type: "string", + description: undefined + }, + `for property ${propertyName}.enumMember` + ); + const namespaceMemberSchema = getValidatedDefinition('DuplicatedEnum.D', currentSpec); + expect(namespaceMemberSchema).to.deep.eq( + { + enum: ["DD"], + type: "string", + "x-nullable": false, + description: undefined, + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.namespaceMember` + ); + }, + mappeds: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.unionMap?.$ref).to.eq("#/definitions/Partial__a-string_-or-_b-number__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq("#/definitions/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq("#/definitions/Partial__a-string_-and-_b-number__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq("#/definitions/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq("#/definitions/Partial__a-string_-or-(_b-string_-and-_c-string_)_", `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq("#/definitions/Partial_(_a-string_-or-_b-string_)-and-_c-string__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); + + const unionMapSchema = getValidatedDefinition('Partial__a-string_-or-_b-number__', currentSpec); + expect(unionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + { + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.unionMap` + ); + const indexedUnionMapSchema = getValidatedDefinition('Partial__a-string_-or-_[b-string]:number__', currentSpec); + expect(indexedUnionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + { + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.indexedUnionMap` + ); + const doubleIndexedUnionMapSchema = getValidatedDefinition('Partial__[a-string]:string_-or-_[b-string]:number__', currentSpec); + expect(doubleIndexedUnionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + { + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleIndexedUnionMap` + ); + const intersectionMapSchema = getValidatedDefinition('Partial__a-string_-and-_b-number__', currentSpec); + expect(intersectionMapSchema).to.deep.eq({ + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + b: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.intersectionMap` + ); + const indexedIntersectionMapSchema = getValidatedDefinition('Partial__a-string_-and-_[b-string]:number__', currentSpec); + expect(indexedIntersectionMapSchema).to.deep.eq({ + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + additionalProperties: { + type: "number", + format: "double" + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.indexedIntersectionMap` + ); + const doubleIndexedIntersectionMapSchema = getValidatedDefinition('Partial__[a-string]:string_-and-_[b-number]:number__', currentSpec); + expect(doubleIndexedIntersectionMapSchema).to.deep.eq({ //Unions are not supported in OpenAPI 2 + properties: {}, + additionalProperties: { + type: "object" + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleIndexedIntersectionMap` + ); + const parenthesizedMapSchema = getValidatedDefinition('Partial__a-string_-or-(_b-string_-and-_c-string_)_', currentSpec); + expect(parenthesizedMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + { + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.parenthesizedMap` + ); + const parenthesizedMap2Schema = getValidatedDefinition('Partial_(_a-string_-or-_b-string_)-and-_c-string__', currentSpec); + expect(parenthesizedMap2Schema).to.deep.eq( //Unions are not supported in OpenAPI 2 + { + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.parenthesizedMap2` + ); + }, + conditionals: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.simpeConditional).to.deep.eq({ + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}`); + expect(propertySchema?.properties?.simpeFalseConditional).to.deep.eq({ + type: "boolean", + format: undefined, + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}`); + expect(propertySchema?.properties?.typedConditional?.$ref).to.eq("#/definitions/Conditional_string.string.number.boolean_", `for property ${propertyName}`); + expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq("#/definitions/Conditional_string.number.number.boolean_", `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq("#/definitions/Dummy_Conditional_string.string.number.boolean__", `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq("#/definitions/Dummy_Conditional_string.number.number.boolean__", `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq("#/definitions/Partial_stringextendsstring%3F_a-number_-never_", `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq("#/definitions/Partial_Conditional_string.string._a-number_.never__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); + + const typedConditionalSchema = getValidatedDefinition('Conditional_string.string.number.boolean_', currentSpec); + expect(typedConditionalSchema).to.deep.eq({ + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedConditional`); + const typedFalseConditionalSchema = getValidatedDefinition('Conditional_string.number.number.boolean_', currentSpec); + expect(typedFalseConditionalSchema).to.deep.eq({ + type: "boolean", + format: undefined, + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedFalseConditional`); + const dummyConditionalSchema = getValidatedDefinition('Dummy_Conditional_string.string.number.boolean__', currentSpec); + expect(dummyConditionalSchema?.$ref).to.eq("#/definitions/Conditional_string.string.number.boolean_", `for property ${propertyName}.dummyConditional`); + const dummyFalseConditionalSchema = getValidatedDefinition('Dummy_Conditional_string.number.number.boolean__', currentSpec); + expect(dummyFalseConditionalSchema?.$ref).to.eq("#/definitions/Conditional_string.number.number.boolean_", `for property ${propertyName}.dummyFalseConditional`); + const mappedConditionalSchema = getValidatedDefinition('Partial_stringextendsstring?_a-number_-never_', currentSpec); + expect(mappedConditionalSchema).to.deep.eq({ + properties: { + a: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + type: "object", + description: "Make all properties in T optional", + example: undefined, + default: undefined, + format: undefined, + }, + `for property ${propertyName}.mappedConditional`); + const mappedTypedConditionalSchema = getValidatedDefinition('Partial_Conditional_string.string._a-number_.never__', currentSpec); + expect(mappedTypedConditionalSchema).to.deep.eq(mappedConditionalSchema, `for property ${propertyName}.mappedTypedConditional`); + }, + typeOperators: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.keysOfAny?.$ref).to.eq("#/definitions/KeysMember", `for property ${propertyName}`); + expect(propertySchema?.properties?.keysOfInterface?.$ref).to.eq("#/definitions/KeysMember_NestedTypeLiteral_", `for property ${propertyName}`); + expect(propertySchema?.properties?.simple).to.deep.eq({ + type: "string", + enum: ["a", "b", "e"], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.simple`); + expect(propertySchema?.properties?.keyofItem).to.deep.eq({ + type: "string", + enum: ["c", "d"], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.keyofItem`); + expect(propertySchema?.properties?.keyofAnyItem).to.deep.eq({ //Unions are not supported in OpenAPI 2 (string | number) + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "object" + }, + `for property ${propertyName}.keyofAnyItem`); + expect(propertySchema?.properties?.keyofAny).to.deep.eq({ //Unions are not supported in OpenAPI 2 (string | number) + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "object" + }, + `for property ${propertyName}.keyofAny`); + expect(propertySchema?.properties?.stringLiterals).to.deep.eq({ + type: "string", + enum: ["A", "B", "C"], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.stringLiterals`); + expect(propertySchema?.properties?.stringAndNumberLiterals).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) + enum: ["A", "B", "3"], + type: "string", + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.stringAndNumberLiterals`); + expect(propertySchema?.properties?.keyofEnum).to.deep.eq({ + type: "string", + enum: ["A", "B", "C"], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofEnum`); + expect(propertySchema?.properties?.numberAndStringKeys).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) + enum: ["a", "3", "4"], + type: "string", + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.numberAndStringKeys`); + expect(propertySchema?.properties?.oneStringKeyInterface).to.deep.eq({ + type: "string", + enum: ["a"], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneStringKeyInterface`); + expect(propertySchema?.properties?.oneNumberKeyInterface).to.deep.eq({ + type: "number", + enum: [3], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneNumberKeyInterface`); + expect(propertySchema?.properties?.indexStrings).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.indexStrings`); + expect(propertySchema?.properties?.indexNumbers).to.deep.eq({ + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}.indexNumbers`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(14, `for property ${propertyName}`); + + const keysOfAnySchema = getValidatedDefinition('KeysMember', currentSpec); + expect(keysOfAnySchema).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) + properties: { + keys: { + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined, + } + }, + required: ["keys"], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keysOfAny`); + + const keysOfInterfaceSchema = getValidatedDefinition('KeysMember_NestedTypeLiteral_', currentSpec); + expect(keysOfInterfaceSchema).to.deep.eq({ + properties: { + keys: { + type: "string", + enum: ["a", "b", "e"], + "x-nullable": false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + } + }, + required: ["keys"], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keysOfInterface`); + }, + nestedTypes: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.multiplePartial?.$ref).to.eq("#/definitions/Partial_Partial__a-string___", `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField?.$ref).to.eq("#/definitions/Partial_SeparateField_Partial__a-string--b-string__.a__", `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField2?.$ref).to.eq("#/definitions/Partial_SeparateField_Partial__a-string--b-string__.a-or-b__", `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField3?.$ref).to.eq("#/definitions/Partial_SeparateField_Partial__a-string--b-number__.a-or-b__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); + + const multiplePartialSchema = getValidatedDefinition('Partial_Partial__a-string___', currentSpec); + expect(multiplePartialSchema).to.deep.eq({ + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.multiplePartial`); + const separateFieldSchema = getValidatedDefinition('Partial_SeparateField_Partial__a-string--b-string__.a__', currentSpec); + expect(separateFieldSchema).to.deep.eq({ + properties: { + omitted: { + $ref: "#/definitions/Omit_Partial__a-string--b-string__.a_", + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField`); + const separateFieldInternalSchema = getValidatedDefinition('Omit_Partial__a-string--b-string__.a_', currentSpec); + expect(separateFieldInternalSchema).to.deep.eq({ + $ref: "#/definitions/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__", + description: "Construct a type with the properties of T except for those in type K.", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField.omitted`); + + const separateFieldInternal2Schema = getValidatedDefinition('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__', currentSpec); + expect(separateFieldInternal2Schema).to.deep.eq({ + properties: { + b: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField.omitted`); + + const separateField2Schema = getValidatedDefinition('Partial_SeparateField_Partial__a-string--b-string__.a-or-b__', currentSpec); + expect(separateField2Schema).to.deep.eq({ + properties: { + omitted: { + $ref: "#/definitions/Omit_Partial__a-string--b-string__.a-or-b_", + description: undefined, + example: undefined, + format: undefined + }, + field: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField2`); + const separateField2InternalSchema = getValidatedDefinition('Omit_Partial__a-string--b-string__.a-or-b_', currentSpec); + expect(separateField2InternalSchema?.$ref).to.eq("#/definitions/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__", + `for property ${propertyName}.separateField2.omitted` + ); + const separateField2Internal2Schema = getValidatedDefinition('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__', currentSpec); + expect(separateField2Internal2Schema).to.deep.eq({ + properties: {}, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField2.omitted`); + + const separateField3Schema = getValidatedDefinition('Partial_SeparateField_Partial__a-string--b-number__.a-or-b__', currentSpec); + expect(separateField3Schema).to.deep.eq({ //Unions are not supported in OpenAPI 2 + properties: + { + omitted: { + $ref: "#/definitions/Omit_Partial__a-string--b-number__.a-or-b_", + description: undefined, + example: undefined, + format: undefined + }, + field: { + type: "object", + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField3`); + const separateField3InternalSchema = getValidatedDefinition('Omit_Partial__a-string--b-number__.a-or-b_', currentSpec); + expect(separateField3InternalSchema?.$ref).to.eq("#/definitions/Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__", + `for property ${propertyName}.separateField3.omitted` + ); + const separateField3Internal2Schema = getValidatedDefinition('Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__', currentSpec); + expect(separateField3Internal2Schema).to.deep.eq({ + properties: {}, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField3.omitted`); + } + }; + + Object.keys(assertionsPerProperty).forEach(aPropertyName => { + const propertySchema = definition.properties![aPropertyName]; + if (!propertySchema) { + throw new Error(`There was no ${aPropertyName} schema generated for the ${currentSpec.specName}`); + } + it(`should produce a valid schema for the ${aPropertyName} property on ${interfaceName} for the ${currentSpec.specName}`, () => { + assertionsPerProperty[aPropertyName](aPropertyName, propertySchema); + }); + }); + + expect(Object.keys(assertionsPerProperty)).to.length( + Object.keys(definition.properties!).length, + `because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`, + ); + }); + }); + + allSpecs.forEach(currentSpec => { + describe(`for spec ${currentSpec.specName}`, () => { + it('should generate a definition description from a model jsdoc comment', () => { + const definition = getValidatedDefinition('TestModel', currentSpec); + expect(definition.description).to.equal('This is a description of a model'); + }); + + it('should generate single first example from jsdoc', () => { + const definition = getValidatedDefinition('TestModel', currentSpec); + if (!definition.example) { + throw new Error('No definition example.'); + } + + expect(definition.example).to.deep.equal({ + boolArray: [true, false], + boolValue: true, + dateValue: '2018-06-25T15:45:00Z', + id: 2, + modelValue: { + id: 3, + email: 'test(at)example.com', + }, + modelsArray: [], + numberArray: [1, 2, 3], + numberArrayReadonly: [1, 2, 3], + numberValue: 1, + optionalString: 'optional string', + strLiteralArr: ['Foo', 'Bar'], + strLiteralVal: 'Foo', + stringArray: ['string one', 'string two'], + stringValue: 'a string', + }); + }); + }); + }); + + allSpecs.forEach(currentSpec => { + describe(`for spec ${currentSpec.specName}`, () => { + it('should generate a default value from jsdoc', () => { + const definition = getValidatedDefinition('TestModel', currentSpec); + if (!definition.properties) { + throw new Error('No definition properties.'); + } + + expect(definition.properties.boolValue.default).to.equal(true); + }); + }); + }); + }); + + describe('Class-based generation', () => { + allSpecs.forEach(currentSpec => { + const modelName = 'TestClassModel'; + const definition = getValidatedDefinition(modelName, currentSpec); + if (!definition.properties) { + throw new Error('Definition has no properties.'); + } + + const properties = definition.properties; + + it('should generate a definition for referenced model', () => { + getValidatedDefinition(modelName, currentSpec); + }); + + it('should generate a required property from a required property', () => { + const propertyName = 'publicStringProperty'; + if (!properties[propertyName]) { + throw new Error(`Property '${propertyName}' was expected to exist.`); + } + + expect(definition.required).to.contain(propertyName); + }); + + it('should generate an optional property from an optional property', () => { + const propertyName = 'optionalPublicStringProperty'; + if (!properties[propertyName]) { + throw new Error(`Property '${propertyName}' was expected to exist.`); + } + }); + + it('should generate a required property from a required property with no access modifier', () => { + const propertyName = 'stringProperty'; + if (!properties[propertyName]) { + throw new Error(`Property '${propertyName}' was expected to exist.`); + } + + expect(definition.required).to.contain(propertyName); + }); + + it('should generate a required property from a required constructor var', () => { + const propertyName = 'publicConstructorVar'; if (!properties[propertyName]) { throw new Error(`Property '${propertyName}' was expected to exist.`); } @@ -2005,3 +3507,7 @@ describe('Definition generation', () => { }); }); }); + + + + diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts index 7bc0358e1..12021c24d 100644 --- a/tests/unit/swagger/schemaDetails3.spec.ts +++ b/tests/unit/swagger/schemaDetails3.spec.ts @@ -1,13 +1,12 @@ -import { expect } from 'chai'; -import 'mocha'; +import { ExtendedSpecConfig } from '@tsoa/cli/cli'; import { MetadataGenerator } from '@tsoa/cli/metadataGeneration/metadataGenerator'; -import { Tsoa } from '@tsoa/runtime'; import { SpecGenerator3 } from '@tsoa/cli/swagger/specGenerator3'; -import { Swagger } from '@tsoa/runtime'; +import { Swagger, Tsoa } from '@tsoa/runtime'; +import { expect } from 'chai'; +import 'mocha'; +import { versionMajorMinor } from 'typescript'; import { getDefaultExtendedOptions } from '../../fixtures/defaultOptions'; import { TestModel } from '../../fixtures/testModel'; -import { ExtendedSpecConfig } from '@tsoa/cli/cli'; -import { versionMajorMinor } from 'typescript'; describe('Definition generation for OpenAPI 3.0.0', () => { const metadataGet = new MetadataGenerator('./fixtures/controllers/getController.ts').Generate(); @@ -1201,7 +1200,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, boolValue: (propertyName, propertySchema) => { expect(propertySchema.type).to.eq('boolean', `for property ${propertyName}.type`); - expect(propertySchema.default).to.eq('true', `for property ${propertyName}.default`); + expect(propertySchema.default).to.eq(true, `for property ${propertyName}.default`); expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`); }, boolArray: (propertyName, propertySchema) => { @@ -1598,6 +1597,10 @@ describe('Definition generation for OpenAPI 3.0.0', () => { default: undefined, }, }, + required: [ + 'record-foo', + 'record-bar' + ], type: 'object', default: undefined, example: undefined, @@ -1633,6 +1636,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { default: undefined, }, }, + required: ["1", "2"], type: 'object', default: undefined, example: undefined, @@ -1641,37 +1645,53 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }); }, stringRecord: (propertyName, propertySchema) => { - expect(propertySchema).to.be.deep.eq({ - properties: {}, + expect(propertySchema.$ref).to.eq('#/components/schemas/Record_string._data-string__'); + const schema = getComponentSchema('Record_string._data-string__', currentSpec); + expect(schema).to.be.deep.eq({ additionalProperties: { properties: { - data: { type: 'string', description: undefined, example: undefined, format: undefined, default: undefined }, + data: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + } }, - required: ['data'], - type: 'object', + required: ["data"], + type: "object" }, - type: 'object', default: undefined, + description: "Construct a type with a set of properties K of type T", example: undefined, format: undefined, - description: undefined, + properties: {}, + type: "object" }); }, numberRecord: (propertyName, propertySchema) => { - expect(propertySchema).to.be.deep.eq({ - properties: {}, + expect(propertySchema.$ref).to.eq('#/components/schemas/Record_number._data-string__'); + const schema = getComponentSchema('Record_number._data-string__', currentSpec); + expect(schema).to.be.deep.eq({ additionalProperties: { properties: { - data: { type: 'string', description: undefined, example: undefined, format: undefined, default: undefined }, + data: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + } }, - required: ['data'], - type: 'object', + required: ["data"], + type: "object" }, - type: 'object', default: undefined, + description: "Construct a type with a set of properties K of type T", example: undefined, format: undefined, - description: undefined, + properties: {}, + type: "object" }); }, modelsObjectIndirect: (propertyName, propertySchema) => { @@ -1813,7 +1833,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { example: 42, minimum: 42, maximum: 42, - default: '42', + default: 42, }); const dateAliasSchema = getComponentSchema('DateAlias', currentSpec); @@ -2203,7 +2223,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { list: { - items: { $ref: '#/components/schemas/ThingContainerWithTitle_string_' }, + items: { type: 'number', format: 'double' }, type: 'array', default: undefined, description: undefined, @@ -2236,6 +2256,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { type: 'string', }, }, + required: ['id'], type: 'object', }); @@ -2248,7 +2269,6 @@ describe('Definition generation for OpenAPI 3.0.0', () => { default: undefined, description: undefined, enum: ['OK', 'KO'], - nullable: false, example: undefined, format: undefined, type: 'string', @@ -2261,7 +2281,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { format: undefined, }, indexedResponseObject: { - $ref: '#/components/schemas/Record_id.any_', + $ref: '#/components/schemas/Record_id._myProp1-string__', description: undefined, example: undefined, format: undefined, @@ -2269,7 +2289,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { indexedType: { type: 'string', default: undefined, description: undefined, format: undefined, example: undefined }, indexedTypeToClass: { $ref: '#/components/schemas/IndexedClass', description: undefined, format: undefined, example: undefined }, indexedTypeToInterface: { $ref: '#/components/schemas/IndexedInterface', description: undefined, format: undefined, example: undefined }, - indexedTypeToAlias: { $ref: '#/components/schemas/IndexedInterfaceAlias', description: undefined, format: undefined, example: undefined }, + indexedTypeToAlias: { $ref: '#/components/schemas/IndexedInterface', description: undefined, format: undefined, example: undefined }, arrayUnion: { default: undefined, description: undefined, @@ -2598,6 +2618,21 @@ describe('Definition generation for OpenAPI 3.0.0', () => { default: undefined, }, }, + required: [ + 'lastname:asc', + 'age:asc', + 'weight:asc', + 'human:asc', + 'gender:asc', + 'nicknames:asc', + 'firstname:desc', + 'lastname:desc', + 'age:desc', + 'weight:desc', + 'human:desc', + 'gender:desc', + 'nicknames:desc' + ], type: 'object', description: undefined, example: undefined, @@ -2653,136 +2688,1755 @@ describe('Definition generation for OpenAPI 3.0.0', () => { `for property ${propertyName}`, ); }, - }; - - const testModel = currentSpec.spec.components.schemas[interfaceModelName]; - (Object.keys(assertionsPerProperty) as Array).forEach(aPropertyName => { - if (!testModel) { - throw new Error(`There was no schema generated for the ${currentSpec.specName}`); - } - const propertySchema = testModel.properties![aPropertyName]; - if (!propertySchema) { - throw new Error(`There was no ${aPropertyName} schema generated for the ${currentSpec.specName}`); - } - it(`should produce a valid schema for the ${aPropertyName} property on ${interfaceModelName} for the ${currentSpec.specName}`, () => { - assertionsPerProperty[aPropertyName](aPropertyName, propertySchema); - }); - }); - - it('should make a choice about additionalProperties', () => { - if (currentSpec.specName === 'specWithNoImplicitExtras') { - expect(testModel.additionalProperties).to.eq(false, forSpec(currentSpec)); - } else { - expect(testModel.additionalProperties).to.eq(true, forSpec(currentSpec)); - } - }); - - it('should have only created schemas for properties on the TypeScript interface', () => { - expect(Object.keys(assertionsPerProperty)).to.length( - Object.keys(testModel.properties!).length, - `because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`, - ); - }); - }); - }); - }); - - describe('Deprecated class properties', () => { - allSpecs.forEach(currentSpec => { - const modelName = 'TestClassModel'; - // Assert - if (!currentSpec.spec.components.schemas) { - throw new Error('spec.components.schemas should have been truthy'); - } - const definition = currentSpec.spec.components.schemas[modelName]; - - if (!definition.properties) { - throw new Error('Definition has no properties.'); - } - - const properties = definition.properties; - - describe(`for ${currentSpec.specName}`, () => { - it('should only mark deprecated properties as deprecated', () => { - const deprecatedPropertyNames = ['deprecated1', 'deprecated2', 'deprecatedPublicConstructorVar', 'deprecatedPublicConstructorVar2']; - Object.entries(properties).forEach(([propertyName, property]) => { - if (deprecatedPropertyNames.includes(propertyName)) { - expect(property.deprecated).to.eq(true, `for property ${propertyName}.deprecated`); - } else { - expect(property.deprecated).to.eq(undefined, `for property ${propertyName}.deprecated`); - } - }); - }); - }); - }); - }); - - describe('Extension class properties', () => { - allSpecs.forEach(currentSpec => { - const modelName = 'TestClassModel'; - // Assert - if (!currentSpec.spec.components.schemas) { - throw new Error('spec.components.schemas should have been truthy'); - } - const definition = currentSpec.spec.components.schemas[modelName]; - - if (!definition.properties) { - throw new Error('Definition has no properties.'); - } - - const properties = definition.properties; - - describe(`for ${currentSpec.specName}`, () => { - it('should put vendor extension on extension field with decorator', () => { - const extensionPropertyName = 'extensionTest'; + namespaces: (propertyName, propertySchema) => { + expect(propertySchema).to.deep.eq( + { + properties: { + typeHolder2: { + $ref: "#/components/schemas/Namespace2.TypeHolder", + description: undefined, + example: undefined, + format: undefined + }, + inModule: { + $ref: "#/components/schemas/Namespace2.Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + typeHolder1: { + $ref: "#/components/schemas/Namespace1.TypeHolder", + description: undefined, + example: undefined, + format: undefined + }, + inNamespace1: { + $ref: "#/components/schemas/Namespace1.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + simple: { + $ref: "#/components/schemas/NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: [ + "typeHolder2", + "inModule", + "typeHolder1", + "inNamespace1", + "simple" + ], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}` + ); - Object.entries(properties).forEach(([propertyName, property]) => { - if (extensionPropertyName === propertyName) { - expect(property).to.have.property('x-key-1'); - expect(property).to.have.property('x-key-2'); + const typeHolder2Schema = getComponentSchema('Namespace2.TypeHolder', currentSpec); + expect(typeHolder2Schema).to.deep.eq( + { + properties: { + inModule: { + $ref: "#/components/schemas/Namespace2.Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + inNamespace2: { + $ref: "#/components/schemas/Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inModule", + "inNamespace2"], + type: "object", + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + description: undefined + }, + `for property ${propertyName}.typeHolder2` + ); - expect(property['x-key-1']).to.deep.equal('value-1'); - expect(property['x-key-2']).to.deep.equal('value-2'); - } - }); - }); + const namespace2_namespace2_namespaceTypeSchema = getComponentSchema('Namespace2.Namespace2.NamespaceType', currentSpec); + expect(namespace2_namespace2_namespaceTypeSchema).to.deep.eq( + { + properties: { + inModule: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + other: { + $ref: "#/components/schemas/Namespace2.Namespace2.NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inModule"], + type: "object", + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + }, + `for property ${propertyName}.typeHolder2.inModule` + ); - it('should put vendor extension on extension field with commetn', () => { - const extensionPropertyName = 'extensionComment'; + const typeHolderSchema = getComponentSchema('Namespace1.TypeHolder', currentSpec); + expect(typeHolderSchema).to.deep.eq( + { + properties: { + inNamespace1_1: + { + $ref: "#/components/schemas/Namespace1.NamespaceType", + description: undefined, + example: undefined, + format: undefined + }, + inNamespace1_2: { + $ref: "#/components/schemas/Namespace1.NamespaceType", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inNamespace1_1", "inNamespace1_2"], + type: "object", + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + description: undefined + }, + `for property ${propertyName}.typeHolder1` + ); - Object.entries(properties).forEach(([propertyName, property]) => { - if (extensionPropertyName === propertyName) { - expect(property).to.have.property('x-key-1'); - expect(property).to.have.property('x-key-2'); + const namespace1_namespaceTypeSchema = getComponentSchema('Namespace1.NamespaceType', currentSpec); + expect(namespace1_namespaceTypeSchema).to.deep.eq( + { + properties: { + inFirstNamespace: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + inFirstNamespace2: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["inFirstNamespace", "inFirstNamespace2"], + type: "object", + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + }, + `for property ${propertyName}.typeHolder1.inNamespace1_1` + ); - expect(property['x-key-1']).to.deep.equal('value-1'); - expect(property['x-key-2']).to.deep.equal('value-2'); - } - }); - }); - }); - }); - }); + const namespace2_namespaceTypeSchema = getComponentSchema('Namespace2.NamespaceType', currentSpec); + expect(namespace2_namespaceTypeSchema).to.deep.eq( + { + properties: { + inSecondNamespace: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + }, + required: ["inSecondNamespace"], + type: "object", + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + }, + `for property ${propertyName}.typeHolder2.inNamespace2` + ); - describe('mixed Enums', () => { - it('should combine to metaschema', () => { - // Arrange - const schemaName = 'tooManyTypesEnum'; - const metadataForEnums: Tsoa.Metadata = { - controllers: [], - referenceTypeMap: { - [schemaName]: { - refName: schemaName, - dataType: 'refEnum', - enums: [1, 'two', 3, 'four'], - deprecated: false, + const namespaceTypeSchema = getComponentSchema('NamespaceType', currentSpec); + expect(namespaceTypeSchema).to.deep.eq( + { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.simple` + ); }, - }, - }; - const swaggerConfig: ExtendedSpecConfig = { - outputDirectory: 'mockOutputDirectory', - entryFile: 'mockEntryFile', - noImplicitAdditionalProperties: 'ignore', + defaults: (propertyName, propertySchema) => { + expect(propertySchema).to.deep.eq({ + default: undefined, + description: undefined, + example: undefined, + format: undefined, + properties: { + basic: { + $ref: "#/components/schemas/DefaultsClass", + description: undefined, + example: undefined, + format: undefined, + }, + defaultNull: { + default: null, + description: undefined, + example: undefined, + format: undefined, + nullable: true, + type: "string" + }, + defaultObject: { + default: { + a: "a", + b: 2 + }, + description: undefined, + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + }, + b: { + default: undefined, + description: undefined, + example: undefined, + format: "double", + type: "number" + } + }, + required: ["b", "a"], + type: "object" + }, + defaultUndefined: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: "string" + }, + replacedTypes: { + $ref: "#/components/schemas/ReplaceTypes_DefaultsClass.boolean.string_", + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["replacedTypes", "basic"], + type: "object" + }, + `for property ${propertyName}` + ); + const basicSchema = getComponentSchema('DefaultsClass', currentSpec); + expect(basicSchema).to.deep.eq( + { + properties: { + boolValue1: { + type: "boolean", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue2: { + type: "boolean", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue3: { + type: "boolean", + default: false, + description: undefined, + example: undefined, + format: undefined + }, + boolValue4: { + type: "boolean", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + required: undefined, + description: undefined, + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + }, + `for property ${propertyName}.basic` + ); + const replacedTypesSchema = getComponentSchema('ReplaceTypes_DefaultsClass.boolean.string_', currentSpec); + expect(replacedTypesSchema).to.deep.eq( + { + properties: { + boolValue1: { + type: "string", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue2: { + type: "string", + default: true, + description: undefined, + example: undefined, + format: undefined + }, + boolValue3: { + type: "string", + default: false, + description: undefined, + example: undefined, + format: undefined + }, + boolValue4: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: undefined, + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.replacedTypes` + ); + }, + jsDocTypeNames: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.simple?.$ref).to.eq("#/components/schemas/Partial__a-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.commented?.$ref).to.eq("#/components/schemas/Partial__a_description-comment_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq("#/components/schemas/Partial__a_description-multiline%5Cncomment_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.defaultValue?.$ref).to.eq("#/components/schemas/Partial__a_default-true_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.deprecated?.$ref).to.eq("#/components/schemas/Partial__a_deprecated-true_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.validators?.$ref).to.eq("#/components/schemas/Partial__a_validators%3A_minLength%3A_value%3A3___-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.examples?.$ref).to.eq("#/components/schemas/Partial__a_example-example_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.extensions?.$ref).to.eq("#/components/schemas/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.ignored?.$ref).to.eq("#/components/schemas/Partial__a_ignored-true_-string__", `for property ${propertyName}`); + + expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(18, `for property ${propertyName}`); + + const simpleSchema = getComponentSchema('Partial__a-string__', currentSpec); + expect(simpleSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.simple` + ); + const commentedSchema = getComponentSchema('Partial__a_description-comment_-string__', currentSpec); + expect(commentedSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + description: "comment", + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.commented` + ); + const multilineCommentedSchema = getComponentSchema('Partial__a_description-multiline\\ncomment_-string__', currentSpec); + expect(multilineCommentedSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + description: "multiline\ncomment", + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.multilineCommented` + ); + const defaultValueSchema = getComponentSchema('Partial__a_default-true_-string__', currentSpec); + expect(defaultValueSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + default: "true", + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.defaultValue` + ); + const deprecatedSchema = getComponentSchema('Partial__a_deprecated-true_-string__', currentSpec); + expect(deprecatedSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + deprecated: true, + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.deprecated` + ); + const validatorsSchema = getComponentSchema('Partial__a_validators:_minLength:_value:3___-string__', currentSpec); + expect(validatorsSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + minLength: 3, + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.validators` + ); + const examplesSchema = getComponentSchema('Partial__a_example-example_-string__', currentSpec); + expect(examplesSchema).to.deep.eq({ + default: undefined, + description: "Make all properties in T optional", + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: "example", + format: undefined, + type: "string" + } + }, + type: "object" + }, + `for property ${propertyName}.examples` + ); + const extensionsSchema = getComponentSchema('Partial__a_extensions:[_key-x-key-1.value-value-1_]_-string__', currentSpec); + expect(extensionsSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + "x-key-1": "value-1", + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.extensions` + ); + const ignoredSchema = getComponentSchema('Partial__a_ignored-true_-string__', currentSpec); + expect(ignoredSchema).to.deep.eq( + { + properties: { + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.ignored` + ); + const indexedSchema = getComponentSchema('Partial__[a-string]:string__', currentSpec); + expect(indexedSchema).to.deep.eq( + { + properties: { + }, + additionalProperties: { + type: "string" + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.indexedSimple` + ); + }, + jsdocMap: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.omitted?.$ref).to.eq("#/components/schemas/Omit_JsDocced.notRelevant_", `for property ${propertyName}`); + expect(propertySchema?.properties?.partial?.$ref).to.eq("#/components/schemas/Partial_JsDocced_", `for property ${propertyName}`); + expect(propertySchema?.properties?.replacedTypes?.$ref).to.eq("#/components/schemas/ReplaceStringAndNumberTypes_JsDocced_", `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleReplacedTypes?.$ref).to.eq("#/components/schemas/ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__", `for property ${propertyName}`); + expect(propertySchema?.properties?.postfixed?.$ref).to.eq("#/components/schemas/Postfixed_JsDocced._PostFix_", `for property ${propertyName}`); + expect(propertySchema?.properties?.values?.$ref).to.eq("#/components/schemas/Values_JsDocced_", `for property ${propertyName}`); + expect(propertySchema?.properties?.typesValues?.$ref).to.eq("#/components/schemas/InternalTypes_Values_JsDocced__", `for property ${propertyName}`); + expect(propertySchema?.properties?.onlyOneValue).to.deep.eq( + { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}.onlyOneValue` + ); + expect(propertySchema?.properties?.synonym?.$ref).to.eq("#/components/schemas/JsDoccedSynonym", `for property ${propertyName}`); + expect(propertySchema?.properties?.synonym2?.$ref).to.eq("#/components/schemas/JsDoccedSynonym2", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(10, `for property ${propertyName}`); + + const omittedSchema = getComponentSchema('Omit_JsDocced.notRelevant_', currentSpec); + expect(omittedSchema).to.deep.eq( + { + $ref: "#/components/schemas/Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__", + description: "Construct a type with the properties of T except for those in type K.", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.omitted` + ); + const omittedSchema2 = getComponentSchema('Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__', currentSpec); + expect(omittedSchema2).to.deep.eq({ + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined, + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined + } + }, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.omitted` + ); + const partialSchema = getComponentSchema('Partial_JsDocced_', currentSpec); + expect(partialSchema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + format: undefined, + example: undefined, + description: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + example: undefined, + description: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.partial` + ); + const replacedTypesSchema = getComponentSchema('ReplaceStringAndNumberTypes_JsDocced_', currentSpec); + expect(replacedTypesSchema).to.deep.eq( + { + $ref: "#/components/schemas/ReplaceTypes_JsDocced.string.number_", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.replacedTypes` + ); + const replacedTypes2Schema = getComponentSchema('ReplaceTypes_JsDocced.string.number_', currentSpec); + expect(replacedTypes2Schema).to.deep.eq({ + properties: { + stringValue: { + type: "number", + format: "double", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + }, + numberValue: { + type: "string", + default: 6, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.replacedTypes` + ); + const doubleReplacedTypesSchema = getComponentSchema('ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__', currentSpec); + expect(doubleReplacedTypesSchema).to.deep.eq( + { + $ref: "#/components/schemas/ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleReplacedTypes` + ); + const doubleReplacedTypes2Schema = getComponentSchema('ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_', currentSpec); + expect(doubleReplacedTypes2Schema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined, + }, + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleReplacedTypes` + ); + const postfixedSchema = getComponentSchema('Postfixed_JsDocced._PostFix_', currentSpec); + expect(postfixedSchema).to.deep.eq( + { + properties: { + stringValue_PostFix: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + numberValue_PostFix: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + } + }, + required: [ + "stringValue_PostFix", "numberValue_PostFix" + ], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.postfixed` + ); + const valuesSchema = getComponentSchema('Values_JsDocced_', currentSpec); + expect(valuesSchema).to.deep.eq( + { + properties: { + stringValue: { + properties: { + value: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["value"], + type: "object", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + properties: { + value: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + required: ["value"], + type: "object", + default: 6, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.values` + ); + const typesValuesSchema = getComponentSchema('InternalTypes_Values_JsDocced__', currentSpec); + expect(typesValuesSchema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined, + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.typesValues` + ); + + const synonymSchema = getComponentSchema('JsDoccedSynonym', currentSpec); + expect(synonymSchema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + required: ["stringValue", "numberValue"], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.synonym` + ); + const synonym2Schema = getComponentSchema('JsDoccedSynonym2', currentSpec); + expect(synonym2Schema).to.deep.eq( + { + properties: { + stringValue: { + type: "string", + default: "def", + maxLength: 3, + description: undefined, + example: undefined, + format: undefined + }, + numberValue: { + type: "integer", + format: "int32", + default: 6, + description: undefined, + example: undefined + } + }, + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.synonym2` + ); + }, + duplicatedDefinitions: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.interfaces?.$ref).to.eq("#/components/schemas/DuplicatedInterface", `for property ${propertyName}`); + expect(propertySchema?.properties?.enums?.$ref).to.eq("#/components/schemas/DuplicatedEnum", `for property ${propertyName}`); + expect(propertySchema?.properties?.enumMember?.$ref).to.eq("#/components/schemas/DuplicatedEnum.C", `for property ${propertyName}`); + expect(propertySchema?.properties?.namespaceMember?.$ref).to.eq("#/components/schemas/DuplicatedEnum.D", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); + + const interfacesSchema = getComponentSchema('DuplicatedInterface', currentSpec); + expect(interfacesSchema).to.deep.eq( + { + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + b: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + required: ["a", "b"], + type: "object", + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, + description: undefined, + + }, + `for property ${propertyName}.interfaces` + ); + const enumsSchema = getComponentSchema('DuplicatedEnum', currentSpec); + expect(enumsSchema).to.deep.eq( + { + enum: ["AA", "BB", "CC"], + type: "string", + description: undefined + }, + `for property ${propertyName}.enums` + ); + const enumMemberSchema = getComponentSchema('DuplicatedEnum.C', currentSpec); + expect(enumMemberSchema).to.deep.eq( + { + enum: ["CC"], + type: "string", + description: undefined + }, + `for property ${propertyName}.enumMember` + ); + const namespaceMemberSchema = getComponentSchema('DuplicatedEnum.D', currentSpec); + expect(namespaceMemberSchema).to.deep.eq( + { + enum: ["DD"], + type: "string", + nullable: false, + description: undefined, + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.namespaceMember` + ); + }, + mappeds: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.unionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-or-_b-number__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-and-_b-number__", `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__", `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-or-(_b-string_-and-_c-string_)_", `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq("#/components/schemas/Partial_(_a-string_-or-_b-string_)-and-_c-string__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); + + const unionMapSchema = getComponentSchema('Partial__a-string_-or-_b-number__', currentSpec); + expect(unionMapSchema).to.deep.eq( + { + anyOf: [ + { + properties: { + a: { + type: "string" + } + }, + type: "object" + }, + { + properties: { + b: { + format: "double", + type: "number" + } + }, + type: "object" + } + ], + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.unionMap` + ); + const indexedUnionMapSchema = getComponentSchema('Partial__a-string_-or-_[b-string]:number__', currentSpec); + expect(indexedUnionMapSchema).to.deep.eq( + { + anyOf: [ + { + properties: { + a: { + type: "string" + } + }, + type: "object" + }, + { + additionalProperties: { + format: "double", + type: "number" + }, + properties: {}, + type: "object" + } + ], + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.indexedUnionMap` + ); + const doubleIndexedUnionMapSchema = getComponentSchema('Partial__[a-string]:string_-or-_[b-string]:number__', currentSpec); + expect(doubleIndexedUnionMapSchema).to.deep.eq( + { + anyOf: [ + { + additionalProperties: { + type: "string" + }, + properties: {}, + type: "object" + }, + { + additionalProperties: { + format: "double", + type: "number" + }, + properties: {}, + type: "object" + } + ], + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleIndexedUnionMap` + ); + const intersectionMapSchema = getComponentSchema('Partial__a-string_-and-_b-number__', currentSpec); + expect(intersectionMapSchema).to.deep.eq({ + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + b: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.intersectionMap` + ); + const indexedIntersectionMapSchema = getComponentSchema('Partial__a-string_-and-_[b-string]:number__', currentSpec); + expect(indexedIntersectionMapSchema).to.deep.eq({ + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + additionalProperties: { + type: "number", + format: "double" + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.indexedIntersectionMap` + ); + const doubleIndexedIntersectionMapSchema = getComponentSchema('Partial__[a-string]:string_-and-_[b-number]:number__', currentSpec); + expect(doubleIndexedIntersectionMapSchema).to.deep.eq({ + properties: {}, + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + format: "double", + type: "number" + } + ] + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.doubleIndexedIntersectionMap` + ); + const parenthesizedMapSchema = getComponentSchema('Partial__a-string_-or-(_b-string_-and-_c-string_)_', currentSpec); + expect(parenthesizedMapSchema).to.deep.eq( + { + anyOf: [ + { + properties: { + a: { + type: "string" + } + }, + type: "object" + }, + { + properties: { + b: { + type: "string" + }, + c: { + type: "string" + } + }, + type: "object" + } + ], + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.parenthesizedMap` + ); + const parenthesizedMap2Schema = getComponentSchema('Partial_(_a-string_-or-_b-string_)-and-_c-string__', currentSpec); + expect(parenthesizedMap2Schema).to.deep.eq( + { + anyOf: [ + { + properties: { + a: { + type: "string" + }, + c: { + type: "string" + } + }, + type: "object" + }, + { + properties: { + b: { + type: "string" + }, + c: { + type: "string" + } + }, + type: "object" + } + ], + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.parenthesizedMap2` + ); + }, + conditionals: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.simpeConditional).to.deep.eq({ + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}`); + expect(propertySchema?.properties?.simpeFalseConditional).to.deep.eq({ + type: "boolean", + format: undefined, + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}`); + expect(propertySchema?.properties?.typedConditional?.$ref).to.eq("#/components/schemas/Conditional_string.string.number.boolean_", `for property ${propertyName}`); + expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq("#/components/schemas/Conditional_string.number.number.boolean_", `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq("#/components/schemas/Dummy_Conditional_string.string.number.boolean__", `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq("#/components/schemas/Dummy_Conditional_string.number.number.boolean__", `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq("#/components/schemas/Partial_stringextendsstring%3F_a-number_-never_", `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq("#/components/schemas/Partial_Conditional_string.string._a-number_.never__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); + + const typedConditionalSchema = getComponentSchema('Conditional_string.string.number.boolean_', currentSpec); + expect(typedConditionalSchema).to.deep.eq({ + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedConditional`); + const typedFalseConditionalSchema = getComponentSchema('Conditional_string.number.number.boolean_', currentSpec); + expect(typedFalseConditionalSchema).to.deep.eq({ + type: "boolean", + format: undefined, + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedFalseConditional`); + const dummyConditionalSchema = getComponentSchema('Dummy_Conditional_string.string.number.boolean__', currentSpec); + expect(dummyConditionalSchema?.$ref).to.eq("#/components/schemas/Conditional_string.string.number.boolean_", `for property ${propertyName}.dummyConditional`); + const dummyFalseConditionalSchema = getComponentSchema('Dummy_Conditional_string.number.number.boolean__', currentSpec); + expect(dummyFalseConditionalSchema?.$ref).to.eq("#/components/schemas/Conditional_string.number.number.boolean_", `for property ${propertyName}.dummyFalseConditional`); + const mappedConditionalSchema = getComponentSchema('Partial_stringextendsstring?_a-number_-never_', currentSpec); + expect(mappedConditionalSchema).to.deep.eq({ + properties: { + a: { + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined, + } + }, + type: "object", + description: "Make all properties in T optional", + example: undefined, + default: undefined, + format: undefined, + }, + `for property ${propertyName}.mappedConditional`); + const mappedTypedConditionalSchema = getComponentSchema('Partial_Conditional_string.string._a-number_.never__', currentSpec); + expect(mappedTypedConditionalSchema).to.deep.eq(mappedConditionalSchema, `for property ${propertyName}.mappedTypedConditional`); + }, + typeOperators: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.keysOfAny?.$ref).to.eq("#/components/schemas/KeysMember", `for property ${propertyName}`); + expect(propertySchema?.properties?.keysOfInterface?.$ref).to.eq("#/components/schemas/KeysMember_NestedTypeLiteral_", `for property ${propertyName}`); + expect(propertySchema?.properties?.simple).to.deep.eq({ + type: "string", + enum: ["a", "b", "e"], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.simple`); + expect(propertySchema?.properties?.keyofItem).to.deep.eq({ + type: "string", + enum: ["c", "d"], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.keyofItem`); + expect(propertySchema?.properties?.keyofAnyItem).to.deep.eq({ + anyOf: [ + { + type: "string" + }, + { + format: "double", + type: "number" + } + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofAnyItem`); + expect(propertySchema?.properties?.keyofAny).to.deep.eq(propertySchema?.properties?.keyofAnyItem, + `for property ${propertyName}.keyofAny`); + expect(propertySchema?.properties?.stringLiterals).to.deep.eq({ + type: "string", + enum: ["A", "B", "C"], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.stringLiterals`); + expect(propertySchema?.properties?.stringAndNumberLiterals).to.deep.eq({ + anyOf: [ + { + enum: [ + "A", + "B" + ], + type: "string" + }, + { + enum: [ + 3 + ], + type: "number" + } + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.stringAndNumberLiterals`); + expect(propertySchema?.properties?.keyofEnum).to.deep.eq({ + type: "string", + enum: ["A", "B", "C"], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofEnum`); + expect(propertySchema?.properties?.numberAndStringKeys).to.deep.eq({ + anyOf: [ + { + enum: [ + "a" + ], + type: "string" + }, + { + enum: [ + 3, + 4 + ], + type: "number" + } + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.numberAndStringKeys`); + expect(propertySchema?.properties?.oneStringKeyInterface).to.deep.eq({ + type: "string", + enum: ["a"], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneStringKeyInterface`); + expect(propertySchema?.properties?.oneNumberKeyInterface).to.deep.eq({ + type: "number", + enum: [3], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneNumberKeyInterface`); + expect(propertySchema?.properties?.indexStrings).to.deep.eq({ + anyOf: [ + { + type: "string" + }, + { + format: "double", + type: "number" + } + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.indexStrings`); + expect(propertySchema?.properties?.indexNumbers).to.deep.eq({ + type: "number", + format: "double", + default: undefined, + description: undefined, + example: undefined + }, + `for property ${propertyName}.indexNumbers`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(14, `for property ${propertyName}`); + + const keysOfAnySchema = getComponentSchema('KeysMember', currentSpec); + expect(keysOfAnySchema).to.deep.eq({ + properties: { + keys: { + anyOf: [ + { + type: "string" + }, + { + format: "double", + type: "number" + } + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + } + }, + required: ["keys"], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keysOfAny`); + + const keysOfInterfaceSchema = getComponentSchema('KeysMember_NestedTypeLiteral_', currentSpec); + expect(keysOfInterfaceSchema).to.deep.eq({ + properties: { + keys: { + type: "string", + enum: ["a", "b", "e"], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + } + }, + required: ["keys"], + type: "object", + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keysOfInterface`); + }, + nestedTypes: (propertyName, propertySchema) => { + expect(propertySchema?.properties?.multiplePartial?.$ref).to.eq("#/components/schemas/Partial_Partial__a-string___", `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField?.$ref).to.eq("#/components/schemas/Partial_SeparateField_Partial__a-string--b-string__.a__", `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField2?.$ref).to.eq("#/components/schemas/Partial_SeparateField_Partial__a-string--b-string__.a-or-b__", `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField3?.$ref).to.eq("#/components/schemas/Partial_SeparateField_Partial__a-string--b-number__.a-or-b__", `for property ${propertyName}`); + + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); + + const multiplePartialSchema = getComponentSchema('Partial_Partial__a-string___', currentSpec); + expect(multiplePartialSchema).to.deep.eq({ + properties: { + a: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.multiplePartial`); + const separateFieldSchema = getComponentSchema('Partial_SeparateField_Partial__a-string--b-string__.a__', currentSpec); + expect(separateFieldSchema).to.deep.eq({ + properties: { + omitted: { + $ref: "#/components/schemas/Omit_Partial__a-string--b-string__.a_", + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField`); + const separateFieldInternalSchema = getComponentSchema('Omit_Partial__a-string--b-string__.a_', currentSpec); + expect(separateFieldInternalSchema).to.deep.eq({ + $ref: "#/components/schemas/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__", + description: "Construct a type with the properties of T except for those in type K.", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField.omitted`); + + const separateFieldInternal2Schema = getComponentSchema('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__', currentSpec); + expect(separateFieldInternal2Schema).to.deep.eq({ + properties: { + b: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField.omitted`); + + const separateField2Schema = getComponentSchema('Partial_SeparateField_Partial__a-string--b-string__.a-or-b__', currentSpec); + expect(separateField2Schema).to.deep.eq({ + properties: { + omitted: { + $ref: "#/components/schemas/Omit_Partial__a-string--b-string__.a-or-b_", + description: undefined, + example: undefined, + format: undefined + }, + field: { + type: "string", + default: undefined, + description: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField2`); + const separateField2InternalSchema = getComponentSchema('Omit_Partial__a-string--b-string__.a-or-b_', currentSpec); + expect(separateField2InternalSchema?.$ref).to.eq("#/components/schemas/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__", + `for property ${propertyName}.separateField2.omitted` + ); + const separateField2Internal2Schema = getComponentSchema('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__', currentSpec); + expect(separateField2Internal2Schema).to.deep.eq({ + properties: {}, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField2.omitted`); + + const separateField3Schema = getComponentSchema('Partial_SeparateField_Partial__a-string--b-number__.a-or-b__', currentSpec); + expect(separateField3Schema).to.deep.eq({ + properties: + { + omitted: { + $ref: "#/components/schemas/Omit_Partial__a-string--b-number__.a-or-b_", + description: undefined, + example: undefined, + format: undefined + }, + field: { + anyOf: [ + { + type: "string" + }, + { + format: "double", + type: "number" + } + ], + description: undefined, + default: undefined, + example: undefined, + format: undefined + } + }, + type: "object", + description: "Make all properties in T optional", + default: undefined, + example: undefined, + format: undefined + }, + `for property ${propertyName}.separateField3`); + const separateField3InternalSchema = getComponentSchema('Omit_Partial__a-string--b-number__.a-or-b_', currentSpec); + expect(separateField3InternalSchema?.$ref).to.eq("#/components/schemas/Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__", + `for property ${propertyName}.separateField3.omitted` + ); + const separateField3Internal2Schema = getComponentSchema('Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__', currentSpec); + expect(separateField3Internal2Schema).to.deep.eq({ + properties: {}, + type: "object", + description: "From T, pick a set of properties whose keys are in the union K", + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField3.omitted`); + } + }; + + const testModel = currentSpec.spec.components.schemas[interfaceModelName]; + (Object.keys(assertionsPerProperty) as Array).forEach(aPropertyName => { + if (!testModel) { + throw new Error(`There was no schema generated for the ${currentSpec.specName}`); + } + const propertySchema = testModel.properties![aPropertyName]; + if (!propertySchema) { + throw new Error(`There was no ${aPropertyName} schema generated for the ${currentSpec.specName}`); + } + it(`should produce a valid schema for the ${aPropertyName} property on ${interfaceModelName} for the ${currentSpec.specName}`, () => { + assertionsPerProperty[aPropertyName](aPropertyName, propertySchema); + }); + }); + + it('should make a choice about additionalProperties', () => { + if (currentSpec.specName === 'specWithNoImplicitExtras') { + expect(testModel.additionalProperties).to.eq(false, forSpec(currentSpec)); + } else { + expect(testModel.additionalProperties).to.eq(true, forSpec(currentSpec)); + } + }); + + it('should have only created schemas for properties on the TypeScript interface', () => { + expect(Object.keys(assertionsPerProperty)).to.length( + Object.keys(testModel.properties!).length, + `because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`, + ); + }); + }); + }); + }); + + describe('Deprecated class properties', () => { + allSpecs.forEach(currentSpec => { + const modelName = 'TestClassModel'; + // Assert + if (!currentSpec.spec.components.schemas) { + throw new Error('spec.components.schemas should have been truthy'); + } + const definition = currentSpec.spec.components.schemas[modelName]; + + if (!definition.properties) { + throw new Error('Definition has no properties.'); + } + + const properties = definition.properties; + + describe(`for ${currentSpec.specName}`, () => { + it('should only mark deprecated properties as deprecated', () => { + const deprecatedPropertyNames = ['deprecated1', 'deprecated2', 'deprecatedPublicConstructorVar', 'deprecatedPublicConstructorVar2']; + Object.entries(properties).forEach(([propertyName, property]) => { + if (deprecatedPropertyNames.includes(propertyName)) { + expect(property.deprecated).to.eq(true, `for property ${propertyName}.deprecated`); + } else { + expect(property.deprecated).to.eq(undefined, `for property ${propertyName}.deprecated`); + } + }); + }); + }); + }); + }); + + describe('Extension class properties', () => { + allSpecs.forEach(currentSpec => { + const modelName = 'TestClassModel'; + // Assert + if (!currentSpec.spec.components.schemas) { + throw new Error('spec.components.schemas should have been truthy'); + } + const definition = currentSpec.spec.components.schemas[modelName]; + + if (!definition.properties) { + throw new Error('Definition has no properties.'); + } + + const properties = definition.properties; + + describe(`for ${currentSpec.specName}`, () => { + it('should put vendor extension on extension field with decorator', () => { + const extensionPropertyName = 'extensionTest'; + + Object.entries(properties).forEach(([propertyName, property]) => { + if (extensionPropertyName === propertyName) { + expect(property).to.have.property('x-key-1'); + expect(property).to.have.property('x-key-2'); + + expect(property['x-key-1']).to.deep.equal('value-1'); + expect(property['x-key-2']).to.deep.equal('value-2'); + } + }); + }); + + it('should put vendor extension on extension field with commetn', () => { + const extensionPropertyName = 'extensionComment'; + + Object.entries(properties).forEach(([propertyName, property]) => { + if (extensionPropertyName === propertyName) { + expect(property).to.have.property('x-key-1'); + expect(property).to.have.property('x-key-2'); + + expect(property['x-key-1']).to.deep.equal('value-1'); + expect(property['x-key-2']).to.deep.equal('value-2'); + } + }); + }); + }); + }); + }); + + describe('mixed Enums', () => { + it('should combine to metaschema', () => { + // Arrange + const schemaName = 'tooManyTypesEnum'; + const metadataForEnums: Tsoa.Metadata = { + controllers: [], + referenceTypeMap: { + [schemaName]: { + refName: schemaName, + dataType: 'refEnum', + enums: [1, 'two', 3, 'four'], + deprecated: false, + }, + }, + }; + const swaggerConfig: ExtendedSpecConfig = { + outputDirectory: 'mockOutputDirectory', + entryFile: 'mockEntryFile', + noImplicitAdditionalProperties: 'ignore', }; // Act @@ -2841,8 +4495,8 @@ describe('Definition generation for OpenAPI 3.0.0', () => { /* tslint:disable:no-string-literal */ const ref = specDefault.spec.paths['/GetTest/ModuleRedeclarationAndNamespace'].get?.responses['200'].content?.['application/json']['schema']?.['$ref']; /* tslint:enable:no-string-literal */ - expect(ref).to.equal('#/components/schemas/TsoaTest.TestModel73'); - expect(getComponentSchema('TsoaTest.TestModel73', specDefault)).to.deep.equal({ + expect(ref).to.equal('#/components/schemas/tsoaTest.TsoaTest.TestModel73'); + expect(getComponentSchema('tsoaTest.TsoaTest.TestModel73', specDefault)).to.deep.equal({ additionalProperties: true, description: undefined, properties: { @@ -2913,29 +4567,8 @@ describe('Definition generation for OpenAPI 3.0.0', () => { expect(currentSpec.paths['/ParameterTest/Inline1'].post?.requestBody?.content['application/json'].schema?.title).to.equal(undefined); }); }); +}); - describe('defaults on required properties should be documented as optional', () => { - it('on models', () => { - const testModel = getComponentSchema('TestModel', specDefault); - - const prop = testModel.properties?.boolValue; - expect(prop).to.deep.eq( - { - type: 'boolean', - default: 'true', - description: undefined, - format: undefined, - example: undefined, - }, - 'boolValue has a default value of true', - ); - expect(testModel.required).to.be.an('array'); - expect(testModel.required).to.not.contain('boolValue', 'boolValue is not required'); - // Others should still be required - expect(testModel.required).to.contain('boolArray', 'boolArray is required'); - }); - }); -}); From 4a00474d55fc468f03fc592ca33d8786ec849d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Tue, 17 Oct 2023 11:02:56 +0200 Subject: [PATCH 02/12] chore: fix const, ===, etc from my prev commit --- .../metadataGeneration/metadataGenerator.ts | 10 +- .../src/metadataGeneration/typeResolver.ts | 388 ++++++++---------- tests/fixtures/testModel.ts | 91 ++-- 3 files changed, 227 insertions(+), 262 deletions(-) diff --git a/packages/cli/src/metadataGeneration/metadataGenerator.ts b/packages/cli/src/metadataGeneration/metadataGenerator.ts index 80b1b9eac..4646e2773 100644 --- a/packages/cli/src/metadataGeneration/metadataGenerator.ts +++ b/packages/cli/src/metadataGeneration/metadataGenerator.ts @@ -12,7 +12,7 @@ export class MetadataGenerator { public readonly typeChecker: TypeChecker; private readonly program: Program; private referenceTypeMap: Tsoa.ReferenceTypeMap = {}; - private modelDefinitionPosMap: { [name: string]: { fileName: string, pos: number }[] } = {}; + private modelDefinitionPosMap: { [name: string]: Array<{ fileName: string; pos: number }> } = {}; private expressionOrigNameMap: Record = {}; constructor( @@ -223,12 +223,12 @@ export class MetadataGenerator { return this.referenceTypeMap[refName]; } - public CheckModelUnicity(refName: string, positions: { fileName: string, pos: number }[]) { + public CheckModelUnicity(refName: string, positions: Array<{ fileName: string; pos: number }>) { if (!this.modelDefinitionPosMap[refName]) { this.modelDefinitionPosMap[refName] = positions; } else { - let origPositions = this.modelDefinitionPosMap[refName]; - if (!(origPositions.length == positions.length && positions.every(pos => origPositions.find(origPos => pos.pos == origPos.pos && pos.fileName == origPos.fileName)))) { + const origPositions = this.modelDefinitionPosMap[refName]; + if (!(origPositions.length === positions.length && positions.every(pos => origPositions.find(origPos => pos.pos === origPos.pos && pos.fileName === origPos.fileName)))) { throw new Error(`Found 2 different model definitions for model ${refName}: orig: ${JSON.stringify(origPositions)}, act: ${JSON.stringify(positions)}`); } } @@ -238,7 +238,7 @@ export class MetadataGenerator { if (!this.expressionOrigNameMap[formattedRefName]) { this.expressionOrigNameMap[formattedRefName] = refName; } else { - if (this.expressionOrigNameMap[formattedRefName] != refName) { + if (this.expressionOrigNameMap[formattedRefName] !== refName) { throw new Error(`Found 2 different type expressions for formatted name "${formattedRefName}": orig: "${this.expressionOrigNameMap[formattedRefName]}", act: "${refName}"`); } } diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index a9f623909..9b43fb613 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -10,16 +10,16 @@ import { getInitializerValue } from './initializer-value'; import { MetadataGenerator } from './metadataGenerator'; const localReferenceTypeCache: { [typeName: string]: Tsoa.ReferenceType } = {}; -const inProgressTypes: { [typeName: string]: ((realType: Tsoa.ReferenceType) => void)[] } = {}; +const inProgressTypes: { [typeName: string]: Array<(realType: Tsoa.ReferenceType) => void> } = {}; type OverrideToken = ts.Token | ts.Token | ts.Token | undefined; type UsableDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration | ts.PropertySignature | ts.TypeAliasDeclaration | ts.EnumMember; type UsableDeclarationWithoutPropertySignature = Exclude; interface Context { [name: string]: { - type: ts.TypeNode, - name: string - } + type: ts.TypeNode; + name: string; + }; } export class TypeResolver { @@ -29,7 +29,7 @@ export class TypeResolver { private readonly parentNode?: ts.Node, private context: Context = {}, private readonly referencer?: ts.Type, - ) { } + ) {} public static clearCache() { Object.keys(localReferenceTypeCache).forEach(key => { @@ -114,7 +114,7 @@ export class TypeResolver { const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce((res, propertySignature: ts.PropertySignature) => { const type = new TypeResolver(propertySignature.type as ts.TypeNode, this.current, propertySignature, this.context).resolve(); - let def = TypeResolver.getDefault(propertySignature); + const def = TypeResolver.getDefault(propertySignature); const property: Tsoa.Property = { example: this.getNodeExample(propertySignature), default: def, @@ -157,40 +157,37 @@ export class TypeResolver { } if (ts.isMappedTypeNode(this.typeNode)) { - let mappedTypeNode = this.typeNode; + const mappedTypeNode = this.typeNode; const getOneOrigDeclaration = (prop: ts.Symbol): ts.Declaration | undefined => { if (prop.declarations) { return prop.declarations[0]; } - let syntheticOrigin: ts.Symbol = (prop as any).links?.syntheticOrigin; - if (syntheticOrigin && syntheticOrigin.name == prop.name) { //Otherwise losts jsDoc like in intellisense + const syntheticOrigin: ts.Symbol = (prop as any).links?.syntheticOrigin; + if (syntheticOrigin && syntheticOrigin.name === prop.name) { + //Otherwise losts jsDoc like in intellisense return syntheticOrigin.declarations?.[0]; } return undefined; - } + }; const isIgnored = (prop: ts.Symbol) => { const declaration = getOneOrigDeclaration(prop); - return declaration !== undefined && - ( - getJSDocTagNames(declaration).some(tag => tag == 'ignore') || - ( - !ts.isPropertyDeclaration(declaration) && - !ts.isPropertySignature(declaration) && - !ts.isParameter(declaration) - ) - ); + return ( + declaration !== undefined && + (getJSDocTagNames(declaration).some(tag => tag === 'ignore') || (!ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration))) + ); }; - let calcMappedType = (type: ts.Type): Tsoa.Type => { - if (type.flags & ts.TypeFlags.Union) { //Intersections are not interesting somehow... - let types = (type as ts.UnionType).types; - let resolvedTypes = types.map(calcMappedType); + const calcMappedType = (type: ts.Type): Tsoa.Type => { + if (type.flags & ts.TypeFlags.Union) { + //Intersections are not interesting somehow... + const types = (type as ts.UnionType).types; + const resolvedTypes = types.map(calcMappedType); return { dataType: 'union', - types: resolvedTypes + types: resolvedTypes, }; } else if (type.flags & ts.TypeFlags.Object) { - let typeProperties: ts.Symbol[] = type.getProperties(); + const typeProperties: ts.Symbol[] = type.getProperties(); const properties: Tsoa.Property[] = typeProperties // Ignore methods, getter, setter and @ignored props .filter(property => isIgnored(property) === false) @@ -199,26 +196,22 @@ export class TypeResolver { const propertyType = this.current.typeChecker.getTypeOfSymbol(property); const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation)!; - let parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc + const parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc const type = new TypeResolver(typeNode, this.current, parent, this.context, propertyType).resolve(); - let required = !(property.flags & ts.SymbolFlags.Optional); + const required = !(property.flags & ts.SymbolFlags.Optional); const comments = property.getDocumentationComment(this.current.typeChecker); - let description = comments.length ? ts.displayPartsToString(comments) : undefined; + const description = comments.length ? ts.displayPartsToString(comments) : undefined; - let initializer = (parent as any)?.initializer; - let def = initializer ? getInitializerValue(initializer, this.current.typeChecker) : - parent ? TypeResolver.getDefault(parent) : undefined; + const initializer = (parent as any)?.initializer; + const def = initializer ? getInitializerValue(initializer, this.current.typeChecker) : parent ? TypeResolver.getDefault(parent) : undefined; // Push property return { name: property.getName(), required: required && def === undefined, - deprecated: parent ? ( - isExistJSDocTag(parent, tag => tag.tagName.text === 'deprecated') || - isDecorator(parent, identifier => identifier.text === 'Deprecated') - ) : false, + deprecated: parent ? isExistJSDocTag(parent, tag => tag.tagName.text === 'deprecated') || isDecorator(parent, identifier => identifier.text === 'Deprecated') : false, type, default: def, // validators are disjunct via types, so it is now OK. @@ -228,7 +221,7 @@ export class TypeResolver { description, format: parent ? this.getNodeFormat(parent) : undefined, example: parent ? this.getNodeExample(parent) : undefined, - extensions: parent ? this.getNodeExtension(parent) : undefined + extensions: parent ? this.getNodeExtension(parent) : undefined, }; }); @@ -236,14 +229,14 @@ export class TypeResolver { dataType: 'nestedObjectLiteral', properties, }; - let indexInfos = this.current.typeChecker.getIndexInfosOfType(type); - let indexTypes = indexInfos.map(indexInfo => { + const indexInfos = this.current.typeChecker.getIndexInfosOfType(type); + const indexTypes = indexInfos.map(indexInfo => { const typeNode = this.current.typeChecker.typeToTypeNode(indexInfo.type, undefined, ts.NodeBuilderFlags.NoTruncation)!; const type = new TypeResolver(typeNode, this.current, mappedTypeNode, this.context, indexInfo.type).resolve(); return type; }); if (indexTypes.length) { - if (indexTypes.length == 1) { + if (indexTypes.length === 1) { objectLiteral.additionalProperties = indexTypes[0]; } else { // { [k: string]: string; } & { [k: number]: number; } @@ -255,7 +248,7 @@ export class TypeResolver { //Every additional property key assumed as string objectLiteral.additionalProperties = { dataType: 'union', - types: indexTypes + types: indexTypes, }; } } @@ -264,44 +257,41 @@ export class TypeResolver { // Known issues & easy to implement: Partial, Partial, ... But I think a programmer not writes types like this throw new GenerateMetadataError(`Unhandled mapped type has found, flags: ${type.flags}`, this.typeNode); } - } + }; - let referencer = this.getReferencer(); - let result: Tsoa.Type = calcMappedType(referencer); + const referencer = this.getReferencer(); + const result: Tsoa.Type = calcMappedType(referencer); return result; } if (ts.isConditionalTypeNode(this.typeNode)) { - let referencer = this.getReferencer(); - let resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation)! + const referencer = this.getReferencer(); + const resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation)!; return new TypeResolver(resolvedNode, this.current, this.typeNode, this.context, referencer).resolve(); } // keyof if (ts.isTypeOperatorNode(this.typeNode) && this.typeNode.operator === ts.SyntaxKind.KeyOfKeyword) { - let type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode); + const type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode); if (type.getFlags() & ts.TypeFlags.Index) { // in case of generic: keyof T. Not handles all possible cases - let symbol = (type as ts.IndexType).type.getSymbol(); + const symbol = (type as ts.IndexType).type.getSymbol(); if (symbol && symbol.getFlags() & ts.TypeFlags.TypeParameter) { - let typeName = symbol.getEscapedName(); + const typeName = symbol.getEscapedName(); if (this.context[typeName as string]) { - let subResult = new TypeResolver(this.context[typeName as string].type, this.current, this.parentNode, this.context).resolve(); - if (subResult.dataType == 'any') { + const subResult = new TypeResolver(this.context[typeName as string].type, this.current, this.parentNode, this.context).resolve(); + if (subResult.dataType === 'any') { return { dataType: 'union', - types: [ - { dataType: 'string' }, - { dataType: 'double' } - ] + types: [{ dataType: 'string' }, { dataType: 'double' }], }; } - let properties = (subResult as Tsoa.RefObjectType).properties?.map(v => v.name); + const properties = (subResult as Tsoa.RefObjectType).properties?.map(v => v.name); if (properties) { return { dataType: 'enum', - enums: properties - } + enums: properties, + }; } else { throw new GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, this.context[typeName as string].type); } @@ -309,8 +299,8 @@ export class TypeResolver { } } else if (type.isUnion()) { const literals = type.types.filter((t): t is ts.LiteralType => t.isLiteral()); - let literalValues: (string | number)[] = []; - for (let literal of literals) { + const literalValues: Array = []; + for (const literal of literals) { if (typeof literal.value == 'number' || typeof literal.value == 'string') { literalValues.push(literal.value); } else { @@ -318,32 +308,24 @@ export class TypeResolver { } } - if (!literals.length && - type.types.length == 3 && - type.types.some(t => t.flags == ts.TypeFlags.String) && - type.types.some(t => t.flags == ts.TypeFlags.Number) && - type.types.some(t => t.flags == ts.TypeFlags.ESSymbol) + if ( + !literals.length && + type.types.length === 3 && + type.types.some(t => t.flags === ts.TypeFlags.String) && + type.types.some(t => t.flags === ts.TypeFlags.Number) && + type.types.some(t => t.flags === ts.TypeFlags.ESSymbol) ) { //keyof any return { dataType: 'union', - types: [ - { dataType: 'string' }, - { dataType: 'double' } - ] + types: [{ dataType: 'string' }, { dataType: 'double' }], }; } - if (!literals.length && - type.types.length == 2 && - type.types.some(t => t.flags == ts.TypeFlags.Number) && - type.types.some(t => t.flags == ts.TypeFlags.String)) { + if (!literals.length && type.types.length === 2 && type.types.some(t => t.flags === ts.TypeFlags.Number) && type.types.some(t => t.flags === ts.TypeFlags.String)) { return { dataType: 'union', - types: [ - { dataType: 'string' }, - { dataType: 'double' } - ] + types: [{ dataType: 'string' }, { dataType: 'double' }], }; } @@ -353,16 +335,16 @@ export class TypeResolver { console.warn(new GenerateMetaDataWarning(`Skipped non-literal type(s) ${problems.join(', ')}`, this.typeNode).toString()); } - let stringMembers = literalValues.filter(v => typeof v == 'string'); - let numberMembers = literalValues.filter(v => typeof v == 'number'); + const stringMembers = literalValues.filter(v => typeof v == 'string'); + const numberMembers = literalValues.filter(v => typeof v == 'number'); if (stringMembers.length && numberMembers.length) { return { dataType: 'union', types: [ { dataType: 'enum', enums: stringMembers }, { dataType: 'enum', enums: numberMembers }, - ] - } + ], + }; } return { dataType: 'enum', @@ -387,7 +369,7 @@ export class TypeResolver { }; } else if ((type.getFlags() & ts.TypeFlags.Number) !== 0) { return { - dataType: 'double' + dataType: 'double', }; } const indexedTypeName = this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode.type)); @@ -407,12 +389,7 @@ export class TypeResolver { if (type === undefined) { throw new GenerateMetadataError(`Could not determine ${numberIndexType ? 'number' : 'string'} index on ${this.current.typeChecker.typeToString(objectType)}`, this.typeNode); } - return new TypeResolver( - this.current.typeChecker.typeToTypeNode(type, this.typeNode.objectType, ts.NodeBuilderFlags.NoTruncation)!, - this.current, - this.typeNode, - this.context, - ).resolve(); + return new TypeResolver(this.current.typeChecker.typeToTypeNode(type, this.typeNode.objectType, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, this.context).resolve(); } // Indexed by literal @@ -434,12 +411,7 @@ export class TypeResolver { } const declaration = this.current.typeChecker.getTypeOfSymbolAtLocation(symbol, this.typeNode.objectType); try { - return new TypeResolver( - this.current.typeChecker.typeToTypeNode(declaration, this.typeNode.objectType, ts.NodeBuilderFlags.NoTruncation)!, - this.current, - this.typeNode, - this.context - ).resolve(); + return new TypeResolver(this.current.typeChecker.typeToTypeNode(declaration, this.typeNode.objectType, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, this.context).resolve(); } catch { throw new GenerateMetadataError( `Could not determine the keys on ${this.current.typeChecker.typeToString( @@ -474,10 +446,7 @@ export class TypeResolver { }; return stringLiteralEnum; } else { - throw new GenerateMetadataError( - `Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, - this.typeNode - ); + throw new GenerateMetadataError(`Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, this.typeNode); } } @@ -676,7 +645,7 @@ export class TypeResolver { if (this.referencer) { return this.referencer; } - if (this.typeNode.pos != -1) { + if (this.typeNode.pos !== -1) { return this.current.typeChecker.getTypeFromTypeNode(this.typeNode); } throw new GenerateMetadataError(`Can not succeeded to calculate referencer type.`, this.typeNode); @@ -696,18 +665,19 @@ export class TypeResolver { //Generates type name for type references private calcRefTypeName(type: ts.EntityName): string { - let getEntityName = (type: ts.EntityName): string => { + const getEntityName = (type: ts.EntityName): string => { if (ts.isIdentifier(type)) { return type.text; } return `${getEntityName(type.left)}.${type.right.text}`; - } + }; let name = getEntityName(type); - if (this.context[name]) { //resolve name only interesting if entity is not qualifiedName + if (this.context[name]) { + //resolve name only interesting if entity is not qualifiedName name = this.context[name].name; //Not needed to check unicity, because generic parameters are checked previously } else { - let declarations = this.getModelTypeDeclarations(type); + const declarations = this.getModelTypeDeclarations(type); //Two possible solutions for recognizing different types: // - Add declaration positions into type names (In an order). @@ -720,8 +690,8 @@ export class TypeResolver { // // The second was implemented, it not changes the usual type name formats. - let oneDeclaration = declarations[0]; //Every declarations should be in the same namespace hierarchy - let identifiers = name.split('.'); + const oneDeclaration = declarations[0]; //Every declarations should be in the same namespace hierarchy + const identifiers = name.split('.'); if (ts.isEnumMember(oneDeclaration)) { name = identifiers.slice(identifiers.length - 2).join('.'); } else { @@ -733,7 +703,7 @@ export class TypeResolver { while (!ts.isSourceFile(actNode)) { if (!(isFirst && ts.isEnumDeclaration(actNode)) && !ts.isModuleBlock(actNode)) { if (ts.isModuleDeclaration(actNode)) { - let moduleName = actNode.name.text; + const moduleName = actNode.name.text; name = `${moduleName}.${name}`; } else { throw new GenerateMetadataError(`This node kind is unknown: ${actNode.kind}`, type); @@ -743,9 +713,9 @@ export class TypeResolver { actNode = actNode.parent; } - let declarationPositions = declarations.map(declaration => ({ + const declarationPositions = declarations.map(declaration => ({ fileName: declaration.getSourceFile().fileName, - pos: declaration.pos + pos: declaration.pos, })); this.current.CheckModelUnicity(name, declarationPositions); } @@ -753,32 +723,31 @@ export class TypeResolver { } private calcMemberJsDocProperties(arg: ts.PropertySignature): string { - let def = TypeResolver.getDefault(arg); - let isDeprecated = isExistJSDocTag(arg, tag => tag.tagName.text === 'deprecated') || - isDecorator(arg, identifier => identifier.text === 'Deprecated'); + const def = TypeResolver.getDefault(arg); + const isDeprecated = isExistJSDocTag(arg, tag => tag.tagName.text === 'deprecated') || isDecorator(arg, identifier => identifier.text === 'Deprecated'); const symbol = this.getSymbolAtLocation(arg.name as ts.Node); const comments = symbol ? symbol.getDocumentationComment(this.current.typeChecker) : []; - let description = comments.length ? ts.displayPartsToString(comments) : undefined; + const description = comments.length ? ts.displayPartsToString(comments) : undefined; - let validators = getPropertyValidators(arg); - let format = this.getNodeFormat(arg); - let example = this.getNodeExample(arg); - let extensions = this.getNodeExtension(arg); - let isIgnored = getJSDocTagNames(arg).some(tag => tag == 'ignore'); + const validators = getPropertyValidators(arg); + const format = this.getNodeFormat(arg); + const example = this.getNodeExample(arg); + const extensions = this.getNodeExtension(arg); + const isIgnored = getJSDocTagNames(arg).some(tag => tag === 'ignore'); - let jsonObj = { + const jsonObj = { default: def, description, - validators: (validators && Object.keys(validators).length) ? validators : undefined, + validators: validators && Object.keys(validators).length ? validators : undefined, format, example: example !== undefined ? example : undefined, extensions: extensions.length ? extensions : undefined, deprecated: isDeprecated ? true : undefined, - ignored: isIgnored ? true : undefined - } - let keys: (keyof typeof jsonObj)[] = Object.keys(jsonObj) as any; - for (let key of keys) { + ignored: isIgnored ? true : undefined, + }; + const keys: Array = Object.keys(jsonObj) as any; + for (const key of keys) { if (jsonObj[key] === undefined) { delete jsonObj[key]; } @@ -792,9 +761,15 @@ export class TypeResolver { //Generates type name for type references private calcTypeName(arg: ts.TypeNode): string { if (ts.isLiteralTypeNode(arg)) { - let literalValue = this.getLiteralValue(arg); + const literalValue = this.getLiteralValue(arg); if (typeof literalValue == 'string') { - return `'${literalValue}'` + return `'${literalValue}'`; + } + if (literalValue === null) { + return 'null'; + } + if (typeof literalValue === 'boolean') { + return literalValue === true ? 'true' : 'false'; } return `${literalValue}`; } @@ -805,20 +780,20 @@ export class TypeResolver { if (ts.isTypeReferenceNode(arg) || ts.isExpressionWithTypeArguments(arg)) { return this.calcTypeReferenceTypeName(arg); } else if (ts.isTypeLiteralNode(arg)) { - let members = arg.members.map(member => { + const members = arg.members.map(member => { if (ts.isPropertySignature(member)) { - let name = (member.name as ts.Identifier).text; - let typeText = this.calcTypeName(member.type as ts.TypeNode); + const name = (member.name as ts.Identifier).text; + const typeText = this.calcTypeName(member.type as ts.TypeNode); return `"${name}"${member.questionToken ? '?' : ''}${this.calcMemberJsDocProperties(member)}: ${typeText}`; } else if (ts.isIndexSignatureDeclaration(member)) { - let typeText = this.calcTypeName(member.type as ts.TypeNode); - if (member.parameters.length != 1) { + const typeText = this.calcTypeName(member.type); + if (member.parameters.length !== 1) { throw new GenerateMetadataError(`Index signature parameters length != 1`, member); } - let indexType = member.parameters[0]; + const indexType = member.parameters[0]; if (ts.isParameter(indexType)) { - let indexName = (indexType.name as ts.Identifier).text; - let indexTypeText = this.calcTypeName(indexType.type as ts.TypeNode); + const indexName = (indexType.name as ts.Identifier).text; + const indexTypeText = this.calcTypeName(indexType.type as ts.TypeNode); if (indexType.questionToken) { throw new GenerateMetadataError(`Question token has found for an indexSignature declaration`, indexType); } @@ -832,53 +807,50 @@ export class TypeResolver { }); return `{${members.join('; ')}}`; } else if (ts.isArrayTypeNode(arg)) { - let typeName = this.calcTypeName(arg.elementType); + const typeName = this.calcTypeName(arg.elementType); return `${typeName}[]`; } else if (ts.isIntersectionTypeNode(arg)) { - let memberTypeNames = arg.types.map(type => this.calcTypeName(type)); + const memberTypeNames = arg.types.map(type => this.calcTypeName(type)); return memberTypeNames.join(' & '); } else if (ts.isUnionTypeNode(arg)) { - let memberTypeNames = arg.types.map(type => this.calcTypeName(type)); + const memberTypeNames = arg.types.map(type => this.calcTypeName(type)); return memberTypeNames.join(' | '); } else if (ts.isTypeOperatorNode(arg)) { - let subTypeName = this.calcTypeName(arg.type); + const subTypeName = this.calcTypeName(arg.type); let operatorName: string; - if (arg.operator == ts.SyntaxKind.KeyOfKeyword) { + if (arg.operator === ts.SyntaxKind.KeyOfKeyword) { operatorName = 'keyof'; - } else if (arg.operator == ts.SyntaxKind.ReadonlyKeyword) { + } else if (arg.operator === ts.SyntaxKind.ReadonlyKeyword) { operatorName = 'readonly'; } else { throw new GenerateMetadataError(`Unknown keyword has found: ${arg.operator}`, arg); } return `${operatorName} ${subTypeName}`; } else if (ts.isTypeQueryNode(arg)) { - let subTypeName = this.calcRefTypeName(arg.exprName); + const subTypeName = this.calcRefTypeName(arg.exprName); return `typeof ${subTypeName}`; } else if (ts.isIndexedAccessTypeNode(arg)) { - let objectTypeName = this.calcTypeName(arg.objectType); - let indexTypeName = this.calcTypeName(arg.indexType); + const objectTypeName = this.calcTypeName(arg.objectType); + const indexTypeName = this.calcTypeName(arg.indexType); return `${objectTypeName}[${indexTypeName}]`; - } else if (arg.kind == ts.SyntaxKind.UnknownKeyword) { + } else if (arg.kind === ts.SyntaxKind.UnknownKeyword) { return 'unknown'; - } else if (arg.kind == ts.SyntaxKind.AnyKeyword) { + } else if (arg.kind === ts.SyntaxKind.AnyKeyword) { return 'any'; - } else if (arg.kind == ts.SyntaxKind.NeverKeyword) { + } else if (arg.kind === ts.SyntaxKind.NeverKeyword) { return 'never'; } else if (ts.isConditionalTypeNode(arg)) { - let checkTypeName = this.calcTypeName(arg.checkType); - let extendsTypeName = this.calcTypeName(arg.extendsType); - let trueTypeName = this.calcTypeName(arg.trueType); - let falseTypeName = this.calcTypeName(arg.falseType); + const checkTypeName = this.calcTypeName(arg.checkType); + const extendsTypeName = this.calcTypeName(arg.extendsType); + const trueTypeName = this.calcTypeName(arg.trueType); + const falseTypeName = this.calcTypeName(arg.falseType); return `${checkTypeName} extends ${extendsTypeName} ? ${trueTypeName} : ${falseTypeName}`; } else if (ts.isParenthesizedTypeNode(arg)) { - let internalTypeName = this.calcTypeName(arg.type); + const internalTypeName = this.calcTypeName(arg.type); return `(${internalTypeName})`; //Parentheses are not really interesting. The type name generation adds parentheses for the clarity } - const warning = new GenerateMetaDataWarning( - `This kind (${arg.kind}) is unhandled, so the type will be any, and no type conflict checks will made`, - arg, - ); + const warning = new GenerateMetaDataWarning(`This kind (${arg.kind}) is unhandled, so the type will be any, and no type conflict checks will made`, arg); console.warn(warning.toString()); return 'any'; @@ -886,8 +858,8 @@ export class TypeResolver { //Generates type name for type references private calcTypeReferenceTypeName(node: ts.TypeReferenceType): string { - let type = TypeResolver.typeReferenceToEntityName(node); - let refTypeName = this.calcRefTypeName(type); + const type = TypeResolver.typeReferenceToEntityName(node); + const refTypeName = this.calcRefTypeName(type); if (Array.isArray(node.typeArguments)) { // Add typeArguments for Synthetic nodes (e.g. Record<> in TestClassModel.indexedResponse) const argumentsString = node.typeArguments.map(type => this.calcTypeName(type)); @@ -897,15 +869,15 @@ export class TypeResolver { } private getReferenceType(node: ts.TypeReferenceType, addToRefTypeMap = true): Tsoa.ReferenceType { - let type = TypeResolver.typeReferenceToEntityName(node); + const type = TypeResolver.typeReferenceToEntityName(node); - let name = this.calcTypeReferenceTypeName(node); - let refTypeName = this.getRefTypeName(name); + const name = this.calcTypeReferenceTypeName(node); + const refTypeName = this.getRefTypeName(name); this.current.CheckExpressionUnicity(refTypeName, name); this.context = this.typeArgumentsToContext(node, type); - let calcReferenceType = (): Tsoa.ReferenceType => { + const calcReferenceType = (): Tsoa.ReferenceType => { try { const existingType = localReferenceTypeCache[name]; if (existingType) { @@ -919,10 +891,10 @@ export class TypeResolver { inProgressTypes[name] = []; const declarations = this.getModelTypeDeclarations(type); - let referenceTypes: Tsoa.ReferenceType[] = []; - for (let declaration of declarations) { + const referenceTypes: Tsoa.ReferenceType[] = []; + for (const declaration of declarations) { if (ts.isTypeAliasDeclaration(declaration)) { - let referencer = node.pos != -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined; + const referencer = node.pos !== -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined; referenceTypes.push(this.getTypeAliasReference(declaration, refTypeName, referencer)); } else if (ts.isEnumDeclaration(declaration)) { referenceTypes.push(this.getEnumerateType(declaration, refTypeName)); @@ -938,7 +910,7 @@ export class TypeResolver { referenceTypes.push(this.getModelReference(declaration, refTypeName)); } } - let referenceType = TypeResolver.mergeReferenceTypes(referenceTypes); + const referenceType = TypeResolver.mergeReferenceTypes(referenceTypes); this.addToLocalReferenceTypeCache(name, referenceType); return referenceType; } catch (err) { @@ -946,8 +918,8 @@ export class TypeResolver { console.error(`There was a problem resolving type of '${name}'.`); throw err; } - } - let result = calcReferenceType(); + }; + const result = calcReferenceType(); if (addToRefTypeMap) { this.current.AddReferenceType(result); } @@ -955,19 +927,13 @@ export class TypeResolver { } private static mergeTwoRefEnumTypes(first: Tsoa.RefEnumType, second: Tsoa.RefEnumType): Tsoa.RefEnumType { - let description = first.description ? - second.description ? `${first.description}\n${second.description}` : first.description : - second.description; + const description = first.description ? (second.description ? `${first.description}\n${second.description}` : first.description) : second.description; - let deprecated = first.deprecated || second.deprecated; + const deprecated = first.deprecated || second.deprecated; - let enums = first.enums ? - second.enums ? [...first.enums, ...second.enums] : first.enums : - second.enums; + const enums = first.enums ? (second.enums ? [...first.enums, ...second.enums] : first.enums) : second.enums; - let enumVarnames = first.enumVarnames ? - second.enumVarnames ? [...first.enumVarnames, ...second.enumVarnames] : first.enumVarnames : - second.enumVarnames; + const enumVarnames = first.enumVarnames ? (second.enumVarnames ? [...first.enumVarnames, ...second.enumVarnames] : first.enumVarnames) : second.enumVarnames; return { dataType: 'refEnum', @@ -975,63 +941,58 @@ export class TypeResolver { enums, enumVarnames, refName: first.refName, - deprecated + deprecated, }; } private static mergeTwoRefObjectTypes(first: Tsoa.RefObjectType, second: Tsoa.RefObjectType): Tsoa.RefObjectType { - let description = first.description ? - second.description ? `${first.description}\n${second.description}` : first.description : - second.description; + const description = first.description ? (second.description ? `${first.description}\n${second.description}` : first.description) : second.description; - let deprecated = first.deprecated || second.deprecated; - let example = first.example || second.example; + const deprecated = first.deprecated || second.deprecated; + const example = first.example || second.example; - let properties = [ - ...first.properties, - ...second.properties.filter(prop => first.properties.every(firstProp => firstProp.name != prop.name)) - ]; + const properties = [...first.properties, ...second.properties.filter(prop => first.properties.every(firstProp => firstProp.name !== prop.name))]; - let mergeAdditionalTypes = (first: Tsoa.Type, second: Tsoa.Type): Tsoa.Type => { + const mergeAdditionalTypes = (first: Tsoa.Type, second: Tsoa.Type): Tsoa.Type => { return { dataType: 'union', - types: [first, second] + types: [first, second], }; - } + }; - let additionalProperties = first.additionalProperties ? - second.additionalProperties ? - mergeAdditionalTypes(first.additionalProperties, second.additionalProperties) : - first.additionalProperties : - second.additionalProperties; + const additionalProperties = first.additionalProperties + ? second.additionalProperties + ? mergeAdditionalTypes(first.additionalProperties, second.additionalProperties) + : first.additionalProperties + : second.additionalProperties; - let result: Tsoa.RefObjectType = { + const result: Tsoa.RefObjectType = { dataType: 'refObject', description, properties, additionalProperties, refName: first.refName, deprecated, - example - } + example, + }; return result; } private static mergeReferenceTypes(referenceTypes: Tsoa.ReferenceType[]): Tsoa.ReferenceType { - if (referenceTypes.length == 1) { + if (referenceTypes.length === 1) { return referenceTypes[0]; } - if (referenceTypes.every(refType => refType.dataType == 'refEnum')) { - let refEnumTypes = referenceTypes as Tsoa.RefEnumType[]; + if (referenceTypes.every(refType => refType.dataType === 'refEnum')) { + const refEnumTypes = referenceTypes as Tsoa.RefEnumType[]; let merged = TypeResolver.mergeTwoRefEnumTypes(refEnumTypes[0], refEnumTypes[1]); for (let i = 2; i < refEnumTypes.length; ++i) { merged = TypeResolver.mergeTwoRefEnumTypes(merged, refEnumTypes[i]); } return merged; } - if (referenceTypes.every(refType => refType.dataType == 'refObject')) { - let refObjectTypes = referenceTypes as Tsoa.RefObjectType[]; + if (referenceTypes.every(refType => refType.dataType === 'refObject')) { + const refObjectTypes = referenceTypes as Tsoa.RefObjectType[]; let merged = TypeResolver.mergeTwoRefObjectTypes(refObjectTypes[0], refObjectTypes[1]); for (let i = 2; i < refObjectTypes.length; ++i) { merged = TypeResolver.mergeTwoRefObjectTypes(merged, refObjectTypes[i]); @@ -1043,7 +1004,7 @@ export class TypeResolver { private addToLocalReferenceTypeCache(name: string, refType: Tsoa.ReferenceType) { if (inProgressTypes[name]) { - for (let fn of inProgressTypes[name]) { + for (const fn of inProgressTypes[name]) { fn(refType); } } @@ -1055,7 +1016,7 @@ export class TypeResolver { private getTypeAliasReference(declaration: ts.TypeAliasDeclaration, refTypeName: string, referencer?: ts.Type): Tsoa.ReferenceType { const example = this.getNodeExample(declaration); - let referenceType: Tsoa.ReferenceType = { + const referenceType: Tsoa.ReferenceType = { dataType: 'refAlias', default: TypeResolver.getDefault(declaration), description: this.getNodeDescription(declaration), @@ -1133,7 +1094,7 @@ export class TypeResolver { .replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__ .replace(/([a-z_0-9]+\??):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_ .replace(/;/g, '--') - .replace(/([a-z\}\)\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, + .replace(/([a-z})\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, } private attemptToResolveKindToPrimitive = (syntaxKind: ts.SyntaxKind): ResolvesToPrimitive | DoesNotResolveToPrimitive => { @@ -1176,7 +1137,7 @@ export class TypeResolver { } as Tsoa.ReferenceType; inProgressTypes[refName].push(realReferenceType => { - for (let key of Object.keys(realReferenceType)) { + for (const key of Object.keys(realReferenceType)) { (referenceType as any)[key] = (realReferenceType as any)[key]; } }); @@ -1200,8 +1161,8 @@ export class TypeResolver { let typeName: string = type.kind === ts.SyntaxKind.Identifier ? type.text : type.right.text; let symbol: ts.Symbol | undefined = this.getSymbolAtLocation(type); - if (!symbol && type.kind == ts.SyntaxKind.QualifiedName) { - let fullEnumSymbol = this.getSymbolAtLocation(type.left); + if (!symbol && type.kind === ts.SyntaxKind.QualifiedName) { + const fullEnumSymbol = this.getSymbolAtLocation(type.left); symbol = fullEnumSymbol.exports?.get(typeName as any); } const declarations = symbol?.getDeclarations(); @@ -1286,7 +1247,7 @@ export class TypeResolver { required = false; } - let def = TypeResolver.getDefault(propertySignature); + const def = TypeResolver.getDefault(propertySignature); const property: Tsoa.Property = { default: def, @@ -1309,7 +1270,8 @@ export class TypeResolver { const tsType = this.current.typeChecker.getTypeAtLocation(propertyDeclaration); - if (!typeNode) { // Type is from initializer + if (!typeNode) { + // Type is from initializer typeNode = this.current.typeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.NoTruncation)!; } @@ -1390,8 +1352,8 @@ export class TypeResolver { ...newContext, [typeParameter.name.text]: { type: resolvedType, - name: name || this.calcTypeName(resolvedType) - } + name: name || this.calcTypeName(resolvedType), + }, }; } } @@ -1537,8 +1499,8 @@ export class TypeResolver { } private static getDefault(node: ts.Node) { - let defaultStr = getJSDocComment(node, 'default'); - if (typeof defaultStr == 'string' && defaultStr != 'undefined') { + const defaultStr = getJSDocComment(node, 'default'); + if (typeof defaultStr == 'string' && defaultStr !== 'undefined') { return JSON.parse(defaultStr); } return undefined; diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts index c597ddbaf..aa663cb5d 100644 --- a/tests/fixtures/testModel.ts +++ b/tests/fixtures/testModel.ts @@ -224,11 +224,11 @@ export interface TestModel extends Model { /** * @default undefined */ - defaultUndefined?: string, + defaultUndefined?: string; /** * @default null */ - defaultNull: string | null, + defaultNull: string | null; /** * @default * { @@ -236,82 +236,82 @@ export interface TestModel extends Model { * "b": 2 * } */ - defaultObject: { a: string, b: number }, + defaultObject: { a: string; b: number }; }; jsDocTypeNames?: { simple: Partial<{ a: string }>; commented: Partial<{ /** comment */ - a: string + a: string; }>; multilineCommented: Partial<{ /** * multiline * comment */ - a: string + a: string; }>; defaultValue: Partial<{ /** @default "true" */ - a: string + a: string; }>; deprecated: Partial<{ /** @deprecated */ - a: string + a: string; }>; validators: Partial<{ /** @minLength 3 */ - a: string + a: string; }>; examples: Partial<{ /** @example "example" */ - a: string + a: string; }>; extensions: Partial<{ /** @extension {"x-key-1": "value-1"} */ - a: string + a: string; }>; ignored: Partial<{ /** @ignore */ - a: string + a: string; }>; indexedSimple: Partial<{ [a: string]: string }>; indexedCommented: Partial<{ /** comment */ - [a: string]: string + [a: string]: string; }>; indexedMultilineCommented: Partial<{ /** * multiline * comment */ - [a: string]: string + [a: string]: string; }>; indexedDefaultValue: Partial<{ /** @default "true" */ - [a: string]: string + [a: string]: string; }>; indexedDeprecated: Partial<{ /** @deprecated */ - [a: string]: string + [a: string]: string; }>; indexedValidators: Partial<{ /** @minLength 3 */ - [a: string]: string + [a: string]: string; }>; indexedExamples: Partial<{ /** @example "example" */ - [a: string]: string + [a: string]: string; }>; indexedExtensions: Partial<{ /** @extension {"x-key-1": "value-1"} */ - [a: string]: string + [a: string]: string; }>; indexedIgnored: Partial<{ /** @ignore */ - [a: string]: string + [a: string]: string; }>; }; @@ -319,7 +319,7 @@ export interface TestModel extends Model { omitted: Omit; partial: Partial; replacedTypes: ReplaceStringAndNumberTypes; - doubleReplacedTypes: ReplaceStringAndNumberTypes> + doubleReplacedTypes: ReplaceStringAndNumberTypes>; postfixed: Postfixed; values: Values; typesValues: InternalTypes>; @@ -356,7 +356,7 @@ export interface TestModel extends Model { dummyFalseConditional: Dummy>; mappedConditional: Partial; mappedTypedConditional: Partial>; - } + }; typeOperators?: { keysOfAny: KeysMember; @@ -368,36 +368,36 @@ export interface TestModel extends Model { stringLiterals: keyof Record<'A' | 'B' | 'C', string>; stringAndNumberLiterals: keyof Record<'A' | 'B' | 3, string>; keyofEnum: keyof typeof DuplicatedEnum; - numberAndStringKeys: keyof { [3]: string, [4]: string, a: string }; + numberAndStringKeys: keyof { [3]: string; [4]: string; a: string }; oneStringKeyInterface: keyof { a: string }; oneNumberKeyInterface: keyof { [3]: string }; indexStrings: keyof { [a: string]: string }; indexNumbers: keyof { [a: number]: string }; - } + }; nestedTypes?: { multiplePartial: Partial>; - separateField: Partial, 'a'>>; - separateField2: Partial, 'a' | 'b'>>; - separateField3: Partial, 'a' | 'b'>>; - } + separateField: Partial, 'a'>>; + separateField2: Partial, 'a' | 'b'>>; + separateField3: Partial, 'a' | 'b'>>; + }; } type SeparateField = { - omitted: Omit, + omitted: Omit; field: T[Field]; -} +}; type KeysMember = { keys: keyof T; -} +}; interface NestedTypeLiteral { a: string; b: { c: string; d: string; - }, + }; e: any; } @@ -420,13 +420,14 @@ class DuplicatedInterface { enum DuplicatedEnum { A = 'AA', - B = 'BB' + B = 'BB', } enum DuplicatedEnum { - C = 'CC' + C = 'CC', } +// eslint-disable-next-line @typescript-eslint/no-namespace namespace DuplicatedEnum { export type D = 'DD'; } @@ -450,10 +451,9 @@ type JsDoccedSynonym2 = { [key in keyof JsDocced]: JsDocced[key] }; type ReplaceTypes = { [K in keyof T]: T[K] extends Type1 ? Type2 : Type1 }; type ReplaceStringAndNumberTypes = ReplaceTypes; type Postfixed = { [K in keyof T as `${K & string}${Postfix}`]: T[K] }; -type Values = { [K in keyof T]: { value: T[K] } } +type Values = { [K in keyof T]: { value: T[K] } }; type InternalTypes> = { [K in keyof T]: T[K]['value'] }; - class DefaultsClass { /** * @default true @@ -462,13 +462,14 @@ class DefaultsClass { /** * @default false */ - boolValue2?= true; - boolValue3?= false; + boolValue2? = true; + boolValue3? = false; boolValue4?: boolean; } type NamespaceType = string; +// eslint-disable-next-line @typescript-eslint/no-namespace namespace Namespace1 { export interface NamespaceType { inFirstNamespace: string; @@ -480,17 +481,20 @@ namespace Namespace1 { } } +// eslint-disable-next-line @typescript-eslint/no-namespace namespace Namespace1 { export interface NamespaceType { inFirstNamespace2: string; } } +// eslint-disable-next-line @typescript-eslint/no-namespace namespace Namespace2 { interface NamespaceType { inSecondNamespace: string; } + // eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace export module Namespace2 { export interface NamespaceType { inModule: string; @@ -515,7 +519,7 @@ interface DeprecatedType { } @Deprecated() -class DeprecatedClass { } +class DeprecatedClass {} interface TypeWithDeprecatedProperty { ok: boolean; @@ -707,14 +711,14 @@ export interface TestSubModel2 extends TestSubModel { testSubModel2: boolean; } -export interface HeritageTestModel extends TypeAlias4, Partial> { } +export interface HeritageTestModel extends TypeAlias4, Partial> {} export interface HeritageBaseModel { value: string; } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface HeritageTestModel2 extends HeritageBaseModel { } +export interface HeritageTestModel2 extends HeritageBaseModel {} export interface DefaultTestModel> { t: GenericRequest; @@ -776,7 +780,7 @@ export class ParameterTestModel { public nicknames?: string[]; } -export class ValidateCustomErrorModel { } +export class ValidateCustomErrorModel {} export class ValidateModel { /** @@ -1060,8 +1064,8 @@ const ClassIndexTest = { type Names = keyof typeof ClassIndexTest; type ResponseDistribute = T extends Names ? { - [key in T]: Record<(typeof ClassIndexTest)[T][number], U>; - } + [key in T]: Record<(typeof ClassIndexTest)[T][number], U>; + } : never; type IndexRecordAlias = ResponseDistribute; @@ -1232,4 +1236,3 @@ type OrderDirection = 'asc' | 'desc'; type OrderOptions = `${keyof E & string}:${OrderDirection}`; type TemplateLiteralString = OrderOptions; - From 7874601b9d8f98e77397040b6f9298f6e457a2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Tue, 17 Oct 2023 15:20:28 +0200 Subject: [PATCH 03/12] fix: format defaults before JSON.parse (#623) --- .../src/metadataGeneration/typeResolver.ts | 49 +- tests/fixtures/testModel.ts | 21 + .../definitionsGeneration/definitions.spec.ts | 1819 +++++++-------- tests/unit/swagger/schemaDetails3.spec.ts | 1960 +++++++++-------- 4 files changed, 2026 insertions(+), 1823 deletions(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 9b43fb613..26aabc690 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -1501,7 +1501,54 @@ export class TypeResolver { private static getDefault(node: ts.Node) { const defaultStr = getJSDocComment(node, 'default'); if (typeof defaultStr == 'string' && defaultStr !== 'undefined') { - return JSON.parse(defaultStr); + let textStartCharacter: `"` | "'" | '`' | undefined = undefined; + const inString = () => textStartCharacter !== undefined; + + let formattedStr = ''; + for (let i = 0; i < defaultStr.length; ++i) { + const actCharacter = defaultStr[i]; + if (inString()) { + if (actCharacter === textStartCharacter) { + formattedStr += '"'; + textStartCharacter = undefined; + } else if (actCharacter === '"') { + formattedStr += '\\"'; + } else if (actCharacter === '\\') { + ++i; + if (i < defaultStr.length) { + const nextCharacter = defaultStr[i]; + if (['n', 't', 'r', 'b', 'f', '\\', '"'].includes(nextCharacter)) { + formattedStr += '\\' + nextCharacter; + } else if (!['v', '0'].includes(nextCharacter)) { + //\v, \0 characters are not compatible with JSON + formattedStr += nextCharacter; + } + } else { + formattedStr += actCharacter; // this is a bug, but let the JSON parser decide how to handle it + } + } else { + formattedStr += actCharacter; + } + } else { + if ([`"`, "'", '`'].includes(actCharacter)) { + textStartCharacter = actCharacter as `"` | "'" | '`'; + formattedStr += '"'; + } else if (actCharacter === '/' && i + 1 < defaultStr.length && defaultStr[i + 1] === '/') { + i += 2; + while (i < defaultStr.length && defaultStr[i] !== '\n') { + ++i; + } + } else { + formattedStr += actCharacter; + } + } + } + try { + const parsed = JSON.parse(formattedStr); + return parsed; + } catch (err) { + throw new GenerateMetadataError(`JSON could not parse default str: "${defaultStr}", preformatted: "${formattedStr}"\nmessage: "${((err as any)?.message as string) || '-'}"`); + } } return undefined; } diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts index aa663cb5d..47b71323d 100644 --- a/tests/fixtures/testModel.ts +++ b/tests/fixtures/testModel.ts @@ -237,6 +237,27 @@ export interface TestModel extends Model { * } */ defaultObject: { a: string; b: number }; + /** + * @default `\`"'\"\'\n\t\r\b\f\v\0\g\x\\`//\0, \v is not supported... + * + */ + stringEscapeCharacters: undefined; //type is not really interesting + /** + * @default //Comment1 + * 4 + * //Comment2 + * + */ + comments: undefined; //type is not really interesting + /** + * @default { + * //Alma + * `\\`: '\n' + * + * } + * + */ + jsonCharacters: undefined; //type is not really interesting }; jsDocTypeNames?: { diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index 451d35c8a..73e6094ad 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -600,7 +600,7 @@ describe('Definition generation', () => { default: undefined, }, }, - required: ["1", "2"], + required: ['1', '2'], type: 'object', default: undefined, example: undefined, @@ -619,18 +619,18 @@ describe('Definition generation', () => { description: undefined, example: undefined, format: undefined, - type: "string" - } + type: 'string', + }, }, - required: ["data"], - type: "object" + required: ['data'], + type: 'object', }, default: undefined, - description: "Construct a type with a set of properties K of type T", + description: 'Construct a type with a set of properties K of type T', example: undefined, format: undefined, properties: {}, - type: "object" + type: 'object', }); }, numberRecord: (propertyName, propertySchema) => { @@ -644,18 +644,18 @@ describe('Definition generation', () => { description: undefined, example: undefined, format: undefined, - type: "string" - } + type: 'string', + }, }, - required: ["data"], - type: "object" + required: ['data'], + type: 'object', }, default: undefined, - description: "Construct a type with a set of properties K of type T", + description: 'Construct a type with a set of properties K of type T', example: undefined, format: undefined, properties: {}, - type: "object" + type: 'object', }); }, modelsObjectIndirect: (propertyName, propertySchema) => { @@ -1575,7 +1575,7 @@ describe('Definition generation', () => { 'weight:desc', 'human:desc', 'gender:desc', - 'nicknames:desc' + 'nicknames:desc', ], type: 'object', description: undefined, @@ -1635,50 +1635,44 @@ describe('Definition generation', () => { { properties: { typeHolder2: { - $ref: "#/definitions/Namespace2.TypeHolder", + $ref: '#/definitions/Namespace2.TypeHolder', description: undefined, example: undefined, - format: undefined + format: undefined, }, inModule: { - $ref: "#/definitions/Namespace2.Namespace2.NamespaceType", + $ref: '#/definitions/Namespace2.Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, typeHolder1: { - $ref: "#/definitions/Namespace1.TypeHolder", + $ref: '#/definitions/Namespace1.TypeHolder', description: undefined, example: undefined, - format: undefined + format: undefined, }, inNamespace1: { - $ref: "#/definitions/Namespace1.NamespaceType", + $ref: '#/definitions/Namespace1.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, simple: { - $ref: "#/definitions/NamespaceType", + $ref: '#/definitions/NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: [ - "typeHolder2", - "inModule", - "typeHolder1", - "inNamespace1", - "simple" - ], - type: "object", + required: ['typeHolder2', 'inModule', 'typeHolder1', 'inNamespace1', 'simple'], + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}` + `for property ${propertyName}`, ); const typeHolder2Schema = getValidatedDefinition('Namespace2.TypeHolder', currentSpec); @@ -1686,25 +1680,24 @@ describe('Definition generation', () => { { properties: { inModule: { - $ref: "#/definitions/Namespace2.Namespace2.NamespaceType", + $ref: '#/definitions/Namespace2.Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, inNamespace2: { - $ref: "#/definitions/Namespace2.NamespaceType", + $ref: '#/definitions/Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inModule", - "inNamespace2"], - type: "object", + required: ['inModule', 'inNamespace2'], + type: 'object', additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, - description: undefined + description: undefined, }, - `for property ${propertyName}.typeHolder2` + `for property ${propertyName}.typeHolder2`, ); const namespace2_namespace2_namespaceTypeSchema = getValidatedDefinition('Namespace2.Namespace2.NamespaceType', currentSpec); @@ -1712,51 +1705,50 @@ describe('Definition generation', () => { { properties: { inModule: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, other: { - $ref: "#/definitions/Namespace2.Namespace2.NamespaceType", + $ref: '#/definitions/Namespace2.Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inModule"], - type: "object", + required: ['inModule'], + type: 'object', description: undefined, - additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.typeHolder2.inModule` + `for property ${propertyName}.typeHolder2.inModule`, ); const typeHolderSchema = getValidatedDefinition('Namespace1.TypeHolder', currentSpec); expect(typeHolderSchema).to.deep.eq( { properties: { - inNamespace1_1: - { - $ref: "#/definitions/Namespace1.NamespaceType", + inNamespace1_1: { + $ref: '#/definitions/Namespace1.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, inNamespace1_2: { - $ref: "#/definitions/Namespace1.NamespaceType", + $ref: '#/definitions/Namespace1.NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inNamespace1_1", "inNamespace1_2"], - type: "object", + required: ['inNamespace1_1', 'inNamespace1_2'], + type: 'object', additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, - description: undefined + description: undefined, }, - `for property ${propertyName}.typeHolder1` + `for property ${propertyName}.typeHolder1`, ); const namespace1_namespaceTypeSchema = getValidatedDefinition('Namespace1.NamespaceType', currentSpec); @@ -1764,26 +1756,26 @@ describe('Definition generation', () => { { properties: { inFirstNamespace: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, inFirstNamespace2: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inFirstNamespace", "inFirstNamespace2"], - type: "object", + required: ['inFirstNamespace', 'inFirstNamespace2'], + type: 'object', description: undefined, - additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.typeHolder1.inNamespace1_1` + `for property ${propertyName}.typeHolder1.inNamespace1_1`, ); const namespace2_namespaceTypeSchema = getValidatedDefinition('Namespace2.NamespaceType', currentSpec); @@ -1791,99 +1783,118 @@ describe('Definition generation', () => { { properties: { inSecondNamespace: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, }, - required: ["inSecondNamespace"], - type: "object", + required: ['inSecondNamespace'], + type: 'object', description: undefined, - additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.typeHolder2.inNamespace2` + `for property ${propertyName}.typeHolder2.inNamespace2`, ); const namespaceTypeSchema = getValidatedDefinition('NamespaceType', currentSpec); expect(namespaceTypeSchema).to.deep.eq( { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.simple` + `for property ${propertyName}.simple`, ); }, defaults: (propertyName, propertySchema) => { - expect(propertySchema).to.deep.eq({ - default: undefined, - description: undefined, - example: undefined, - format: undefined, - properties: { - basic: { - $ref: "#/definitions/DefaultsClass", - description: undefined, - example: undefined, - format: undefined - }, - defaultNull: { - default: null, - description: undefined, - example: undefined, - format: undefined, - type: "string", - "x-nullable": true - }, - defaultObject: { - default: { - a: "a", - b: 2 + expect(propertySchema).to.deep.eq( + { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + properties: { + basic: { + $ref: '#/definitions/DefaultsClass', + description: undefined, + example: undefined, + format: undefined, }, - description: undefined, - example: undefined, - format: undefined, - properties: { - a: { - default: undefined, - description: undefined, - example: undefined, - format: undefined, - type: "string" + defaultNull: { + default: null, + description: undefined, + example: undefined, + format: undefined, + type: 'string', + 'x-nullable': true, + }, + defaultObject: { + default: { + a: 'a', + b: 2, }, - b: { - default: undefined, - description: undefined, - example: undefined, - format: "double", - type: "number" - } + description: undefined, + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: 'string', + }, + b: { + default: undefined, + description: undefined, + example: undefined, + format: 'double', + type: 'number', + }, + }, + required: ['b', 'a'], + type: 'object', + }, + defaultUndefined: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: 'string', + }, + replacedTypes: { + $ref: '#/definitions/ReplaceTypes_DefaultsClass.boolean.string_', + description: undefined, + example: undefined, + format: undefined, + }, + comments: { + default: 4, + description: undefined, + example: undefined, + format: undefined, + }, + jsonCharacters: { + default: { '\\': '\n' }, + description: undefined, + example: undefined, + format: undefined, + }, + stringEscapeCharacters: { + default: '`"\'"\'\n\t\r\b\fgx\\', + description: undefined, + example: undefined, + format: undefined, }, - required: ["b", "a"], - type: "object" - }, - defaultUndefined: { - default: undefined, - description: undefined, - example: undefined, - format: undefined, - type: "string" }, - replacedTypes: { - $ref: "#/definitions/ReplaceTypes_DefaultsClass.boolean.string_", - description: undefined, - example: undefined, - format: undefined - } + required: ['replacedTypes', 'basic'], + type: 'object', }, - required: ["replacedTypes", "basic"], - type: "object" - }, - `for property ${propertyName}` + `for property ${propertyName}`, ); const basicSchema = getValidatedDefinition('DefaultsClass', currentSpec); @@ -1891,103 +1902,103 @@ describe('Definition generation', () => { { properties: { boolValue1: { - type: "boolean", + type: 'boolean', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue2: { - type: "boolean", + type: 'boolean', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue3: { - type: "boolean", + type: 'boolean', default: false, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue4: { - type: "boolean", + type: 'boolean', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", + type: 'object', required: undefined, description: undefined, - additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true + additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.basic` + `for property ${propertyName}.basic`, ); const replacedTypesSchema = getValidatedDefinition('ReplaceTypes_DefaultsClass.boolean.string_', currentSpec); expect(replacedTypesSchema).to.deep.eq( { properties: { boolValue1: { - type: "string", + type: 'string', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue2: { - type: "string", + type: 'string', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue3: { - type: "string", + type: 'string', default: false, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue4: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", + type: 'object', description: undefined, default: undefined, example: undefined, format: undefined, }, - `for property ${propertyName}.replacedTypes` + `for property ${propertyName}.replacedTypes`, ); }, jsDocTypeNames: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.simple?.$ref).to.eq("#/definitions/Partial__a-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.commented?.$ref).to.eq("#/definitions/Partial__a_description-comment_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq("#/definitions/Partial__a_description-multiline%5Cncomment_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.defaultValue?.$ref).to.eq("#/definitions/Partial__a_default-true_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.deprecated?.$ref).to.eq("#/definitions/Partial__a_deprecated-true_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.validators?.$ref).to.eq("#/definitions/Partial__a_validators%3A_minLength%3A_value%3A3___-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.examples?.$ref).to.eq("#/definitions/Partial__a_example-example_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.extensions?.$ref).to.eq("#/definitions/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.ignored?.$ref).to.eq("#/definitions/Partial__a_ignored-true_-string__", `for property ${propertyName}`); - - expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.simple?.$ref).to.eq('#/definitions/Partial__a-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.commented?.$ref).to.eq('#/definitions/Partial__a_description-comment_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq('#/definitions/Partial__a_description-multiline%5Cncomment_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.defaultValue?.$ref).to.eq('#/definitions/Partial__a_default-true_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.deprecated?.$ref).to.eq('#/definitions/Partial__a_deprecated-true_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.validators?.$ref).to.eq('#/definitions/Partial__a_validators%3A_minLength%3A_value%3A3___-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.examples?.$ref).to.eq('#/definitions/Partial__a_example-example_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.extensions?.$ref).to.eq('#/definitions/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.ignored?.$ref).to.eq('#/definitions/Partial__a_ignored-true_-string__', `for property ${propertyName}`); + + expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(18, `for property ${propertyName}`); @@ -1996,388 +2007,387 @@ describe('Definition generation', () => { { properties: { a: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.simple` + `for property ${propertyName}.simple`, ); const commentedSchema = getValidatedDefinition('Partial__a_description-comment_-string__', currentSpec); expect(commentedSchema).to.deep.eq( { properties: { a: { - type: "string", - description: "comment", + type: 'string', + description: 'comment', default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.commented` + `for property ${propertyName}.commented`, ); const multilineCommentedSchema = getValidatedDefinition('Partial__a_description-multiline\\ncomment_-string__', currentSpec); expect(multilineCommentedSchema).to.deep.eq( { properties: { a: { - type: "string", - description: "multiline\ncomment", + type: 'string', + description: 'multiline\ncomment', default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.multilineCommented` + `for property ${propertyName}.multilineCommented`, ); const defaultValueSchema = getValidatedDefinition('Partial__a_default-true_-string__', currentSpec); expect(defaultValueSchema).to.deep.eq( { properties: { a: { - type: "string", - default: "true", + type: 'string', + default: 'true', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.defaultValue` + `for property ${propertyName}.defaultValue`, ); const deprecatedSchema = getValidatedDefinition('Partial__a_deprecated-true_-string__', currentSpec); expect(deprecatedSchema).to.deep.eq( { properties: { a: { - type: "string", - "x-deprecated": true, + type: 'string', + 'x-deprecated': true, description: undefined, default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.deprecated` + `for property ${propertyName}.deprecated`, ); const validatorsSchema = getValidatedDefinition('Partial__a_validators:_minLength:_value:3___-string__', currentSpec); expect(validatorsSchema).to.deep.eq( { properties: { a: { - type: "string", + type: 'string', minLength: 3, description: undefined, default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.validators` + `for property ${propertyName}.validators`, ); const examplesSchema = getValidatedDefinition('Partial__a_example-example_-string__', currentSpec); - expect(examplesSchema).to.deep.eq({ - default: undefined, - description: "Make all properties in T optional", - example: undefined, - format: undefined, - properties: { - a: { - default: undefined, - description: undefined, - example: "example", - format: undefined, - type: "string" - } + expect(examplesSchema).to.deep.eq( + { + default: undefined, + description: 'Make all properties in T optional', + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: 'example', + format: undefined, + type: 'string', + }, + }, + type: 'object', }, - type: "object" - }, - `for property ${propertyName}.examples` + `for property ${propertyName}.examples`, ); const extensionsSchema = getValidatedDefinition('Partial__a_extensions:[_key-x-key-1.value-value-1_]_-string__', currentSpec); expect(extensionsSchema).to.deep.eq( { properties: { a: { - type: "string", - "x-key-1": "value-1", + type: 'string', + 'x-key-1': 'value-1', description: undefined, default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.extensions` + `for property ${propertyName}.extensions`, ); const ignoredSchema = getValidatedDefinition('Partial__a_ignored-true_-string__', currentSpec); expect(ignoredSchema).to.deep.eq( { - properties: { - }, - type: "object", - description: "Make all properties in T optional", + properties: {}, + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.ignored` + `for property ${propertyName}.ignored`, ); const indexedSchema = getValidatedDefinition('Partial__[a-string]:string__', currentSpec); expect(indexedSchema).to.deep.eq( { - properties: { - }, + properties: {}, additionalProperties: { - type: "string" + type: 'string', }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.indexedSimple` + `for property ${propertyName}.indexedSimple`, ); }, jsdocMap: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.omitted?.$ref).to.eq("#/definitions/Omit_JsDocced.notRelevant_", `for property ${propertyName}`); - expect(propertySchema?.properties?.partial?.$ref).to.eq("#/definitions/Partial_JsDocced_", `for property ${propertyName}`); - expect(propertySchema?.properties?.replacedTypes?.$ref).to.eq("#/definitions/ReplaceStringAndNumberTypes_JsDocced_", `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleReplacedTypes?.$ref).to.eq("#/definitions/ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__", `for property ${propertyName}`); - expect(propertySchema?.properties?.postfixed?.$ref).to.eq("#/definitions/Postfixed_JsDocced._PostFix_", `for property ${propertyName}`); - expect(propertySchema?.properties?.values?.$ref).to.eq("#/definitions/Values_JsDocced_", `for property ${propertyName}`); - expect(propertySchema?.properties?.typesValues?.$ref).to.eq("#/definitions/InternalTypes_Values_JsDocced__", `for property ${propertyName}`); + expect(propertySchema?.properties?.omitted?.$ref).to.eq('#/definitions/Omit_JsDocced.notRelevant_', `for property ${propertyName}`); + expect(propertySchema?.properties?.partial?.$ref).to.eq('#/definitions/Partial_JsDocced_', `for property ${propertyName}`); + expect(propertySchema?.properties?.replacedTypes?.$ref).to.eq('#/definitions/ReplaceStringAndNumberTypes_JsDocced_', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleReplacedTypes?.$ref).to.eq('#/definitions/ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__', `for property ${propertyName}`); + expect(propertySchema?.properties?.postfixed?.$ref).to.eq('#/definitions/Postfixed_JsDocced._PostFix_', `for property ${propertyName}`); + expect(propertySchema?.properties?.values?.$ref).to.eq('#/definitions/Values_JsDocced_', `for property ${propertyName}`); + expect(propertySchema?.properties?.typesValues?.$ref).to.eq('#/definitions/InternalTypes_Values_JsDocced__', `for property ${propertyName}`); expect(propertySchema?.properties?.onlyOneValue).to.deep.eq( { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, - example: undefined + example: undefined, }, - `for property ${propertyName}.onlyOneValue` + `for property ${propertyName}.onlyOneValue`, ); - expect(propertySchema?.properties?.synonym?.$ref).to.eq("#/definitions/JsDoccedSynonym", `for property ${propertyName}`); - expect(propertySchema?.properties?.synonym2?.$ref).to.eq("#/definitions/JsDoccedSynonym2", `for property ${propertyName}`); + expect(propertySchema?.properties?.synonym?.$ref).to.eq('#/definitions/JsDoccedSynonym', `for property ${propertyName}`); + expect(propertySchema?.properties?.synonym2?.$ref).to.eq('#/definitions/JsDoccedSynonym2', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(10, `for property ${propertyName}`); const omittedSchema = getValidatedDefinition('Omit_JsDocced.notRelevant_', currentSpec); expect(omittedSchema).to.deep.eq( { - $ref: "#/definitions/Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__", - description: "Construct a type with the properties of T except for those in type K.", + $ref: '#/definitions/Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__', + description: 'Construct a type with the properties of T except for those in type K.', default: undefined, example: undefined, format: undefined, }, - `for property ${propertyName}.omitted` + `for property ${propertyName}.omitted`, ); const omittedSchema2 = getValidatedDefinition('Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__', currentSpec); - expect(omittedSchema2).to.deep.eq({ - properties: { - stringValue: { - type: "string", - default: "def", - maxLength: 3, - description: undefined, - example: undefined, - format: undefined, - }, - numberValue: { - type: "integer", - format: "int32", - default: 6, - description: undefined, - example: undefined - } - }, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.omitted` - ); - const partialSchema = getValidatedDefinition('Partial_JsDocced_', currentSpec); - expect(partialSchema).to.deep.eq( + expect(omittedSchema2).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, - format: undefined, + description: undefined, example: undefined, - description: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, + description: undefined, example: undefined, - description: undefined - } + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.partial` + `for property ${propertyName}.omitted`, + ); + const partialSchema = getValidatedDefinition('Partial_JsDocced_', currentSpec); + expect(partialSchema).to.deep.eq( + { + properties: { + stringValue: { + type: 'string', + default: 'def', + maxLength: 3, + format: undefined, + example: undefined, + description: undefined, + }, + numberValue: { + type: 'integer', + format: 'int32', + default: 6, + example: undefined, + description: undefined, + }, + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.partial`, ); const replacedTypesSchema = getValidatedDefinition('ReplaceStringAndNumberTypes_JsDocced_', currentSpec); expect(replacedTypesSchema).to.deep.eq( { - $ref: "#/definitions/ReplaceTypes_JsDocced.string.number_", + $ref: '#/definitions/ReplaceTypes_JsDocced.string.number_', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.replacedTypes` + `for property ${propertyName}.replacedTypes`, ); const replacedTypes2Schema = getValidatedDefinition('ReplaceTypes_JsDocced.string.number_', currentSpec); - expect(replacedTypes2Schema).to.deep.eq({ - properties: { - stringValue: { - type: "number", - format: "double", - default: "def", - maxLength: 3, - description: undefined, - example: undefined, + expect(replacedTypes2Schema).to.deep.eq( + { + properties: { + stringValue: { + type: 'number', + format: 'double', + default: 'def', + maxLength: 3, + description: undefined, + example: undefined, + }, + numberValue: { + type: 'string', + default: 6, + description: undefined, + example: undefined, + format: undefined, + }, }, - numberValue: { - type: "string", - default: 6, - description: undefined, - example: undefined, - format: undefined - } + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, }, - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.replacedTypes` + `for property ${propertyName}.replacedTypes`, ); const doubleReplacedTypesSchema = getValidatedDefinition('ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__', currentSpec); expect(doubleReplacedTypesSchema).to.deep.eq( { - $ref: "#/definitions/ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_", + $ref: '#/definitions/ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.doubleReplacedTypes` + `for property ${propertyName}.doubleReplacedTypes`, ); const doubleReplacedTypes2Schema = getValidatedDefinition('ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_', currentSpec); expect(doubleReplacedTypes2Schema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, description: undefined, example: undefined, }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.doubleReplacedTypes` + `for property ${propertyName}.doubleReplacedTypes`, ); const postfixedSchema = getValidatedDefinition('Postfixed_JsDocced._PostFix_', currentSpec); expect(postfixedSchema).to.deep.eq( { properties: { stringValue_PostFix: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue_PostFix: { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, - example: undefined - } + example: undefined, + }, }, - required: [ - "stringValue_PostFix", "numberValue_PostFix" - ], - type: "object", + required: ['stringValue_PostFix', 'numberValue_PostFix'], + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.postfixed` + `for property ${propertyName}.postfixed`, ); const valuesSchema = getValidatedDefinition('Values_JsDocced_', currentSpec); expect(valuesSchema).to.deep.eq( @@ -2386,74 +2396,74 @@ describe('Definition generation', () => { stringValue: { properties: { value: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["value"], - type: "object", - default: "def", + required: ['value'], + type: 'object', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { properties: { value: { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, example: undefined, - } + }, }, - required: ["value"], - type: "object", + required: ['value'], + type: 'object', default: 6, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.values` + `for property ${propertyName}.values`, ); const typesValuesSchema = getValidatedDefinition('InternalTypes_Values_JsDocced__', currentSpec); expect(typesValuesSchema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, description: undefined, example: undefined, - } + }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.typesValues` + `for property ${propertyName}.typesValues`, ); const synonymSchema = getValidatedDefinition('JsDoccedSynonym', currentSpec); @@ -2461,63 +2471,63 @@ describe('Definition generation', () => { { properties: { stringValue: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, example: undefined, - } + }, }, - required: ["stringValue", "numberValue"], - type: "object", + required: ['stringValue', 'numberValue'], + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.synonym` + `for property ${propertyName}.synonym`, ); const synonym2Schema = getValidatedDefinition('JsDoccedSynonym2', currentSpec); expect(synonym2Schema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, description: undefined, - example: undefined - } + example: undefined, + }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.synonym2` + `for property ${propertyName}.synonym2`, ); }, duplicatedDefinitions: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.interfaces?.$ref).to.eq("#/definitions/DuplicatedInterface", `for property ${propertyName}`); - expect(propertySchema?.properties?.enums?.$ref).to.eq("#/definitions/DuplicatedEnum", `for property ${propertyName}`); - expect(propertySchema?.properties?.enumMember?.$ref).to.eq("#/definitions/DuplicatedEnum.C", `for property ${propertyName}`); - expect(propertySchema?.properties?.namespaceMember?.$ref).to.eq("#/definitions/DuplicatedEnum.D", `for property ${propertyName}`); + expect(propertySchema?.properties?.interfaces?.$ref).to.eq('#/definitions/DuplicatedInterface', `for property ${propertyName}`); + expect(propertySchema?.properties?.enums?.$ref).to.eq('#/definitions/DuplicatedEnum', `for property ${propertyName}`); + expect(propertySchema?.properties?.enumMember?.$ref).to.eq('#/definitions/DuplicatedEnum.C', `for property ${propertyName}`); + expect(propertySchema?.properties?.namespaceMember?.$ref).to.eq('#/definitions/DuplicatedEnum.D', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); @@ -2526,578 +2536,651 @@ describe('Definition generation', () => { { properties: { a: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, b: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["a", "b"], - type: "object", + required: ['a', 'b'], + type: 'object', additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' || currentSpec.specName === 'dynamicSpecWithNoImplicitExtras' ? false : true, description: undefined, - }, - `for property ${propertyName}.interfaces` + `for property ${propertyName}.interfaces`, ); const enumsSchema = getValidatedDefinition('DuplicatedEnum', currentSpec); expect(enumsSchema).to.deep.eq( { - enum: ["AA", "BB", "CC"], - type: "string", - description: undefined + enum: ['AA', 'BB', 'CC'], + type: 'string', + description: undefined, }, - `for property ${propertyName}.enums` + `for property ${propertyName}.enums`, ); const enumMemberSchema = getValidatedDefinition('DuplicatedEnum.C', currentSpec); expect(enumMemberSchema).to.deep.eq( { - enum: ["CC"], - type: "string", - description: undefined + enum: ['CC'], + type: 'string', + description: undefined, }, - `for property ${propertyName}.enumMember` + `for property ${propertyName}.enumMember`, ); const namespaceMemberSchema = getValidatedDefinition('DuplicatedEnum.D', currentSpec); expect(namespaceMemberSchema).to.deep.eq( { - enum: ["DD"], - type: "string", - "x-nullable": false, + enum: ['DD'], + type: 'string', + 'x-nullable': false, description: undefined, default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.namespaceMember` + `for property ${propertyName}.namespaceMember`, ); }, mappeds: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.unionMap?.$ref).to.eq("#/definitions/Partial__a-string_-or-_b-number__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq("#/definitions/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq("#/definitions/Partial__a-string_-and-_b-number__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq("#/definitions/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq("#/definitions/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq("#/definitions/Partial__a-string_-or-(_b-string_-and-_c-string_)_", `for property ${propertyName}`); - expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq("#/definitions/Partial_(_a-string_-or-_b-string_)-and-_c-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.unionMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-_b-number__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq('#/definitions/Partial__a-string_-and-_b-number__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq('#/definitions/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq( + '#/definitions/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__', + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-(_b-string_-and-_c-string_)_', `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/definitions/Partial_(_a-string_-or-_b-string_)-and-_c-string__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); const unionMapSchema = getValidatedDefinition('Partial__a-string_-or-_b-number__', currentSpec); - expect(unionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + expect(unionMapSchema).to.deep.eq( + //Unions are not supported in OpenAPI 2 { - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.unionMap` + `for property ${propertyName}.unionMap`, ); const indexedUnionMapSchema = getValidatedDefinition('Partial__a-string_-or-_[b-string]:number__', currentSpec); - expect(indexedUnionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + expect(indexedUnionMapSchema).to.deep.eq( + //Unions are not supported in OpenAPI 2 { - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.indexedUnionMap` + `for property ${propertyName}.indexedUnionMap`, ); const doubleIndexedUnionMapSchema = getValidatedDefinition('Partial__[a-string]:string_-or-_[b-string]:number__', currentSpec); - expect(doubleIndexedUnionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + expect(doubleIndexedUnionMapSchema).to.deep.eq( + //Unions are not supported in OpenAPI 2 { - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.doubleIndexedUnionMap` + `for property ${propertyName}.doubleIndexedUnionMap`, ); const intersectionMapSchema = getValidatedDefinition('Partial__a-string_-and-_b-number__', currentSpec); - expect(intersectionMapSchema).to.deep.eq({ - properties: { - a: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined + expect(intersectionMapSchema).to.deep.eq( + { + properties: { + a: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + b: { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, }, - b: { - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined, - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.intersectionMap` + `for property ${propertyName}.intersectionMap`, ); const indexedIntersectionMapSchema = getValidatedDefinition('Partial__a-string_-and-_[b-string]:number__', currentSpec); - expect(indexedIntersectionMapSchema).to.deep.eq({ - properties: { - a: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } - }, - additionalProperties: { - type: "number", - format: "double" + expect(indexedIntersectionMapSchema).to.deep.eq( + { + properties: { + a: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + additionalProperties: { + type: 'number', + format: 'double', + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.indexedIntersectionMap` + `for property ${propertyName}.indexedIntersectionMap`, ); const doubleIndexedIntersectionMapSchema = getValidatedDefinition('Partial__[a-string]:string_-and-_[b-number]:number__', currentSpec); - expect(doubleIndexedIntersectionMapSchema).to.deep.eq({ //Unions are not supported in OpenAPI 2 - properties: {}, - additionalProperties: { - type: "object" + expect(doubleIndexedIntersectionMapSchema).to.deep.eq( + { + //Unions are not supported in OpenAPI 2 + properties: {}, + additionalProperties: { + type: 'object', + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.doubleIndexedIntersectionMap` + `for property ${propertyName}.doubleIndexedIntersectionMap`, ); const parenthesizedMapSchema = getValidatedDefinition('Partial__a-string_-or-(_b-string_-and-_c-string_)_', currentSpec); - expect(parenthesizedMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 + expect(parenthesizedMapSchema).to.deep.eq( + //Unions are not supported in OpenAPI 2 { - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.parenthesizedMap` + `for property ${propertyName}.parenthesizedMap`, ); const parenthesizedMap2Schema = getValidatedDefinition('Partial_(_a-string_-or-_b-string_)-and-_c-string__', currentSpec); - expect(parenthesizedMap2Schema).to.deep.eq( //Unions are not supported in OpenAPI 2 + expect(parenthesizedMap2Schema).to.deep.eq( + //Unions are not supported in OpenAPI 2 { - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.parenthesizedMap2` + `for property ${propertyName}.parenthesizedMap2`, ); }, conditionals: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.simpeConditional).to.deep.eq({ - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined - }, - `for property ${propertyName}`); - expect(propertySchema?.properties?.simpeFalseConditional).to.deep.eq({ - type: "boolean", - format: undefined, - default: undefined, - description: undefined, - example: undefined - }, - `for property ${propertyName}`); - expect(propertySchema?.properties?.typedConditional?.$ref).to.eq("#/definitions/Conditional_string.string.number.boolean_", `for property ${propertyName}`); - expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq("#/definitions/Conditional_string.number.number.boolean_", `for property ${propertyName}`); - expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq("#/definitions/Dummy_Conditional_string.string.number.boolean__", `for property ${propertyName}`); - expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq("#/definitions/Dummy_Conditional_string.number.number.boolean__", `for property ${propertyName}`); - expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq("#/definitions/Partial_stringextendsstring%3F_a-number_-never_", `for property ${propertyName}`); - expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq("#/definitions/Partial_Conditional_string.string._a-number_.never__", `for property ${propertyName}`); + expect(propertySchema?.properties?.simpeConditional).to.deep.eq( + { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.simpeFalseConditional).to.deep.eq( + { + type: 'boolean', + format: undefined, + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.typedConditional?.$ref).to.eq('#/definitions/Conditional_string.string.number.boolean_', `for property ${propertyName}`); + expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq('#/definitions/Conditional_string.number.number.boolean_', `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq('#/definitions/Dummy_Conditional_string.string.number.boolean__', `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq('#/definitions/Dummy_Conditional_string.number.number.boolean__', `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq('#/definitions/Partial_stringextendsstring%3F_a-number_-never_', `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq('#/definitions/Partial_Conditional_string.string._a-number_.never__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); const typedConditionalSchema = getValidatedDefinition('Conditional_string.string.number.boolean_', currentSpec); - expect(typedConditionalSchema).to.deep.eq({ - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined, - }, - `for property ${propertyName}.typedConditional`); + expect(typedConditionalSchema).to.deep.eq( + { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedConditional`, + ); const typedFalseConditionalSchema = getValidatedDefinition('Conditional_string.number.number.boolean_', currentSpec); - expect(typedFalseConditionalSchema).to.deep.eq({ - type: "boolean", - format: undefined, - default: undefined, - description: undefined, - example: undefined, - }, - `for property ${propertyName}.typedFalseConditional`); + expect(typedFalseConditionalSchema).to.deep.eq( + { + type: 'boolean', + format: undefined, + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedFalseConditional`, + ); const dummyConditionalSchema = getValidatedDefinition('Dummy_Conditional_string.string.number.boolean__', currentSpec); - expect(dummyConditionalSchema?.$ref).to.eq("#/definitions/Conditional_string.string.number.boolean_", `for property ${propertyName}.dummyConditional`); + expect(dummyConditionalSchema?.$ref).to.eq('#/definitions/Conditional_string.string.number.boolean_', `for property ${propertyName}.dummyConditional`); const dummyFalseConditionalSchema = getValidatedDefinition('Dummy_Conditional_string.number.number.boolean__', currentSpec); - expect(dummyFalseConditionalSchema?.$ref).to.eq("#/definitions/Conditional_string.number.number.boolean_", `for property ${propertyName}.dummyFalseConditional`); + expect(dummyFalseConditionalSchema?.$ref).to.eq('#/definitions/Conditional_string.number.number.boolean_', `for property ${propertyName}.dummyFalseConditional`); const mappedConditionalSchema = getValidatedDefinition('Partial_stringextendsstring?_a-number_-never_', currentSpec); - expect(mappedConditionalSchema).to.deep.eq({ - properties: { - a: { - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined, - } + expect(mappedConditionalSchema).to.deep.eq( + { + properties: { + a: { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + }, + type: 'object', + description: 'Make all properties in T optional', + example: undefined, + default: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - example: undefined, - default: undefined, - format: undefined, - }, - `for property ${propertyName}.mappedConditional`); + `for property ${propertyName}.mappedConditional`, + ); const mappedTypedConditionalSchema = getValidatedDefinition('Partial_Conditional_string.string._a-number_.never__', currentSpec); expect(mappedTypedConditionalSchema).to.deep.eq(mappedConditionalSchema, `for property ${propertyName}.mappedTypedConditional`); }, typeOperators: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.keysOfAny?.$ref).to.eq("#/definitions/KeysMember", `for property ${propertyName}`); - expect(propertySchema?.properties?.keysOfInterface?.$ref).to.eq("#/definitions/KeysMember_NestedTypeLiteral_", `for property ${propertyName}`); - expect(propertySchema?.properties?.simple).to.deep.eq({ - type: "string", - enum: ["a", "b", "e"], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.simple`); - expect(propertySchema?.properties?.keyofItem).to.deep.eq({ - type: "string", - enum: ["c", "d"], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.keyofItem`); - expect(propertySchema?.properties?.keyofAnyItem).to.deep.eq({ //Unions are not supported in OpenAPI 2 (string | number) - default: undefined, - description: undefined, - example: undefined, - format: undefined, - type: "object" - }, - `for property ${propertyName}.keyofAnyItem`); - expect(propertySchema?.properties?.keyofAny).to.deep.eq({ //Unions are not supported in OpenAPI 2 (string | number) - default: undefined, - description: undefined, - example: undefined, - format: undefined, - type: "object" - }, - `for property ${propertyName}.keyofAny`); - expect(propertySchema?.properties?.stringLiterals).to.deep.eq({ - type: "string", - enum: ["A", "B", "C"], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.stringLiterals`); - expect(propertySchema?.properties?.stringAndNumberLiterals).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) - enum: ["A", "B", "3"], - type: "string", - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.stringAndNumberLiterals`); - expect(propertySchema?.properties?.keyofEnum).to.deep.eq({ - type: "string", - enum: ["A", "B", "C"], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keyofEnum`); - expect(propertySchema?.properties?.numberAndStringKeys).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) - enum: ["a", "3", "4"], - type: "string", - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.numberAndStringKeys`); - expect(propertySchema?.properties?.oneStringKeyInterface).to.deep.eq({ - type: "string", - enum: ["a"], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.oneStringKeyInterface`); - expect(propertySchema?.properties?.oneNumberKeyInterface).to.deep.eq({ - type: "number", - enum: [3], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.oneNumberKeyInterface`); - expect(propertySchema?.properties?.indexStrings).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.indexStrings`); - expect(propertySchema?.properties?.indexNumbers).to.deep.eq({ - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined - }, - `for property ${propertyName}.indexNumbers`); + expect(propertySchema?.properties?.keysOfAny?.$ref).to.eq('#/definitions/KeysMember', `for property ${propertyName}`); + expect(propertySchema?.properties?.keysOfInterface?.$ref).to.eq('#/definitions/KeysMember_NestedTypeLiteral_', `for property ${propertyName}`); + expect(propertySchema?.properties?.simple).to.deep.eq( + { + type: 'string', + enum: ['a', 'b', 'e'], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.simple`, + ); + expect(propertySchema?.properties?.keyofItem).to.deep.eq( + { + type: 'string', + enum: ['c', 'd'], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofItem`, + ); + expect(propertySchema?.properties?.keyofAnyItem).to.deep.eq( + { + //Unions are not supported in OpenAPI 2 (string | number) + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: 'object', + }, + `for property ${propertyName}.keyofAnyItem`, + ); + expect(propertySchema?.properties?.keyofAny).to.deep.eq( + { + //Unions are not supported in OpenAPI 2 (string | number) + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: 'object', + }, + `for property ${propertyName}.keyofAny`, + ); + expect(propertySchema?.properties?.stringLiterals).to.deep.eq( + { + type: 'string', + enum: ['A', 'B', 'C'], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.stringLiterals`, + ); + expect(propertySchema?.properties?.stringAndNumberLiterals).to.deep.eq( + { + //Unions are not perfectly supported in OpenAPI 2 (string | number) + enum: ['A', 'B', '3'], + type: 'string', + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.stringAndNumberLiterals`, + ); + expect(propertySchema?.properties?.keyofEnum).to.deep.eq( + { + type: 'string', + enum: ['A', 'B', 'C'], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofEnum`, + ); + expect(propertySchema?.properties?.numberAndStringKeys).to.deep.eq( + { + //Unions are not perfectly supported in OpenAPI 2 (string | number) + enum: ['a', '3', '4'], + type: 'string', + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.numberAndStringKeys`, + ); + expect(propertySchema?.properties?.oneStringKeyInterface).to.deep.eq( + { + type: 'string', + enum: ['a'], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneStringKeyInterface`, + ); + expect(propertySchema?.properties?.oneNumberKeyInterface).to.deep.eq( + { + type: 'number', + enum: [3], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneNumberKeyInterface`, + ); + expect(propertySchema?.properties?.indexStrings).to.deep.eq( + { + //Unions are not perfectly supported in OpenAPI 2 (string | number) + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.indexStrings`, + ); + expect(propertySchema?.properties?.indexNumbers).to.deep.eq( + { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.indexNumbers`, + ); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(14, `for property ${propertyName}`); const keysOfAnySchema = getValidatedDefinition('KeysMember', currentSpec); - expect(keysOfAnySchema).to.deep.eq({ //Unions are not perfectly supported in OpenAPI 2 (string | number) - properties: { - keys: { - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined, - } + expect(keysOfAnySchema).to.deep.eq( + { + //Unions are not perfectly supported in OpenAPI 2 (string | number) + properties: { + keys: { + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + required: ['keys'], + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, }, - required: ["keys"], - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keysOfAny`); + `for property ${propertyName}.keysOfAny`, + ); const keysOfInterfaceSchema = getValidatedDefinition('KeysMember_NestedTypeLiteral_', currentSpec); - expect(keysOfInterfaceSchema).to.deep.eq({ - properties: { - keys: { - type: "string", - enum: ["a", "b", "e"], - "x-nullable": false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - } + expect(keysOfInterfaceSchema).to.deep.eq( + { + properties: { + keys: { + type: 'string', + enum: ['a', 'b', 'e'], + 'x-nullable': false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + required: ['keys'], + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, }, - required: ["keys"], - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keysOfInterface`); + `for property ${propertyName}.keysOfInterface`, + ); }, nestedTypes: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.multiplePartial?.$ref).to.eq("#/definitions/Partial_Partial__a-string___", `for property ${propertyName}`); - expect(propertySchema?.properties?.separateField?.$ref).to.eq("#/definitions/Partial_SeparateField_Partial__a-string--b-string__.a__", `for property ${propertyName}`); - expect(propertySchema?.properties?.separateField2?.$ref).to.eq("#/definitions/Partial_SeparateField_Partial__a-string--b-string__.a-or-b__", `for property ${propertyName}`); - expect(propertySchema?.properties?.separateField3?.$ref).to.eq("#/definitions/Partial_SeparateField_Partial__a-string--b-number__.a-or-b__", `for property ${propertyName}`); + expect(propertySchema?.properties?.multiplePartial?.$ref).to.eq('#/definitions/Partial_Partial__a-string___', `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField?.$ref).to.eq('#/definitions/Partial_SeparateField_Partial__a-string--b-string__.a__', `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField2?.$ref).to.eq('#/definitions/Partial_SeparateField_Partial__a-string--b-string__.a-or-b__', `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField3?.$ref).to.eq('#/definitions/Partial_SeparateField_Partial__a-string--b-number__.a-or-b__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); const multiplePartialSchema = getValidatedDefinition('Partial_Partial__a-string___', currentSpec); - expect(multiplePartialSchema).to.deep.eq({ - properties: { - a: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + expect(multiplePartialSchema).to.deep.eq( + { + properties: { + a: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.multiplePartial`); + `for property ${propertyName}.multiplePartial`, + ); const separateFieldSchema = getValidatedDefinition('Partial_SeparateField_Partial__a-string--b-string__.a__', currentSpec); - expect(separateFieldSchema).to.deep.eq({ - properties: { - omitted: { - $ref: "#/definitions/Omit_Partial__a-string--b-string__.a_", - description: undefined, - example: undefined, - format: undefined, + expect(separateFieldSchema).to.deep.eq( + { + properties: { + omitted: { + $ref: '#/definitions/Omit_Partial__a-string--b-string__.a_', + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, }, - field: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.separateField`); + `for property ${propertyName}.separateField`, + ); const separateFieldInternalSchema = getValidatedDefinition('Omit_Partial__a-string--b-string__.a_', currentSpec); - expect(separateFieldInternalSchema).to.deep.eq({ - $ref: "#/definitions/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__", - description: "Construct a type with the properties of T except for those in type K.", - default: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.separateField.omitted`); + expect(separateFieldInternalSchema).to.deep.eq( + { + $ref: '#/definitions/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__', + description: 'Construct a type with the properties of T except for those in type K.', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField.omitted`, + ); const separateFieldInternal2Schema = getValidatedDefinition('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__', currentSpec); - expect(separateFieldInternal2Schema).to.deep.eq({ - properties: { - b: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + expect(separateFieldInternal2Schema).to.deep.eq( + { + properties: { + b: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField.omitted`); + `for property ${propertyName}.separateField.omitted`, + ); const separateField2Schema = getValidatedDefinition('Partial_SeparateField_Partial__a-string--b-string__.a-or-b__', currentSpec); - expect(separateField2Schema).to.deep.eq({ - properties: { - omitted: { - $ref: "#/definitions/Omit_Partial__a-string--b-string__.a-or-b_", - description: undefined, - example: undefined, - format: undefined + expect(separateField2Schema).to.deep.eq( + { + properties: { + omitted: { + $ref: '#/definitions/Omit_Partial__a-string--b-string__.a-or-b_', + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, }, - field: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField2`); + `for property ${propertyName}.separateField2`, + ); const separateField2InternalSchema = getValidatedDefinition('Omit_Partial__a-string--b-string__.a-or-b_', currentSpec); - expect(separateField2InternalSchema?.$ref).to.eq("#/definitions/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__", - `for property ${propertyName}.separateField2.omitted` + expect(separateField2InternalSchema?.$ref).to.eq( + '#/definitions/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__', + `for property ${propertyName}.separateField2.omitted`, ); const separateField2Internal2Schema = getValidatedDefinition('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__', currentSpec); - expect(separateField2Internal2Schema).to.deep.eq({ - properties: {}, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField2.omitted`); + expect(separateField2Internal2Schema).to.deep.eq( + { + properties: {}, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField2.omitted`, + ); const separateField3Schema = getValidatedDefinition('Partial_SeparateField_Partial__a-string--b-number__.a-or-b__', currentSpec); - expect(separateField3Schema).to.deep.eq({ //Unions are not supported in OpenAPI 2 - properties: + expect(separateField3Schema).to.deep.eq( { - omitted: { - $ref: "#/definitions/Omit_Partial__a-string--b-number__.a-or-b_", - description: undefined, - example: undefined, - format: undefined + //Unions are not supported in OpenAPI 2 + properties: { + omitted: { + $ref: '#/definitions/Omit_Partial__a-string--b-number__.a-or-b_', + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: 'object', + description: undefined, + default: undefined, + example: undefined, + format: undefined, + }, }, - field: { - type: "object", - description: undefined, - default: undefined, - example: undefined, - format: undefined - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField3`); + `for property ${propertyName}.separateField3`, + ); const separateField3InternalSchema = getValidatedDefinition('Omit_Partial__a-string--b-number__.a-or-b_', currentSpec); - expect(separateField3InternalSchema?.$ref).to.eq("#/definitions/Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__", - `for property ${propertyName}.separateField3.omitted` + expect(separateField3InternalSchema?.$ref).to.eq( + '#/definitions/Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__', + `for property ${propertyName}.separateField3.omitted`, ); const separateField3Internal2Schema = getValidatedDefinition('Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__', currentSpec); - expect(separateField3Internal2Schema).to.deep.eq({ - properties: {}, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.separateField3.omitted`); - } + expect(separateField3Internal2Schema).to.deep.eq( + { + properties: {}, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField3.omitted`, + ); + }, }; Object.keys(assertionsPerProperty).forEach(aPropertyName => { @@ -3507,7 +3590,3 @@ describe('Definition generation', () => { }); }); }); - - - - diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts index 12021c24d..780de3e09 100644 --- a/tests/unit/swagger/schemaDetails3.spec.ts +++ b/tests/unit/swagger/schemaDetails3.spec.ts @@ -1597,10 +1597,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { default: undefined, }, }, - required: [ - 'record-foo', - 'record-bar' - ], + required: ['record-foo', 'record-bar'], type: 'object', default: undefined, example: undefined, @@ -1636,7 +1633,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { default: undefined, }, }, - required: ["1", "2"], + required: ['1', '2'], type: 'object', default: undefined, example: undefined, @@ -1655,18 +1652,18 @@ describe('Definition generation for OpenAPI 3.0.0', () => { description: undefined, example: undefined, format: undefined, - type: "string" - } + type: 'string', + }, }, - required: ["data"], - type: "object" + required: ['data'], + type: 'object', }, default: undefined, - description: "Construct a type with a set of properties K of type T", + description: 'Construct a type with a set of properties K of type T', example: undefined, format: undefined, properties: {}, - type: "object" + type: 'object', }); }, numberRecord: (propertyName, propertySchema) => { @@ -1680,18 +1677,18 @@ describe('Definition generation for OpenAPI 3.0.0', () => { description: undefined, example: undefined, format: undefined, - type: "string" - } + type: 'string', + }, }, - required: ["data"], - type: "object" + required: ['data'], + type: 'object', }, default: undefined, - description: "Construct a type with a set of properties K of type T", + description: 'Construct a type with a set of properties K of type T', example: undefined, format: undefined, properties: {}, - type: "object" + type: 'object', }); }, modelsObjectIndirect: (propertyName, propertySchema) => { @@ -2631,7 +2628,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { 'weight:desc', 'human:desc', 'gender:desc', - 'nicknames:desc' + 'nicknames:desc', ], type: 'object', description: undefined, @@ -2693,50 +2690,44 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { typeHolder2: { - $ref: "#/components/schemas/Namespace2.TypeHolder", + $ref: '#/components/schemas/Namespace2.TypeHolder', description: undefined, example: undefined, - format: undefined + format: undefined, }, inModule: { - $ref: "#/components/schemas/Namespace2.Namespace2.NamespaceType", + $ref: '#/components/schemas/Namespace2.Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, typeHolder1: { - $ref: "#/components/schemas/Namespace1.TypeHolder", + $ref: '#/components/schemas/Namespace1.TypeHolder', description: undefined, example: undefined, - format: undefined + format: undefined, }, inNamespace1: { - $ref: "#/components/schemas/Namespace1.NamespaceType", + $ref: '#/components/schemas/Namespace1.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, simple: { - $ref: "#/components/schemas/NamespaceType", + $ref: '#/components/schemas/NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: [ - "typeHolder2", - "inModule", - "typeHolder1", - "inNamespace1", - "simple" - ], - type: "object", + required: ['typeHolder2', 'inModule', 'typeHolder1', 'inNamespace1', 'simple'], + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}` + `for property ${propertyName}`, ); const typeHolder2Schema = getComponentSchema('Namespace2.TypeHolder', currentSpec); @@ -2744,25 +2735,24 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { inModule: { - $ref: "#/components/schemas/Namespace2.Namespace2.NamespaceType", + $ref: '#/components/schemas/Namespace2.Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, inNamespace2: { - $ref: "#/components/schemas/Namespace2.NamespaceType", + $ref: '#/components/schemas/Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inModule", - "inNamespace2"], - type: "object", + required: ['inModule', 'inNamespace2'], + type: 'object', additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, - description: undefined + description: undefined, }, - `for property ${propertyName}.typeHolder2` + `for property ${propertyName}.typeHolder2`, ); const namespace2_namespace2_namespaceTypeSchema = getComponentSchema('Namespace2.Namespace2.NamespaceType', currentSpec); @@ -2770,51 +2760,50 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { inModule: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, other: { - $ref: "#/components/schemas/Namespace2.Namespace2.NamespaceType", + $ref: '#/components/schemas/Namespace2.Namespace2.NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inModule"], - type: "object", + required: ['inModule'], + type: 'object', description: undefined, additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.typeHolder2.inModule` + `for property ${propertyName}.typeHolder2.inModule`, ); const typeHolderSchema = getComponentSchema('Namespace1.TypeHolder', currentSpec); expect(typeHolderSchema).to.deep.eq( { properties: { - inNamespace1_1: - { - $ref: "#/components/schemas/Namespace1.NamespaceType", + inNamespace1_1: { + $ref: '#/components/schemas/Namespace1.NamespaceType', description: undefined, example: undefined, - format: undefined + format: undefined, }, inNamespace1_2: { - $ref: "#/components/schemas/Namespace1.NamespaceType", + $ref: '#/components/schemas/Namespace1.NamespaceType', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inNamespace1_1", "inNamespace1_2"], - type: "object", + required: ['inNamespace1_1', 'inNamespace1_2'], + type: 'object', additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, - description: undefined + description: undefined, }, - `for property ${propertyName}.typeHolder1` + `for property ${propertyName}.typeHolder1`, ); const namespace1_namespaceTypeSchema = getComponentSchema('Namespace1.NamespaceType', currentSpec); @@ -2822,26 +2811,26 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { inFirstNamespace: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, inFirstNamespace2: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["inFirstNamespace", "inFirstNamespace2"], - type: "object", + required: ['inFirstNamespace', 'inFirstNamespace2'], + type: 'object', description: undefined, additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.typeHolder1.inNamespace1_1` + `for property ${propertyName}.typeHolder1.inNamespace1_1`, ); const namespace2_namespaceTypeSchema = getComponentSchema('Namespace2.NamespaceType', currentSpec); @@ -2849,202 +2838,221 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { inSecondNamespace: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, }, - required: ["inSecondNamespace"], - type: "object", + required: ['inSecondNamespace'], + type: 'object', description: undefined, additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.typeHolder2.inNamespace2` + `for property ${propertyName}.typeHolder2.inNamespace2`, ); const namespaceTypeSchema = getComponentSchema('NamespaceType', currentSpec); expect(namespaceTypeSchema).to.deep.eq( { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.simple` + `for property ${propertyName}.simple`, ); }, defaults: (propertyName, propertySchema) => { - expect(propertySchema).to.deep.eq({ - default: undefined, - description: undefined, - example: undefined, - format: undefined, - properties: { - basic: { - $ref: "#/components/schemas/DefaultsClass", - description: undefined, - example: undefined, - format: undefined, - }, - defaultNull: { - default: null, - description: undefined, - example: undefined, - format: undefined, - nullable: true, - type: "string" - }, - defaultObject: { - default: { - a: "a", - b: 2 + expect(propertySchema).to.deep.eq( + { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + properties: { + basic: { + $ref: '#/components/schemas/DefaultsClass', + description: undefined, + example: undefined, + format: undefined, }, - description: undefined, - example: undefined, - format: undefined, - properties: { - a: { - default: undefined, - description: undefined, - example: undefined, - format: undefined, - type: "string" + defaultNull: { + default: null, + description: undefined, + example: undefined, + format: undefined, + nullable: true, + type: 'string', + }, + defaultObject: { + default: { + a: 'a', + b: 2, }, - b: { - default: undefined, - description: undefined, - example: undefined, - format: "double", - type: "number" - } + description: undefined, + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: 'string', + }, + b: { + default: undefined, + description: undefined, + example: undefined, + format: 'double', + type: 'number', + }, + }, + required: ['b', 'a'], + type: 'object', + }, + defaultUndefined: { + default: undefined, + description: undefined, + example: undefined, + format: undefined, + type: 'string', + }, + replacedTypes: { + $ref: '#/components/schemas/ReplaceTypes_DefaultsClass.boolean.string_', + description: undefined, + example: undefined, + format: undefined, + }, + comments: { + default: 4, + description: undefined, + example: undefined, + format: undefined, + }, + jsonCharacters: { + default: { '\\': '\n' }, + description: undefined, + example: undefined, + format: undefined, + }, + stringEscapeCharacters: { + default: '`"\'"\'\n\t\r\b\fgx\\', + description: undefined, + example: undefined, + format: undefined, }, - required: ["b", "a"], - type: "object" - }, - defaultUndefined: { - default: undefined, - description: undefined, - example: undefined, - format: undefined, - type: "string" }, - replacedTypes: { - $ref: "#/components/schemas/ReplaceTypes_DefaultsClass.boolean.string_", - description: undefined, - example: undefined, - format: undefined - } + required: ['replacedTypes', 'basic'], + type: 'object', }, - required: ["replacedTypes", "basic"], - type: "object" - }, - `for property ${propertyName}` + `for property ${propertyName}`, ); const basicSchema = getComponentSchema('DefaultsClass', currentSpec); expect(basicSchema).to.deep.eq( { properties: { boolValue1: { - type: "boolean", + type: 'boolean', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue2: { - type: "boolean", + type: 'boolean', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue3: { - type: "boolean", + type: 'boolean', default: false, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue4: { - type: "boolean", + type: 'boolean', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", + type: 'object', required: undefined, description: undefined, additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, }, - `for property ${propertyName}.basic` + `for property ${propertyName}.basic`, ); const replacedTypesSchema = getComponentSchema('ReplaceTypes_DefaultsClass.boolean.string_', currentSpec); expect(replacedTypesSchema).to.deep.eq( { properties: { boolValue1: { - type: "string", + type: 'string', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue2: { - type: "string", + type: 'string', default: true, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue3: { - type: "string", + type: 'string', default: false, description: undefined, example: undefined, - format: undefined + format: undefined, }, boolValue4: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", + type: 'object', description: undefined, default: undefined, example: undefined, format: undefined, }, - `for property ${propertyName}.replacedTypes` + `for property ${propertyName}.replacedTypes`, ); }, jsDocTypeNames: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.simple?.$ref).to.eq("#/components/schemas/Partial__a-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.commented?.$ref).to.eq("#/components/schemas/Partial__a_description-comment_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq("#/components/schemas/Partial__a_description-multiline%5Cncomment_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.defaultValue?.$ref).to.eq("#/components/schemas/Partial__a_default-true_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.deprecated?.$ref).to.eq("#/components/schemas/Partial__a_deprecated-true_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.validators?.$ref).to.eq("#/components/schemas/Partial__a_validators%3A_minLength%3A_value%3A3___-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.examples?.$ref).to.eq("#/components/schemas/Partial__a_example-example_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.extensions?.$ref).to.eq("#/components/schemas/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__", `for property ${propertyName}`); - expect(propertySchema?.properties?.ignored?.$ref).to.eq("#/components/schemas/Partial__a_ignored-true_-string__", `for property ${propertyName}`); - - expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring__", `for property ${propertyName}`); + expect(propertySchema?.properties?.simple?.$ref).to.eq('#/components/schemas/Partial__a-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.commented?.$ref).to.eq('#/components/schemas/Partial__a_description-comment_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq('#/components/schemas/Partial__a_description-multiline%5Cncomment_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.defaultValue?.$ref).to.eq('#/components/schemas/Partial__a_default-true_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.deprecated?.$ref).to.eq('#/components/schemas/Partial__a_deprecated-true_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.validators?.$ref).to.eq('#/components/schemas/Partial__a_validators%3A_minLength%3A_value%3A3___-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.examples?.$ref).to.eq('#/components/schemas/Partial__a_example-example_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.extensions?.$ref).to.eq('#/components/schemas/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.ignored?.$ref).to.eq('#/components/schemas/Partial__a_ignored-true_-string__', `for property ${propertyName}`); + + expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(18, `for property ${propertyName}`); @@ -3053,388 +3061,390 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { a: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.simple` + `for property ${propertyName}.simple`, ); const commentedSchema = getComponentSchema('Partial__a_description-comment_-string__', currentSpec); expect(commentedSchema).to.deep.eq( { properties: { a: { - type: "string", - description: "comment", + type: 'string', + description: 'comment', default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.commented` + `for property ${propertyName}.commented`, ); const multilineCommentedSchema = getComponentSchema('Partial__a_description-multiline\\ncomment_-string__', currentSpec); expect(multilineCommentedSchema).to.deep.eq( { properties: { a: { - type: "string", - description: "multiline\ncomment", + type: 'string', + description: 'multiline\ncomment', default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.multilineCommented` + `for property ${propertyName}.multilineCommented`, ); const defaultValueSchema = getComponentSchema('Partial__a_default-true_-string__', currentSpec); expect(defaultValueSchema).to.deep.eq( { properties: { a: { - type: "string", - default: "true", + type: 'string', + default: 'true', description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.defaultValue` + `for property ${propertyName}.defaultValue`, ); const deprecatedSchema = getComponentSchema('Partial__a_deprecated-true_-string__', currentSpec); expect(deprecatedSchema).to.deep.eq( { properties: { a: { - type: "string", + type: 'string', deprecated: true, description: undefined, default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.deprecated` + `for property ${propertyName}.deprecated`, ); const validatorsSchema = getComponentSchema('Partial__a_validators:_minLength:_value:3___-string__', currentSpec); expect(validatorsSchema).to.deep.eq( { properties: { a: { - type: "string", + type: 'string', minLength: 3, description: undefined, default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.validators` + `for property ${propertyName}.validators`, ); const examplesSchema = getComponentSchema('Partial__a_example-example_-string__', currentSpec); - expect(examplesSchema).to.deep.eq({ - default: undefined, - description: "Make all properties in T optional", - example: undefined, - format: undefined, - properties: { - a: { - default: undefined, - description: undefined, - example: "example", - format: undefined, - type: "string" - } + expect(examplesSchema).to.deep.eq( + { + default: undefined, + description: 'Make all properties in T optional', + example: undefined, + format: undefined, + properties: { + a: { + default: undefined, + description: undefined, + example: 'example', + format: undefined, + type: 'string', + }, + }, + type: 'object', }, - type: "object" - }, - `for property ${propertyName}.examples` + `for property ${propertyName}.examples`, ); const extensionsSchema = getComponentSchema('Partial__a_extensions:[_key-x-key-1.value-value-1_]_-string__', currentSpec); expect(extensionsSchema).to.deep.eq( { properties: { a: { - type: "string", - "x-key-1": "value-1", + type: 'string', + 'x-key-1': 'value-1', description: undefined, default: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.extensions` + `for property ${propertyName}.extensions`, ); const ignoredSchema = getComponentSchema('Partial__a_ignored-true_-string__', currentSpec); expect(ignoredSchema).to.deep.eq( { - properties: { - }, - type: "object", - description: "Make all properties in T optional", + properties: {}, + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.ignored` + `for property ${propertyName}.ignored`, ); const indexedSchema = getComponentSchema('Partial__[a-string]:string__', currentSpec); expect(indexedSchema).to.deep.eq( { - properties: { - }, + properties: {}, additionalProperties: { - type: "string" + type: 'string', }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.indexedSimple` + `for property ${propertyName}.indexedSimple`, ); }, jsdocMap: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.omitted?.$ref).to.eq("#/components/schemas/Omit_JsDocced.notRelevant_", `for property ${propertyName}`); - expect(propertySchema?.properties?.partial?.$ref).to.eq("#/components/schemas/Partial_JsDocced_", `for property ${propertyName}`); - expect(propertySchema?.properties?.replacedTypes?.$ref).to.eq("#/components/schemas/ReplaceStringAndNumberTypes_JsDocced_", `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleReplacedTypes?.$ref).to.eq("#/components/schemas/ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__", `for property ${propertyName}`); - expect(propertySchema?.properties?.postfixed?.$ref).to.eq("#/components/schemas/Postfixed_JsDocced._PostFix_", `for property ${propertyName}`); - expect(propertySchema?.properties?.values?.$ref).to.eq("#/components/schemas/Values_JsDocced_", `for property ${propertyName}`); - expect(propertySchema?.properties?.typesValues?.$ref).to.eq("#/components/schemas/InternalTypes_Values_JsDocced__", `for property ${propertyName}`); + expect(propertySchema?.properties?.omitted?.$ref).to.eq('#/components/schemas/Omit_JsDocced.notRelevant_', `for property ${propertyName}`); + expect(propertySchema?.properties?.partial?.$ref).to.eq('#/components/schemas/Partial_JsDocced_', `for property ${propertyName}`); + expect(propertySchema?.properties?.replacedTypes?.$ref).to.eq('#/components/schemas/ReplaceStringAndNumberTypes_JsDocced_', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleReplacedTypes?.$ref).to.eq( + '#/components/schemas/ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__', + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.postfixed?.$ref).to.eq('#/components/schemas/Postfixed_JsDocced._PostFix_', `for property ${propertyName}`); + expect(propertySchema?.properties?.values?.$ref).to.eq('#/components/schemas/Values_JsDocced_', `for property ${propertyName}`); + expect(propertySchema?.properties?.typesValues?.$ref).to.eq('#/components/schemas/InternalTypes_Values_JsDocced__', `for property ${propertyName}`); expect(propertySchema?.properties?.onlyOneValue).to.deep.eq( { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, - example: undefined + example: undefined, }, - `for property ${propertyName}.onlyOneValue` + `for property ${propertyName}.onlyOneValue`, ); - expect(propertySchema?.properties?.synonym?.$ref).to.eq("#/components/schemas/JsDoccedSynonym", `for property ${propertyName}`); - expect(propertySchema?.properties?.synonym2?.$ref).to.eq("#/components/schemas/JsDoccedSynonym2", `for property ${propertyName}`); + expect(propertySchema?.properties?.synonym?.$ref).to.eq('#/components/schemas/JsDoccedSynonym', `for property ${propertyName}`); + expect(propertySchema?.properties?.synonym2?.$ref).to.eq('#/components/schemas/JsDoccedSynonym2', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(10, `for property ${propertyName}`); const omittedSchema = getComponentSchema('Omit_JsDocced.notRelevant_', currentSpec); expect(omittedSchema).to.deep.eq( { - $ref: "#/components/schemas/Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__", - description: "Construct a type with the properties of T except for those in type K.", + $ref: '#/components/schemas/Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__', + description: 'Construct a type with the properties of T except for those in type K.', default: undefined, example: undefined, format: undefined, }, - `for property ${propertyName}.omitted` + `for property ${propertyName}.omitted`, ); const omittedSchema2 = getComponentSchema('Pick_JsDocced.Exclude_keyofJsDocced.notRelevant__', currentSpec); - expect(omittedSchema2).to.deep.eq({ - properties: { - stringValue: { - type: "string", - default: "def", - maxLength: 3, - description: undefined, - example: undefined, - format: undefined, - }, - numberValue: { - type: "integer", - format: "int32", - default: 6, - description: undefined, - example: undefined - } - }, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.omitted` - ); - const partialSchema = getComponentSchema('Partial_JsDocced_', currentSpec); + expect(omittedSchema2).to.deep.eq( + { + properties: { + stringValue: { + type: 'string', + default: 'def', + maxLength: 3, + description: undefined, + example: undefined, + format: undefined, + }, + numberValue: { + type: 'integer', + format: 'int32', + default: 6, + description: undefined, + example: undefined, + }, + }, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.omitted`, + ); + const partialSchema = getComponentSchema('Partial_JsDocced_', currentSpec); expect(partialSchema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, format: undefined, example: undefined, - description: undefined + description: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, example: undefined, - description: undefined - } + description: undefined, + }, }, - type: "object", - description: "Make all properties in T optional", + type: 'object', + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.partial` + `for property ${propertyName}.partial`, ); const replacedTypesSchema = getComponentSchema('ReplaceStringAndNumberTypes_JsDocced_', currentSpec); expect(replacedTypesSchema).to.deep.eq( { - $ref: "#/components/schemas/ReplaceTypes_JsDocced.string.number_", + $ref: '#/components/schemas/ReplaceTypes_JsDocced.string.number_', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.replacedTypes` + `for property ${propertyName}.replacedTypes`, ); const replacedTypes2Schema = getComponentSchema('ReplaceTypes_JsDocced.string.number_', currentSpec); - expect(replacedTypes2Schema).to.deep.eq({ - properties: { - stringValue: { - type: "number", - format: "double", - default: "def", - maxLength: 3, - description: undefined, - example: undefined, + expect(replacedTypes2Schema).to.deep.eq( + { + properties: { + stringValue: { + type: 'number', + format: 'double', + default: 'def', + maxLength: 3, + description: undefined, + example: undefined, + }, + numberValue: { + type: 'string', + default: 6, + description: undefined, + example: undefined, + format: undefined, + }, }, - numberValue: { - type: "string", - default: 6, - description: undefined, - example: undefined, - format: undefined - } + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, }, - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.replacedTypes` + `for property ${propertyName}.replacedTypes`, ); const doubleReplacedTypesSchema = getComponentSchema('ReplaceStringAndNumberTypes_ReplaceStringAndNumberTypes_JsDocced__', currentSpec); expect(doubleReplacedTypesSchema).to.deep.eq( { - $ref: "#/components/schemas/ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_", + $ref: '#/components/schemas/ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.doubleReplacedTypes` + `for property ${propertyName}.doubleReplacedTypes`, ); const doubleReplacedTypes2Schema = getComponentSchema('ReplaceTypes_ReplaceStringAndNumberTypes_JsDocced_.string.number_', currentSpec); expect(doubleReplacedTypes2Schema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, description: undefined, example: undefined, }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.doubleReplacedTypes` + `for property ${propertyName}.doubleReplacedTypes`, ); const postfixedSchema = getComponentSchema('Postfixed_JsDocced._PostFix_', currentSpec); expect(postfixedSchema).to.deep.eq( { properties: { stringValue_PostFix: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue_PostFix: { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, - example: undefined - } + example: undefined, + }, }, - required: [ - "stringValue_PostFix", "numberValue_PostFix" - ], - type: "object", + required: ['stringValue_PostFix', 'numberValue_PostFix'], + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.postfixed` + `for property ${propertyName}.postfixed`, ); const valuesSchema = getComponentSchema('Values_JsDocced_', currentSpec); expect(valuesSchema).to.deep.eq( @@ -3443,74 +3453,74 @@ describe('Definition generation for OpenAPI 3.0.0', () => { stringValue: { properties: { value: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["value"], - type: "object", - default: "def", + required: ['value'], + type: 'object', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { properties: { value: { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, example: undefined, - } + }, }, - required: ["value"], - type: "object", + required: ['value'], + type: 'object', default: 6, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.values` + `for property ${propertyName}.values`, ); const typesValuesSchema = getComponentSchema('InternalTypes_Values_JsDocced__', currentSpec); expect(typesValuesSchema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, description: undefined, example: undefined, - } + }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.typesValues` + `for property ${propertyName}.typesValues`, ); const synonymSchema = getComponentSchema('JsDoccedSynonym', currentSpec); @@ -3518,63 +3528,63 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { stringValue: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "number", - format: "double", + type: 'number', + format: 'double', default: undefined, description: undefined, example: undefined, - } + }, }, - required: ["stringValue", "numberValue"], - type: "object", + required: ['stringValue', 'numberValue'], + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.synonym` + `for property ${propertyName}.synonym`, ); const synonym2Schema = getComponentSchema('JsDoccedSynonym2', currentSpec); expect(synonym2Schema).to.deep.eq( { properties: { stringValue: { - type: "string", - default: "def", + type: 'string', + default: 'def', maxLength: 3, description: undefined, example: undefined, - format: undefined + format: undefined, }, numberValue: { - type: "integer", - format: "int32", + type: 'integer', + format: 'int32', default: 6, description: undefined, - example: undefined - } + example: undefined, + }, }, - type: "object", + type: 'object', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.synonym2` + `for property ${propertyName}.synonym2`, ); }, duplicatedDefinitions: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.interfaces?.$ref).to.eq("#/components/schemas/DuplicatedInterface", `for property ${propertyName}`); - expect(propertySchema?.properties?.enums?.$ref).to.eq("#/components/schemas/DuplicatedEnum", `for property ${propertyName}`); - expect(propertySchema?.properties?.enumMember?.$ref).to.eq("#/components/schemas/DuplicatedEnum.C", `for property ${propertyName}`); - expect(propertySchema?.properties?.namespaceMember?.$ref).to.eq("#/components/schemas/DuplicatedEnum.D", `for property ${propertyName}`); + expect(propertySchema?.properties?.interfaces?.$ref).to.eq('#/components/schemas/DuplicatedInterface', `for property ${propertyName}`); + expect(propertySchema?.properties?.enums?.$ref).to.eq('#/components/schemas/DuplicatedEnum', `for property ${propertyName}`); + expect(propertySchema?.properties?.enumMember?.$ref).to.eq('#/components/schemas/DuplicatedEnum.C', `for property ${propertyName}`); + expect(propertySchema?.properties?.namespaceMember?.$ref).to.eq('#/components/schemas/DuplicatedEnum.D', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); @@ -3583,69 +3593,74 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { a: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined + format: undefined, }, b: { - type: "string", + type: 'string', default: undefined, description: undefined, example: undefined, - format: undefined - } + format: undefined, + }, }, - required: ["a", "b"], - type: "object", + required: ['a', 'b'], + type: 'object', additionalProperties: currentSpec.specName === 'specWithNoImplicitExtras' ? false : true, description: undefined, - }, - `for property ${propertyName}.interfaces` + `for property ${propertyName}.interfaces`, ); const enumsSchema = getComponentSchema('DuplicatedEnum', currentSpec); expect(enumsSchema).to.deep.eq( { - enum: ["AA", "BB", "CC"], - type: "string", - description: undefined + enum: ['AA', 'BB', 'CC'], + type: 'string', + description: undefined, }, - `for property ${propertyName}.enums` + `for property ${propertyName}.enums`, ); const enumMemberSchema = getComponentSchema('DuplicatedEnum.C', currentSpec); expect(enumMemberSchema).to.deep.eq( { - enum: ["CC"], - type: "string", - description: undefined + enum: ['CC'], + type: 'string', + description: undefined, }, - `for property ${propertyName}.enumMember` + `for property ${propertyName}.enumMember`, ); const namespaceMemberSchema = getComponentSchema('DuplicatedEnum.D', currentSpec); expect(namespaceMemberSchema).to.deep.eq( { - enum: ["DD"], - type: "string", + enum: ['DD'], + type: 'string', nullable: false, description: undefined, default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.namespaceMember` + `for property ${propertyName}.namespaceMember`, ); }, mappeds: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.unionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-or-_b-number__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-and-_b-number__", `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq("#/components/schemas/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__", `for property ${propertyName}`); - expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq("#/components/schemas/Partial__a-string_-or-(_b-string_-and-_c-string_)_", `for property ${propertyName}`); - expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq("#/components/schemas/Partial_(_a-string_-or-_b-string_)-and-_c-string__", `for property ${propertyName}`); + expect(propertySchema?.properties?.unionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-_b-number__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq( + '#/components/schemas/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__', + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-and-_b-number__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq( + '#/components/schemas/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__', + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-(_b-string_-and-_c-string_)_', `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/components/schemas/Partial_(_a-string_-or-_b-string_)-and-_c-string__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); @@ -3656,27 +3671,27 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { a: { - type: "string" - } + type: 'string', + }, }, - type: "object" + type: 'object', }, { properties: { b: { - format: "double", - type: "number" - } + format: 'double', + type: 'number', + }, }, - type: "object" - } + type: 'object', + }, ], - description: "Make all properties in T optional", + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.unionMap` + `for property ${propertyName}.unionMap`, ); const indexedUnionMapSchema = getComponentSchema('Partial__a-string_-or-_[b-string]:number__', currentSpec); expect(indexedUnionMapSchema).to.deep.eq( @@ -3685,26 +3700,26 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { a: { - type: "string" - } + type: 'string', + }, }, - type: "object" + type: 'object', }, { additionalProperties: { - format: "double", - type: "number" + format: 'double', + type: 'number', }, properties: {}, - type: "object" - } + type: 'object', + }, ], - description: "Make all properties in T optional", + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.indexedUnionMap` + `for property ${propertyName}.indexedUnionMap`, ); const doubleIndexedUnionMapSchema = getComponentSchema('Partial__[a-string]:string_-or-_[b-string]:number__', currentSpec); expect(doubleIndexedUnionMapSchema).to.deep.eq( @@ -3712,97 +3727,100 @@ describe('Definition generation for OpenAPI 3.0.0', () => { anyOf: [ { additionalProperties: { - type: "string" + type: 'string', }, properties: {}, - type: "object" + type: 'object', }, { additionalProperties: { - format: "double", - type: "number" + format: 'double', + type: 'number', }, properties: {}, - type: "object" - } + type: 'object', + }, ], - description: "Make all properties in T optional", + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.doubleIndexedUnionMap` + `for property ${propertyName}.doubleIndexedUnionMap`, ); const intersectionMapSchema = getComponentSchema('Partial__a-string_-and-_b-number__', currentSpec); - expect(intersectionMapSchema).to.deep.eq({ - properties: { - a: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined + expect(intersectionMapSchema).to.deep.eq( + { + properties: { + a: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + b: { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, }, - b: { - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined, - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.intersectionMap` + `for property ${propertyName}.intersectionMap`, ); const indexedIntersectionMapSchema = getComponentSchema('Partial__a-string_-and-_[b-string]:number__', currentSpec); - expect(indexedIntersectionMapSchema).to.deep.eq({ - properties: { - a: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } - }, - additionalProperties: { - type: "number", - format: "double" + expect(indexedIntersectionMapSchema).to.deep.eq( + { + properties: { + a: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + additionalProperties: { + type: 'number', + format: 'double', + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.indexedIntersectionMap` + `for property ${propertyName}.indexedIntersectionMap`, ); const doubleIndexedIntersectionMapSchema = getComponentSchema('Partial__[a-string]:string_-and-_[b-number]:number__', currentSpec); - expect(doubleIndexedIntersectionMapSchema).to.deep.eq({ - properties: {}, - additionalProperties: { - anyOf: [ - { - type: "string" - }, - { - format: "double", - type: "number" - } - ] + expect(doubleIndexedIntersectionMapSchema).to.deep.eq( + { + properties: {}, + additionalProperties: { + anyOf: [ + { + type: 'string', + }, + { + format: 'double', + type: 'number', + }, + ], + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.doubleIndexedIntersectionMap` + `for property ${propertyName}.doubleIndexedIntersectionMap`, ); const parenthesizedMapSchema = getComponentSchema('Partial__a-string_-or-(_b-string_-and-_c-string_)_', currentSpec); expect(parenthesizedMapSchema).to.deep.eq( @@ -3811,29 +3829,29 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { a: { - type: "string" - } + type: 'string', + }, }, - type: "object" + type: 'object', }, { properties: { b: { - type: "string" + type: 'string', }, c: { - type: "string" - } + type: 'string', + }, }, - type: "object" - } + type: 'object', + }, ], - description: "Make all properties in T optional", + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.parenthesizedMap` + `for property ${propertyName}.parenthesizedMap`, ); const parenthesizedMap2Schema = getComponentSchema('Partial_(_a-string_-or-_b-string_)-and-_c-string__', currentSpec); expect(parenthesizedMap2Schema).to.deep.eq( @@ -3842,471 +3860,513 @@ describe('Definition generation for OpenAPI 3.0.0', () => { { properties: { a: { - type: "string" + type: 'string', }, c: { - type: "string" - } + type: 'string', + }, }, - type: "object" + type: 'object', }, { properties: { b: { - type: "string" + type: 'string', }, c: { - type: "string" - } + type: 'string', + }, }, - type: "object" - } + type: 'object', + }, ], - description: "Make all properties in T optional", + description: 'Make all properties in T optional', default: undefined, example: undefined, - format: undefined + format: undefined, }, - `for property ${propertyName}.parenthesizedMap2` + `for property ${propertyName}.parenthesizedMap2`, ); }, conditionals: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.simpeConditional).to.deep.eq({ - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined - }, - `for property ${propertyName}`); - expect(propertySchema?.properties?.simpeFalseConditional).to.deep.eq({ - type: "boolean", - format: undefined, - default: undefined, - description: undefined, - example: undefined - }, - `for property ${propertyName}`); - expect(propertySchema?.properties?.typedConditional?.$ref).to.eq("#/components/schemas/Conditional_string.string.number.boolean_", `for property ${propertyName}`); - expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq("#/components/schemas/Conditional_string.number.number.boolean_", `for property ${propertyName}`); - expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq("#/components/schemas/Dummy_Conditional_string.string.number.boolean__", `for property ${propertyName}`); - expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq("#/components/schemas/Dummy_Conditional_string.number.number.boolean__", `for property ${propertyName}`); - expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq("#/components/schemas/Partial_stringextendsstring%3F_a-number_-never_", `for property ${propertyName}`); - expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq("#/components/schemas/Partial_Conditional_string.string._a-number_.never__", `for property ${propertyName}`); + expect(propertySchema?.properties?.simpeConditional).to.deep.eq( + { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.simpeFalseConditional).to.deep.eq( + { + type: 'boolean', + format: undefined, + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}`, + ); + expect(propertySchema?.properties?.typedConditional?.$ref).to.eq('#/components/schemas/Conditional_string.string.number.boolean_', `for property ${propertyName}`); + expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq('#/components/schemas/Conditional_string.number.number.boolean_', `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq('#/components/schemas/Dummy_Conditional_string.string.number.boolean__', `for property ${propertyName}`); + expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq('#/components/schemas/Dummy_Conditional_string.number.number.boolean__', `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq('#/components/schemas/Partial_stringextendsstring%3F_a-number_-never_', `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq('#/components/schemas/Partial_Conditional_string.string._a-number_.never__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); const typedConditionalSchema = getComponentSchema('Conditional_string.string.number.boolean_', currentSpec); - expect(typedConditionalSchema).to.deep.eq({ - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined, - }, - `for property ${propertyName}.typedConditional`); + expect(typedConditionalSchema).to.deep.eq( + { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedConditional`, + ); const typedFalseConditionalSchema = getComponentSchema('Conditional_string.number.number.boolean_', currentSpec); - expect(typedFalseConditionalSchema).to.deep.eq({ - type: "boolean", - format: undefined, - default: undefined, - description: undefined, - example: undefined, - }, - `for property ${propertyName}.typedFalseConditional`); + expect(typedFalseConditionalSchema).to.deep.eq( + { + type: 'boolean', + format: undefined, + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.typedFalseConditional`, + ); const dummyConditionalSchema = getComponentSchema('Dummy_Conditional_string.string.number.boolean__', currentSpec); - expect(dummyConditionalSchema?.$ref).to.eq("#/components/schemas/Conditional_string.string.number.boolean_", `for property ${propertyName}.dummyConditional`); + expect(dummyConditionalSchema?.$ref).to.eq('#/components/schemas/Conditional_string.string.number.boolean_', `for property ${propertyName}.dummyConditional`); const dummyFalseConditionalSchema = getComponentSchema('Dummy_Conditional_string.number.number.boolean__', currentSpec); - expect(dummyFalseConditionalSchema?.$ref).to.eq("#/components/schemas/Conditional_string.number.number.boolean_", `for property ${propertyName}.dummyFalseConditional`); + expect(dummyFalseConditionalSchema?.$ref).to.eq('#/components/schemas/Conditional_string.number.number.boolean_', `for property ${propertyName}.dummyFalseConditional`); const mappedConditionalSchema = getComponentSchema('Partial_stringextendsstring?_a-number_-never_', currentSpec); - expect(mappedConditionalSchema).to.deep.eq({ - properties: { - a: { - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined, - } + expect(mappedConditionalSchema).to.deep.eq( + { + properties: { + a: { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + }, + type: 'object', + description: 'Make all properties in T optional', + example: undefined, + default: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - example: undefined, - default: undefined, - format: undefined, - }, - `for property ${propertyName}.mappedConditional`); + `for property ${propertyName}.mappedConditional`, + ); const mappedTypedConditionalSchema = getComponentSchema('Partial_Conditional_string.string._a-number_.never__', currentSpec); expect(mappedTypedConditionalSchema).to.deep.eq(mappedConditionalSchema, `for property ${propertyName}.mappedTypedConditional`); }, typeOperators: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.keysOfAny?.$ref).to.eq("#/components/schemas/KeysMember", `for property ${propertyName}`); - expect(propertySchema?.properties?.keysOfInterface?.$ref).to.eq("#/components/schemas/KeysMember_NestedTypeLiteral_", `for property ${propertyName}`); - expect(propertySchema?.properties?.simple).to.deep.eq({ - type: "string", - enum: ["a", "b", "e"], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.simple`); - expect(propertySchema?.properties?.keyofItem).to.deep.eq({ - type: "string", - enum: ["c", "d"], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.keyofItem`); - expect(propertySchema?.properties?.keyofAnyItem).to.deep.eq({ - anyOf: [ - { - type: "string" - }, - { - format: "double", - type: "number" - } - ], - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keyofAnyItem`); - expect(propertySchema?.properties?.keyofAny).to.deep.eq(propertySchema?.properties?.keyofAnyItem, - `for property ${propertyName}.keyofAny`); - expect(propertySchema?.properties?.stringLiterals).to.deep.eq({ - type: "string", - enum: ["A", "B", "C"], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.stringLiterals`); - expect(propertySchema?.properties?.stringAndNumberLiterals).to.deep.eq({ - anyOf: [ - { - enum: [ - "A", - "B" - ], - type: "string" - }, - { - enum: [ - 3 - ], - type: "number" - } - ], - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.stringAndNumberLiterals`); - expect(propertySchema?.properties?.keyofEnum).to.deep.eq({ - type: "string", - enum: ["A", "B", "C"], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keyofEnum`); - expect(propertySchema?.properties?.numberAndStringKeys).to.deep.eq({ - anyOf: [ - { - enum: [ - "a" - ], - type: "string" - }, - { - enum: [ - 3, - 4 - ], - type: "number" - } - ], - default: undefined, - description: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.numberAndStringKeys`); - expect(propertySchema?.properties?.oneStringKeyInterface).to.deep.eq({ - type: "string", - enum: ["a"], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.oneStringKeyInterface`); - expect(propertySchema?.properties?.oneNumberKeyInterface).to.deep.eq({ - type: "number", - enum: [3], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.oneNumberKeyInterface`); - expect(propertySchema?.properties?.indexStrings).to.deep.eq({ - anyOf: [ - { - type: "string" - }, - { - format: "double", - type: "number" - } - ], - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.indexStrings`); - expect(propertySchema?.properties?.indexNumbers).to.deep.eq({ - type: "number", - format: "double", - default: undefined, - description: undefined, - example: undefined - }, - `for property ${propertyName}.indexNumbers`); + expect(propertySchema?.properties?.keysOfAny?.$ref).to.eq('#/components/schemas/KeysMember', `for property ${propertyName}`); + expect(propertySchema?.properties?.keysOfInterface?.$ref).to.eq('#/components/schemas/KeysMember_NestedTypeLiteral_', `for property ${propertyName}`); + expect(propertySchema?.properties?.simple).to.deep.eq( + { + type: 'string', + enum: ['a', 'b', 'e'], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.simple`, + ); + expect(propertySchema?.properties?.keyofItem).to.deep.eq( + { + type: 'string', + enum: ['c', 'd'], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofItem`, + ); + expect(propertySchema?.properties?.keyofAnyItem).to.deep.eq( + { + anyOf: [ + { + type: 'string', + }, + { + format: 'double', + type: 'number', + }, + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofAnyItem`, + ); + expect(propertySchema?.properties?.keyofAny).to.deep.eq(propertySchema?.properties?.keyofAnyItem, `for property ${propertyName}.keyofAny`); + expect(propertySchema?.properties?.stringLiterals).to.deep.eq( + { + type: 'string', + enum: ['A', 'B', 'C'], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.stringLiterals`, + ); + expect(propertySchema?.properties?.stringAndNumberLiterals).to.deep.eq( + { + anyOf: [ + { + enum: ['A', 'B'], + type: 'string', + }, + { + enum: [3], + type: 'number', + }, + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.stringAndNumberLiterals`, + ); + expect(propertySchema?.properties?.keyofEnum).to.deep.eq( + { + type: 'string', + enum: ['A', 'B', 'C'], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.keyofEnum`, + ); + expect(propertySchema?.properties?.numberAndStringKeys).to.deep.eq( + { + anyOf: [ + { + enum: ['a'], + type: 'string', + }, + { + enum: [3, 4], + type: 'number', + }, + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.numberAndStringKeys`, + ); + expect(propertySchema?.properties?.oneStringKeyInterface).to.deep.eq( + { + type: 'string', + enum: ['a'], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneStringKeyInterface`, + ); + expect(propertySchema?.properties?.oneNumberKeyInterface).to.deep.eq( + { + type: 'number', + enum: [3], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.oneNumberKeyInterface`, + ); + expect(propertySchema?.properties?.indexStrings).to.deep.eq( + { + anyOf: [ + { + type: 'string', + }, + { + format: 'double', + type: 'number', + }, + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.indexStrings`, + ); + expect(propertySchema?.properties?.indexNumbers).to.deep.eq( + { + type: 'number', + format: 'double', + default: undefined, + description: undefined, + example: undefined, + }, + `for property ${propertyName}.indexNumbers`, + ); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(14, `for property ${propertyName}`); const keysOfAnySchema = getComponentSchema('KeysMember', currentSpec); - expect(keysOfAnySchema).to.deep.eq({ - properties: { - keys: { - anyOf: [ - { - type: "string" - }, - { - format: "double", - type: "number" - } - ], - default: undefined, - description: undefined, - example: undefined, - format: undefined, - } + expect(keysOfAnySchema).to.deep.eq( + { + properties: { + keys: { + anyOf: [ + { + type: 'string', + }, + { + format: 'double', + type: 'number', + }, + ], + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + required: ['keys'], + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, }, - required: ["keys"], - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keysOfAny`); + `for property ${propertyName}.keysOfAny`, + ); const keysOfInterfaceSchema = getComponentSchema('KeysMember_NestedTypeLiteral_', currentSpec); - expect(keysOfInterfaceSchema).to.deep.eq({ - properties: { - keys: { - type: "string", - enum: ["a", "b", "e"], - nullable: false, - default: undefined, - description: undefined, - example: undefined, - format: undefined, - } + expect(keysOfInterfaceSchema).to.deep.eq( + { + properties: { + keys: { + type: 'string', + enum: ['a', 'b', 'e'], + nullable: false, + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + required: ['keys'], + type: 'object', + default: undefined, + description: undefined, + example: undefined, + format: undefined, }, - required: ["keys"], - type: "object", - default: undefined, - description: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.keysOfInterface`); + `for property ${propertyName}.keysOfInterface`, + ); }, nestedTypes: (propertyName, propertySchema) => { - expect(propertySchema?.properties?.multiplePartial?.$ref).to.eq("#/components/schemas/Partial_Partial__a-string___", `for property ${propertyName}`); - expect(propertySchema?.properties?.separateField?.$ref).to.eq("#/components/schemas/Partial_SeparateField_Partial__a-string--b-string__.a__", `for property ${propertyName}`); - expect(propertySchema?.properties?.separateField2?.$ref).to.eq("#/components/schemas/Partial_SeparateField_Partial__a-string--b-string__.a-or-b__", `for property ${propertyName}`); - expect(propertySchema?.properties?.separateField3?.$ref).to.eq("#/components/schemas/Partial_SeparateField_Partial__a-string--b-number__.a-or-b__", `for property ${propertyName}`); + expect(propertySchema?.properties?.multiplePartial?.$ref).to.eq('#/components/schemas/Partial_Partial__a-string___', `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField?.$ref).to.eq('#/components/schemas/Partial_SeparateField_Partial__a-string--b-string__.a__', `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField2?.$ref).to.eq('#/components/schemas/Partial_SeparateField_Partial__a-string--b-string__.a-or-b__', `for property ${propertyName}`); + expect(propertySchema?.properties?.separateField3?.$ref).to.eq('#/components/schemas/Partial_SeparateField_Partial__a-string--b-number__.a-or-b__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(4, `for property ${propertyName}`); const multiplePartialSchema = getComponentSchema('Partial_Partial__a-string___', currentSpec); - expect(multiplePartialSchema).to.deep.eq({ - properties: { - a: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + expect(multiplePartialSchema).to.deep.eq( + { + properties: { + a: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.multiplePartial`); + `for property ${propertyName}.multiplePartial`, + ); const separateFieldSchema = getComponentSchema('Partial_SeparateField_Partial__a-string--b-string__.a__', currentSpec); - expect(separateFieldSchema).to.deep.eq({ - properties: { - omitted: { - $ref: "#/components/schemas/Omit_Partial__a-string--b-string__.a_", - description: undefined, - example: undefined, - format: undefined, + expect(separateFieldSchema).to.deep.eq( + { + properties: { + omitted: { + $ref: '#/components/schemas/Omit_Partial__a-string--b-string__.a_', + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, }, - field: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.separateField`); + `for property ${propertyName}.separateField`, + ); const separateFieldInternalSchema = getComponentSchema('Omit_Partial__a-string--b-string__.a_', currentSpec); - expect(separateFieldInternalSchema).to.deep.eq({ - $ref: "#/components/schemas/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__", - description: "Construct a type with the properties of T except for those in type K.", - default: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.separateField.omitted`); + expect(separateFieldInternalSchema).to.deep.eq( + { + $ref: '#/components/schemas/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__', + description: 'Construct a type with the properties of T except for those in type K.', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField.omitted`, + ); const separateFieldInternal2Schema = getComponentSchema('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a__', currentSpec); - expect(separateFieldInternal2Schema).to.deep.eq({ - properties: { - b: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + expect(separateFieldInternal2Schema).to.deep.eq( + { + properties: { + b: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, + }, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField.omitted`); + `for property ${propertyName}.separateField.omitted`, + ); const separateField2Schema = getComponentSchema('Partial_SeparateField_Partial__a-string--b-string__.a-or-b__', currentSpec); - expect(separateField2Schema).to.deep.eq({ - properties: { - omitted: { - $ref: "#/components/schemas/Omit_Partial__a-string--b-string__.a-or-b_", - description: undefined, - example: undefined, - format: undefined + expect(separateField2Schema).to.deep.eq( + { + properties: { + omitted: { + $ref: '#/components/schemas/Omit_Partial__a-string--b-string__.a-or-b_', + description: undefined, + example: undefined, + format: undefined, + }, + field: { + type: 'string', + default: undefined, + description: undefined, + example: undefined, + format: undefined, + }, }, - field: { - type: "string", - default: undefined, - description: undefined, - example: undefined, - format: undefined - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField2`); + `for property ${propertyName}.separateField2`, + ); const separateField2InternalSchema = getComponentSchema('Omit_Partial__a-string--b-string__.a-or-b_', currentSpec); - expect(separateField2InternalSchema?.$ref).to.eq("#/components/schemas/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__", - `for property ${propertyName}.separateField2.omitted` + expect(separateField2InternalSchema?.$ref).to.eq( + '#/components/schemas/Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__', + `for property ${propertyName}.separateField2.omitted`, ); const separateField2Internal2Schema = getComponentSchema('Pick_Partial__a-string--b-string__.Exclude_keyofPartial__a-string--b-string__.a-or-b__', currentSpec); - expect(separateField2Internal2Schema).to.deep.eq({ - properties: {}, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField2.omitted`); + expect(separateField2Internal2Schema).to.deep.eq( + { + properties: {}, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField2.omitted`, + ); const separateField3Schema = getComponentSchema('Partial_SeparateField_Partial__a-string--b-number__.a-or-b__', currentSpec); - expect(separateField3Schema).to.deep.eq({ - properties: + expect(separateField3Schema).to.deep.eq( { - omitted: { - $ref: "#/components/schemas/Omit_Partial__a-string--b-number__.a-or-b_", - description: undefined, - example: undefined, - format: undefined + properties: { + omitted: { + $ref: '#/components/schemas/Omit_Partial__a-string--b-number__.a-or-b_', + description: undefined, + example: undefined, + format: undefined, + }, + field: { + anyOf: [ + { + type: 'string', + }, + { + format: 'double', + type: 'number', + }, + ], + description: undefined, + default: undefined, + example: undefined, + format: undefined, + }, }, - field: { - anyOf: [ - { - type: "string" - }, - { - format: "double", - type: "number" - } - ], - description: undefined, - default: undefined, - example: undefined, - format: undefined - } + type: 'object', + description: 'Make all properties in T optional', + default: undefined, + example: undefined, + format: undefined, }, - type: "object", - description: "Make all properties in T optional", - default: undefined, - example: undefined, - format: undefined - }, - `for property ${propertyName}.separateField3`); + `for property ${propertyName}.separateField3`, + ); const separateField3InternalSchema = getComponentSchema('Omit_Partial__a-string--b-number__.a-or-b_', currentSpec); - expect(separateField3InternalSchema?.$ref).to.eq("#/components/schemas/Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__", - `for property ${propertyName}.separateField3.omitted` + expect(separateField3InternalSchema?.$ref).to.eq( + '#/components/schemas/Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__', + `for property ${propertyName}.separateField3.omitted`, ); const separateField3Internal2Schema = getComponentSchema('Pick_Partial__a-string--b-number__.Exclude_keyofPartial__a-string--b-number__.a-or-b__', currentSpec); - expect(separateField3Internal2Schema).to.deep.eq({ - properties: {}, - type: "object", - description: "From T, pick a set of properties whose keys are in the union K", - default: undefined, - example: undefined, - format: undefined, - }, - `for property ${propertyName}.separateField3.omitted`); - } + expect(separateField3Internal2Schema).to.deep.eq( + { + properties: {}, + type: 'object', + description: 'From T, pick a set of properties whose keys are in the union K', + default: undefined, + example: undefined, + format: undefined, + }, + `for property ${propertyName}.separateField3.omitted`, + ); + }, }; const testModel = currentSpec.spec.components.schemas[interfaceModelName]; @@ -4568,7 +4628,3 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }); }); }); - - - - From 096fa70ef4d520ae36ba2f1a673a074123d1debd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Tue, 17 Oct 2023 23:17:30 +0200 Subject: [PATCH 04/12] fix: generated names should accept by swagger ui --- .../src/metadataGeneration/typeResolver.ts | 9 ++- .../definitionsGeneration/definitions.spec.ts | 63 ++++++++++--------- tests/unit/swagger/schemaDetails3.spec.ts | 60 +++++++++--------- 3 files changed, 71 insertions(+), 61 deletions(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 26aabc690..2d3e36c55 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -1082,7 +1082,7 @@ export class TypeResolver { //Generates a name from the original type expression. //This function is not invertable, so it's possible, that 2 type expressions have the same refTypeName. private getRefTypeName(name: string): string { - return name + const preformattedName = name //Preformatted name handles most cases .replace(/<|>/g, '_') .replace(/\s+/g, '') .replace(/,/g, '.') @@ -1095,6 +1095,13 @@ export class TypeResolver { .replace(/([a-z_0-9]+\??):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_ .replace(/;/g, '--') .replace(/([a-z})\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, + + //Safety fixes to replace all characters which are not accepted by swagger ui + const formattedName = preformattedName.replace(/[^A-Za-z0-9\-._]/g, match => { + return `_${match.charCodeAt(0)}_`; + }); + + return formattedName; } private attemptToResolveKindToPrimitive = (syntaxKind: ts.SyntaxKind): ResolvesToPrimitive | DoesNotResolveToPrimitive => { diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index 73e6094ad..5756e4b0b 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -1982,23 +1982,23 @@ describe('Definition generation', () => { jsDocTypeNames: (propertyName, propertySchema) => { expect(propertySchema?.properties?.simple?.$ref).to.eq('#/definitions/Partial__a-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.commented?.$ref).to.eq('#/definitions/Partial__a_description-comment_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq('#/definitions/Partial__a_description-multiline%5Cncomment_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq('#/definitions/Partial__a_description-multiline_92_ncomment_-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.defaultValue?.$ref).to.eq('#/definitions/Partial__a_default-true_-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.deprecated?.$ref).to.eq('#/definitions/Partial__a_deprecated-true_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.validators?.$ref).to.eq('#/definitions/Partial__a_validators%3A_minLength%3A_value%3A3___-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.validators?.$ref).to.eq('#/definitions/Partial__a_validators_58__minLength_58__value_58_3___-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.examples?.$ref).to.eq('#/definitions/Partial__a_example-example_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.extensions?.$ref).to.eq('#/definitions/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.extensions?.$ref).to.eq('#/definitions/Partial__a_extensions_58__91__key-x-key-1.value-value-1__93__-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.ignored?.$ref).to.eq('#/definitions/Partial__a_ignored-true_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq('#/definitions/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(18, `for property ${propertyName}`); @@ -2042,7 +2042,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.commented`, ); - const multilineCommentedSchema = getValidatedDefinition('Partial__a_description-multiline\\ncomment_-string__', currentSpec); + const multilineCommentedSchema = getValidatedDefinition('Partial__a_description-multiline_92_ncomment_-string__', currentSpec); expect(multilineCommentedSchema).to.deep.eq( { properties: { @@ -2103,7 +2103,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.deprecated`, ); - const validatorsSchema = getValidatedDefinition('Partial__a_validators:_minLength:_value:3___-string__', currentSpec); + const validatorsSchema = getValidatedDefinition('Partial__a_validators_58__minLength_58__value_58_3___-string__', currentSpec); expect(validatorsSchema).to.deep.eq( { properties: { @@ -2144,7 +2144,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.examples`, ); - const extensionsSchema = getValidatedDefinition('Partial__a_extensions:[_key-x-key-1.value-value-1_]_-string__', currentSpec); + const extensionsSchema = getValidatedDefinition('Partial__a_extensions_58__91__key-x-key-1.value-value-1__93__-string__', currentSpec); expect(extensionsSchema).to.deep.eq( { properties: { @@ -2177,7 +2177,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.ignored`, ); - const indexedSchema = getValidatedDefinition('Partial__[a-string]:string__', currentSpec); + const indexedSchema = getValidatedDefinition('Partial___91_a-string_93__58_string__', currentSpec); expect(indexedSchema).to.deep.eq( { properties: {}, @@ -2591,16 +2591,19 @@ describe('Definition generation', () => { }, mappeds: (propertyName, propertySchema) => { expect(propertySchema?.properties?.unionMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-_b-number__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); - expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq('#/definitions/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-__91_b-string_93__58_number__', `for property ${propertyName}`); + expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq( + '#/definitions/Partial___91_a-string_93__58_string_-or-__91_b-string_93__58_number__', + `for property ${propertyName}`, + ); expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq('#/definitions/Partial__a-string_-and-_b-number__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq('#/definitions/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq('#/definitions/Partial__a-string_-and-__91_b-string_93__58_number__', `for property ${propertyName}`); expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq( - '#/definitions/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__', + '#/definitions/Partial___91_a-string_93__58_string_-and-__91_b-number_93__58_number__', `for property ${propertyName}`, ); - expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-(_b-string_-and-_c-string_)_', `for property ${propertyName}`); - expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/definitions/Partial_(_a-string_-or-_b-string_)-and-_c-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-_40__b-string_-and-_c-string__41__', `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/definitions/Partial__40__a-string_-or-_b-string__41_-and-_c-string__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); @@ -2616,7 +2619,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.unionMap`, ); - const indexedUnionMapSchema = getValidatedDefinition('Partial__a-string_-or-_[b-string]:number__', currentSpec); + const indexedUnionMapSchema = getValidatedDefinition('Partial__a-string_-or-__91_b-string_93__58_number__', currentSpec); expect(indexedUnionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 { @@ -2628,7 +2631,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.indexedUnionMap`, ); - const doubleIndexedUnionMapSchema = getValidatedDefinition('Partial__[a-string]:string_-or-_[b-string]:number__', currentSpec); + const doubleIndexedUnionMapSchema = getValidatedDefinition('Partial___91_a-string_93__58_string_-or-__91_b-string_93__58_number__', currentSpec); expect(doubleIndexedUnionMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 { @@ -2667,7 +2670,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.intersectionMap`, ); - const indexedIntersectionMapSchema = getValidatedDefinition('Partial__a-string_-and-_[b-string]:number__', currentSpec); + const indexedIntersectionMapSchema = getValidatedDefinition('Partial__a-string_-and-__91_b-string_93__58_number__', currentSpec); expect(indexedIntersectionMapSchema).to.deep.eq( { properties: { @@ -2691,7 +2694,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.indexedIntersectionMap`, ); - const doubleIndexedIntersectionMapSchema = getValidatedDefinition('Partial__[a-string]:string_-and-_[b-number]:number__', currentSpec); + const doubleIndexedIntersectionMapSchema = getValidatedDefinition('Partial___91_a-string_93__58_string_-and-__91_b-number_93__58_number__', currentSpec); expect(doubleIndexedIntersectionMapSchema).to.deep.eq( { //Unions are not supported in OpenAPI 2 @@ -2707,7 +2710,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.doubleIndexedIntersectionMap`, ); - const parenthesizedMapSchema = getValidatedDefinition('Partial__a-string_-or-(_b-string_-and-_c-string_)_', currentSpec); + const parenthesizedMapSchema = getValidatedDefinition('Partial__a-string_-or-_40__b-string_-and-_c-string__41__', currentSpec); expect(parenthesizedMapSchema).to.deep.eq( //Unions are not supported in OpenAPI 2 { @@ -2719,7 +2722,7 @@ describe('Definition generation', () => { }, `for property ${propertyName}.parenthesizedMap`, ); - const parenthesizedMap2Schema = getValidatedDefinition('Partial_(_a-string_-or-_b-string_)-and-_c-string__', currentSpec); + const parenthesizedMap2Schema = getValidatedDefinition('Partial__40__a-string_-or-_b-string__41_-and-_c-string__', currentSpec); expect(parenthesizedMap2Schema).to.deep.eq( //Unions are not supported in OpenAPI 2 { @@ -2757,7 +2760,7 @@ describe('Definition generation', () => { expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq('#/definitions/Conditional_string.number.number.boolean_', `for property ${propertyName}`); expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq('#/definitions/Dummy_Conditional_string.string.number.boolean__', `for property ${propertyName}`); expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq('#/definitions/Dummy_Conditional_string.number.number.boolean__', `for property ${propertyName}`); - expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq('#/definitions/Partial_stringextendsstring%3F_a-number_-never_', `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq('#/definitions/Partial_stringextendsstring_63__a-number_-never_', `for property ${propertyName}`); expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq('#/definitions/Partial_Conditional_string.string._a-number_.never__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); @@ -2788,7 +2791,7 @@ describe('Definition generation', () => { expect(dummyConditionalSchema?.$ref).to.eq('#/definitions/Conditional_string.string.number.boolean_', `for property ${propertyName}.dummyConditional`); const dummyFalseConditionalSchema = getValidatedDefinition('Dummy_Conditional_string.number.number.boolean__', currentSpec); expect(dummyFalseConditionalSchema?.$ref).to.eq('#/definitions/Conditional_string.number.number.boolean_', `for property ${propertyName}.dummyFalseConditional`); - const mappedConditionalSchema = getValidatedDefinition('Partial_stringextendsstring?_a-number_-never_', currentSpec); + const mappedConditionalSchema = getValidatedDefinition('Partial_stringextendsstring_63__a-number_-never_', currentSpec); expect(mappedConditionalSchema).to.deep.eq( { properties: { diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts index 780de3e09..d7fa1b04c 100644 --- a/tests/unit/swagger/schemaDetails3.spec.ts +++ b/tests/unit/swagger/schemaDetails3.spec.ts @@ -3036,23 +3036,23 @@ describe('Definition generation for OpenAPI 3.0.0', () => { jsDocTypeNames: (propertyName, propertySchema) => { expect(propertySchema?.properties?.simple?.$ref).to.eq('#/components/schemas/Partial__a-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.commented?.$ref).to.eq('#/components/schemas/Partial__a_description-comment_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq('#/components/schemas/Partial__a_description-multiline%5Cncomment_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.multilineCommented?.$ref).to.eq('#/components/schemas/Partial__a_description-multiline_92_ncomment_-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.defaultValue?.$ref).to.eq('#/components/schemas/Partial__a_default-true_-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.deprecated?.$ref).to.eq('#/components/schemas/Partial__a_deprecated-true_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.validators?.$ref).to.eq('#/components/schemas/Partial__a_validators%3A_minLength%3A_value%3A3___-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.validators?.$ref).to.eq('#/components/schemas/Partial__a_validators_58__minLength_58__value_58_3___-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.examples?.$ref).to.eq('#/components/schemas/Partial__a_example-example_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.extensions?.$ref).to.eq('#/components/schemas/Partial__a_extensions%3A%5B_key-x-key-1.value-value-1_%5D_-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.extensions?.$ref).to.eq('#/components/schemas/Partial__a_extensions_58__91__key-x-key-1.value-value-1__93__-string__', `for property ${propertyName}`); expect(propertySchema?.properties?.ignored?.$ref).to.eq('#/components/schemas/Partial__a_ignored-true_-string__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq('#/components/schemas/Partial__%5Ba-string%5D%3Astring__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedSimple?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedCommented?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedMultilineCommented?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDefaultValue?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedDeprecated?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedValidators?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExamples?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedExtensions?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIgnored?.$ref).to.eq('#/components/schemas/Partial___91_a-string_93__58_string__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(18, `for property ${propertyName}`); @@ -3096,7 +3096,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.commented`, ); - const multilineCommentedSchema = getComponentSchema('Partial__a_description-multiline\\ncomment_-string__', currentSpec); + const multilineCommentedSchema = getComponentSchema('Partial__a_description-multiline_92_ncomment_-string__', currentSpec); expect(multilineCommentedSchema).to.deep.eq( { properties: { @@ -3157,7 +3157,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.deprecated`, ); - const validatorsSchema = getComponentSchema('Partial__a_validators:_minLength:_value:3___-string__', currentSpec); + const validatorsSchema = getComponentSchema('Partial__a_validators_58__minLength_58__value_58_3___-string__', currentSpec); expect(validatorsSchema).to.deep.eq( { properties: { @@ -3198,7 +3198,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.examples`, ); - const extensionsSchema = getComponentSchema('Partial__a_extensions:[_key-x-key-1.value-value-1_]_-string__', currentSpec); + const extensionsSchema = getComponentSchema('Partial__a_extensions_58__91__key-x-key-1.value-value-1__93__-string__', currentSpec); expect(extensionsSchema).to.deep.eq( { properties: { @@ -3231,7 +3231,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.ignored`, ); - const indexedSchema = getComponentSchema('Partial__[a-string]:string__', currentSpec); + const indexedSchema = getComponentSchema('Partial___91_a-string_93__58_string__', currentSpec); expect(indexedSchema).to.deep.eq( { properties: {}, @@ -3648,19 +3648,19 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, mappeds: (propertyName, propertySchema) => { expect(propertySchema?.properties?.unionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-_b-number__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedUnionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-__91_b-string_93__58_number__', `for property ${propertyName}`); expect(propertySchema?.properties?.doubleIndexedUnionMap?.$ref).to.eq( - '#/components/schemas/Partial__%5Ba-string%5D%3Astring_-or-_%5Bb-string%5D%3Anumber__', + '#/components/schemas/Partial___91_a-string_93__58_string_-or-__91_b-string_93__58_number__', `for property ${propertyName}`, ); expect(propertySchema?.properties?.intersectionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-and-_b-number__', `for property ${propertyName}`); - expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-and-_%5Bb-string%5D%3Anumber__', `for property ${propertyName}`); + expect(propertySchema?.properties?.indexedIntersectionMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-and-__91_b-string_93__58_number__', `for property ${propertyName}`); expect(propertySchema?.properties?.doubleIndexedIntersectionMap?.$ref).to.eq( - '#/components/schemas/Partial__%5Ba-string%5D%3Astring_-and-_%5Bb-number%5D%3Anumber__', + '#/components/schemas/Partial___91_a-string_93__58_string_-and-__91_b-number_93__58_number__', `for property ${propertyName}`, ); - expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-(_b-string_-and-_c-string_)_', `for property ${propertyName}`); - expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/components/schemas/Partial_(_a-string_-or-_b-string_)-and-_c-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-_40__b-string_-and-_c-string__41__', `for property ${propertyName}`); + expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/components/schemas/Partial__40__a-string_-or-_b-string__41_-and-_c-string__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); @@ -3693,7 +3693,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.unionMap`, ); - const indexedUnionMapSchema = getComponentSchema('Partial__a-string_-or-_[b-string]:number__', currentSpec); + const indexedUnionMapSchema = getComponentSchema('Partial__a-string_-or-__91_b-string_93__58_number__', currentSpec); expect(indexedUnionMapSchema).to.deep.eq( { anyOf: [ @@ -3721,7 +3721,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.indexedUnionMap`, ); - const doubleIndexedUnionMapSchema = getComponentSchema('Partial__[a-string]:string_-or-_[b-string]:number__', currentSpec); + const doubleIndexedUnionMapSchema = getComponentSchema('Partial___91_a-string_93__58_string_-or-__91_b-string_93__58_number__', currentSpec); expect(doubleIndexedUnionMapSchema).to.deep.eq( { anyOf: [ @@ -3775,7 +3775,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.intersectionMap`, ); - const indexedIntersectionMapSchema = getComponentSchema('Partial__a-string_-and-_[b-string]:number__', currentSpec); + const indexedIntersectionMapSchema = getComponentSchema('Partial__a-string_-and-__91_b-string_93__58_number__', currentSpec); expect(indexedIntersectionMapSchema).to.deep.eq( { properties: { @@ -3799,7 +3799,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.indexedIntersectionMap`, ); - const doubleIndexedIntersectionMapSchema = getComponentSchema('Partial__[a-string]:string_-and-_[b-number]:number__', currentSpec); + const doubleIndexedIntersectionMapSchema = getComponentSchema('Partial___91_a-string_93__58_string_-and-__91_b-number_93__58_number__', currentSpec); expect(doubleIndexedIntersectionMapSchema).to.deep.eq( { properties: {}, @@ -3822,7 +3822,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.doubleIndexedIntersectionMap`, ); - const parenthesizedMapSchema = getComponentSchema('Partial__a-string_-or-(_b-string_-and-_c-string_)_', currentSpec); + const parenthesizedMapSchema = getComponentSchema('Partial__a-string_-or-_40__b-string_-and-_c-string__41__', currentSpec); expect(parenthesizedMapSchema).to.deep.eq( { anyOf: [ @@ -3853,7 +3853,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.parenthesizedMap`, ); - const parenthesizedMap2Schema = getComponentSchema('Partial_(_a-string_-or-_b-string_)-and-_c-string__', currentSpec); + const parenthesizedMap2Schema = getComponentSchema('Partial__40__a-string_-or-_b-string__41_-and-_c-string__', currentSpec); expect(parenthesizedMap2Schema).to.deep.eq( { anyOf: [ @@ -3913,7 +3913,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { expect(propertySchema?.properties?.typedFalseConditional?.$ref).to.eq('#/components/schemas/Conditional_string.number.number.boolean_', `for property ${propertyName}`); expect(propertySchema?.properties?.dummyConditional?.$ref).to.eq('#/components/schemas/Dummy_Conditional_string.string.number.boolean__', `for property ${propertyName}`); expect(propertySchema?.properties?.dummyFalseConditional?.$ref).to.eq('#/components/schemas/Dummy_Conditional_string.number.number.boolean__', `for property ${propertyName}`); - expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq('#/components/schemas/Partial_stringextendsstring%3F_a-number_-never_', `for property ${propertyName}`); + expect(propertySchema?.properties?.mappedConditional?.$ref).to.eq('#/components/schemas/Partial_stringextendsstring_63__a-number_-never_', `for property ${propertyName}`); expect(propertySchema?.properties?.mappedTypedConditional?.$ref).to.eq('#/components/schemas/Partial_Conditional_string.string._a-number_.never__', `for property ${propertyName}`); expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); @@ -3944,7 +3944,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { expect(dummyConditionalSchema?.$ref).to.eq('#/components/schemas/Conditional_string.string.number.boolean_', `for property ${propertyName}.dummyConditional`); const dummyFalseConditionalSchema = getComponentSchema('Dummy_Conditional_string.number.number.boolean__', currentSpec); expect(dummyFalseConditionalSchema?.$ref).to.eq('#/components/schemas/Conditional_string.number.number.boolean_', `for property ${propertyName}.dummyFalseConditional`); - const mappedConditionalSchema = getComponentSchema('Partial_stringextendsstring?_a-number_-never_', currentSpec); + const mappedConditionalSchema = getComponentSchema('Partial_stringextendsstring_63__a-number_-never_', currentSpec); expect(mappedConditionalSchema).to.deep.eq( { properties: { From 9ff1e365b7333d6873ddf6e5e265fe5d22d8d08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Thu, 19 Oct 2023 16:39:54 +0200 Subject: [PATCH 05/12] fix: getTypeOfSymbol is not exist on 'TypeChecker --- packages/cli/src/metadataGeneration/typeResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 2d3e36c55..0e9c53f6c 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -193,7 +193,7 @@ export class TypeResolver { .filter(property => isIgnored(property) === false) // Transform to property .map(property => { - const propertyType = this.current.typeChecker.getTypeOfSymbol(property); + const propertyType = this.current.typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode); const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation)!; const parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc From ded85e4177ec2a6fc4d157dddd6737928bc60089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Sat, 21 Oct 2023 12:39:33 +0200 Subject: [PATCH 06/12] fix: Handle partial --- .../src/metadataGeneration/typeResolver.ts | 9 +++++++ tests/fixtures/testModel.ts | 3 +++ .../definitionsGeneration/definitions.spec.ts | 27 ++++++++++++++++++- tests/unit/swagger/schemaDetails3.spec.ts | 27 ++++++++++++++++++- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 0e9c53f6c..80e093254 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -186,6 +186,15 @@ export class TypeResolver { dataType: 'union', types: resolvedTypes, }; + } else if (type.flags & ts.TypeFlags.Undefined) { + return { + dataType: 'undefined', + }; + } else if (type.flags & ts.TypeFlags.Null) { + return { + dataType: 'enum', + enums: [null], + }; } else if (type.flags & ts.TypeFlags.Object) { const typeProperties: ts.Symbol[] = type.getProperties(); const properties: Tsoa.Property[] = typeProperties diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts index 47b71323d..01b9f6b3d 100644 --- a/tests/fixtures/testModel.ts +++ b/tests/fixtures/testModel.ts @@ -366,6 +366,9 @@ export interface TestModel extends Model { doubleIndexedIntersectionMap: Partial<{ [a: string]: string } & { [b: number]: number }>; parenthesizedMap: Partial<{ a: string } | ({ b: string } & { c: string })>; parenthesizedMap2: Partial<({ a: string } | { b: string }) & { c: string }>; + + undefinedMap: Partial; + nullMap: Partial; }; conditionals?: { diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index 5756e4b0b..e22dcd50e 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -2604,8 +2604,10 @@ describe('Definition generation', () => { ); expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/definitions/Partial__a-string_-or-_40__b-string_-and-_c-string__41__', `for property ${propertyName}`); expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/definitions/Partial__40__a-string_-or-_b-string__41_-and-_c-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.undefinedMap?.$ref).to.eq('#/definitions/Partial_undefined_', `for property ${propertyName}`); + expect(propertySchema?.properties?.nullMap?.$ref).to.eq('#/definitions/Partial_null_', `for property ${propertyName}`); - expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(10, `for property ${propertyName}`); const unionMapSchema = getValidatedDefinition('Partial__a-string_-or-_b-number__', currentSpec); expect(unionMapSchema).to.deep.eq( @@ -2734,6 +2736,29 @@ describe('Definition generation', () => { }, `for property ${propertyName}.parenthesizedMap2`, ); + const undefinedMapSchema = getValidatedDefinition('Partial_undefined_', currentSpec); + expect(undefinedMapSchema).to.deep.eq( + { + default: undefined, + description: 'Make all properties in T optional', + example: undefined, + format: undefined, + }, + `for property ${propertyName}.undefinedMap`, + ); + const nullMapSchema = getValidatedDefinition('Partial_null_', currentSpec); + expect(nullMapSchema).to.deep.eq( + { + enum: [null], + type: 'number', + 'x-nullable': true, + default: undefined, + description: 'Make all properties in T optional', + example: undefined, + format: undefined, + }, + `for property ${propertyName}.nullMap`, + ); }, conditionals: (propertyName, propertySchema) => { expect(propertySchema?.properties?.simpeConditional).to.deep.eq( diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts index d7fa1b04c..28ec8346b 100644 --- a/tests/unit/swagger/schemaDetails3.spec.ts +++ b/tests/unit/swagger/schemaDetails3.spec.ts @@ -3661,8 +3661,10 @@ describe('Definition generation for OpenAPI 3.0.0', () => { ); expect(propertySchema?.properties?.parenthesizedMap?.$ref).to.eq('#/components/schemas/Partial__a-string_-or-_40__b-string_-and-_c-string__41__', `for property ${propertyName}`); expect(propertySchema?.properties?.parenthesizedMap2?.$ref).to.eq('#/components/schemas/Partial__40__a-string_-or-_b-string__41_-and-_c-string__', `for property ${propertyName}`); + expect(propertySchema?.properties?.undefinedMap?.$ref).to.eq('#/components/schemas/Partial_undefined_', `for property ${propertyName}`); + expect(propertySchema?.properties?.nullMap?.$ref).to.eq('#/components/schemas/Partial_null_', `for property ${propertyName}`); - expect(Object.keys(propertySchema?.properties || {}).length).to.eq(8, `for property ${propertyName}`); + expect(Object.keys(propertySchema?.properties || {}).length).to.eq(10, `for property ${propertyName}`); const unionMapSchema = getComponentSchema('Partial__a-string_-or-_b-number__', currentSpec); expect(unionMapSchema).to.deep.eq( @@ -3887,6 +3889,29 @@ describe('Definition generation for OpenAPI 3.0.0', () => { }, `for property ${propertyName}.parenthesizedMap2`, ); + const undefinedMapSchema = getComponentSchema('Partial_undefined_', currentSpec); + expect(undefinedMapSchema).to.deep.eq( + { + default: undefined, + description: 'Make all properties in T optional', + example: undefined, + format: undefined, + }, + `for property ${propertyName}.undefinedMap`, + ); + const nullMapSchema = getComponentSchema('Partial_null_', currentSpec); + expect(nullMapSchema).to.deep.eq( + { + enum: [null], + type: 'number', + nullable: true, + default: undefined, + description: 'Make all properties in T optional', + example: undefined, + format: undefined, + }, + `for property ${propertyName}.nullMap`, + ); }, conditionals: (propertyName, propertySchema) => { expect(propertySchema?.properties?.simpeConditional).to.deep.eq( From f869ed00f74e93db0eb5bdf1ae1703b311a19469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Mon, 23 Oct 2023 15:53:51 +0200 Subject: [PATCH 07/12] fix: independent compiler problem --- packages/cli/src/routeGeneration/routeGenerator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/routeGeneration/routeGenerator.ts b/packages/cli/src/routeGeneration/routeGenerator.ts index 9284fc5c6..4fc863126 100644 --- a/packages/cli/src/routeGeneration/routeGenerator.ts +++ b/packages/cli/src/routeGeneration/routeGenerator.ts @@ -68,7 +68,7 @@ export abstract class AbstractRouteGenerator Date: Tue, 24 Oct 2023 08:30:54 +0200 Subject: [PATCH 08/12] fix: \r\n vs. \n in different op systems --- packages/cli/src/metadataGeneration/typeResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 80e093254..f09c9542a 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -1103,6 +1103,7 @@ export class TypeResolver { .replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__ .replace(/([a-z_0-9]+\??):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_ .replace(/;/g, '--') + .replace(/\r\n/g, '\n') .replace(/([a-z})\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, //Safety fixes to replace all characters which are not accepted by swagger ui From bb53e2ec202962e2785fdf11f0abede4a43591d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Sun, 29 Oct 2023 08:06:22 +0100 Subject: [PATCH 09/12] fix: \n character is handled different in windows --- packages/cli/src/metadataGeneration/typeResolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index f09c9542a..6fb6c024b 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -1103,13 +1103,13 @@ export class TypeResolver { .replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__ .replace(/([a-z_0-9]+\??):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_ .replace(/;/g, '--') - .replace(/\r\n/g, '\n') .replace(/([a-z})\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_, //Safety fixes to replace all characters which are not accepted by swagger ui - const formattedName = preformattedName.replace(/[^A-Za-z0-9\-._]/g, match => { + let formattedName = preformattedName.replace(/[^A-Za-z0-9\-._]/g, match => { return `_${match.charCodeAt(0)}_`; }); + formattedName = formattedName.replace(/92_r_92_n/g, '92_n'); //Windows uses \r\n, but linux uses \n. return formattedName; } From 160ed28a7ed936b1ae4fb84e7a903d69fb132f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Mon, 6 Nov 2023 16:01:35 +0100 Subject: [PATCH 10/12] fix: windows-dependent tests --- tests/unit/swagger/definitionsGeneration/definitions.spec.ts | 4 +++- tests/unit/swagger/schemaDetails3.spec.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index e22dcd50e..63203531a 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -7,6 +7,7 @@ import 'mocha'; import { versionMajorMinor } from 'typescript'; import { getDefaultOptions } from '../../../fixtures/defaultOptions'; import { TestModel } from '../../../fixtures/testModel'; +import * as os from 'os'; describe('Definition generation', () => { const metadata = new MetadataGenerator('./fixtures/controllers/getController.ts').Generate(); @@ -2043,12 +2044,13 @@ describe('Definition generation', () => { `for property ${propertyName}.commented`, ); const multilineCommentedSchema = getValidatedDefinition('Partial__a_description-multiline_92_ncomment_-string__', currentSpec); + const expectedDescription = os.platform() === 'win32' ? 'multiline\r\ncomment' : `multiline\ncomment`; expect(multilineCommentedSchema).to.deep.eq( { properties: { a: { type: 'string', - description: 'multiline\ncomment', + description: expectedDescription, default: undefined, example: undefined, format: undefined, diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts index 28ec8346b..d44826c50 100644 --- a/tests/unit/swagger/schemaDetails3.spec.ts +++ b/tests/unit/swagger/schemaDetails3.spec.ts @@ -7,6 +7,7 @@ import 'mocha'; import { versionMajorMinor } from 'typescript'; import { getDefaultExtendedOptions } from '../../fixtures/defaultOptions'; import { TestModel } from '../../fixtures/testModel'; +import * as os from 'os'; describe('Definition generation for OpenAPI 3.0.0', () => { const metadataGet = new MetadataGenerator('./fixtures/controllers/getController.ts').Generate(); @@ -3097,12 +3098,13 @@ describe('Definition generation for OpenAPI 3.0.0', () => { `for property ${propertyName}.commented`, ); const multilineCommentedSchema = getComponentSchema('Partial__a_description-multiline_92_ncomment_-string__', currentSpec); + const expectedDescription = os.platform() === 'win32' ? 'multiline\r\ncomment' : `multiline\ncomment`; expect(multilineCommentedSchema).to.deep.eq( { properties: { a: { type: 'string', - description: 'multiline\ncomment', + description: expectedDescription, default: undefined, example: undefined, format: undefined, From 9b6bb40fc041555f5dffac7b873a654ba943339a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Mon, 6 Nov 2023 17:06:47 +0100 Subject: [PATCH 11/12] fix: default value not means not required in docs --- packages/cli/src/metadataGeneration/typeResolver.ts | 8 ++++---- .../swagger/definitionsGeneration/definitions.spec.ts | 2 +- tests/unit/swagger/schemaDetails3.spec.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 6fb6c024b..39b8fb901 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -121,7 +121,7 @@ export class TypeResolver { description: this.getNodeDescription(propertySignature), format: this.getNodeFormat(propertySignature), name: (propertySignature.name as ts.Identifier).text, - required: !propertySignature.questionToken && def === undefined, + required: !propertySignature.questionToken, type, validators: getPropertyValidators(propertySignature) || {}, deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'), @@ -219,7 +219,7 @@ export class TypeResolver { // Push property return { name: property.getName(), - required: required && def === undefined, + required, deprecated: parent ? isExistJSDocTag(parent, tag => tag.tagName.text === 'deprecated') || isDecorator(parent, identifier => identifier.text === 'Deprecated') : false, type, default: def, @@ -1272,7 +1272,7 @@ export class TypeResolver { example: this.getNodeExample(propertySignature), format: this.getNodeFormat(propertySignature), name: identifier.text, - required: required && def === undefined, + required, type: new TypeResolver(propertySignature.type, this.current, propertySignature.type.parent, this.context).resolve(), validators: getPropertyValidators(propertySignature) || {}, deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'), @@ -1311,7 +1311,7 @@ export class TypeResolver { example: this.getNodeExample(propertyDeclaration), format: this.getNodeFormat(propertyDeclaration), name: identifier.text, - required: required && def === undefined, + required, type, validators: getPropertyValidators(propertyDeclaration) || {}, // class properties and constructor parameters may be deprecated either via jsdoc annotation or decorator diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index 63203531a..785ef8229 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -1892,7 +1892,7 @@ describe('Definition generation', () => { format: undefined, }, }, - required: ['replacedTypes', 'basic'], + required: ['defaultNull', 'replacedTypes', 'basic'], type: 'object', }, `for property ${propertyName}`, diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts index d44826c50..01c72ce01 100644 --- a/tests/unit/swagger/schemaDetails3.spec.ts +++ b/tests/unit/swagger/schemaDetails3.spec.ts @@ -2947,7 +2947,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => { format: undefined, }, }, - required: ['replacedTypes', 'basic'], + required: ['defaultNull', 'replacedTypes', 'basic'], type: 'object', }, `for property ${propertyName}`, From c1eea73e095a8ea1c1e4d3f971d694a119e475d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A1ndor=20N=C3=A9meth?= Date: Mon, 6 Nov 2023 18:03:09 +0100 Subject: [PATCH 12/12] fix: notices --- packages/cli/src/metadataGeneration/typeResolver.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/metadataGeneration/typeResolver.ts b/packages/cli/src/metadataGeneration/typeResolver.ts index 39b8fb901..936664952 100644 --- a/packages/cli/src/metadataGeneration/typeResolver.ts +++ b/packages/cli/src/metadataGeneration/typeResolver.ts @@ -282,13 +282,16 @@ export class TypeResolver { // keyof if (ts.isTypeOperatorNode(this.typeNode) && this.typeNode.operator === ts.SyntaxKind.KeyOfKeyword) { const type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode); - if (type.getFlags() & ts.TypeFlags.Index) { + if (type.isIndexType()) { // in case of generic: keyof T. Not handles all possible cases - const symbol = (type as ts.IndexType).type.getSymbol(); + const symbol = type.type.getSymbol(); if (symbol && symbol.getFlags() & ts.TypeFlags.TypeParameter) { const typeName = symbol.getEscapedName(); - if (this.context[typeName as string]) { - const subResult = new TypeResolver(this.context[typeName as string].type, this.current, this.parentNode, this.context).resolve(); + if (typeof typeName !== 'string') { + throw new GenerateMetadataError(`typeName is not string, but ${typeof typeName}`, this.typeNode); + } + if (this.context[typeName]) { + const subResult = new TypeResolver(this.context[typeName].type, this.current, this.parentNode, this.context).resolve(); if (subResult.dataType === 'any') { return { dataType: 'union', @@ -302,7 +305,7 @@ export class TypeResolver { enums: properties, }; } else { - throw new GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, this.context[typeName as string].type); + throw new GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, this.context[typeName].type); } } }