Skip to content
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: generated TS interfaces extend type defined by additional properties #1028

Closed
wants to merge 1 commit into from

Conversation

mitjacotic
Copy link

Description

When generating TS interfaces right now, additionalPropertes are added as actual property to the intrerface, eg:

interface Foo {
  additionalProperties?: Map<string, any>
}

this is wrong, instead interface should extend such type:

interface Foo extends Map<string, any> {

}

This PR fixes that:

  • interfaces do not contain additionalProperties property
  • interfaces extend type defined by additional properties when using record and map mapType
  • extending can be turned off using extendInterfaceWithAdditionalProperties config

Related issue(s)
Resolves #959

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our contributors guide useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 1, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@mitjacotic mitjacotic changed the title generated TS interfaces extend type defined by additional properties fix: generated TS interfaces extend type defined by additional properties Dec 1, 2022
@mitjacotic mitjacotic marked this pull request as draft December 1, 2022 11:58
Copy link
Member

@jonaslagoni jonaslagoni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the time to provide this PR @mitjacotic! Let me know if you need anything from me to finish it 🙂

@mitjacotic
Copy link
Author

@jonaslagoni It slipped my mind, but in the past I had similar issue with mixed types where some keys are known and others not. I found various issues on this topic: microsoft/TypeScript#17867, microsoft/TypeScript#20597 etc.

Here are a few points that I've checked:

1

Extended type should always indicate that value can be undefined:

interface a extends Record<string, string | undefined> {}

right now what this PR produces is:

interface a extends Record<string, string> {}
const foo: a = {}

a.bar // TS thinks this is a number

2

known keys inside the interface need to be of same type/subtype as in the type interface is extending:

// OK:
interface a extends Record<string, any> {
  known_key: string
}
interface a extends Record<string, string> {
  known_key: string
}
interface a extends Record<string, string | number> {
  known_key: string
}

// NOT OK, TS errors out:
interface a extends Record<string, number> {
  known_key: string
}

I believe this last example is the one that should be covered, if it can't be, I don't really see this PR solves anything.

2.1

The above problem can kin of be fixed using types instead:

type a = { [key: string]: number | undefined } & {
  known_key: string;
}

But the problem with such type is that a variable can not be defined without force casting:

// fails saying that known_string is incompatible with index signature
const foo: a = {
  known_key: 'wer'
}

// fails since known_key is required:
const foo: a = {}

// but setters/getters work ok 
const foo: a = { } as unknown as bar

a.known_key // is string
a.foo // is number or undefined

a.known_key = 'foo' // OK
a.foo = 123 // OK
a.bar = 'wer' // OK - errors since it should be number

Generator currently only generates classes or interfaces, so the 2.1 option with types would require a new option to generate types.
And I don't see sense in having this functionality in just for the types and not interfaces too, especially when even using types would require force casting. What do you think? I'm thinking of just closing this PR, hopefully TS will gain support for such mixed types in the future.

@jonaslagoni
Copy link
Member

jonaslagoni commented Dec 8, 2022

Hmm, quite a dilemma 😆

Do you see it as feasible to do the following:

  • Look through all the properties and see if one contain ConstrainedDictionaryModel with serialization type unwrap (this is where the additionalProperty will be located).
  • If it contains it you know you need to extend the interface, now it's time to determine the type for the values. This is where we can do it complex, or easy.
    • Easy approach (or good first approach): Use any
    • Harder approach: Gather all the property types as a union, and use that value. This is the best approximation of which types can be used.

This should account for all your cases I think 🤔? Or does one of them not fit into this solution?

@jonaslagoni
Copy link
Member

We are gonna re-create the next branch for version 2 changes, which will close your PR, feel free to re-create or retarget your PR for master as it contains the version 1 changes 🙂 Or if your change contains a breaking change, target it for the version 2 in next.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants