@ui5/webcomponents-tools 0.0.0-07d38e78e → 0.0.0-093de5dd1

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +1030 -0
  2. package/LICENSE.txt +201 -0
  3. package/README.md +7 -10
  4. package/assets-meta.js +1 -5
  5. package/bin/dev.js +3 -2
  6. package/bin/ui5nps.js +274 -0
  7. package/components-package/eslint.js +59 -31
  8. package/components-package/nps.js +98 -75
  9. package/components-package/vite.config.js +7 -11
  10. package/components-package/wdio.js +12 -5
  11. package/icons-collection/nps.js +30 -21
  12. package/lib/amd-to-es6/index.js +15 -10
  13. package/lib/cem/cem.js +12 -0
  14. package/lib/cem/custom-elements-manifest.config.mjs +90 -45
  15. package/lib/cem/event.mjs +69 -32
  16. package/lib/cem/merge.mjs +220 -0
  17. package/lib/cem/patch/@custom-elements-manifest/analyzer/cli.js +128 -0
  18. package/lib/cem/patch/@custom-elements-manifest/analyzer/package.json +59 -0
  19. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/browser-entrypoint.js +23 -0
  20. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/create.js +117 -0
  21. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/arrow-function.js +26 -0
  22. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/class-jsdoc.js +157 -0
  23. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/classes.js +20 -0
  24. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createArrowFunction.js +17 -0
  25. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createAttribute.js +24 -0
  26. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createClass.js +301 -0
  27. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createClassField.js +26 -0
  28. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createFunctionLike.js +73 -0
  29. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createMixin.js +33 -0
  30. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/createVariable.js +22 -0
  31. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/creators/handlers.js +338 -0
  32. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/custom-elements-define-calls.js +90 -0
  33. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/exports.js +156 -0
  34. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/function-like.js +24 -0
  35. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/mixins.js +29 -0
  36. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/reexported-wrapped-mixin-exports.js +84 -0
  37. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/analyse-phase/variables.js +34 -0
  38. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/collect-phase/collect-imports.js +101 -0
  39. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/catalyst/catalyst.js +11 -0
  40. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/catalyst/controller.js +34 -0
  41. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/catalyst-major-2/catalyst.js +11 -0
  42. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/catalyst-major-2/controller.js +34 -0
  43. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/decorators/attr.js +53 -0
  44. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/decorators/custom-element-decorator.js +36 -0
  45. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/fast/fast.js +7 -0
  46. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/lit/lit.js +13 -0
  47. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/lit/member-denylist.js +21 -0
  48. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/lit/method-denylist.js +20 -0
  49. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/lit/property-decorator.js +94 -0
  50. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/lit/static-properties.js +121 -0
  51. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/lit/utils.js +66 -0
  52. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/framework-plugins/stencil/stencil.js +129 -0
  53. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/index.js +80 -0
  54. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/link-phase/cleanup-classes.js +25 -0
  55. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/link-phase/field-denylist.js +22 -0
  56. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/link-phase/method-denylist.js +25 -0
  57. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/post-processing/apply-inheritance.js +78 -0
  58. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/post-processing/is-custom-element.js +34 -0
  59. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/post-processing/link-class-to-tagname.js +27 -0
  60. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/post-processing/remove-unexported-declarations.js +23 -0
  61. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/features/post-processing/resolve-initializers.js +52 -0
  62. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/ast-helpers.js +186 -0
  63. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/cli-helpers.js +164 -0
  64. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/exports.js +44 -0
  65. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/find-external-manifests.js +67 -0
  66. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/imports.js +25 -0
  67. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/index.js +71 -0
  68. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/jsdoc.js +19 -0
  69. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/manifest-helpers.js +194 -0
  70. package/lib/cem/patch/@custom-elements-manifest/analyzer/src/utils/mixins.js +112 -0
  71. package/lib/cem/schema-internal.json +65 -0
  72. package/lib/cem/types-internal.d.ts +14 -2
  73. package/lib/cem/utils.mjs +69 -30
  74. package/lib/cem/validate.js +61 -55
  75. package/lib/chokidar/chokidar.js +28 -0
  76. package/lib/copy-and-watch/index.js +105 -97
  77. package/lib/copy-list/index.js +16 -10
  78. package/lib/create-icons/index.js +24 -19
  79. package/lib/create-illustrations/index.js +49 -27
  80. package/lib/create-new-component/{tsFileContentTemplate.js → Component.js} +12 -9
  81. package/lib/create-new-component/ComponentTemplate.js +12 -0
  82. package/lib/create-new-component/index.js +13 -12
  83. package/lib/css-processors/css-processor-components.mjs +74 -59
  84. package/lib/css-processors/css-processor-themes.mjs +85 -62
  85. package/lib/css-processors/shared.mjs +5 -35
  86. package/lib/dev-server/{dev-server.js → dev-server.mjs} +26 -14
  87. package/lib/dev-server/virtual-index-html-plugin.js +24 -20
  88. package/lib/eslint/eslint.js +44 -0
  89. package/lib/generate-js-imports/illustrations.js +53 -54
  90. package/lib/generate-json-imports/i18n.js +56 -36
  91. package/lib/generate-json-imports/themes.js +27 -14
  92. package/lib/hbs2ui5/RenderTemplates/LitRenderer.js +12 -7
  93. package/lib/hbs2ui5/index.js +3 -3
  94. package/lib/i18n/defaults.js +15 -9
  95. package/lib/i18n/toJSON.js +38 -12
  96. package/lib/icons-hash/icons-hash.mjs +149 -0
  97. package/lib/remove-dev-mode/remove-dev-mode.mjs +38 -24
  98. package/lib/rimraf/rimraf.js +31 -0
  99. package/lib/scoping/get-all-tags.js +9 -2
  100. package/lib/test-runner/test-runner.js +56 -48
  101. package/lib/vite-bundler/vite-bundler.mjs +35 -0
  102. package/package.json +22 -19
  103. package/tsconfig.json +18 -0
  104. package/lib/css-processors/css-processor-component-styles.mjs +0 -48
  105. package/lib/dev-server/ssr-dom-shim-loader.js +0 -26
