@ui5/webcomponents-tools 0.0.0-0a35d6b3d → 0.0.0-0dd36ca4b

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 (37) hide show
  1. package/CHANGELOG.md +514 -0
  2. package/assets-meta.js +2 -6
  3. package/components-package/nps.js +12 -18
  4. package/components-package/wdio.js +404 -405
  5. package/icons-collection/nps.js +2 -2
  6. package/lib/amd-to-es6/index.js +102 -0
  7. package/lib/amd-to-es6/no-remaining-require.js +33 -0
  8. package/lib/cem/custom-elements-manifest.config.mjs +83 -45
  9. package/lib/cem/event.mjs +20 -3
  10. package/lib/cem/schema-internal.json +3 -0
  11. package/lib/cem/types-internal.d.ts +550 -786
  12. package/lib/cem/types.d.ts +520 -655
  13. package/lib/cem/utils.mjs +49 -20
  14. package/lib/cem/validate.js +20 -13
  15. package/lib/create-icons/index.js +8 -6
  16. package/lib/create-illustrations/index.js +40 -33
  17. package/lib/create-new-component/index.js +4 -11
  18. package/lib/create-new-component/tsFileContentTemplate.js +3 -12
  19. package/lib/css-processors/css-processor-component-styles.mjs +47 -0
  20. package/lib/css-processors/scope-variables.mjs +3 -0
  21. package/lib/dev-server/ssr-dom-shim-loader.js +26 -0
  22. package/lib/generate-js-imports/illustrations.js +9 -9
  23. package/lib/generate-json-imports/i18n.js +3 -35
  24. package/lib/generate-json-imports/themes.js +2 -29
  25. package/lib/i18n/defaults.js +1 -1
  26. package/lib/scoping/lint-src.js +8 -7
  27. package/package.json +3 -2
  28. package/components-package/wdio.sync.js +0 -368
  29. package/lib/create-new-component/jsFileContentTemplate.js +0 -73
  30. package/lib/esm-abs-to-rel/index.js +0 -61
  31. package/lib/generate-custom-elements-manifest/index.js +0 -327
  32. package/lib/jsdoc/config.json +0 -29
  33. package/lib/jsdoc/configTypescript.json +0 -29
  34. package/lib/jsdoc/plugin.js +0 -2468
  35. package/lib/jsdoc/preprocess.js +0 -146
  36. package/lib/jsdoc/template/publish.js +0 -4120
  37. 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();
@@ -13,13 +13,19 @@ 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,
23
+ toKebabCase
22
24
  } from "./utils.mjs";
25
+ import { generateCustomData } from "cem-plugin-vs-code-custom-data-generator";
26
+ import { customElementJetBrainsPlugin } from "custom-element-jet-brains-integration";
27
+
28
+ const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
23
29
 
24
30
  const extractClassNodeJSDoc = node => {
25
31
  const fileContent = node.getFullText();
@@ -163,6 +169,24 @@ function processClass(ts, classNode, moduleDoc) {
163
169
 
164
170
  if (propertyDecorator) {
165
171
  member._ui5validator = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => ["validator", "type"].includes(property.name.text))?.initializer?.text || "String";
172
+ member._ui5noAttribute = propertyDecorator?.expression?.arguments[0]?.properties?.find(property => property.name.text === "noAttribute")?.initializer?.kind === ts.SyntaxKind.TrueKeyword || undefined;
173
+ }
174
+
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);
179
+ const tsProgramMember = tsProgramClassNode.members.find(m => ts.isPropertyDeclaration(m) && m.name?.text === member.name);
180
+ const attributeValue = typeChecker.typeToString(typeChecker.getTypeAtLocation(tsProgramMember), tsProgramMember);
181
+
182
+ currClass.attributes.push({
183
+ description: member.description,
184
+ name: toKebabCase(member.name),
185
+ default: member.default,
186
+ fieldName: member.name,
187
+ type: { text: attributeValue },
188
+ deprecated: member.deprecated
189
+ })
166
190
  }
167
191
 
168
192
  if (hasTag(memberParsedJsDoc, "formProperty")) {
@@ -181,24 +205,14 @@ function processClass(ts, classNode, moduleDoc) {
181
205
  member.default = tagValue;
182
206
  }
183
207
 
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
- }
208
+ if (member.privacy === "public" && !member.default) {
209
+ logDocumentationError(moduleDoc.path, `Missing default value for '${member.name}'.`)
192
210
  }
