@ui5/webcomponents-tools 1.22.0-rc.1 → 1.22.0-rc.3

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
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.22.0-rc.3](https://github.com/SAP/ui5-webcomponents/compare/v1.22.0-rc.2...v1.22.0-rc.3) (2024-02-01)
7
+
8
+ **Note:** Version bump only for package @ui5/webcomponents-tools
9
+
10
+
11
+
12
+
13
+
14
+ # [1.22.0-rc.2](https://github.com/SAP/ui5-webcomponents/compare/v1.22.0-rc.1...v1.22.0-rc.2) (2024-01-25)
15
+
16
+ **Note:** Version bump only for package @ui5/webcomponents-tools
17
+
18
+
19
+
20
+
21
+
6
22
  # [1.22.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v1.22.0-rc.0...v1.22.0-rc.1) (2024-01-18)
7
23
 
8
24
 
@@ -135,13 +135,13 @@ const getScripts = (options) => {
135
135
  bundle: `node ${LIB}/dev-server/dev-server.js ${viteConfig}`,
136
136
  },
137
137
  generateAPI: {
138
- default: `nps ${ tsOption ? "generateAPI.generateCEM generateAPI.validateCEM" : "generateAPI.prepare generateAPI.preprocess generateAPI.jsdoc generateAPI.cleanup generateAPI.prepareManifest"}`,
139
- generateCEM: `cem analyze --config "${LIB}/cem/custom-elements-manifest.config.mjs"`,
140
- validateCEM: `node "${LIB}/cem/validate.js"`,
138
+ default: `nps ${ tsOption ? "generateAPI.generateCEM generateAPI.validateCEM" : "generateAPI.prepare generateAPI.preprocess generateAPI.jsdoc generateAPI.cleanup generateAPI.prepareManifest generateAPI.validateCEM"}`,
139
+ generateCEM: `cem analyze --config "${LIB}/cem/custom-elements-manifest.config.mjs" ${ options.dev ? "--dev" : "" }`,
140
+ validateCEM: `node "${LIB}/cem/validate.js" ${ options.dev ? "--dev" : "" }`,
141
141
  prepare: `node "${LIB}/copy-and-watch/index.js" --silent "dist/**/*.js" jsdoc-dist/`,
142
142
  prepareManifest: `node "${LIB}/generate-custom-elements-manifest/index.js" dist dist`,
143
143
  preprocess: `node "${preprocessJSDocScript}" jsdoc-dist/ src`,
144
- jsdoc: `jsdoc -c "${LIB}/jsdoc/configTypescript.json"`,
144
+ jsdoc: `jsdoc -c "${LIB}/jsdoc/config.json"`,
145
145
  cleanup: "rimraf jsdoc-dist/"
146
146
  },
147
147
  };
@@ -0,0 +1,104 @@
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
+ code = code.replace(/sap\.ui\.require/g, "require");
20
+
21
+ return (await babelCore.transformAsync(code, {
22
+ plugins: [['babel-plugin-amd-to-esm', {}]]
23
+ })).code;
24
+ }
25
+
26
+ const convertAbsImportsToRelative = (filePath, code) => {
27
+ let changed = false;
28
+ // console.log("File processing started: ", srcPath);
29
+
30
+ if (code.includes("import(")) {
31
+ // esprima can't parse this, but it's from the project files
32
+ return;
33
+ }
34
+
35
+ const tree = babelParser.parse(code, { sourceType: "module" });
36
+ const importer = filePath.replace(basePath, "");
37
+ const importerDir = path.dirname(importer);
38
+ // console.log("Importer -> ", importer);
39
+
40
+ tree?.program?.body?.forEach(node => {
41
+ if (node.type === "ImportDeclaration") {
42
+ let importee = node.source.value;
43
+ // console.log(importee);
44
+ if (importee.startsWith(".")) {
45
+ // add .js extension if missing
46
+ if (!importee.endsWith(".js")) {
47
+ node.source.value += ".js"
48
+ changed = true;
49
+ }
50
+ return;
51
+ }
52
+ let importeeDir = path.dirname(importee);
53
+ let importeeFile = path.basename(importee);
54
+ let relativePath = path.relative(importerDir, importeeDir);
55
+ if (relativePath.length === 0) {
56
+ relativePath = "."
57
+ }
58
+ if (!relativePath.startsWith(".")) {
59
+ relativePath = "./" + relativePath;
60
+ }
61
+
62
+ relativePath = relativePath.replace(/\\/g, "/"); // the browser expects unix paths
63
+ let relativeImport = `${relativePath}/${importeeFile}.js`;
64
+ // console.log(importee + " --> " + relativeImport);
65
+ node.source.value = relativeImport;
66
+ changed = true;
67
+ }
68
+ });
69
+
70
+ return changed ? babelGenerator(tree).code : code;
71
+ }
72
+
73
+ const replaceGlobalCoreUsage = (filePath, code) => {
74
+ if (!filePath.includes("Configuration")) {
75
+ const replaced = code.replace(/sap\.ui\.getCore\(\)/g, `Core`);
76
+ return code !== replaced ? `import Core from 'sap/ui/core/Core';${replaced}` : code;
77
+ }
78
+
79
+ return code;
80
+ };
81
+
82
+ const transformAmdToES6Module = async (filePath) => {
83
+ await convertSAPUIDefineToDefine(filePath);
84
+
85
+ let code = (await fs.readFile(filePath)).toString();
86
+
87
+ code = await convertAmdToEs6(code);
88
+
89
+ code = replaceGlobalCoreUsage(filePath, code);
90
+
91
+ code = convertAbsImportsToRelative(filePath, code);
92
+
93
+ return fs.writeFile(filePath, code);
94
+ }
95
+
96
+ const transformAmdToES6Modules = async () => {
97
+ const { globby } = await import("globby");
98
+ const fileNames = await globby(basePath.replace(/\\/g, "/") + "**/*.js");
99
+ return Promise.all(fileNames.map(transformAmdToES6Module).filter(x => !!x));
100
+ };
101
+
102
+ transformAmdToES6Modules().then(() => {
103
+ console.log("Success: all amd modules are transformed to es6!");
104
+ });
@@ -13,14 +13,17 @@ import {
13
13
  hasTag,
14
14
  findTag,
15
15
  findAllTags,
16
- getJSDocErrors,
17
16
  getTypeRefs,
18
17
  normalizeDescription,
19
18
  formatArrays,
20
19
  isClass,
21
- normalizeTagType
20
+ normalizeTagType,
21
+ logDocumentationError,
22
+ displayDocumentationErrors
22
23
  } from "./utils.mjs";
