@cicctencent/tars2node-cli 0.0.1

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,645 @@
1
+ // @ts-nocheck
2
+ /* eslint-disable*/
3
+ 'use strict';
4
+
5
+ const path = require('path');
6
+ const fs = require('fs-extra');
7
+ const ts = require('typescript');
8
+ const pkg = require('../../../package.json');
9
+
10
+ const ConstDef = require('./def/const');
11
+ const EnumDef = require('./def/enum');
12
+ const InterfDef = require('./def/interf');
13
+ const ProxyDef = require('./def/proxy');
14
+ const StructDef = require('./def/struct');
15
+
16
+ const Printer = require('./printer');
17
+ const Typing = require('./typing');
18
+ const Finder = require('../finder');
19
+ const fse = require('fs-extra');
20
+
21
+ /**
22
+ *
23
+ * 声明JCE-Struct节点类型
24
+ * @typedef { refType | vecType | string | mapType } tarsType
25
+ *
26
+ * @typedef {object} refType
27
+ * @property {string} target
28
+ * @property {string[]} nested
29
+ *
30
+ * @typedef {object} vecType
31
+ * @property {'vector'} type
32
+ * @property {tarsType} target
33
+ *
34
+ * @typedef {object} mapType
35
+ * @property {'map'} type
36
+ * @property {tarsType} key
37
+ * @property {tarsType} value
38
+ *
39
+ * @typedef {object} constValue
40
+ * @property {tarsType} type
41
+ * @property {*} init
42
+ *
43
+ * @typedef {object} TarsConstNode
44
+ * @property {string} id
45
+ * @property {'const'} type
46
+ * @property {constValue} value
47
+ *
48
+ * @typedef {object} enumValue
49
+ * @property {string} id
50
+ * @property {number} value
51
+ *
52
+ * @typedef {object} TarsEnumNode
53
+ * @property {string} id
54
+ * @property {'enum'} type
55
+ * @property {enumValue[]} value
56
+ *
57
+ * @typedef {object} param
58
+ * @property {string} id
59
+ * @property {'in'|'out'} io
60
+ * @property {tarsType} type
61
+ *
62
+ * @typedef {object} TarsInterfMethod
63
+ * @property {string} id
64
+ * @property {'func'} type
65
+ * @property {tarsType} output
66
+ * @property {param[]} params
67
+ *
68
+ * @typedef {object} TarsInterfNode
69
+ * @property {string} id
70
+ * @property {'interface'} type
71
+ * @property {TarsInterfMethod[]} methods
72
+ *
73
+ * @typedef {object} member
74
+ * @property {number} index
75
+ * @property {string} id
76
+ * @property {boolean} require
77
+ * @property {tarsType} type
78
+ * @property {null|string} init
79
+ *
80
+ * @typedef {object} TarsStructNode
81
+ * @property {string} id
82
+ * @property {'struct'} type
83
+ * @property {member[]} members
84
+ *
85
+ * @typedef {TarsStructNode|TarsInterfNode|TarsEnumNode|TarsConstNode} TarsNode
86
+ *
87
+ * @typedef {object} TarsModule
88
+ * @property {string} pathname
89
+ * @property {TarsNode[]} value
90
+ *
91
+ * @typedef {Object.<string, TarsModule>} TarsTree
92
+ *
93
+ * 工具类类型
94
+ * @typedef {import("./printer")} Printer
95
+ * @typedef {import("./typing")} Typing
96
+ *
97
+ * @typedef {object} CompilerOptions
98
+ * @property {'all'|'client'|'service'} mode
99
+ * @property {'es5'|'all'} type
100
+ * @property {'string'|'number'|'bigint'} long
101
+ * @property {boolean} withReturn
102
+ * @property {boolean} gather
103
+ * @property {boolean} interface
104
+ * @property {string} [output]
105
+ *
106
+ */
107
+
108
+ class Generator {
109
+ /**
110
+ * @constructor
111
+ * @param {TarsTree} tree
112
+ * @param {string} entryPath
113
+ * @param {CompilerOptions} options - 编译配置选项
114
+ */
115
+ constructor(tree, entryPath, options) {
116
+ const { dir } = path.parse(entryPath);
117
+ this._validOptions(options);
118
+
119
+ /** @type {string[]} */
120
+ this._modulesToImport = [];
121
+ this._printer = new Printer();
122
+ this._finder = new Finder(tree);
123
+ this._typing = new Typing(this._finder, options);
124
+ this._tree = tree;
125
+ this._options = options;
126
+
127
+ this._entryPath = entryPath;
128
+ this._entryDir = path.dirname(entryPath);
129
+ this._dir = options.output || dir;
130
+
131
+ this.fixRecurAndMerge = options.fixRecurAndMerge || false
132
+ this.useNameSpace = options.useNameSpace
133
+
134
+ this._hasInterf = false;
135
+ this._hasStruct = false;
136
+
137
+ this._gConst = new ConstDef(this._printer, this._typing, options);
138
+ this._gEnum = new EnumDef(this._printer, this._typing, options);
139
+ this._gInterf = new InterfDef(this._printer, this._typing, options);
140
+ this._gProxy = new ProxyDef(this._printer, this._typing, options);
141
+ this._gStruct = new StructDef(this._printer, this._typing, options);
142
+
143
+ /** @type {string[]} */
144
+ this._mappingMembers = [];
145
+ this._unitDict = {}; //避免重复声明
146
+ }
147
+
148
+ async build() {
149
+ this._finder.refresh();
150
+ this._typing.refresh();
151
+ await Promise.all(Object.keys(this._tree)
152
+ .map(async (key) => {
153
+ await this._generateModule(key, this._tree[key]);
154
+ }));
155
+ }
156
+
157
+ async memo() {
158
+ this._finder.refresh();
159
+ this._typing.refresh();
160
+ const output = await Promise.all(Object.keys(this._tree)
161
+ .map(key => this._generateModuleInMemo(key, this._tree[key])));
162
+ return output;
163
+ }
164
+
165
+ getTree() {
166
+ return this._tree;
167
+ }
168
+
169
+ /**
170
+ * @param {CompilerOptions} options
171
+ */
172
+ _validOptions(options) {
173
+ if (!['string', 'number', 'bigint'].includes(options.long)) {
174
+ throw new Error('options.long should be one of string|number|bigint');
175
+ }
176
+ }
177
+
178
+ /**
179
+ * @param {string} moduleName
180
+ */
181
+ _importRelatedModules(moduleName) {
182
+ this._hasInterf = false;
183
+ this._hasStruct = false;
184
+ this._modulesToImport = [];
185
+ this._mappingMembers = [];
186
+ const { value } = this._tree[moduleName];
187
+ value.forEach((m) => {
188
+ if (m.type === 'interface') {
189
+ this._hasInterf = true;
190
+ m.methods.forEach(({ params, output }) => {
191
+ this._checkIsTypeNeedImport(output, moduleName, this.fixRecurAndMerge);
192
+ params && params.forEach(({ type }) => { // 测试函数允许没有参数
193
+ this._checkIsTypeNeedImport(type, moduleName, this.fixRecurAndMerge);
194
+ });
195
+ });
196
+ } else if (m.type === 'struct') {
197
+ this._hasStruct = true;
198
+ m.members.forEach(({ type }) => {
199
+ this._checkIsTypeNeedImport(type, moduleName, this.fixRecurAndMerge);
200
+ });
201
+ }
202
+ });
203
+ }
204
+ // 重置所有存在nestid的module name
205
+ resetModuleImport(moduleName) {
206
+ this._tree[moduleName].value.forEach((m, index1) => {
207
+ if (m.type === 'interface') {
208
+ // m.methods.map(({ params, output }) => {
209
+ // // this._tree[moduleName].value[index]
210
+ // if (this._checkIsTypeNeedImport(output, moduleName)) {
211
+ // this._tree[moduleName].value[index]
212
+ // }
213
+ // params && params.forEach(({ type }) => { // 测试函数允许没有参数
214
+ // this._checkIsTypeNeedImport(type, moduleName);
215
+ // });
216
+ // });
217
+ } else if (m.type === 'struct') {
218
+ m.members.forEach((member, index2) => {
219
+ if (this._checkIsTypeNeedImport(this._tree[moduleName].value[index1].members[index2].type, moduleName, true)) {
220
+ // this._tree[moduleName].value[index].members[index2].type.nested.
221
+ }
222
+ });
223
+ }
224
+ });
225
+ }
226
+
227
+ /**
228
+ * @description 判断类型是否需要导入对应文件
229
+ * @param {tarsType} t - 需要检查的类型
230
+ * @param {string} current
231
+ */
232
+ _checkIsTypeNeedImport(t, current, removeOther = false) {
233
+ /** @type {(name: string) => string} */
234
+ const checkIsCurrent = name => /** @type {string} */ {
235
+ return (name && name !== current && !this._modulesToImport.includes(name) && this._modulesToImport.push(name))
236
+ }
237
+ if (typeof t === 'string') return;
238
+ if (this._typing.isRefType(t)) {
239
+ const [moduleName] = t.nested;
240
+ if (removeOther && moduleName && moduleName != current) {
241
+ return t.nested = [current]
242
+ }
243
+ checkIsCurrent(moduleName);
244
+ } else if (this._typing.isVector(t)) {
245
+ if (this._typing.isRefType(t.target)) {
246
+ const [moduleName] = t.target.nested;
247
+ if (removeOther && moduleName && moduleName != current) {
248
+ return t.target.nested = [current]
249
+ }
250
+ checkIsCurrent(moduleName);
251
+ } else {
252
+ this._checkIsTypeNeedImport(t.target, current, removeOther)
253
+ }
254
+ } else if (this._typing.isMap(t)) {
255
+ if (this._typing.isRefType(t.key)) {
256
+ const [moduleName] = t.key.nested;
257
+ if (removeOther && moduleName && moduleName != current) {
258
+ return t.key.nested = [current]
259
+ }
260
+ checkIsCurrent(moduleName);
261
+ }
262
+
263
+ if (this._typing.isRefType(t.value)) {
264
+ const [moduleName] = t.value.nested;
265
+ if (removeOther && moduleName && moduleName != current) {
266
+ return t.value.nested = [current]
267
+ }
268
+ checkIsCurrent(moduleName);
269
+ } else {
270
+ this._checkIsTypeNeedImport(t.value, current, removeOther)
271
+ }
272
+ }
273
+ }
274
+
275
+ /**
276
+ * @param {string} moduleName
277
+ * @param {TarsNode} node
278
+ * @param {'client'|'service'|'web'} type
279
+ */
280
+ _generateNode(moduleName, node, type) {
281
+ if(node.type === 'key') return;
282
+
283
+ let unitDict = this._unitDict[moduleName];
284
+ if (!unitDict) {
285
+ unitDict = this._unitDict[moduleName] = {};
286
+ }
287
+ if (unitDict[node.id]) {
288
+ return;
289
+ } else {
290
+ unitDict[node.id] = 1;
291
+ }
292
+
293
+ switch (node.type) {
294
+ case 'struct': {
295
+ this._gStruct.reset(moduleName, node).build();
296
+ break;
297
+ }
298
+ case 'interface': {
299
+ if (this._options.mode === 'web') {
300
+ // TODO 前端用http调用,没有实现Buffer的方式
301
+ return;
302
+ }
303
+ if (type === 'client') this._gProxy.reset(moduleName, node).build();
304
+ else {
305
+ this._gInterf.reset(moduleName, node).build();
306
+ this._mappingMembers.push(node.id);
307
+ }
308
+ break;
309
+ }
310
+ case 'enum': {
311
+ this._gEnum.reset(moduleName, node).build();
312
+ break;
313
+ }
314
+ case 'const': {
315
+ this._gConst.reset(moduleName, node).build();
316
+ break;
317
+ }
318
+ default:
319
+ throw new Error(`Type: ##${node || 'key'}## is not support yet`);
320
+ }
321
+ }
322
+
323
+ async _generateModuleInMemo(moduleName, targetModule) {
324
+ const { pathname } = targetModule;
325
+ const { name } = path.parse(pathname);
326
+
327
+ const output = {};
328
+
329
+ if (pathname !== this._entryPath) {
330
+ output[`${name}`] = await this._generateModuleAsString(moduleName, targetModule);
331
+ } else if (this._options.mode === 'all') {
332
+ output[name] = await this._generateModuleAsString(moduleName, targetModule);
333
+ output[`${name}Proxy`] = await this._generateModuleAsString(moduleName, targetModule, 'client');
334
+ } else if (this._options.mode === 'client') {
335
+ output[`${name}Proxy`] = await this._generateModuleAsString(moduleName, targetModule, 'client');
336
+ } else {
337
+ output[name] = await this._generateModuleAsString(moduleName, targetModule);
338
+ }
339
+
340
+ return output;
341
+ }
342
+
343
+ /**
344
+ * @param {string} moduleName
345
+ * @param {TarsModule} targetModule
346
+ * @param {'client'|'service'} [type='service']
347
+ */
348
+ async _generateModuleAsString(
349
+ moduleName,
350
+ { value },
351
+ type = 'service',
352
+ ) {
353
+ this._importRelatedModules(moduleName);
354
+ this._printer.resetIndent();
355
+ this._copyRight(type);
356
+ this._prepare(type);
357
+
358
+ this._printer
359
+ .line('export namespace ')
360
+ .word(moduleName, 'R')
361
+ .scope(() => {
362
+ if (this._hasInterf && type === 'client') {
363
+ this._commonInterface();
364
+ }
365
+ value.forEach((node) => {
366
+ this._generateNode(moduleName, node, type);
367
+ this._printer.newline();
368
+ });
369
+ if (this._mappingMembers.length > 0) {
370
+ this._printer
371
+ .line('export interface Mapping ')
372
+ .scope(() => {
373
+ this._mappingMembers.forEach((name) => {
374
+ this._printer.line(`${name}: typeof ${name}Imp;`);
375
+ });
376
+ this._printer.line('[index: string]: new() => unknown;');
377
+ })
378
+ .semicolon();
379
+ this._printer
380
+ .line('export const mapping: Mapping = ')
381
+ .scope(() => {
382
+ this._mappingMembers.forEach((name) => {
383
+ this._printer.line(`${name}: ${name}Imp,`);
384
+ });
385
+ })
386
+ .semicolon();
387
+ }
388
+ });
389
+ const buf = this._printer.print();
390
+ return this._writeToString(buf.trim());
391
+ }
392
+
393
+ /**
394
+ * @param {string} buf
395
+ * @return {string}
396
+ */
397
+ _writeToString(buf) {
398
+ let content = buf;
399
+ if (this._options.type !== 'ts') {
400
+ content = ts.transpileModule(buf, {
401
+ compilerOptions: {
402
+ module: 'commonjs',
403
+ target: 'es6',
404
+ sourceMap: true,
405
+
406
+ moduleResolution: 'node',
407
+ esModuleInterop: true,
408
+ allowJs: false,
409
+ allowUnreachableCode: true,
410
+ allowUnusedLabels: true,
411
+ noStrictGenericChecks: true,
412
+ skipLibCheck: true,
413
+ },
414
+ }).outputText;
415
+ }
416
+ return content;
417
+ }
418
+
419
+ /**
420
+ * @param {string} moduleName
421
+ * @param {TarsModule} targetModule
422
+ */
423
+ async _generateModule(moduleName, targetModule) {
424
+ const { pathname, aimFile } = targetModule;
425
+ const { name } = path.parse(pathname);
426
+ const ext = this._options.type !== 'ts' ? 'js' : 'ts';
427
+
428
+ if (aimFile) {
429
+ // 把引用文件放在相对位置
430
+ await this._generateModuleAndWrite(moduleName, targetModule, `${aimFile}.${ext}`);
431
+ } else if (pathname !== this._entryPath) {
432
+ await this._generateModuleAndWrite(moduleName, targetModule, `${name}.${ext}`);
433
+ } else if (this._options.mode === 'all') {
434
+ await this._generateModuleAndWrite(moduleName, targetModule, `${name}.${ext}`);
435
+ await this._generateModuleAndWrite(moduleName, targetModule, `${name}Proxy.${ext}`, 'client');
436
+ } else if (this._options.mode === 'client') {
437
+ await this._generateModuleAndWrite(moduleName, targetModule, `${name}Proxy.${ext}`, 'client');
438
+ } else {
439
+ await this._generateModuleAndWrite(moduleName, targetModule, `${name}.${ext}`);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * @param {string} moduleName
445
+ * @param {TarsModule} targetModule
446
+ * @param {string} fileName
447
+ * @param {'client'|'service'} [type='service']
448
+ */
449
+ async _generateModuleAndWrite(
450
+ moduleName,
451
+ { value },
452
+ fileName,
453
+ type = 'service',
454
+ ) {
455
+ if (this.fixRecurAndMerge) {
456
+ this.resetModuleImport(moduleName)
457
+ // return fse.writeFileSync('/home/wlq/workstation_1/jv-awesome/node_modules/@jv/tars2node-cli/test/hhhhhh.json', JSON.stringify(this._tree[moduleName].value ,"","\t"), );
458
+ }
459
+ this._importRelatedModules(moduleName);
460
+ this._printer.resetIndent();
461
+ // todo: 在window和mac的路径不一样导致变化,先注释掉
462
+ // this._copyRight(type);
463
+ this._prepare(type);
464
+ const commonCode = () => {
465
+ if (this._hasInterf && type === "client") {
466
+ this._commonInterface();
467
+ }
468
+ value.forEach((node) => {
469
+ this._generateNode(moduleName, node, type);
470
+ this._printer.newline();
471
+ });
472
+ if (this._mappingMembers.length > 0) {
473
+ this._printer
474
+ .line("export interface Mapping ")
475
+ .scope(() => {
476
+ this._mappingMembers.forEach((name) => {
477
+ this._printer.line(`${name}: typeof ${name}Imp;`);
478
+ });
479
+ this._printer.line("[index: string]: new() => unknown;");
480
+ })
481
+ .semicolon();
482
+ this._printer
483
+ .line("export const mapping: Mapping = ")
484
+ .scope(() => {
485
+ this._mappingMembers.forEach((name) => {
486
+ this._printer.line(`${name}: ${name}Imp,`);
487
+ });
488
+ })
489
+ .semicolon();
490
+ }
491
+ }
492
+ if (this.useNameSpace) {
493
+ this._printer
494
+ .line('export namespace ')
495
+ .word(moduleName, 'R')
496
+ .scope(() => {
497
+ commonCode()
498
+ });
499
+ } else {
500
+ commonCode()
501
+ }
502
+ const buf = this._printer.print();
503
+ await this._writeToFile(fileName, buf.trim());
504
+ }
505
+
506
+ _commonInterface() {
507
+ this._printer
508
+ .newline()
509
+ .word('export interface SharedArgumentInfo', 'R')
510
+ .scope(() => {
511
+ this._printer
512
+ .line('name: string;')
513
+ .line('class: string;')
514
+ .line("direction: 'in' | 'out';");
515
+ })
516
+ .line('export interface SharedFunctionInfo ')
517
+ .scope(() => {
518
+ this._printer
519
+ .line('name: string;')
520
+ .line('return: string;')
521
+ .line('arguments: SharedArgumentInfo[];');
522
+ });
523
+ }
524
+
525
+ /**
526
+ * @param {string} pathname
527
+ * @param {string} buf
528
+ * @return {Promise<string>}
529
+ */
530
+ _writeToFile(pathname, buf) {
531
+ let content = buf;
532
+ const filename = path.resolve(this._dir, pathname);
533
+ if (this._options.type !== 'ts') {
534
+ content = ts.transpileModule(buf, {
535
+ compilerOptions: {
536
+ module: 'commonjs',
537
+ target: 'es6',
538
+ sourceMap: false,
539
+
540
+ moduleResolution: 'node',
541
+ esModuleInterop: true,
542
+ allowJs: false,
543
+ allowUnreachableCode: true,
544
+ allowUnusedLabels: true,
545
+ noStrictGenericChecks: true,
546
+ skipLibCheck: true,
547
+ },
548
+ }).outputText;
549
+ }
550
+ return new Promise((res, rej) => {
551
+ fs.outputFile(filename, content, (err) => {
552
+ if (err) rej(err);
553
+ // console.info('\x1b[34m', `👻 Write to ${filename} success`);
554
+ res(filename);
555
+ });
556
+ });
557
+ }
558
+
559
+ _copyRight(type) {
560
+ this._printer
561
+ .line('// **********************************************************************')
562
+ .line(`// Parsed And Generated By ${pkg.name}@${pkg.version}`)
563
+ .line('// Maintained By <liquidliang>')
564
+ .line(`// Generated from "${path.relative(process.cwd(), this._entryPath)}" by ${this._options.mode} Mode.`)
565
+ .line(`// This type is ${type}`)
566
+ .line('// **********************************************************************');
567
+ }
568
+
569
+ /**
570
+ * @param {'client'|'service'} [type='service']
571
+ */
572
+ _prepare(type) {
573
+ if (this._options.mode !== 'web') {
574
+ this._printer
575
+ .newline()
576
+ .line('/* tslint:disable */')
577
+ .newline()
578
+ .line(`/// <reference types="node" />`)
579
+ }
580
+ this._modulesToImport.forEach((mod) => {
581
+ const { pathname } = this._tree[mod];
582
+ const { name } = path.parse(pathname);
583
+ let aimFile = `./${name}`;
584
+ if (this._options.useModuleName) {
585
+ aimFile = path.relative(this._entryDir, pathname).replace(/\.\w+$/, '').replace(/\\/g, '/');
586
+ if (!/^[\.\\\/]/.test(aimFile)) {
587
+ aimFile = `./${aimFile}`;
588
+ }
589
+ this._tree[mod].aimFile = aimFile;
590
+ }
591
+ this._printer.line(`import * as $${mod} from '${aimFile}';`);
592
+ });
593
+ if (this._options.mode === 'web') {
594
+ return;
595
+ }
596
+ this._printer
597
+ .line("import * as TarsStream from '@tars/stream';");
598
+ if (this._hasInterf) {
599
+ this._printer.line("import * as TarsRpc from '@tars/rpc';");
600
+ if (type !== 'client') this._printer.line("import assert from 'assert';");
601
+ else {
602
+ this._printer
603
+ .line('const _makeError = function (data: any, message: string, type?: number): any ')
604
+ .scope(() => {
605
+ this._printer
606
+ .line('const error: any = new Error(message || "");')
607
+ .line('error.request = data.request;')
608
+ .line('error.response = ')
609
+ .scope(() => {
610
+ this._printer.line('costtime: data.request.costtime');
611
+ })
612
+ .semicolon();
613
+ this._printer
614
+ .line('if (type === TarsRpc.error.CLIENT.DECODE_ERROR) ')
615
+ .scope(() => {
616
+ this._printer
617
+ .line('error.name = "DECODE_ERROR";')
618
+ .line('error.response.error = ')
619
+ .scope(() => {
620
+ this._printer
621
+ .line('code: type,')
622
+ .line('message: message');
623
+ }).semicolon();
624
+ }).word('else', 'LR').scope(() => {
625
+ this._printer
626
+ .line('error.name = "RPC_ERROR";')
627
+ .line('error.response.error = data.error;');
628
+ })
629
+ .line('return error;');
630
+ }).semicolon();
631
+ }
632
+ }
633
+
634
+
635
+ this._printer.newline();
636
+ if (this._hasStruct) this._printer.line('const _hasOwnProperty = Object.prototype.hasOwnProperty;').newline();
637
+ }
638
+ }
639
+
640
+ /**
641
+ * @param {TarsTree} tree
642
+ * @param {string} entryPath
643
+ * @param {CompilerOptions} options - 编译配置选项
644
+ */
645
+ module.exports = Generator;
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const SuperPrinter = require('../printer');
4
+
5
+ class Printer extends SuperPrinter {
6
+ /**
7
+ * @typedef {object} classMethodInfo
8
+ * @property {string} name
9
+ * @property {[string, string][]} params
10
+ * @property {string} [type]
11
+ * @property {string} [returnType]
12
+ * @property {() => void} [callback]
13
+ *
14
+ * @param {classMethodInfo} info
15
+ * @return {this}
16
+ */
17
+ classMethod({
18
+ name,
19
+ params,
20
+ type,
21
+ returnType,
22
+ callback,
23
+ }) {
24
+ this.newline();
25
+ if (type) this.word(type, 'R');
26
+ this.word(name).word('(');
27
+ params.forEach(([p, t], i) => {
28
+ this.word(`${p}: ${t}`);
29
+ if (i + 1 < params.length) {
30
+ this.word(',').space(1);
31
+ }
32
+ });
33
+ this.word(')');
34
+ if (returnType) this.word(`: ${returnType}`);
35
+ this.space(1).scope(callback);
36
+
37
+ return this;
38
+ }
39
+
40
+ /**
41
+ * @typedef {object} classPropsInfo
42
+ * @property {string} name
43
+ * @property {string} valueType
44
+ * @property {string} [type]
45
+ * @property {string} [value]
46
+ *
47
+ * @param {classPropsInfo} info
48
+ * @return {this}
49
+ */
50
+ classProps({
51
+ name,
52
+ valueType,
53
+ value,
54
+ type,
55
+ }) {
56
+ this.newline();
57
+ if (type) this.word(type, 'R');
58
+ this.word(`${name}: ${valueType}`);
59
+ if (value) this.word('=', 'LR').word(value);
60
+ this.semicolon();
61
+
62
+ return this;
63
+ }
64
+ }
65
+
66
+ module.exports = Printer;