Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
WoH committed Dec 8, 2024
2 parents e41794f + 666fec3 commit 2fff0db
Show file tree
Hide file tree
Showing 78 changed files with 3,367 additions and 2,416 deletions.
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ module.exports = {
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/closeStaleIssuesAndPRs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 30
days-before-close: 5
days-before-stale: 150
days-before-close: 30
exempt-issue-labels: 'help wanted,Pending feedback'
exempt-pr-labels: 'help wanted,breaking change'
4 changes: 2 additions & 2 deletions .github/workflows/runTestsOnPush.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ jobs:
matrix:
node-version: [18, 20, 21]
os: [macos-latest, ubuntu-latest, windows-latest]
typescript-version: ['^4.9.5', '^5.2.2']
typescript-version: ['^5.0.0']

steps:
- uses: actions/checkout@master

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: yarn
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "6.4.0",
"version": "6.5.1",
"npmClient": "yarn",
"command": {
"version": {
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"husky": "^4.3.0",
"lerna": "^8.0.1",
"lint-staged": "^15.2.0",
"prettier": "^3.1.1",
"lerna": "^8.1.8",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"ts-node": "^10.9.2",
"typedoc": "^0.25.12"
"typedoc": "^0.26.7"
},
"resolutions": {
"typescript": "^5.2.2"
Expand Down
20 changes: 10 additions & 10 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tsoa/cli",
"description": "Build swagger-compliant REST APIs using TypeScript and Node",
"version": "6.4.0",
"version": "6.5.1",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
Expand Down Expand Up @@ -30,25 +30,25 @@
"author": "Luke Autry <lukeautry@gmail.com> (http://www.lukeautry.com)",
"license": "MIT",
"dependencies": {
"@tsoa/runtime": "^6.4.0",
"@types/multer": "^1.4.11",
"@tsoa/runtime": "^6.5.1",
"@types/multer": "^1.4.12",
"fs-extra": "^11.2.0",
"glob": "^10.3.10",
"handlebars": "^4.7.8",
"merge-anything": "^5.1.4",
"merge-anything": "^5.1.7",
"minimatch": "^9.0.1",
"ts-deepmerge": "^7.0.0",
"typescript": "^5.3.3",
"validator": "^13.11.0",
"yaml": "^2.4.1",
"ts-deepmerge": "^7.0.2",
"typescript": "^5.7.2",
"validator": "^13.12.0",
"yaml": "^2.6.1",
"yargs": "^17.7.1"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/minimatch": "^5.1.0",
"@types/node": "^18.0.0",
"@types/validator": "^13.11.7",
"@types/yargs": "^17.0.32",
"@types/validator": "^13.12.2",
"@types/yargs": "^17.0.33",
"copyfiles": "^2.4.1",
"rimraf": "^5.0.5"
},
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,11 @@ export async function generateSpecAndRoutes(args: SwaggerArgs, metadata?: Tsoa.M
throw err;
}
}
export type RouteGeneratorModule<Config extends ExtendedRoutesConfig> = {
default: new (
metadata: Tsoa.Metadata,
routesConfig: Config,
) => {
GenerateCustomRoutes: () => Promise<void>;
};
};
10 changes: 7 additions & 3 deletions packages/cli/src/metadataGeneration/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ 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)}`;
Expand All @@ -34,9 +38,9 @@ export function prettyLocationOfNode(node: Node | TypeNode) {
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 || '<unknown name>');
name = node.pos !== -1 && node.parent ? node.getText() : (node as any).name?.text || '<unknown name>';
} else {
name = node.parent.pos !== -1 ? node.parent.getText() : ((node as any).parent.name?.text || '<unknown name>');
name = node.parent.pos !== -1 ? node.parent.getText() : (node as any).parent.name?.text || '<unknown name>';
}
return `This was caused by '${name}'`;
}
8 changes: 6 additions & 2 deletions packages/cli/src/metadataGeneration/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ export function getExtensions(decorators: ts.Identifier[], metadataGenerator: Me

const [decoratorKeyArg, decoratorValueArg] = extensionDecorator.parent.arguments;

if (!ts.isStringLiteral(decoratorKeyArg)) {
if (!ts.isStringLiteral(decoratorKeyArg) && !ts.isIdentifier(decoratorKeyArg)) {
throw new Error('The first argument of @Extension must be a string');
}

const attributeKey = decoratorKeyArg.text;
const attributeKey = ts.isIdentifier(decoratorKeyArg) ? getInitializerValue(decoratorKeyArg, metadataGenerator.typeChecker) : decoratorKeyArg.text;

if (typeof attributeKey !== 'string') {
throw new Error('The first argument of @Extension must be a string');
}

if (!decoratorValueArg) {
throw new Error(`Extension '${attributeKey}' must contain a value`);
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/metadataGeneration/methodGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,11 @@ export class MethodGenerator {

return decorators.map(decorator => {
const [name, description, example, produces] = getDecoratorValues(decorator, this.current.typeChecker);
responseExamplesByName[name] = responseExamplesByName[name] ? [...responseExamplesByName[name], example] : [example];

if (example !== undefined) {
responseExamplesByName[name] = responseExamplesByName[name] ? [...responseExamplesByName[name], example] : [example];
}

return {
description: description || '',
examples: responseExamplesByName[name] || undefined,
Expand Down
24 changes: 22 additions & 2 deletions packages/cli/src/metadataGeneration/parameterGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,36 @@ export class ParameterGenerator {
};
}

private extractTsoaResponse(typeNode: ts.TypeNode | undefined): ts.TypeReferenceNode | undefined {
if (!typeNode || !ts.isTypeReferenceNode(typeNode)) {
return undefined;
}
if (typeNode.typeName.getText() === 'TsoaResponse') {
return typeNode;
}

const symbol = this.current.typeChecker.getTypeAtLocation(typeNode).aliasSymbol;
if (!symbol || !symbol.declarations) {
return undefined;
}
const declaration = symbol.declarations[0];
if (!ts.isTypeAliasDeclaration(declaration) || !ts.isTypeReferenceNode(declaration.type)) {
return undefined;
}

return declaration.type.typeName.getText() === 'TsoaResponse' ? declaration.type : undefined;
}

private getResParameters(parameter: ts.ParameterDeclaration): Tsoa.ResParameter[] {
const parameterName = (parameter.name as ts.Identifier).text;
const decorator = getNodeFirstDecoratorValue(this.parameter, this.current.typeChecker, ident => ident.text === 'Res') || parameterName;
if (!decorator) {
throw new GenerateMetadataError('Could not find Decorator', parameter);
}

const typeNode = parameter.type;
const typeNode = this.extractTsoaResponse(parameter.type);

if (!typeNode || !ts.isTypeReferenceNode(typeNode) || typeNode.typeName.getText() !== 'TsoaResponse') {
if (!typeNode) {
throw new GenerateMetadataError('@Res() requires the type to be TsoaResponse<HTTPStatusCode, ResBody>', parameter);
}

Expand Down
19 changes: 10 additions & 9 deletions packages/cli/src/metadataGeneration/transformer/enumTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Tsoa } from '@tsoa/runtime';

import { Transformer } from './transformer';
import { isExistJSDocTag } from '../../utils/jsDocUtils';
import { TypeResolver } from '../typeResolver';

export class EnumTransformer extends Transformer {
public static mergeMany(many: Tsoa.RefEnumType[]): Tsoa.RefEnumType {
Expand Down Expand Up @@ -40,36 +41,36 @@ export class EnumTransformer extends Transformer {
return isEnumDeclaration(declaration) || isEnumMember(declaration);
}

public transform(declaration: EnumDeclaration | EnumMember, enumName: string): Tsoa.RefEnumType {
public transform(resolver: TypeResolver, declaration: EnumDeclaration | EnumMember, enumName: string): Tsoa.RefEnumType {
if (isEnumDeclaration(declaration)) {
return this.transformDeclaration(declaration, enumName);
return this.transformDeclaration(resolver, declaration, enumName);
}
return this.transformMember(declaration, enumName);
return this.transformMember(resolver, declaration, enumName);
}

private transformDeclaration(declaration: EnumDeclaration, enumName: string): Tsoa.RefEnumType {
private transformDeclaration(resolver: TypeResolver, declaration: EnumDeclaration, enumName: string): Tsoa.RefEnumType {
const isNotUndefined = <T>(item: T): item is Exclude<T, undefined> => {
return item === undefined ? false : true;
};
const enums = declaration.members.map(e => this.resolver.current.typeChecker.getConstantValue(e)).filter(isNotUndefined);
const enums = declaration.members.map(e => resolver.current.typeChecker.getConstantValue(e)).filter(isNotUndefined);
const enumVarnames = declaration.members.map(e => e.name.getText()).filter(isNotUndefined);

return {
dataType: 'refEnum',
description: this.resolver.getNodeDescription(declaration),
example: this.resolver.getNodeExample(declaration),
description: resolver.getNodeDescription(declaration),
example: resolver.getNodeExample(declaration),
enums,
enumVarnames,
refName: enumName,
deprecated: isExistJSDocTag(declaration, tag => tag.tagName.text === 'deprecated'),
};
}

private transformMember(declaration: EnumMember, enumName: string): Tsoa.RefEnumType {
private transformMember(resolver: TypeResolver, declaration: EnumMember, enumName: string): Tsoa.RefEnumType {
return {
dataType: 'refEnum',
refName: enumName,
enums: [this.resolver.current.typeChecker.getConstantValue(declaration)!],
enums: [resolver.current.typeChecker.getConstantValue(declaration)!],
enumVarnames: [declaration.name.getText()],
deprecated: isExistJSDocTag(declaration, tag => tag.tagName.text === 'deprecated'),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { TypeNode, Node } from 'typescript';
import { SyntaxKind } from 'typescript';
import { type TypeNode, SyntaxKind } from 'typescript';
import { Tsoa, assertNever } from '@tsoa/runtime';

import { Transformer } from './transformer';
import { getJSDocTagNames } from '../../utils/jsDocUtils';

export class PrimitiveTransformer extends Transformer {
public static resolveKindToPrimitive(syntaxKind: SyntaxKind): ResolvesToPrimitive {
Expand All @@ -18,46 +16,45 @@ export class PrimitiveTransformer extends Transformer {
return 'void';
case SyntaxKind.UndefinedKeyword:
return 'undefined';
case SyntaxKind.NullKeyword:
return 'null';
default:
return undefined;
}
};
}

public transform(typeNode: TypeNode, parentNode?: Node): Tsoa.PrimitiveType | undefined {
public transform(defaultNumberType: DefaultNumberType, typeNode: TypeNode, partentJsDocTagNames?: string[]): Tsoa.Type | undefined {
const resolvedType = PrimitiveTransformer.resolveKindToPrimitive(typeNode.kind);
if (!resolvedType) {
return;
}

const defaultNumberType = this.resolver.current.defaultNumberType;

switch (resolvedType) {
case 'number':
return this.transformNumber(defaultNumberType, parentNode);
return this.transformNumber(defaultNumberType, partentJsDocTagNames);
case 'string':
case 'boolean':
case 'void':
case 'undefined':
return { dataType: resolvedType };
case 'null':
return {
dataType: 'enum',
enums: [null],
};
default:
return assertNever(resolvedType);
}
}

private transformNumber(
defaultNumberType: NonNullable<"double" | "float" | "integer" | "long" | undefined>,
parentNode?: Node,
): Tsoa.PrimitiveType {
if (!parentNode) {
private transformNumber(defaultNumberType: DefaultNumberType, partentJsDocTagNames?: string[]): Tsoa.PrimitiveType {
if (!partentJsDocTagNames || partentJsDocTagNames.length === 0) {
return { dataType: defaultNumberType };
}

const tags = getJSDocTagNames(parentNode).filter(name => {
const tags = partentJsDocTagNames.filter(name => {
return ['isInt', 'isLong', 'isFloat', 'isDouble'].some(m => m === name);
});
if (tags.length === 0) {
return { dataType: defaultNumberType };
}

switch (tags[0]) {
case 'isInt':
Expand All @@ -74,4 +71,5 @@ export class PrimitiveTransformer extends Transformer {
}
}

type ResolvesToPrimitive = 'number' | 'string' | 'boolean' | 'void' | 'undefined' | undefined;
type DefaultNumberType = NonNullable<'double' | 'float' | 'integer' | 'long' | undefined>;
type ResolvesToPrimitive = 'number' | 'string' | 'boolean' | 'void' | 'undefined' | 'null' | undefined;
Loading

0 comments on commit 2fff0db

Please sign in to comment.