-
Notifications
You must be signed in to change notification settings - Fork 28
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
Allow generic type definitions & non-default exports #357
base: main
Are you sure you want to change the base?
Conversation
fix(ts-interface-generator): handling for non-default export ManagedObjects
|
@danielang Thanks a lot for your contribution! |
Hey @akudev, |
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.
Thanks, this looks good. Just some minor things grammar/typo nits. I'll have another look at the code later.
@@ -95,18 +95,75 @@ This is a problem for application code using controls developed in TypeScript as | |||
|
|||
This tool scans all TypeScript source files for <b>top-level definitions of classes</b> inheriting from `sap.ui.base.ManagedObject` (in most cases those might be sub-classes of `sap.ui.core.Control`, so they will be called "controls" for simplicity). | |||
|
|||
For any such control, the metadata is parsed, analyzed, and a new TypeScript file is constructed, which contains an interface declaration with the methods generated by UI5 at runtime. | |||
For any such control, the metadata is parsed, analyzed, and a new TypeScript file is constructed, which contains an interface declaration with the methods generated by UI5 at runtime. This generated interface gets merged with the already existing code using TypeScripts [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) concept. |
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.
TypeScript's
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 sentence has been taken over into #476
|
||
### Handling `default` and named exports | ||
|
||
In order that the Declaration Merging of TypeScript works, the modifiers of the generated interface has to match the parsed UI5 artifact. |
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.
Plural or singular? Decide for either "modifiers ... have to" or "modifier ... has to".
} | ||
``` | ||
|
||
- **Default export:** |
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.
As regular UI5 controls are default exports of their module and custom control owners might strive for similar usage, I would mention this case first - before the named exports. (possibly also mention that standard UI5 controls are done like this)
|
||
- **Default export:** | ||
```typescript | ||
export default abstract class MyAbstractControl extends Control { |
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.
Is there a particular reason for this control to be named "...Abstract..."? At first glance I would have expected both code samples to be as similar as possible apart from the difference which is supposed to be demonstrated.
} | ||
``` | ||
|
||
becomes |
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.
"becomes" sounds like a transformation to me. What about being a bit more verbose and saying "for ... the following interface is generated" or so?
(same in the other samples)
|
||
### Generics | ||
|
||
This tool also supports generic classes which extending `ManagedObject` class. In order to enable the Declaration Merging of TypeScript the generic type parameters needs to be incooperated into the generated interface declaration. |
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.
"which are extending" or I think better: "which extend" (no native speaker here, but we are not describing a current action, but rather a general state)
|
||
### Generics | ||
|
||
This tool also supports generic classes which extending `ManagedObject` class. In order to enable the Declaration Merging of TypeScript the generic type parameters needs to be incooperated into the generated interface declaration. |
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.
Plural: "parameters need to be"
And:
I guess it's spelled "incorporated"
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.
Code looks good to me as well, I'll try out one or two things with the changed code.
Object.prototype.hasOwnProperty.call( | ||
existingImportsInSourceFile, | ||
typeName | ||
) |
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.
Out of curiosity: how is this different from if (typeName in existingImportsInSourceFile)
? Any inherited properties we should be aware of? Don't think so. Or just doing it like this in general to avoid inherited-property-problems in other places?
No need to change, just wondering.
|
||
let importSpecifier: ts.ImportSpecifier; | ||
|
||
// TODO: Use a method to check for versions |
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.
Indeed. :-) But outside the scope of this PR.
@danielang While the code is fine and the docu just has minor issues, I think there is a problem in how it works: when the generic class is used in its own API (and probably also when the class is used in APIs of other controls), then the generated methods in the interface do not have the generics part. Example (shortened): export default class SampleControl<T extends Toolbar> extends Control {
static readonly metadata = {
aggregations: {
partner: { type: "SampleControl" }
},
};
} causes the generation of declare module "./SampleControl" {
interface $SampleControlSettings extends $ControlSettings {
partner?: SampleControl[] | SampleControl | AggregationBindingInfo | `{${string}}`;
}
export default interface SampleControl<T extends Toolbar> {
// aggregation: partner
getPartner(): SampleControl[];
addPartner(partner: SampleControl): this;
insertPartner(partner: SampleControl, index: number): this;
removePartner(partner: number | string | SampleControl): this;
removeAllPartner(): SampleControl[];
indexOfPartner(partner: SampleControl): number;
destroyPartner(): this;
}
} There's the class name "SampleControl" all over the place - without generics. Which makes TS complain: "Generic type 'SampleControl' requires 1 type argument(s).". One could argue that the metadata actually it should be: partner: { type: "SampleControl<T extends Toolbar>" } but this doesn't work either because
Right now I don't know how to deal with this. Of course writing types as strings in the metadata section is What do you think? |
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.
In #476 I have implemented, among other things, the "non-default exports" aspect of this change. So at least that part will be supported. As I wrote, there are some issues with the generics, so this remaining part of the PR looks tricky.
factory.createModifier(ts.SyntaxKind.ExportKeyword), | ||
factory.createModifier(ts.SyntaxKind.DefaultKeyword), | ||
], | ||
classDeclaration.modifiers?.filter( |
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 think this does not cover cases like
class XYZ { ... }
...
export default XYZ;
(but I covered both in #476)
@@ -95,18 +95,75 @@ This is a problem for application code using controls developed in TypeScript as | |||
|
|||
This tool scans all TypeScript source files for <b>top-level definitions of classes</b> inheriting from `sap.ui.base.ManagedObject` (in most cases those might be sub-classes of `sap.ui.core.Control`, so they will be called "controls" for simplicity). | |||
|
|||
For any such control, the metadata is parsed, analyzed, and a new TypeScript file is constructed, which contains an interface declaration with the methods generated by UI5 at runtime. | |||
For any such control, the metadata is parsed, analyzed, and a new TypeScript file is constructed, which contains an interface declaration with the methods generated by UI5 at runtime. This generated interface gets merged with the already existing code using TypeScripts [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) concept. |
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 sentence has been taken over into #476
feat(ts-interface-generator): handeling generic type definitions
fix(ts-interface-generator): handling for non-default export ManagedObjects