datagrok-tools 4.13.77 → 4.13.79

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.
@@ -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
 
@@ -21,6 +21,7 @@ let pseudoParams = exports.pseudoParams = /*#__PURE__*/function (pseudoParams) {
21
21
  return pseudoParams;
22
22
  }({});
23
23
  let FUNC_TYPES = exports.FUNC_TYPES = /*#__PURE__*/function (FUNC_TYPES) {
24
+ FUNC_TYPES["APP"] = "app";
24
25
  FUNC_TYPES["CELL_RENDERER"] = "cellRenderer";
25
26
  FUNC_TYPES["FILE_EXPORTER"] = "fileExporter";
26
27
  FUNC_TYPES["FILE_IMPORTER"] = "file-handler";
@@ -28,8 +29,43 @@ let FUNC_TYPES = exports.FUNC_TYPES = /*#__PURE__*/function (FUNC_TYPES) {
28
29
  FUNC_TYPES["SETTINGS_EDITOR"] = "packageSettingsEditor";
29
30
  FUNC_TYPES["VIEWER"] = "viewer";
30
31
  FUNC_TYPES["FILTER"] = "filter";
32
+ FUNC_TYPES["AUTOSTART"] = "autostart";
33
+ FUNC_TYPES["INIT"] = "init";
34
+ FUNC_TYPES["EDITOR"] = "editor";
35
+ FUNC_TYPES["PANEL"] = "panel";
36
+ FUNC_TYPES["FOLDER_VIEWER"] = "folderViewer";
37
+ FUNC_TYPES["SEM_TYPE_DETECTOR"] = "semTypeDetector";
38
+ FUNC_TYPES["DASHBOARD"] = "dashboard";
39
+ FUNC_TYPES["FUNCTION_ANALYSIS"] = "functionAnalysis";
40
+ FUNC_TYPES["CONVERTER"] = "converter";
31
41
  return FUNC_TYPES;
32
42
  }({});
43
+ const typesToAnnotation = exports.typesToAnnotation = {
44
+ 'DataFrame': 'dataframe',
45
+ 'DG.DataFrame': 'dataframe',
46
+ 'Column': 'column',
47
+ 'DG.Column': 'column',
48
+ 'ColumnList': 'column_list',
49
+ 'DG.ColumnList': 'column_list',
50
+ 'FileInfo': 'file',
51
+ 'DG.FileInfo': 'file',
52
+ 'Uint8Array': 'blob',
53
+ 'number': 'double',
54
+ 'boolean': 'bool',
55
+ 'dayjs.Dayjs': 'datetime',
56
+ 'Dayjs': 'datetime',
57
+ 'graphics': 'graphics',
58
+ 'DG.View': 'view',
59
+ 'View': 'view',
60
+ 'DG.Widget': 'widget',
61
+ 'Widget': 'widget',
62
+ 'DG.FuncCall': 'funccall',
63
+ 'FuncCall': 'funccall',
64
+ 'DG.SemanticValue': 'semantic_value',
65
+ 'SemanticValue': 'semantic_value',
66
+ 'any': 'dynamic'
67
+ };
68
+
33
69
  /** Generates an annotation header for a function based on provided metadata. */
34
70
  function getFuncAnnotation(data, comment = '//', sep = '\n') {
35
71
  const isFileViewer = data.tags?.includes(FUNC_TYPES.FILE_VIEWER) ?? false;
@@ -38,24 +74,48 @@ function getFuncAnnotation(data, comment = '//', sep = '\n') {
38
74
  if (data.name) s += `${comment}name: ${data.name}${sep}`;
39
75
  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
76
  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}`;
77
+ s += `${comment}tags: ${isFileViewer && data[pseudoParams.EXTENSIONS] ? data.tags.concat(data[pseudoParams.EXTENSIONS].map(ext => 'fileViewer-' + ext)).join(', ') : data.tags.join(', ')}${sep}`;
42
78
  }
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;
79
+ for (let input of data.inputs ?? []) {
80
+ if (!input) continue;
81
+ let type = input?.type;
82
+ let isArray = false;
83
+ if (type?.includes(`[]`)) {
84
+ type = type.replace(/\[\]$/, '');
85
+ isArray = true;
46
86
  }
87
+ const annotationType = typesToAnnotation[type ?? ''];
88
+ if (input?.options?.type) type = input?.options?.type;else if (annotationType) {
89
+ if (isArray) type = `list<${annotationType}>`;else type = annotationType;
90
+ } else type = 'dynamic';
91
+ console.log(input);
92
+ const options = input?.options?.options ? buildStringOfOptions(input.options.options ?? {}) : '';
93
+ const functionName = (input.options?.name ? input?.options?.name ?? ` ${input.name?.replaceAll('.', '')}` : '')?.trim();
94
+ s += comment + 'input: ' + type + ' ' + functionName + (input.defaultValue !== undefined ? `= ${input.defaultValue}` : '') + ' ' + options.replaceAll('"', '\'') + sep;
47
95
  }
48
96
  if (data.outputs) {
49
97
  for (const output of data.outputs) s += comment + 'output: ' + output.type + (output.name ? ` ${output.name}` : '') + sep;
50
98
  }
99
+ if (data.meta) {
100
+ for (let entry of Object.entries(data.meta)) s += `${comment}meta.${entry[0]}: ${entry[1]}${sep}`;
101
+ }
51
102
  for (const parameter in data) {
52
- if (parameter === pseudoParams.EXTENSION || parameter === pseudoParams.INPUT_TYPE) continue;else if (parameter === pseudoParams.EXTENSIONS) {
103
+ if (parameter === pseudoParams.EXTENSION || parameter === pseudoParams.INPUT_TYPE || parameter === 'meta') continue;else if (parameter === pseudoParams.EXTENSIONS) {
53
104
  if (isFileViewer) continue;
54
105
  s += `${comment}meta.ext: ${data[parameter]}${sep}`;
55
106
  } else if (!headerParams.includes(parameter)) s += `${comment}meta.${parameter}: ${data[parameter]}${sep}`;
56
107
  }
57
108
  return s;
58
109
  }
110
+ function buildStringOfOptions(options) {
111
+ let optionsInString = [];
112
+ for (const [key, value] of Object.entries(options ?? {})) {
113
+ let val = value;
114
+ if (Array.isArray(value)) val = JSON.stringify(value);
115
+ optionsInString.push(`${key}: ${val}`);
116
+ }
117
+ return `{ ${optionsInString.join('; ')} }`;
118
+ }
59
119
  const reservedDecorators = exports.reservedDecorators = {
60
120
  viewer: {
61
121
  metadata: {
@@ -136,6 +196,133 @@ const reservedDecorators = exports.reservedDecorators = {
136
196
  }]
137
197
  },
138
198
  genFunc: generateFunc
199
+ },
200
+ func: {
201
+ metadata: {
202
+ tags: [],
203
+ inputs: [],
204
+ outputs: [{
205
+ name: 'result',
206
+ type: 'dynamic'
207
+ }]
208
+ },
209
+ genFunc: generateFunc
210
+ },
211
+ app: {
212
+ metadata: {
213
+ tags: [FUNC_TYPES.APP],
214
+ inputs: [],
215
+ outputs: [{
216
+ name: 'result',
217
+ type: 'view'
218
+ }]
219
+ },
220
+ genFunc: generateFunc
221
+ },
222
+ autostart: {
223
+ metadata: {
224
+ tags: [FUNC_TYPES.AUTOSTART],
225
+ inputs: [],
226
+ outputs: []
227
+ },
228
+ genFunc: generateFunc
229
+ },
230
+ init: {
231
+ metadata: {
232
+ tags: [FUNC_TYPES.INIT],
233
+ inputs: [],
234
+ outputs: []
235
+ },
236
+ genFunc: generateFunc
237
+ },
238
+ editor: {
239
+ metadata: {
240
+ tags: [FUNC_TYPES.EDITOR],
241
+ inputs: [{
242
+ name: 'call',
243
+ type: 'funccall'
244
+ }],
245
+ outputs: []
246
+ },
247
+ genFunc: generateFunc
248
+ },
249
+ panel: {
250
+ metadata: {
251
+ tags: [FUNC_TYPES.PANEL],
252
+ inputs: [],
253
+ outputs: [{
254
+ name: 'result',
255
+ type: 'widget'
256
+ }]
257
+ },
258
+ genFunc: generateFunc
259
+ },
260
+ folderViewer: {
261
+ metadata: {
262
+ tags: [FUNC_TYPES.FOLDER_VIEWER],
263
+ inputs: [{
264
+ name: 'folder',
265
+ type: 'file'
266
+ }, {
267
+ name: 'files',
268
+ type: 'list<file>'
269
+ }],
270
+ outputs: [{
271
+ name: 'result',
272
+ type: 'widget'
273
+ }]
274
+ },
275
+ genFunc: generateFunc
276
+ },
277
+ semTypeDetector: {
278
+ metadata: {
279
+ tags: [FUNC_TYPES.SEM_TYPE_DETECTOR],
280
+ inputs: [{
281
+ name: 'col',
282
+ type: 'column'
283
+ }],
284
+ outputs: [{
285
+ name: 'result',
286
+ type: 'string'
287
+ }]
288
+ },
289
+ genFunc: generateFunc
290
+ },
291
+ dashboard: {
292
+ metadata: {
293
+ tags: [FUNC_TYPES.DASHBOARD],
294
+ inputs: [],
295
+ outputs: [{
296
+ name: 'result',
297
+ type: 'widget'
298
+ }]
299
+ },
300
+ genFunc: generateFunc
301
+ },
302
+ functionAnalysis: {
303
+ metadata: {
304
+ tags: [FUNC_TYPES.FUNCTION_ANALYSIS],
305
+ inputs: [],
306
+ outputs: [{
307
+ name: 'result',
308
+ type: 'view'
309
+ }]
310
+ },
311
+ genFunc: generateFunc
312
+ },
313
+ converter: {
314
+ metadata: {
315
+ tags: [FUNC_TYPES.CONVERTER],
316
+ inputs: [{
317
+ name: 'value',
318
+ type: 'dynamic'
319
+ }],
320
+ outputs: [{
321
+ name: 'result',
322
+ type: 'dynamic'
323
+ }]
324
+ },
325
+ genFunc: generateFunc
139
326
  }
140
327
  };
141
328
 
@@ -145,8 +332,10 @@ function generateClassFunc(annotation, className, sep = '\n') {
145
332
  }
146
333
 
147
334
  /** Generates a DG function. */
148
- function generateFunc(annotation, funcName, sep = '\n') {
149
- return annotation + `export function _${funcName}() {${sep} return ${funcName}();${sep}}${sep.repeat(2)}`;
335
+ function generateFunc(annotation, funcName, sep = '\n', className = '', inputs = []) {
336
+ let funcSigNature = inputs.map(e => `${e.name}: ${e.type}`).join(', ');
337
+ let funcArguments = inputs.map(e => e.name).join(', ');
338
+ return annotation + `export function _${funcName}(${funcSigNature}) {${sep} return ${className.length > 0 ? `${className}.` : ''}${funcName}(${funcArguments});${sep}}${sep.repeat(2)}`;
150
339
  }
151
340
  function generateImport(className, path, sep = '\n') {
152
341
  return `import {${className}} from '${path}';${sep}`;
@@ -90,6 +90,22 @@ async function getBrowserPage(puppeteer, params = defaultLaunchParameters) {
90
90
  url = await getWebUrl(url, token);
91
91
  console.log(`Using web root: ${url}`);
92
92
  const browser = await puppeteer.launch(params);
93
+ if (params.debug) {
94
+ const targets = await browser.targets();
95
+ const devtoolsTarget = targets.find(t => {
96
+ return t.type() === 'other' && t.url().startsWith('devtools://');
97
+ });
98
+ if (devtoolsTarget) {
99
+ const client = await devtoolsTarget.createCDPSession();
100
+ await client.send('Runtime.enable');
101
+ await client.send('Runtime.evaluate', {
102
+ expression: `
103
+ window.UI.viewManager.showView('network');
104
+ window.UI.dockController.setDockSide('bottom')
105
+ `
106
+ });
107
+ }
108
+ }
93
109
  const page = await browser.newPage();
94
110
  await page.setViewport({
95
111
  width: 1920,
@@ -304,7 +320,7 @@ function saveCsvResults(stringToSave, csvReportDir) {
304
320
  _fs.default.writeFileSync(csvReportDir, modifiedStrings.join('\n'), 'utf8');
305
321
  color.info('Saved `test-report.csv`\n');
306
322
  }
307
- async function helloTests(testsParams, stopOnFail) {
323
+ async function runTests(testsParams, stopOnFail) {
308
324
  let failed = false;
309
325
  let verbosePassed = "";
310
326
  let verboseSkipped = "";
@@ -348,7 +364,8 @@ async function helloTests(testsParams, stopOnFail) {
348
364
  row["flaking"] = success && window.DG.Test.isReproducing;
349
365
  df.changeColumnType('result', window.DG.COLUMN_TYPE.STRING);
350
366
  df.changeColumnType('logs', window.DG.COLUMN_TYPE.STRING);
351
- df.changeColumnType('memoryDelta', window.DG.COLUMN_TYPE.BIG_INT);
367
+ // df.changeColumnType('memoryDelta', (<any>window).DG.COLUMN_TYPE.BIG_INT);
368
+
352
369
  if (resultDF === undefined) resultDF = df;else resultDF = resultDF.append(df);
353
370
  if (row["skipped"]) {
354
371
  verboseSkipped += `Test result : Skipped : ${time} : ${category}: ${testName} : ${result}\n`;
@@ -398,7 +415,7 @@ async function helloTests(testsParams, stopOnFail) {
398
415
  }
399
416
  async function runBrowser(testExecutionData, browserOptions, browsersId, testInvocationTimeout = 3600000) {
400
417
  let testsToRun = {
401
- func: helloTests.toString(),
418
+ func: runTests.toString(),
402
419
  tests: testExecutionData
403
420
  };
404
421
  return await timeout(async () => {
@@ -433,8 +450,8 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
433
450
  if (options.ciCd) window.DG.Test.isCiCd = true;
434
451
  if (options.debug) window.DG.Test.isInDebug = true;
435
452
  return new Promise((resolve, reject) => {
436
- window.helloTests = eval('(' + testData.func + ')');
437
- window.helloTests(testData.tests, options.stopOnTimeout).then(results => {
453
+ window.runTests = eval('(' + testData.func + ')');
454
+ window.runTests(testData.tests, options.stopOnTimeout).then(results => {
438
455
  resolve(results);
439
456
  }).catch(e => {
440
457
  resolve({
@@ -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.77",
3
+ "version": "4.13.79",
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",
@@ -19,7 +22,7 @@
19
22
  "os": "^0.1.2",
20
23
  "papaparse": "^5.4.1",
21
24
  "path": "^0.12.7",
22
- "puppeteer": "^22.10.0",
25
+ "puppeteer": "22.10.0",
23
26
  "puppeteer-screen-recorder": "3.0.3"
24
27
  },
25
28
  "scripts": {
@@ -1,75 +1,233 @@
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 = '\nimport * as DG from \'datagrok-api/dg\';\n\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)
28
- continue;
29
- const ast = parse(content, {
30
- sourceType: 'module',
31
- plugins: ['typescript', [
32
- 'decorators',
33
- {decoratorsBeforeExport: true},
34
- ]],
35
+ const content = fs.readFileSync(file, "utf-8");
36
+ if (!content) continue;
37
+ const ast = tsParser.parse(content, {
38
+ sourceType: "module",
39
+ plugins: [
40
+ ["decorators", { decoratorsBeforeExport: true }],
41
+ "classProperties",
42
+ "typescript",
43
+ ],
35
44
  });
36
45
  const functions = [];
37
- const imports = [];
38
-
39
- traverse(ast, {
40
- ClassDeclaration: (nodePath) => this._addNodeData(nodePath.node, file,
41
- srcDirPath, functions, imports, genImports, genExports),
42
- });
43
-
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');
46
+ let imports = new Set();
47
+
48
+ const fileName = path.basename(file);
49
+ if(fileName === 'package-functions.ts')
50
+ imports = new Set([... this._readImports(content)]);
51
+
52
+ this._walk(ast, (node, parentClass) => {
53
+ const decorators = node.decorators;
54
+ if (!decorators || decorators.length === 0) return;
55
+
56
+ if (node?.type === "ClassDeclaration")
57
+ this._addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports);
58
+
59
+ if (node?.type === "MethodDefinition")
60
+ this._addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports, parentClass);
61
+ });
62
+ this._insertImports([...imports]);
63
+ fs.appendFileSync(this.options.outputPath, functions.join("\n"), "utf-8");
53
64
  }
54
65
 
55
66
  this._writeToPackageFile(packageFilePath, genImports, genExports);
56
67
  });
57
68
  }
58
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
+
91
+ const annotationByReturnType = node?.type === 'MethodDefinition' ? this._readFunctionReturnTypeInfo(node) : '';
92
+ const annotationByReturnTypeObj = { name: 'result', type: annotationByReturnType };
93
+
94
+ let importString = generateImport(node?.type === 'MethodDefinition' ? className : identifierName, modifyImportPath(path.dirname(this.options.outputPath), file));
95
+ imports.add(importString);
96
+ const funcName = `_${identifierName}`;
97
+ const funcAnnotaionOptions = {
98
+ ...reservedDecorators[name]['metadata'],
99
+ ...(annotationByReturnType ? {outputs: [annotationByReturnTypeObj ?? {}]} : {}),
100
+ ...Object.fromEntries(decoratorOptions),
101
+ ...{inputs: functionParams},
102
+ };
103
+
104
+ functions.push(reservedDecorators[name]['genFunc'](getFuncAnnotation(funcAnnotaionOptions), identifierName,'\n', (className ?? ''), functionParams));
105
+
106
+ genImports.push(generateImport(funcName, modifyImportPath(srcDirPath, this.options.outputPath)));
107
+ genExports.push(generateExport(funcName));
108
+ }
109
+
110
+ _readImports (content) {
111
+ const importRegex = /(import(?:[\s\w{},*]+from\s*)?['"]([^'"]+)['"];)/g;
112
+ const results = [];
113
+
114
+ let match;
115
+ while ((match = importRegex.exec(content)) !== null) {
116
+ results.push(`${match[1]}\n`);
117
+ }
118
+ return results;
119
+ }
120
+
121
+ _readDecoratorOptions(properties){
122
+ const resultMap = new Map();
123
+
124
+ for(let prop of properties)
125
+ resultMap.set(prop.key.name, this._evalLiteral(prop.value));
126
+
127
+ return resultMap;
128
+ }
129
+
130
+ _evalLiteral(node) {
131
+ if (!node) return null;
132
+
133
+ switch (node.type) {
134
+ case 'Literal':
135
+ return node.value;
136
+
137
+ case 'ArrayExpression':
138
+ return node.elements.map(el => this._evalLiteral(el));
139
+
140
+ case 'ObjectExpression':
141
+ return Object.fromEntries(
142
+ node.properties.map(p => {
143
+ return [p.key.name || p.key.value, this._evalLiteral(p.value)];
144
+ })
145
+ );
146
+
147
+ default:
148
+ return '';
149
+ }
150
+ }
151
+
152
+ _readMethodParamas(node) {
153
+ const params = node?.value?.params?.map(param => {
154
+ let baseParam = param.type === 'TSNonNullExpression' ? param.expression : param;
155
+ let defaultValue = undefined;
156
+ const options = param.decorators?.length > 0? Object.fromEntries(this._readDecoratorOptions(param.decorators[0]?.expression?.arguments[0].properties)) : undefined;
157
+
158
+ if(baseParam.type === 'AssignmentPattern')
159
+ {
160
+ if (baseParam?.right?.type === 'Literal')
161
+ defaultValue = baseParam?.right?.raw;
162
+ baseParam = baseParam?.left;
163
+ }
164
+
165
+ if (baseParam.type === 'RestElement' || baseParam.type === 'Identifier') {
166
+ let name =
167
+ baseParam.type === 'RestElement'
168
+ ? `...${baseParam.argument.name}`
169
+ : baseParam.name;
170
+
171
+ if (baseParam?.argument?.typeAnnotation)
172
+ name += ': ' + generate(baseParam.argument.typeAnnotation.typeAnnotation).code;
173
+
174
+ let type = '';
175
+ if (baseParam?.typeAnnotation)
176
+ type = generate(baseParam.typeAnnotation.typeAnnotation).code;
177
+ else
178
+ type = 'any';
179
+
180
+ return { name: name, type: type, defaultValue: defaultValue, options: options };
181
+ }
182
+ else if (baseParam.type === 'ObjectPattern' || baseParam.type === 'ArrayPattern') {
183
+ let name = '';
184
+ if (baseParam.type === 'ObjectPattern') {
185
+ const properties = baseParam.properties.map(prop => {
186
+ if (prop.type === 'Property' && prop.key.type === 'Identifier')
187
+ return prop.key.name;
188
+ else if (prop.type === 'RestElement' && prop.argument.type === 'Identifier')
189
+ return `...${prop.argument.name}`;
190
+ else
191
+ return generate(prop).code;
192
+ });
193
+ name = `{ ${properties.join(', ')} }`;
194
+ } else {
195
+ const elements = baseParam.elements.map(elem => {
196
+ if (elem) {
197
+ if (elem.type === 'Identifier')
198
+ return elem.name;
199
+ else
200
+ return generate(elem).code;
201
+ } else return '';
202
+ });
203
+ name = `[${elements.join(', ')}]`;
204
+ }
205
+
206
+ let type = '';
207
+ if (baseParam.typeAnnotation)
208
+ type = generate(baseParam.typeAnnotation.typeAnnotation).code;
209
+ else
210
+ type = 'any';
211
+
212
+ return { name: name, type: type, defaultValue: defaultValue, options: options };
213
+ }
214
+ return { name: 'value', type: 'any', options: undefined };
215
+ });
216
+ return params;
217
+ }
218
+
59
219
  _getTsFiles(root) {
60
220
  const tsFiles = [];
61
221
  const extPattern = /\.tsx?$/;
62
- const excludedFiles = ['package.ts', 'package-test.ts', 'package.g.ts'];
222
+ const excludedFiles = ["package.ts", "package-test.ts", "package.g.ts"];
63
223
 
64
224
  function findFiles(dir) {
65
225
  const files = fs.readdirSync(dir);
66
226
  for (const file of files) {
67
227
  const fullPath = path.join(dir, file);
68
- if (fs.statSync(fullPath).isDirectory())
69
- findFiles(fullPath);
70
- else if (extPattern.test(file) && !excludedFiles.includes(file))
228
+ if (fs.statSync(fullPath).isDirectory()) findFiles(fullPath);
229
+ else if (extPattern.test(file) && !excludedFiles.includes(file))
71
230
  tsFiles.push(fullPath);
72
-
73
231
  }
74
232
  }
75
233
 
@@ -77,70 +235,112 @@ class FuncGeneratorPlugin {
77
235
  return tsFiles;
78
236
  }
79
237
 
238
+ _walk(node, visitor, parent = null) {
239
+ if (!node || typeof node !== "object") return;
240
+
241
+ visitor(node, parent);
242
+
243
+ for (const key in node) {
244
+ const value = node[key];
245
+ if (Array.isArray(value)) {
246
+ value.forEach((child) => {
247
+ if (child && typeof child.type === "string") {
248
+ this._walk(child, visitor, node.type === 'ClassDeclaration'? node : parent );
249
+ }
250
+ });
251
+ } else if (value && typeof value.type === "string") {
252
+ this._walk(value, visitor, node.type === 'ClassDeclaration'? node : parent );
253
+ }
254
+ }
255
+ }
256
+
257
+ _readFunctionReturnTypeInfo(node) {
258
+ let resultType = 'any';
259
+ let isArray = false;
260
+ const annotation = node.value.returnType.typeAnnotation;
261
+ if (annotation?.typeName?.name === 'Promise')
262
+ {
263
+ const argumnets = annotation.typeArguments?.params;
264
+ if (argumnets && argumnets.length===1)
265
+ {
266
+ if (argumnets[0].typeName)
267
+ resultType = argumnets[0].typeName?.right?.name ?? argumnets[0].typeName?.name;
268
+ else if (argumnets[0].type !== 'TSArrayType')
269
+ resultType = this._getTypeNameFromNode(argumnets[0]);
270
+ else if (argumnets[0].elementType.type !== 'TSTypeReference'){
271
+ isArray = true;
272
+ resultType = this._getTypeNameFromNode(argumnets[0]?.elementType);
273
+ }
274
+ else{
275
+ isArray = true;
276
+ resultType = argumnets[0].elementType?.typeName?.name || argumnets[0].elementType?.typeName?.right?.name;
277
+ }
278
+ }
279
+ }
280
+ else{
281
+ if (annotation.type === 'TSTypeReference')
282
+ resultType = annotation.typeName?.right?.name ?? annotation.typeName?.name;
283
+ else if (annotation.type !== 'TSArrayType')
284
+ resultType = this._getTypeNameFromNode(annotation);
285
+ else if (annotation.elementType.type !== 'TSTypeReference'){
286
+ isArray = true;
287
+ resultType = this._getTypeNameFromNode(annotation?.elementType);
288
+ }
289
+ else{
290
+ isArray = true;
291
+ resultType = (annotation?.elementType?.typeName?.name || annotation?.elementType?.typeName?.right?.name);
292
+ }
293
+ }
294
+
295
+ resultType = typesToAnnotation[resultType];
296
+ if (isArray && resultType)
297
+ resultType = `list<${resultType}>`
298
+ return resultType;
299
+ }
300
+
301
+ _getTypeNameFromNode(typeNode) {
302
+ if (typeNode.type === 'TSTypeReference') {
303
+ return typeNode.typeName.name;
304
+ } else if (typeNode.type === 'TSVoidKeyword') {
305
+ return 'void';
306
+ } else if (typeNode.type === 'TSNumberKeyword') {
307
+ return 'number';
308
+ } else if (typeNode.type === 'TSStringKeyword') {
309
+ return 'string';
310
+ } else if (typeNode.type === 'TSBooleanKeyword') {
311
+ return 'boolean';
312
+ } else {
313
+ return typeNode.type;
314
+ }
315
+ }
316
+
80
317
  _clearGeneratedFile() {
81
- fs.writeFileSync(this.options.outputPath, '');
318
+ fs.writeFileSync(this.options.outputPath, baseImport);
82
319
  }
83
320
 
84
321
  _writeToPackageFile(filePath, imports, exports) {
85
- if (imports.length !== exports.length)
86
- return;
87
- let content = fs.readFileSync(filePath, 'utf-8');
322
+ if (imports.length !== exports.length) return;
323
+ let content = fs.readFileSync(filePath, "utf-8");
88
324
  for (let i = 0; i < imports.length; i++) {
89
325
  const importStatement = imports[i];
90
326
  const exportStatement = exports[i];
91
327
  if (!content.includes(importStatement.trim()))
92
328
  content = importStatement + content + exportStatement;
93
329
  }
94
- fs.writeFileSync(filePath, content, 'utf-8');
95
- }
96
-
97
- _addNodeData(node, file, srcDirPath, functions, imports, genImports, genExports) {
98
- if (!node.decorators || !node.decorators.length)
99
- return;
100
-
101
- function modifyImportPath(dirPath, filePath) {
102
- const relativePath = path.relative(dirPath, filePath);
103
- return `./${relativePath.slice(0, relativePath.length - 3).replace(/\\/g, '/')}`;
104
- }
105
-
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
- }
116
-
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));
124
-
125
- genImports.push(generateImport(funcName, modifyImportPath(srcDirPath, this.options.outputPath)));
126
- genExports.push(generateExport(funcName));
127
- }
128
- }
330
+ fs.writeFileSync(filePath, content, "utf-8");
129
331
  }
130
332
 
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);
333
+ _insertImports(importArray) {
334
+ if (fs.existsSync(this.options.outputPath)) {
335
+ const content = fs.readFileSync(this.options.outputPath, "utf-8");
336
+ if (content)
337
+ importArray.push(content);
338
+ const output = importArray.join("");
339
+ fs.writeFileSync(this.options.outputPath, `${output}`, "utf-8");
340
+ }
341
+ else
342
+ fs.writeFileSync(this.options.outputPath, `${baseImport}\n${importArray.join("")}`, "utf-8");
143
343
  }
144
344
  }
145
345
 
146
- module.exports = FuncGeneratorPlugin;
346
+ module.exports = FuncGeneratorPlugin;