datagrok-tools 4.13.78 → 4.14.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Datagrok-tools changelog
2
2
 
3
+ ## 4.14.0 (2025-05-12)
4
+
5
+ ### Features
6
+
7
+ * Added annotation creation by decorators
8
+
3
9
  ## 4.13.70 (2025-04-14)
4
10
 
5
11
  ### Features
@@ -9,7 +9,7 @@ exports.generateExport = generateExport;
9
9
  exports.generateFunc = generateFunc;
10
10
  exports.generateImport = generateImport;
11
11
  exports.getFuncAnnotation = getFuncAnnotation;
12
- exports.reservedDecorators = exports.pseudoParams = exports.headerParams = void 0;
12
+ exports.typesToAnnotation = exports.reservedDecorators = exports.pseudoParams = exports.headerParams = void 0;
13
13
  /* eslint-disable no-unused-vars */
14
14
  /* eslint-disable valid-jsdoc */
15
15
 
@@ -20,42 +20,117 @@ let pseudoParams = exports.pseudoParams = /*#__PURE__*/function (pseudoParams) {
20
20
  pseudoParams["INPUT_TYPE"] = "inputType";
21
21
  return pseudoParams;
22
22
  }({});
23
+ const nonMetaData = ['sidebar', 'editor'];
24
+ const decoratorOptionToAnnotation = new Map([['initialValue', 'default']]);
23
25
  let FUNC_TYPES = exports.FUNC_TYPES = /*#__PURE__*/function (FUNC_TYPES) {
26
+ FUNC_TYPES["APP"] = "app";
24
27
  FUNC_TYPES["CELL_RENDERER"] = "cellRenderer";
25
28
  FUNC_TYPES["FILE_EXPORTER"] = "fileExporter";
26
- FUNC_TYPES["FILE_IMPORTER"] = "file-handler";
29
+ FUNC_TYPES["FILE_HANDLER"] = "file-handler";
27
30
  FUNC_TYPES["FILE_VIEWER"] = "fileViewer";
28
31
  FUNC_TYPES["SETTINGS_EDITOR"] = "packageSettingsEditor";
29
32
  FUNC_TYPES["VIEWER"] = "viewer";
30
33
  FUNC_TYPES["FILTER"] = "filter";
34
+ FUNC_TYPES["AUTOSTART"] = "autostart";
35
+ FUNC_TYPES["INIT"] = "init";
36
+ FUNC_TYPES["EDITOR"] = "editor";
37
+ FUNC_TYPES["PANEL"] = "panel";
38
+ FUNC_TYPES["FOLDER_VIEWER"] = "folderViewer";
39
+ FUNC_TYPES["SEM_TYPE_DETECTOR"] = "semTypeDetector";
40
+ FUNC_TYPES["DASHBOARD"] = "dashboard";
41
+ FUNC_TYPES["FUNCTION_ANALYSIS"] = "functionAnalysis";
42
+ FUNC_TYPES["CONVERTER"] = "converter";
43
+ FUNC_TYPES["MODEL"] = "model";
31
44
  return FUNC_TYPES;
32
45
  }({});
46
+ const typesToAnnotation = exports.typesToAnnotation = {
47
+ 'DataFrame': 'dataframe',
48
+ 'DG.DataFrame': 'dataframe',
49
+ 'Column': 'column',
50
+ 'DG.Column': 'column',
51
+ 'ColumnList': 'column_list',
52
+ 'DG.ColumnList': 'column_list',
53
+ 'FileInfo': 'file',
54
+ 'DG.FileInfo': 'file',
55
+ 'Uint8Array': 'blob',
56
+ 'number': 'double',
57
+ 'boolean': 'bool',
58
+ 'dayjs.Dayjs': 'datetime',
59
+ 'Dayjs': 'datetime',
60
+ 'graphics': 'graphics',
61
+ 'DG.View': 'view',
62
+ 'View': 'view',
63
+ 'DG.Widget': 'widget',
64
+ 'Widget': 'widget',
65
+ 'DG.FuncCall': 'funccall',
66
+ 'FuncCall': 'funccall',
67
+ 'DG.SemanticValue': 'semantic_value',
68
+ 'SemanticValue': 'semantic_value',
69
+ 'any': 'dynamic',
70
+ 'void': 'void',
71
+ 'string': 'string'
72
+ };
73
+
33
74
  /** Generates an annotation header for a function based on provided metadata. */
