@ui5/webcomponents-tools 0.0.0-38861c872 → 0.0.0-38f83ffef

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 (48) hide show
  1. package/CHANGELOG.md +1121 -0
  2. package/README.md +2 -5
  3. package/assets-meta.js +3 -11
  4. package/components-package/eslint.js +59 -31
  5. package/components-package/nps.js +48 -26
  6. package/components-package/vite.config.js +7 -11
  7. package/components-package/wdio.js +415 -405
  8. package/icons-collection/nps.js +2 -2
  9. package/lib/amd-to-es6/index.js +102 -0
  10. package/lib/amd-to-es6/no-remaining-require.js +33 -0
  11. package/lib/cem/custom-elements-manifest.config.mjs +129 -55
  12. package/lib/cem/event.mjs +75 -21
  13. package/lib/cem/schema-internal.json +71 -0
  14. package/lib/cem/types-internal.d.ts +564 -785
  15. package/lib/cem/types.d.ts +520 -655
  16. package/lib/cem/utils.mjs +115 -47
  17. package/lib/cem/validate.js +41 -37
  18. package/lib/create-icons/index.js +13 -10
  19. package/lib/create-illustrations/index.js +44 -9
  20. package/lib/create-new-component/{tsFileContentTemplate.js → Component.js} +13 -10
  21. package/lib/create-new-component/ComponentTemplate.js +12 -0
  22. package/lib/create-new-component/index.js +14 -22
  23. package/lib/css-processors/css-processor-components.mjs +3 -2
  24. package/lib/css-processors/css-processor-themes.mjs +2 -7
  25. package/lib/css-processors/scope-variables.mjs +3 -0
  26. package/lib/css-processors/shared.mjs +2 -22
  27. package/lib/dev-server/{dev-server.js → dev-server.mjs} +4 -4
  28. package/lib/dev-server/virtual-index-html-plugin.js +24 -20
  29. package/lib/generate-json-imports/i18n.js +46 -62
  30. package/lib/generate-json-imports/themes.js +13 -32
  31. package/lib/hbs2ui5/RenderTemplates/LitRenderer.js +12 -7
  32. package/lib/hbs2ui5/index.js +3 -3
  33. package/lib/i18n/defaults.js +3 -2
  34. package/lib/remove-dev-mode/remove-dev-mode.mjs +37 -0
  35. package/lib/scoping/get-all-tags.js +9 -2
  36. package/lib/scoping/lint-src.js +8 -7
  37. package/package.json +11 -8
  38. package/tsconfig.json +18 -0
  39. package/components-package/wdio.sync.js +0 -368
  40. package/lib/create-new-component/jsFileContentTemplate.js +0 -73
  41. package/lib/esm-abs-to-rel/index.js +0 -61
  42. package/lib/generate-custom-elements-manifest/index.js +0 -327
  43. package/lib/jsdoc/config.json +0 -29
  44. package/lib/jsdoc/configTypescript.json +0 -29
  45. package/lib/jsdoc/plugin.js +0 -2468
  46. package/lib/jsdoc/preprocess.js +0 -146
  47. package/lib/jsdoc/template/publish.js +0 -4120
  48. package/lib/replace-global-core/index.js +0 -25
