@ui5/webcomponents-tools 1.21.1 → 1.22.0-rc.0

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.
@@ -0,0 +1,355 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ let JSDocErrors = [];
5
+
6
+ const getDeprecatedStatus = (jsdocComment) => {
7
+ const deprecatedTag = findTag(jsdocComment, "deprecated");
8
+ return deprecatedTag?.name
9
+ ? deprecatedTag.description
10
+ ? `${deprecatedTag.name} ${deprecatedTag.description}`
11
+ : deprecatedTag.name
12
+ : deprecatedTag
13
+ ? true
14
+ : undefined;
15
+ };
16
+
17
+ const normalizeDescription = (description) => {
18
+ return typeof description === 'string' ? description.replaceAll(/^-\s+|^(\n)+|(\n)+$/g, ""): description;
19
+ }
20
+
21
+ const getTypeRefs = (ts, classNodeMember, member) => {
22
+ const extractTypeRefs = (type) => {
23
+ if (type?.kind === ts.SyntaxKind.TypeReference) {
24
+ return type.typeArguments?.length
25
+ ? type.typeArguments.map((typeRef) => typeRef.typeName?.text)
26
+ : [type.typeName?.text];
27
+ } else if (type?.kind === ts.SyntaxKind.UnionType) {
28
+ return type.types
29
+ .map((type) => extractTypeRefs(type))
30
+ .flat(1);
31
+ } else if (type?.kind === ts.SyntaxKind.TemplateLiteralType) {
32
+ if (member.type) {
33
+ member.type.text = member.type.text.replaceAll?.(/`|\${|}/g, "");
34
+ }
35
+
36
+ return type.templateSpans?.length
37
+ ? type.templateSpans.map((typeRef) => typeRef.type?.typeName?.text)
38
+ : [type.typeName?.text];
39
+ }
40
+ };
41
+
42
+ let typeRefs = extractTypeRefs(classNodeMember.type);
43
+
44
+ if (typeRefs) {
45
+ typeRefs = typeRefs.filter((e) => !!e);
46
+ }
47
+
48
+ return typeRefs?.length ? typeRefs : undefined;
49
+ };
50
+
51
+ const getSinceStatus = (jsdocComment) => {
52
+ const sinceTag = findTag(jsdocComment, "since");
53
+ return sinceTag
54
+ ? sinceTag.description
55
+ ? `${sinceTag.name} ${sinceTag.description}`
56
+ : sinceTag.name
57
+ : undefined;
58
+ };
59
+
60
+ const getPrivacyStatus = (jsdocComment) => {
61
+ const privacyTag = findTag(jsdocComment, ["public", "private", "protected"]);
62
+ return privacyTag?.tag || "private";
63
+ };
64
+
65
+ const findPackageName = (ts, sourceFile, typeName, packageJSON) => {
66
+ const localStatements = [
67
+ ts.SyntaxKind.EnumDeclaration,
68
+ ts.SyntaxKind.InterfaceDeclaration,
69
+ ts.SyntaxKind.ClassDeclaration,
70
+ ts.SyntaxKind.TypeAliasDeclaration,
71
+ ];
72
+
73
+ const isLocalDeclared = sourceFile.statements.some(
74
+ (statement) =>
75
+ localStatements.includes(statement.kind) && statement?.name?.text === typeName
76
+ );
77
+
78
+ if (isLocalDeclared) {
79
+ return packageJSON?.name;
80
+ } else {
81
+ const importStatements = sourceFile.statements?.filter(
82
+ (statement) => statement.kind === ts.SyntaxKind.ImportDeclaration
83
+ );
84
+ const currentModuleSpecifier = importStatements.find((statement) => {
85
+ if (statement.importClause?.name?.text === typeName) {
86
+ return true;
87
+ }
88
+
89
+ return statement.importClause?.namedBindings?.elements?.some(
90
+ (element) => element.name?.text === typeName
91
+ );
92
+ })?.moduleSpecifier;
93
+
94
+ if (currentModuleSpecifier?.text?.startsWith(".")) {
95
+ return packageJSON?.name;
96
+ } else {
97
+ return Object.keys(packageJSON?.dependencies || {}).find(
98
+ (dependency) =>
99
+ currentModuleSpecifier?.text?.startsWith(`${dependency}/`)
100
+ );
101
+ }
102
+ }
103
+ };
104
+
105
+ const findImportPath = (ts, sourceFile, typeName, packageJSON, modulePath) => {
106
+ const localStatements = [
107
+ ts.SyntaxKind.EnumDeclaration,
108
+ ts.SyntaxKind.InterfaceDeclaration,
109
+ ts.SyntaxKind.ClassDeclaration,
110
+ ts.SyntaxKind.TypeAliasDeclaration,
111
+ ];
112
+
113
+ const isLocalDeclared = sourceFile.statements.some(
114
+ (statement) =>
115
+ localStatements.includes(statement.kind) && statement?.name?.text === typeName
116
+ );
117
+
118
+ if (isLocalDeclared) {
119
+ return (
120
+ modulePath?.replace("src", "dist")?.replace(".ts", ".js") || undefined
121
+ );
122
+ } else {
123
+ const importStatements = sourceFile.statements?.filter(
124
+ (statement) => statement.kind === ts.SyntaxKind.ImportDeclaration
125
+ );
126
+ const currentModuleSpecifier = importStatements.find((statement) => {
127
+ if (statement.importClause?.name?.text === typeName) {
128
+ return true;
129
+ }
130
+
131
+ return statement.importClause?.namedBindings?.elements?.some(
132
+ (element) => element.name?.text === typeName
133
+ );
134
+ })?.moduleSpecifier;
135
+
136
+ if (currentModuleSpecifier?.text?.startsWith(".")) {
137
+ return (
138
+ path.join(path.dirname(modulePath), currentModuleSpecifier.text)
139
+ ?.replace("src", "dist")?.replace(".ts", ".js") || undefined
140
+ );
141
+ } else {
142
+ const packageName = Object.keys(packageJSON?.dependencies || {}).find(
143
+ (dependency) =>
144
+ currentModuleSpecifier?.text?.startsWith(dependency)
145
+ );
146
+ return currentModuleSpecifier?.text
147
+ ?.replace(`${packageName}/`, "") || undefined;
148
+ }
149
+ }
150
+ };
151
+
152
+
153
+ const isClass = text => {
154
+ return text.includes("@abstract") || text.includes("@class") || text.includes("@constructor");
155
+ };
156
+
157
+ const normalizeTagType = (type) => {
158
+ return type?.trim();
159
+ }
160
+
161
+ const getReference = (ts, type, classNode, modulePath) => {
162
+ let sourceFile = classNode.parent;
163
+
164
+ while (sourceFile && sourceFile.kind !== ts.SyntaxKind.SourceFile) {
165
+ sourceFile = sourceFile.parent;
166
+ }
167
+
168
+ const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
169
+
170
+ const typeName =
171
+ typeof type === "string"
172
+ ? normalizeTagType(type)
173
+ : type.class?.expression?.text ||
174
+ type.typeExpression?.type?.getText() ||
175
+ type.typeExpression?.type?.elementType?.typeName?.text;
176
+ const packageName = findPackageName(ts, sourceFile, typeName, packageJSON);
177
+ const importPath = findImportPath(
178
+ ts,
179
+ sourceFile,
180
+ typeName,
181
+ packageJSON,
182
+ modulePath
183
+ );
184
+
185
+ return packageName && {
186
+ name: typeName,
187
+ package: packageName,
188
+ module: importPath,
189
+ };
190
+ };
191
+
192
+ const getType = (type) => {
193
+ const typeName = typeof type === "string" ? normalizeTagType(type) : type?.type;
194
+
195
+ const multiple =
196
+ typeName?.endsWith("[]") || typeName?.startsWith("Array<");
197
+ const name = multiple
198
+ ? typeName?.replace("[]", "")?.replace("Array<", "")?.replace(">", "")
199
+ : typeName;
200
+
201
+ return typeName ? { typeName: multiple ? `Array<${name}>` : typeName, name, multiple } : undefined;
202
+ };
203
+
204
+ const commonTags = ["public", "protected", "private", "since", "deprecated"];
205
+
206
+ const allowedTags = {
207
+ field: [...commonTags, "formEvents", "formProperty", "default"],
208
+ slot: [...commonTags, "default"],
209
+ event: [...commonTags, "param", "allowPreventDefault", "native"],
210
+ eventParam: [...commonTags],
211
+ method: [...commonTags, "param", "returns", "override"],
212
+ class: [...commonTags, "constructor", "class", "abstract", "implements", "extends", "slot", "csspart"],
213
+ enum: [...commonTags],
214
+ enumMember: [...commonTags],
215
+ interface: [...commonTags],
216
+ };
217
+ allowedTags.getter = [...allowedTags.field, "override"]
218
+
219
+ const tagMatchCallback = (tag, tagName) => {
220
+ const currentTagName = tag.tag;
221
+
222
+ return typeof tagName === "string"
223
+ ? currentTagName === tagName
224
+ : tagName.includes(currentTagName);
225
+ };
226
+
227
+ const findDecorator = (node, decoratorName) => {
228
+ return node?.decorators?.find(
229
+ (decorator) =>
230
+ decorator?.expression?.expression?.text === decoratorName
231
+ );
232
+ };
233
+
234
+ const findAllDecorators = (node, decoratorName) => {
235
+ return (
236
+ node?.decorators?.filter(
237
+ (decorator) =>
238
+ decorator?.expression?.expression?.text === decoratorName
239
+ ) || []
240
+ );
241
+ };
242
+
243
+ const hasTag = (jsDoc, tagName) => {
244
+ if (!jsDoc) {
245
+ return;
246
+ }
247
+
248
+ return jsDoc?.tags?.some((tag) => tagMatchCallback(tag, tagName));
249
+ };
250
+
251
+ const findTag = (jsDoc, tagName) => {
252
+ if (!jsDoc) {
253
+ return;
254
+ }
255
+
256
+ return jsDoc?.tags?.find((tag) => tagMatchCallback(tag, tagName));
257
+ };
258
+
259
+ const findAllTags = (jsDoc, tagName) => {
260
+ if (!jsDoc) {
261
+ return [];
262
+ }
263
+
264
+ const foundTags = jsDoc?.tags?.filter((tag) => tagMatchCallback(tag, tagName));
265
+
266
+ return foundTags || [];
267
+ };
268
+
269
+ const validateJSDocTag = (tag) => {
270
+ const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "native", "formProperty", "constructor", "override"];
271
+ let tagName = tag.tag;
272
+
273
+ if (booleanTags.includes(tag.tag)) {
274
+ tagName = "boolean";
275
+ }
276
+
277
+ switch (tagName) {
278
+ case "boolean":
279
+ return !tag.name && !tag.type && !tag.description;
280
+ case "deprecated":
281
+ return !tag.type;
282
+ case "extends":
283
+ return !tag.type && tag.name && !tag.description;
284
+ case "implements":
285
+ return tag.type && !tag.name && !tag.description;
286
+ case "slot":
287
+ return tag.type && tag.name && tag.description;
288
+ case "csspart":
289
+ return !tag.type && tag.name && tag.description;
290
+ case "since":
291
+ return !tag.type && tag.name;
292
+ case "returns":
293
+ return !tag.type && tag.name;
294
+ case "default":
295
+ return !tag.type && !tag.description;
296
+ case "class":
297
+ return !tag.type;
298
+ case "param":
299
+ return !tag.type && tag.name;
300
+ case "eventparam":
301
+ return tag.type && tag.name;
302
+ case "formEvents":
303
+ return !tag.type && tag.name;
304
+ default:
305
+ return false;
306
+ }
307
+ };
308
+
309
+ const validateJSDocComment = (fieldType, jsdocComment, node, moduleDoc) => {
310
+ return !!jsdocComment?.tags?.every((tag) => {
311
+ let isValid = false
312
+
313
+ if (fieldType === "event" && tag?.tag === "param") {
314
+ isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag({...tag, tag: "eventparam"});
315
+ } else {
316
+ isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag(tag);
317
+ }
318
+
319
+ if (!isValid) {
320
+ JSDocErrors.push(
321
+ `=== ERROR: Problem found with ${node}'s JSDoc comment in ${moduleDoc.path}: \n\t- @${tag.tag} tag is being used wrong or it's not part of ${fieldType} JSDoc tags`
322
+ );
323
+ }
324
+
325
+ return !!isValid;
326
+ });
327
+ };
328
+
329
+ const getJSDocErrors = () => {
330
+ return JSDocErrors;
331
+ };
332
+
333
+ const formatArrays = (typeText) => {
334
+ return typeText.replaceAll(/(\S+)\[\]/g, "Array<$1>")
335
+ }
336
+
337
+ export {
338
+ getPrivacyStatus,
339
+ getSinceStatus,
340
+ getDeprecatedStatus,
341
+ getType,
342
+ getReference,
343
+ validateJSDocComment,
344
+ findDecorator,
345
+ findAllDecorators,
346
+ hasTag,
347
+ findTag,
348
+ findAllTags,
349
+ getJSDocErrors,
350
+ getTypeRefs,
351
+ normalizeDescription,
352
+ formatArrays,
353
+ isClass,
354
+ normalizeTagType
355
+ };
@@ -0,0 +1,63 @@
1
+ const fs = require('fs');
2
+ const Ajv = require('ajv');
3
+ const path = require('path');
4
+
5
+ // Load your JSON schema
6
+ const extenalSchema = require('./schema.json');
7
+ const internalSchema = require('./schema-internal.json');
8
+
9
+ // Load your JSON data from the input file
10
+ const inputFilePath = path.join(process.cwd(), "dist/custom-elements.json"); // Update with your file path
11
+ const customManifest = fs.readFileSync(inputFilePath, 'utf8');
12
+ const inputDataInternal = JSON.parse(customManifest);
13
+
14
+ const clearProps = (data) => {
15
+ if (Array.isArray(data)) {
16
+ for (let i = 0; i < data.length; i++) {
17
+ if (typeof data[i] === "object") {
18
+ if (["enum", "interface"].includes(data[i].kind)) {
19
+ data.splice(i, 1);
20
+ i--;
21
+ } else {
22
+ clearProps(data[i]);
23
+ }
24
+ }
25
+ }
26
+ } else if (typeof data === "object") {
27
+ Object.keys(data).forEach(prop => {
28
+ if (prop.startsWith("_ui5")) {
29
+ delete data[prop];
30
+ } else if (typeof data[prop] === "object") {
31
+ clearProps(data[prop]);
32
+ }
33
+ });
34
+ }
35
+
36
+ return data;
37
+ }
38
+
39
+ const inputDataExternal = clearProps(JSON.parse(JSON.stringify(inputDataInternal)));
40
+
41
+ const ajv = new Ajv({ allowUnionTypes: true, allError: true })
42
+
43
+ let validate = ajv.compile(internalSchema)
44
+
45
+ // Validate the JSON data against the schema
46
+ if (validate(inputDataInternal)) {
47
+ console.log('Validation internal custom-elements successful');
48
+ } else {
49
+ console.error('Validation of internal custom-elements failed');
50
+ // console.error('Validation of internal custom-elements failed:', validate.errors);
51
+ }
52
+
53
+ validate = ajv.compile(extenalSchema)
54
+
55
+ // Validate the JSON data against the schema
56
+ if (validate(inputDataExternal)) {
57
+ console.log('Validation external custom-elements successful');
58
+ fs.writeFileSync(inputFilePath, JSON.stringify(inputDataExternal, null, 2), 'utf8');
59
+ fs.writeFileSync(inputFilePath.replace("custom-elements", "custom-elements-internal"), JSON.stringify(inputDataInternal, null, 2), 'utf8');
60
+ } else {
61
+ console.error('Validation of external custom-elements failed:');
62
+ // console.error('Validation of external custom-elements failed:', ajv.errorsText(validate.errors));
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ui5/webcomponents-tools",
3
- "version": "1.21.1",
3
+ "version": "1.22.0-rc.0",
4
4
  "description": "UI5 Web Components: webcomponents.tools",
5
5
  "author": "SAP SE (https://www.sap.com)",
6
6
  "license": "Apache-2.0",
@@ -21,6 +21,7 @@
21
21
  "directory": "packages/tools"
22
22
  },
23
23
  "dependencies": {
24
+ "@custom-elements-manifest/analyzer": "^0.8.4",
24
25
  "@typescript-eslint/eslint-plugin": "^6.9.0",
25
26
  "@typescript-eslint/parser": "^6.9.0",
26
27
  "@wdio/cli": "^7.19.7",
@@ -29,11 +30,13 @@
29
30
  "@wdio/mocha-framework": "^7.19.7",
30
31
  "@wdio/spec-reporter": "^7.19.7",
31
32
  "@wdio/static-server-service": "^7.19.5",
33
+ "ajv": "^8.12.0",
32
34
  "chai": "^4.3.4",
33
35
  "child_process": "^1.0.2",
34
36
  "chokidar": "^3.5.1",
35
37
  "chokidar-cli": "^3.0.0",
36
38
  "command-line-args": "^5.1.1",
39
+ "comment-parser": "^1.4.0",
37
40
  "concurrently": "^6.0.0",
38
41
  "cross-env": "^7.0.3",
39
42
  "escodegen": "^2.0.0",
@@ -75,5 +78,6 @@
75
78
  "devDependencies": {
76
79
  "esbuild": "^0.19.9",
77
80
  "yargs": "^17.5.1"
78
- }
79
- }
81
+ },
82
+ "gitHead": "4cf0711a7e7a57b5b31453e8286f0a50ab4cb8d0"
83
+ }