23
24
 
25
+ const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
26
+
24
27
  const extractClassNodeJSDoc = node => {
25
28
  const fileContent = node.getFullText();
26
29
  const allJSDocsRegExp = new RegExp(`\\/\\*\\*(.|\\n)+?\\s+\\*\\/`, "gm");
@@ -163,6 +166,7 @@ function processClass(ts, classNode, moduleDoc) {
163
166
 
164
167
  if (propertyDecorator) {
165
168
  member._ui5validator = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => ["validator", "type"].includes(property.name.text))?.initializer?.text || "String";
169
+ member._ui5noAttribute = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => property.name.text === "noAttribute")?.initializer?.kind === ts.SyntaxKind.TrueKeyword || undefined;
166
170
  }
167
171
 
168
172
  if (hasTag(memberParsedJsDoc, "formProperty")) {
@@ -181,24 +185,14 @@ function processClass(ts, classNode, moduleDoc) {
181
185
  member.default = tagValue;
182
186
  }
183
187
 
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
- }
188
+ if (member.privacy === "public" && !member.default) {
189
+ logDocumentationError(moduleDoc.path, `Missing default value for '${member.name}'.`)
192
190
  }
193
191
 
194
192
  // Getters are treated as fields so they should not have return, instead of return they should have default value defined with @default
195
193
  if (member.readonly) {
196
194
  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
- );
195
+ logDocumentationError(moduleDoc.path, `Missing return type for read-only field '${member.name}'.`)
202
196
  }
203
197
 
204
198
  delete member.return;
@@ -247,21 +241,19 @@ function processClass(ts, classNode, moduleDoc) {
247
241
  if (member.return) {
248
242
  const returnTag = findTag(memberParsedJsDoc, "returns");
249
243
  member.return.description = returnTag?.description ? `${returnTag.name} ${returnTag.description}` : returnTag?.name;
250
- member.return.type.text = classNodeMember?.type?.getFullText?.()?.trim();
244
+ member.return.type.text = formatArrays(classNodeMember?.type?.getFullText?.()?.trim());
251
245
  const typeRefs = (getTypeRefs(ts, classNodeMember, member.return)
252
246
  ?.map(typeRef => getReference(ts, typeRef, classNodeMember, moduleDoc.path)).filter(Boolean)) || [];
253
247
 
254
248
  if (typeRefs.length) {
255
249
  member.return.type.references = typeRefs;
256
250
  }
257
- }
258
-
259
- if (member.privacy === "public" && !member.return) {
260
- const JSDocErrors = getJSDocErrors();
261
251
 
262
- JSDocErrors.push(
263
- `=== ERROR: Problem found with ${member.name}'s JSDoc comment in ${moduleDoc.path}: Missing return type`
264
- );
252
+ if (member.privacy === "public" && !member.return.type.text) {
253
+ logDocumentationError(moduleDoc.path, `Missing return type for function '${member.name}'.`)
254
+ }
255
+ } else if (member.privacy === "public") {
256
+ logDocumentationError(moduleDoc.path, `Missing return type for function '${member.name}'.`)
265
257
  }
266
258
  }
267
259
  }
@@ -421,13 +413,18 @@ export default {
421
413
  }
422
414
  }