@@ -41,8 +41,8 @@ const copyIconAssetsCommand = (options) => {
41
41
  const getScripts = (options) => {
42
42
  const createJSImportsCmd = createIconImportsCommand(options);
43
43
  const copyAssetsCmd = copyIconAssetsCommand(options);
44
- const tsCommand = options.typescript ? "tsc --build" : "";
45
- const tsCrossEnv = options.typescript ? "cross-env UI5_TS=true" : "";
44
+ const tsCommand = !options.legacy ? "tsc --build" : "";
45
+ const tsCrossEnv = !options.legacy ? "cross-env UI5_TS=true" : "";
46
46
 
47
47
  const scripts = {
48
48
  clean: "rimraf dist && rimraf src/generated",
@@ -0,0 +1,102 @@
1
+ const fs = require("fs").promises;
2
+ const path = require("path");
3
+ const basePath = process.argv[2];
4
+ const babelCore = require("@babel/core");
5
+ const babelParser = require("@babel/parser");
6
+ const babelGenerator = require("@babel/generator").default;
7
+ const replaceAsync = require('replace-in-file');
8
+
9
+ const convertSAPUIDefineToDefine = async (filePath) => {
10
+ return replaceAsync({
11
+ files: filePath,
12
+ processor: (input) => {
13
+ return input.replace("sap.ui.define", "define").replace(", /* bExport= */ false", "").replace(", /* bExport= */ true", "");
14
+ }
15
+ })
16
+ }
17
+
18
+ const convertAmdToEs6 = async (code) => {
19
+ return (await babelCore.transformAsync(code, {
20
+ plugins: [['babel-plugin-amd-to-esm', {}]]
21
+ })).code;
22
+ }
23
+
24
+ const convertAbsImportsToRelative = (filePath, code) => {
25
+ let changed = false;
26
+ // console.log("File processing started: ", srcPath);
27
+
28
+ if (code.includes("import(")) {
29
+ // esprima can't parse this, but it's from the project files
30
+ return;
31
+ }
32
+
33
+ const tree = babelParser.parse(code, { sourceType: "module" });
34
+ const importer = filePath.replace(basePath, "");
35
+ const importerDir = path.dirname(importer);
36
+ // console.log("Importer -> ", importer);
37
+
38
+ tree?.program?.body?.forEach(node => {
39
+ if (node.type === "ImportDeclaration") {
40
+ let importee = node.source.value;
41
+ // console.log(importee);
42
+ if (importee.startsWith(".")) {
43
+ // add .js extension if missing
44
+ if (!importee.endsWith(".js")) {
45
+ node.source.value += ".js"
46
+ changed = true;
47
+ }
48
+ return;
49
+ }
50
+ let importeeDir = path.dirname(importee);
51
+ let importeeFile = path.basename(importee);
52
+ let relativePath = path.relative(importerDir, importeeDir);
53
+ if (relativePath.length === 0) {
54
+ relativePath = "."
55
+ }
56
+ if (!relativePath.startsWith(".")) {
57
+ relativePath = "./" + relativePath;
58
+ }
59
+
60
+ relativePath = relativePath.replace(/\\/g, "/"); // the browser expects unix paths
61
+ let relativeImport = `${relativePath}/${importeeFile}.js`;
62
+ // console.log(importee + " --> " + relativeImport);
63
+ node.source.value = relativeImport;
64
+ changed = true;
65
+ }
66
+ });
67
+
68
+ return changed ? babelGenerator(tree).code : code;
69
+ }
70
+
71
+ const replaceGlobalCoreUsage = (filePath, code) => {
72
+ if (!filePath.includes("Configuration")) {
73
+ const replaced = code.replace(/sap\.ui\.getCore\(\)/g, `Core`);
74
+ return code !== replaced ? `import Core from 'sap/ui/core/Core';${replaced}` : code;
75
+ }
76
+
77
+ return code;
78
+ };
79
+
80
+ const transformAmdToES6Module = async (filePath) => {
81
+ await convertSAPUIDefineToDefine(filePath);
82
+
83
+ let code = (await fs.readFile(filePath)).toString();
84
+
85
+ code = await convertAmdToEs6(code);
86
+
87
+ code = replaceGlobalCoreUsage(filePath, code);
88
+
89
+ code = convertAbsImportsToRelative(filePath, code);
90
+
91
+ return fs.writeFile(filePath, code);
92
+ }
93
+
94
+ const transformAmdToES6Modules = async () => {
95
+ const { globby } = await import("globby");
96
+ const fileNames = await globby(basePath.replace(/\\/g, "/") + "**/*.js");
97
+ return Promise.all(fileNames.map(transformAmdToES6Module).filter(x => !!x));
98
+ };
99
+
100
+ transformAmdToES6Modules().then(() => {
101
+ console.log("Success: all amd modules are transformed to es6!");
102
+ });
@@ -0,0 +1,33 @@
1
+ const fs = require("fs").promises;
2
+ const path = require("path");
3
+ const basePath = process.argv[2];
4
+ const babelCore = require("@babel/core");
5
+ const babelParser = require("@babel/parser");
6
+ const babelGenerator = require("@babel/generator").default;
7
+ const walk = require("estree-walk");
8
+
9
+ const checkHasRequire = (filePath, code) => {
10
+ code = code.replace(/sap\.ui\.require/g, "unhandledRequire");
11
+
12
+ const tree = babelParser.parse(code, { sourceType: "module" });
13
+ walk(tree, {
14
+ CallExpression: function (node) {
15
+ if (node.type === "CallExpression" && node?.callee?.name === "unhandledRequire") {
16
+ throw new Error(`sap.ui.require found in ${filePath}`);
17
+ }
18
+ }
19
+ });
20
+ }
21
+
22
+ const checkFile = async (filePath) => {
23
+ let code = (await fs.readFile(filePath)).toString();
24
+ checkHasRequire(filePath, code);
25
+ }
26
+
27
+ const checkAll = async () => {
28
+ const { globby } = await import("globby");
29
+ const fileNames = await globby(basePath.replace(/\\/g, "/") + "**/*.js");
30
+ return Promise.all(fileNames.map(checkFile).filter(x => !!x));
31
+ };
32
+
33
+ checkAll();
@@ -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,
@@ -13,13 +14,20 @@ import {
13
14
  hasTag,
14
15
  findTag,
15
16
  findAllTags,
16
- getJSDocErrors,
17
17
  getTypeRefs,
18
18
  normalizeDescription,
19
19
  formatArrays,
20
20
  isClass,
21
- normalizeTagType
21
+ normalizeTagType,
22
+ logDocumentationError,
23
+ displayDocumentationErrors,
24
+ toKebabCase
22
25
  } from "./utils.mjs";
26
+ import { generateCustomData } from "cem-plugin-vs-code-custom-data-generator";
27
+ import { customElementJetBrainsPlugin } from "custom-element-jet-brains-integration";
28
+
29
+ const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
30
+ const devMode = process.env.UI5_CEM_MODE === "dev";
23
31
 
24
32
  const extractClassNodeJSDoc = node => {
25
33
  const fileContent = node.getFullText();
@@ -58,21 +66,43 @@ function processClass(ts, classNode, moduleDoc) {
58
66
  currClass.customElement = !!customElementDecorator || className === "UI5Element" || undefined;
59
67
  currClass.kind = "class";
60
68
  currClass.deprecated = getDeprecatedStatus(classParsedJsDoc);
69
+ currClass._ui5experimental = getExperimentalStatus(classParsedJsDoc);
61
70
  currClass._ui5since = getSinceStatus(classParsedJsDoc);
62
71
  currClass._ui5privacy = getPrivacyStatus(classParsedJsDoc);
63
72
  currClass._ui5abstract = hasTag(classParsedJsDoc, "abstract") ? true : undefined;
64
73
  currClass.description = normalizeDescription(classParsedJsDoc.description || findTag(classParsedJsDoc, "class")?.description);
65
74
  currClass._ui5implements = findAllTags(classParsedJsDoc, "implements")
66
- .map(tag => getReference(ts, normalizeTagType(tag.type), classNode, moduleDoc.path))
75
+ .map(tag => {
76
+ const correctInterfaceDescription = classNode?.heritageClauses?.some(heritageClause => {
77
+ return heritageClause?.types?.some(type => type.expression?.text === normalizeTagType(tag.type));
78
+ });
79
+
80
+ if (!correctInterfaceDescription) {
81
+ logDocumentationError(moduleDoc.path, `@interface {${tag.type}} tag is used, but the class doesn't implement the corresponding interface`)
82
+ }
83
+
84
+ return getReference(ts, normalizeTagType(tag.type), classNode, moduleDoc.path)
85
+ })
67
86
  .filter(Boolean);
68
87
 
69
88
 
70
89
  if (hasTag(classParsedJsDoc, "extends")) {
71
90
  const superclassTag = findTag(classParsedJsDoc, "extends");
72
- currClass.superclass = getReference(ts, superclassTag.name, classNode, moduleDoc.path);
91
+ const customElement = superclassTag.name === "HTMLElement";
73
92
 
74
- if (currClass.superclass?.name === "UI5Element") {
93
+ if (customElement) {
94
+ currClass.superclass = { name: "HTMLElement" }
75
95
  currClass.customElement = true;
96
+ } else {
97
+ currClass.superclass = getReference(ts, superclassTag.name, classNode, moduleDoc.path);
98
+
99
+ if (classNode?.heritageClauses?.[0]?.types?.[0]?.expression?.text !== superclassTag.name) {
100
+ logDocumentationError(moduleDoc.path, `@extends ${superclassTag.name} is used, but the class doesn't extend the corresponding superclass`)
101
+ }
102
+
103
+ if (currClass.superclass?.name === "UI5Element") {
104
+ currClass.customElement = true;
105
+ }
76
106
  }
77
107
  }
78
108
 
@@ -105,9 +135,13 @@ function processClass(ts, classNode, moduleDoc) {
105
135
  }
106
136
 
107
137
  // Events
108
- currClass.events = findAllDecorators(classNode, "event")
138
+ currClass.events = findAllDecorators(classNode, ["event", "eventStrict"])
109
139
  ?.map(event => processEvent(ts, event, classNode, moduleDoc));
110
140
 
141
+ const filename = classNode.getSourceFile().fileName;
142
+ const sourceFile = typeProgram.getSourceFile(filename);
143
+ const tsProgramClassNode = sourceFile.statements.find(statement => ts.isClassDeclaration(statement) && statement.name?.text === classNode.name?.text);
144
+
111
145
  // Slots (with accessor), methods and fields
112
146
  for (let i = 0; i < (currClass.members?.length || 0); i++) {
113
147
  const member = currClass.members[i];
@@ -162,7 +196,29 @@ function processClass(ts, classNode, moduleDoc) {
162
196
  const propertyDecorator = findDecorator(classNodeMember, "property");
163
197
 
164
198
  if (propertyDecorator) {
165
- member._ui5validator = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => ["validator", "type"].includes(property.name.text))?.initializer?.text || "String";
199
+ member._ui5noAttribute = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => property.name.text === "noAttribute")?.initializer?.kind === ts.SyntaxKind.TrueKeyword || undefined;
200
+ }
201
+
202
+ if (currClass.customElement && member.privacy === "public") {
203
+ const tsProgramMember = tsProgramClassNode.members.find(m => ts.isPropertyDeclaration(m) && m.name?.text === member.name);
204
+ const attributeValue = typeChecker.typeToString(typeChecker.getTypeAtLocation(tsProgramMember), tsProgramMember);
205
+
206
+ if (attributeValue === "boolean" && member.default === "true") {
207
+ logDocumentationError(moduleDoc.path, `Boolean properties must be initialzed to false. [${member.name}] property of class [${className}] is intialized to \`true\``)
208
+ }
209
+
210
+ if (!member.type) {
211
+ logDocumentationError(moduleDoc.path, `Public properties must have type. The type of [${member.name}] property is not determinated automatically. Please check it.`)
212
+ }
213
+
214
+ currClass.attributes.push({
215
+ description: member.description,
216
+ name: toKebabCase(member.name),
217
+ default: member.default,
218
+ fieldName: member.name,
219
+ type: { text: attributeValue },
220
+ deprecated: member.deprecated
221
+ })
166
222
  }
167
223
 
168
224
  if (hasTag(memberParsedJsDoc, "formProperty")) {
@@ -181,24 +237,14 @@ function processClass(ts, classNode, moduleDoc) {
181
237
  member.default = tagValue;
182
238
  }
183
239
 
184
- if (member.privacy === "public") {
185
- const JSDocErrors = getJSDocErrors();
186
-
187
- if (!member.default) {
188
- JSDocErrors.push(
189
- `=== ERROR: Problem found with ${member.name}'s JSDoc comment in ${moduleDoc.path}: Default value is missing`
190
- );
191
- }
240
+ if (member.privacy === "public" && !member.default) {
241
+ logDocumentationError(moduleDoc.path, `Missing default value for '${member.name}'.`)
192
242
  }
193
243
 
194
244
  // Getters are treated as fields so they should not have return, instead of return they should have default value defined with @default
195
245
  if (member.readonly) {
196
246
  if (member.privacy === "public" && !member.type) {
197
- const JSDocErrors = getJSDocErrors();
198
-
199
- JSDocErrors.push(
200
- `=== ERROR: Problem found with ${member.name}'s JSDoc comment in ${moduleDoc.path}: Missing return type`
201
- );
247
+ logDocumentationError(moduleDoc.path, `Missing return type for read-only field '${member.name}'.`)
202
248
  }
203
249
 
204
250
  delete member.return;
@@ -247,21 +293,19 @@ function processClass(ts, classNode, moduleDoc) {
247
293
  if (member.return) {
248
294
  const returnTag = findTag(memberParsedJsDoc, "returns");
249
295
  member.return.description = returnTag?.description ? `${returnTag.name} ${returnTag.description}` : returnTag?.name;
250
- member.return.type.text = classNodeMember?.type?.getFullText?.()?.trim();
296
+ member.return.type.text = formatArrays(classNodeMember?.type?.getFullText?.()?.trim());
251
297
  const typeRefs = (getTypeRefs(ts, classNodeMember, member.return)
252
298
  ?.map(typeRef => getReference(ts, typeRef, classNodeMember, moduleDoc.path)).filter(Boolean)) || [];
253
299
 
254
300
  if (typeRefs.length) {
255
301
  member.return.type.references = typeRefs;
256
302
  }
257
- }
258
-
259
- if (member.privacy === "public" && !member.return) {
260
- const JSDocErrors = getJSDocErrors();
261
303
 
262
- JSDocErrors.push(
263
- `=== ERROR: Problem found with ${member.name}'s JSDoc comment in ${moduleDoc.path}: Missing return type`
264
- );
304
+ if (member.privacy === "public" && !member.return.type.text) {
305
+ logDocumentationError(moduleDoc.path, `Missing return type for function '${member.name}'.`)
306
+ }
307
+ } else if (member.privacy === "public") {
308
+ logDocumentationError(moduleDoc.path, `Missing return type for function '${member.name}'.`)
265
309
  }
266
310
  }
267
311
  }
@@ -281,6 +325,7 @@ function processInterface(ts, interfaceNode, moduleDoc) {
281
325
  kind: "interface",
282
326
  name: interfaceName,
283
327
  description: normalizeDescription(interfaceParsedJsDoc?.description),
328
+ _ui5experimental: getExperimentalStatus(interfaceParsedJsDoc),
284
329
  _ui5privacy: getPrivacyStatus(interfaceParsedJsDoc),
285
330
  _ui5since: getSinceStatus(interfaceParsedJsDoc),
286
331
  deprecated: getDeprecatedStatus(interfaceParsedJsDoc),
@@ -301,6 +346,7 @@ function processEnum(ts, enumNode, moduleDoc) {
301
346
  kind: "enum",
302
347
  name: enumName,
303
348
  description: normalizeDescription(enumJSdoc?.comment),
349
+ _ui5experimental: getExperimentalStatus(enumParsedJsDoc),
304
350
  _ui5privacy: getPrivacyStatus(enumParsedJsDoc),
305
351
  _ui5since: getSinceStatus(enumParsedJsDoc),
306
352
  deprecated: getDeprecatedStatus(enumParsedJsDoc) || undefined,
@@ -342,7 +388,7 @@ const processPublicAPI = object => {
342
388
  if ((key === "privacy" && object[key] !== "public") || (key === "_ui5privacy" && object[key] !== "public")) {
343
389
  return true;
344
390
  } else if (typeof object[key] === "object") {
345
- if (key === "cssParts" || key === "_ui5implements") {
391
+ if (key === "cssParts" || key === "attributes" || key === "_ui5implements") {
346
392
  continue;
347
393
  }
348
394
 
@@ -413,21 +459,18 @@ export default {
413
459
  }
414
460
  },
415
461
  moduleLinkPhase({ moduleDoc }) {
416
- for (let i = 0; i < moduleDoc.declarations.length; i++) {
417
- const shouldRemove = processPublicAPI(moduleDoc.declarations[i]) || ["function", "variable"].includes(moduleDoc.declarations[i].kind)
418
- if (shouldRemove) {
419
- moduleDoc.declarations.splice(i, 1);
420
- i--;
421
- }
422
- }
462
+ moduleDoc.path = moduleDoc.path?.replace(/^src/, "dist").replace(/\.ts$/, ".js");
423
463
 
424
- if (moduleDoc.exports) {
425
- moduleDoc.exports = moduleDoc.exports.filter(e => !(e.kind === "custom-element-definition" && !moduleDoc.declarations?.find(d => d.name === e.name)?.tagName))
426
- }
464
+ moduleDoc.exports = moduleDoc.exports.
465
+ filter(e => !(e.kind === "custom-element-definition" && !moduleDoc.declarations?.find(d => d.name === e.name)?.tagName))
427
466
 
428
- moduleDoc.exports?.forEach(e => {
467
+ moduleDoc.exports.forEach(e => {
429
468
  const classNode = moduleDoc.declarations.find(c => c.name === e.declaration.name);
430
469
 
470
+ if (e.declaration && e.declaration.module) {
471
+ e.declaration.module = e.declaration.module.replace(/^src/, "dist").replace(/\.ts$/, ".js");
472
+ }
473
+
431
474
  if (classNode?.customElement && classNode.tagName && e.kind !== "custom-element-definition") {
432
475
  moduleDoc.exports.push({
433
476
  kind: "custom-element-definition",
@@ -441,23 +484,54 @@ export default {
441
484
  })
442
485
  },
443
486
  packageLinkPhase({ customElementsManifest }) {
444
- // Uncomment and handle errors appropriately
445
- // const JSDocErrors = getJSDocErrors();
446
- // if (JSDocErrors.length > 0) {
447
- // console.log(JSDocErrors.join("\n"));
448
- // console.log(`Invalid JSDoc. ${JSDocErrors.length} were found.`);
449
- // throw new Error(`Invalid JSDoc.`);
450
- // }
451
-
452
- customElementsManifest.modules?.forEach(m => {
453
- m.path = m.path?.replace(/^src/, "dist").replace(/\.ts$/, ".js");
454
-
455
- m.exports?.forEach(e => {
456
- if (e.declaration && e.declaration.module)
457
- e.declaration.module = e.declaration?.module?.replace(/^src/, "dist").replace(/\.ts$/, ".js");
487
+ customElementsManifest.modules.forEach(moduleDoc => {
488
+ for (let i = 0; i < moduleDoc.declarations.length; i++) {
489
+ const shouldRemove = processPublicAPI(moduleDoc.declarations[i]) || ["function", "variable"].includes(moduleDoc.declarations[i].kind)
490
+ if (shouldRemove) {
491
+ moduleDoc.declarations.splice(i, 1);
492
+ i--;
493
+ }
494
+ }
495
+
496
+ const typeReferences = new Set();
497
+ const registerTypeReference = reference => typeReferences.add(JSON.stringify(reference))
498
+
499
+ moduleDoc.declarations.forEach(declaration => {
500
+ ["events", "slots", "members"].forEach(memberType => {
501
+ declaration[memberType]?.forEach(member => {
502
+ if (member.type?.references) {
503
+ member.type.references.forEach(registerTypeReference)
504
+ } else if (member._ui5type?.references) {
505
+ member._ui5type.references.forEach(registerTypeReference)
506
+ } else if (member.kind === "method") {
507
+ member.return?.type?.references?.forEach(registerTypeReference)
508
+
509
+ member.parameters?.forEach(parameter => {
510
+ parameter.type?.references?.forEach(registerTypeReference)
511
+ })
512
+ }
513
+ })
514
+ })
458
515
  });
459
- })
516
+
517
+ typeReferences.forEach(reference => {
518
+ reference = JSON.parse(reference);
519
+ if (reference.package === packageJSON?.name && reference.module === moduleDoc.path) {
520
+ const hasExport = moduleDoc.exports.some(e => e.declaration?.name === reference.name && e.declaration?.module === reference.module)
521
+
522
+ if (!hasExport) {
523
+ logDocumentationError(moduleDoc.path?.replace(/^dist/, "src").replace(/\.js$/, ".ts"), `Type '${reference.name}' is used to describe a public API but is not exported.`,)
524
+ }
525
+ }
526
+ })
527
+ });
528
+
529
+ if (devMode) {
530
+ displayDocumentationErrors();
531
+ }
460
532
  }
461
533
  },
534
+ generateCustomData({ outdir: "dist", cssFileName: null, cssPropertiesDocs: false }),
535
+ customElementJetBrainsPlugin({ outdir: "dist", cssFileName: null, cssPropertiesDocs: false })
462
536
  ],
463
537
  };
package/lib/cem/event.mjs CHANGED
@@ -4,29 +4,22 @@ import {
4
4
  getDeprecatedStatus,
5
5
  getSinceStatus,
6
6
  getType,
7
+ getTypeRefs,
7
8
  validateJSDocComment,
8
9
  hasTag,
9
10
  findTag,
10
11
  findAllTags,
11
12
  getReference,
12
13
  normalizeDescription,
13
- normalizeTagType
14
+ normalizeTagType,
15
+ logDocumentationError
14
16
  } from "./utils.mjs";
15
17
 
16
18
  const jsDocRegExp = /\/\*\*(.|\n)+?\s+\*\//;
19
+ const ASTFalseKeywordCode = 94;
17
20
 
18
21
  const getParams = (ts, eventDetails, commentParams, classNode, moduleDoc) => {
19
22
  return commentParams?.map(commentParam => {
20
- const decoratorParam = eventDetails?.find(prop => prop?.name?.text === commentParam?.name);
21
-
22
- if (!decoratorParam || !decoratorParam?.jsDoc?.[0]) {
23
- return;
24
- }
25
-
26
- const decoratorParamParsedComment = parse(decoratorParam?.jsDoc?.[0]?.getText?.(), { spacing: 'preserve' })[0];
27
-
28
- validateJSDocComment("eventParam", decoratorParamParsedComment, decoratorParam.name?.text, moduleDoc);
29
-
30
23
  const { typeName, name } = getType(normalizeTagType(commentParam?.type));
31
24
  let type;
32
25
 
@@ -45,17 +38,16 @@ const getParams = (ts, eventDetails, commentParams, classNode, moduleDoc) => {
45
38
  return {
46
39
  type,
47
40
  name: commentParam?.name,
48
- _ui5privacy: getPrivacyStatus(decoratorParamParsedComment),
41
+ _ui5privacy: "public",
49
42
  description: normalizeDescription(commentParam?.description),
50
- _ui5since: getSinceStatus(decoratorParamParsedComment),
51
- deprecated: getDeprecatedStatus(decoratorParamParsedComment),
52
43
  };
53
- }).filter(pair => !!pair);
44
+ });
54
45
  };
55
46
 
56
47
  function processEvent(ts, event, classNode, moduleDoc) {
48
+ const name = event?.expression?.arguments?.[0]?.text;
57
49
  const result = {
58
- name: event?.expression?.arguments?.[0]?.text,
50
+ name,
59
51
  _ui5privacy: "private",
60
52
  type: { text: "CustomEvent" }
61
53
  };
@@ -68,19 +60,35 @@ function processEvent(ts, event, classNode, moduleDoc) {
68
60
 
69
61
  const eventParsedComment = parse(comment, { spacing: 'preserve' })[0];
70
62
 
71
- validateJSDocComment("event", eventParsedComment, event?.expression?.arguments?.[0]?.text, moduleDoc);
63
+ validateJSDocComment("event", eventParsedComment, name, moduleDoc);
72
64
 
73
65
  const deprecatedTag = findTag(eventParsedComment, "deprecated");
74
66
  const privacy = findTag(eventParsedComment, ["public", "private", "protected"])?.tag || "private";
75
67
  const sinceTag = findTag(eventParsedComment, "since");
76
68
  const commentParams = findAllTags(eventParsedComment, "param");
77
- const allowPreventDefault = hasTag(eventParsedComment, "allowPreventDefault") || undefined;
78
69
  const description = normalizeDescription(eventParsedComment?.description);
79
70
  const native = hasTag(eventParsedComment, "native");
80
- const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties;
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
+ });
81
87
 
82
88
  result.description = description;
83
- result._ui5allowPreventDefault = allowPreventDefault;
89
+ result._ui5Cancelable = eventCancelable !== undefined ? eventCancelable : false;
90
+ result._ui5allowPreventDefault = result._ui5Cancelable;
91
+ result._ui5Bubbles = eventBubbles !== undefined ? eventBubbles : false;
84
92
 
85
93
  if (native) {
86
94
  result.type = { text: "Event" };
@@ -104,7 +112,53 @@ function processEvent(ts, event, classNode, moduleDoc) {
104
112
  : sinceTag.name;
105
113
  }
106
114
 
107
- 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
+
108
162
  result._ui5parameters = getParams(ts, eventDetails, commentParams, classNode, moduleDoc);
109
163
  }
110
164