193
211
 
194
212
  // Getters are treated as fields so they should not have return, instead of return they should have default value defined with @default
195
213
  if (member.readonly) {
196
214
  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
- );
215
+ logDocumentationError(moduleDoc.path, `Missing return type for read-only field '${member.name}'.`)
202
216
  }
203
217
 
204
218
  delete member.return;
@@ -247,21 +261,19 @@ function processClass(ts, classNode, moduleDoc) {
247
261
  if (member.return) {
248
262
  const returnTag = findTag(memberParsedJsDoc, "returns");
249
263
  member.return.description = returnTag?.description ? `${returnTag.name} ${returnTag.description}` : returnTag?.name;
250
- member.return.type.text = classNodeMember?.type?.getFullText?.()?.trim();
264
+ member.return.type.text = formatArrays(classNodeMember?.type?.getFullText?.()?.trim());
251
265
  const typeRefs = (getTypeRefs(ts, classNodeMember, member.return)
252
266
  ?.map(typeRef => getReference(ts, typeRef, classNodeMember, moduleDoc.path)).filter(Boolean)) || [];
253
267
 
254
268
  if (typeRefs.length) {
255
269
  member.return.type.references = typeRefs;
256
270
  }
257
- }
258
-
259
- if (member.privacy === "public" && !member.return) {
260
- const JSDocErrors = getJSDocErrors();
261
271
 
262
- JSDocErrors.push(
263
- `=== ERROR: Problem found with ${member.name}'s JSDoc comment in ${moduleDoc.path}: Missing return type`
264
- );
272
+ if (member.privacy === "public" && !member.return.type.text) {
273
+ logDocumentationError(moduleDoc.path, `Missing return type for function '${member.name}'.`)
274
+ }
275
+ } else if (member.privacy === "public") {
276
+ logDocumentationError(moduleDoc.path, `Missing return type for function '${member.name}'.`)
265
277
  }
266
278
  }
267
279
  }
@@ -342,7 +354,7 @@ const processPublicAPI = object => {
342
354
  if ((key === "privacy" && object[key] !== "public") || (key === "_ui5privacy" && object[key] !== "public")) {
343
355
  return true;
344
356
  } else if (typeof object[key] === "object") {
345
- if (key === "cssParts" || key === "_ui5implements") {
357
+ if (key === "cssParts" || key === "attributes" || key === "_ui5implements") {
346
358
  continue;
347
359
  }
348
360
 
@@ -421,13 +433,18 @@ export default {
421
433
  }
422
434
  }
423
435
 
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
- }
436
+ moduleDoc.path = moduleDoc.path?.replace(/^src/, "dist").replace(/\.ts$/, ".js");
437
+
438
+ moduleDoc.exports = moduleDoc.exports.
439
+ filter(e => !(e.kind === "custom-element-definition" && !moduleDoc.declarations?.find(d => d.name === e.name)?.tagName))
427
440
 
428
- moduleDoc.exports?.forEach(e => {
441
+ moduleDoc.exports.forEach(e => {
429
442
  const classNode = moduleDoc.declarations.find(c => c.name === e.declaration.name);
430
443
 
444
+ if (e.declaration && e.declaration.module) {
445
+ e.declaration.module = e.declaration.module.replace(/^src/, "dist").replace(/\.ts$/, ".js");
446
+ }
447
+
431
448
  if (classNode?.customElement && classNode.tagName && e.kind !== "custom-element-definition") {
432
449
  moduleDoc.exports.push({
433
450
  kind: "custom-element-definition",
@@ -439,25 +456,46 @@ export default {
439
456
  })
440
457
  }
441
458
  })
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
- });
459
+
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
+ })
478
+ })
479
+ });
480
+
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)
485
+
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.`,)
488
+ }
489
+ }
459
490
  })
491
+ },
492
+ packageLinkPhase({ context }) {
493
+ if (context.dev) {
494
+ displayDocumentationErrors();
495
+ }
460
496
  }
461
497
  },
498
+ generateCustomData({ outdir: "dist", cssFileName: null, cssPropertiesDocs: false }),
499
+ customElementJetBrainsPlugin({ outdir: "dist", cssFileName: null, cssPropertiesDocs: false })
462
500
  ],
463
501
  };
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
  },