From 5239c0dbe9d7aa0fe0016b2fd9482c1511025f46 Mon Sep 17 00:00:00 2001 From: Daniel Lang Date: Wed, 25 May 2022 15:30:28 +0200 Subject: [PATCH 1/5] feat(ts-interface-generator): handeling generic type definitions fix(ts-interface-generator): handling for non-default export ManagedObjects --- .../src/astGenerationHelper.ts | 115 ++++++++++++++++++ .../src/interfaceGenerationHelper.ts | 33 +++-- .../generateInterfaceWithGenerics.test.ts | 72 +++++++++++ ...generateInterfaceWithTypedGenerics.test.ts | 75 ++++++++++++ .../simple/SampleGenericManagedObject.ts | 17 +++ .../SampleGenericManagedObject.ts | 26 ++++ .../tsconfig-testgenerics-typed.json | 22 ++++ .../tsconfig-testgenerics.json | 22 ++++ 8 files changed, 375 insertions(+), 7 deletions(-) create mode 100644 packages/ts-interface-generator/src/test/generateInterfaceWithGenerics.test.ts create mode 100644 packages/ts-interface-generator/src/test/generateInterfaceWithTypedGenerics.test.ts create mode 100644 packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts create mode 100644 packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts create mode 100644 packages/ts-interface-generator/tsconfig-testgenerics-typed.json create mode 100644 packages/ts-interface-generator/tsconfig-testgenerics.json diff --git a/packages/ts-interface-generator/src/astGenerationHelper.ts b/packages/ts-interface-generator/src/astGenerationHelper.ts index 607fbea7..750913e7 100644 --- a/packages/ts-interface-generator/src/astGenerationHelper.ts +++ b/packages/ts-interface-generator/src/astGenerationHelper.ts @@ -268,6 +268,120 @@ function createBindingStringTypeNode() { ); } +function generateGenericTypeImports( + sourceFile: ts.SourceFile, + classDeclaration: ts.ClassDeclaration, + statements: ts.Statement[], + requiredImports: RequiredImports +): ts.Statement[] { + const { typeParameters } = classDeclaration; + const requiredGenericTypeImports: ts.Statement[] = []; + + if (!typeParameters || typeParameters?.length === 0) { + return requiredGenericTypeImports; + } + + const existingImportsInSourceFile: { + [name: string]: { statement: ts.ImportDeclaration; exportName?: string }; + } = {}; + + for (const statement of sourceFile.statements) { + if (ts.isImportDeclaration(statement)) { + if (statement.importClause) { + const { name, namedBindings } = statement.importClause; + + if (name) { + existingImportsInSourceFile[name.getText()] = { statement }; + } + + if (namedBindings) { + namedBindings.forEachChild((node) => { + if (ts.isImportSpecifier(node)) { + const typeName = node.name.getText(); + let exportName = typeName; + + if (node.propertyName) { + exportName = node.propertyName.getText(); + } + + existingImportsInSourceFile[typeName] = { statement, exportName }; + } + }); + } + } + } + } + + for (const typeParameter of typeParameters) { + if (typeParameter.constraint) { + const typeName = typeParameter.constraint.getText(); + + if (nameIsUsed(typeName, requiredImports)) { + // import is already created + continue; + } else if (existingImportsInSourceFile.hasOwnProperty(typeName)) { + const { statement, exportName } = existingImportsInSourceFile[typeName]; + + const moduleName = statement.moduleSpecifier.getText(); + const moduleSpecifierClone = factory.createStringLiteral( + moduleName.substring(1, moduleName.length - 1) + ); + + let importClause: ts.ImportClause; + const typeNameIdentifier = factory.createIdentifier(typeName); + + if (!exportName) { + importClause = factory.createImportClause( + true, + typeNameIdentifier, + undefined + ); + } else { + const propertyName = + typeName !== exportName + ? factory.createIdentifier(exportName) + : undefined; + + let importSpecifier: ts.ImportSpecifier; + + // TODO: Use a method to check for versions + if (parseFloat(ts.version) >= 4.5) { + importSpecifier = factory.createImportSpecifier( + true, + propertyName, + typeNameIdentifier + ); + } else { + // @ts-ignore + importSpecifier = factory.createImportSpecifier( + propertyName, + typeNameIdentifier + ); + } + + importClause = factory.createImportClause( + false, + undefined, + factory.createNamedImports([importSpecifier]) + ); + } + + const clone = factory.createImportDeclaration( + statement.decorators, + statement.modifiers, + importClause, + moduleSpecifierClone, + statement.assertClause + ); + + requiredGenericTypeImports.push(clone); + } + } + } + + return requiredGenericTypeImports; +} + function printConstructorBlockWarning( settingsTypeName: string, className: string, @@ -1161,6 +1275,7 @@ function createConstructorBlock(settingsTypeName: string) { export { generateMethods, generateSettingsInterface, + generateGenericTypeImports, addLineBreakBefore, createConstructorBlock, }; diff --git a/packages/ts-interface-generator/src/interfaceGenerationHelper.ts b/packages/ts-interface-generator/src/interfaceGenerationHelper.ts index 7749eb3c..0b9a9c59 100644 --- a/packages/ts-interface-generator/src/interfaceGenerationHelper.ts +++ b/packages/ts-interface-generator/src/interfaceGenerationHelper.ts @@ -7,6 +7,7 @@ import { generateMethods, generateSettingsInterface, addLineBreakBefore, + generateGenericTypeImports, } from "./astGenerationHelper"; import astToString from "./astToString"; import log from "loglevel"; @@ -43,6 +44,8 @@ const interestingBaseSettingsClasses: { '"sap/ui/core/Control".$ControlSettings': "$ControlSettings", }; +const interfaceIncompatibleModifiers = new Set([ts.SyntaxKind.AbstractKeyword]); + /** * Checks the given source file for any classes derived from sap.ui.base.ManagedObject and generates for each one an interface file next to the source file * with the name .gen.d.ts @@ -424,6 +427,7 @@ function generateInterface( { sourceFile, className, + classDeclaration, settingsTypeFullName, interestingBaseClass, constructorSignaturesAvailable, @@ -431,6 +435,7 @@ function generateInterface( }: { sourceFile: ts.SourceFile; className: string; + classDeclaration: ts.ClassDeclaration; settingsTypeFullName: string; interestingBaseClass: | "ManagedObject" @@ -479,7 +484,8 @@ function generateInterface( const moduleName = path.basename(fileName, path.extname(fileName)); const ast = buildAST( classInfo, - sourceFile.fileName, + sourceFile, + classDeclaration, constructorSignaturesAvailable, moduleName, settingsTypeFullName, @@ -495,12 +501,15 @@ function generateInterface( function buildAST( classInfo: ClassInfo, - classFileName: string, + sourceFile: ts.SourceFile, + classDeclaration: ts.ClassDeclaration, constructorSignaturesAvailable: boolean, moduleName: string, settingsTypeFullName: string, allKnownGlobals: GlobalToModuleMapping ) { + const { fileName: classFileName } = sourceFile; + const requiredImports: RequiredImports = {}; const methods = generateMethods(classInfo, requiredImports, allKnownGlobals); if (methods.length === 0) { @@ -519,14 +528,24 @@ function buildAST( const statements: ts.Statement[] = getImports(requiredImports); + const requiredGenericTypeImports = generateGenericTypeImports( + sourceFile, + classDeclaration, + statements, + requiredImports + ); + + if (requiredGenericTypeImports.length > 0) { + statements.push(...requiredGenericTypeImports); + } + const myInterface = factory.createInterfaceDeclaration( undefined, - [ - factory.createModifier(ts.SyntaxKind.ExportKeyword), - factory.createModifier(ts.SyntaxKind.DefaultKeyword), - ], + classDeclaration.modifiers?.filter( + (modifier) => !interfaceIncompatibleModifiers.has(modifier.kind) + ), classInfo.name, - undefined, + classDeclaration.typeParameters, undefined, methods ); diff --git a/packages/ts-interface-generator/src/test/generateInterfaceWithGenerics.test.ts b/packages/ts-interface-generator/src/test/generateInterfaceWithGenerics.test.ts new file mode 100644 index 00000000..0479df91 --- /dev/null +++ b/packages/ts-interface-generator/src/test/generateInterfaceWithGenerics.test.ts @@ -0,0 +1,72 @@ +import ts from "typescript"; +import { generateInterfaces } from "../interfaceGenerationHelper"; +import { initialize } from "../typeScriptEnvironment"; + +test("Generating the interface for a class using generics", () => { + const expected = `import { PropertyBindingInfo } from "sap/ui/base/ManagedObject"; +import { $ManagedObjectSettings } from "sap/ui/base/ManagedObject"; + +declare module "./SampleGenericManagedObject" { + + /** + * Interface defining the settings object used in constructor calls + */ + interface $SampleGenericManagedObjectSettings extends $ManagedObjectSettings { + text?: string | PropertyBindingInfo; + } + + export default interface SampleGenericManagedObject { + + // property: text + getText(): string; + setText(text: string): this; + } +} +`; + + function onTSProgramUpdate( + program: ts.Program, + typeChecker: ts.TypeChecker, + changedFiles: string[], // is an empty array in non-watch case; is at least one file in watch case + allKnownGlobals: { + [key: string]: { moduleName: string; exportName?: string }; + } + ) { + // files recognized as "real" app source files should be exactly one: SampleGenericManagedObject.ts + const sourceFiles: ts.SourceFile[] = program + .getSourceFiles() + .filter((sourceFile) => { + if ( + sourceFile.fileName.indexOf("@types") === -1 && + sourceFile.fileName.indexOf("node_modules/") === -1 && + sourceFile.fileName.indexOf(".gen.d.ts") === -1 + ) { + return true; + } + }); + expect(sourceFiles).toHaveLength(1); + expect(sourceFiles[0].fileName).toMatch(/.*SampleGenericManagedObject.ts/); + + // this function will be called with the resulting generated interface text - here the big result check occurs + function checkResult( + sourceFileName: string, + className: string, + interfaceText: string + ) { + expect(sourceFileName).toMatch(/.*SampleGenericManagedObject.ts/); + expect(className).toEqual("SampleGenericManagedObject"); + + expect(interfaceText).toEqual(expected); + } + + // trigger the interface generation - the result will be given to and checked in the function above + generateInterfaces( + sourceFiles[0], + typeChecker, + allKnownGlobals, + checkResult + ); + } + + initialize("./tsconfig-testgenerics.json", onTSProgramUpdate, {}); +}); diff --git a/packages/ts-interface-generator/src/test/generateInterfaceWithTypedGenerics.test.ts b/packages/ts-interface-generator/src/test/generateInterfaceWithTypedGenerics.test.ts new file mode 100644 index 00000000..a5abc777 --- /dev/null +++ b/packages/ts-interface-generator/src/test/generateInterfaceWithTypedGenerics.test.ts @@ -0,0 +1,75 @@ +import ts from "typescript"; +import { generateInterfaces } from "../interfaceGenerationHelper"; +import { initialize } from "../typeScriptEnvironment"; + +test("Generating the interface for a class using typed generics", () => { + const expected = `import { PropertyBindingInfo } from "sap/ui/base/ManagedObject"; +import { $ManagedObjectSettings } from "sap/ui/base/ManagedObject"; +import { type $UIComponentSettings } from "sap/ui/core/UIComponent"; +import { type $ComponentSettings as $RenamedComponentSettings } from "sap/ui/core/Component"; +import type Component from "sap/ui/core/Component"; + +declare module "./SampleGenericManagedObject" { + + /** + * Interface defining the settings object used in constructor calls + */ + interface $SampleGenericManagedObjectSettings extends $ManagedObjectSettings { + text?: string | PropertyBindingInfo; + } + + export default interface SampleGenericManagedObject { + + // property: text + getText(): string; + setText(text: string): this; + } +} +`; + + function onTSProgramUpdate( + program: ts.Program, + typeChecker: ts.TypeChecker, + changedFiles: string[], // is an empty array in non-watch case; is at least one file in watch case + allKnownGlobals: { + [key: string]: { moduleName: string; exportName?: string }; + } + ) { + // files recognized as "real" app source files should be exactly one: SampleGenericManagedObject.ts + const sourceFiles: ts.SourceFile[] = program + .getSourceFiles() + .filter((sourceFile) => { + if ( + sourceFile.fileName.indexOf("@types") === -1 && + sourceFile.fileName.indexOf("node_modules/") === -1 && + sourceFile.fileName.indexOf(".gen.d.ts") === -1 + ) { + return true; + } + }); + expect(sourceFiles).toHaveLength(1); + expect(sourceFiles[0].fileName).toMatch(/.*SampleGenericManagedObject.ts/); + + // this function will be called with the resulting generated interface text - here the big result check occurs + function checkResult( + sourceFileName: string, + className: string, + interfaceText: string + ) { + expect(sourceFileName).toMatch(/.*SampleGenericManagedObject.ts/); + expect(className).toEqual("SampleGenericManagedObject"); + + expect(interfaceText).toEqual(expected); + } + + // trigger the interface generation - the result will be given to and checked in the function above + generateInterfaces( + sourceFiles[0], + typeChecker, + allKnownGlobals, + checkResult + ); + } + + initialize("./tsconfig-testgenerics-typed.json", onTSProgramUpdate, {}); +}); diff --git a/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts b/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts new file mode 100644 index 00000000..089c3979 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts @@ -0,0 +1,17 @@ +import ManagedObject from "sap/ui/base/ManagedObject"; + +/** + * @name ui5tssampleapp.generics.SampleGenericManagedObject + */ +export default class SampleGenericManagedObject< + TOptions, + TOptions2 +> extends ManagedObject { + static readonly metadata = { + properties: { + text: { + type: "string", + }, + }, + }; +} diff --git a/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts b/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts new file mode 100644 index 00000000..5b7e558d --- /dev/null +++ b/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts @@ -0,0 +1,26 @@ +import ManagedObject from "sap/ui/base/ManagedObject"; +import UIComponent, { $UIComponentSettings } from "sap/ui/core/UIComponent"; +import Component, { + $ComponentSettings as $RenamedComponentSettings, +} from "sap/ui/core/Component"; + +/** + * @name ui5tssampleapp.generics.SampleGenericManagedObject + */ +export default class SampleGenericManagedObject< + TOptions extends $UIComponentSettings, + TOptions2 extends $RenamedComponentSettings, + TOptions3 extends Component, + TOptions4 extends {} = any +> extends ManagedObject { + static readonly metadata = { + properties: { + text: { + type: "string", + }, + }, + }; + + private _component: TOptions3; + private _uiComponent: UIComponent; +} diff --git a/packages/ts-interface-generator/tsconfig-testgenerics-typed.json b/packages/ts-interface-generator/tsconfig-testgenerics-typed.json new file mode 100644 index 00000000..b07d0a6d --- /dev/null +++ b/packages/ts-interface-generator/tsconfig-testgenerics-typed.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "rootDirs": ["./src/test/testdata/generics"], + "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'. */, + "sourceMap": true /* Generates corresponding '.map' file. */, + "strict": true /* Enable all strict type-checking options. */, + "strictNullChecks": false /* Enable strict null checks. */, + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "typeRoots": [ + "./node_modules/@types", + "../../node_modules/@types", + "./node_modules/@sapui5/ts-types-esm", + "../../node_modules/@sapui5/ts-types-esm" + ] + }, + "include": ["./src/test/testdata/generics/typed_condition/**/*"] +} diff --git a/packages/ts-interface-generator/tsconfig-testgenerics.json b/packages/ts-interface-generator/tsconfig-testgenerics.json new file mode 100644 index 00000000..b9b75e7a --- /dev/null +++ b/packages/ts-interface-generator/tsconfig-testgenerics.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "rootDirs": ["./src/test/testdata/generics"], + "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'. */, + "sourceMap": true /* Generates corresponding '.map' file. */, + "strict": true /* Enable all strict type-checking options. */, + "strictNullChecks": false /* Enable strict null checks. */, + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, + "typeRoots": [ + "./node_modules/@types", + "../../node_modules/@types", + "./node_modules/@sapui5/ts-types-esm", + "../../node_modules/@sapui5/ts-types-esm" + ] + }, + "include": ["./src/test/testdata/generics/simple/**/*"] +} From d8e3353292b793d8806220f1c7f3fcbd3232ee51 Mon Sep 17 00:00:00 2001 From: Daniel Lang Date: Mon, 30 May 2022 16:07:43 +0200 Subject: [PATCH 2/5] fix(ts-interface-generator): linting errors of added and modified files --- .../ts-interface-generator/src/astGenerationHelper.ts | 10 ++++++++-- .../generics/simple/SampleGenericManagedObject.ts | 3 +++ .../typed_condition/SampleGenericManagedObject.ts | 8 ++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/ts-interface-generator/src/astGenerationHelper.ts b/packages/ts-interface-generator/src/astGenerationHelper.ts index 750913e7..7ea98e2b 100644 --- a/packages/ts-interface-generator/src/astGenerationHelper.ts +++ b/packages/ts-interface-generator/src/astGenerationHelper.ts @@ -319,7 +319,12 @@ function generateGenericTypeImports( if (nameIsUsed(typeName, requiredImports)) { // import is already created continue; - } else if (existingImportsInSourceFile.hasOwnProperty(typeName)) { + } else if ( + Object.prototype.hasOwnProperty.call( + existingImportsInSourceFile, + typeName + ) + ) { const { statement, exportName } = existingImportsInSourceFile[typeName]; const moduleName = statement.moduleSpecifier.getText(); @@ -346,13 +351,14 @@ function generateGenericTypeImports( // TODO: Use a method to check for versions if (parseFloat(ts.version) >= 4.5) { + // @ts-ignore after 4.5, createImportSpecifier got a third parameter (in the beginning!). This code shall work with older and newer versions, but as the compile-time error check is considering either <4.5 or >=4.5, one of these lines is recognized as error importSpecifier = factory.createImportSpecifier( true, propertyName, typeNameIdentifier ); } else { - // @ts-ignore + // @ts-ignore after 4.5, createImportSpecifier got a third parameter (in the beginning!). This code shall work with older and newer versions, but as the compile-time error check is considering either <4.5 or >=4.5, one of these lines is recognized as error importSpecifier = factory.createImportSpecifier( propertyName, typeNameIdentifier diff --git a/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts b/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts index 089c3979..9354a8e1 100644 --- a/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts +++ b/packages/ts-interface-generator/src/test/testdata/generics/simple/SampleGenericManagedObject.ts @@ -14,4 +14,7 @@ export default class SampleGenericManagedObject< }, }, }; + + private _options: TOptions; + private _options2: TOptions2; } diff --git a/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts b/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts index 5b7e558d..8d077bec 100644 --- a/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts +++ b/packages/ts-interface-generator/src/test/testdata/generics/typed_condition/SampleGenericManagedObject.ts @@ -1,5 +1,7 @@ +/* eslint-disable @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any */ + import ManagedObject from "sap/ui/base/ManagedObject"; -import UIComponent, { $UIComponentSettings } from "sap/ui/core/UIComponent"; +import { $UIComponentSettings } from "sap/ui/core/UIComponent"; import Component, { $ComponentSettings as $RenamedComponentSettings, } from "sap/ui/core/Component"; @@ -21,6 +23,8 @@ export default class SampleGenericManagedObject< }, }; + private _uiComponentSettings: TOptions; + private _componentSettings: TOptions2; private _component: TOptions3; - private _uiComponent: UIComponent; + private _any: TOptions4; } From 780ec2c16be5f3e6a773daa29cb73ec17cfaa3b2 Mon Sep 17 00:00:00 2001 From: Daniel Lang Date: Tue, 31 May 2022 17:48:49 +0200 Subject: [PATCH 3/5] docs(ts-interface-generator): added features section --- packages/ts-interface-generator/README.md | 60 ++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/ts-interface-generator/README.md b/packages/ts-interface-generator/README.md index bdfcb3de..1d97e83e 100644 --- a/packages/ts-interface-generator/README.md +++ b/packages/ts-interface-generator/README.md @@ -95,18 +95,74 @@ This is a problem for application code using controls developed in TypeScript as 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. +For any such control, the metadata is parsed, analyzed, and a new TypeScript file is constructed, which contains an interface declaration with the methods generated by UI5 at runtime. This generated interface gets merged with the already existing code using TypeScripts [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) concept. 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. ;-) +## Features + +### Handling `default` and named exports + +In order that the Declaration Merging of TypeScript works, the modifiers of the generated interface has to match the parsed UI5 artifact. + +```typescript +export class MyCustomControl extends Control { + ... +} +``` + +becomes + +```typescript +export interface MyCustomControl { + ... +} +``` + +```typescript +export default abstract class MyAbstractControl extends Control { + ... +} +``` + +becomes + +```typescript +export default interface MyAbstractControl { + ... +} +``` + +### Generics + +This tool also supports generic classes which extending `ManagedObject` class. In order to enable the Declaration Merging of TypeScript the generic type parameters needs to be incooperated into the generated interface declaration. +It takes care that types used to constrain the generic type parameter are imported in the generated interface if required. + +```typescript +export interface ICommonListOptions { + mode: 'ul' | 'ol'; +} + +export default abstract class MyList { + ... +} +``` + +becomes + +```typescript +export default interface MyList { + ... +} +``` + ## TODO - copy the original API documentation to the generated methods - 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 From 2ffbc8e9b9930854a4b1830ff32ba158166ad593 Mon Sep 17 00:00:00 2001 From: Daniel Lang Date: Tue, 31 May 2022 17:56:57 +0200 Subject: [PATCH 4/5] docs(ts-interface-generator): fix layout of readme --- packages/ts-interface-generator/README.md | 39 ++++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/ts-interface-generator/README.md b/packages/ts-interface-generator/README.md index 1d97e83e..fb6372a8 100644 --- a/packages/ts-interface-generator/README.md +++ b/packages/ts-interface-generator/README.md @@ -106,34 +106,35 @@ Oh, and the tool itself is implemented in TypeScript because TypeScript makes de ### Handling `default` and named exports In order that the Declaration Merging of TypeScript works, the modifiers of the generated interface has to match the parsed UI5 artifact. - -```typescript -export class MyCustomControl extends Control { +- **Named export:** + ```typescript + export class MyCustomControl extends Control { ... -} -``` + } + ``` -becomes + becomes -```typescript -export interface MyCustomControl { + ```typescript + export interface MyCustomControl { ... -} -``` + } + ``` -```typescript -export default abstract class MyAbstractControl extends Control { +- **Default export:** + ```typescript + export default abstract class MyAbstractControl extends Control { ... -} -``` + } + ``` -becomes + becomes -```typescript -export default interface MyAbstractControl { + ```typescript + export default interface MyAbstractControl { ... -} -``` + } + ``` ### Generics From 4d55970e24d7b3e6d58e205a628d111302eb700e Mon Sep 17 00:00:00 2001 From: Daniel Lang Date: Wed, 1 Jun 2022 11:07:45 +0200 Subject: [PATCH 5/5] docs(ts-interface-generator): fix layout of readme for prettier --- packages/ts-interface-generator/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/ts-interface-generator/README.md b/packages/ts-interface-generator/README.md index fb6372a8..c6820cf7 100644 --- a/packages/ts-interface-generator/README.md +++ b/packages/ts-interface-generator/README.md @@ -106,10 +106,12 @@ Oh, and the tool itself is implemented in TypeScript because TypeScript makes de ### Handling `default` and named exports In order that the Declaration Merging of TypeScript works, the modifiers of the generated interface has to match the parsed UI5 artifact. + - **Named export:** + ```typescript export class MyCustomControl extends Control { - ... + ... } ``` @@ -117,14 +119,15 @@ In order that the Declaration Merging of TypeScript works, the modifiers of the ```typescript export interface MyCustomControl { - ... + ... } ``` - **Default export:** + ```typescript export default abstract class MyAbstractControl extends Control { - ... + ... } ``` @@ -132,7 +135,7 @@ In order that the Declaration Merging of TypeScript works, the modifiers of the ```typescript export default interface MyAbstractControl { - ... + ... } ```