From 62213b2d10b1fac9dbb21af43eedabc51eec92df Mon Sep 17 00:00:00 2001 From: Frank Weigel Date: Fri, 10 Oct 2025 14:16:28 +0200 Subject: [PATCH] fix(dts-generator): fix more derived type names for module:* classes When a managed object class has a module:* name, the names are now correctly split into prefix and base name when calculating the name of the EventParameters and EventType types. The implementation of the calculations is now shared with the calculation implemented in b6c0fe52c1603042439df035b5ba8f5439b732f8. --- .../dts-generator/src/phases/json-fixer.ts | 14 ++-- .../dts-generator/src/utils/base-utils.ts | 79 +++++++++++++++++++ .../json-constructor-settings-interfaces.ts | 71 +---------------- .../utils/json-event-parameter-interfaces.ts | 46 ++++++----- 4 files changed, 108 insertions(+), 102 deletions(-) diff --git a/packages/dts-generator/src/phases/json-fixer.ts b/packages/dts-generator/src/phases/json-fixer.ts index b41d80f..c3ebcd3 100644 --- a/packages/dts-generator/src/phases/json-fixer.ts +++ b/packages/dts-generator/src/phases/json-fixer.ts @@ -20,7 +20,7 @@ import { import { TypeParser } from "../utils/type-parser.js"; import { TSASTTypeBuilder } from "../utils/ts-ast-type-builder.js"; import { addConstructorSettingsInterfaces } from "../utils/json-constructor-settings-interfaces.js"; -import { splitName } from "../utils/base-utils.js"; +import { calculateDerivedNames, splitName } from "../utils/base-utils.js"; import { Directives } from "../generate-from-objects.js"; import { addEventParameterInterfaces } from "../utils/json-event-parameter-interfaces.js"; @@ -452,12 +452,7 @@ function determineMissingExportsForTypes(symbols: ConcreteSymbol[]) { function basename(fqn: string) { // Note: this differs from splitName(fqn)[0] as it handles module:* namepaths - if (fqn.startsWith("module:")) { - const p = fqn.lastIndexOf("/"); - fqn = fqn.slice(p + 1); - } - const p = fqn.lastIndexOf("."); - return fqn.slice(p + 1); + return calculateDerivedNames(fqn).basename; } symbols.forEach((symbol) => { @@ -481,12 +476,13 @@ function determineMissingExportsForTypes(symbols: ConcreteSymbol[]) { } else { // non-canonical names need to be reported at least // TODO consumers need some documentation about the necessary imports (SDK enhancement?) + const { exportName } = calculateDerivedNames(symbol.name); log.warn( `**** symbol ${symbol.name} exported from "${ symbol.module - }" as "${basename(symbol.name)}"`, + }" as "${exportName}"`, ); - symbol.export = basename(symbol.name); + symbol.export = exportName; } }); } diff --git a/packages/dts-generator/src/utils/base-utils.ts b/packages/dts-generator/src/utils/base-utils.ts index 7fc9b43..58bed99 100644 --- a/packages/dts-generator/src/utils/base-utils.ts +++ b/packages/dts-generator/src/utils/base-utils.ts @@ -2,3 +2,82 @@ export function splitName(name: string, separator = ".") { const p = name.lastIndexOf(separator); return p < 0 ? ["", name] : [name.slice(0, p), name.slice(p + 1)]; } + +/* + * Calculates a derived name from an entity name by first calculating the basename + * of the entity, then applying the given `createDerivedBasename` transformation and + * then building a qualified name again from it. + * + * The returned object contains the fully qualified name `name`, the derived `basename` + * and the assumed `exportName` under which the derived entity is exported from the + * containing module. + * + * Examples (based on the $Settings transformation): + * (1) sap.m.Button --> + * name: "sap.m.$ButtonSettings" + * basename: "$ButtonSettings" + * exportName: "$ButtonSettings" + * + * (2) module:my/lib//NotificationListGroupItem --> + * name: "module:my/lib/NotificationListGroupItem.$NotificationListGroupItemSettings" + * basename: "$NotificationListGroupItemSettings" + * exportName: "$NotificationListGroupItemSettings" + * + * (3) module:my/lib/SegmentedButton.Item + * name: "module:my/lib/SegmentedButton.$ItemSettings" + * basename: "$ItemSettings" + * exportName: "$ItemSettings" + * + * (4) module:my/lib/SegmentedButton.Item.SubItem + * name: "module:my/lib/SegmentedButton.Item.$SubItemSettings" + * basename: "$SubItemSettings" + * exportName: "Item.$SubItemSettings" + */ +export function calculateDerivedNames( + fqn: string, + createDerivedBasename: (basename: string) => string = (basename) => basename, +): { + name: string; + basename: string; + exportName: string; +} { + const makeNameAndBasename = (fqn: string) => { + const [prefix, basename] = splitName(fqn); + const derivedBasename = createDerivedBasename(basename); + return [`${prefix}${prefix ? "." : ""}${derivedBasename}`, derivedBasename]; + }; + + if (fqn.startsWith("module:")) { + const [pkgname, moduleAndExport] = splitName( + fqn.slice("module:".length), + "/", + ); + const pos = moduleAndExport.indexOf("."); + if (pos < 0) { + // case (2), default export + const [, settingsName] = makeNameAndBasename(moduleAndExport); + return { + name: `${fqn}.${settingsName}`, + basename: settingsName, + exportName: settingsName, + }; + } + // case (3) and (4), named export + const moduleBaseName = moduleAndExport.slice(0, pos); + const exportName = moduleAndExport.slice(pos + 1); + const [settingsNameWithPrefix, settingsName] = + makeNameAndBasename(exportName); + return { + name: `module:${pkgname}${pkgname ? "/" : ""}${moduleBaseName}.${settingsNameWithPrefix}`, + basename: settingsName, + exportName: settingsNameWithPrefix, + }; + } + // case (1), global name + const [fqSettingsName, settingsName] = makeNameAndBasename(fqn); + return { + name: fqSettingsName, + basename: settingsName, + exportName: settingsName, + }; +} diff --git a/packages/dts-generator/src/utils/json-constructor-settings-interfaces.ts b/packages/dts-generator/src/utils/json-constructor-settings-interfaces.ts index 16576be..f92eb5d 100644 --- a/packages/dts-generator/src/utils/json-constructor-settings-interfaces.ts +++ b/packages/dts-generator/src/utils/json-constructor-settings-interfaces.ts @@ -4,80 +4,13 @@ import { ClassSymbol, ConcreteSymbol, InterfaceSymbol, - ObjConstructor, - ObjEvent, - Ui5Property, } from "../types/api-json.js"; const log = getLogger("@ui5/dts-generator/constructor-settings-interfaces"); -import { splitName } from "./base-utils.js"; +import { calculateDerivedNames } from "./base-utils.js"; import { TypeReference } from "../types/ast.js"; -/* - * Calculates the name for the $Settings interface, the basename of the name - * (last name segment) and the assumed export name under which the settings - * are exported from the containing module. - * - * Examples: - * (1) sap.m.Button --> - * name: "sap.m.$ButtonSettings" - * basename: "$ButtonSettings" - * exportName: "$ButtonSettings" - * - * (2) module:my/lib//NotificationListGroupItem --> - * name: "module:my/lib/NotificationListGroupItem.$NotificationListGroupItemSettings" - * basename: "$NotificationListGroupItemSettings" - * exportName: "$NotificationListGroupItemSettings" - * - * (3) module:my/lib/SegmentedButton.Item - * name: "module:my/lib/SegmentedButton.$ItemSettings" - * basename: "$ItemSettings" - * exportName: "$ItemSettings" - * - * (4) module:my/lib/SegmentedButton.Item.SubItem - * name: "module:my/lib/SegmentedButton.Item.$SubItemSettings" - * basename: "$SubItemSettings" - * exportName: "Item.$SubItemSettings" - */ export function makeSettingsNames(fqn: string) { - const makeNameAndBasename = (fqn: string) => { - const [prefix, basename] = splitName(fqn); - const settings = `\$${basename}Settings`; - return [`${prefix}${prefix ? "." : ""}${settings}`, settings]; - }; - - if (fqn.startsWith("module:")) { - const [pkgname, moduleAndExport] = splitName( - fqn.slice("module:".length), - "/", - ); - const pos = moduleAndExport.indexOf("."); - if (pos < 0) { - // case (2), default export - const [, settingsName] = makeNameAndBasename(moduleAndExport); - return { - name: `${fqn}.${settingsName}`, - basename: settingsName, - exportName: settingsName, - }; - } - // case (3) and (4), named export - const moduleBaseName = moduleAndExport.slice(0, pos); - const exportName = moduleAndExport.slice(pos + 1); - const [settingsNameWithPrefix, settingsName] = - makeNameAndBasename(exportName); - return { - name: `module:${pkgname}${pkgname ? "/" : ""}${moduleBaseName}.${settingsNameWithPrefix}`, - basename: settingsName, - exportName: settingsNameWithPrefix, - }; - } - // case (1), global name - const [fqSettingsName, settingsName] = makeNameAndBasename(fqn); - return { - name: fqSettingsName, - basename: settingsName, - exportName: settingsName, - }; + return calculateDerivedNames(fqn, (basename) => `\$${basename}Settings`); } export function isA( diff --git a/packages/dts-generator/src/utils/json-event-parameter-interfaces.ts b/packages/dts-generator/src/utils/json-event-parameter-interfaces.ts index 80a0e31..a9d1c64 100644 --- a/packages/dts-generator/src/utils/json-event-parameter-interfaces.ts +++ b/packages/dts-generator/src/utils/json-event-parameter-interfaces.ts @@ -5,13 +5,11 @@ import { ConcreteSymbol, InterfaceSymbol, ObjCallableParameter, - ObjEvent, - ObjMethod, TypedefSymbol, Ui5Event, } from "../types/api-json.js"; const log = getLogger("@ui5/dts-generator/constructor-settings-interfaces"); -import { splitName } from "./base-utils.js"; +import { calculateDerivedNames } from "./base-utils.js"; import { addJsDocProps, isA, @@ -60,12 +58,16 @@ function createEventParameterInterfaces( const makeEventParametersName = (fqn: string, eventName: string) => { const capitalizedEventName = eventName.charAt(0).toUpperCase() + eventName.slice(1); - const [pkgname, basename] = splitName(fqn); return { - fullEventParametersName: `${pkgname}.${basename}\$${capitalizedEventName}EventParameters`, - shortEventParametersName: `${basename}\$${capitalizedEventName}EventParameters`, - fullEventTypealiasName: `${pkgname}.${basename}\$${capitalizedEventName}Event`, - shortEventTypealiasName: `${basename}\$${capitalizedEventName}Event`, + eventParametersNames: calculateDerivedNames( + fqn, + (basename: string) => + `${basename}\$${capitalizedEventName}EventParameters`, + ), + eventTypeAliasNames: calculateDerivedNames( + fqn, + (basename: string) => `${basename}\$${capitalizedEventName}Event`, + ), }; }; @@ -254,19 +256,15 @@ function createEventParameterInterfaces( if (symbol.events) { symbol.events.forEach((event) => { // build the interface (with no properties (event parameters) yet) - const { - fullEventParametersName, - shortEventParametersName, - fullEventTypealiasName, - shortEventTypealiasName, - } = makeEventParametersName(symbol.name, event.name); + const { eventParametersNames, eventTypeAliasNames } = + makeEventParametersName(symbol.name, event.name); const eventParametersInterface: InterfaceSymbol = { kind: "interface", - name: fullEventParametersName, - basename: shortEventParametersName, + name: eventParametersNames.name, + basename: eventParametersNames.basename, module: symbol.module, resource: symbol.resource, - export: shortEventParametersName, + export: eventParametersNames.exportName, visibility: symbol.visibility, __isNotAMarkerInterface: true, }; @@ -290,7 +288,7 @@ function createEventParameterInterfaces( eventParametersInterface.extends = makeEventParametersName( superClass.name, superEvent.name, - ).fullEventParametersName; + ).eventParametersNames.name; } // get inherited parameters to check compatibility and remove them from the new inheriting interface @@ -338,14 +336,14 @@ function createEventParameterInterfaces( // now also create a type alias for the event with these parameters and the concrete event source type const eventTypedef: TypedefSymbol = { kind: "typedef", - name: fullEventTypealiasName, + name: eventTypeAliasNames.name, type: { kind: "TypeReference", typeName: "sap.ui.base.Event", typeArguments: [ { kind: "TypeReference", - typeName: shortEventParametersName, + typeName: eventParametersNames.basename, }, { kind: "TypeReference", @@ -353,10 +351,10 @@ function createEventParameterInterfaces( }, ], }, - basename: shortEventTypealiasName, + basename: eventTypeAliasNames.basename, module: symbol.module, resource: symbol.resource, - export: shortEventTypealiasName, + export: eventTypeAliasNames.exportName, visibility: symbol.visibility, }; eventTypedef.description = `Event object of the ${symbol.basename}#${event.name} event.`; @@ -374,7 +372,7 @@ function createEventParameterInterfaces( enrichEventMethods( symbol, event.name, - shortEventTypealiasName, + eventTypeAliasNames.basename, eventParametersInterface.name, ); @@ -399,7 +397,7 @@ function createEventParameterInterfaces( "sap.ui.base.Event" ) { eventPropertyType.parameters[0].type.typeName = - shortEventTypealiasName; // replace "Event" with the specific event type + eventTypeAliasNames.basename; // replace "Event" with the specific event type } else { log.info( `Property '${event.name}' in the settings object for ${symbol.name} does not relate to an event. Maybe shadowed by a property with same name.`,