ui5-tooling-modules
Version:
UI5 CLI extensions to load and convert node modules as UI5 AMD-like modules
999 lines (924 loc) • 31 kB
JavaScript
const prettier = require("prettier");
const { join } = require("path");
const { writeFileSync, existsSync, mkdirSync } = require("fs");
const handlebars = require("handlebars");
const { baseTemplate, loadAndCompile } = require("./HandlebarsHelper");
const SimpleLogger = require("./SimpleLogger");
const logger = SimpleLogger.create("🧑💻 DTS");
const WebComponentRegistryHelper = require("./WebComponentRegistryHelper");
const rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i;
const mSingular = { children: -3, ies: "y", ves: "f", oes: -2, ses: -2, ches: -2, shes: -2, xes: -2, s: -1 };
const actions = {
Get: "get",
Set: "set",
Add: "add",
Insert: "insert",
Remove: "remove",
RemoveAll: "removeAll",
Destroy: "destroy",
IndexOf: "indexOf",
Attach: "attach",
Detach: "detach",
};
/**
* Attempts to convert a plural name to its singular form based on common English rules.
*
* @param {string} sName - The plural name to convert
* @returns {string} The singular form of the name
*/
const guessSingularName = function (sName) {
return sName.replace(rPlural, function ($, sPlural) {
const vRepl = mSingular[sPlural.toLowerCase()];
return typeof vRepl === "string" ? vRepl : sPlural.slice(0, vRepl);
});
};
const capitalize = (value) => value.replace(/^([a-z])/, (g) => g.toUpperCase());
/**
* Determines if a word should use "an" instead of "a" as its indefinite article.
* This function uses a more comprehensive approach that considers common phonetic patterns.
*
* @param {string} word - The word to check
* @returns {boolean} True if the word should use "an", false if it should use "a"
*/
const shouldUseAn = function (word) {
if (!word || typeof word !== "string") return false;
// Convert to lowercase for consistent checking
const lowerWord = word.toLowerCase();
// Check for vowel sounds at the beginning
if (/^[aeiou]/i.test(lowerWord)) {
// Exception for words that start with 'u' but have a 'yu' sound
if (lowerWord.startsWith("u") && (lowerWord.startsWith("uni") || lowerWord.startsWith("use") || lowerWord.startsWith("uti") || lowerWord.startsWith("eu"))) {
return false;
}
// Exception for words that start with 'o' but have a 'w' sound
if (lowerWord.startsWith("o") && (lowerWord.startsWith("one") || lowerWord.startsWith("once"))) {
return false;
}
return true;
}
// Check for silent 'h' words
if (lowerWord.startsWith("h") && (lowerWord.startsWith("hour") || lowerWord.startsWith("heir") || lowerWord.startsWith("honor") || lowerWord.startsWith("honest"))) {
return true;
}
return false;
};
/**
* Generates a standardized documentation for a method or property.
*
* @param {object} options - Documentation generation options
* @param {string} options.entityType - Type of entity (property, aggregation, association, event)
* @param {string} options.entityName - Name of the entity
* @param {string} options.action - Action being performed (get, set, add, remove, etc.)
* @param {string} [options.description] - Description of the entity
* @param {string} [options.defaultValueDesc] - Default value information
* @param {string} [options.returnValueDesc] - Description of the return value
* @param {string} [options.className] - Name of the class containing the entity (for events)
* @returns {string} Formatted documentation string
*/
const generateStandardDocumentation = function (options) {
const { entityType, entityName, action, description = "", defaultValueDesc, className = "", typeInfo } = options;
const linkRef = `{@link #${actions.Get}${capitalize(entityName)} ${entityName}}`;
let text;
let defaultValue;
let returnValue = "@returns Reference to `this` in order to allow method chaining";
// Determine the appropriate indefinite article
const article = shouldUseAn(entityName) ? "an" : "a";
// Generate standard text based on action and entity type
switch (action) {
case actions.Get:
if (entityType === "property") {
text = `Gets current value of ${entityType} ${linkRef}.`;
returnValue = `@returns Value of ${entityType} \`${entityName}\``;
} else if (entityType === "aggregation") {
text = `Gets content of ${entityType} ${linkRef}.`;
} else if (entityType === "association") {
text = `${typeInfo.multiple ? "Returns array of IDs of the elements which are the current targets" : "ID of the element which is the current target"} of the ${entityType} ${linkRef}.`;
returnValue = `@returns Reference the IDs of the associated controls`;
} else {
returnValue = `@returns The content of the ${entityType}`;
}
defaultValue = defaultValueDesc !== undefined ? `Default value is \`${defaultValueDesc}\`.` : "";
break;
case actions.Set:
if (entityType === "property") {
text = `Sets a new value for ${entityType} ${linkRef}.`;
} else if (entityType === "aggregation") {
text = `Sets the aggregated ${linkRef}.`;
} else if (entityType === "association") {
text = `Sets the associated ${linkRef}.`;
}
defaultValue =
defaultValueDesc !== undefined
? `When called with a value of \`null\` or \`undefined\`, the default value of the property will be restored.\n\nDefault value is \`${defaultValueDesc}\`.`
: "";
break;
case actions.Add:
text = `Adds some ${entityName} to the ${entityType} ${linkRef}.`;
break;
case actions.Insert:
text = `Inserts ${article} ${entityName} into the ${entityType} ${linkRef}.`;
break;
case actions.Remove:
text = `Removes ${article} ${entityName} from the ${entityType} ${linkRef}.`;
returnValue = `@returns The removed ${entityName} or \`null\``;
break;
case actions.RemoveAll:
text = `Removes all the controls from the ${entityType} ${linkRef}.`;
returnValue = `@returns An array of the removed elements (might be empty)`;
break;
case actions.Destroy:
text = `Destroys the ${entityName} in the ${entityType} ${linkRef}.`;
break;
case actions.IndexOf:
text = `Checks for the provided '${typeInfo.dedicatedTypes[0].dtsType}' in the ${entityType} ${linkRef} and returns its index if found or -1 otherwise.`;
returnValue = `@returns The index of the provided control in the ${entityType} if found, or -1 otherwise`;
break;
case actions.Attach:
text = `Attaches event handler \`fnFunction\` to the ${linkRef} event${className ? ` of this \`${className}\`` : ""}.`;
break;
case actions.Detach:
text = `Detaches event handler \`fnFunction\` from the ${linkRef} event${className ? ` of this \`${className}\`` : ""}.`;
break;
default:
text = `Interacts with ${entityType} ${linkRef}.`;
}
return baseTemplate({ text, description, defaultValue, returnValue });
};
/**
* Handlebar Helpers specific to the DTSSerializer.
*/
handlebars.registerHelper("escapeInterfaceName", function (namespace, interfaceName) {
const ui5QualifiedName = `${namespace}.${interfaceName}`;
return `__implements_${ui5QualifiedName.replace(/[/.@-]/g, "_")}: boolean;`;
});
handlebars.registerHelper("generateImports", function (module, info) {
let imports = "import type { ";
for (const key in info) {
if (info[key].default) {
imports += "default as ";
}
imports += key;
if (info[key].alternativeImportName) {
imports += ` as ${info[key].alternativeImportName}`;
}
imports += ", ";
}
imports = imports.replace(/,\s$/, ` } from "${module}";`);
return imports;
});
/**
* Generates the TypeScript type name for an event's settings.
*
* @param {string} className - Name of the class containing the event
* @param {string} eventName - Name of the event
* @returns {string} TypeScript type name for the event settings
*/
const generateEventSettings = function (className, eventName) {
return `${className}$${capitalize(eventName)}Event`;
};
handlebars.registerHelper("generateEventSettings", generateEventSettings);
/**
* Generates TypeScript type definitions for property settings.
*
* @param {Array<Object>} types - Array of type definitions
* @param {boolean} addSingleAndMulti - Flag whether to add also single
* type definition in case type is multi
* @param {Array<Object>} specialTypes - Array of additional fix type definitions
*
* @returns {string} TypeScript type definition string
*/
const processTypes = function (types, addSingleAndMulti = false) {
const typeStrings = new Set();
const processDedicatedTypes = function (dedicatedTypes) {
const typeStrings = new Set();
for (const dedicatedType of dedicatedTypes) {
typeStrings.add(dedicatedType.dtsType);
if (dedicatedType.isEnum) {
typeStrings.add(`keyof typeof ${dedicatedType.dtsType}`);
}
}
return [...typeStrings].join(" | ");
};
for (const type of types) {
const typeString = processDedicatedTypes(type.dedicatedTypes);
typeStrings.add(type.multiple ? `Array<${typeString}>` : typeString);
if (type.multiple && addSingleAndMulti) {
typeStrings.add(typeString);
}
}
return [...typeStrings].join(" | ");
};
handlebars.registerHelper("processTypes", processTypes);
handlebars.registerHelper("generateAggregationSettings", function (types) {
return processTypes(
[
...types,
{
dedicatedTypes: [
{
dtsType: "AggregationBindingInfo",
},
{
dtsType: "`{${string}}`",
},
],
},
],
true,
);
});
handlebars.registerHelper("generatePropertySettingsWithBindingInfo", function (types) {
return processTypes([
...types,
{
dedicatedTypes: [
{
dtsType: "PropertyBindingInfo",
},
{
dtsType: "`{${string}}`",
},
],
},
]);
});
handlebars.registerHelper("generateAssociationSettings", function (types) {
return processTypes(
[
...types,
{
multiple: types.some((t) => t.multiple),
dedicatedTypes: [{ dtsType: "string" }],
},
],
true,
);
});
/**
* All needed HBS templates for serializing JSDoc comments.
*/
const Templates = {
module: loadAndCompile("../templates/dts/Module.hbs"),
class: loadAndCompile("../templates/dts/Class.hbs"),
};
/**
* The DTSSerializer is responsible for generating TypeScript declaration files (.d.ts)
* for UI5 web components. It provides methods to initialize classes, write class bodies,
* and serialize different UI5 elements like properties, aggregations, associations, and events.
*/
const DTSSerializer = {
/**
* Deactivates the *.d.ts file generation via ui5.yaml configuration.
* When called, all methods in this object will be replaced with no-op functions.
*/
deactivate() {
for (const s in this) {
this[s] = () => {};
}
},
/**
* Serializes a full set of *.d.ts files for each class/enum/interface in this registry entry.
* Location: ".ui5-tooling-modules/types"
* @param {WebComponentRegistry.Entry} registryEntry the registry entry which will receive a set of serialized *.d.ts files.
*/
prepare: function (registryEntry) {
const cachePath = join(process.cwd(), ".ui5-tooling-modules");
if (!existsSync(join(cachePath, "types"))) {
// Directory does not exist, create it
mkdirSync(join(cachePath, "types"), { recursive: true });
}
prettier
.format(
Templates.module({
registryEntry,
}),
{
// TODO: read from prettier config if existing
// otherwise use some defaults
semi: true,
trailingComma: "none",
parser: "typescript",
},
)
.then((prettifiedTypes) => {
if (prettifiedTypes) {
writeFileSync(join(cachePath, "types", `${registryEntry.qualifiedNamespace}.gen.d.ts`), prettifiedTypes, { encoding: "utf-8" });
}
});
for (const clazz in registryEntry.classes) {
if (registryEntry.classes[clazz].superclass) {
prettier
.format(
Templates.class({
clazz: registryEntry.classes[clazz],
BINDING_STRING_PLACEHOLDER: "`{${string}}`",
}),
{
// TODO: read from prettier config if existing
// otherwise use some defaults
semi: true,
trailingComma: "none",
parser: "typescript",
},
)
.then((prettifiedTypes) => {
if (prettifiedTypes) {
writeFileSync(join(cachePath, "types", `${registryEntry.classes[clazz]._ui5QualifiedName}.gen.d.ts`), prettifiedTypes, { encoding: "utf-8" });
}
});
}
}
},
/**
* Initializes the internal objects holding the DTS information for the given class.
* Will be filled on the go when the custom elements metadata is analyzed.
* @param {WebComponentRegistry.ClassDef} classDef the class def to be initialized
*/
initClass(classDef) {
classDef._dts = {
globalImports: {},
imports: {},
settings: {},
properties: {},
aggregations: {},
associations: {},
events: {},
methods: {},
};
},
/**
* Writes the main class body DTS information, e.g. properties, aggregations, ...
* @param {WebComponentRegistry.ClassDef} classDef the class def for which the main class body should be written
*/
writeClassBody(classDef) {
// Common utility functions
/**
* Creates a template for method documentation.
*
* @param {object} options - Method template options
* @param {object} [options.methodName] - Name of the method
* @param {object} [options.params] - Method parameters
* @param {string} [options.description] - Method description
* @param {Array<object>} [options.returnValueTypes] - Return value types
* @returns {object} Method template object
*/
const getMethodTemplate = function ({ methodName = "", params = {}, description = "", returnValueTypes = [{ dedicatedTypes: [{ dtsType: "this" }] }] } = {}) {
return {
methodName,
params,
description,
returnValueTypes,
};
};
/**
* Prepares all necessary information to later generate the correct import statements for a given class.
*
* @param {string} importSource The path to the module from which the import should be loaded.
* @param {string} namedImport The name of the export as well as the variable name to be used for the import.
* @param {object} importOptions An object containing additional import configurations.
*/
function writeImports(importSource, namedImport, importOptions) {
classDef._dts[importOptions?.global ? "globalImports" : "imports"][importSource] ??= {};
classDef._dts[importOptions?.global ? "globalImports" : "imports"][importSource][namedImport] = Object.assign({}, importOptions);
}
/**
* Processes types and adds necessary imports
*
* @param {Array<object>} types - Array of type objects
*/
const processTypesAndAddImports = (types) => {
types.forEach((type) => {
type.dedicatedTypes.forEach((type) => {
// Adds import statements for types that are from external packages.
if (type.packageName && type.dtsType !== classDef.name) {
writeImports(type.packageName, type.dtsType, { default: type.isClass, global: type.globalImport });
}
});
});
};
/**
* Creates a method with standard documentation
*
* @param {string} methodName - Name of the method to create
* @param {object} options - Options for method creation
* @param {object} [options.params] - Method parameters
* @param {object} docOptions - Documentation options
* @param {Array<object>} [returnValueTypes] - Return value types
*/
const createMethod = (options) => {
const { methodSuffix = "", params = {}, docOptions, returnValueTypes } = options;
let methodName = docOptions.action;
if ([actions.Set, actions.Get, actions.RemoveAll, actions.Destroy, actions.Detach, actions.Attach].includes(docOptions.action)) {
methodName += capitalize(docOptions.entityName);
} else if ([actions.Add, actions.Insert, actions.Remove, actions.IndexOf].includes(docOptions.action)) {
methodName += capitalize(guessSingularName(docOptions.entityName));
}
classDef._dts.methods[`${methodName}${methodSuffix}`] = getMethodTemplate({
methodName,
params,
description: generateStandardDocumentation(docOptions),
returnValueTypes,
});
};
/**
* Creates methods for multiple items (for aggregations and associations)
*
* @param {object} info - Entity information
* @param {string} entityType - Type of entity (aggregation or association)
* @param {Array<object>} returnValueTypes - Return value types for removeAll method
*/
const createMultipleItemMethods = (info, entityType, returnValueTypes) => {
const singularName = guessSingularName(info.name);
// Add method
createMethod({
params: {
[singularName]: {
types: entityType === "association" ? [...info.types, { dedicatedTypes: [{ dtsType: "ID" }] }] : info.types,
},
},
docOptions: {
entityType,
entityName: info.name,
action: actions.Add,
},
});
// Insert method (only for aggregations)
if (entityType === "aggregation") {
createMethod({
params: {
[singularName]: {
types: info.types,
},
["index"]: {
types: [
{
dedicatedTypes: [
{
dtsType: "int",
},
],
},
],
},
},
docOptions: {
entityType,
entityName: info.name,
action: actions.Insert,
},
});
}
// Remove method
createMethod({
params: {
[singularName]: {
types: [
...info.types,
{
dedicatedTypes: [
{
dtsType: "int",
},
],
},
{
dedicatedTypes: [
{
dtsType: "ID",
},
],
},
],
},
},
docOptions: {
entityType,
entityName: info.name,
action: actions.Remove,
},
returnValueTypes: [
...(entityType === "association"
? [
{
dedicatedTypes: [
{
dtsType: "ID",
},
],
},
]
: [...info.types]),
{
dedicatedTypes: [
{
dtsType: "null",
},
],
},
],
});
// RemoveAll method
createMethod({
docOptions: {
entityType,
entityName: info.name,
action: actions.RemoveAll,
description: entityType === "aggregation" ? `Additionally, it unregisters them from the hosting UIArea.` : undefined,
},
returnValueTypes,
});
// IndexOf method (only for aggregations)
if (entityType === "aggregation") {
createMethod({
params: {
[singularName]: {
types: [...info.types],
},
},
docOptions: {
entityType,
entityName: info.name,
action: actions.IndexOf,
typeInfo: info.types[0],
},
returnValueTypes: [
{
dedicatedTypes: [
{
dtsType: "int",
},
],
},
],
});
}
};
/**
* Generates all necessary components for a property
*
* @param {string} propertyName - The name of the property
*/
function writeProperties(propertyName) {
const propertyInfo = classDef._dts.properties[propertyName];
propertyInfo.needsBindingString = !propertyInfo.readonly;
processTypesAndAddImports(propertyInfo.types);
// properties of type "string" don't need the binding string syntax `{${string}}`
propertyInfo.types.forEach((type) => {
if (type.dtsType === "string") {
propertyInfo.needsBindingString = false;
}
});
// Generate getter method
createMethod({
docOptions: {
entityType: "property",
entityName: propertyInfo.name,
action: actions.Get,
description: propertyInfo.description,
defaultValueDesc: propertyInfo.defaultValue,
},
returnValueTypes: propertyInfo.types,
});
if (propertyInfo.readonly) {
delete classDef._dts.properties[propertyName];
} else {
// Add default import for properties
writeImports("sap/ui/base/ManagedObject", "PropertyBindingInfo");
// Generate setter method
createMethod({
params: {
[propertyInfo.name]: {
types: [
...propertyInfo.types,
{
dedicatedTypes: [
{
dtsType: "null",
},
],
},
],
optional: propertyInfo.defaultValue !== undefined,
},
},
docOptions: {
entityType: "property",
entityName: propertyInfo.name,
action: actions.Set,
description: propertyInfo.description,
defaultValueDesc: propertyInfo.defaultValue,
},
});
}
}
/**
* Generates all necessary components for an aggregation
*
* @param {string} aggregationName - The name of the aggregation
*/
function writeAggregations(aggregationName) {
const aggregationInfo = classDef._dts.aggregations[aggregationName];
// Add default imports
writeImports("sap/ui/base/ManagedObject", "AggregationBindingInfo");
writeImports("sap/ui/core/library", "ID");
// Process types and add imports
processTypesAndAddImports(aggregationInfo.types);
const multiple = !!aggregationInfo.types[0].multiple;
// Generate getter method
createMethod({
docOptions: {
entityType: "aggregation",
entityName: aggregationInfo.name,
action: actions.Get,
description: aggregationInfo.description,
},
returnValueTypes: aggregationInfo.types,
});
// Generate destroy method
createMethod({
docOptions: {
entityType: "aggregation",
entityName: aggregationInfo.name,
action: actions.Destroy,
},
});
if (multiple) {
// Create methods for multiple items
createMultipleItemMethods(aggregationInfo, "aggregation", aggregationInfo.types);
} else {
logger.error(`Aggregation '${aggregationInfo.name}' of web component '${classDef.name}' accepts only a single control. This should not be the case and should be checked.`);
}
}
/**
* Generates all necessary components for an association
*
* @param {string} associationName - The name of the association
*/
function writeAssociations(associationName) {
const associationInfo = classDef._dts.associations[associationName];
// Add default import
writeImports("sap/ui/core/library", "ID");
// Process types and add imports
processTypesAndAddImports(associationInfo.types);
const multiple = !!associationInfo.types[0].multiple;
const returnValueTypes = [
{
multiple,
dedicatedTypes: [
{
dtsType: "ID",
},
],
},
];
// Generate getter method
createMethod({
docOptions: {
entityType: "association",
entityName: associationInfo.name,
action: actions.Get,
typeInfo: associationInfo.types[0],
},
returnValueTypes,
});
if (multiple) {
// Create methods for multiple items
createMultipleItemMethods(associationInfo, "association", returnValueTypes);
} else {
// Generate setter method for single association
createMethod({
params: {
[associationInfo.name]: {
types: associationInfo.types,
},
},
docOptions: {
entityType: "association",
entityName: associationInfo.name,
action: actions.Set,
},
});
}
}
/**
* Generates all necessary components for an event
*
* @param {string} eventName - The name of the event
*/
function writeEvents(eventName) {
const eventInfo = classDef._dts.events[eventName];
// Add default import for events
writeImports("sap/ui/base/Event", "Event", { default: true });
// Process event parameters and add imports
for (const paramName in eventInfo.parameters) {
processTypesAndAddImports(eventInfo.parameters[paramName].types);
}
// Create common parameters for event methods
const detachParams = {
["fnFunction"]: {
types: [
{
dedicatedTypes: [
{
dtsType: `(p1: ${generateEventSettings(classDef.name, eventInfo.name)}) => void`,
},
],
},
],
description: "The function to be called when the event occurs",
},
["oListener"]: {
types: [
{
dedicatedTypes: [
{
dtsType: "object",
},
],
},
],
description: `Context object to call the event handler with. Defaults to this ${classDef.name} itself`,
optional: true,
},
};
// Generate detach method
createMethod({
params: detachParams,
docOptions: {
entityType: "event",
entityName: eventInfo.name,
action: actions.Detach,
description: "The passed function and listener object must match the ones used for event registration.",
className: classDef.name,
},
});
// Generate attach method
createMethod({
params: Object.assign({}, detachParams),
docOptions: {
entityType: "event",
entityName: eventInfo.name,
action: actions.Attach,
description: `When called, the context of the event handler (its \`this\`) will be bound to \`oListener\` if specified,\n * otherwise it will be bound to this \`${classDef.name}\` itself.`,
className: classDef.name,
},
});
// Generate attach method
createMethod({
methodSuffix: "WithOptionalOdata",
params: Object.assign(
{
["oData"]: {
types: [
{
dedicatedTypes: [
{
dtsType: "object",
},
],
},
],
description: "An application-specific payload object that will be passed to the event handler along with the event object when firing the event",
},
},
detachParams,
),
docOptions: {
entityType: "event",
entityName: eventInfo.name,
action: actions.Attach,
description: `When called, the context of the event handler (its \`this\`) will be bound to \`oListener\` if specified,\n * otherwise it will be bound to this \`${classDef.name}\` itself.`,
className: classDef.name,
},
});
}
/**
* Processes method information, adding imports and formatting descriptions
*
* @param {string} methodName - The name of the method to process
*/
function writeMethods(methodName) {
const methodInfo = classDef._dts.methods[methodName];
methodInfo.methodName = methodInfo.name;
// Process parameter types and add imports
for (const paramName in methodInfo.params) {
processTypesAndAddImports(methodInfo.params[paramName].types);
}
// Format description if present
if (methodInfo.description) {
methodInfo.description = baseTemplate({
description: methodInfo.description,
});
}
}
// Main execution flow
if (classDef.superclass) {
// Set up superclass information
let superclassSettings;
if (WebComponentRegistryHelper.isUI5Element(classDef.superclass) || WebComponentRegistryHelper.isWebComponent(classDef.superclass)) {
superclassSettings = {
class: "WebComponent",
module: "sap/ui/core/webc/WebComponent",
};
} else {
superclassSettings = {
class: classDef.superclass.name,
module: classDef.superclass._ui5QualifiedNameSlashes,
};
}
// Handle implements
classDef._dts.implementsMarker = classDef._ui5implements.filter((interface) => !classDef._unkownInterfaces.has(interface.name));
if (classDef._dts.implementsMarker.length) {
classDef._dts.implements = classDef._dts.implementsMarker.map((interfaceDef) => interfaceDef.name);
for (const interfaceDef of classDef._dts.implementsMarker) {
let importOptions;
if (interfaceDef.name === classDef.name) {
importOptions = {
alternativeImportName: `${interfaceDef.package.replace(/\//g, "_")}_${interfaceDef.name}`,
};
classDef._dts.implements = [importOptions.alternativeImportName];
}
writeImports(interfaceDef.package, interfaceDef.name, importOptions);
}
}
// Add superclass imports
writeImports(superclassSettings.module, superclassSettings.class, { default: true });
writeImports(superclassSettings.module, `$${superclassSettings.class}Settings`);
classDef._dts.extends = superclassSettings.class;
// Process all entity types
for (const method in classDef._dts.methods) {
writeMethods(method);
}
for (const property in classDef._dts.properties) {
writeProperties(property);
}
for (const aggregation in classDef._dts.aggregations) {
writeAggregations(aggregation);
}
for (const association in classDef._dts.associations) {
writeAssociations(association);
}
for (const event in classDef._dts.events) {
writeEvents(event);
}
}
},
/**
* Generically collects DTS information for the given class and entity, e.g. classDef = Button, entityType = "properties", entityDef = {...}
* @param {WebComponentRegistry.ClassDef} classDef
* @param {string} entityType the type of entity that should be written, e.g. "properties", "aggregations", ...
* @param {object} entityDef the entity definition, e.g. property or aggregation info
*/
writeDts(classDef, entityType, entityDef) {
// we clone the objects here to prevent accidental side effects
classDef._dts[entityType][entityDef.name] = Object.assign({}, entityDef);
},
/**
* Updates the Data Type Structure (DTS) information for given entities within the class definition.
* This method can be used to move an association to aggregations or to change the type of an event,
* among other updates.
*
* @param {object} classDef - The class definition object. This represents the structure of the class
* where the updates will be applied.
*
* @param {object} source - The source object containing the entity and its name to be updated.
*
* Example:
* ```javascript
* {
* entity: 'property', // e.g., property, aggregation, association, event, method
* name: 'entityName'
* }
* ```
*
* @param {object} target - The target object specifying the desired changes for the entity. This object
* should include the name of the entity along with any properties that need to be updated.
* Properties of the parameter which do not change are optional. If a property is omitted in the
* target, the property from the source will be used.
*
* Example:
* ```javascript
* {
* entity: 'property', // e.g., property, aggregation, association, event, method
* name: 'entityName',
* types: [{
* dtsType: 'string',
* ui5Type: 'string'
* }, ...]
* }
* ```
*/
updateDts(classDef, source, target) {
const targetEntity = target.entity || source.entity;
const targetName = target.name || source.name;
classDef._dts[targetEntity][targetName] = classDef._dts[source.entity][source.name];
classDef._dts[targetEntity][targetName].name = targetName;
if (target.types) {
classDef._dts[targetEntity][targetName].types = target.types;
}
if (target.entity || target.name) {
delete classDef._dts[source.entity][source.name];
}
},
};
module.exports = DTSSerializer;