@@ -4,6 +4,7 @@ import path from "path";
4
4
  import fs from 'fs';
5
5
  import {
6
6
  getDeprecatedStatus,
7
+ getExperimentalStatus,
7
8
  getSinceStatus,
8
9
  getPrivacyStatus,
9
10
  getReference,
@@ -26,6 +27,17 @@ import { generateCustomData } from "cem-plugin-vs-code-custom-data-generator";
26
27
  import { customElementJetBrainsPlugin } from "custom-element-jet-brains-integration";
27
28
 
28
29
  const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
30
+ let aliasMap = {};
31
+
32
+ const devMode = process.env.UI5_CEM_MODE === "dev";
33
+ try {
34
+ aliasMap = JSON.parse(fs.readFileSync("./.ui5-cem-aliases.json"));
35
+ } catch (e) {
36
+ if (devMode) {
37
+ console.warn("No .ui5-cem-aliases.json file found. Continuing without aliases.");
38
+ }
39
+ }
40
+
29
41
 
30
42
  const extractClassNodeJSDoc = node => {
31
43
  const fileContent = node.getFullText();
@@ -64,12 +76,23 @@ function processClass(ts, classNode, moduleDoc) {
64
76
  currClass.customElement = !!customElementDecorator || className === "UI5Element" || undefined;
65
77
  currClass.kind = "class";
66
78
  currClass.deprecated = getDeprecatedStatus(classParsedJsDoc);
79
+ currClass._ui5experimental = getExperimentalStatus(classParsedJsDoc);
67
80
  currClass._ui5since = getSinceStatus(classParsedJsDoc);
68
81
  currClass._ui5privacy = getPrivacyStatus(classParsedJsDoc);
69
82
  currClass._ui5abstract = hasTag(classParsedJsDoc, "abstract") ? true : undefined;
70
83
  currClass.description = normalizeDescription(classParsedJsDoc.description || findTag(classParsedJsDoc, "class")?.description);
71
84
  currClass._ui5implements = findAllTags(classParsedJsDoc, "implements")
72
- .map(tag => getReference(ts, normalizeTagType(tag.type), classNode, moduleDoc.path))
85
+ .map(tag => {
86
+ const correctInterfaceDescription = classNode?.heritageClauses?.some(heritageClause => {
87
+ return heritageClause?.types?.some(type => type.expression?.text === normalizeTagType(tag.type));
88
+ });
89
+
90
+ if (!correctInterfaceDescription) {
91
+ logDocumentationError(moduleDoc.path, `@interface {${tag.type}} tag is used, but the class doesn't implement the corresponding interface`)
92
+ }
93
+
94
+ return getReference(ts, normalizeTagType(tag.type), classNode, moduleDoc.path)
95
+ })
73
96
  .filter(Boolean);
74
97
 
75
98
 
@@ -77,6 +100,10 @@ function processClass(ts, classNode, moduleDoc) {
77
100
  const superclassTag = findTag(classParsedJsDoc, "extends");
78
101
  currClass.superclass = getReference(ts, superclassTag.name, classNode, moduleDoc.path);
79
102
 
103
+ if (classNode?.heritageClauses?.[0]?.types?.[0]?.expression?.text !== superclassTag.name) {
104
+ logDocumentationError(moduleDoc.path, `@extends ${superclassTag.name} is used, but the class doesn't extend the corresponding superclass`)
105
+ }
106
+
80
107
  if (currClass.superclass?.name === "UI5Element") {
81
108
  currClass.customElement = true;
82
109
  }
@@ -111,9 +138,13 @@ function processClass(ts, classNode, moduleDoc) {
111
138
  }
112
139
 
113
140
  // Events
114
- currClass.events = findAllDecorators(classNode, "event")
141
+ currClass.events = findAllDecorators(classNode, ["event", "eventStrict"])
115
142
  ?.map(event => processEvent(ts, event, classNode, moduleDoc));
116
143
 
144
+ const filename = classNode.getSourceFile().fileName;
145
+ const sourceFile = typeProgram.getSourceFile(filename);
146
+ const tsProgramClassNode = sourceFile.statements.find(statement => ts.isClassDeclaration(statement) && statement.name?.text === classNode.name?.text);
147
+
117
148
  // Slots (with accessor), methods and fields
118
149
  for (let i = 0; i < (currClass.members?.length || 0); i++) {
119
150
  const member = currClass.members[i];
@@ -168,17 +199,21 @@ function processClass(ts, classNode, moduleDoc) {
168
199
  const propertyDecorator = findDecorator(classNodeMember, "property");
169
200
 
170
201
  if (propertyDecorator) {
171
- member._ui5validator = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => ["validator", "type"].includes(property.name.text))?.initializer?.text || "String";
172
202
  member._ui5noAttribute = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => property.name.text === "noAttribute")?.initializer?.kind === ts.SyntaxKind.TrueKeyword || undefined;
173
203
  }
174
204
 
175
- if (currClass.customElement && member.privacy === "public" && !propertyDecorator?.expression?.arguments[0]?.properties?.find(property => property.name.text === "multiple") && !["object"].includes(member._ui5validator?.toLowerCase())) {
176
- const filename = classNode.getSourceFile().fileName;
177
- const sourceFile = typeProgram.getSourceFile(filename);
178
- const tsProgramClassNode = sourceFile.statements.find(statement => ts.isClassDeclaration(statement) && statement.name?.text === classNode.name?.text);
205
+ if (currClass.customElement && member.privacy === "public") {
179
206
  const tsProgramMember = tsProgramClassNode.members.find(m => ts.isPropertyDeclaration(m) && m.name?.text === member.name);
180
207
  const attributeValue = typeChecker.typeToString(typeChecker.getTypeAtLocation(tsProgramMember), tsProgramMember);
181
208
 
209
+ if (attributeValue === "boolean" && member.default === "true") {
210
+ logDocumentationError(moduleDoc.path, `Boolean properties must be initialzed to false. [${member.name}] property of class [${className}] is intialized to \`true\``)
211
+ }
212
+
213
+ if (!member.type) {
214
+ logDocumentationError(moduleDoc.path, `Public properties must have type. The type of [${member.name}] property is not determinated automatically. Please check it.`)
215
+ }
216
+
182
217
  currClass.attributes.push({
183
218
  description: member.description,
184
219
  name: toKebabCase(member.name),
@@ -293,6 +328,7 @@ function processInterface(ts, interfaceNode, moduleDoc) {
293
328
  kind: "interface",
294
329
  name: interfaceName,
295
330
  description: normalizeDescription(interfaceParsedJsDoc?.description),
331
+ _ui5experimental: getExperimentalStatus(interfaceParsedJsDoc),
296
332
  _ui5privacy: getPrivacyStatus(interfaceParsedJsDoc),
297
333
  _ui5since: getSinceStatus(interfaceParsedJsDoc),
298
334
  deprecated: getDeprecatedStatus(interfaceParsedJsDoc),
@@ -313,6 +349,7 @@ function processEnum(ts, enumNode, moduleDoc) {
313
349
  kind: "enum",
314
350
  name: enumName,
315
351
  description: normalizeDescription(enumJSdoc?.comment),
352
+ _ui5experimental: getExperimentalStatus(enumParsedJsDoc),
316
353
  _ui5privacy: getPrivacyStatus(enumParsedJsDoc),
317
354
  _ui5since: getSinceStatus(enumParsedJsDoc),
318
355
  deprecated: getDeprecatedStatus(enumParsedJsDoc) || undefined,
@@ -425,14 +462,6 @@ export default {
425
462
  }
426
463
  },
427
464
  moduleLinkPhase({ moduleDoc }) {
428
- for (let i = 0; i < moduleDoc.declarations.length; i++) {
429
- const shouldRemove = processPublicAPI(moduleDoc.declarations[i]) || ["function", "variable"].includes(moduleDoc.declarations[i].kind)
430
- if (shouldRemove) {
431
- moduleDoc.declarations.splice(i, 1);
432
- i--;
433
- }
434
- }
435
-
436
465
  moduleDoc.path = moduleDoc.path?.replace(/^src/, "dist").replace(/\.ts$/, ".js");
437
466
 
438
467
  moduleDoc.exports = moduleDoc.exports.
@@ -456,41 +485,57 @@ export default {
456
485
  })
457
486
  }
458
487
  })
488
+ },
489
+ packageLinkPhase({ customElementsManifest }) {
490
+ customElementsManifest.modules.forEach(moduleDoc => {
491
+ for (let i = 0; i < moduleDoc.declarations.length; i++) {
492
+ const shouldRemove = processPublicAPI(moduleDoc.declarations[i]) || ["function", "variable"].includes(moduleDoc.declarations[i].kind)
493
+ if (shouldRemove) {
494
+ moduleDoc.declarations.splice(i, 1);
495
+ i--;
496
+ }
497
+ }
459
498
 
460
- const typeReferences = new Set();
461
- const registerTypeReference = reference => typeReferences.add(JSON.stringify(reference))
462
-
463
- moduleDoc.declarations.forEach(declaration => {
464
- ["events", "slots", "members"].forEach(memberType => {
465
- declaration[memberType]?.forEach(member => {
466
- if (member.type?.references) {
467
- member.type.references.forEach(registerTypeReference)
468
- } else if (member._ui5type?.references) {
469
- member._ui5type.references.forEach(registerTypeReference)
470
- } else if (member.kind === "method") {
471
- member.return?.type?.references?.forEach(registerTypeReference)
472
-
473
- member.parameters?.forEach(parameter => {
474
- parameter.type?.references?.forEach(registerTypeReference)
475
- })
476
- }
477
- })
499
+ moduleDoc.declarations.forEach(declaration => {
500
+ if (declaration.superclass?.name && aliasMap[declaration.superclass.name]) {
501
+ declaration.superclass.name = aliasMap[declaration.superclass.name];
502
+ }
478
503
  })
479
- });
480
504
 
481
- typeReferences.forEach(reference => {
482
- reference = JSON.parse(reference);
483
- if (reference.package === packageJSON?.name && reference.module === moduleDoc.path) {
484
- const hasExport = moduleDoc.exports.some(e => e.declaration?.name === reference.name && e.declaration?.module === reference.module)
505
+ const typeReferences = new Set();
506
+ const registerTypeReference = reference => typeReferences.add(JSON.stringify(reference))
507
+
508
+ moduleDoc.declarations.forEach(declaration => {
509
+ ["events", "slots", "members"].forEach(memberType => {
510
+ declaration[memberType]?.forEach(member => {
511
+ if (member.type?.references) {
512
+ member.type.references.forEach(registerTypeReference)
513
+ } else if (member._ui5type?.references) {
514
+ member._ui5type.references.forEach(registerTypeReference)
515
+ } else if (member.kind === "method") {
516
+ member.return?.type?.references?.forEach(registerTypeReference)
517
+
518
+ member.parameters?.forEach(parameter => {
519
+ parameter.type?.references?.forEach(registerTypeReference)
520
+ })
521
+ }
522
+ })
523
+ })
524
+ });
525
+
526
+ typeReferences.forEach(reference => {
527
+ reference = JSON.parse(reference);
528
+ if (reference.package === packageJSON?.name && reference.module === moduleDoc.path) {
529
+ const hasExport = moduleDoc.exports.some(e => e.declaration?.name === reference.name && e.declaration?.module === reference.module)
485
530
 
486
- if (!hasExport) {
487
- logDocumentationError(moduleDoc.path?.replace(/^dist/, "src").replace(/\.js$/, ".ts"), `Type '${reference.name}' is used to describe a public API but is not exported.`,)
531
+ if (!hasExport) {
532
+ logDocumentationError(moduleDoc.path?.replace(/^dist/, "src").replace(/\.js$/, ".ts"), `Type '${reference.name}' is used to describe a public API but is not exported.`,)
533
+ }
488
534
  }
489
- }
490
- })
491
- },
492
- packageLinkPhase({ context }) {
493
- if (context.dev) {
535
+ })
536
+ });
537
+
538
+ if (devMode) {
494
539
  displayDocumentationErrors();
495
540
  }
496
541
  }
package/lib/cem/event.mjs CHANGED
@@ -16,19 +16,10 @@ import {
16
16
  } from "./utils.mjs";
17
17
 
18
18
  const jsDocRegExp = /\/\*\*(.|\n)+?\s+\*\//;
19
+ const ASTFalseKeywordCode = 94;
19
20
 
20
21
  const getParams = (ts, eventDetails, commentParams, classNode, moduleDoc) => {
21
22
  return commentParams?.map(commentParam => {
22
- const decoratorParam = eventDetails?.find(prop => prop?.name?.text === commentParam?.name);
23
-
24
- if (!decoratorParam || !decoratorParam?.jsDoc?.[0]) {
25
- return;
26
- }
27
-
28
- const decoratorParamParsedComment = parse(decoratorParam?.jsDoc?.[0]?.getText?.(), { spacing: 'preserve' })[0];
29
-
30
- validateJSDocComment("eventParam", decoratorParamParsedComment, decoratorParam.name?.text, moduleDoc);
31
-
32
23
  const { typeName, name } = getType(normalizeTagType(commentParam?.type));
33
24
  let type;
34
25
 
@@ -47,12 +38,10 @@ const getParams = (ts, eventDetails, commentParams, classNode, moduleDoc) => {
47
38
  return {
48
39
  type,
49
40
  name: commentParam?.name,
50
- _ui5privacy: getPrivacyStatus(decoratorParamParsedComment),
41
+ _ui5privacy: "public",
51
42
  description: normalizeDescription(commentParam?.description),
52
- _ui5since: getSinceStatus(decoratorParamParsedComment),
53
- deprecated: getDeprecatedStatus(decoratorParamParsedComment),
54
43
  };
55
- }).filter(pair => !!pair);
44
+ });
56
45
  };
57
46
 
58
47
  function processEvent(ts, event, classNode, moduleDoc) {
@@ -77,30 +66,32 @@ function processEvent(ts, event, classNode, moduleDoc) {
77
66
  const privacy = findTag(eventParsedComment, ["public", "private", "protected"])?.tag || "private";
78
67
  const sinceTag = findTag(eventParsedComment, "since");
79
68
  const commentParams = findAllTags(eventParsedComment, "param");
80
- const allowPreventDefault = hasTag(eventParsedComment, "allowPreventDefault") || undefined;
81
69
  const description = normalizeDescription(eventParsedComment?.description);
82
70
  const native = hasTag(eventParsedComment, "native");
83
- const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties;
84
-
85
- if (event?.expression?.arguments?.[1] && !event?.expression?.typeArguments) {
86
- logDocumentationError(moduleDoc.path, `Event details for event '${name}' must be described using generics. Add type via generics to the decorator: @event<TypeForDetails>("${name}", {details}).`)
87
- }
71
+ const eventArgs = event?.expression?.arguments;
72
+ let eventBubbles;
73
+ let eventCancelable;
74
+ let eventDetails;
75
+
76
+ eventArgs && eventArgs.forEach(arg => {
77
+ arg.properties?.forEach(prop => {
78
+ if (prop.name?.text === "bubbles") {
79
+ eventBubbles = prop.initializer.kind === ASTFalseKeywordCode ? false : true;
80
+ } else if (prop.name?.text === "cancelable") {
81
+ eventCancelable = prop.initializer.kind === ASTFalseKeywordCode ? false : true;
82
+ } else if (prop.name?.text === "detail") {
83
+ eventDetails = prop.initializer?.properties;
84
+ }
85
+ });
86
+ });
88
87
 
89
88
  result.description = description;
90
- result._ui5allowPreventDefault = allowPreventDefault;
89
+ result._ui5Cancelable = eventCancelable !== undefined ? eventCancelable : false;
90
+ result._ui5allowPreventDefault = result._ui5Cancelable;
91
+ result._ui5Bubbles = eventBubbles !== undefined ? eventBubbles : false;
91
92
 
92
93
  if (native) {
93
94
  result.type = { text: "Event" };
94
- } else if (event?.expression?.typeArguments) {
95
- const typesText = event?.expression?.typeArguments.map(type => type.typeName?.text).filter(Boolean).join(" | ");
96
- const typeRefs = (getTypeRefs(ts, event.expression)
97
- ?.map(e => getReference(ts, e, event, moduleDoc.path)).filter(Boolean)) || [];
98
-
99
- result.type = { text: `CustomEvent<${typesText}>` };
100
-
101
- if (typeRefs.length) {
102
- result.type.references = typeRefs;
103
- }
104
95
  }
105
96
 
106
97
  if (privacy) {
@@ -121,7 +112,53 @@ function processEvent(ts, event, classNode, moduleDoc) {
121
112
  : sinceTag.name;
122
113
  }
123
114
 
124
- if (commentParams && eventDetails) {
115
+ const eventDetailType = classNode.members?.find(member => {
116
+ return ts.isPropertyDeclaration(member) && member.name.text === "eventDetails"
117
+ })?.type;
118
+ const eventDetailRef = eventDetailType?.members?.find(member => member.name.text === name) || eventDetailType?.types?.[eventDetailType?.types?.length - 1]?.members?.find(member => member.name.text === name);
119
+ const hasGeneric = !!event?.expression?.typeArguments
120
+
121
+ if (commentParams.length) {
122
+ if (eventDetailRef && hasGeneric) {
123
+ logDocumentationError(moduleDoc.path, `Event details for event '${name}' has to be defined either with generic or with eventDetails.`)
124
+ } else if (eventDetails) {
125
+ if (hasGeneric) {
126
+ const typesText = event?.expression?.typeArguments.map(type => type.typeName?.text).filter(Boolean).join(" | ");
127
+ const typeRefs = (getTypeRefs(ts, event.expression)
128
+ ?.map(e => getReference(ts, e, event, moduleDoc.path)).filter(Boolean)) || [];
129
+
130
+ result.type = { text: `CustomEvent<${typesText}>` };
131
+
132
+ if (typeRefs.length) {
133
+ result.type.references = typeRefs;
134
+ }
135
+ } else if (eventDetailRef) {
136
+ const typesText = eventDetailRef?.type?.typeName?.text;
137
+ const typeRefs = (getTypeRefs(ts, eventDetailRef)
138
+ ?.map(e => getReference(ts, e, event, moduleDoc.path)).filter(Boolean)) || [];
139
+
140
+ result.type = { text: `CustomEvent<${typesText}>` };
141
+
142
+ if (typeRefs.length) {
143
+ result.type.references = typeRefs;
144
+ }
145
+ } else {
146
+ logDocumentationError(moduleDoc.path, `Event details for event '${name}' must be described using generics. Add type via generics to the decorator: @event<TypeForDetails>("${name}", {details}).`)
147
+ }
148
+ } else if (eventDetailRef) {
149
+ const typesText = eventDetailRef?.type?.typeName?.text;
150
+ const typeRefs = (getTypeRefs(ts, eventDetailRef)
151
+ ?.map(e => getReference(ts, e, event, moduleDoc.path)).filter(Boolean)) || [];
152
+
153
+ result.type = { text: `CustomEvent<${typesText}>` };
154
+
155
+ if (typeRefs.length) {
156
+ result.type.references = typeRefs;
157
+ }
158
+ } else {
159
+ logDocumentationError(moduleDoc.path, `Event details for event '${name}' must be described.`)
160
+ }
161
+
125
162
  result._ui5parameters = getParams(ts, eventDetails, commentParams, classNode, moduleDoc);
126
163
  }
127
164
 
@@ -0,0 +1,220 @@
1
+ import { pathToFileURL } from "url";
2
+ import path from "path";
3
+ import { createRequire } from 'module';
4
+ import { readFile, writeFile } from "fs/promises";
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ const UI5_BASE_CLASS = "UI5Element";
9
+
10
+ const main = async (argv) => {
11
+ let customElementsPath = null;
12
+ const CACHED_CEMS = new Map();
13
+ const DECLARATION_PACKAGE = new WeakMap();
14
+ const DECLARATION_MODULE = new WeakMap();
15
+
16
+ function removeInheritedFrom(obj) {
17
+ if (obj === null || typeof obj !== 'object') {
18
+ return obj;
19
+ }
20
+
21
+ if (Array.isArray(obj)) {
22
+ return obj.map(item => removeInheritedFrom(item));
23
+ }
24
+
25
+ const result = {};
26
+ for (const [key, value] of Object.entries(obj)) {
27
+ if (key === 'inheritedFrom') {
28
+ continue;
29
+ }
30
+ result[key] = removeInheritedFrom(value);
31
+ }
32
+ return result;
33
+ }
34
+
35
+ async function readPackageJson(filePath) {
36
+ try {
37
+ return JSON.parse(await readFile(filePath, "utf-8"));
38
+ } catch (error) {
39
+ throw new Error(`Failed to read package.json at ${filePath}: ${error.message}`);
40
+ }
41
+ }
42
+
43
+ async function loadPackageJson(depName) {
44
+ try {
45
+ // First try the standard require method (works when exports includes package.json)
46
+ const pkg = require(`${depName}/package.json`);
47
+ const pkgPath = require.resolve(`${depName}/package.json`);
48
+ return { path: path.dirname(pkgPath), pkg };
49
+ } catch (e) {
50
+ // If that fails, resolve the package path and read package.json directly
51
+ try {
52
+ const packagePath = require.resolve(depName);
53
+ let currentDir = path.dirname(packagePath);
54
+
55
+ // Navigate up to find package.json (the resolved path might be deep in dist/ or similar)
56
+ while (currentDir !== path.parse(currentDir).root) {
57
+ try {
58
+ const pkgPath = path.join(currentDir, 'package.json');
59
+ const content = await readFile(pkgPath, 'utf-8');
60
+ const pkg = JSON.parse(content);
61
+
62
+ // Verify this is the correct package.json by checking the name
63
+ if (pkg.name === depName) {
64
+ return { path: currentDir, pkg };
65
+ }
66
+ } catch {
67
+ // Continue searching up the directory tree
68
+ }
69
+ currentDir = path.dirname(currentDir);
70
+ }
71
+ } catch (resolveError) {
72
+ // console.warn(`Could not resolve ${depName}:`, resolveError.message);
73
+ }
74
+ return null;
75
+ }
76
+ }
77
+
78
+ async function collectThirdPartyCem() {
79
+ const packageJSONPath = path.resolve(process.cwd(), "package.json");
80
+ const packageJSON = await readPackageJson(packageJSONPath);
81
+
82
+ const dependencyKeys = Object.keys(packageJSON).filter(key => key.toLowerCase().includes("dependencies"));
83
+ const dependencies = dependencyKeys.flatMap(key => Object.keys(packageJSON[key]));
84
+
85
+ const thirdPartCEM = (await Promise.all(dependencies.map(async dep => {
86
+ const result = await loadPackageJson(dep);
87
+ if (!result?.pkg?.customElements) return null;
88
+
89
+ return {
90
+ path: result.path,
91
+ name: dep,
92
+ cem: result.pkg.customElements
93
+ };
94
+ }))).filter(Boolean);
95
+
96
+ await Promise.all(thirdPartCEM.map(async dep => {
97
+ const cemPath = path.resolve(dep.path, dep.cem);
98
+ try {
99
+ const cemContent = JSON.parse(await readFile(cemPath, "utf-8"));
100
+ CACHED_CEMS.set(dep.name, cemContent);
101
+ } catch (error) {
102
+ console.warn(`Failed to read CEM for ${dep.name} from ${cemPath}: ${error.message}`);
103
+ }
104
+ }));
105
+ }
106
+
107
+ async function readCurrentCEM() {
108
+ const packageJSONPath = path.resolve(process.cwd(), "package.json");
109
+ const packageJSON = await readPackageJson(packageJSONPath);
110
+
111
+ if (!packageJSON?.customElements) {
112
+ return null;
113
+ }
114
+
115
+ customElementsPath = packageJSON.customElements;
116
+ const cemPath = path.resolve(process.cwd(), customElementsPath);
117
+
118
+ try {
119
+ const cemContent = JSON.parse(await readFile(cemPath, "utf-8"));
120
+ CACHED_CEMS.set(packageJSON.name, cemContent);
121
+ return cemContent;
122
+ } catch (error) {
123
+ throw new Error(`Failed to read CEM from ${cemPath}: ${error.message}`);
124
+ }
125
+ }
126
+
127
+ async function resolveReference(ref) {
128
+ const pkg = CACHED_CEMS.get(ref.package);
129
+
130
+ if (!pkg) {
131
+ return null;
132
+ }
133
+
134
+ const mod = (pkg.modules || []).find(m => m.path === ref.module);
135
+
136
+ if (!mod) {
137
+ return null;
138
+ }
139
+
140
+ const declaration = (mod.declarations || []).find(d => d.name === ref.name);
141
+
142
+ if (!declaration) {
143
+ return null;
144
+ }
145
+
146
+ DECLARATION_PACKAGE.set(declaration, ref.package);
147
+ DECLARATION_MODULE.set(declaration, ref.module);
148
+
149
+ return resolveDeclaration(declaration);
150
+ }
151
+
152
+ async function resolveDeclaration(declaration) {
153
+ if (!declaration.superclass || declaration.superclass.name === UI5_BASE_CLASS) {
154
+ return [declaration];
155
+ }
156
+
157
+ const superclassDeclarations = await resolveReference(declaration.superclass);
158
+ return [declaration, superclassDeclarations].flat().filter(Boolean);
159
+ }
160
+
161
+ const merge = async () => {
162
+ const currentCEM = await readCurrentCEM();
163
+ if (!currentCEM) {
164
+ throw new Error("No custom elements manifest found in current project");
165
+ }
166
+
167
+ await collectThirdPartyCem();
168
+
169
+ const modules = currentCEM.modules || [];
170
+
171
+ for (const mod of modules) {
172
+ const declarations = (mod.declarations || []).filter(d => d.kind === "class");
173
+
174
+ for (const declaration of declarations) {
175
+ const declarationHierarchy = await resolveDeclaration(declaration);
176
+ const allKeys = declarationHierarchy.flatMap(dec => Object.keys(dec));
177
+ const uniqueKeys = [...new Set(allKeys)];
178
+ const arrayKeys = uniqueKeys
179
+ .filter(key => !key.startsWith("_ui5"))
180
+ .filter(key => declarationHierarchy.some(dec => Array.isArray(dec[key])));
181
+
182
+ for (const key of arrayKeys) {
183
+ const allItems = declarationHierarchy.flatMap(dec => dec[key] || []);
184
+
185
+ // Remove duplicates based on name property
186
+ const seen = new Set();
187
+ declaration[key] = allItems.filter(item => {
188
+ if (!item.name) return true;
189
+ if (seen.has(item.name)) return false;
190
+ seen.add(item.name);
191
+ return true;
192
+ });
193
+ }
194
+ }
195
+ }
196
+
197
+ const cleanedCEM = removeInheritedFrom(currentCEM);
198
+ const outputPath = path.resolve(process.cwd(), customElementsPath);
199
+
200
+ try {
201
+ await writeFile(outputPath, JSON.stringify(cleanedCEM, null, 2), "utf-8");
202
+ console.log(`Successfully merged CEM to ${outputPath}`);
203
+ } catch (error) {
204
+ throw new Error(`Failed to write merged CEM to ${outputPath}: ${error.message}`);
205
+ }
206
+ };
207
+
208
+ await merge();
209
+ }
210
+
211
+ const filePath = process.argv[1];
212
+ const fileUrl = pathToFileURL(filePath).href;
213
+
214
+ if (import.meta.url === fileUrl) {
215
+ main(process.argv)
216
+ }
217
+
218
+ export default {
219
+ _ui5mainFn: main
220
+ }