Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: Referencer, Name conflicts, Mapped unions, etc. #1498

Merged
merged 12 commits into from
Nov 10, 2023
20 changes: 12 additions & 8 deletions packages/cli/src/metadataGeneration/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class GenerateMetadataError extends Error {
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small thing, do not crash while trying to make exceptions to throw (or when make warning messages).

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)}`;
Expand All @@ -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 || '<unknown name>');
} 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 || '<unknown name>');
}
return `This was caused by '${name}'`;
}
35 changes: 26 additions & 9 deletions packages/cli/src/metadataGeneration/metadataGenerator.ts
Original file line number Diff line number Diff line change
@@ -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<ClassDeclaration>();
public readonly typeChecker: TypeChecker;
private readonly program: Program;
private referenceTypeMap: Tsoa.ReferenceTypeMap = {};
private circularDependencyResolvers = new Array<(referenceTypes: Tsoa.ReferenceTypeMap) => void>();
private modelDefinitionPosMap: { [name: string]: Array<{ fileName: string; pos: number }> } = {};
private expressionOrigNameMap: Record<string, string> = {};

constructor(
entryFile: string,
Expand All @@ -35,7 +36,6 @@ export class MetadataGenerator {

this.checkForMethodSignatureDuplicates(controllers);
this.checkForPathParamSignatureDuplicates(controllers);
this.circularDependencyResolvers.forEach(c => c(this.referenceTypeMap));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in another place


return {
controllers,
Expand Down Expand Up @@ -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;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeURIComponent() not interesting here, because typeResolver guarantees, that only OpenApi-accepted names can be generated.

}

public GetReferenceType(refName: string) {
return this.referenceTypeMap[refName];
}

public OnFinish(callback: (referenceTypes: Tsoa.ReferenceTypeMap) => void) {
this.circularDependencyResolvers.push(callback);
public CheckModelUnicity(refName: string, positions: Array<{ fileName: string; pos: number }>) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type unicity check:

  • We want to check, that type A<B<...>,{ c: C}> is uniq or not. For this, we need to check if every A, B & C type identifier definitions are uniq or not. Uniq = A type can have more than one definition, but their definition positions array must be uniq. Position = fileName + pos. Not stored, so not interesting, which local machine the check is made.
  • A and A_B_ types are both renamed to A_B_ to use it in OpenApi. This is a second type of name conflicts.

if (!this.modelDefinitionPosMap[refName]) {
this.modelDefinitionPosMap[refName] = positions;
} else {
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)}`);
}
}
}

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() {
Expand Down
Loading