Skip to content

Commit

Permalink
fix(typescript): typecheck generated samples + fixes (OpenAPITools#19903
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
joscha authored Oct 18, 2024
1 parent ce09134 commit dc3718c
Show file tree
Hide file tree
Showing 139 changed files with 7,325 additions and 497 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/samples-typescript-typecheck.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: TypeScript clients type checks

on:
pull_request:
paths:
- samples/**
- bin/ts-typecheck-all.sh
- .github/workflows/samples-typescript-typecheck.yaml
jobs:
build:
name: Typecheck TypeScript samples
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version:
- 20
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Run type checker
run: ./bin/ts-typecheck-all.sh
49 changes: 49 additions & 0 deletions bin/ts-typecheck-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash

set -euo pipefail

log() {
echo "$@" >&2
}

npm_install() {
# --ignore-scripts because we don't want to run any pre- or postinstall scripts
# --no-package-lock because we don't want to update or create the package-lock.json
# --no-fund because we don't want to check for funding
# --no-audit because we don't want to run an audit
# --suppress-warnings because we don't want to see any warnings whilst type checking
npm i \
--suppress-warnings \
--ignore-scripts \
--no-package-lock \
--no-fund \
--no-audit \
"$@"
}

main() {
local root_dir
root_dir=$(git rev-parse --show-toplevel)
local dir

for dir in $(git ls-files samples | grep 'tsconfig.json$' | xargs -n1 dirname | sort -u); do
if [[ ! -f "${root_dir}/${dir}/.openapi-generator-ignore" ]]; then
# This is not a generated sample; skip it
continue
fi
if [[ ! -f "${root_dir}/${dir}/package.json" ]]; then
# we can't really guarantee that all dependencies are there to do a typecheck...
continue
fi
log "${dir}"
pushd "${root_dir}/${dir}" > /dev/null
npm_install \
|| npm_install --force # --force because we have some incompatible peer-dependencies that can't be fixed
npm exec [email protected] --yes -- tsc --noEmit
log "${dir}"
log
popd > /dev/null
done
}

main "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ public AbstractTypeScriptClientCodegen() {
typeMapping.put("Array", "Array");
typeMapping.put("array", "Array");
typeMapping.put("boolean", "boolean");
typeMapping.put("decimal", "string");
typeMapping.put("string", "string");
typeMapping.put("int", "number");
typeMapping.put("float", "number");
Expand Down Expand Up @@ -746,6 +747,11 @@ public String toDefaultValue(Schema p) {
return p.getDefault().toString();
}
return UNDEFINED_VALUE;
} else if (ModelUtils.isDecimalSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
return UNDEFINED_VALUE;
} else if (ModelUtils.isDateSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class Api {
*/
protected ensureParamIsSet<T>(context: string, params: T, paramName: keyof T): void {
if (null === params[paramName]) {
throw new Error(`Missing required parameter ${paramName} when calling ${context}`);
throw new Error(`Missing required parameter ${String(paramName)} when calling ${context}`);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,11 @@ function querystringSingleKey(key: string, value: string | number | null | undef
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
}

export function exists(json: any, key: string) {
const value = json[key];
return value !== null && value !== undefined;
}

{{^withoutRuntimeChecks}}
export function mapValues(data: any, fn: (item: any) => any) {
return Object.keys(data).reduce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function appFromJS(any: any): any {
if (isIndexed(value)) {
return knownIndexedSetByKey.indexOf(key) !== -1 ? value.toSet() : value.toList();
} // we're reviving an array -> it's a List
const MatchingType = knownRecordFactories.get(value.get('recType')) as { new(input?: any): any }; // check if we know a Record with this type
const MatchingType = knownRecordFactories.get(value.get('recType') as string) as { new(input?: any): any }; // check if we know a Record with this type
if (MatchingType) {
return new MatchingType(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function *{{nickname}}Saga() {
yield takeLatest({{nickname}}, {{nickname}}SagaImp);
}

export function *{{nickname}}SagaImp(_action_: Action<Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}>) {
export function *{{nickname}}SagaImp(_action_: Action<Payload{{#lambda.titlecase}}{{#lambda.camelcase}}{{nickname}}{{/lambda.camelcase}}{{/lambda.titlecase}}>){{^returnType}}: any{{/returnType}} {
const {markErrorsAsHandled, ..._payloadRest_} = _action_.payload;
try {
{{#returnTypeSupportsEntities}}
Expand Down Expand Up @@ -233,7 +233,7 @@ export function *{{nickname}}SagaImp(_action_: Action<Payload{{#lambda.titlecase
{{^returnType}}
return undefined;
{{/returnType}}
} catch (error) {
} catch (error: any) {
if (markErrorsAsHandled) {error.wasHandled = true; }
yield put({{nickname}}Failure({error, requestPayload: _action_.payload}));
return error;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{{#useAxiosHttpModule}}
import type { HttpService } from '@nestjs/axios';
{{/useAxiosHttpModule}}
{{^useAxiosHttpModule}}
import type { HttpService } from '@nestjs/common';
{{/useAxiosHttpModule}}
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';

export interface ConfigurationParameters {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@nestjs/testing": "~{{nestVersion}}",
"@types/express": "^4.16.0",
"@types/jest": "^24.0.15",
"@types/node": "^14.8.2",
"@types/node": "*",
"@types/supertest": "^2.0.8",
"concurrently": "^4.1.1",
"nodemon": "^1.19.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,14 @@ export type ApiKeyConfiguration = string;
export type HttpBasicConfiguration = { "username": string, "password": string };
export type HttpBearerConfiguration = { tokenProvider: TokenProvider };
export type OAuth2Configuration = { accessToken: string };
export type HttpSignatureConfiguration = unknown; // TODO: Implement

export type AuthMethodsConfiguration = {
{{^useInversify}}
"default"?: SecurityAuthentication,
{{/useInversify}}
{{#authMethods}}
"{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{^-last}},{{/-last}}
"{{name}}"?: {{#isApiKey}}ApiKeyConfiguration{{/isApiKey}}{{#isBasicBasic}}HttpBasicConfiguration{{/isBasicBasic}}{{#isBasicBearer}}HttpBearerConfiguration{{/isBasicBearer}}{{#isOAuth}}OAuth2Configuration{{/isOAuth}}{{#isHttpSignature}}HttpSignatureConfiguration{{/isHttpSignature}}{{^-last}},{{/-last}}
{{/authMethods}}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,12 @@ export class ResponseContext {
return result;
}

const parameters = this.headers[headerName].split(";");
const parameters = this.headers[headerName]!.split(";");
for (const parameter of parameters) {
let [key, value] = parameter.split("=", 2);
if (!key) {
continue;
}
key = key.toLowerCase().trim();
if (value === undefined) {
result[""] = key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ export class ServerConfiguration<T extends { [key: string]: string }> implements

private getUrl() {
let replacedUrl = this.url;
for (const key in this.variableConfiguration) {
var re = new RegExp("{" + key + "}","g");
replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]);
for (const [key, value] of Object.entries(this.variableConfiguration)) {
replacedUrl = replacedUrl.replaceAll(`{${key}}`, value);
}
return replacedUrl
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type MimeTypeDescriptor = {
* the payload.
*/
const parseMimeType = (mimeType: string): MimeTypeDescriptor => {
const [type, subtype] = mimeType.split('/');
const [type = '', subtype = ''] = mimeType.split('/');
return {
type,
subtype,
Expand Down Expand Up @@ -272,7 +272,7 @@ export class ObjectSerializer {
if (mediaType === undefined) {
return undefined;
}
return mediaType.split(";")[0].trim().toLowerCase();
return (mediaType.split(";")[0] ?? '').trim().toLowerCase();
}
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{
{{/vars}}

{{#discriminator}}
static readonly discriminator: string | undefined = "{{discriminatorName}}";
static {{#parent}}override {{/parent}}readonly discriminator: string | undefined = "{{discriminatorName}}";
{{/discriminator}}
{{^discriminator}}
static readonly discriminator: string | undefined = undefined;
static {{#parent}}override {{/parent}}readonly discriminator: string | undefined = undefined;
{{/discriminator}}
{{#hasDiscriminatorWithNonEmptyMapping}}

static readonly mapping: {[index: string]: string} | undefined = {
static {{#parent}}override {{/parent}}readonly mapping: {[index: string]: string} | undefined = {
{{#discriminator.mappedModels}}
"{{mappingName}}": "{{modelName}}",
{{/discriminator.mappedModels}}
};
{{/hasDiscriminatorWithNonEmptyMapping}}
{{^hasDiscriminatorWithNonEmptyMapping}}

static readonly mapping: {[index: string]: string} | undefined = undefined;
static {{#parent}}override {{/parent}}readonly mapping: {[index: string]: string} | undefined = undefined;
{{/hasDiscriminatorWithNonEmptyMapping}}

{{^isArray}}
static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [
static {{#parent}}override {{/parent}}readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [
{{#vars}}
{
"name": "{{name}}",
Expand All @@ -58,7 +58,7 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{
{{/vars}}
];

static getAttributeTypeMap() {
static {{#parent}}override {{/parent}}getAttributeTypeMap() {
{{#parent}}
return super.getAttributeTypeMap().concat({{classname}}.attributeTypeMap);
{{/parent}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"rxjs": "^6.4.0",
{{/useRxJS}}
{{#useInversify}}
"inversify": "^5.0.1",
"inversify": "^6.0.1",
{{/useInversify}}
"es6-promise": "^4.2.4",
"url-parse": "^1.4.3"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inject, injectable, multiInject, optional, interfaces } from "inversify";

import { Configuration } from "../configuration";
import { ServerConfiguration, servers } from "../servers";
import { ServerConfiguration, servers{{#servers}}, server1{{/servers}} } from "../servers";
import { HttpLibrary{{^useRxJS}}, wrapHttpLibrary{{/useRxJS}} } from "../http/http";
import { Middleware{{^useRxJS}}, PromiseMiddlewareWrapper{{/useRxJS}} } from "../middleware";
import { authMethodServices, AuthMethods } from "../auth/auth";
Expand Down Expand Up @@ -42,7 +42,7 @@ class InjectableConfiguration implements AbstractConfiguration {
public authMethods: AuthMethods = {};

constructor(
@inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration = servers[0],
@inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration{{#servers}} = server1{{/servers}},
@inject(AbstractHttpLibrary) @optional() httpApi: AbstractHttpLibrary,
@multiInject(AbstractMiddleware) @optional() middleware: AbstractMiddleware[] = [],
@multiInject(AbstractAuthMethod) @optional() securityConfiguration: AbstractAuthMethod[] = []
Expand Down Expand Up @@ -90,6 +90,9 @@ export class ApiServiceBinder {
* return value;
*/
public bindServerConfigurationToPredefined(idx: number) {
if (!servers[idx]) {
throw new Error(`Server ${idx} is not available.`);
}
this.bindServerConfiguration.toConstantValue(servers[idx]);
return servers[idx];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,29 @@
"declaration": true,

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

"removeComments": true,
"sourceMap": true,
"outDir": "./dist",
"noLib": false,
{{#platforms}}
"lib": [
"es6"
,"ES2017.Object"
,"ES2021.String"
{{#node}}
"lib": [ "es6" ],
{{/node}}
{{#browser}}
"lib": [ "es6", "dom" ],
,"dom"
{{/browser}}
],
{{/platforms}}
{{#useInversify}}
"experimentalDecorators": true,
Expand Down
Loading

0 comments on commit dc3718c

Please sign in to comment.