423
415
 
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
- }
416
+ moduleDoc.path = moduleDoc.path?.replace(/^src/, "dist").replace(/\.ts$/, ".js");
417
+
418
+ moduleDoc.exports = moduleDoc.exports.
419
+ filter(e => !(e.kind === "custom-element-definition" && !moduleDoc.declarations?.find(d => d.name === e.name)?.tagName))
427
420
 
428
- moduleDoc.exports?.forEach(e => {
421
+ moduleDoc.exports.forEach(e => {
429
422
  const classNode = moduleDoc.declarations.find(c => c.name === e.declaration.name);
430
423
 
424
+ if (e.declaration && e.declaration.module) {
425
+ e.declaration.module = e.declaration.module.replace(/^src/, "dist").replace(/\.ts$/, ".js");
426
+ }
427
+
431
428
  if (classNode?.customElement && classNode.tagName && e.kind !== "custom-element-definition") {
432
429
  moduleDoc.exports.push({
433
430
  kind: "custom-element-definition",
@@ -439,24 +436,46 @@ export default {
439
436
  })
440
437
  }
441
438
  })
442
- },
443
- 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");
458
- });
439
+
440
+ const typeReferences = new Set();
441
+ const registerTypeReference = reference => typeReferences.add(JSON.stringify(reference))
442
+
443
+ moduleDoc.declarations.forEach(declaration => {
444
+ ["events", "slots", "members"].forEach(memberType => {
445
+ declaration[memberType]?.forEach(member => {
446
+ if (member.type?.references) {
447
+ member.type.references.forEach(registerTypeReference)
448
+ } else if (member._ui5type?.references) {
449
+ member._ui5type.references.forEach(registerTypeReference)
450
+ } else if (member.kind === "method") {
451
+ member.return?.type?.references?.forEach(registerTypeReference)
452
+
453
+ member.parameters?.forEach(parameter => {
454
+ parameter.type?.references?.forEach(registerTypeReference)
455
+ })
456
+ }
457
+ })
458
+ })
459
+ });
460
+
461
+ typeReferences.forEach(reference => {
462
+ reference = JSON.parse(reference);
463
+ if (reference.package === packageJSON?.name && reference.module === moduleDoc.path) {
464
+ const hasExport = moduleDoc.exports.some(e => e.declaration?.name === reference.name && e.declaration?.module === reference.module)
465
+
466
+ if (!hasExport) {
467
+ logDocumentationError(moduleDoc.path?.replace(/^dist/, "src").replace(/\.js$/, ".ts"), `Type '${reference.name}' is used to describe a public API but is not exported.`,)
468
+ }
469
+ }
459
470
  })
471
+
472
+ moduleDoc.exports = moduleDoc.exports.
473
+ filter(e => moduleDoc.declarations.find(d => d.name === e.declaration.name && ["class", "function", "variable", "enum"].includes(d.kind)) || e.name === "default");
474
+ },
475
+ packageLinkPhase({ context }) {
476
+ if (context.dev) {
477
+ displayDocumentationErrors();
478
+ }
460
479
  }
461
480
  },
462
481
  ],
package/lib/cem/event.mjs CHANGED
@@ -4,13 +4,15 @@ 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+\*\//;
@@ -54,8 +56,9 @@ const getParams = (ts, eventDetails, commentParams, classNode, moduleDoc) => {
54
56
  };
55
57
 
56
58
  function processEvent(ts, event, classNode, moduleDoc) {
59
+ const name = event?.expression?.arguments?.[0]?.text;
57
60
  const result = {
58
- name: event?.expression?.arguments?.[0]?.text,
61
+ name,
59
62
  _ui5privacy: "private",
60
63
  type: { text: "CustomEvent" }
61
64
  };
@@ -68,7 +71,7 @@ function processEvent(ts, event, classNode, moduleDoc) {
68
71
 
69
72
  const eventParsedComment = parse(comment, { spacing: 'preserve' })[0];
70
73
 
71
- validateJSDocComment("event", eventParsedComment, event?.expression?.arguments?.[0]?.text, moduleDoc);
74
+ validateJSDocComment("event", eventParsedComment, name, moduleDoc);
72
75
 
73
76
  const deprecatedTag = findTag(eventParsedComment, "deprecated");
74
77
  const privacy = findTag(eventParsedComment, ["public", "private", "protected"])?.tag || "private";
@@ -79,11 +82,25 @@ function processEvent(ts, event, classNode, moduleDoc) {
79
82
  const native = hasTag(eventParsedComment, "native");
80
83
  const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties;
81
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
+ }
88
+
82
89
  result.description = description;
83
90
  result._ui5allowPreventDefault = allowPreventDefault;
84
91
 
85
92
  if (native) {
86
93
  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
+ }
87
104
  }
88
105
 
89
106
  if (privacy) {
@@ -245,6 +245,9 @@
245
245
  "ClassField": {
246
246
  "additionalProperties": false,
247
247
  "properties": {
248
+ "_ui5noAttribute": {
249
+ "type": "boolean"
250
+ },
248
251
  "_ui5validator": {
249
252
  "type": "string"
250
253
  },