34
75
  function getFuncAnnotation(data, comment = '//', sep = '\n') {
35
76
  const isFileViewer = data.tags?.includes(FUNC_TYPES.FILE_VIEWER) ?? false;
36
- const isFileImporter = data.tags?.includes(FUNC_TYPES.FILE_IMPORTER) ?? false;
77
+ const isFileImporter = data.tags?.includes(FUNC_TYPES.FILE_HANDLER) ?? false;
37
78
  let s = '';
38
79
  if (data.name) s += `${comment}name: ${data.name}${sep}`;
39
80
  if (pseudoParams.EXTENSION in data && data.tags != null && data.tags.includes(FUNC_TYPES.FILE_EXPORTER)) s += `${comment}description: Save as ${data[pseudoParams.EXTENSION]}${sep}`;else if (data.description) s += `${comment}description: ${data.description}${sep}`;
40
- if (data.tags) {
41
- s += `${comment}tags: ${isFileViewer && data[pseudoParams.EXTENSIONS] ? data.tags.concat(data[pseudoParams.EXTENSIONS].map(ext => 'fileViewer-' + ext)).join() : data.tags.join()}${sep}`;
81
+ if (data.tags && data.tags?.length > 0) {
82
+ s += `${comment}tags: ${isFileViewer && data[pseudoParams.EXTENSIONS] ? data.tags.concat(data[pseudoParams.EXTENSIONS].map(ext => 'fileViewer-' + ext)).join(', ') : data.tags.join(', ')}${sep}`;
42
83
  }
43
- if (data.inputs) {
44
- for (const input of data.inputs) {
45
- s += comment + 'input: ' + (isFileImporter && data[pseudoParams.INPUT_TYPE] ? data[pseudoParams.INPUT_TYPE] : input.type) + (input.name ? ` ${input.name}` : '') + sep;
84
+ for (let input of data.inputs ?? []) {
85
+ if (!input) continue;
86
+ let type = input?.type;
87
+ let isArray = false;
88
+ if (type?.includes(`[]`)) {
89
+ type = type.replace(/\[\]$/, '');
90
+ isArray = true;
46
91
  }
92
+ const annotationType = typesToAnnotation[type ?? ''];
93
+ if (input?.options?.type) type = input?.options?.type;else if (annotationType) {
94
+ if (isArray) type = `list<${annotationType}>`;else type = annotationType;
95
+ } else type = 'dynamic';
96
+ const options = input?.options?.options ? buildStringOfOptions(input.options.options ?? {}) : '';
97
+ const functionName = (input.options?.name ? input?.options?.name : ` ${input.name?.replaceAll('.', '')}`)?.trim();
98
+ s += comment + 'input: ' + type + ' ' + functionName + (input.defaultValue !== undefined ? `= ${input.defaultValue}` : '') + ' ' + options.replaceAll('"', '\'') + sep;
47
99
  }
48
100
  if (data.outputs) {
49
- for (const output of data.outputs) s += comment + 'output: ' + output.type + (output.name ? ` ${output.name}` : '') + sep;
101
+ for (const output of data.outputs) if (output.type !== 'void') s += comment + 'output: ' + output.type + (output.name ? ` ${output.name}${output.options ? ` ${buildStringOfOptions(output.options)}` : ''}` : '') + sep;
102
+ }
103
+ if (data.meta) {
104
+ for (let entry of Object.entries(data.meta)) s += `${comment}meta.${entry[0]}: ${entry[1]}${sep}`;
50
105
  }
51
106
  for (const parameter in data) {
52
- if (parameter === pseudoParams.EXTENSION || parameter === pseudoParams.INPUT_TYPE) continue;else if (parameter === pseudoParams.EXTENSIONS) {
107
+ if (parameter === pseudoParams.EXTENSION || parameter === pseudoParams.INPUT_TYPE || parameter === 'meta' || parameter === 'isAsync' || parameter === 'test') continue;else if (parameter === pseudoParams.EXTENSIONS) {
53
108
  if (isFileViewer) continue;
54
109
  s += `${comment}meta.ext: ${data[parameter]}${sep}`;
55
- } else if (!headerParams.includes(parameter)) s += `${comment}meta.${parameter}: ${data[parameter]}${sep}`;
110
+ } else if (!headerParams.includes(parameter)) {
111
+ if (nonMetaData.includes(parameter)) s += `${comment}${parameter}: ${data[parameter]}${sep}`;else s += `${comment}meta.${parameter}: ${data[parameter]}${sep}`;
112
+ }
113
+ }
114
+ if (data.test) {
115
+ for (let entry of Object.entries(data.test)) {
116
+ if (entry[0] === 'test' || entry[0] === 'wait') s += `${comment}`;else s += `, `;
117
+ s += `${entry[0]}: ${entry[1]} `;
118
+ }
119
+ s += `${sep}`;
56
120
  }
57
121
  return s;
58
122
  }
123
+ function buildStringOfOptions(options) {
124
+ let optionsInString = [];
125
+ for (const [key, value] of Object.entries(options ?? {})) {
126
+ let val = value;
127
+ let option = key;
128
+ option = decoratorOptionToAnnotation.get(option) ?? option;
129
+ if (Array.isArray(value)) val = JSON.stringify(value);
130
+ optionsInString.push(`${option}: ${val}`);
131
+ }
132
+ return `{ ${optionsInString.join('; ')} }`;
133
+ }
59
134
  const reservedDecorators = exports.reservedDecorators = {
60
135
  viewer: {
61
136
  metadata: {
@@ -98,9 +173,9 @@ const reservedDecorators = exports.reservedDecorators = {
98
173
  },
99
174
  genFunc: generateFunc
100
175
  },
101
- fileImporter: {
176
+ fileHandler: {
102
177
  metadata: {
103
- tags: [FUNC_TYPES.FILE_IMPORTER],
178
+ tags: [FUNC_TYPES.FILE_HANDLER],
104
179
  inputs: [{
105
180
  name: 'content',
106
181
  type: 'string'
@@ -136,6 +211,163 @@ const reservedDecorators = exports.reservedDecorators = {
136
211
  }]
137
212
  },
138
213
  genFunc: generateFunc
214
+ },
215
+ func: {
216
+ metadata: {
217
+ tags: [],
218
+ inputs: [],
219
+ outputs: [{
220
+ name: 'result',
221
+ type: 'dynamic'
222
+ }]
223
+ },
224
+ genFunc: generateFunc
225
+ },
226
+ app: {
227
+ metadata: {
228
+ tags: [FUNC_TYPES.APP],
229
+ inputs: [],
230
+ outputs: [{
231
+ name: 'result',
232
+ type: 'view'
233
+ }]
234
+ },
235
+ genFunc: generateFunc
236
+ },
237
+ autostart: {
238
+ metadata: {
239
+ tags: [FUNC_TYPES.AUTOSTART],
240
+ inputs: [],
241
+ outputs: []
242
+ },
243
+ genFunc: generateFunc
244
+ },
245
+ init: {
246
+ metadata: {
247
+ tags: [FUNC_TYPES.INIT],
248
+ inputs: [],
249
+ outputs: []
250
+ },
251
+ genFunc: generateFunc
252
+ },
253
+ editor: {
254
+ metadata: {
255
+ tags: [FUNC_TYPES.EDITOR],
256
+ inputs: [{
257
+ name: 'call',
258
+ type: 'funccall'
259
+ }],
260
+ outputs: []
261
+ },
262
+ genFunc: generateFunc
263
+ },
264
+ panel: {
265
+ metadata: {
266
+ tags: [FUNC_TYPES.PANEL],
267
+ inputs: [],
268
+ outputs: [{
269
+ name: 'result',
270
+ type: 'widget'
271
+ }]
272
+ },
273
+ genFunc: generateFunc
274
+ },
275
+ folderViewer: {
276
+ metadata: {
277
+ tags: [FUNC_TYPES.FOLDER_VIEWER],
278
+ inputs: [{
279
+ name: 'folder',
280
+ type: 'file'
281
+ }, {
282
+ name: 'files',
283
+ type: 'list<file>'
284
+ }],
285
+ outputs: [{
286
+ name: 'result',
287
+ type: 'widget'
288
+ }]
289
+ },
290
+ genFunc: generateFunc
291
+ },
292
+ semTypeDetector: {
293
+ metadata: {
294
+ tags: [FUNC_TYPES.SEM_TYPE_DETECTOR],
295
+ inputs: [{
296
+ name: 'col',
297
+ type: 'column'
298
+ }],
299
+ outputs: [{
300
+ name: 'result',
301
+ type: 'string'
302
+ }]
303
+ },
304
+ genFunc: generateFunc
305
+ },
306
+ dashboard: {
307
+ metadata: {
308
+ tags: [FUNC_TYPES.DASHBOARD],
309
+ inputs: [],
310
+ outputs: [{
311
+ name: 'result',
312
+ type: 'widget'
313
+ }]
314
+ },
315
+ genFunc: generateFunc
316
+ },
317
+ functionAnalysis: {
318
+ metadata: {
319
+ tags: [FUNC_TYPES.FUNCTION_ANALYSIS],
320
+ inputs: [],
321
+ outputs: [{
322
+ name: 'result',
323
+ type: 'view'
324
+ }]
325
+ },
326
+ genFunc: generateFunc
327
+ },
328
+ converter: {
329
+ metadata: {
330
+ tags: [FUNC_TYPES.CONVERTER],
331
+ inputs: [{
332
+ name: 'value',
333
+ type: 'dynamic'
334
+ }],
335
+ outputs: [{
336
+ name: 'result',
337
+ type: 'dynamic'
338
+ }]
339
+ },
340
+ genFunc: generateFunc
341
+ },
342
+ demo: {
343
+ metadata: {
344
+ tags: [],
345
+ inputs: [],
346
+ outputs: []
347
+ },
348
+ genFunc: generateFunc
349
+ },
350
+ treeBrowser: {
351
+ metadata: {
352
+ tags: [],
353
+ inputs: [{
354
+ name: 'treeNode',
355
+ type: 'dynamic'
356
+ }, {
357
+ name: 'browseView',
358
+ type: 'view'
359
+ }],
360
+ outputs: []
361
+ },
362
+ genFunc: generateFunc
363
+ },
364
+ model: {
365
+ metadata: {
366
+ tags: [FUNC_TYPES.MODEL],
367
+ inputs: [],
368
+ outputs: []
369
+ },
370
+ genFunc: generateFunc
139
371
  }
140
372
  };
141
373
 
@@ -145,8 +377,10 @@ function generateClassFunc(annotation, className, sep = '\n') {
145
377
  }
146
378
 
147
379
  /** Generates a DG function. */
148
- function generateFunc(annotation, funcName, sep = '\n') {
149
- return annotation + `export function _${funcName}() {${sep} return ${funcName}();${sep}}${sep.repeat(2)}`;
380
+ function generateFunc(annotation, funcName, sep = '\n', className = '', inputs = [], isAsync = false) {
381
+ let funcSigNature = inputs.map(e => `${e.name}: ${e.type}`).join(', ');
382
+ let funcArguments = inputs.map(e => e.name).join(', ');
383
+ return annotation + `export ${isAsync ? 'async ' : ''}function ${funcName}(${funcSigNature}) {${sep} return ${className.length > 0 ? `${className}.` : ''}${funcName}(${funcArguments});${sep}}${sep.repeat(2)}`;
150
384
  }
151
385
  function generateImport(className, path, sep = '\n') {
152
386
  return `import {${className}} from '${path}';${sep}`;
@@ -7,7 +7,7 @@
7
7
  "datagrok-api": "^1.21.3",
8
8
  "cash-dom": "^8.1.5",
9
9
  "dayjs": "^1.11.13",
10
- "@datagrok-libraries/utils": "^4.3.6"
10
+ "@datagrok-libraries/utils": "^4.5.7"
11
11
  },
12
12
  "devDependencies": {
13
13
  "datagrok-tools": "latest",
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "4.13.78",
3
+ "version": "4.14.0",
4
4
  "description": "Utility to upload and publish packages to Datagrok",
5
5
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
6
6
  "dependencies": {
7
7
  "@babel/parser": "^7.26.10",
8
8
  "@babel/runtime": "^7.23.8",
9
9
  "@babel/traverse": "^7.23.7",
10
+ "@typescript-eslint/typescript-estree": "^8.31.1",
11
+ "@typescript-eslint/visitor-keys": "^8.31.1",
10
12
  "archiver": "^4.0.2",
11
13
  "archiver-promise": "^1.0.0",
14
+ "estraverse": "^5.3.0",
12
15
  "fs": "^0.0.1-security",
13
16
  "ignore-walk": "^3.0.4",
14
17
  "inquirer": "^7.3.3",
@@ -1,75 +1,243 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const {parse} = require('@babel/parser');
4
- const traverse = require('@babel/traverse').default;
5
- const {reservedDecorators, getFuncAnnotation, generateImport, generateExport} = require('../bin/utils/func-generation');
1
+ const fs = require("fs");
2
+ const path = require("path");
6
3
 
4
+ const tsParser = require("@typescript-eslint/typescript-estree");
5
+ const generate = require('@babel/generator').default;
6
+
7
+ const {
8
+ reservedDecorators,
9
+ getFuncAnnotation,
10
+ generateImport,
11
+ generateExport,
12
+ typesToAnnotation
13
+ } = require("../bin/utils/func-generation");
14
+
15
+ const baseImport = '\n';
7
16
 
8
17
  class FuncGeneratorPlugin {
9
- constructor(options = {outputPath: './src/package.g.ts'}) {
18
+ constructor(options = { outputPath: "./src/package.g.ts" }) {
10
19
  this.options = options;
11
20
  }
12
21
 
13
22
  apply(compiler) {
14
- const srcDirPath = path.join(compiler.context, 'src');
15
- let packageFilePath = path.join(srcDirPath, 'package.ts');
16
- if(!fs.existsSync(packageFilePath))
17
- packageFilePath = path.join(srcDirPath, 'package.js');
23
+ const srcDirPath = path.join(compiler.context, "src");
24
+ let packageFilePath = path.join(srcDirPath, "package.ts");
25
+ if (!fs.existsSync(packageFilePath))
26
+ packageFilePath = path.join(srcDirPath, "package.js");
18
27
  const tsFiles = this._getTsFiles(srcDirPath);
19
28
  const genImports = [];
20
29
  const genExports = [];
21
30
 
22
- compiler.hooks.compilation.tap('FuncGeneratorPlugin', (_compilation) => {
31
+ compiler.hooks.compilation.tap("FuncGeneratorPlugin", (_compilation) => {
23
32
  this._clearGeneratedFile();
24
33
 
25
34
  for (const file of tsFiles) {
26
- const content = fs.readFileSync(file, 'utf-8');
27
- if (!content)
35
+ const content = fs.readFileSync(file, "utf-8");
36
+ if(!content.includes('@grok.decorators.'))
28
37
  continue;
29
- const ast = parse(content, {
30
- sourceType: 'module',
31
- plugins: ['typescript', [
32
- 'decorators',
33
- {decoratorsBeforeExport: true},
34
- ]],
38
+
39
+ if (!content) continue;
40
+ const ast = tsParser.parse(content, {
41
+ sourceType: "module",
42
+ plugins: [
43
+ ["decorators", { decoratorsBeforeExport: true }],
44
+ "classProperties",
45
+ "typescript",
46
+ ],
35
47
  });
36
48
  const functions = [];
37
- const imports = [];
38
-
39
- traverse(ast, {
40
- ClassDeclaration: (nodePath) => this._addNodeData(nodePath.node, file,
41
- srcDirPath, functions, imports, genImports, genExports),
49
+ let imports = new Set();
50
+ this._walk(ast, (node, parentClass) => {
51
+ const decorators = node.decorators;
52
+ if (!decorators || decorators.length === 0) return;
53
+
54
+ if (node?.type === "ClassDeclaration")
55
+ this._addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports);
56
+
57
+ if (node?.type === "MethodDefinition")
58
+ this._addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports, parentClass);
42
59
  });
60
+ this._insertImports([...imports]);
61
+ fs.appendFileSync(this.options.outputPath, functions.join(""), "utf-8");
62
+ }
63
+ this._checkPackageFileForDecoratorsExport(packageFilePath);
64
+ // Uncommment to add obvious import/export
65
+ // this._writeToPackageFile(packageFilePath, genImports, genExports);
66
+
67
+ });
68
+ }
69
+
70
+ _addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports, parent = undefined) {
71
+ if (!node.decorators || !node.decorators.length || node.decorators?.length === 0)
72
+ return;
73
+
74
+ function modifyImportPath(dirPath, filePath) {
75
+ const relativePath = path.relative(dirPath, filePath);
76
+ return `./${relativePath.slice(0, relativePath.length - 3).replace(/\\/g, '/')}`;
77
+ }
78
+
79
+ const decorator = node.decorators[0];
80
+ const exp = decorator.expression;
81
+ const name = exp.callee?.property?.name || exp.callee?.name;
82
+ const identifierName = node.id?.name || node.key?.name;
83
+ const className = parent?.id?.name || parent?.key?.name;
84
+
85
+ if (!name) return;
86
+
87
+ const decoratorOptions = this._readDecoratorOptions(exp.arguments[0].properties);
88
+ decoratorOptions.set('tags', [...(reservedDecorators[name]['metadata']['tags'] ?? [] ), ...(decoratorOptions.get('tags') ?? [])])
89
+ const functionParams = node?.type === 'MethodDefinition' ? this._readMethodParamas(node) : [];
90
+ const annotationByReturnType = node?.type === 'MethodDefinition' ? this._readFunctionReturnTypeInfo(node) : '';
91
+ const annotationByReturnTypeObj = { name: 'result', type: annotationByReturnType };
92
+ const isMethodAsync = this._isMethodAsync(node);
93
+ let importString = generateImport(node?.type === 'MethodDefinition' ? className : identifierName, modifyImportPath(path.dirname(this.options.outputPath), file));
94
+ imports.add(importString);
95
+ const funcName = `${node?.type === 'MethodDefinition' ? '' : '_'}${identifierName}`;
96
+ const funcAnnotaionOptions = {
97
+ ...reservedDecorators[name]['metadata'],
98
+ ...(annotationByReturnType ? {outputs: [annotationByReturnTypeObj ?? {}]} : {}),
99
+ ...Object.fromEntries(decoratorOptions),
100
+ ...{inputs: functionParams},
101
+ ...{isAsync: isMethodAsync}
102
+ };
103
+ if (!funcAnnotaionOptions.name)
104
+ funcAnnotaionOptions.name = identifierName;
105
+ functions.push(reservedDecorators[name]['genFunc'](getFuncAnnotation(funcAnnotaionOptions), identifierName,'\n', (className ?? ''), functionParams, funcAnnotaionOptions.isAsync ?? false));
106
+ genImports.push(generateImport(funcName, modifyImportPath(srcDirPath, this.options.outputPath)));
107
+ genExports.push(generateExport(funcName));
108
+ }
109
+
110
+ _isMethodAsync(node){
111
+ let result = false;
112
+ if (node.type === 'MethodDefinition')
113
+ result = node.value.async;
114
+ return result;
115
+ }
116
+
117
+ _readImports (content) {
118
+ const importRegex = /(import(?:[\s\w{},*]+from\s*)?['"]([^'"]+)['"];)/g;
119
+ const results = [];
43
120
 
44
- if (fs.existsSync(this.options.outputPath)) {
45
- const content = fs.readFileSync(this.options.outputPath, 'utf-8');
46
- const output = content ? this._insertImports(content, imports) : imports.join('\n');
47
- fs.writeFileSync(this.options.outputPath, output, 'utf-8');
48
- } else
49
- fs.writeFileSync(this.options.outputPath, imports.join('\n'), 'utf-8');
50
-
51
-
52
- fs.appendFileSync(this.options.outputPath, functions.join('\n'), 'utf-8');
121
+ let match;
122
+ while ((match = importRegex.exec(content)) !== null) {
123
+ results.push(`${match[1]}\n`);
124
+ }
125
+ return results;
126
+ }
127
+
128
+ _readDecoratorOptions(properties){
129
+ const resultMap = new Map();
130
+
131
+ for(let prop of properties)
132
+ resultMap.set(prop.key.name, this._evalLiteral(prop.value));
133
+
134
+ return resultMap;
135
+ }
136
+
137
+ _evalLiteral(node) {
138
+ if (!node) return null;
139
+
140
+ switch (node.type) {
141
+ case 'Literal':
142
+ return node.value;
143
+
144
+ case 'ArrayExpression':
145
+ return node.elements.map(el => this._evalLiteral(el));
146
+
147
+ case 'ObjectExpression':
148
+ return Object.fromEntries(
149
+ node.properties.map(p => {
150
+ return [p.key.name || p.key.value, this._evalLiteral(p.value)];
151
+ })
152
+ );
153
+
154
+ default:
155
+ return '';
156
+ }
157
+ }
158
+
159
+ _readMethodParamas(node) {
160
+ const params = node?.value?.params?.map(param => {
161
+ let baseParam = param.type === 'TSNonNullExpression' ? param.expression : param;
162
+ const options = param.decorators?.length > 0? Object.fromEntries(this._readDecoratorOptions(param.decorators[0]?.expression?.arguments[0].properties)) : undefined;
163
+
164
+ // Commented code finds value by default of function's variable
165
+ // let defaultValue = undefined;
166
+ if(baseParam.type === 'AssignmentPattern')
167
+ {
168
+ // if (baseParam?.right?.type === 'Literal')
169
+ // defaultValue = baseParam?.right?.raw;
170
+ baseParam = baseParam?.left;
171
+ }
172
+
173
+ if (baseParam.type === 'RestElement' || baseParam.type === 'Identifier') {
174
+ let name =
175
+ baseParam.type === 'RestElement'
176
+ ? `...${baseParam.argument.name}`
177
+ : baseParam.name;
178
+ if (baseParam?.argument?.typeAnnotation)
179
+ name += ': ' + generate(baseParam.argument.typeAnnotation.typeAnnotation).code;
180
+
181
+ let type = '';
182
+ if (baseParam?.typeAnnotation?.typeAnnotation?.typeName || baseParam?.typeAnnotation?.typeAnnotation?.elementType?.typeName)
183
+ type = 'any';
184
+ else if (baseParam?.typeAnnotation?.typeAnnotation)
185
+ type = generate(baseParam.typeAnnotation.typeAnnotation).code;
186
+ let params = baseParam.typeAnnotation.typeAnnotation.typeArguments?.params;
187
+ if(type !== 'any' && params && params.length > 0)
188
+ type += `<${params.map((e)=>e.typeName.name).join(',')}>`;
189
+ return { name: name, type: type, options: options };
53
190
  }
191
+ // Commented code belove sets more strong types for ObjectPatterns and ArrayPatterns
192
+ // else if (baseParam.type === 'ObjectPattern' || baseParam.type === 'ArrayPattern') {
193
+ // let name = '';
194
+ // if (baseParam.type === 'ObjectPattern') {
195
+ // const properties = baseParam.properties.map(prop => {
196
+ // if (prop.type === 'Property' && prop.key.type === 'Identifier')
197
+ // return prop.key.name;
198
+ // else if (prop.type === 'RestElement' && prop.argument.type === 'Identifier')
199
+ // return `...${prop.argument.name}`;
200
+ // else
201
+ // return generate(prop).code;
202
+ // });
203
+ // name = `{ ${properties.join(', ')} }`;
204
+ // } else {
205
+ // const elements = baseParam.elements.map(elem => {
206
+ // if (elem) {
207
+ // if (elem.type === 'Identifier')
208
+ // return elem.name;
209
+ // else
210
+ // return generate(elem).code;
211
+ // } else return '';
212
+ // });
213
+ // name = `[${elements.join(', ')}]`;
214
+ // }
215
+
216
+ // let type = '';
217
+ // if (baseParam.typeAnnotation)
218
+ // type = generate(baseParam.typeAnnotation.typeAnnotation).code;
219
+ // else
220
+ // type = 'any';
54
221
 
55
- this._writeToPackageFile(packageFilePath, genImports, genExports);
222
+ // return { name: name, type: type, options: options };
223
+ // }
224
+ return { name: 'value', type: 'any', options: undefined };
56
225
  });
226
+ return params;
57
227
  }
58
228
 
59
229
  _getTsFiles(root) {
60
230
  const tsFiles = [];
61
231
  const extPattern = /\.tsx?$/;
62
- const excludedFiles = ['package.ts', 'package-test.ts', 'package.g.ts'];
232
+ const excludedFiles = ["package-test.ts", "package.g.ts"];
63
233
 
64
234
  function findFiles(dir) {
65
235
  const files = fs.readdirSync(dir);
66
236
  for (const file of files) {
67
237
  const fullPath = path.join(dir, file);
68
- if (fs.statSync(fullPath).isDirectory())
69
- findFiles(fullPath);
70
- else if (extPattern.test(file) && !excludedFiles.includes(file))
238
+ if (fs.statSync(fullPath).isDirectory()) findFiles(fullPath);
239
+ else if (extPattern.test(file) && !excludedFiles.includes(file))
71
240
  tsFiles.push(fullPath);
72
-
73
241
  }
74
242
  }
75
243
 
@@ -77,70 +245,120 @@ class FuncGeneratorPlugin {
77
245
  return tsFiles;
78
246
  }
79
247
 
80
- _clearGeneratedFile() {
81
- fs.writeFileSync(this.options.outputPath, '');
82
- }
83
-
84
- _writeToPackageFile(filePath, imports, exports) {
85
- if (imports.length !== exports.length)
86
- return;
87
- let content = fs.readFileSync(filePath, 'utf-8');
88
- for (let i = 0; i < imports.length; i++) {
89
- const importStatement = imports[i];
90
- const exportStatement = exports[i];
91
- if (!content.includes(importStatement.trim()))
92
- content = importStatement + content + exportStatement;
248
+ _walk(node, visitor, parent = null) {
249
+ if (!node || typeof node !== "object") return;
250
+
251
+ visitor(node, parent);
252
+
253
+ for (const key in node) {
254
+ const value = node[key];
255
+ if (Array.isArray(value)) {
256
+ value.forEach((child) => {
257
+ if (child && typeof child.type === "string") {
258
+ this._walk(child, visitor, node.type === 'ClassDeclaration'? node : parent );
259
+ }
260
+ });
261
+ } else if (value && typeof value.type === "string") {
262
+ this._walk(value, visitor, node.type === 'ClassDeclaration'? node : parent );
263
+ }
93
264
  }
94
- fs.writeFileSync(filePath, content, 'utf-8');
95
265
  }
96
266
 
97
- _addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports) {
98
- if (!node.decorators || !node.decorators.length)
99
- return;
267
+ _readFunctionReturnTypeInfo(node) {
268
+ let resultType = 'any';
269
+ let isArray = false;
270
+ const annotation = node.value?.returnType?.typeAnnotation;
271
+ if (annotation) {
272
+ if (annotation?.typeName?.name === 'Promise')
273
+ {
274
+ const argumnets = annotation.typeArguments?.params;
275
+ if (argumnets && argumnets.length===1)
276
+ {
277
+ if (argumnets[0].typeName)
278
+ resultType = argumnets[0].typeName?.right?.name ?? argumnets[0].typeName?.name;
279
+ else if (argumnets[0].type !== 'TSArrayType')
280
+ resultType = this._getTypeNameFromNode(argumnets[0]);
281
+ else if (argumnets[0].elementType.type !== 'TSTypeReference'){
282
+ isArray = true;
283
+ resultType = this._getTypeNameFromNode(argumnets[0]?.elementType);
284
+ }
285
+ else{
286
+ isArray = true;
287
+ resultType = argumnets[0].elementType?.typeName?.name || argumnets[0].elementType?.typeName?.right?.name;
288
+ }
289
+ }
290
+ }
291
+ else{
292
+ if (annotation.type === 'TSTypeReference')
293
+ resultType = annotation.typeName?.right?.name ?? annotation.typeName?.name;
294
+ else if (annotation.type !== 'TSArrayType')
295
+ resultType = this._getTypeNameFromNode(annotation);
296
+ else if (annotation.elementType.type !== 'TSTypeReference'){
297
+ isArray = true;
298
+ resultType = this._getTypeNameFromNode(annotation?.elementType);
299
+ }
300
+ else{
301
+ isArray = true;
302
+ resultType = (annotation?.elementType?.typeName?.name || annotation?.elementType?.typeName?.right?.name);
303
+ }
304
+ }
305
+ }
306
+ resultType = typesToAnnotation[resultType];
307
+ if (isArray && resultType)
308
+ resultType = `list<${resultType}>`
309
+ return resultType;
310
+ }
100
311
 
101
- function modifyImportPath(dirPath, filePath) {
102
- const relativePath = path.relative(dirPath, filePath);
103
- return `./${relativePath.slice(0, relativePath.length - 3).replace(/\\/g, '/')}`;
312
+ _getTypeNameFromNode(typeNode) {
313
+ if (typeNode.type === 'TSTypeReference') {
314
+ return typeNode.typeName.name;
315
+ } else if (typeNode.type === 'TSVoidKeyword') {
316
+ return 'void';
317
+ } else if (typeNode.type === 'TSNumberKeyword') {
318
+ return 'number';
319
+ } else if (typeNode.type === 'TSStringKeyword') {
320
+ return 'string';
321
+ } else if (typeNode.type === 'TSBooleanKeyword') {
322
+ return 'boolean';
323
+ } else {
324
+ return typeNode.type;
104
325
  }
326
+ }
105
327
 
106
- for (const decorator of node.decorators) {
107
- const exp = decorator.expression;
108
- const name = exp.callee.property.name;
109
- const options = {};
110
- if (name in reservedDecorators) {
111
- if (exp.arguments && exp.arguments.length === 1) {
112
- const props = exp.arguments[0].properties;
113
- for (const prop of props)
114
- options[prop.key.name] = prop.value.value;
115
- }
328
+ _clearGeneratedFile() {
329
+ fs.writeFileSync(this.options.outputPath, baseImport);
330
+ }
116
331
 
117
- const className = node.id.name;
118
- imports.push(generateImport(className, modifyImportPath(path.dirname(this.options.outputPath), file)));
119
- const funcName = `_${className}`;
120
- functions.push(reservedDecorators[name]['genFunc'](getFuncAnnotation({
121
- ...reservedDecorators[name]['metadata'],
122
- ...options,
123
- }), className));
332
+ _checkPackageFileForDecoratorsExport(packagePath){
333
+ const content = fs.readFileSync(packagePath, "utf-8");
334
+ const decoratorsExportRegex = /export\s*\*\s*from\s*'\.\/package\.g';/;
335
+ if (!decoratorsExportRegex.test(content))
336
+ console.warn(`\nWARNING: Your package doesn't export package.g.ts file to package.ts \n please add "export * from './package.g';" to the package.ts file.\n`);
337
+ }
124
338
 
125
- genImports.push(generateImport(funcName, modifyImportPath(srcDirPath, this.options.outputPath)));
126
- genExports.push(generateExport(funcName));
127
- }
339
+ _writeToPackageFile(filePath, imports, exp) {
340
+ if (imports.length !== exp.length) return;
341
+ let content = fs.readFileSync(filePath, "utf-8");
342
+ for (let i = 0; i < imports.length; i++) {
343
+ const importStatement = imports[i];
344
+ const exportStatement = exp[i];
345
+ if (!content.includes(importStatement.trim()))
346
+ content = importStatement + content + exportStatement;
128
347
  }
348
+ fs.writeFileSync(filePath, content, "utf-8");
129
349
  }
130
350
 
131
- _insertImports(content, imports) {
132
- const ast = parse(content, {sourceType: 'module', plugins: ['typescript', 'decorators']});
133
- let lastImportLoc = null;
134
- traverse(ast, {
135
- ImportDeclaration(nodePath) {
136
- lastImportLoc = nodePath.node.end + 1;
137
- },
138
- });
139
- return lastImportLoc === null ? imports.join('\n') + content :
140
- content.slice(0, lastImportLoc) + imports.join('\n') +
141
- (content[lastImportLoc] === '\n' ? '' : '\n') +
142
- content.slice(lastImportLoc, content.length);
351
+ _insertImports(importArray) {
352
+ if (fs.existsSync(this.options.outputPath)) {
353
+ const content = fs.readFileSync(this.options.outputPath, "utf-8");
354
+ if (content)
355
+ importArray.push(content);
356
+ const output = importArray.join("");
357
+ fs.writeFileSync(this.options.outputPath, `${output}`, "utf-8");
358
+ }
359
+ else
360
+ fs.writeFileSync(this.options.outputPath, `${baseImport}\n${importArray.join("")}`, "utf-8");
143
361
  }
144
362
  }
145
363
 
146
- module.exports = FuncGeneratorPlugin;
364
+ module.exports = FuncGeneratorPlugin;