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/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/testdata/sampleControl/SampleControl.gen.d.ts b/packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.gen.d.ts similarity index 100% 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 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 100% rename from packages/ts-interface-generator/src/test/testdata/sampleControl/SampleControl.ts rename to packages/ts-interface-generator/src/test/samples/sampleControl/SampleControl.ts 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/testdata/sampleWebComponent/SampleWebComponent.gen.d.ts b/packages/ts-interface-generator/src/test/samples/sampleWebComponent/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/samples/sampleWebComponent/SampleWebComponent.gen.d.ts 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..39835b11 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.gen.d.ts @@ -0,0 +1,47 @@ +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 { + + /** + * The text. + * + * @since 1.0 + */ + text?: string | PropertyBindingInfo; + } + + interface MyControl { + + // property: text + + /** + * Gets current value of property "text". + * + * The text. + * + * @since 1.0 + * + * @returns Value of property "text" + */ + getText(): string; + + /** + * Sets a new value for property "text". + * + * The text. + * + * @since 1.0 + * 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..80b0ed8a --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/instance-exported/MyControl.ts @@ -0,0 +1,33 @@ +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 class MyControl extends Control { + static readonly metadata: MetadataOptions = { + properties: { + /** + * The text. + * @since 1.0 + */ + 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/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..0b468e24 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.gen.d.ts @@ -0,0 +1,47 @@ +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 { + + /** + * The text. + * + * @since 1.0 + */ + text?: string | PropertyBindingInfo; + } + + export default interface MyControl { + + // property: text + + /** + * Gets current value of property "text". + * + * The text. + * + * @since 1.0 + * + * @returns Value of property "text" + */ + getText(): string; + + /** + * Sets a new value for property "text". + * + * The text. + * + * @since 1.0 + * 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..4222d0d3 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/simple-control/MyControl.ts @@ -0,0 +1,31 @@ +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 = { + properties: { + /** + * The text. + * @since 1.0 + */ + 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/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/**/*"] }