-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
fix(typescript): typecheck generated samples + fixes #19903
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(typescript): typecheck generated samples + fixes #19903
Conversation
| @@ -0,0 +1,25 @@ | |||
| name: TypeScript clients type checks | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workflow runs the type check shell file
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We find all checked in samples that have a tsconfig.json
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can see in the CI logs, but basically there are a couple really really outdated dependencies, which can't even be updated anymore, like https://github.com/angular/tsickle/ for example. Hence we need to sometimes override incompatible peer dependencies, etc.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this does the actual type checking and fails the job if there are errors
| typeMapping.put("Array", "Array"); | ||
| typeMapping.put("array", "Array"); | ||
| typeMapping.put("boolean", "boolean"); | ||
| typeMapping.put("decimal", "string"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
without this change,
openapi-generator/modules/openapi-generator/src/test/resources/3_0/snakecase-discriminator.yaml
Lines 1556 to 1558 in 6b0e581
| decimal: | |
| type: string | |
| format: number |
for example would exopect a full-blown Decimal class, which is never generated. You can see code references to it that are not resolved as the file is not there. For example:
Line 16 in ce09134
| import type { Decimal } from './Decimal'; |
| 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}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes:
Api.ts:73:53 - error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.
73 throw new Error(`Missing required parameter ${paramName} when calling ${context}`);
~~~~~~~~~
Found 1 error in Api.ts:73
| return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; | ||
| } | ||
|
|
||
| export function exists(json: any, key: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes:
src/models/GetPetRegionsResponse.ts:15:10 - error TS2305: Module '"../runtime"' has no exported member 'exists'.
15 import { exists, mapValues } from '../runtime';
~~~~~~
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes
src/runtimeSagasAndRecords.ts:16:55 - error TS2345: Argument of type 'unknown' is not assignable to parameter of type 'string'.
16 const MatchingType = knownRecordFactories.get(value.get('recType')) as { new(input?: any): any }; // check if we know a Record with this type
~~~~~~~~~~~~~~~~~~~~
| return undefined; | ||
| {{/returnType}} | ||
| } catch (error) { | ||
| } catch (error: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no implicit any
| } | ||
|
|
||
| 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}} { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no implicit any
| @@ -1,4 +1,9 @@ | |||
| {{#useAxiosHttpModule}} | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nestjs/axios is only in the package.json when useAxiosHttpModule is truthy. If not, the package is not a dependency, which then fails in the type checks, as it can't be resolved. Other code uses the HttpService from nestjs/common in that case, so we do that here as well now, too.
| "@nestjs/testing": "~{{nestVersion}}", | ||
| "@types/express": "^4.16.0", | ||
| "@types/jest": "^24.0.15", | ||
| "@types/node": "^14.8.2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These types are so old that some definitions clash with the current typescript version.
| } | ||
|
|
||
| const parameters = this.headers[headerName].split(";"); | ||
| const parameters = this.headers[headerName]!.split(";"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is guarded further up, but some of the older TS versions we have can't determine this, yet.
| const parameters = this.headers[headerName]!.split(";"); | ||
| for (const parameter of parameters) { | ||
| let [key, value] = parameter.split("=", 2); | ||
| if (!key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined guard
| * | ||
| * This helper transforms a string mime-type into an internal representation. | ||
| * This simplifies the implementation of predicates that in turn define common rules for parsing or stringifying | ||
| * the payload. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined guards here too
| */ | ||
| {{/description}} | ||
| '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; | ||
| {{/vars}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noImplicitOverride fixes
| class InjectableConfiguration implements AbstractConfiguration { | ||
| public httpApi: HttpLibrary = new DefaultHttpLibrary(); | ||
| public middleware: Middleware[] = []; | ||
| public authMethods: AuthMethods = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
services/index.ts:26:9 - error TS2322: Type 'ServerConfiguration<{}> | undefined' is not assignable to type 'AbstractServerConfiguration'.
Type 'undefined' is not assignable to type 'AbstractServerConfiguration'.
26 @inject(AbstractServerConfiguration) @optional() public baseServer: AbstractServerConfiguration = servers[0],
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
services/index.ts:68:54 - error TS2345: Argument of type 'ServerConfiguration<{}> | undefined' is not assignable to parameter of type 'AbstractServerConfiguration'.
Type 'undefined' is not assignable to type 'AbstractServerConfiguration'.
68 this.bindServerConfiguration.toConstantValue(servers[idx]);
~~~~~~~~~~~~
| {{/useRxJS}} | ||
| {{#useInversify}} | ||
| "inversify": "^5.0.1", | ||
| "inversify": "^6.0.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inversify 5.x doesn't work properly with our current TS version. It's this issue here: https://stackoverflow.com/questions/75776252/inversify-typescript-5-ts1239-unable-to-resolve-signature-of-parameter-decor
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we were to generate this file, we could used the samples axis to produce one for each project with tsconfig. This would run the checks in parallel and make failures easier to understand. It would be the first time we do a dynamic build step though. Let me know what you prefer. We can also improve this later.
| export type HttpBasicConfiguration = { "username": string, "password": string }; | ||
| export type HttpBearerConfiguration = { tokenProvider: TokenProvider }; | ||
| export type OAuth2Configuration = { accessToken: string }; | ||
| export type HttpSignatureConfiguration = unknown; // TODO: Implement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not actually sure what this has to look like. Currently it's not supported as we don't have it implemented. This fix is to produce valid syntactic code, the implementation is probably a bit more involved.
You can see the fix it produces below in samples/openapi3/client/petstore/typescript/builds/explode-query/auth/auth.ts.
And how it is currently broken (syntactically invalid TS), here:
openapi-generator/samples/openapi3/client/petstore/typescript/builds/explode-query/auth/auth.ts
Lines 164 to 166 in ce09134
| "bearer_test"?: HttpBearerConfiguration, | |
| "http_signature_test"?: | |
| } |
| * return value; | ||
| */ | ||
| public bindServerConfigurationToPredefined(idx: number) { | ||
| if (!servers[idx]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
servers[idx] can be undefined
| {{/node}} | ||
| {{#browser}} | ||
| "lib": [ "es6", "dom" ], | ||
| ,"dom" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unified this, browser only gets "dom" in addition to the rest.
modules/openapi-generator/src/main/resources/typescript/tsconfig.mustache
Outdated
Show resolved
Hide resolved
| "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. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added these three
| {{#platforms}} | ||
| "lib": [ | ||
| "es6" | ||
| ,"ES2017.Object" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
brings Object.entries
| "lib": [ | ||
| "es6" | ||
| ,"ES2017.Object" | ||
| ,"ES2021.String" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String.replaceAll
macjohnny
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you so much for this contribution, this is awesome!
it allows to ensure the generated samples compile, even without writing any unit tests!
|
One thing to mention is, that currently none of the samples has a |
| on: | ||
| pull_request: | ||
| paths: | ||
| - samples/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joscha can we run this only if there's a change in the typescript-related samples?
#20000 (a recent PR) shows the workflow failed: https://github.com/OpenAPITools/openapi-generator/actions/runs/11606840555/job/32319917402?pr=20000
can you please take a look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can reduce it a bit more. In order to be super precise we'd need the full dependency graph, that is tricky as it includes any package.lock, node versions, etc. - it's not impossible but the repository is not set up for it currently, we'd want to also get the node dependency from nix for example. I can probably improve it a bit more, however I won't have access to a computer for a few more days. If the tests fail regularly with false positives, feel free to comment out the yaml and I'll fix and reenable when I get access to a computer.
This pull request adds type checking to generated samples via
.github/workflows/samples-typescript-typecheck.yaml.There are various issues in the current generated samples, from simple missing overrides, over incorrect types up to missing code pieces like classes.
I will leave explanations on each fix needed via code comments on the change.
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
master(upcoming7.x.0minor release - breaking changes with fallbacks),8.0.x(breaking changes without fallbacks)