diff --git a/.prettierignore b/.prettierignore
index 705d16e5..ef146b36 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -5,11 +5,12 @@ packages/dts-generator/src/checkDtslint/dtslintConfig/openui5-tests.ts
packages/dts-generator/src/resources/core-preamble.d.ts
packages/dts-generator/temp/
packages/ts-interface-generator/dist/
-packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.gen.d.ts
-packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.ts
-packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleManagedObject.gen.d.ts
-packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleManagedObject.ts
-packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleAnotherManagedObject.gen.d.ts
-packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleAnotherManagedObject.ts
-packages/ts-interface-generator/src/test/testdata/sampleWebComponent/SampleWebComponent.gen.d.ts
+packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.gen.d.ts
+packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.ts
+packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleManagedObject.gen.d.ts
+packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleManagedObject.ts
+packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleAnotherManagedObject.gen.d.ts
+packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleAnotherManagedObject.ts
+packages/ts-interface-generator/src/test/samples/sampleWebComponent/SampleWebComponent.gen.d.ts
+packages/ts-interface-generator/src/test/testcases/
test-packages
diff --git a/packages/ts-interface-generator/.eslintrc.js b/packages/ts-interface-generator/.eslintrc.js
index 53494117..eec0f638 100644
--- a/packages/ts-interface-generator/.eslintrc.js
+++ b/packages/ts-interface-generator/.eslintrc.js
@@ -33,6 +33,6 @@ module.exports = {
".eslintrc.js",
"someFile.js",
"*.gen.d.ts",
- "src/test/testdata/sampleWebComponent/**/*",
+ "src/test/samples/sampleWebComponent/**/*",
],
};
diff --git a/packages/ts-interface-generator/README.md b/packages/ts-interface-generator/README.md
index e3e0a4ff..6cb0a3e5 100644
--- a/packages/ts-interface-generator/README.md
+++ b/packages/ts-interface-generator/README.md
@@ -55,6 +55,26 @@ This is the list of available command-line arguments, including the ones already
- `--loglevel`: Set the console logging verbosity; options are: `error`, `warn`, `info`, `debug`, `trace`; default level is `info`
- `--jsdoc`: Set the amount of JSDoc which should be generated; options are: `none`, `minimal`, `verbose`; default is `verbose`: by default, the JSDoc documentation written in the control metadata for the properties, aggregations etc. is added to the generated methods, plus generic documentation for the `@param` and `@returns` tags as well as some additional generic documentation like for default values (if any). By setting `--jsdoc none` or `--jsdoc minimal` you can decide to omit all JSDoc or to only add the JSDoc written in the control.
+## Why?
+
+When developing UI5 controls, the control metadata declares properties, aggregations, events etc. The methods related to these (like `getText()` for a `text` property or `attachPress(...)` for a `press` event) do not need to be implemented - they are generated by the UI5 framework at runtime.
+
+However, as the TypeScript environment does not know about these methods, a call to any of them will be marked as error in the source code. And on top of this, there is also no code completion for these methods and no type information and in-place documentation for parameters and return values.
+
+This is a problem for application code using controls developed in TypeScript as well as for the development of these controls in TypeScript. Both development scenarios involve calling those API methods which are not known to the TypeScript environment. Thus, there needs to be a way to make TypeScript aware of them.
+
+(Remark: the controls shipped with the UI5 framework are implemented in JavaScript and a complete set of TypeScript type definitions is created during the framework build from the JSDoc comments. So both facets of the problem do not apply to them.)
+
+## How it Works
+
+This tool scans all TypeScript source files for top-level definitions of classes 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. This generated interface gets merged with the already existing code using TypeScript's [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) concept.
+
+Unfortunately, these separate interface declarations cannot define new constructors (see e.g. [this related TS issue](https://github.com/microsoft/TypeScript/issues/2957)). Hence those must be manually added to each control (one-time effort, pasting 3 lines of code). The interface generator writes the required lines to the console.
+
+Oh, and the tool itself is implemented in TypeScript because TypeScript makes development more efficient. ;-)
+
## Limitations
See the [TODO](#TODO) section for examples of features not yet implemented.
@@ -75,42 +95,19 @@ To detect whether the required constructor signatures are already present in the
### Second(+)-level Inheritance
-When there are at least two levels of custom controls (inheriting from each other), there is the error message: `Class static side 'typeof SomeOtherControl' incorrectly extends base class static side 'typeof MyControl'. The types of 'metadata.properties' are incompatible between these types.`
-
-As a workaround, you can assign a type to the `metadata` field in the parent class. Using type `object` is sufficient, but it can also be more specific:
+When there are at least two levels of custom controls (inheriting from each other) and you get the error message: `Class static side 'typeof SomeOtherControl' incorrectly extends base class static side 'typeof MyControl'. The types of 'metadata.properties' are incompatible between these types.`, then you have missed to assign the `MetadataOptions` type to the `metadata` field in the parent class. (Before UI5 version 1.110, you can use the type `object` instead):
```ts
-static readonly metadata: object = {
+static readonly metadata: MetadataOptions = {
...
```
See [#338](https://github.com/SAP/ui5-typescript/issues/338) for more details.
-## Why?
-
-When developing UI5 controls, the control metadata declares properties, aggregations, events etc. The methods related to these (like `getText()` for a `text` property or `attachPress(...)` for a `press` event) do not need to be implemented - they are generated by the UI5 framework at runtime.
-
-However, as the TypeScript environment does not know about these methods, a call to any of them will be marked as error in the source code. And on top of this, there is also no code completion for these methods and no type information and in-place documentation for parameters and return values.
-
-This is a problem for application code using controls developed in TypeScript as well as for the development of these controls in TypeScript. Both development scenarios involve calling those API methods which are not known to the TypeScript environment. Thus, there needs to be a way to make TypeScript aware of them.
-
-(Remark: the controls shipped with the UI5 framework are implemented in JavaScript and a complete set of TypeScript type definitions is created during the framework build from the JSDoc comments. So both facets of the problem do not apply to them.)
-
-## How it Works
-
-This tool scans all TypeScript source files for top-level definitions of classes 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.
-
-Unfortunately, these separate interface declarations cannot define new constructors (see e.g. [this related TS issue](https://github.com/microsoft/TypeScript/issues/2957)). Hence those must be manually added to each control (one-time effort, pasting 3 lines of code). The interface generator writes the required lines to the console.
-
-Oh, and the tool itself is implemented in TypeScript because TypeScript makes development more efficient. ;-)
-
## TODO
- make sure watch mode does it right (also run on deletion? Delete interfaces before-creating? Only create interfaces for updated files?)
- consider further information like deprecation etc.
-- it is probably required to check whether the control class being handled is the default export or a named export. Right now it is assumed that it is the default export. Other cases are not tested and likely not working.
- ...
## Support
diff --git a/packages/ts-interface-generator/src/interfaceGenerationHelper.ts b/packages/ts-interface-generator/src/interfaceGenerationHelper.ts
index 13996179..d0d720f9 100644
--- a/packages/ts-interface-generator/src/interfaceGenerationHelper.ts
+++ b/packages/ts-interface-generator/src/interfaceGenerationHelper.ts
@@ -22,12 +22,21 @@ let ManagedObjectSymbol: ts.Symbol,
ElementSymbol: ts.Symbol,
ControlSymbol: ts.Symbol,
WebComponentSymbol: ts.Symbol;
+
+// needs to be called to reset the base classes cache, so they are re-identified in the new type world
+function resetBaseClasses() {
+ ManagedObjectSymbol = undefined;
+ ElementSymbol = undefined;
+ ControlSymbol = undefined;
+ WebComponentSymbol = undefined;
+}
+
function interestingBaseClassForSymbol(
typeChecker: ts.TypeChecker,
symbol: ts.Symbol,
): "ManagedObject" | "Element" | "Control" | "WebComponent" | undefined {
if (!ManagedObjectSymbol) {
- // cache - TODO: needs to be refreshed when the UI5 type definitions are updated during a run of the tool!
+ // cache (execution takes one-digit milliseconds) - TODO: does it need to be refreshed when the UI5 type definitions are updated during a run of the tool, or is the clearing from generateInterfaces sufficient?
// identify the symbols for the interesting classes
const managedObjectModuleDeclaration = typeChecker
.getAmbientModules()
@@ -132,6 +141,7 @@ function generateInterfaces(
interfaceText: string,
) => void = writeInterfaceFile,
) {
+ resetBaseClasses(); // typeChecker might be from a new type world
const mos = getManagedObjects(sourceFile, typeChecker);
// find out whether type version 1.115.1 or later is used, where "Event" is a class with generics (this influences what we need to generate)
@@ -184,6 +194,30 @@ function getManagedObjects(
sourceFile: ts.SourceFile,
typeChecker: ts.TypeChecker,
) {
+ // First find the default export (in contrast to named exports) of this ES module - we want to find top-level statements like:
+ // export default class MyControl extends Control {...} // direct export of the class
+ // export default MyControl; // export of a variable which holds the class
+ // we don't care about other default exports, including instances of the class:
+ // export default new MyControl(); // instance export
+ // and we are also not interested in named exports of the class here
+ // export class MyControl extends Control {...} // etc.
+ let defaultExport: ts.Identifier | ts.ClassDeclaration | undefined;
+ sourceFile.statements.forEach((statement) => {
+ if (
+ ts.isExportAssignment(statement) &&
+ ts.isIdentifier(statement.expression)
+ ) {
+ defaultExport = statement.expression;
+ } else if (ts.isClassDeclaration(statement)) {
+ const hasDefaultModifier = statement.modifiers?.some(
+ (modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword,
+ );
+ if (hasDefaultModifier) {
+ defaultExport = statement;
+ }
+ }
+ });
+
const managedObjects: ManagedObjectInfo[] = [];
sourceFile.statements.forEach((statement) => {
if (ts.isClassDeclaration(statement)) {
@@ -331,10 +365,20 @@ In any case, you need to make the parent class ${typeChecker.getFullyQualifiedNa
const constructorSignaturesAvailable =
checkConstructors(statement);
+ const className = statement.name ? statement.name.text : "";
+
+ // is this class a default export?
+ const isDefaultExport =
+ defaultExport &&
+ ((ts.isIdentifier(defaultExport) &&
+ defaultExport.text === className) ||
+ defaultExport === statement);
+
// store the information about the identified ManagedObject/Control
managedObjects.push({
sourceFile,
- className: statement.name ? statement.name.text : "",
+ className,
+ isDefaultExport,
classDeclaration: statement,
settingsTypeFullName,
interestingBaseClass,
@@ -700,6 +744,7 @@ function generateInterface(
{
sourceFile,
className,
+ isDefaultExport,
settingsTypeFullName,
interestingBaseClass,
constructorSignaturesAvailable,
@@ -708,6 +753,7 @@ function generateInterface(
| {
sourceFile: ts.SourceFile;
className: string;
+ isDefaultExport: boolean;
settingsTypeFullName: string;
interestingBaseClass:
| "ManagedObject"
@@ -801,6 +847,7 @@ function generateInterface(
const moduleName = path.basename(fileName, path.extname(fileName));
const ast = buildAST(
classInfo,
+ isDefaultExport,
sourceFile.fileName,
constructorSignaturesAvailable,
moduleName,
@@ -818,6 +865,7 @@ function generateInterface(
function buildAST(
classInfo: ClassInfo,
+ isDefaultExport: boolean,
classFileName: string,
constructorSignaturesAvailable: boolean,
moduleName: string,
@@ -882,29 +930,51 @@ function buildAST(
let myInterface;
if (parseFloat(ts.version) >= 4.8) {
- myInterface = factory.createInterfaceDeclaration(
- [
- factory.createModifier(ts.SyntaxKind.ExportKeyword),
- factory.createModifier(ts.SyntaxKind.DefaultKeyword),
- ],
- classInfo.name,
- undefined,
- undefined,
- methods,
- );
+ if (isDefaultExport) {
+ myInterface = factory.createInterfaceDeclaration(
+ [
+ factory.createModifier(ts.SyntaxKind.ExportKeyword),
+ factory.createModifier(ts.SyntaxKind.DefaultKeyword),
+ ],
+ classInfo.name,
+ undefined,
+ undefined,
+ methods,
+ );
+ } else {
+ myInterface = factory.createInterfaceDeclaration(
+ [], // no export needed for module augmentation when class is a named export in the original file!
+ classInfo.name,
+ undefined,
+ undefined,
+ methods,
+ );
+ }
} else {
- myInterface = factory.createInterfaceDeclaration(
- undefined,
- [
- factory.createModifier(ts.SyntaxKind.ExportKeyword),
- factory.createModifier(ts.SyntaxKind.DefaultKeyword),
- ],
- classInfo.name,
- undefined,
- undefined,
- // @ts-ignore: below TS 4.8 there were more params
- methods,
- );
+ if (isDefaultExport) {
+ myInterface = factory.createInterfaceDeclaration(
+ undefined,
+ [
+ factory.createModifier(ts.SyntaxKind.ExportKeyword),
+ factory.createModifier(ts.SyntaxKind.DefaultKeyword),
+ ],
+ classInfo.name,
+ undefined,
+ undefined,
+ // @ts-ignore: below TS 4.8 there were more params
+ methods,
+ );
+ } else {
+ myInterface = factory.createInterfaceDeclaration(
+ undefined,
+ [], // no export needed for module augmentation when class is a named export in the original file!
+ classInfo.name,
+ undefined,
+ undefined,
+ // @ts-ignore: below TS 4.8 there were more params
+ methods,
+ );
+ }
}
addLineBreakBefore(myInterface, 2);
@@ -945,8 +1015,9 @@ function buildAST(
statements.push(genericEventDefinitionModule);
}
- // if needed, assemble the second module declaration
- if (requiredImports.selfIsUsed) {
+ // If needed, assemble the second module declaration.
+ // In case the class is not a default export, the first module declaration will already be without export, so this second module declaration is not needed anyway
+ if (requiredImports.selfIsUsed && isDefaultExport) {
let myInterface2;
if (parseFloat(ts.version) >= 4.8) {
myInterface2 = factory.createInterfaceDeclaration(
@@ -988,7 +1059,7 @@ function buildAST(
ts.addSyntheticLeadingComment(
module2,
ts.SyntaxKind.SingleLineCommentTrivia,
- " this duplicate interface without export is needed to avoid \"Cannot find name '" +
+ " this duplicate interface without export is needed to avoid \"Cannot find name '" + // TODO: does not seem to be needed any longer; investigate and try to reproduce
classInfo.name +
"'\" TypeScript errors above",
);
diff --git a/packages/ts-interface-generator/src/test/interfaceGenerationHelper.test.ts b/packages/ts-interface-generator/src/test/interfaceGenerationHelper.test.ts
index ecb3c47c..39d122d1 100644
--- a/packages/ts-interface-generator/src/test/interfaceGenerationHelper.test.ts
+++ b/packages/ts-interface-generator/src/test/interfaceGenerationHelper.test.ts
@@ -86,6 +86,11 @@ declare module "./SampleControl" {
*/
alsoLabelledBy?: Control | string | (Control | string)[];
+ /**
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ singlePress?: (event: SampleControl$SinglePressEvent) => void;
+
/**
* Fired when double-clicked.
*/
@@ -338,6 +343,62 @@ declare module "./SampleControl" {
*/
removeAllAlsoLabelledBy(): string[];
+ // event: singlePress
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(data: CustomDataType, fn: (event: SampleControl$SinglePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "singlePress" to attached listeners.
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ fireSinglePress(parameters?: SampleControl$SinglePressEventParameters): this;
+
// event: doublePress
/**
@@ -393,19 +454,35 @@ declare module "./SampleControl" {
* The return value of this method indicates whether the default action should be executed.
*
* @param parameters Parameters to pass along with the event
+ * @param [mParameters.delay] Fired when double-clicked.
+ *
* @returns Whether or not to prevent the default action
*/
fireDoublePress(parameters?: SampleControl$DoublePressEventParameters): boolean;
}
+ /**
+ * Interface describing the parameters of SampleControl's 'singlePress' event.
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ // eslint-disable-next-line
+ export interface SampleControl$SinglePressEventParameters {
+ }
+
/**
* Interface describing the parameters of SampleControl's 'doublePress' event.
* Fired when double-clicked.
*/
- // eslint-disable-next-line
export interface SampleControl$DoublePressEventParameters {
+ delay?: number;
}
+ /**
+ * Type describing the SampleControl's 'singlePress' event.
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ export type SampleControl$SinglePressEvent = Event;
+
/**
* Type describing the SampleControl's 'doublePress' event.
* Fired when double-clicked.
@@ -661,6 +738,62 @@ declare module "./SampleControl" {
*/
removeAllAlsoLabelledBy(): string[];
+ // event: singlePress
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(data: CustomDataType, fn: (event: SampleControl$SinglePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "singlePress" to attached listeners.
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ fireSinglePress(parameters?: SampleControl$SinglePressEventParameters): this;
+
// event: doublePress
/**
@@ -716,6 +849,8 @@ declare module "./SampleControl" {
* The return value of this method indicates whether the default action should be executed.
*
* @param parameters Parameters to pass along with the event
+ * @param [mParameters.delay] Fired when double-clicked.
+ *
* @returns Whether or not to prevent the default action
*/
fireDoublePress(parameters?: SampleControl$DoublePressEventParameters): boolean;
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.gen.d.ts b/packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.gen.d.ts
similarity index 77%
rename from packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.gen.d.ts
rename to packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.gen.d.ts
index 1185e1af..3139b4de 100644
--- a/packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.gen.d.ts
+++ b/packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.gen.d.ts
@@ -40,6 +40,11 @@ declare module "./SampleControl" {
*/
alsoLabelledBy?: Control | string | (Control | string)[];
+ /**
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ singlePress?: (event: SampleControl$SinglePressEvent) => void;
+
/**
* Fired when double-clicked.
*/
@@ -292,6 +297,62 @@ declare module "./SampleControl" {
*/
removeAllAlsoLabelledBy(): string[];
+ // event: singlePress
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(data: CustomDataType, fn: (event: SampleControl$SinglePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "singlePress" to attached listeners.
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ fireSinglePress(parameters?: SampleControl$SinglePressEventParameters): this;
+
// event: doublePress
/**
@@ -347,19 +408,35 @@ declare module "./SampleControl" {
* The return value of this method indicates whether the default action should be executed.
*
* @param parameters Parameters to pass along with the event
+ * @param [mParameters.delay] Fired when double-clicked.
+ *
* @returns Whether or not to prevent the default action
*/
fireDoublePress(parameters?: SampleControl$DoublePressEventParameters): boolean;
}
+ /**
+ * Interface describing the parameters of SampleControl's 'singlePress' event.
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ // eslint-disable-next-line
+ export interface SampleControl$SinglePressEventParameters {
+ }
+
/**
* Interface describing the parameters of SampleControl's 'doublePress' event.
* Fired when double-clicked.
*/
- // eslint-disable-next-line
export interface SampleControl$DoublePressEventParameters {
+ delay?: number;
}
+ /**
+ * Type describing the SampleControl's 'singlePress' event.
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ export type SampleControl$SinglePressEvent = Event;
+
/**
* Type describing the SampleControl's 'doublePress' event.
* Fired when double-clicked.
@@ -615,6 +692,62 @@ declare module "./SampleControl" {
*/
removeAllAlsoLabelledBy(): string[];
+ // event: singlePress
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(data: CustomDataType, fn: (event: SampleControl$SinglePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "singlePress" to attached listeners.
+ *
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ fireSinglePress(parameters?: SampleControl$SinglePressEventParameters): this;
+
// event: doublePress
/**
@@ -670,6 +803,8 @@ declare module "./SampleControl" {
* The return value of this method indicates whether the default action should be executed.
*
* @param parameters Parameters to pass along with the event
+ * @param [mParameters.delay] Fired when double-clicked.
+ *
* @returns Whether or not to prevent the default action
*/
fireDoublePress(parameters?: SampleControl$DoublePressEventParameters): boolean;
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.ts b/packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.ts
similarity index 83%
rename from packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.ts
rename to packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.ts
index 1af77fa2..d03161bc 100644
--- a/packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.ts
+++ b/packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.ts
@@ -46,10 +46,22 @@ export default class SampleControl extends Button {
alsoLabelledBy: { type: "sap.ui.core.Control", multiple: true },
},
events: {
+ /**
+ * Fired when single-clicked. This event has no parameters, which requires an eslint-disable in the generated code.
+ */
+ singlePress: {},
/**
* Fired when double-clicked.
*/
- doublePress: { allowPreventDefault: true },
+ doublePress: {
+ allowPreventDefault: true,
+ parameters: {
+ /**
+ * The amount of milliseconds between the first and second press
+ */
+ delay: { type: "int" }
+ }
+ }
},
};
@@ -72,6 +84,8 @@ export default class SampleControl extends Button {
};
doit() {
+ this.fireSinglePress();
+ this.fireDoublePress({ delay: 100 });
alert("Hello");
}
}
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleAnotherManagedObject.gen.d.ts b/packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleAnotherManagedObject.gen.d.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleAnotherManagedObject.gen.d.ts
rename to packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleAnotherManagedObject.gen.d.ts
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleAnotherManagedObject.ts b/packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleAnotherManagedObject.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleAnotherManagedObject.ts
rename to packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleAnotherManagedObject.ts
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleManagedObject.gen.d.ts b/packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleManagedObject.gen.d.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleManagedObject.gen.d.ts
rename to packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleManagedObject.gen.d.ts
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleManagedObject.ts b/packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleManagedObject.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleManagedObject/SampleManagedObject.ts
rename to packages/ts-interface-generator/src/test/samples/sampleManagedObject/SampleManagedObject.ts
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleWebComponent/App.codetest.ts b/packages/ts-interface-generator/src/test/samples/sampleWebComponent/App.codetest.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleWebComponent/App.codetest.ts
rename to packages/ts-interface-generator/src/test/samples/sampleWebComponent/App.codetest.ts
diff --git a/packages/ts-interface-generator/src/test/samples/sampleWebComponent/SampleWebComponent.gen.d.ts b/packages/ts-interface-generator/src/test/samples/sampleWebComponent/SampleWebComponent.gen.d.ts
new file mode 100644
index 00000000..56868676
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/samples/sampleWebComponent/SampleWebComponent.gen.d.ts
@@ -0,0 +1,729 @@
+import Event from "sap/ui/base/Event";
+import { CSSColor } from "sap/ui/core/library";
+import Control from "sap/ui/core/Control";
+import { AggregationBindingInfo } from "sap/ui/base/ManagedObject";
+import WebComponent from "sap/ui/core/webc/WebComponent";
+import TooltipBase from "sap/ui/core/TooltipBase";
+import { PropertyBindingInfo } from "sap/ui/base/ManagedObject";
+import { $WebComponentSettings } from "sap/ui/core/webc/WebComponent";
+
+declare module "./SampleWebComponent" {
+
+ /**
+ * Interface defining the settings object used in constructor calls
+ */
+ interface $SampleWebComp onentSettings extends $WebComponentSettings {
+
+ /**
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ */
+ subtext?: string | PropertyBindingInfo;
+
+ /**
+ * Determines the text color of the SampleWebComponent
.
+ *
+ * @experimental
+ */
+ textColor?: CSSColor | PropertyBindingInfo | `{${string}}`;
+
+ /**
+ * Usage of mapping
+ */
+ text?: string | PropertyBindingInfo;
+
+ /**
+ * Determines the content of the SampleWebComponent
.
+ */
+ content?: Control[] | Control | AggregationBindingInfo | `{${string}}`;
+ header?: WebComponent;
+ tooltip?: TooltipBase | string | PropertyBindingInfo;
+ partnerControl?: SampleWebComponent | string;
+
+ /**
+ * This is an association.
+ */
+ alsoLabelledBy?: Control | string | (Control | string)[];
+
+ /**
+ * Fired when double-clicked.
+ */
+ doublePress?: (event: SampleWebComponent$DoublePressEvent) => void;
+ }
+
+ export default interface SampleWebComponent {
+
+ // property: subtext
+
+ /**
+ * Gets current value of property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ *
+ * @returns Value of property "subtext"
+ */
+ getSubtext(): string;
+
+ /**
+ * Sets a new value for property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param subtext New value for property "subtext"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setSubtext(subtext: string): this;
+
+ // property: textColor
+
+ /**
+ * Gets current value of property "textColor".
+ *
+ * Determines the text color of the SampleWebComponent
.
+ *
+ * @experimental
+ * Default value is: ""
+ * @returns Value of property "textColor"
+ */
+ getTextColor(): CSSColor;
+
+ /**
+ * Sets a new value for property "textColor".
+ *
+ * Determines the text color of the SampleWebComponent
.
+ *
+ * @experimental
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * Default value is: ""
+ * @param [textColor=""] New value for property "textColor"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTextColor(textColor: CSSColor): this;
+
+ // property: text
+
+ /**
+ * Gets current value of property "text".
+ *
+ * Usage of mapping
+ *
+ * @returns Value of property "text"
+ */
+ getText(): string;
+
+ /**
+ * Sets a new value for property "text".
+ *
+ * Usage of mapping
+ *
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param text New value for property "text"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setText(text: string): this;
+
+ // aggregation: content
+
+ /**
+ * Gets content of aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ */
+ getContent(): Control[];
+
+ /**
+ * Adds some content to the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addContent(content: Control): this;
+
+ /**
+ * Inserts a content into the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content to insert; if empty, nothing is inserted
+ * @param index The "0"-based index the content should be inserted at; for
+ * a negative value of "iIndex", the content is inserted at position 0; for a value
+ * greater than the current size of the aggregation, the content is inserted at
+ * the last position
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ insertContent(content: Control, index: number): this;
+
+ /**
+ * Removes a content from the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content to remove or its index or id
+ * @returns The removed content or "null"
+ */
+ removeContent(content: number | string | Control): Control | null;
+
+ /**
+ * Removes all the controls from the aggregation "content".
+ * Additionally, it unregisters them from the hosting UIArea.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllContent(): Control[];
+
+ /**
+ * Checks for the provided "sap.ui.core.Control" in the aggregation "content".
+ * and returns its index if found or -1 otherwise.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content whose index is looked for
+ * @returns The index of the provided control in the aggregation if found, or -1 otherwise
+ */
+ indexOfContent(content: Control): number;
+
+ /**
+ * Destroys all the content in the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyContent(): this;
+
+ /**
+ * Binds aggregation "content" to model data.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * See {@link sap.ui.base.ManagedObject#bindAggregation ManagedObject.bindAggregation} for a
+ * detailed description of the possible properties of "oBindingInfo".
+ * @param oBindingInfo The binding information
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ bindContent(bindingInfo: AggregationBindingInfo): this;
+
+ /**
+ * Unbinds aggregation "content" from model data.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ unbindContent(): this;
+
+ // aggregation: header
+
+ /**
+ * Gets content of aggregation "header".
+ */
+ getHeader(): WebComponent;
+
+ /**
+ * Sets the aggregated header.
+ *
+ * @param header The header to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setHeader(header: WebComponent): this;
+
+ /**
+ * Destroys the header in the aggregation "header".
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyHeader(): this;
+
+ // aggregation: tooltip
+
+ /**
+ * Gets content of aggregation "tooltip".
+ */
+ getTooltip(): TooltipBase;
+
+ /**
+ * Sets the aggregated tooltip.
+ *
+ * @param tooltip The tooltip to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTooltip(tooltip: TooltipBase): this;
+
+ /**
+ * Destroys the tooltip in the aggregation "tooltip".
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyTooltip(): this;
+
+ // association: partnerControl
+
+ /**
+ * ID of the element which is the current target of the association "partnerControl", or "null".
+ */
+ getPartnerControl(): string;
+
+ /**
+ * Sets the associated partnerControl.
+ *
+ * @param partnerControl ID of an element which becomes the new target of this "partnerControl" association; alternatively, an element instance may be given
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setPartnerControl(partnerControl?: string | SampleWebComponent): this;
+
+ // association: alsoLabelledBy
+
+ /**
+ * Returns array of IDs of the elements which are the current targets of the association "alsoLabelledBy".
+ *
+ * This is an association.
+ */
+ getAlsoLabelledBy(): string[];
+
+ /**
+ * Adds some alsoLabelledBy into the association "alsoLabelledBy".
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addAlsoLabelledBy(alsoLabelledBy: string | Control): this;
+
+ /**
+ * Removes an alsoLabelledBy from the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to be removed or its index or ID
+ * @returns The removed alsoLabelledBy or "null"
+ */
+ removeAlsoLabelledBy(alsoLabelledBy: number | string | Control): string;
+
+ /**
+ * Removes all the controls in the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllAlsoLabelledBy(): string[];
+
+ // event: doublePress
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleWebComponent".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleWebComponent" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleWebComponent" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(fn: (event: SampleWebComponent$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleWebComponent".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleWebComponent" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleWebComponent" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(data: CustomDataType, fn: (event: SampleWebComponent$DoublePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "doublePress" event of this "SampleWebComponent".
+ *
+ * Fired when double-clicked.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachDoublePress(fn: (event: SampleWebComponent$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "doublePress" to attached listeners.
+ *
+ * Fired when double-clicked.
+ *
+ * Listeners may prevent the default action of this event by calling the "preventDefault" method on the event object.
+ * The return value of this method indicates whether the default action should be executed.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Whether or not to prevent the default action
+ */
+ fireDoublePress(parameters?: SampleWebComponent$DoublePressEventParameters): boolean;
+ }
+
+ /**
+ * Interface describing the parameters of SampleWebComponent's 'doublePress' event.
+ * Fired when double-clicked.
+ */
+ // eslint-disable-next-line
+ export interface SampleWebComponent$DoublePressEventParameters {
+ }
+
+ /**
+ * Type describing the SampleWebComponent's 'doublePress' event.
+ * Fired when double-clicked.
+ */
+ export type SampleWebComponent$DoublePressEvent = Event;
+}
+
+// this duplicate interface without export is needed to avoid "Cannot find name 'SampleWebComponent'" TypeScript errors above
+declare module "./SampleWebComponent" {
+ interface SampleWebComponent {
+
+ // property: subtext
+
+ /**
+ * Gets current value of property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ *
+ * @returns Value of property "subtext"
+ */
+ getSubtext(): string;
+
+ /**
+ * Sets a new value for property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param subtext New value for property "subtext"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setSubtext(subtext: string): this;
+
+ // property: textColor
+
+ /**
+ * Gets current value of property "textColor".
+ *
+ * Determines the text color of the SampleWebComponent
.
+ *
+ * @experimental
+ * Default value is: ""
+ * @returns Value of property "textColor"
+ */
+ getTextColor(): CSSColor;
+
+ /**
+ * Sets a new value for property "textColor".
+ *
+ * Determines the text color of the SampleWebComponent
.
+ *
+ * @experimental
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * Default value is: ""
+ * @param [textColor=""] New value for property "textColor"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTextColor(textColor: CSSColor): this;
+
+ // property: text
+
+ /**
+ * Gets current value of property "text".
+ *
+ * Usage of mapping
+ *
+ * @returns Value of property "text"
+ */
+ getText(): string;
+
+ /**
+ * Sets a new value for property "text".
+ *
+ * Usage of mapping
+ *
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param text New value for property "text"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setText(text: string): this;
+
+ // aggregation: content
+
+ /**
+ * Gets content of aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ */
+ getContent(): Control[];
+
+ /**
+ * Adds some content to the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addContent(content: Control): this;
+
+ /**
+ * Inserts a content into the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content to insert; if empty, nothing is inserted
+ * @param index The "0"-based index the content should be inserted at; for
+ * a negative value of "iIndex", the content is inserted at position 0; for a value
+ * greater than the current size of the aggregation, the content is inserted at
+ * the last position
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ insertContent(content: Control, index: number): this;
+
+ /**
+ * Removes a content from the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content to remove or its index or id
+ * @returns The removed content or "null"
+ */
+ removeContent(content: number | string | Control): Control | null;
+
+ /**
+ * Removes all the controls from the aggregation "content".
+ * Additionally, it unregisters them from the hosting UIArea.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllContent(): Control[];
+
+ /**
+ * Checks for the provided "sap.ui.core.Control" in the aggregation "content".
+ * and returns its index if found or -1 otherwise.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @param content The content whose index is looked for
+ * @returns The index of the provided control in the aggregation if found, or -1 otherwise
+ */
+ indexOfContent(content: Control): number;
+
+ /**
+ * Destroys all the content in the aggregation "content".
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyContent(): this;
+
+ /**
+ * Binds aggregation "content" to model data.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * See {@link sap.ui.base.ManagedObject#bindAggregation ManagedObject.bindAggregation} for a
+ * detailed description of the possible properties of "oBindingInfo".
+ * @param oBindingInfo The binding information
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ bindContent(bindingInfo: AggregationBindingInfo): this;
+
+ /**
+ * Unbinds aggregation "content" from model data.
+ *
+ * Determines the content of the SampleWebComponent
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ unbindContent(): this;
+
+ // aggregation: header
+
+ /**
+ * Gets content of aggregation "header".
+ */
+ getHeader(): WebComponent;
+
+ /**
+ * Sets the aggregated header.
+ *
+ * @param header The header to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setHeader(header: WebComponent): this;
+
+ /**
+ * Destroys the header in the aggregation "header".
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyHeader(): this;
+
+ // aggregation: tooltip
+
+ /**
+ * Gets content of aggregation "tooltip".
+ */
+ getTooltip(): TooltipBase;
+
+ /**
+ * Sets the aggregated tooltip.
+ *
+ * @param tooltip The tooltip to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTooltip(tooltip: TooltipBase): this;
+
+ /**
+ * Destroys the tooltip in the aggregation "tooltip".
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyTooltip(): this;
+
+ // association: partnerControl
+
+ /**
+ * ID of the element which is the current target of the association "partnerControl", or "null".
+ */
+ getPartnerControl(): string;
+
+ /**
+ * Sets the associated partnerControl.
+ *
+ * @param partnerControl ID of an element which becomes the new target of this "partnerControl" association; alternatively, an element instance may be given
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setPartnerControl(partnerControl?: string | SampleWebComponent): this;
+
+ // association: alsoLabelledBy
+
+ /**
+ * Returns array of IDs of the elements which are the current targets of the association "alsoLabelledBy".
+ *
+ * This is an association.
+ */
+ getAlsoLabelledBy(): string[];
+
+ /**
+ * Adds some alsoLabelledBy into the association "alsoLabelledBy".
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addAlsoLabelledBy(alsoLabelledBy: string | Control): this;
+
+ /**
+ * Removes an alsoLabelledBy from the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to be removed or its index or ID
+ * @returns The removed alsoLabelledBy or "null"
+ */
+ removeAlsoLabelledBy(alsoLabelledBy: number | string | Control): string;
+
+ /**
+ * Removes all the controls in the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllAlsoLabelledBy(): string[];
+
+ // event: doublePress
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleWebComponent".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleWebComponent" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleWebComponent" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(fn: (event: SampleWebComponent$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleWebComponent".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleWebComponent" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleWebComponent" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(data: CustomDataType, fn: (event: SampleWebComponent$DoublePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "doublePress" event of this "SampleWebComponent".
+ *
+ * Fired when double-clicked.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachDoublePress(fn: (event: SampleWebComponent$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "doublePress" to attached listeners.
+ *
+ * Fired when double-clicked.
+ *
+ * Listeners may prevent the default action of this event by calling the "preventDefault" method on the event object.
+ * The return value of this method indicates whether the default action should be executed.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Whether or not to prevent the default action
+ */
+ fireDoublePress(parameters?: SampleWebComponent$DoublePressEventParameters): boolean;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleWebComponent/SampleWebComponent.ts b/packages/ts-interface-generator/src/test/samples/sampleWebComponent/SampleWebComponent.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleWebComponent/SampleWebComponent.ts
rename to packages/ts-interface-generator/src/test/samples/sampleWebComponent/SampleWebComponent.ts
diff --git a/packages/ts-interface-generator/src/test/testdata/tsfiles/someFile.js b/packages/ts-interface-generator/src/test/samples/tsfiles/someFile.js
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/tsfiles/someFile.js
rename to packages/ts-interface-generator/src/test/samples/tsfiles/someFile.js
diff --git a/packages/ts-interface-generator/src/test/testdata/tsfiles/someFile.ts b/packages/ts-interface-generator/src/test/samples/tsfiles/someFile.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/tsfiles/someFile.ts
rename to packages/ts-interface-generator/src/test/samples/tsfiles/someFile.ts
diff --git a/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.gen.d.ts
new file mode 100644
index 00000000..3b505d64
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.gen.d.ts
@@ -0,0 +1,34 @@
+import { PropertyBindingInfo } from "sap/ui/base/ManagedObject";
+import { $ControlSettings } from "sap/ui/core/Control";
+
+declare module "./MyControl" {
+
+ /**
+ * Interface defining the settings object used in constructor calls
+ */
+ interface $MyControlSettings extends $ControlSettings {
+ text?: string | PropertyBindingInfo;
+ }
+
+ interface MyControl {
+
+ // property: text
+
+ /**
+ * Gets current value of property "text".
+ *
+ * @returns Value of property "text"
+ */
+ getText(): string;
+
+ /**
+ * Sets a new value for property "text".
+ *
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param text New value for property "text"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setText(text: string): this;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.ts b/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.ts
new file mode 100644
index 00000000..76722190
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.ts
@@ -0,0 +1,27 @@
+import Control from "sap/ui/core/Control";
+import { MetadataOptions } from "sap/ui/core/Element";
+import RenderManager from "sap/ui/core/RenderManager";
+
+/**
+ * @namespace my
+ */
+export class MyControl extends Control {
+ static readonly metadata: MetadataOptions = {
+ properties: {
+ text: "string",
+ },
+ };
+
+ static renderer = {
+ apiVersion: 2,
+ render: function (rm: RenderManager, control: MyControl) {
+ rm.openStart("div", control);
+ rm.openEnd();
+ // @ts-ignore this only works with the generated interface
+ rm.text(control.getText());
+ rm.close("div");
+ },
+ };
+}
+
+export default new MyControl();
diff --git a/packages/ts-interface-generator/src/test/testcases/separate-export/MyControl.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/separate-export/MyControl.gen.d.ts
new file mode 100644
index 00000000..d3b6a458
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/separate-export/MyControl.gen.d.ts
@@ -0,0 +1,34 @@
+import { PropertyBindingInfo } from "sap/ui/base/ManagedObject";
+import { $ControlSettings } from "sap/ui/core/Control";
+
+declare module "./MyControl" {
+
+ /**
+ * Interface defining the settings object used in constructor calls
+ */
+ interface $MyControlSettings extends $ControlSettings {
+ text?: string | PropertyBindingInfo;
+ }
+
+ export default interface MyControl {
+
+ // property: text
+
+ /**
+ * Gets current value of property "text".
+ *
+ * @returns Value of property "text"
+ */
+ getText(): string;
+
+ /**
+ * Sets a new value for property "text".
+ *
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param text New value for property "text"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setText(text: string): this;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/separate-export/MyControl.ts b/packages/ts-interface-generator/src/test/testcases/separate-export/MyControl.ts
new file mode 100644
index 00000000..92455b94
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/separate-export/MyControl.ts
@@ -0,0 +1,27 @@
+import Control from "sap/ui/core/Control";
+import { MetadataOptions } from "sap/ui/core/Element";
+import RenderManager from "sap/ui/core/RenderManager";
+
+/**
+ * @namespace my
+ */
+class MyControl extends Control {
+ static readonly metadata: MetadataOptions = {
+ properties: {
+ text: "string",
+ },
+ };
+
+ static renderer = {
+ apiVersion: 2,
+ render: function (rm: RenderManager, control: MyControl) {
+ rm.openStart("div", control);
+ rm.openEnd();
+ // @ts-ignore this only works with the generated interface
+ rm.text(control.getText());
+ rm.close("div");
+ },
+ };
+}
+
+export default MyControl;
\ No newline at end of file
diff --git a/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.gen.d.ts
new file mode 100644
index 00000000..d3b6a458
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.gen.d.ts
@@ -0,0 +1,34 @@
+import { PropertyBindingInfo } from "sap/ui/base/ManagedObject";
+import { $ControlSettings } from "sap/ui/core/Control";
+
+declare module "./MyControl" {
+
+ /**
+ * Interface defining the settings object used in constructor calls
+ */
+ interface $MyControlSettings extends $ControlSettings {
+ text?: string | PropertyBindingInfo;
+ }
+
+ export default interface MyControl {
+
+ // property: text
+
+ /**
+ * Gets current value of property "text".
+ *
+ * @returns Value of property "text"
+ */
+ getText(): string;
+
+ /**
+ * Sets a new value for property "text".
+ *
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param text New value for property "text"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setText(text: string): this;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.ts b/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.ts
new file mode 100644
index 00000000..ae2e29ef
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.ts
@@ -0,0 +1,25 @@
+import Control from "sap/ui/core/Control";
+import { MetadataOptions } from "sap/ui/core/Element";
+import RenderManager from "sap/ui/core/RenderManager";
+
+/**
+ * @namespace my
+ */
+export default class MyControl extends Control {
+ static readonly metadata: MetadataOptions = {
+ properties: {
+ text: "string",
+ },
+ };
+
+ static renderer = {
+ apiVersion: 2,
+ render: function (rm: RenderManager, control: MyControl) {
+ rm.openStart("div", control);
+ rm.openEnd();
+ // @ts-ignore this only works with the generated interface
+ rm.text(control.getText());
+ rm.close("div");
+ },
+ };
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts b/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts
new file mode 100644
index 00000000..d5deeb3c
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts
@@ -0,0 +1,96 @@
+import fs from "fs";
+import path from "path";
+import ts from "typescript";
+import log from "loglevel";
+import { generateInterfaces } from "../../interfaceGenerationHelper";
+import { getAllKnownGlobals, GlobalToModuleMapping } from "../../typeScriptEnvironment";
+
+const testCasesDir = path.resolve(__dirname);
+
+const standardTsConfig: ts.CompilerOptions = {
+ target: ts.ScriptTarget.ES2022,
+ module: ts.ModuleKind.CommonJS,
+ strict: true,
+ moduleResolution: ts.ModuleResolutionKind.Node16,
+ esModuleInterop: true,
+ skipLibCheck: true,
+ forceConsistentCasingInFileNames: true,
+};
+
+describe("Single Testcases", () => {
+ beforeAll(() => {
+ jest.spyOn(log, "warn").mockImplementation(() => {});
+ });
+
+ afterAll(() => {
+ // Restore the original console.warn method
+ jest.restoreAllMocks();
+ });
+
+ fs.readdirSync(testCasesDir).forEach((testCase) => {
+ const testCaseDir = path.join(testCasesDir, testCase);
+ // abort if not a directory
+ if (!fs.lstatSync(testCaseDir).isDirectory()) {
+ return;
+ }
+
+ test(`Interface generation for ${testCase}`, async () => {
+ // setup TypeScript program
+ const tsConfigPath = path.join(testCaseDir, "tsconfig.json");
+ const tsFiles = fs
+ .readdirSync(testCaseDir)
+ .filter((file) => file.endsWith(".ts") && !file.endsWith(".d.ts"))
+ .map((file) => path.join(testCaseDir, file));
+
+ const tsConfig = fs.existsSync(tsConfigPath)
+ ? { configFilePath: tsConfigPath }
+ : standardTsConfig;
+ const program = ts.createProgram(tsFiles, tsConfig);
+ const typeChecker = program.getTypeChecker();
+ const allKnownGlobals: GlobalToModuleMapping = getAllKnownGlobals(typeChecker);
+
+ const sourceFiles = program.getSourceFiles().filter((sourceFile) => {
+ return !sourceFile.isDeclarationFile;
+ });
+
+ const runGenerateInterfaces = async (
+ sourceFile: ts.SourceFile,
+ ): Promise => {
+ return new Promise((resolve) => {
+ const resultProcessor = (
+ sourceFileName: string,
+ className: string,
+ interfaceText: string,
+ ) => {
+ resolve(interfaceText);
+ };
+
+ generateInterfaces(
+ sourceFile,
+ typeChecker,
+ allKnownGlobals,
+ resultProcessor,
+ );
+ });
+ };
+
+ for (const sourceFile of sourceFiles) {
+ const generatedInterfaces = await runGenerateInterfaces(sourceFile);
+
+ const expectedOutputPath = sourceFile.fileName.replace(
+ /\.ts$/,
+ ".gen.d.ts",
+ );
+
+ if (!fs.existsSync(expectedOutputPath)) {
+ // write the generated output to the file if it does not exist
+ fs.writeFileSync(expectedOutputPath, generatedInterfaces);
+ console.log(`Generated output written to ${expectedOutputPath}`);
+ } else {
+ const expectedOutput = fs.readFileSync(expectedOutputPath, "utf-8");
+ expect(generatedInterfaces).toEqual(expectedOutput);
+ }
+ }
+ }, 15000); // typical run takes a second, so increase the 5000 ms default timeout to be safe
+ });
+});
diff --git a/packages/ts-interface-generator/src/test/testcases/type-used-in-api/MyControl.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/type-used-in-api/MyControl.gen.d.ts
new file mode 100644
index 00000000..71431719
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/type-used-in-api/MyControl.gen.d.ts
@@ -0,0 +1,64 @@
+import { $ControlSettings } from "sap/ui/core/Control";
+
+declare module "./MyControl" {
+
+ /**
+ * Interface defining the settings object used in constructor calls
+ */
+ interface $MyControlSettings extends $ControlSettings {
+ otherControl?: MyControl;
+ }
+
+ export default interface MyControl {
+
+ // aggregation: otherControl
+
+ /**
+ * Gets content of aggregation "otherControl".
+ */
+ getOtherControl(): MyControl;
+
+ /**
+ * Sets the aggregated otherControl.
+ *
+ * @param otherControl The otherControl to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setOtherControl(otherControl: MyControl): this;
+
+ /**
+ * Destroys the otherControl in the aggregation "otherControl".
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyOtherControl(): this;
+ }
+}
+
+// this duplicate interface without export is needed to avoid "Cannot find name 'MyControl'" TypeScript errors above
+declare module "./MyControl" {
+ interface MyControl {
+
+ // aggregation: otherControl
+
+ /**
+ * Gets content of aggregation "otherControl".
+ */
+ getOtherControl(): MyControl;
+
+ /**
+ * Sets the aggregated otherControl.
+ *
+ * @param otherControl The otherControl to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setOtherControl(otherControl: MyControl): this;
+
+ /**
+ * Destroys the otherControl in the aggregation "otherControl".
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyOtherControl(): this;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/type-used-in-api/MyControl.ts b/packages/ts-interface-generator/src/test/testcases/type-used-in-api/MyControl.ts
new file mode 100644
index 00000000..572aed9e
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/type-used-in-api/MyControl.ts
@@ -0,0 +1,27 @@
+import Control from "sap/ui/core/Control";
+import { MetadataOptions } from "sap/ui/core/Element";
+import RenderManager from "sap/ui/core/RenderManager";
+
+/**
+ * This is my control.
+ *
+ * @namespace my
+ */
+export default class MyControl extends Control {
+ static readonly metadata: MetadataOptions = {
+ aggregations: {
+ otherControl: { multiple: false, type: "MyControl" },
+ },
+ };
+
+ static renderer = {
+ apiVersion: 2,
+ render: function (rm: RenderManager, control: MyControl) {
+ rm.openStart("div", control);
+ rm.openEnd();
+ // @ts-ignore this only works with the generated interface
+ rm.renderControl(control.getOtherControl());
+ rm.close("div");
+ },
+ };
+}
diff --git a/packages/ts-interface-generator/src/test/testdata/sampleWebComponent/SampleWebComponent.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/web-component/SampleWebComponent.gen.d.ts
similarity index 100%
rename from packages/ts-interface-generator/src/test/testdata/sampleWebComponent/SampleWebComponent.gen.d.ts
rename to packages/ts-interface-generator/src/test/testcases/web-component/SampleWebComponent.gen.d.ts
diff --git a/packages/ts-interface-generator/src/test/testcases/web-component/SampleWebComponent.ts b/packages/ts-interface-generator/src/test/testcases/web-component/SampleWebComponent.ts
new file mode 100644
index 00000000..a085e562
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/web-component/SampleWebComponent.ts
@@ -0,0 +1,92 @@
+import WebComponent, { MetadataOptions } from "sap/ui/core/webc/WebComponent";
+
+/**
+ * A SampleWebComponent is a control wrapper for a Web Component and this is its documentation.
+ *
+ * @namespace ui5tssampleapp.control
+ */
+export default class SampleWebComponent extends WebComponent {
+ // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures
+ constructor(idOrSettings?: string | $SampleWebComponentSettings);
+ constructor(id?: string, settings?: $SampleWebComponentSettings);
+ constructor(id?: string, settings?: $SampleWebComponentSettings) {
+ super(id, settings);
+ }
+
+ static readonly metadata: MetadataOptions = {
+ tag: "sample-webcomponent",
+ properties: {
+ /**
+ * The text that appears below the main text.
+ * @since 1.0
+ */
+ subtext: "string",
+
+ /**
+ * Determines the text color of the SampleWebComponent
.
+ *
+ * @experimental
+ */
+ textColor: { type: "sap.ui.core.CSSColor", defaultValue: "" },
+
+ /**
+ * Usage of mapping
+ */
+ text: {
+ type: "string",
+ mapping: {
+ type: "textContent",
+ },
+ },
+ },
+ aggregations: {
+ /**
+ * Determines the content of the SampleWebComponent
.
+ */
+ content: { multiple: true, type: "sap.ui.core.Control", bindable: true },
+ header: { multiple: false, type: "sap.ui.core.webc.WebComponent" },
+ tooltip: {
+ multiple: false,
+ type: "sap.ui.core.TooltipBase",
+ altTypes: ["string"],
+ },
+ },
+ defaultAggregation: "content",
+ associations: {
+ partnerControl: "SampleWebComponent",
+ /**
+ * This is an association.
+ */
+ alsoLabelledBy: { type: "sap.ui.core.Control", multiple: true },
+ },
+ events: {
+ /**
+ * Fired when double-clicked.
+ */
+ doublePress: { allowPreventDefault: true },
+ },
+ methods: ["somePublicMethod"],
+ getters: ["somePublicGetter"],
+ };
+}
+
+/**
+ * implement the methods and getters
+ */
+declare module "./SampleWebComponent" {
+ export default interface SampleWebComponent {
+ /**
+ * Some public method returning a "string"
+ *
+ * @since 1.0
+ *
+ * @returns Value of property "subtext"
+ */
+ somePublicMethod(): string;
+
+ /**
+ * Some public getter being a "string"
+ */
+ somePublicGetter: string;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/xl-control-with-all-features/SampleControl.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/xl-control-with-all-features/SampleControl.gen.d.ts
new file mode 100644
index 00000000..fbc4b911
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/xl-control-with-all-features/SampleControl.gen.d.ts
@@ -0,0 +1,856 @@
+import Event from "sap/ui/base/Event";
+import { CSSColor } from "sap/ui/core/library";
+import Control from "sap/ui/core/Control";
+import { AggregationBindingInfo } from "sap/ui/base/ManagedObject";
+import TooltipBase from "sap/ui/core/TooltipBase";
+import { PropertyBindingInfo } from "sap/ui/base/ManagedObject";
+import { $ButtonSettings } from "sap/m/Button";
+
+declare module "./SampleControl" {
+
+ /**
+ * Interface defining the settings object used in constructor calls
+ */
+ interface $SampleControlSettings extends $ButtonSettings {
+
+ /**
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ */
+ subtext?: string | PropertyBindingInfo;
+
+ /**
+ * Determines the text color of the SampleControl
.
+ *
+ * @experimental
+ */
+ textColor?: CSSColor | PropertyBindingInfo | `{${string}}`;
+
+ /**
+ * Determines the content of the SampleControl
.
+ */
+ content?: Control[] | Control | AggregationBindingInfo | `{${string}}`;
+
+ /**
+ * The header - there can be only one
+ */
+ header?: Control;
+
+ /**
+ * The tooltip - either a string or a TooltipBase
+ */
+ tooltip?: TooltipBase | string | PropertyBindingInfo;
+
+ /**
+ * Another control belonging to this one
+ */
+ partnerControl?: SampleControl | string;
+
+ /**
+ * This is an association.
+ */
+ alsoLabelledBy?: Control | string | (Control | string)[];
+
+ /**
+ * Fired when single-clicked. This event has no parameters.
+ */
+ singlePress?: (event: SampleControl$SinglePressEvent) => void;
+
+ /**
+ * Fired when double-clicked.
+ */
+ doublePress?: (event: SampleControl$DoublePressEvent) => void;
+ }
+
+ export default interface SampleControl {
+
+ // property: subtext
+
+ /**
+ * Gets current value of property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ *
+ * @returns Value of property "subtext"
+ */
+ getSubtext(): string;
+
+ /**
+ * Sets a new value for property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param subtext New value for property "subtext"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setSubtext(subtext: string): this;
+
+ // property: textColor
+
+ /**
+ * Gets current value of property "textColor".
+ *
+ * Determines the text color of the SampleControl
.
+ *
+ * @experimental
+ * Default value is: ""
+ * @returns Value of property "textColor"
+ */
+ getTextColor(): CSSColor;
+
+ /**
+ * Sets a new value for property "textColor".
+ *
+ * Determines the text color of the SampleControl
.
+ *
+ * @experimental
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * Default value is: ""
+ * @param [textColor=""] New value for property "textColor"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTextColor(textColor: CSSColor): this;
+
+ // aggregation: content
+
+ /**
+ * Gets content of aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ */
+ getContent(): Control[];
+
+ /**
+ * Adds some content to the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addContent(content: Control): this;
+
+ /**
+ * Inserts a content into the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content to insert; if empty, nothing is inserted
+ * @param index The "0"-based index the content should be inserted at; for
+ * a negative value of "iIndex", the content is inserted at position 0; for a value
+ * greater than the current size of the aggregation, the content is inserted at
+ * the last position
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ insertContent(content: Control, index: number): this;
+
+ /**
+ * Removes a content from the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content to remove or its index or id
+ * @returns The removed content or "null"
+ */
+ removeContent(content: number | string | Control): Control | null;
+
+ /**
+ * Removes all the controls from the aggregation "content".
+ * Additionally, it unregisters them from the hosting UIArea.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllContent(): Control[];
+
+ /**
+ * Checks for the provided "sap.ui.core.Control" in the aggregation "content".
+ * and returns its index if found or -1 otherwise.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content whose index is looked for
+ * @returns The index of the provided control in the aggregation if found, or -1 otherwise
+ */
+ indexOfContent(content: Control): number;
+
+ /**
+ * Destroys all the content in the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyContent(): this;
+
+ /**
+ * Binds aggregation "content" to model data.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * See {@link sap.ui.base.ManagedObject#bindAggregation ManagedObject.bindAggregation} for a
+ * detailed description of the possible properties of "oBindingInfo".
+ * @param oBindingInfo The binding information
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ bindContent(bindingInfo: AggregationBindingInfo): this;
+
+ /**
+ * Unbinds aggregation "content" from model data.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ unbindContent(): this;
+
+ // aggregation: header
+
+ /**
+ * Gets content of aggregation "header".
+ *
+ * The header - there can be only one
+ */
+ getHeader(): Control;
+
+ /**
+ * Sets the aggregated header.
+ *
+ * The header - there can be only one
+ *
+ * @param header The header to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setHeader(header: Control): this;
+
+ /**
+ * Destroys the header in the aggregation "header".
+ *
+ * The header - there can be only one
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyHeader(): this;
+
+ // aggregation: tooltip
+
+ /**
+ * Gets content of aggregation "tooltip".
+ *
+ * The tooltip - either a string or a TooltipBase
+ */
+ getTooltip(): TooltipBase;
+
+ /**
+ * Sets the aggregated tooltip.
+ *
+ * The tooltip - either a string or a TooltipBase
+ *
+ * @param tooltip The tooltip to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTooltip(tooltip: TooltipBase): this;
+
+ /**
+ * Destroys the tooltip in the aggregation "tooltip".
+ *
+ * The tooltip - either a string or a TooltipBase
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyTooltip(): this;
+
+ // association: partnerControl
+
+ /**
+ * ID of the element which is the current target of the association "partnerControl", or "null".
+ *
+ * Another control belonging to this one
+ */
+ getPartnerControl(): string;
+
+ /**
+ * Sets the associated partnerControl.
+ *
+ * Another control belonging to this one
+ *
+ * @param partnerControl ID of an element which becomes the new target of this "partnerControl" association; alternatively, an element instance may be given
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setPartnerControl(partnerControl?: string | SampleControl): this;
+
+ // association: alsoLabelledBy
+
+ /**
+ * Returns array of IDs of the elements which are the current targets of the association "alsoLabelledBy".
+ *
+ * This is an association.
+ */
+ getAlsoLabelledBy(): string[];
+
+ /**
+ * Adds some alsoLabelledBy into the association "alsoLabelledBy".
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addAlsoLabelledBy(alsoLabelledBy: string | Control): this;
+
+ /**
+ * Removes an alsoLabelledBy from the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to be removed or its index or ID
+ * @returns The removed alsoLabelledBy or "null"
+ */
+ removeAlsoLabelledBy(alsoLabelledBy: number | string | Control): string;
+
+ /**
+ * Removes all the controls in the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllAlsoLabelledBy(): string[];
+
+ // event: singlePress
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(data: CustomDataType, fn: (event: SampleControl$SinglePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "singlePress" to attached listeners.
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ fireSinglePress(parameters?: SampleControl$SinglePressEventParameters): this;
+
+ // event: doublePress
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleControl".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(fn: (event: SampleControl$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleControl".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(data: CustomDataType, fn: (event: SampleControl$DoublePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "doublePress" event of this "SampleControl".
+ *
+ * Fired when double-clicked.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachDoublePress(fn: (event: SampleControl$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "doublePress" to attached listeners.
+ *
+ * Fired when double-clicked.
+ *
+ * Listeners may prevent the default action of this event by calling the "preventDefault" method on the event object.
+ * The return value of this method indicates whether the default action should be executed.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @param [mParameters.delay] Fired when double-clicked.
+ *
+ * @returns Whether or not to prevent the default action
+ */
+ fireDoublePress(parameters?: SampleControl$DoublePressEventParameters): boolean;
+ }
+
+ /**
+ * Interface describing the parameters of SampleControl's 'singlePress' event.
+ * Fired when single-clicked. This event has no parameters.
+ */
+ // eslint-disable-next-line
+ export interface SampleControl$SinglePressEventParameters {
+ }
+
+ /**
+ * Interface describing the parameters of SampleControl's 'doublePress' event.
+ * Fired when double-clicked.
+ */
+ export interface SampleControl$DoublePressEventParameters {
+ delay?: number;
+ }
+
+ /**
+ * Type describing the SampleControl's 'singlePress' event.
+ * Fired when single-clicked. This event has no parameters.
+ */
+ export type SampleControl$SinglePressEvent = Event;
+
+ /**
+ * Type describing the SampleControl's 'doublePress' event.
+ * Fired when double-clicked.
+ */
+ export type SampleControl$DoublePressEvent = Event;
+}
+
+// this duplicate interface without export is needed to avoid "Cannot find name 'SampleControl'" TypeScript errors above
+declare module "./SampleControl" {
+ interface SampleControl {
+
+ // property: subtext
+
+ /**
+ * Gets current value of property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ *
+ * @returns Value of property "subtext"
+ */
+ getSubtext(): string;
+
+ /**
+ * Sets a new value for property "subtext".
+ *
+ * The text that appears below the main text.
+ *
+ * @since 1.0
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * @param subtext New value for property "subtext"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setSubtext(subtext: string): this;
+
+ // property: textColor
+
+ /**
+ * Gets current value of property "textColor".
+ *
+ * Determines the text color of the SampleControl
.
+ *
+ * @experimental
+ * Default value is: ""
+ * @returns Value of property "textColor"
+ */
+ getTextColor(): CSSColor;
+
+ /**
+ * Sets a new value for property "textColor".
+ *
+ * Determines the text color of the SampleControl
.
+ *
+ * @experimental
+ * When called with a value of "null" or "undefined", the default value of the property will be restored.
+ *
+ * Default value is: ""
+ * @param [textColor=""] New value for property "textColor"
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTextColor(textColor: CSSColor): this;
+
+ // aggregation: content
+
+ /**
+ * Gets content of aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ */
+ getContent(): Control[];
+
+ /**
+ * Adds some content to the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addContent(content: Control): this;
+
+ /**
+ * Inserts a content into the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content to insert; if empty, nothing is inserted
+ * @param index The "0"-based index the content should be inserted at; for
+ * a negative value of "iIndex", the content is inserted at position 0; for a value
+ * greater than the current size of the aggregation, the content is inserted at
+ * the last position
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ insertContent(content: Control, index: number): this;
+
+ /**
+ * Removes a content from the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content to remove or its index or id
+ * @returns The removed content or "null"
+ */
+ removeContent(content: number | string | Control): Control | null;
+
+ /**
+ * Removes all the controls from the aggregation "content".
+ * Additionally, it unregisters them from the hosting UIArea.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllContent(): Control[];
+
+ /**
+ * Checks for the provided "sap.ui.core.Control" in the aggregation "content".
+ * and returns its index if found or -1 otherwise.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @param content The content whose index is looked for
+ * @returns The index of the provided control in the aggregation if found, or -1 otherwise
+ */
+ indexOfContent(content: Control): number;
+
+ /**
+ * Destroys all the content in the aggregation "content".
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyContent(): this;
+
+ /**
+ * Binds aggregation "content" to model data.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * See {@link sap.ui.base.ManagedObject#bindAggregation ManagedObject.bindAggregation} for a
+ * detailed description of the possible properties of "oBindingInfo".
+ * @param oBindingInfo The binding information
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ bindContent(bindingInfo: AggregationBindingInfo): this;
+
+ /**
+ * Unbinds aggregation "content" from model data.
+ *
+ * Determines the content of the SampleControl
.
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ unbindContent(): this;
+
+ // aggregation: header
+
+ /**
+ * Gets content of aggregation "header".
+ *
+ * The header - there can be only one
+ */
+ getHeader(): Control;
+
+ /**
+ * Sets the aggregated header.
+ *
+ * The header - there can be only one
+ *
+ * @param header The header to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setHeader(header: Control): this;
+
+ /**
+ * Destroys the header in the aggregation "header".
+ *
+ * The header - there can be only one
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyHeader(): this;
+
+ // aggregation: tooltip
+
+ /**
+ * Gets content of aggregation "tooltip".
+ *
+ * The tooltip - either a string or a TooltipBase
+ */
+ getTooltip(): TooltipBase;
+
+ /**
+ * Sets the aggregated tooltip.
+ *
+ * The tooltip - either a string or a TooltipBase
+ *
+ * @param tooltip The tooltip to set
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setTooltip(tooltip: TooltipBase): this;
+
+ /**
+ * Destroys the tooltip in the aggregation "tooltip".
+ *
+ * The tooltip - either a string or a TooltipBase
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ destroyTooltip(): this;
+
+ // association: partnerControl
+
+ /**
+ * ID of the element which is the current target of the association "partnerControl", or "null".
+ *
+ * Another control belonging to this one
+ */
+ getPartnerControl(): string;
+
+ /**
+ * Sets the associated partnerControl.
+ *
+ * Another control belonging to this one
+ *
+ * @param partnerControl ID of an element which becomes the new target of this "partnerControl" association; alternatively, an element instance may be given
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ setPartnerControl(partnerControl?: string | SampleControl): this;
+
+ // association: alsoLabelledBy
+
+ /**
+ * Returns array of IDs of the elements which are the current targets of the association "alsoLabelledBy".
+ *
+ * This is an association.
+ */
+ getAlsoLabelledBy(): string[];
+
+ /**
+ * Adds some alsoLabelledBy into the association "alsoLabelledBy".
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to add; if empty, nothing is inserted
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ addAlsoLabelledBy(alsoLabelledBy: string | Control): this;
+
+ /**
+ * Removes an alsoLabelledBy from the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @param alsoLabelledBy The alsoLabelledBy to be removed or its index or ID
+ * @returns The removed alsoLabelledBy or "null"
+ */
+ removeAlsoLabelledBy(alsoLabelledBy: number | string | Control): string;
+
+ /**
+ * Removes all the controls in the association named alsoLabelledBy.
+ *
+ * This is an association.
+ *
+ * @returns An array of the removed elements (might be empty)
+ */
+ removeAllAlsoLabelledBy(): string[];
+
+ // event: singlePress
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachSinglePress(data: CustomDataType, fn: (event: SampleControl$SinglePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "singlePress" event of this "SampleControl".
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachSinglePress(fn: (event: SampleControl$SinglePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "singlePress" to attached listeners.
+ *
+ * Fired when single-clicked. This event has no parameters.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ fireSinglePress(parameters?: SampleControl$SinglePressEventParameters): this;
+
+ // event: doublePress
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleControl".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(fn: (event: SampleControl$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Attaches event handler "fn" to the "doublePress" event of this "SampleControl".
+ *
+ * Fired when double-clicked.
+ *
+ * When called, the context of the event handler (its "this") will be bound to "oListener" if specified,
+ * otherwise it will be bound to this "SampleControl" itself.
+ *
+ * @param data An application-specific payload object that will be passed to the event handler along with the event object when firing the event
+ * @param fn The function to be called when the event occurs
+ * @param listener Context object to call the event handler with. Defaults to this "SampleControl" itself
+ *
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ attachDoublePress(data: CustomDataType, fn: (event: SampleControl$DoublePressEvent, data: CustomDataType) => void, listener?: object): this;
+
+ /**
+ * Detaches event handler "fn" from the "doublePress" event of this "SampleControl".
+ *
+ * Fired when double-clicked.
+ *
+ * The passed function and listener object must match the ones used for event registration.
+ *
+ * @param fn The function to be called, when the event occurs
+ * @param listener Context object on which the given function had to be called
+ * @returns Reference to "this" in order to allow method chaining
+ */
+ detachDoublePress(fn: (event: SampleControl$DoublePressEvent) => void, listener?: object): this;
+
+ /**
+ * Fires event "doublePress" to attached listeners.
+ *
+ * Fired when double-clicked.
+ *
+ * Listeners may prevent the default action of this event by calling the "preventDefault" method on the event object.
+ * The return value of this method indicates whether the default action should be executed.
+ *
+ * @param parameters Parameters to pass along with the event
+ * @param [mParameters.delay] Fired when double-clicked.
+ *
+ * @returns Whether or not to prevent the default action
+ */
+ fireDoublePress(parameters?: SampleControl$DoublePressEventParameters): boolean;
+ }
+}
diff --git a/packages/ts-interface-generator/src/test/testcases/xl-control-with-all-features/SampleControl.ts b/packages/ts-interface-generator/src/test/testcases/xl-control-with-all-features/SampleControl.ts
new file mode 100644
index 00000000..e2e24628
--- /dev/null
+++ b/packages/ts-interface-generator/src/test/testcases/xl-control-with-all-features/SampleControl.ts
@@ -0,0 +1,102 @@
+import Button from "sap/m/Button";
+import { MetadataOptions } from "sap/ui/core/Element";
+import RenderManager from "sap/ui/core/RenderManager";
+
+/**
+ * A SampleControl is a control and this is its documentation.
+ *
+ * @namespace ui5tssampleapp.control
+ */
+export default class SampleControl extends Button {
+
+ // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures
+ constructor(idOrSettings?: string | $SampleControlSettings);
+ constructor(id?: string, settings?: $SampleControlSettings);
+ constructor(id?: string, settings?: $SampleControlSettings) { super(id, settings); }
+
+ static readonly metadata: MetadataOptions = {
+ properties: {
+ /**
+ * The text that appears below the main text.
+ * @since 1.0
+ */
+ subtext: "string",
+
+ /**
+ * Determines the text color of the SampleControl
.
+ *
+ * @experimental
+ */
+ textColor: { type: "sap.ui.core.CSSColor", defaultValue: "" },
+ },
+ aggregations: {
+ /**
+ * Determines the content of the SampleControl
.
+ */
+ content: { multiple: true, type: "sap.ui.core.Control", bindable: true },
+ /**
+ * The header - there can be only one
+ */
+ header: { multiple: false, type: "sap.ui.core.Control" },
+ /**
+ * The tooltip - either a string or a TooltipBase
+ */
+ tooltip: { multiple: false, type: "sap.ui.core.TooltipBase", altTypes : ["string"]}
+ },
+ defaultAggregation: "content",
+ associations: {
+ /**
+ * Another control belonging to this one
+ */
+ partnerControl: "SampleControl",
+ /**
+ * This is an association.
+ */
+ alsoLabelledBy: { type: "sap.ui.core.Control", multiple: true },
+ },
+ events: {
+ /**
+ * Fired when single-clicked. This event has no parameters.
+ */
+ singlePress: {},
+ /**
+ * Fired when double-clicked.
+ */
+ doublePress: {
+ allowPreventDefault: true,
+ parameters: {
+ /**
+ * The amount of milliseconds between the first and second press
+ */
+ delay: { type: "int" }
+ }
+ }
+ },
+ };
+
+ static renderer = {
+ apiVersion: 2,
+ render: function (rm: RenderManager, control: SampleControl) {
+ rm.openStart("div", control);
+ rm.openEnd();
+
+ rm.text(control.getText());
+ // @ts-ignore this only works with the generated interface
+ rm.text(control.getSubtext());
+ // @ts-ignore this only works with the generated interface
+ const content = control.getContent();
+ for (let i = 0; i < content.length; i++) {
+ rm.renderControl(content[i]);
+ }
+ rm.close("div");
+ }
+ };
+
+ doit() {
+ this.fireDoublePress({
+ delay: 100
+ });
+ this.fireSinglePress();
+ alert("Hello");
+ }
+}
diff --git a/packages/ts-interface-generator/src/typeScriptEnvironment.ts b/packages/ts-interface-generator/src/typeScriptEnvironment.ts
index bd8b270a..1717cf01 100644
--- a/packages/ts-interface-generator/src/typeScriptEnvironment.ts
+++ b/packages/ts-interface-generator/src/typeScriptEnvironment.ts
@@ -13,6 +13,10 @@ import log from "loglevel";
*
*/
+interface GlobalToModuleMapping {
+ [key: string]: { moduleName: string; exportName?: string };
+}
+
type TSProgramUpdateCallback = (
program: ts.Program,
typeChecker: ts.TypeChecker,
@@ -192,6 +196,16 @@ function onProgramChanged(
) {
const program = builderProgram.getProgram();
const typeChecker = program.getTypeChecker();
+ const allKnownGlobals: GlobalToModuleMapping =
+ getAllKnownGlobals(typeChecker);
+
+ // call the callback
+ onTSProgramUpdate(program, typeChecker, changedFiles, allKnownGlobals);
+}
+
+function getAllKnownGlobals(
+ typeChecker: ts.TypeChecker,
+): GlobalToModuleMapping {
const allKnownGlobals: GlobalToModuleMapping = {};
// build a map of all known modules declared in the d.ts files (and elsewhere) along with their respective exports (so we can correctly identify enums which do not live in a module on their own)
@@ -215,11 +229,9 @@ function onProgramChanged(
}
allKnownGlobals[globalName] = entry;
});
- //allKnownModules[mod.name] = exports;
});
- // call the callback
- onTSProgramUpdate(program, typeChecker, changedFiles, allKnownGlobals);
+ return allKnownGlobals;
}
-export { initialize };
+export { initialize, getAllKnownGlobals, GlobalToModuleMapping };
diff --git a/packages/ts-interface-generator/src/types.d.ts b/packages/ts-interface-generator/src/types.d.ts
index e433c0d0..d8f96d1b 100644
--- a/packages/ts-interface-generator/src/types.d.ts
+++ b/packages/ts-interface-generator/src/types.d.ts
@@ -6,6 +6,7 @@ interface ManagedObjectInfo {
sourceFile: ts.SourceFile;
className: string;
classDeclaration: ts.ClassDeclaration;
+ isDefaultExport: boolean;
settingsTypeFullName: string;
interestingBaseClass:
| "ManagedObject"
diff --git a/packages/ts-interface-generator/tsconfig-testcontrol.json b/packages/ts-interface-generator/tsconfig-testcontrol.json
index 0480c420..8f290719 100644
--- a/packages/ts-interface-generator/tsconfig-testcontrol.json
+++ b/packages/ts-interface-generator/tsconfig-testcontrol.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "rootDirs": ["./src/test/testdata/sampleControl"],
+ "rootDirs": ["./src/test/samples/sampleControl"],
"outDir": "./src/test/dist",
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
@@ -12,5 +12,5 @@
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
- "include": ["./src/test/testdata/sampleControl/**/*"]
+ "include": ["./src/test/samples/sampleControl/**/*"]
}
diff --git a/packages/ts-interface-generator/tsconfig-testmanagedobject.json b/packages/ts-interface-generator/tsconfig-testmanagedobject.json
index 6a5e7609..9a5a2c52 100644
--- a/packages/ts-interface-generator/tsconfig-testmanagedobject.json
+++ b/packages/ts-interface-generator/tsconfig-testmanagedobject.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "rootDirs": ["./src/test/testdata/sampleManagedObject"],
+ "rootDirs": ["./src/test/samples/sampleManagedObject"],
"outDir": "./src/test/dist",
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
@@ -12,5 +12,5 @@
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
- "include": ["./src/test/testdata/sampleManagedObject/**/*"]
+ "include": ["./src/test/samples/sampleManagedObject/**/*"]
}
diff --git a/packages/ts-interface-generator/tsconfig-testwebcomponent.json b/packages/ts-interface-generator/tsconfig-testwebcomponent.json
index 4cfcc965..d1511e89 100644
--- a/packages/ts-interface-generator/tsconfig-testwebcomponent.json
+++ b/packages/ts-interface-generator/tsconfig-testwebcomponent.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "rootDirs": ["./src/test/testdata/sampleWebComponent"],
+ "rootDirs": ["./src/test/samples/sampleWebComponent"],
"outDir": "./src/test/dist",
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
@@ -12,5 +12,5 @@
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
- "include": ["./src/test/testdata/sampleWebComponent/**/*"]
+ "include": ["./src/test/samples/sampleWebComponent/**/*"]
}