@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,383 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const peg = require('pegjs');
6
+ const assert = require('assert');
7
+ const globby = require('globby');
8
+ const util = require('util');
9
+
10
+ const PEG_SOURCE = './../../jce.pegjs';
11
+ const parser = buildParser();
12
+
13
+ class AST {
14
+ /**
15
+ * constructor
16
+ * @param {string} pathname
17
+ */
18
+ constructor(pathname) {
19
+ assert(pathname.length > 0, 'Need at least one entry file to parse.');
20
+
21
+ this.pathname = pathname;
22
+ /** @type {Object.<string, *>} */
23
+ this.parsed = {};
24
+ this.parser = parser;
25
+ /** @type {Object.<string, *>} */
26
+ this.refMap = {};
27
+ /** @type {Object.<string, *>} */
28
+ this.fileRefMap = {};
29
+ // 倒排索引
30
+ this.invertedMap = {};
31
+ /** @type {string[]} */
32
+ this.entry = [];
33
+ /** 入口文件路径 */
34
+ this.entryFilepath = '';
35
+ /** 记录文件是否已经被加载 */
36
+ this.refFilePath = new Map();
37
+ }
38
+
39
+ // 原始编译方式
40
+ buildAST() {
41
+ const result = this.parseTars();
42
+
43
+ this.setRefMap(result.modules, result.pathname, true);
44
+ this.handleRefRecursively(result.refs, this.pathname);
45
+ return this.refMap;
46
+ }
47
+
48
+ // 按文件模块解析AST
49
+ buildFileAST() {
50
+ const result = this.parseTars();
51
+ this.setFileRefMap(result.modules, result.pathname);
52
+ this.handleFileRefRecursively(result.refs, this.pathname);
53
+ // fs.writeFileSync('fileRefMap.json', JSON.stringify(this.fileRefMap, null, 2),{encoding: 'utf8'});
54
+ // fs.writeFileSync('invertedMap.json', JSON.stringify(this.invertedMap, null, 2),{encoding: 'utf8'});
55
+ this.changeModuleName()
56
+ // fs.writeFileSync('refMap.json', JSON.stringify(this.refMap, null, 2),{encoding: 'utf8'});
57
+ return this.refMap;
58
+ }
59
+
60
+ // 解决递归问题,合并依赖文件。
61
+ buildASTRecursiveMerge() {
62
+ const result = this.parseTars();
63
+ this.setRefMapRecuriveMerge(result.modules, result.pathname, true);
64
+ this.handleRefRecursivelyMerge(result.refs, this.pathname, true);
65
+ // TODO:: 归一化 this.changeModuleName()
66
+ this.changeModuleNameOrigin();
67
+ // 合并文件,并去除依赖
68
+ this.mergeModuleFile();
69
+ return this.refMap;
70
+ }
71
+
72
+ // 合并 include 文件
73
+ mergeModuleFile() {
74
+ const entryModelName = path.basename(this.pathname, '.tars');
75
+ Object.keys(this.refMap).forEach(key => {
76
+ const moduleName = path.basename(this.refMap[key].pathname, '.tars');
77
+ // 入口文件作为合并基准
78
+ if (moduleName == entryModelName) {
79
+ } else {
80
+ this.refMap[entryModelName].value = this.refMap[entryModelName].value.concat(this.refMap[key].value);
81
+ delete this.refMap[key];
82
+ }
83
+ // this.refMap[entryModelName].value.map(m => {
84
+ // if (m.type === 'interface') {
85
+ // // TODO:: 处理方法
86
+ // // m.methods.forEach(({ params, output }) => {
87
+ // // this._checkIsTypeNeedImport(output, moduleName);
88
+ // // params && params.forEach(({ type }) => { // 测试函数允许没有参数
89
+ // // this._checkIsTypeNeedImport(type, moduleName);
90
+ // // });
91
+ // // });
92
+ // } else if (m.type === 'struct') {
93
+ // // 处理结构体
94
+ // m.members.forEach(({ type }) => {
95
+ // this.renameImport(type, entryModelName);
96
+ // });
97
+ // }
98
+ // })
99
+ });
100
+ }
101
+ /**
102
+ * 修改 module name
103
+ * */
104
+ changeModuleNameOrigin() {
105
+ // 记录映射关系
106
+ let moduleMap = new Map();
107
+ Object.keys(this.refMap).forEach(key => {
108
+ const moduleName = path.basename(this.refMap[key].pathname, '.tars');
109
+ moduleMap.set(key, moduleName);
110
+ this.refMap[moduleName] = this.refMap[key];
111
+ // 去除改变之前的多余的module
112
+ delete this.refMap[key];
113
+ });
114
+ // 改变包的引用名称
115
+ Object.keys(this.refMap).forEach(key => {
116
+ this.refMap[key].value &&
117
+ this.refMap[key].value.forEach(item => {
118
+ item.members &&
119
+ item.members.forEach(member => {
120
+ // 需要改变引用包名称
121
+ if (util.isObject(member.type) && member.type.nested && member.type.nested.length > 0) {
122
+ member.type.nested = member.type.nested.map(module => {
123
+ return moduleMap.get(module);
124
+ });
125
+ }
126
+ });
127
+ });
128
+ });
129
+ }
130
+
131
+ /**
132
+ * @param {*[]} modules
133
+ * @param {string} pathname
134
+ */
135
+ setFileRefMap(modules, pathname) {
136
+ const fileRefMap = this.fileRefMap[pathname] = {};
137
+ modules.forEach(({ id, type, value }) => {
138
+ assert(type === 'module', `File ${id} is not a tars module.`);
139
+ (value || []).forEach(item => {
140
+ // 这里不考虑从哪里引用了,因为法确定
141
+ this.invertedMap[item.id] = {
142
+ pathname,
143
+ moduleId: id
144
+ };
145
+ });
146
+ fileRefMap[id] = value;
147
+ });
148
+ }
149
+
150
+ /** 递归处理引用
151
+ * @param {{type: string, target: string}[]} refList
152
+ * @param {string} pathname
153
+ */
154
+ handleFileRefRecursively(refList, pathname) {
155
+ const parsedRefs = this.parseRefs(refList, pathname);
156
+
157
+ parsedRefs.forEach(({ refs, pathname: _pathname, modules }) => {
158
+ this.setFileRefMap(modules, _pathname);
159
+ this.handleFileRefRecursively(refs, _pathname);
160
+ });
161
+ }
162
+
163
+ /** 递归处理引用
164
+ * @param {{type: string, target: string}[]} refList
165
+ * @param {string} pathname
166
+ */
167
+ handleRefRecursively(refList, pathname) {
168
+ const parsedRefs = this.parseRefs(refList, pathname);
169
+
170
+ parsedRefs.forEach(({ refs, pathname: _pathname, modules }) => {
171
+ this.setRefMap(modules, _pathname);
172
+ this.handleFileRefRecursively(refs, _pathname);
173
+ });
174
+ }
175
+
176
+ /**
177
+ * 修改 module name
178
+ * */
179
+ changeModuleName() {
180
+ // 记录映射关系
181
+ let moduleMap = {};
182
+ Object.keys(this.fileRefMap).forEach(key => {
183
+ const moduleName = path.basename(key).replace(/\.\w+$/, '');
184
+ const module = this.fileRefMap[key];
185
+ // path到name
186
+ moduleMap[key] = moduleName;
187
+ // todo: 目前一个文件只允许导出1个module
188
+ this.refMap[moduleName] = {
189
+ pathname: key,
190
+ value: module[Object.keys(module)[0]],
191
+ };
192
+ });
193
+
194
+ const changeNest = type => {
195
+ if (typeof type !== 'object') {
196
+ return;
197
+ }
198
+ if (typeof type.key === 'object') {
199
+ changeNest(type.key);
200
+ }
201
+ if (typeof type.value === 'object') {
202
+ changeNest(type.value);
203
+ }
204
+
205
+ if (typeof type.target === 'object') {
206
+ changeNest(type.target);
207
+ }
208
+
209
+ if (typeof type.target !== 'string') {
210
+ return;
211
+ }
212
+ const conf = this.invertedMap[type.target];
213
+ // 需要改变引用包名称
214
+ if (conf) {
215
+ const moduleName = moduleMap[conf.pathname];
216
+ if (moduleName) {
217
+ type.nested = [moduleName];
218
+ }
219
+ }
220
+ };
221
+
222
+ // 改变包的引用名称
223
+ Object.keys(this.refMap).forEach(key => {
224
+ const refObj = this.refMap[key];
225
+ refObj.value.forEach(item => {
226
+ (item.members || []).forEach(member => {
227
+ changeNest(member.type);
228
+ });
229
+ });
230
+ });
231
+ }
232
+
233
+ /**
234
+ * @param {*[]} modules
235
+ * @param {string} pathname
236
+ * @param {boolean} [isEntry = false]
237
+ */
238
+ setRefMap(modules, pathname, isEntry = false) {
239
+ modules.forEach(({
240
+ id,
241
+ type,
242
+ value,
243
+ }) => {
244
+ assert(type === 'module', `File ${id} is not a tars module.`);
245
+ if (!this.refMap[id]) {
246
+ if (isEntry) {
247
+ this.entry.push(id);
248
+ }
249
+
250
+ this.refMap[id] = {};
251
+ this.refMap[id].pathname = pathname;
252
+ this.refMap[id].value = value;
253
+ } else {
254
+ this.refMap[id].value = this.refMap[id].value.concat(value);
255
+ }
256
+ });
257
+ }
258
+
259
+
260
+ /**
261
+ * @param {*[]} modules
262
+ * @param {string} pathname
263
+ * @param {boolean} [isEntry = false]
264
+ */
265
+ setRefMapRecuriveMerge(modules, pathname, isEntry = false) {
266
+ return modules.map(({ id, type, value }) => {
267
+ assert(type === 'module', `File ${id} is not a tars module.`);
268
+ if (!this.refMap[id]) {
269
+ if (isEntry) {
270
+ this.entry.push(id);
271
+ this.entryFilepath = pathname;
272
+ }
273
+
274
+ this.refMap[id] = {};
275
+ this.refMap[id].pathname = pathname;
276
+ this.refMap[id].value = value;
277
+ } else {
278
+ // 判断文件路径是否已经被使用。由于用户定义的协议的 module name 不跟随文件名
279
+ if (this.refFilePath.get(`${pathname}${id}`)) {
280
+ // console.error('文件已被加载>>>', this.refFilePath.get(`${pathname}${id}`), pathname);
281
+ // Object.keys(this.refMap).forEach(key => {
282
+ // console.error(`key>>>> ${key}`, this.refMap[key].value.length, pathname);
283
+ // })
284
+ return false;
285
+ }
286
+ this.refMap[id].value = this.refMap[id].value.concat(value);
287
+ }
288
+ // 记录加载的文件
289
+ this.refFilePath.set(`${pathname}${id}`, id);
290
+ return true;
291
+ }).filter(item => item).length > 0
292
+ }
293
+
294
+ /**
295
+ * @param {string} [pathname]
296
+ */
297
+ parseTars(pathname) {
298
+ pathname = pathname || this.pathname;
299
+ assert(pathname.length > 0, 'Need at least one .tars file to parse.');
300
+ let result;
301
+ try {
302
+ result = parser.parse(fs.readFileSync(pathname, 'utf8'));
303
+ } catch (e) {
304
+ console.error('\x1b[31m', `💥 Parse ${pathname} fail.`);
305
+ if (e.location && e.location.start && e.location.end) {
306
+ console.error('\x1b[31m', `💥 Error line: start at ${e.location.start.line} end at ${e.location.end.line}`);
307
+ console.error('\x1b[31m', `💥 Error column: start at ${e.location.start.column} end at ${e.location.end.column}`);
308
+ }
309
+ console.error('\x1b[31m', `💥 ${e.stack}`);
310
+ throw new Error('An error occured, compiling stoped');
311
+ }
312
+ const refs = /** @type {{type: string, target: string}[]} */ (result).filter(({ type }) => type === 'ref');
313
+ const modules = /** @type {{type: string, target: string}[]} */ (result).filter(({ type }) => type === 'module');
314
+ assert(modules.length > 0, 'A file must include at least one module.');
315
+
316
+ const parsed = {
317
+ refs,
318
+ modules,
319
+ pathname,
320
+ };
321
+ this.parsed[pathname] = parsed;
322
+ return parsed;
323
+ }
324
+
325
+ /**
326
+ * @param {{type: string, target: string}[]} refs
327
+ * @param {string} pathname
328
+ */
329
+ parseRefs(refs, pathname) {
330
+ pathname = pathname || this.pathname;
331
+ const { dir } = path.parse(pathname);
332
+ return refs.map(ref => {
333
+ let targetPath = path.resolve(dir, ref.target);
334
+ let result = this.parsed[targetPath];
335
+ if (!result) {
336
+ if (!fs.existsSync(targetPath)) {
337
+ const { base } = path.parse(targetPath);
338
+ const cwd = path.resolve(dir, '../../'); // 从上2级目录开始找
339
+ const filePaths = globby.sync(`**/${base}`, {
340
+ cwd,
341
+ });
342
+ if (filePaths.length) {
343
+ targetPath = path.resolve(cwd, filePaths[0]);
344
+ } else {
345
+ throw new Error(`找不到${pathname}依赖的${base}文件!`);
346
+ }
347
+ }
348
+ result = this.parseTars(targetPath);
349
+ }
350
+ return result;
351
+ });
352
+ }
353
+
354
+ /**
355
+ * 递归处理引用
356
+ * @param {{type: string, target: string}[]} refList
357
+ * @param {string} pathname
358
+ */
359
+ handleRefRecursivelyMerge(refList, pathname, init = false) {
360
+ // 入口文件只需要执行一次。不需要重复导入
361
+ if (this.entryFilepath == pathname && !init) {
362
+ return;
363
+ }
364
+ const parsedRefs = this.parseRefs(refList, pathname);
365
+ parsedRefs.forEach(({ refs, pathname: _pathname, modules }) => {
366
+ const hasNew = this.setRefMapRecuriveMerge(modules, _pathname);
367
+ // 判断是否有新增文件,有新增文件,需要递归查找引用文件
368
+ if (!hasNew) {
369
+ // console.error("Recursively >>> error", _pathname);
370
+ return;
371
+ }
372
+ this.handleRefRecursivelyMerge(refs, _pathname);
373
+ });
374
+ }
375
+ }
376
+
377
+ function buildParser() {
378
+ const content = fs.readFileSync(path.resolve(__dirname, PEG_SOURCE), 'utf8');
379
+ const source = content.toString();
380
+ return peg.generate(source);
381
+ }
382
+
383
+ module.exports = AST;
package/main.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ export interface Config {
2
+ withReturn?: boolean;
3
+ interface?: boolean;
4
+ gather?: boolean;
5
+ long?: 'string'|'number'|'bigint';
6
+ }
7
+
8
+ export declare function run(
9
+ entry: string,
10
+ type: 'ts' | 'es5',
11
+ dir?: string,
12
+ mode?: 'service' | 'client' | 'all',
13
+ config?: Config
14
+ ): Promise<Array<void>>;
15
+
16
+ export declare function process(
17
+ entry: string,
18
+ type: 'ts' | 'es5',
19
+ dir?: string,
20
+ mode?: 'service' | 'client' | 'all',
21
+ config?: Config
22
+ ): Promise<object>;
23
+
24
+ export interface AST {
25
+ entry: string[];
26
+ buildAST(): object;
27
+ }
28
+
29
+ export declare function ast(entry: string): AST;
package/main.js ADDED
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const Generator = require('./lib/generator/ts/generator');
6
+ const TsParser = require('./lib/parser/parser');
7
+
8
+ function runner(entry, type, dir, mode = 'service', config = {}) {
9
+ dir = dir || path.parse(entry).dir;
10
+
11
+ const parser = new TsParser(entry);
12
+ let ast
13
+ if (config.useModuleName === false) {
14
+ ast = parser.buildAST()
15
+ } else if (config.fixRecurAndMerge) {
16
+ ast = parser.buildASTRecursiveMerge()
17
+ } else {
18
+ ast = parser.buildFileAST()
19
+ }
20
+ return new Generator(ast, entry, {
21
+ mode,
22
+ type,
23
+ output: dir,
24
+ long: 'string',
25
+ ...config,
26
+ });
27
+ }
28
+
29
+ module.exports.ast = entry => new TsParser(entry).buildAST();
30
+ module.exports.memo = (...args) => runner(...args).memo();
31
+ module.exports.run = (...args) => runner(...args).build();
32
+ module.exports.process = (...args) => {
33
+ const generator = runner(...args);
34
+ return generator.build().then(() => generator.getTree());
35
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@cicctencent/tars2node-cli",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "main.js",
6
+ "types": "main.d.ts",
7
+ "access": "public",
8
+ "files": [
9
+ "lib",
10
+ "bin",
11
+ "main.d.ts",
12
+ "jce.pegjs"
13
+ ],
14
+ "scripts": {
15
+ "test": "mocha --recursive \"./test/**/*.test.js\" -w --watch-extensions js",
16
+ "test:lint": "eslint main.js bin/ lib/"
17
+ },
18
+ "author": "liquidliang",
19
+ "original_author": "kealxiao",
20
+ "license": "MIT",
21
+ "bin": {
22
+ "tars": "./bin/tars.js"
23
+ },
24
+ "dependencies": {
25
+ "chokidar": "^2.1.8",
26
+ "commander": "^2.20.3",
27
+ "fs-extra": "^8.1.0",
28
+ "globby": "^11.1.0",
29
+ "pegjs": "^0.10.0",
30
+ "typescript": "^3.9.10"
31
+ },
32
+ "devDependencies": {
33
+ "@tars/rpc": "latest",
34
+ "@tars/stream": "latest",
35
+ "@types/fs-extra": "^8.1.5",
36
+ "@types/node": "^10.17.60",
37
+ "@types/pegjs": "^0.10.6",
38
+ "chai": "^4.5.0",
39
+ "eslint": "^5.16.0",
40
+ "eslint-config-airbnb-base": "^13.2.0",
41
+ "eslint-plugin-import": "^2.32.0",
42
+ "mocha": "^5.2.0"
43
+ }
44
+ }