Skip to content

Commit 6380e48

Browse files
authored
Merge pull request #1433 from AudunWA/awa/fix/strict-ts
fix: enable compilerOptions.strict for all packages
2 parents 0f4e2ad + 9ff7d08 commit 6380e48

File tree

16 files changed

+131
-120
lines changed

16 files changed

+131
-120
lines changed

packages/cli/src/cli.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,17 @@ const getConfig = async (configPath = 'tsoa.json'): Promise<Config> => {
6060
config = JSON.parse(configRaw.toString('utf8'));
6161
}
6262
} catch (err) {
63-
if (err.code === 'MODULE_NOT_FOUND') {
63+
if (!(err instanceof Error)) {
64+
console.error(err);
65+
throw Error(`Unhandled error encountered loading '${configPath}': ${String(err)}`);
66+
} else if ('code' in err && err.code === 'MODULE_NOT_FOUND') {
6467
throw Error(`No config file found at '${configPath}'`);
6568
} else if (err.name === 'SyntaxError') {
66-
// eslint-disable-next-line no-console
6769
console.error(err);
6870
const errorType = ext === '.js' ? 'JS' : 'JSON';
69-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
7071
throw Error(`Invalid ${errorType} syntax in config at '${configPath}': ${err.message}`);
7172
} else {
72-
// eslint-disable-next-line no-console
7373
console.error(err);
74-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
7574
throw Error(`Unhandled error encountered loading '${configPath}': ${err.message}`);
7675
}
7776
}

packages/cli/src/metadataGeneration/extension.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as ts from 'typescript';
2-
import { getInitializerValue } from './initializer-value';
2+
import { getInitializerValue, isNonUndefinedInitializerValue } from './initializer-value';
33
import { MetadataGenerator } from './metadataGenerator';
44
import { Tsoa } from '@tsoa/runtime';
55
import { safeFromJson } from '../utils/jsonUtils';
@@ -22,10 +22,12 @@ export function getExtensions(decorators: ts.Identifier[], metadataGenerator: Me
2222
throw new Error(`Extension '${attributeKey}' must contain a value`);
2323
}
2424

25-
validateExtensionKey(attributeKey);
25+
assertValidExtensionKey(attributeKey);
2626

2727
const attributeValue = getInitializerValue(decoratorValueArg, metadataGenerator.typeChecker);
28-
28+
if (!isNonUndefinedInitializerValue(attributeValue)) {
29+
throw new Error(`Extension '${attributeKey}' cannot have an undefined initializer value`);
30+
}
2931
return { key: attributeKey, value: attributeValue };
3032
});
3133

@@ -39,8 +41,7 @@ export function getExtensionsFromJSDocComments(comments: string[]): Tsoa.Extensi
3941
if (extensionData) {
4042
const keys = Object.keys(extensionData);
4143
keys.forEach(key => {
42-
validateExtensionKey(key);
43-
44+
assertValidExtensionKey(key);
4445
extensions.push({ key: key, value: extensionData[key] });
4546
});
4647
}
@@ -49,8 +50,8 @@ export function getExtensionsFromJSDocComments(comments: string[]): Tsoa.Extensi
4950
return extensions;
5051
}
5152

