@ui5/builder 5.0.0-alpha.3 → 5.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,33 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
4
4
 
5
5
  A list of unreleased changes can be found [here](https://github.com/SAP/ui5-builder/compare/v4.0.11...HEAD).
6
6
 
7
+ ## [5.0.0-alpha.4](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.3...builder-v5.0.0-alpha.4) (2026-03-24)
8
+
9
+
10
+ ### Features
11
+
12
+ * **builder:** Add dynamic section for API reference during JSDoc build ([47970f7](https://github.com/UI5/cli/commit/47970f74c4adf63625e4f330f19090083f563f82))
13
+ * **builder:** Add hierarchical subsection support for API reference sections ([49a3ef5](https://github.com/UI5/cli/commit/49a3ef569074ff84b5f6dfc2cfc619cd1654c2c8))
14
+ * **builder:** Automatically add [@lends](https://github.com/lends) tag to extend calls during JSdoc build ([3547f42](https://github.com/UI5/cli/commit/3547f425b33663f7e5e2618a6d876fe018058d99))
15
+ * **builder:** In JSDoc build, push the deprecation of namespaces down to the contained APIs ([71c08f2](https://github.com/UI5/cli/commit/71c08f2bb1d2cfee34ab9ca2e9e6042b5e67ae0c))
16
+ * **builder:** Mark cross-references to typedefs in the api.json (SDK variant) ([a474700](https://github.com/UI5/cli/commit/a47470050e804192bfab857f6c224faa6e445e10))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **builder:** Improve JSDoc support for "module:" syntax ([aca05f0](https://github.com/UI5/cli/commit/aca05f0b75561f33842ccb30248ede3a82f2ae46))
22
+ * **builder:** JSDoc: avoid errors when symbol has no events ([6e517e6](https://github.com/UI5/cli/commit/6e517e6c7f480784b93e37e94dcf74e0a689427f))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @ui5/fs bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
30
+ * @ui5/logger bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
31
+ * devDependencies
32
+ * @ui5/project bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
33
+
7
34
  ## [5.0.0-alpha.3](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.2...builder-v5.0.0-alpha.3) (2026-02-13)
8
35
 
9
36
 
@@ -58,7 +58,7 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
58
58
  * Excludes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later
59
59
  * exclusion overrides an earlier inclusion, and vice versa.
60
60
  *
61
- * <caption>List of modules as glob patterns that should be in- or excluded</caption>
61
+ * <i>Example:</i>
62
62
  *
63
63
  * ```js
64
64
  * // Includes everything from "some/path/to/module/",
@@ -47,10 +47,10 @@ const typeParser = new TypeParser();
47
47
  * @param {string} sLibraryFile Path to the .library file of the library, used to extract further documentation information
48
48
  * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or
49
49
  * an array of paths of those files
50
- * @param {string} sFAQDir Path to the directory containing the sources for the FAQ section in APiRef
50
+ * @param {string} sSectionsDir Path to the directory containing the sources for the sections (FAQ, documentation, etc.) in the API Reference
51
51
  * @returns {Promise} A Promise that resolves after the transformation has been completed
52
52
  */
53
- function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sFAQDir, options) {
53
+ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sSectionsDir, options) {
54
54
  const fs = options && options.fs || require("fs");
55
55
  const returnOutputFiles = options && !!options.returnOutputFiles;
56
56
 
@@ -59,7 +59,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
59
59
  log.info(" output file: " + sOutputFile);
60
60
  log.info(" library file: " + sLibraryFile);
61
61
  log.info(" dependency dir: " + vDependencyAPIFiles);
62
- log.info(" FAQ src dir: " + sFAQDir);
62
+ log.info(" sections src dir: " + sSectionsDir);
63
63
  if (options && options.fs) {
64
64
  log.info("Using custom fs.");
65
65
  }
@@ -156,9 +156,10 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
156
156
  * UI5Types: ["sap.ui.core.Control"] // skip template if its value is "${0}" (default value)
157
157
  * }
158
158
  * @param {string} sComplexType
159
+ * @param {Object<string,object>} oAllDependentAPIs Map of entity names to their symbol
159
160
  * @returns {{template=: string, UI5Types=: string[]}}
160
161
  */
161
- function parseUI5Types(sComplexType) {
162
+ function parseUI5Types(sComplexType, oAllDependentAPIs) {
162
163
  let oParsed;
163
164
  try {
164
165
  oParsed = typeParser.parseSimpleTypes(sComplexType, isUI5Type);
@@ -176,6 +177,14 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
176
177
 
177
178
  if (oParsed.simpleTypes?.length) { // it can be empty if none of the included simple types satisfied the filter function
178
179
  result.UI5Types = oParsed.simpleTypes;
180
+
181
+ // check whether the type refers to a typedef and set a marker
182
+ if (result.template == null && result.UI5Types.length === 1) {
183
+ const symbol = oAllDependentAPIs[result.UI5Types[0]];
184
+ if (symbol?.kind === "typedef" && Array.isArray(symbol.properties)) {
185
+ result.refersToTypedef = true;
186
+ }
187
+ }
179
188
  }
180
189
 
181
190
  return result;
@@ -266,6 +275,8 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
266
275
  * @param {object} oChainObject chain object
267
276
  */
268
277
  const transformApiJson = function (oChainObject) {
278
+ const {oAllDependentAPIs} = oChainObject;
279
+
269
280
  // Function is a copy from: LibraryInfo.js => LibraryInfo.prototype._getActualComponent => "match" inline method
270
281
  function matchComponent(sModuleName, sPattern) {
271
282
  sModuleName = sModuleName.toLowerCase();
@@ -435,7 +446,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
435
446
  // Types
436
447
  oParameter.types = [];
437
448
  if (oParameter.type) {
438
- oParameter.typeInfo = parseUI5Types(oParameter.type);
449
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
439
450
  // Keep file size in check
440
451
  delete oParameter.type;
441
452
  }
@@ -520,7 +531,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
520
531
  // Type
521
532
  if (oSymbol.kind !== "enum") { // enum properties don't have an own type
522
533
  if (oProperty.type) {
523
- oProperty.typeInfo = parseUI5Types(oProperty.type);
534
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
524
535
  // Keep file size in check
525
536
  delete oProperty.type;
526
537
  }
@@ -560,7 +571,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
560
571
 
561
572
  // Type
562
573
  if (oProperty.type) {
563
- oProperty.typeInfo = parseUI5Types(oProperty.type);
574
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
564
575
  // Keep file size in check
565
576
  delete oProperty.type;
566
577
  }
@@ -657,7 +668,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
657
668
  Object.keys(aParams).forEach((sParam) => {
658
669
  const sSince = aParams[sParam].since;
659
670
  const oDeprecated = aParams[sParam].deprecated;
660
- const oEvtInSymbol = oSymbol.events.find((e) => e.name === oEvent.name);
671
+ const oEvtInSymbol = oSymbol.events?.find((e) => e.name === oEvent.name);
661
672
  const oParamInSymbol = oEvtInSymbol && oEvtInSymbol.parameters[0] &&
662
673
  oEvtInSymbol.parameters[0].parameterProperties &&
663
674
  oEvtInSymbol.parameters[0].parameterProperties.getParameters &&
@@ -687,7 +698,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
687
698
 
688
699
  // Link Enabled
689
700
  if (oSetting.type) {
690
- oSetting.typeInfo = parseUI5Types(oSetting.type);
701
+ oSetting.typeInfo = parseUI5Types(oSetting.type, oAllDependentAPIs);
691
702
  delete oSetting.type; // Keep file size in check
692
703
  }
693
704
 
@@ -756,7 +767,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
756
767
  oEvent.parameters.forEach((oParameter) => {
757
768
 
758
769
  if (oParameter.type) {
759
- oParameter.typeInfo = parseUI5Types(oParameter.type);
770
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
760
771
  delete oParameter.type; // Keep file size in check
761
772
  }
762
773
 
@@ -789,7 +800,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
789
800
  if (oSymbol.methods) {
790
801
 
791
802
  // Pre-process methods
792
- methods.buildMethodsModel(oSymbol.methods);
803
+ methods.buildMethodsModel(oSymbol.methods, oAllDependentAPIs);
793
804
 
794
805
  oSymbol.methods.forEach((oMethod) => {
795
806
 
@@ -950,6 +961,14 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
950
961
  }
951
962
 
952
963
  function loadDependencyLibraryFiles (oChainObject) {
964
+ const oAllDependentAPIs = oChainObject.oAllDependentAPIs = Object.create(null);
965
+ // add symbols from current library
966
+ if (oChainObject.fileData.symbols) {
967
+ for (const oSymbol of oChainObject.fileData.symbols) {
968
+ oAllDependentAPIs[oSymbol.name] = oSymbol;
969
+ }
970
+ }
971
+
953
972
  if (!oChainObject.aDependentLibraryFiles) {
954
973
  return oChainObject;
955
974
  }
@@ -978,6 +997,12 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
978
997
  // In this case we don't add it to the dependency list to skip double iteration.
979
998
  if (oData && oChainObject.fileData.library !== oData.library) {
980
999
  oDependentAPIs[oData.library] = oData.symbols;
1000
+
1001
+ if (Array.isArray(oData.symbols)) {
1002
+ for (const oSymbol of oData.symbols) {
1003
+ oAllDependentAPIs[oSymbol.name] = oSymbol;
1004
+ }
1005
+ }
981
1006
  }
982
1007
  });
983
1008
 
@@ -987,22 +1012,69 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
987
1012
  }
988
1013
 
989
1014
  /**
990
- * Check for existence of FAQ data
991
- * (FAQ data must be defined as *.md files in the <code>sFAQDir</code>)
1015
+ * Check for existence of sections data
1016
+ * (Sections data must be defined as *.md files in the <code>sSectionsDir</code>)
992
1017
  * and add a boolean flag in case it exists
993
1018
  *
994
1019
  * @param oChainObject chain object
995
1020
  */
996
- function addFlagsForFAQData(oChainObject) {
997
- if (!sFAQDir) {
1021
+ function addFlagsForSectionsData(oChainObject) {
1022
+ if (!sSectionsDir) {
998
1023
  return oChainObject;
999
1024
  }
1025
+
1000
1026
  const slibName = oChainObject.fileData.library;
1027
+
1001
1028
  oChainObject.fileData.symbols.forEach(function(symbol) {
1002
- const sfile = symbol.name.replace(slibName, "").replace(/[.]/g, "/") + ".md";
1003
- if (fs.existsSync(path.join(sFAQDir, sfile))) {
1004
- symbol.hasFAQ = true;
1029
+ const sComponentPath = symbol.name.replace(slibName, "").replace(/^[.]/, "").replace(/[.]/g, "/");
1030
+ if (!sComponentPath) {
1031
+ return;
1005
1032
  }
1033
+
1034
+ const componentDir = path.join(sSectionsDir, sComponentPath);
1035
+ if (fs.existsSync(componentDir)) {
1036
+ try {
1037
+ const dirContents = fs.readdirSync(componentDir, { withFileTypes: true });
1038
+
1039
+ // Separate files and directories in one pass
1040
+ const mdFiles = [];
1041
+ const dirNames = new Set();
1042
+ dirContents.forEach((dirent) => {
1043
+ if (dirent.isFile() && dirent.name.endsWith('.md')) {
1044
+ mdFiles.push(dirent.name.replace(/\.md$/, ''));
1045
+ } else if (dirent.isDirectory()) {
1046
+ dirNames.add(dirent.name);
1047
+ }
1048
+ });
1049
+
1050
+ // Build customSections array with subsection support
1051
+ const customSections = mdFiles.map(function(sectionName) {
1052
+ // Check if there's a matching directory for this section
1053
+ if (dirNames.has(sectionName)) {
1054
+ try {
1055
+ const subsectionFiles = fs.readdirSync(path.join(componentDir, sectionName), { withFileTypes: true })
1056
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
1057
+ .map((dirent) => dirent.name.replace(/\.md$/, ''));
1058
+
1059
+ if (subsectionFiles.length > 0) {
1060
+ return { name: sectionName, hasSubsections: true, subsections: subsectionFiles };
1061
+ }
1062
+ } catch (error) {
1063
+ log.error('Error scanning subsection directory:', path.join(componentDir, sectionName), error);
1064
+ }
1065
+ }
1066
+ return sectionName;
1067
+ });
1068
+
1069
+ if (customSections.length > 0) {
1070
+ symbol.customSections = customSections;
1071
+ }
1072
+
1073
+ } catch (error) {
1074
+ log.error('Error scanning component sections directory:', componentDir, error);
1075
+ }
1076
+ }
1077
+
1006
1078
  });
1007
1079
  return oChainObject;
1008
1080
  }
@@ -2175,7 +2247,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2175
2247
  * Adjusts methods info so that it can be easily displayed in a table
2176
2248
  * @param aMethods - the methods array initially coming from the server
2177
2249
  */
2178
- buildMethodsModel: function (aMethods) {
2250
+ buildMethodsModel: function (aMethods, oAllDependentAPIs) {
2179
2251
  var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) {
2180
2252
  if (oParameter.parameterProperties) {
2181
2253
  Object.keys(oParameter.parameterProperties).forEach(function (sProperty) {
@@ -2185,7 +2257,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2185
2257
 
2186
2258
  // Handle types
2187
2259
  if (oProperty.type) {
2188
- oProperty.typeInfo = parseUI5Types(oProperty.type);
2260
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
2189
2261
  // Keep file size in check
2190
2262
  delete oProperty.type;
2191
2263
  }
@@ -2217,7 +2289,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2217
2289
  if (oMethod.parameters) {
2218
2290
  oMethod.parameters.forEach(function (oParameter) {
2219
2291
  if (oParameter.type) {
2220
- oParameter.typeInfo = parseUI5Types(oParameter.type);
2292
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
2221
2293
  // Keep file size in check
2222
2294
  delete oParameter.type;
2223
2295
  }
@@ -2238,7 +2310,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2238
2310
  // Handle return values
2239
2311
  if (oMethod.returnValue && oMethod.returnValue.type) {
2240
2312
  // Handle types
2241
- oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type);
2313
+ oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type, oAllDependentAPIs);
2242
2314
  }
2243
2315
 
2244
2316
  });
@@ -2375,6 +2447,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2375
2447
  inputFile: sInputFile,
2376
2448
  outputFile: sOutputFile,
2377
2449
  libraryFile: sLibraryFile,
2450
+ sectionsDir: sSectionsDir,
2378
2451
  aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null
2379
2452
  };
2380
2453
 
@@ -2387,7 +2460,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2387
2460
  .then(getAPIJSONPromise)
2388
2461
  .then(loadDependencyLibraryFiles)
2389
2462
  .then(transformApiJson)
2390
- .then(addFlagsForFAQData)
2463
+ .then(addFlagsForSectionsData)
2391
2464
  .then(createApiRefApiJson);
2392
2465
  return p;
2393
2466
 
@@ -73,7 +73,8 @@ const path = require('jsdoc/path');
73
73
  const logger = require('jsdoc/util/logger');
74
74
  const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || env.opts.sapui5 || {};
75
75
  const escope = require("escope");
76
- const {isSemVer} = require("./template/utils/versionUtil");
76
+ const templatePath = path.getResourcePath(env.opts.template) || "./template";
77
+ const {isSemVer} = require(`${templatePath}/utils/versionUtil`);
77
78
 
78
79
  /* ---- logging ---- */
79
80
 
@@ -1220,11 +1221,13 @@ function collectClassInfo(extendCall, classDoclet) {
1220
1221
  if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) {
1221
1222
  baseType = classDoclet.augments[0];
1222
1223
  }
1224
+ const jsdocClassName = classDoclet?.kind === "class" ? classDoclet.alias || classDoclet.longname : undefined;
1225
+
1223
1226
  if ( isMemberExpression(extendCall.callee) ) {
1224
1227
  const baseCandidate = getResolvedObjectName(extendCall.callee.object);
1225
1228
  if ( baseCandidate && baseType == null ) {
1226
1229
  baseType = baseCandidate;
1227
- } else if ( baseCandidate !== baseType ) {
1230
+ } else if ( baseCandidate !== toRuntimeMetadataName(baseType) ) {
1228
1231
  future(`documented base type '${baseType}' doesn't match technical base type '${baseCandidate}'`);
1229
1232
  }
1230
1233
  }
@@ -1283,6 +1286,32 @@ function collectClassInfo(extendCall, classDoclet) {
1283
1286
  }
1284
1287
 
1285
1288
  const classInfoNode = extendCall.arguments[1];
1289
+ if (classInfoNode?.type === Syntax.ObjectExpression && jsdocClassName && addLeadingCommentNode) {
1290
+ // Take care of the @lends comment only
1291
+ // - when the class info is an object literal (otherwise lends makes no sense)
1292
+ // - when a class name is found (it might differ from the runtime metadata name)
1293
+ // - when the entity is documented as a class (or else '.prototype' would be wrong)
1294
+ const classInfoComment = getLeadingCommentNode(classInfoNode);
1295
+ if (classInfoComment == null) {
1296
+ // If the class info object doesn't have a @lends tag, add it
1297
+ const raw = `/** @lends ${jsdocClassName}.prototype */`;
1298
+ info(`lends comment injected for class '${jsdocClassName}'`);
1299
+ addLeadingCommentNode(classInfoNode, {
1300
+ type: "CommentBlock",
1301
+ raw,
1302
+ value: raw.slice(2,-2),
1303
+ loc: classInfoNode.loc
1304
+ });
1305
+ } else {
1306
+ // Otherwise check whether the name matches the name of the described class
1307
+ const doclet = new Doclet(getRawComment(classInfoComment), {});
1308
+ // The name after the @lends is surprisingly stored in the 'alias' property
1309
+ if (doclet.alias !== `${jsdocClassName}.prototype`) {
1310
+ future(`lends comment '${doclet.alias}' doesn't match documented class name '${jsdocClassName}'`);
1311
+ }
1312
+ }
1313
+ }
1314
+
1286
1315
  const classInfoMap = createPropertyMap(classInfoNode);
1287
1316
  if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) {
1288
1317
  warning(`class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '${classInfoMap.metadata.value.type}'.`);
@@ -1772,7 +1801,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
1772
1801
  }
1773
1802
 
1774
1803
  function name(prefix, n, _static) {
1775
- return oClassInfo.name + rname(prefix, n, _static);
1804
+ return jsdocClassName + rname(prefix, n, _static);
1776
1805
  }
1777
1806
 
1778
1807
  /*
@@ -1884,6 +1913,18 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
1884
1913
  const p = m ? m.index : -1;
1885
1914
  const hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0;
1886
1915
  const classAccess = doclet?.access ?? "public";
1916
+
1917
+ // If doclet exists and has an alias or longname, use that when building the fully qualified
1918
+ // names of methods or events.
1919
+ // Do this only when the doclet is of kind "class". Many TypeScript classes unfortunately
1920
+ // use a broken pattern with @namespace (needs to be deprecated in babel-plugin-transform-modules-ui5)
1921
+ const jsdocClassName =
1922
+ (doclet?.kind === "class" ? doclet.alias || doclet.longname : undefined) || oClassInfo.name;
1923
+ if (jsdocClassName !== oClassInfo.name) {
1924
+ info("jsdoc class name differs from runtime metadata name", jsdocClassName, oClassInfo.name);
1925
+ }
1926
+
1927
+ const displayClassName = oClassInfo.name;
1887
1928
  const visibility = createVisibilityTags(classAccess, doclet?.__ui5?.stakeholders);
1888
1929
  const thisClass = 'this'; // oClassInfo.name
1889
1930
 
@@ -2007,7 +2048,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2007
2048
  }
2008
2049
 
2009
2050
  newJSDoc([
2010
- "Returns a metadata object for class " + oClassInfo.name + ".",
2051
+ "Returns a metadata object for class " + displayClassName + ".",
2011
2052
  "",
2012
2053
  "@returns {sap.ui.base.Metadata} Metadata object describing this class",
2013
2054
  visibility,
@@ -2018,7 +2059,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2018
2059
 
2019
2060
  if ( !oClassInfo["final"] ) {
2020
2061
  newJSDoc([
2021
- "Creates a new subclass of class " + oClassInfo.name + " with name <code>sClassName</code>",
2062
+ "Creates a new subclass of class " + displayClassName + " with name <code>sClassName</code>",
2022
2063
  "and enriches it with the information contained in <code>oClassInfo</code>.",
2023
2064
  "",
2024
2065
  "<code>oClassInfo</code> might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.",
@@ -2304,7 +2345,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2304
2345
  "",
2305
2346
  info.enableEventBubbling ? "This event bubbles up the control hierarchy." : "",
2306
2347
  "",
2307
- "@name " + oClassInfo.name + "#" + n,
2348
+ "@name " + jsdocClassName + "#" + n,
2308
2349
  "@event",
2309
2350
  devStateTags,
2310
2351
  "@param {sap.ui.base.Event} oControlEvent",
@@ -2321,10 +2362,10 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2321
2362
  newJSDoc(lines);
2322
2363
 
2323
2364
  newJSDoc([
2324
- "Attaches event handler <code>fnFunction</code> to the " + link + " event of this <code>" + oClassInfo.name + "</code>.",
2365
+ "Attaches event handler <code>fnFunction</code> to the " + link + " event of this <code>" + displayClassName + "</code>.",
2325
2366
  "",
2326
2367
  "When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code> if specified, ",
2327
- "otherwise it will be bound to this <code>" + oClassInfo.name + "</code> itself.",
2368
+ "otherwise it will be bound to this <code>" + displayClassName + "</code> itself.",
2328
2369
  "",
2329
2370
  !newStyle && info.doc ? info.doc : "",
2330
2371
  "",
@@ -2333,7 +2374,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2333
2374
  "@param {function(sap.ui.base.Event):void}",
2334
2375
  " fnFunction The function to be called when the event occurs",
2335
2376
  "@param {object}",
2336
- " [oListener] Context object to call the event handler with. Defaults to this <code>" + oClassInfo.name + "</code> itself",
2377
+ " [oListener] Context object to call the event handler with. Defaults to this <code>" + displayClassName + "</code> itself",
2337
2378
  "",
2338
2379
  "@returns {" + thisClass + "} Reference to <code>this</code> in order to allow method chaining",
2339
2380
  devStateTags,
@@ -2342,7 +2383,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2342
2383
  "@function"
2343
2384
  ]);
2344
2385
  newJSDoc([
2345
- "Detaches event handler <code>fnFunction</code> from the " + link + " event of this <code>" + oClassInfo.name + "</code>.",
2386
+ "Detaches event handler <code>fnFunction</code> from the " + link + " event of this <code>" + displayClassName + "</code>.",
2346
2387
  "",
2347
2388
  "The passed function and listener object must match the ones used for event registration.",
2348
2389
  "",
@@ -2439,6 +2480,7 @@ function toRuntimeMetadataName(longname) {
2439
2480
 
2440
2481
  let isDocComment;
2441
2482
  let getLeadingCommentNode;
2483
+ let addLeadingCommentNode;
2442
2484
 
2443
2485
  // JSDoc added the node type <code>Syntax.File</code> with the same change that activated Babylon
2444
2486
  // See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869
@@ -2464,6 +2506,15 @@ if ( Syntax.File === 'File' ) {
2464
2506
  return undefined;
2465
2507
  };
2466
2508
 
2509
+ addLeadingCommentNode = function(node, comment) {
2510
+ node.leadingComments ??= [];
2511
+ let index = node.leadingComments.length;
2512
+ while (index > 0 && node.leadingComments[index - 1].type !== 'CommentBlock') {
2513
+ index--;
2514
+ }
2515
+ node.leadingComments.splice(index, 0, comment);
2516
+ }
2517
+
2467
2518
  } else {
2468
2519
 
2469
2520
  // JSDoc versions before 3.5.0
@@ -2498,6 +2549,9 @@ if ( Syntax.File === 'File' ) {
2498
2549
 
2499
2550
  return comment;
2500
2551
  };
2552
+
2553
+ // not implemented for espree (no automatic injection of @lends comments)
2554
+ addLeadingCommentNode = undefined;
2501
2555
  }
2502
2556
 
2503
2557
  //--- comment related functions that are independent from the JSdoc version
@@ -231,6 +231,7 @@ function publish(symbolSet) {
231
231
 
232
232
  // now resolve relationships
233
233
  const aRootNamespaces = createNamespaceTree(allSymbols);
234
+ pushDownDeprecations(allSymbols);
234
235
  createInheritanceTree(allSymbols);
235
236
  collectMembers(allSymbols);
236
237
  mergeEventDocumentation(allSymbols);
@@ -374,6 +375,33 @@ function makeNamespace(memberof) {
374
375
  ]);
375
376
  }
376
377
 
378
+ function pushDownDeprecations() {
379
+
380
+ function pushDown(oSymbol, sInheritedDeprecation) {
381
+ if (oSymbol == null || !isFirstClassSymbol(oSymbol)) {
382
+ return;
383
+ }
384
+ const sOwnDeprecation = oSymbol.deprecated;
385
+ if (sOwnDeprecation == null && sInheritedDeprecation != null) {
386
+ oSymbol.deprecated = sInheritedDeprecation;
387
+ info(`mark ${oSymbol.longname} as deprecated (${sInheritedDeprecation})`);
388
+ } else if (sOwnDeprecation != null) {
389
+ const info = extractSince(sOwnDeprecation);
390
+ const sinceBecause = info.since ? `As of version ${info.since}, because`: `Because`;
391
+ sInheritedDeprecation = `${sinceBecause} it is part of the deprecated package ${oSymbol.longname}`;
392
+ }
393
+ oSymbol.__ui5.children?.forEach((oChild) => pushDown(oChild, sInheritedDeprecation));
394
+ }
395
+
396
+ // w/o a library name we can't start with the library namespace
397
+ if (!templateConf.uilib) {
398
+ return;
399
+ }
400
+
401
+ // start the push down at the library namespace to ensure locality
402
+ pushDown(lookup(templateConf.uilib));
403
+ }
404
+
377
405
  //---- inheritance hierarchy ----------------------------------------------------------------------------
378
406
 
379
407
  /*
@@ -55,7 +55,7 @@ function getCreatorOptions(projectName) {
55
55
  * Therefore it is also not supported in combination with self-contained build.
56
56
  * </p>
57
57
  *
58
- * <caption>sample resources.json</caption>
58
+ * <i>sample resources.json</i>
59
59
  *
60
60
  * ```js
61
61
  * const resourcesJson = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ui5/builder",
3
- "version": "5.0.0-alpha.3",
3
+ "version": "5.0.0-alpha.4",
4
4
  "description": "UI5 CLI - Builder",
5
5
  "author": {
6
6
  "name": "SAP SE",
@@ -91,8 +91,8 @@
91
91
  },
92
92
  "dependencies": {
93
93
  "@jridgewell/sourcemap-codec": "^1.5.5",
94
- "@ui5/fs": "^5.0.0-alpha.3",
95
- "@ui5/logger": "^5.0.0-alpha.3",
94
+ "@ui5/fs": "^5.0.0-alpha.4",
95
+ "@ui5/logger": "^5.0.0-alpha.4",
96
96
  "cheerio": "1.0.0",
97
97
  "escape-unicode": "^0.3.0",
98
98
  "escope": "^4.0.0",
@@ -109,9 +109,9 @@
109
109
  "devDependencies": {
110
110
  "@istanbuljs/esm-loader-hook": "^0.3.0",
111
111
  "@jridgewell/trace-mapping": "^0.3.31",
112
- "@ui5/project": "^5.0.0-alpha.3",
112
+ "@ui5/project": "^5.0.0-alpha.4",
113
113
  "ava": "^6.4.1",
114
- "cross-env": "^7.0.3",
114
+ "cross-env": "^10.1.0",
115
115
  "eslint": "^9.36.0",
116
116
  "esmock": "^2.7.3",
117
117
  "line-column": "^1.0.2",