Skip to content

Commit dc3718c

Browse files
authored
fix(typescript): typecheck generated samples + fixes (OpenAPITools#19903)
* fix(typescript): typecheck generated samples + fixes * wip(today's fortune): The sum of the Universe is zero. * Update .github/workflows/samples-typescript-typecheck.yaml * Update modules/openapi-generator/src/main/resources/typescript/tsconfig.mustache * chore: regenerate samples
1 parent ce09134 commit dc3718c

File tree

139 files changed

+7325
-497
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+7325
-497
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: TypeScript clients type checks
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- samples/**
7+
- bin/ts-typecheck-all.sh
8+
- .github/workflows/samples-typescript-typecheck.yaml
9+
jobs:
10+
build:
11+
name: Typecheck TypeScript samples
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
node-version:
17+
- 20
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: ${{ matrix.node-version }}
24+
25+
- name: Run type checker
26+
run: ./bin/ts-typecheck-all.sh

bin/ts-typecheck-all.sh

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
log() {
6+
echo "$@" >&2
7+
}
8+
9+
npm_install() {
10+
# --ignore-scripts because we don't want to run any pre- or postinstall scripts
11+
# --no-package-lock because we don't want to update or create the package-lock.json
12+
# --no-fund because we don't want to check for funding
13+
# --no-audit because we don't want to run an audit
14+
# --suppress-warnings because we don't want to see any warnings whilst type checking
15+
npm i \
16+
--suppress-warnings \
17+
--ignore-scripts \
18+
--no-package-lock \
19+
--no-fund \
20+
--no-audit \
21+
"$@"
22+
}
23+
24+
main() {
25+
local root_dir
26+
root_dir=$(git rev-parse --show-toplevel)
27+
local dir
28+
29+
for dir in $(git ls-files samples | grep 'tsconfig.json$' | xargs -n1 dirname | sort -u); do
30+
if [[ ! -f "${root_dir}/${dir}/.openapi-generator-ignore" ]]; then
31+
# This is not a generated sample; skip it
32+
continue
33+
fi
34+
if [[ ! -f "${root_dir}/${dir}/package.json" ]]; then
35+
# we can't really guarantee that all dependencies are there to do a typecheck...
36+
continue
37+
fi
38+
log "${dir}"
39+
pushd "${root_dir}/${dir}" > /dev/null
40+
npm_install \
41+
|| npm_install --force # --force because we have some incompatible peer-dependencies that can't be fixed
42+
npm exec [email protected] --yes -- tsc --noEmit
43+
log "${dir}"
44+
log
45+
popd > /dev/null
46+
done
47+
}
48+
49+
main "$@"

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java

+6
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ public AbstractTypeScriptClientCodegen() {
338338
typeMapping.put("Array", "Array");
339339
typeMapping.put("array", "Array");
340340
typeMapping.put("boolean", "boolean");
341+
typeMapping.put("decimal", "string");
341342
typeMapping.put("string", "string");
342343
typeMapping.put("int", "number");
343344
typeMapping.put("float", "number");
@@ -746,6 +747,11 @@ public String toDefaultValue(Schema p) {
746747
return p.getDefault().toString();
747748
}
748749
return UNDEFINED_VALUE;
750+
} else if (ModelUtils.isDecimalSchema(p)) {
751+
if (p.getDefault() != null) {
752+
return p.getDefault().toString();
753+
}
754+
return UNDEFINED_VALUE;
749755
} else if (ModelUtils.isDateSchema(p)) {
750756
if (p.getDefault() != null) {
751757
return p.getDefault().toString();

modules/openapi-generator/src/main/resources/typescript-aurelia/Api.ts.mustache

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export class Api {
5959
*/
6060
protected ensureParamIsSet<T>(context: string, params: T, paramName: keyof T): void {
6161
if (null === params[paramName]) {
62-
throw new Error(`Missing required parameter ${paramName} when calling ${context}`);
62+
throw new Error(`Missing required parameter ${String(paramName)} when calling ${context}`);
6363
}
6464
}
6565
}

modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache

+5
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ function querystringSingleKey(key: string, value: string | number | null | undef
326326
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
327327
}
328328

329+
export function exists(json: any, key: string) {
330+
const value = json[key];
331+
return value !== null && value !== undefined;
332+
}
333+
329334
{{^withoutRuntimeChecks}}
330335
export function mapValues(data: any, fn: (item: any) => any) {
331336
return Object.keys(data).reduce(

modules/openapi-generator/src/main/resources/typescript-fetch/runtimeSagasAndRecords.mustache

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function appFromJS(any: any): any {
1313
if (isIndexed(value)) {
1414
return knownIndexedSetByKey.indexOf(key) !== -1 ? value.toSet() : value.toList();
1515
} // we're reviving an array -> it's a List
16-
const MatchingType = knownRecordFactories.get(value.get('recType')) as { new(input?: any): any }; // check if we know a Record with this type
16+
const MatchingType = knownRecordFactories.get(value.get('recType') as string) as { new(input?: any): any }; // check if we know a Record with this type
1717
if (MatchingType) {
1818
return new MatchingType(value);
1919
}

modules/openapi-generator/src/main/resources/typescript-fetch/sagas.mustache

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export function *{{nickname}}Saga() {
8989
yield takeLatest({{nickname}}, {{nickname}}SagaImp);
9090
}
9191

92-
export function *{{nickname}}SagaImp(_action_: Action<Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}>) {
92+
export function *{{nickname}}SagaImp(_action_: Action<Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}>){{^returnType}}: any{{/returnType}} {
9393
const {markErrorsAsHandled, ..._payloadRest_} = _action_.payload;
9494
try {
9595
{{#returnTypeSupportsEntities}}
@@ -233,7 +233,7 @@ export function *{{nickname}}SagaImp(_action_: Action<Payload{{#lambda.titlecase
233233
{{^returnType}}
234234
return undefined;
235235
{{/returnType}}
236-
} catch (error) {
236+
} catch (error: any) {
237237
if (markErrorsAsHandled) {error.wasHandled = true; }
238238
yield put({{nickname}}Failure({error, requestPayload: _action_.payload}));
239239
return error;

modules/openapi-generator/src/main/resources/typescript-nestjs/configuration.mustache

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
{{#useAxiosHttpModule}}
12
import type { HttpService } from '@nestjs/axios';
3+
{{/useAxiosHttpModule}}
4+
{{^useAxiosHttpModule}}
5+
import type { HttpService } from '@nestjs/common';
6+
{{/useAxiosHttpModule}}
27
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
38

49
export interface ConfigurationParameters {

modules/openapi-generator/src/main/resources/typescript-nestjs/package.mustache

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@nestjs/testing": "~{{nestVersion}}",
4444
"@types/express": "^4.16.0",
4545
"@types/jest": "^24.0.15",
46-
"@types/node": "^14.8.2",
46+
"@types/node": "*",
4747
"@types/supertest": "^2.0.8",
4848
"concurrently": "^4.1.1",
4949
"nodemon": "^1.19.1",

modules/openapi-generator/src/main/resources/typescript/auth/auth.mustache

+2-1
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,14 @@ export type ApiKeyConfiguration = string;
123123
export type HttpBasicConfiguration = { "username": string, "password": string };
124124
export type HttpBearerConfiguration = { tokenProvider: TokenProvider };
125125
export type OAuth2Configuration = { accessToken: string };
126+
export type HttpSignatureConfiguration = unknown; // TODO: Implement
126127

127128
export type AuthMethodsConfiguration = {
128129
{{^useInversify}}
129130
"default"?: SecurityAuthentication,
130131
{{/useInversify}}
131132
{{#authMethods}}
132-
"{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{^-last}},{{/-last}}
133+
"{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{#isHttpSignature}}HttpSignatureConfiguration{{/isHttpSignature}}{{^-last}},{{/-last}}
133134
{{/authMethods}}
134135
}
135136

modules/openapi-generator/src/main/resources/typescript/http/http.mustache

+4-1
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,12 @@ export class ResponseContext {
243243
return result;
244244
}
245245

246-
const parameters = this.headers[headerName].split(";");
246+
const parameters = this.headers[headerName]!.split(";");
247247
for (const parameter of parameters) {
248248
let [key, value] = parameter.split("=", 2);
249+
if (!key) {
250+
continue;
251+
}
249252
key = key.toLowerCase().trim();
250253
if (value === undefined) {
251254
result[""] = key;

modules/openapi-generator/src/main/resources/typescript/http/servers.mustache

+2-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ export class ServerConfiguration<T extends { [key: string]: string }> implements
3030

3131
private getUrl() {
3232
let replacedUrl = this.url;
33-
for (const key in this.variableConfiguration) {
34-
var re = new RegExp("{" + key + "}","g");
35-
replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]);
33+
for (const [key, value] of Object.entries(this.variableConfiguration)) {
34+
replacedUrl = replacedUrl.replaceAll(`{${key}}`, value);
3635
}
3736
return replacedUrl
3837
}

modules/openapi-generator/src/main/resources/typescript/model/ObjectSerializer.mustache

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ type MimeTypeDescriptor = {
6565
* the payload.
6666
*/
6767
const parseMimeType = (mimeType: string): MimeTypeDescriptor => {
68-
const [type, subtype] = mimeType.split('/');
68+
const [type = '', subtype = ''] = mimeType.split('/');
6969
return {
7070
type,
7171
subtype,
@@ -272,7 +272,7 @@ export class ObjectSerializer {
272272
if (mediaType === undefined) {
273273
return undefined;
274274
}
275-
return mediaType.split(";")[0].trim().toLowerCase();
275+
return (mediaType.split(";")[0] ?? '').trim().toLowerCase();
276276
}
277277
278278
/**

modules/openapi-generator/src/main/resources/typescript/model/model.mustache

+6-6
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,26 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{
2727
{{/vars}}
2828

2929
{{#discriminator}}
30-
static readonly discriminator: string | undefined = "{{discriminatorName}}";
30+
static {{#parent}}override {{/parent}}readonly discriminator: string | undefined = "{{discriminatorName}}";
3131
{{/discriminator}}
3232
{{^discriminator}}
33-
static readonly discriminator: string | undefined = undefined;
33+
static {{#parent}}override {{/parent}}readonly discriminator: string | undefined = undefined;
3434
{{/discriminator}}
3535
{{#hasDiscriminatorWithNonEmptyMapping}}
3636

37-
static readonly mapping: {[index: string]: string} | undefined = {
37+
static {{#parent}}override {{/parent}}readonly mapping: {[index: string]: string} | undefined = {
3838
{{#discriminator.mappedModels}}
3939
"{{mappingName}}": "{{modelName}}",
4040
{{/discriminator.mappedModels}}
4141
};
4242
{{/hasDiscriminatorWithNonEmptyMapping}}
4343
{{^hasDiscriminatorWithNonEmptyMapping}}
4444

45-
static readonly mapping: {[index: string]: string} | undefined = undefined;
45+
static {{#parent}}override {{/parent}}readonly mapping: {[index: string]: string} | undefined = undefined;
4646
{{/hasDiscriminatorWithNonEmptyMapping}}
4747

4848
{{^isArray}}
49-
static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [
49+
static {{#parent}}override {{/parent}}readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [
5050
{{#vars}}
5151
{
5252
"name": "{{name}}",
@@ -58,7 +58,7 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{
5858
{{/vars}}
5959
];
6060

61-
static getAttributeTypeMap() {
61+
static {{#parent}}override {{/parent}}getAttributeTypeMap() {
6262
{{#parent}}
6363
return super.getAttributeTypeMap().concat({{classname}}.attributeTypeMap);
6464
{{/parent}}

modules/openapi-generator/src/main/resources/typescript/package.mustache

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"rxjs": "^6.4.0",
7070
{{/useRxJS}}
7171
{{#useInversify}}
72-
"inversify": "^5.0.1",
72+
"inversify": "^6.0.1",
7373
{{/useInversify}}
7474
"es6-promise": "^4.2.4",
7575
"url-parse": "^1.4.3"

modules/openapi-generator/src/main/resources/typescript/services/index.mustache

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { inject, injectable, multiInject, optional, interfaces } from "inversify";
22

33
import { Configuration } from "../configuration";
4-
import { ServerConfiguration, servers } from "../servers";
4+
import { ServerConfiguration, servers{{#servers}}, server1{{/servers}} } from "../servers";
55
import { HttpLibrary{{^useRxJS}}, wrapHttpLibrary{{/useRxJS}} } from "../http/http";
66
import { Middleware{{^useRxJS}}, PromiseMiddlewareWrapper{{/useRxJS}} } from "../middleware";
77
import { authMethodServices, AuthMethods } from "../auth/auth";
@@ -42,7 +42,7 @@ class InjectableConfiguration implements AbstractConfiguration {
4242
public authMethods: AuthMethods = {};
4343

4444
constructor(
45-
@inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration = servers[0],
45+
@inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration{{#servers}} = server1{{/servers}},
4646
@inject(AbstractHttpLibrary) @optional() httpApi: AbstractHttpLibrary,
4747
@multiInject(AbstractMiddleware) @optional() middleware: AbstractMiddleware[] = [],
4848
@multiInject(AbstractAuthMethod) @optional() securityConfiguration: AbstractAuthMethod[] = []
@@ -90,6 +90,9 @@ export class ApiServiceBinder {
9090
* return value;
9191
*/
9292
public bindServerConfigurationToPredefined(idx: number) {
93+
if (!servers[idx]) {
94+
throw new Error(`Server ${idx} is not available.`);
95+
}
9396
this.bindServerConfiguration.toConstantValue(servers[idx]);
9497
return servers[idx];
9598
}

modules/openapi-generator/src/main/resources/typescript/tsconfig.mustache

+13-6
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,29 @@
1313
"declaration": true,
1414

1515
/* Additional Checks */
16-
"noUnusedLocals": false, /* Report errors on unused locals. */ // TODO: reenable (unused imports!)
17-
"noUnusedParameters": false, /* Report errors on unused parameters. */ // TODO: set to true again
18-
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
19-
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
16+
"noUnusedLocals": false, /* Report errors on unused locals. */ // TODO: reenable (unused imports!)
17+
"noUnusedParameters": false, /* Report errors on unused parameters. */ // TODO: set to true again
18+
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
19+
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
20+
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
21+
"noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
22+
"noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
2023

2124
"removeComments": true,
2225
"sourceMap": true,
2326
"outDir": "./dist",
2427
"noLib": false,
2528
{{#platforms}}
29+
"lib": [
30+
"es6"
31+
,"ES2017.Object"
32+
,"ES2021.String"
2633
{{#node}}
27-
"lib": [ "es6" ],
2834
{{/node}}
2935
{{#browser}}
30-
"lib": [ "es6", "dom" ],
36+
,"dom"
3137
{{/browser}}
38+
],
3239
{{/platforms}}
3340
{{#useInversify}}
3441
"experimentalDecorators": true,

0 commit comments

Comments
 (0)