52-
function validateExtensionKey(key: string) {
53-
if (key.indexOf('x-') !== 0) {
53+
function assertValidExtensionKey(key: string): asserts key is `x-${string}` {
54+
if (!key.startsWith('x-')) {
5455
throw new Error('Extensions must begin with "x-" to be valid. Please see the following link for more information: https://swagger.io/docs/specification/openapi-extensions/');
5556
}
5657
}

packages/cli/src/metadataGeneration/initializer-value.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@ const hasInitializer = (node: ts.Node): node is ts.HasInitializer => Object.prot
55
const extractInitializer = (decl?: ts.Declaration) => (decl && hasInitializer(decl) && (decl.initializer as ts.Expression)) || undefined;
66
const extractImportSpecifier = (symbol?: ts.Symbol) => (symbol?.declarations && symbol.declarations.length > 0 && ts.isImportSpecifier(symbol.declarations[0]) && symbol.declarations[0]) || undefined;
77

8-
export const getInitializerValue = (initializer?: ts.Expression | ts.ImportSpecifier, typeChecker?: ts.TypeChecker, type?: Tsoa.Type) => {
8+
export type InitializerValue = string | number | boolean | undefined | null | InitializerValue[];
9+
export type DefinedInitializerValue = string | number | boolean | null | DefinedInitializerValue[];
10+
export function isNonUndefinedInitializerValue(value: InitializerValue): value is DefinedInitializerValue {
11+
if (Array.isArray(value)) {
12+
return value.every(isNonUndefinedInitializerValue);
13+
} else {
14+
return value !== undefined;
15+
}
16+
}
17+
18+
export function getInitializerValue(initializer?: ts.Expression | ts.ImportSpecifier, typeChecker?: ts.TypeChecker, type?: Tsoa.Type): InitializerValue | DefinedInitializerValue {
919
if (!initializer || !typeChecker) {
1020
return;
1121
}
@@ -81,4 +91,4 @@ export const getInitializerValue = (initializer?: ts.Expression | ts.ImportSpeci
8191
return getInitializerValue(extractInitializer(symbol?.valueDeclaration) || extractImportSpecifier(symbol), typeChecker);
8292
}
8393
}
84-
};
94+
}

packages/cli/src/metadataGeneration/methodGenerator.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import { Tsoa } from '@tsoa/runtime';
1212
import { TypeResolver } from './typeResolver';
1313
import { getHeaderType } from '../utils/headerTypeHelpers';
1414

15+
type HttpMethod = 'options' | 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
16+
1517
export class MethodGenerator {
16-
private method: 'options' | 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
17-
private path: string;
18+
protected method?: HttpMethod;
19+
protected path?: string;
1820
private produces?: string[];
1921
private consumes?: string;
2022

@@ -30,8 +32,8 @@ export class MethodGenerator {
3032
this.processMethodDecorators();
3133
}
3234

33-
public IsValid() {
34-
return !!this.method;
35+
public IsValid(): this is { method: HttpMethod; path: string } {
36+
return this.method !== undefined && this.path !== undefined;
3537
}
3638

3739
public Generate(): Tsoa.Method {
@@ -76,15 +78,21 @@ export class MethodGenerator {
7678
}
7779

7880
private buildParameters() {
81+
if (!this.IsValid()) {
82+
throw new GenerateMetadataError("This isn't a valid a controller method.");
83+
}
84+
7985
const fullPath = path.join(this.parentPath || '', this.path);
86+
const method = this.method;
8087
const parameters = this.node.parameters
8188
.map(p => {
8289
try {
83-
return new ParameterGenerator(p, this.method, fullPath, this.current).Generate();
90+
return new ParameterGenerator(p, method, fullPath, this.current).Generate();
8491
} catch (e) {
8592
const methodId = this.node.name as ts.Identifier;
8693
const controllerId = (this.node.parent as ts.ClassDeclaration).name as ts.Identifier;
87-
throw new GenerateMetadataError(`${String(e.message)} \n in '${controllerId.text}.${methodId.text}'`);
94+
const message = e instanceof Error ? e.message : String(e);
95+
throw new GenerateMetadataError(`${message} \n in '${controllerId.text}.${methodId.text}'`);
8896
}
8997
})
9098
.reduce((flattened, params) => [...flattened, ...params], []);

packages/cli/src/metadataGeneration/parameterGenerator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ export class ParameterGenerator {
414414
exampleLabels,
415415
};
416416
} catch (e) {
417-
throw new GenerateMetadataError(`JSON format is incorrect: ${String(e.message)}`);
417+
const message = e instanceof Error ? e.message : String(e);
418+
throw new GenerateMetadataError(`JSON format is incorrect: ${message}`);
418419
}
419420
}
420421
}
@@ -427,7 +428,7 @@ export class ParameterGenerator {
427428
return ['header', 'query', 'queries', 'path', 'body', 'bodyprop', 'request', 'res', 'inject', 'uploadedfile', 'uploadedfiles', 'formfield'].some(d => d === decoratorName.toLocaleLowerCase());
428429
}
429430

430-
private supportPathDataType(parameterType: Tsoa.Type) {
431+
private supportPathDataType(parameterType: Tsoa.Type): boolean {
431432
const supportedPathDataTypes: Tsoa.TypeStringLiteral[] = ['string', 'integer', 'long', 'float', 'double', 'date', 'datetime', 'buffer', 'boolean', 'enum', 'refEnum', 'file', 'any'];
432433
if (supportedPathDataTypes.find(t => t === parameterType.dataType)) {
433434
return true;

packages/cli/src/metadataGeneration/typeResolver.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -109,25 +109,23 @@ export class TypeResolver {
109109
}
110110

111111
if (ts.isTypeLiteralNode(this.typeNode)) {
112-
const properties = this.typeNode.members
113-
.filter(member => ts.isPropertySignature(member))
114-
.reduce((res, propertySignature: ts.PropertySignature) => {
115-
const type = new TypeResolver(propertySignature.type as ts.TypeNode, this.current, propertySignature, this.context).resolve();
116-
const property: Tsoa.Property = {
117-
example: this.getNodeExample(propertySignature),
118-
default: getJSDocComment(propertySignature, 'default'),
119-
description: this.getNodeDescription(propertySignature),
120-
format: this.getNodeFormat(propertySignature),
121-
name: (propertySignature.name as ts.Identifier).text,
122-
required: !propertySignature.questionToken,
123-
type,
124-
validators: getPropertyValidators(propertySignature) || {},
125-
deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'),
126-
extensions: this.getNodeExtension(propertySignature),
127-
};
112+
const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce<Tsoa.Property[]>((res, propertySignature: ts.PropertySignature) => {
113+
const type = new TypeResolver(propertySignature.type as ts.TypeNode, this.current, propertySignature, this.context).resolve();
114+
const property: Tsoa.Property = {
115+
example: this.getNodeExample(propertySignature),
116+
default: getJSDocComment(propertySignature, 'default'),
117+
description: this.getNodeDescription(propertySignature),
118+
format: this.getNodeFormat(propertySignature),
119+
name: (propertySignature.name as ts.Identifier).text,
120+
required: !propertySignature.questionToken,
121+
type,
122+
validators: getPropertyValidators(propertySignature) || {},
123+
deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'),
124+
extensions: this.getNodeExtension(propertySignature),
125+
};
128126

129-
return [property, ...res];
130-
}, []);
127+
return [property, ...res];
128+
}, []);
131129

132130
const indexMember = this.typeNode.members.find(member => ts.isIndexSignatureDeclaration(member));
133131
let additionalType: Tsoa.Type | undefined;
@@ -256,7 +254,7 @@ export class TypeResolver {
256254
const type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode);
257255

258256
if (type.isUnion()) {
259-
const literals = type.types.filter(t => t.isLiteral()).reduce((acc, t: ts.LiteralType) => [...acc, t.value.toString()], []);
257+
const literals = type.types.filter((t): t is ts.LiteralType => t.isLiteral()).reduce<string[]>((acc, t: ts.LiteralType) => [...acc, t.value.toString()], []);
260258

261259
// Warn on nonsense (`number`, `typeof Symbol.iterator`)
262260
if (type.types.find(t => !t.isLiteral()) !== undefined) {
@@ -361,7 +359,7 @@ export class TypeResolver {
361359

362360
if (ts.isTemplateLiteralTypeNode(this.typeNode)) {
363361
const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer || this.typeNode);
364-
if (type.isUnion() && type.types.every(unionElementType => unionElementType.isStringLiteral())) {
362+
if (type.isUnion() && type.types.every((unionElementType): unionElementType is ts.StringLiteralType => unionElementType.isStringLiteral())) {
365363
const stringLiteralEnum: Tsoa.EnumType = {
366364
dataType: 'enum',
367365
enums: type.types.map((stringLiteralType: ts.StringLiteralType) => stringLiteralType.value),
@@ -544,7 +542,7 @@ export class TypeResolver {
544542
return nodes;
545543
}
546544

547-
private hasFlag(type: ts.Symbol | ts.Declaration, flag) {
545+
private hasFlag(type: ts.Symbol | ts.Declaration, flag: ts.NodeFlags | ts.SymbolFlags) {
548546
return (type.flags & flag) === flag;
549547
}
550548

@@ -913,7 +911,9 @@ export class TypeResolver {
913911

914912
// Interface model
915913
if (ts.isInterfaceDeclaration(node)) {
916-
return node.members.filter(member => !isIgnored(member) && ts.isPropertySignature(member)).map((member: ts.PropertySignature) => this.propertyFromSignature(member, overrideToken));
914+
return node.members
915+
.filter((member): member is ts.PropertySignature => !isIgnored(member) && ts.isPropertySignature(member))
916+
.map((member: ts.PropertySignature) => this.propertyFromSignature(member, overrideToken));
917917
}
918918

919919
const properties: Array<ts.PropertyDeclaration | ts.ParameterDeclaration> = [];

packages/cli/src/swagger/specGenerator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ export abstract class SpecGenerator {
9696
}
9797
}
9898

99-
protected abstract getSwaggerTypeForUnionType(type: Tsoa.UnionType, title?: string);
99+
protected abstract getSwaggerTypeForUnionType(type: Tsoa.UnionType, title?: string): Swagger.Schema | Swagger.BaseSchema;
100100

101-
protected abstract getSwaggerTypeForIntersectionType(type: Tsoa.IntersectionType, title?: string);
101+
protected abstract getSwaggerTypeForIntersectionType(type: Tsoa.IntersectionType, title?: string): Swagger.Schema | Swagger.BaseSchema;
102102

103103
protected abstract buildProperties(properties: Tsoa.Property[]): { [propertyName: string]: Swagger.Schema | Swagger.Schema3 };
104104

packages/cli/src/swagger/specGenerator2.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { convertColonPathParams, normalisePath } from './../utils/pathUtils';
55
import { DEFAULT_REQUEST_MEDIA_TYPE, DEFAULT_RESPONSE_MEDIA_TYPE, getValue } from './../utils/swaggerUtils';
66
import { SpecGenerator } from './specGenerator';
77
import { UnspecifiedObject } from '../utils/unspecifiedObject';
8+
import { shouldIncludeValidatorInSchema } from '../utils/validatorUtils';
89

910
export class SpecGenerator2 extends SpecGenerator {
1011
constructor(protected readonly metadata: Tsoa.Metadata, protected readonly config: ExtendedSpecConfig) {
@@ -113,13 +114,11 @@ export class SpecGenerator2 extends SpecGenerator {
113114
const swaggerType = this.getSwaggerType(referenceType.type);
114115
const format = referenceType.format as Swagger.DataFormat;
115116
const validators = Object.keys(referenceType.validators)
116-
.filter(key => {
117-
return !key.startsWith('is') && key !== 'minDate' && key !== 'maxDate';
118-
})
117+
.filter(shouldIncludeValidatorInSchema)
119118
.reduce((acc, key) => {
120119
return {
121120
...acc,
122-
[key]: referenceType.validators[key].value,
121+
[key]: referenceType.validators[key]!.value,
123122
};
124123
}, {});
125124

@@ -229,11 +228,11 @@ export class SpecGenerator2 extends SpecGenerator {
229228
}
230229

231230
if (res.headers) {
232-
const headers = {};
231+
const headers: { [name: string]: Swagger.Header } = {};
233232
if (res.headers.dataType === 'refObject' || res.headers.dataType === 'nestedObjectLiteral') {
234233
res.headers.properties.forEach((each: Tsoa.Property) => {
235234
headers[each.name] = {
236-
...this.getSwaggerType(each.type),
235+
...(this.getSwaggerType(each.type) as Swagger.Header),
237236
description: each.description,
238237
};
239238
});
@@ -353,13 +352,11 @@ export class SpecGenerator2 extends SpecGenerator {
353352
return parameter;
354353
}
355354

356-
const validatorObjs = {};
355+
const validatorObjs: Partial<Record<Tsoa.SchemaValidatorKey, unknown>> = {};
357356
Object.keys(source.validators)
358-
.filter(key => {
359-
return !key.startsWith('is') && key !== 'minDate' && key !== 'maxDate';
360-
})
361-
.forEach((key: string) => {
362-
validatorObjs[key] = source.validators[key].value;
357+
.filter(shouldIncludeValidatorInSchema)
358+
.forEach(key => {
359+
validatorObjs[key] = source.validators[key]!.value;
363360
});
364361

365362
if (source.in === 'body' && source.type.dataType === 'array') {
@@ -396,7 +393,7 @@ export class SpecGenerator2 extends SpecGenerator {
396393
const properties: { [propertyName: string]: Swagger.Schema2 } = {};
397394

398395
source.forEach(property => {
399-
const swaggerType = this.getSwaggerType(property.type) as Swagger.Schema2;
396+
let swaggerType = this.getSwaggerType(property.type) as Swagger.Schema2;
400397
const format = property.format as Swagger.DataFormat;
401398
swaggerType.description = property.description;
402399
swaggerType.example = property.example;
@@ -405,11 +402,9 @@ export class SpecGenerator2 extends SpecGenerator {
405402
swaggerType.default = property.default;
406403

407404
Object.keys(property.validators)
408-
.filter(key => {
409-
return !key.startsWith('is') && key !== 'minDate' && key !== 'maxDate';
410-
})
405+
.filter(shouldIncludeValidatorInSchema)
411406
.forEach(key => {
412-
swaggerType[key] = property.validators[key].value;
407+
swaggerType = { ...swaggerType, [key]: property.validators[key]!.value };
413408
});
414409
}
415410
if (property.deprecated) {

0 commit comments

Comments
 (0)