├── .npmignore ├── .gitignore ├── bin ├── t2t └── thrift2ts ├── .prettierrc ├── src ├── prettier-config.ts ├── error.ts ├── gen.ts ├── helpers.ts ├── gen-cli.ts ├── genTS.ts ├── genAst.ts └── genServiceClient.ts ├── test ├── index.js ├── shared.thrift ├── tutorial.thrift └── ast.json ├── lib ├── prettier-config.js ├── error.js ├── index.js ├── gen.js ├── helpers.js ├── gen-cli.js ├── genTS.js ├── genAst.js └── genServiceClient.js ├── templates ├── README.md ├── thrift-xhr-request.ts └── thrift-ws-request.ts ├── sample ├── webApi-request.ts └── rpc-request.ts ├── tsconfig.json ├── package.json ├── LICENSE ├── README-CN.md ├── README.md └── yarn.lock /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | sample/ 3 | templates/ 4 | test/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | *.log 4 | services 5 | test -------------------------------------------------------------------------------- /bin/t2t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/gen-cli'); 4 | -------------------------------------------------------------------------------- /bin/thrift2ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/gen-cli'); 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /src/prettier-config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tabWidth: 4, 3 | useTabs: false, 4 | singleQuote: false, 5 | bracketSpacing: true, 6 | parser: "typescript" 7 | }; 8 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var gen = require("../lib/genAst").default; 2 | var fs = require("fs"); 3 | 4 | var code = fs.readFileSync("./test/tutorial.thrift"); 5 | 6 | fs.writeFileSync("./test/ast.json", JSON.stringify(gen(code))); 7 | -------------------------------------------------------------------------------- /lib/prettier-config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.default = { 4 | tabWidth: 4, 5 | useTabs: false, 6 | singleQuote: false, 7 | bracketSpacing: true, 8 | parser: "typescript" 9 | }; 10 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | export class ThriftSyntaxError extends SyntaxError { 2 | public context: string; 3 | public line: number; 4 | 5 | constructor(message, context, line) { 6 | super(message); 7 | this.context = context; 8 | this.line = line; 9 | this.name = "THRIFT_SYNTAX_ERROR"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/gen.ts: -------------------------------------------------------------------------------- 1 | import genAST from "./genAst"; 2 | import genTS from "./genTS"; 3 | import prettierConfig from "./prettier-config"; 4 | const prettier = require("prettier"); 5 | 6 | export default (code: string, Request: string) => { 7 | const ts = genTS(genAST(code), Request); 8 | return prettier.format(ts, prettierConfig); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/error.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class ThriftSyntaxError extends SyntaxError { 4 | constructor(message, context, line) { 5 | super(message); 6 | this.context = context; 7 | this.line = line; 8 | this.name = "THRIFT_SYNTAX_ERROR"; 9 | } 10 | } 11 | exports.ThriftSyntaxError = ThriftSyntaxError; 12 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | These thrift rpc request templates are applied to a typical thrift RPC call, which using thrift data protocol and transport and could connect to a thrift RPC server. 4 | 5 | Two connection options could be supplied for your web applications: 6 | 7 | * WebSockert connection 8 | * XHR connection 9 | 10 | You could also modify these templates for a convenience. 11 | -------------------------------------------------------------------------------- /test/shared.thrift: -------------------------------------------------------------------------------- 1 | namespace cpp shared 2 | namespace d share // "shared" would collide with the eponymous D keyword. 3 | namespace dart shared 4 | namespace java shared 5 | namespace perl shared 6 | namespace php shared 7 | namespace haxe shared 8 | 9 | struct SharedStruct { 10 | 1: i32 key 11 | 2: string value 12 | } 13 | 14 | service SharedService { 15 | SharedStruct getStruct(1: i32 key) 16 | } 17 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | let genAST = require('./genAst'); 4 | let genTS = require('./genTS'); 5 | const path_1 = require("path"); 6 | const fs_1 = require("fs"); 7 | module.exports = (code, output, webAPIPath) => { 8 | let ts = genTS(genAST(code), webAPIPath); 9 | let file = path_1.default.resolve(output); 10 | fs_1.default.writeFileSync(file, ts); 11 | }; 12 | -------------------------------------------------------------------------------- /lib/gen.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const genAst_1 = require("./genAst"); 4 | const genTS_1 = require("./genTS"); 5 | const prettier_config_1 = require("./prettier-config"); 6 | const prettier = require("prettier"); 7 | exports.default = (code, Request) => { 8 | const ts = genTS_1.default(genAst_1.default(code), Request); 9 | return prettier.format(ts, prettier_config_1.default); 10 | }; 11 | -------------------------------------------------------------------------------- /sample/webApi-request.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A sample webApi request implementation 3 | */ 4 | 5 | import axios from "axios"; 6 | 7 | const apiBaseAddress = "http://test.com/api/"; 8 | 9 | const request = axios.create({ 10 | baseURL: apiBaseAddress, 11 | timeout: 1000, 12 | headers: { "Content-Type": "application/json;charset:utf-8" } 13 | }); 14 | 15 | export default function webApi(method, params) { 16 | return request.post(method.split(".").join("/"), params); 17 | } 18 | -------------------------------------------------------------------------------- /sample/rpc-request.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A sample RPC request implementation 3 | */ 4 | 5 | import axios from "axios"; 6 | 7 | const rpcAddress = "http://test.com/service/rpc"; 8 | 9 | const request = axios.create({ 10 | baseURL: rpcAddress, 11 | timeout: 1000, 12 | headers: { "Content-Type": "text/plain;charset:utf-8" } 13 | }); 14 | 15 | let _id = 0; 16 | export default function jsonRPC(method, params) { 17 | return request.post({ 18 | id: _id++, 19 | method, 20 | params 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.thrift2TsPath = (path, ext = false, isService = true) => { 4 | path = path.replace(/(.*)\.thrift$/i, isService ? "$1Service" : "$1Client"); 5 | if (!path.startsWith(".")) { 6 | path = "./" + path; 7 | } 8 | return ext ? path + ".ts" : path; 9 | }; 10 | exports.getThriftFileName = (path) => { 11 | let reg = /([\.\/]?)([^\/\.]+)\.thrift$/i; 12 | let match = path.match(reg); 13 | if (match.length >= 3) { 14 | return match[2]; 15 | } 16 | return ""; 17 | }; 18 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | // Helpers 2 | export const thrift2TsPath = ( 3 | path: string, 4 | ext: boolean = false, 5 | isService: boolean = true 6 | ): string => { 7 | path = path.replace(/(.*)\.thrift$/i, isService ? "$1Service" : "$1Client"); 8 | if (!path.startsWith(".")) { 9 | path = "./" + path; 10 | } 11 | return ext ? path + ".ts" : path; 12 | }; 13 | 14 | export const getThriftFileName = (path: string): string => { 15 | let reg = /([\.\/]?)([^\/\.]+)\.thrift$/i; 16 | let match = path.match(reg); 17 | if (match.length >= 3) { 18 | return match[2]; 19 | } 20 | return ""; 21 | }; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "allowSyntheticDefaultImports": true, 5 | "allowJs": true, 6 | "target": "es2015", 7 | "lib": [ 8 | "es2016" 9 | ], 10 | "module": "commonjs", 11 | "noImplicitAny": false, 12 | "removeComments": true, 13 | "emitDecoratorMetadata": true, 14 | "experimentalDecorators": true, 15 | "baseUrl": "./src", 16 | "strictNullChecks": false, 17 | "noUnusedLocals": true, 18 | "outDir": "./lib" 19 | }, 20 | "include": [ 21 | "./src/**/*.ts", 22 | "./src/*.d.ts" 23 | ], 24 | "exclude": [ 25 | "node_modules", 26 | "**/*.spec.ts?", 27 | "lib" 28 | ], 29 | "typeRoots": [ 30 | "node_modules/@types" 31 | ] 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thrift2ts", 3 | "version": "0.8.1", 4 | "description": "Generate thrift RPC service clients and type definition document, all in TypeScript", 5 | "main": "./lib/gen.js", 6 | "bin": { 7 | "thrift2ts": "./bin/thrift2ts", 8 | "t2t": "./bin/t2t" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "prepublishOnly": "npm run build" 13 | }, 14 | "repository": "git@github.com:thundernet8/thrift2ts.git", 15 | "author": "wuxueqian ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@types/node": "^7.0.29", 19 | "mkdirp": "^0.5.1", 20 | "prettier": "^1.5.2", 21 | "typescript": "^2.3.4", 22 | "yargs": "^8.0.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/tutorial.thrift: -------------------------------------------------------------------------------- 1 | include "shared.thrift" 2 | 3 | namespace cpp tutorial 4 | namespace d tutorial 5 | namespace dart tutorial 6 | namespace java tutorial 7 | namespace php tutorial 8 | namespace perl tutorial 9 | namespace haxe tutorial 10 | 11 | typedef i32 MyInteger 12 | 13 | const i32 INT32CONSTANT = 9853 14 | const map MAPCONSTANT = {'hello':'world', 'goodnight':'moon'} 15 | 16 | enum Operation { 17 | ADD = 1, 18 | SUBTRACT = 2, 19 | MULTIPLY = 3, 20 | DIVIDE = 4 21 | } 22 | 23 | struct Work { 24 | 1: i32 num1 = 0, 25 | 2: i32 num2, 26 | 3: Operation op, 27 | 4: optional string comment, 28 | } 29 | 30 | exception InvalidOperation { 31 | 1: i32 whatOp, 32 | 2: string why = 'abc' 33 | } 34 | 35 | service Calculator extends shared.SharedService { 36 | 37 | void ping(), 38 | 39 | i32 add(1:i32 num1, 2: optional i32 num2), 40 | 41 | i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), 42 | 43 | oneway void zip() 44 | 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 吴学谦 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /templates/thrift-xhr-request.ts: -------------------------------------------------------------------------------- 1 | import clients from "./clients"; 2 | 3 | import ThriftBrowser from "browser-thrift2"; 4 | const { 5 | TJSONProtocol, 6 | TBufferedTransport, 7 | createWSConnection, 8 | createXHRConnection, 9 | createClient 10 | } = ThriftBrowser; 11 | 12 | /** 13 | * Please replace the host, port, and other options for connecting RPC server 14 | */ 15 | let conn = createXHRConnection("localhost", 9090, { 16 | path: "/thrift/rpc" 17 | }); 18 | 19 | conn.on("error", err => { 20 | console.dir(err); 21 | }); 22 | 23 | export default function thriftRPC(method, params): Promise { 24 | let splits: string[] = method.split(".").map(x => x !== ""); 25 | if (splits.length < 2) { 26 | throw new Error( 27 | "Invalid RPC method, the correct format is `ServiceName.MethodName`" 28 | ); 29 | } 30 | let service = splits[0]; 31 | let func = splits[1]; 32 | 33 | let client = createClient(clients[service], conn); 34 | return new Promise((resolve, reject) => { 35 | try { 36 | client[func]( 37 | ...Object.keys(params).map(key => params[key]), 38 | (err, result) => { 39 | if (err) { 40 | reject(err); 41 | } else { 42 | resolve(result); 43 | } 44 | } 45 | ); 46 | } catch (e) { 47 | reject(e); 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /templates/thrift-ws-request.ts: -------------------------------------------------------------------------------- 1 | import clients from "./clients"; 2 | 3 | import ThriftBrowser from "browser-thrift2"; 4 | const { 5 | TJSONProtocol, 6 | TBufferedTransport, 7 | createWSConnection, 8 | createXHRConnection, 9 | createClient 10 | } = ThriftBrowser; 11 | 12 | /** 13 | * Please replace the host, port, and other options for connecting RPC server 14 | */ 15 | let conn = createWSConnection("localhost", 9090, { 16 | path: "/thrift/rpc" 17 | }); 18 | conn.open(); 19 | 20 | conn.on("open", err => { 21 | console.log("WebSockert connected to: ", conn.uri()); 22 | }); 23 | 24 | conn.on("error", err => { 25 | console.dir(err); 26 | }); 27 | 28 | export default function thriftRPC(method, params): Promise { 29 | let splits: string[] = method.split(".").map(x => x !== ""); 30 | if (splits.length < 2) { 31 | throw new Error( 32 | "Invalid RPC method, the correct format is `ServiceName.MethodName`" 33 | ); 34 | } 35 | let service = splits[0]; 36 | let func = splits[1]; 37 | 38 | let client = createClient(clients[service], conn); 39 | return new Promise((resolve, reject) => { 40 | try { 41 | client[func]( 42 | ...Object.keys(params).map(key => params[key]), 43 | (err, result) => { 44 | if (err) { 45 | reject(err); 46 | } else { 47 | resolve(result); 48 | } 49 | } 50 | ); 51 | } catch (e) { 52 | reject(e); 53 | } 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /lib/gen-cli.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const path_1 = require("path"); 4 | const fs_1 = require("fs"); 5 | const genAst_1 = require("./genAst"); 6 | const genTS_1 = require("./genTS"); 7 | const genServiceClient_1 = require("./genServiceClient"); 8 | const mkdirp = require("mkdirp"); 9 | const yargs = require("yargs"); 10 | const argv = yargs 11 | .version(function () { 12 | return require("../package.json").version; 13 | }) 14 | .alias("v", "version") 15 | .alias("i", "input") 16 | .alias("o", "output") 17 | .alias("r", "request") 18 | .alias("c", "clients") 19 | .alias("h", "help") 20 | .usage("Usage: $0 [options]") 21 | .example("$0 -i ./member.thrift -o ./services -r ./request", "") 22 | .demandOption(["i"]) 23 | .default("o", "./services") 24 | .default("r", "./request") 25 | .default("c", false) 26 | .describe("i", "Input thrift file path") 27 | .describe("o", "Ouput typescript file folder") 28 | .describe("c", "Whether generate thrift service clients") 29 | .describe("r", "webApi or JSON-RPC request implementation file path, will be imported in generated typescript file") 30 | .epilog("Copyright " + new Date().getFullYear()) 31 | .help().argv; 32 | const resolveInput = (input, basePath) => { 33 | if (!input.startsWith(".")) { 34 | input = "./" + input; 35 | } 36 | const extName = path_1.extname(input); 37 | let paths = [basePath, input + (extName ? "" : ".thrift")]; 38 | return path_1.resolve(...paths.filter(x => !!x)); 39 | }; 40 | const resolveOutput = (input, primary = false, isClients = false) => { 41 | const extName = path_1.extname(input); 42 | const baseName = path_1.basename(input, extName).replace(".", ""); 43 | const outputFolder = isClients 44 | ? path_1.resolve(argv.o, "clients") 45 | : path_1.resolve(argv.o); 46 | if (path_1.extname(outputFolder).replace(".", "").toLowerCase() === "ts") { 47 | if (primary) { 48 | return path_1.resolve(outputFolder); 49 | } 50 | else { 51 | return path_1.resolve(path_1.dirname(outputFolder), baseName + (isClients ? "Client.ts" : "Service.ts")); 52 | } 53 | } 54 | else { 55 | return path_1.resolve(outputFolder, baseName + (isClients ? "Client.ts" : "Service.ts")); 56 | } 57 | }; 58 | try { 59 | let clientsIndexPath = null; 60 | let clientsDict = {}; 61 | let genClientsIndex = () => { 62 | let tsCode = genServiceClient_1.clientsIndex(clientsDict); 63 | fs_1.writeFileSync(clientsIndexPath, tsCode); 64 | }; 65 | let gen = () => { 66 | let handledFiles = []; 67 | let primary = true; 68 | const recursiveGen = (input, basePath = null, isClients = false) => { 69 | let inputFile = resolveInput(input, basePath); 70 | if (!basePath) { 71 | basePath = path_1.dirname(inputFile); 72 | } 73 | if (!fs_1.existsSync(inputFile)) { 74 | throw new Error(`The specified file <${input}> is not exists`); 75 | } 76 | let outputFile = resolveOutput(input, primary, isClients); 77 | primary = false; 78 | if (handledFiles.indexOf(input) > -1) { 79 | return; 80 | } 81 | handledFiles.push(input); 82 | mkdirp(path_1.resolve(path_1.dirname(outputFile))); 83 | const ast = genAst_1.default(fs_1.readFileSync(inputFile).toString()); 84 | const tsCode = isClients 85 | ? genServiceClient_1.default(ast) 86 | : genTS_1.default(ast, argv.r); 87 | fs_1.writeFileSync(outputFile, tsCode); 88 | if (isClients) { 89 | let fileBasename = path_1.basename(outputFile, path_1.extname(outputFile)); 90 | clientsIndexPath = path_1.resolve(path_1.dirname(outputFile), "index.ts"); 91 | if (clientsDict[fileBasename] === undefined) { 92 | clientsDict[fileBasename] = Object.keys(ast["service"]); 93 | } 94 | else { 95 | clientsDict[fileBasename] = clientsDict[fileBasename].concat(Object.keys(ast["service"])); 96 | } 97 | } 98 | if (ast["include"]) { 99 | let includes = Object.keys(ast["include"]).map(key => ast["include"][key]["value"]); 100 | includes.forEach(include => { 101 | recursiveGen(include, basePath, isClients); 102 | }); 103 | } 104 | }; 105 | return recursiveGen; 106 | }; 107 | gen()(argv.i); 108 | if (argv.c) { 109 | gen()(argv.i, null, true); 110 | genClientsIndex(); 111 | } 112 | } 113 | catch (err) { 114 | throw err; 115 | } 116 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | [English Intro](./README.md) 2 | 3 | ## Thrift2TS 4 | 5 | Thrift作为服务间RPC通信的流行方案,其本身是一个类型定义语言(IDL),由于其语言无关性,即可以一次编写服务数据类型定义和方法接口文档,然后通过工具生成指定平台语言的可调用的RPC服务代码。 6 | 7 | 本工具将 Thrift (IDL) 文件解析并生成TypeScript文件,可以用作前端服务请求方法相关代码的类型文档,这样在编写调用RPC方法代码时可利用TypeScript强大的类型支持提高开发效率。 8 | 9 | Thrift除了定义IDL,还有一套自己的数据传输方法和协议,以便在RPC客户端和服务端之间通信,因此本工具也添加了RPC服务客户端代码的生成支持,利用所有生成的代码文件,可以完成一整套的完全基于Thrift的RPC调用。 10 | 11 | Thrift官方提供了JS的生成工具,那么本工具的存在主要是为了解决以下问题: 12 | 13 | * 官方生成的JS文件,适用于浏览器端的使用了大量全局变量,没有模块导出,并不适合我们常规模块化开发时引入 14 | 15 | * 官方生成的Node JS,适用于Node客户端,由于大量使用了浏览器端不支持的net/http/Buffer模块,无法直接在浏览器端使用 16 | 17 | * 官方生成的JS文件,没有Call sequence支持,如果服务端响应时间不一,可能造成回调顺序错误,一个解决办法就是每次的RPC调用都重生成Service Client和Connection实例,但是我更希望特别是WebSocket的connection实例能够重用,即在一个WebSocket连接上发送多个RPC调用,避免频繁的TCP连接打开和关闭 18 | 19 | * 官方生成的JS文件,因为没有类型支持,对开发时静态检查和提示不便,因此本工具全部生成TypeScript代码 20 | 21 | [关于Thrift](https://thrift.apache.org/docs/idl) 22 | 23 | [关于TypeScript](https://www.typescriptlang.org/docs/home.html) 24 | 25 | ## 示例 26 | 27 | [Thrift2TS Demo Project](https://github.com/thundernet8/Thrift2TS-Flow-Demo) 28 | 29 | ## 安装 30 | 31 | npm 32 | ``` 33 | npm install thrift2ts -g 34 | ``` 35 | 36 | yarn 37 | ``` 38 | yarn global add thrift2ts 39 | ``` 40 | 41 | ## 使用 42 | 43 | ### CLI 44 | ``` 45 | t2t -i [thrift文件路径] -o [typescript文件输出目录] -r [request方法导入路径] -c 46 | ``` 47 | 48 | sample 49 | ``` 50 | t2t -i ./common.thrift -o ./services -r ./request -c 51 | ``` 52 | 53 | ### 代码调用 54 | ``` 55 | var thrift2ts = require('thrift2ts').default; 56 | var thriftCode = 'XXX'; 57 | 58 | var tsCode = thrift2ts(thriftCode, './request') 59 | ``` 60 | 61 | ## 示例 62 | 63 | Thrift 64 | 65 | ``` 66 | namespace java com.company.javabusz 67 | namespace go com.company.gobusz 68 | 69 | include "./Common.thrift" 70 | 71 | enum EmployeeType { 72 | Junior = 0, 73 | Senior = 1, 74 | Manager, 75 | Director = 0xa 76 | } 77 | 78 | struct Employee { 79 | 1:required string name; 80 | 2:optional i32 age; 81 | 3:required map tasks; 82 | } 83 | 84 | exception NetworkException { 85 | 1:required i32 code; 86 | 2:required string message; 87 | 3:optional string url; 88 | } 89 | 90 | const i32 year = 2017 91 | 92 | const list languages = ['Java', 'Go', 'JavaScript'] 93 | 94 | const map lanAges = {'Java': 20, 'Go': 8, 'JavaScript': 16} 95 | 96 | const bool happy = true 97 | 98 | // This is a map definition 99 | typedef map EmployeesCatlog // a tail comment 100 | 101 | service EmployeeOperator { 102 | list QueryEmployee(1:i32 age) 103 | } 104 | 105 | service EmployeeSalaryOperator extends EmployeeOperator { 106 | bool OperateEmployeeSalaryByType(1:EmployeeType type, 2:i64 amount, 2:string note); 107 | } 108 | ``` 109 | Convert to TypeScript 110 | 111 | ``` 112 | /** 113 | * Auto generated by Thrift2Ts. 114 | * 115 | * Mon Jun 19 2017 22:42:06 GMT+0800 (CST) 116 | */ 117 | 118 | import Request from "./request"; 119 | 120 | import * as Common from "./CommonService"; 121 | 122 | export const year: number = 2017; 123 | 124 | export const languages: string[] = ["Java", "Go", "JavaScript"]; 125 | 126 | export const lanAges: {[key: string]: number} = {"Java": 20, "Go": 8, "JavaScript": 16}; 127 | 128 | export const happy: boolean = true; 129 | 130 | export enum EmployeeType { 131 | Junior = 0, 132 | Senior = 1, 133 | Manager = 2, 134 | Director = 10 135 | } 136 | 137 | export interface NetworkException { 138 | code: number; 139 | message: string; 140 | url?: string; 141 | } 142 | 143 | export interface Employee { 144 | name: string; 145 | age?: number; 146 | tasks: {[key: string]: number}; 147 | } 148 | 149 | export function QueryEmployee(age: number): Promise { 150 | return Request("EmployeeOperator.QueryEmployee", { age }) 151 | } 152 | 153 | export function OperateEmployeeSalaryByType(type: EmployeeType, amount: number, note: string): Promise { 154 | return Request("EmployeeSalaryOperator.OperateEmployeeSalaryByType", { type, amount, note }) 155 | } 156 | 157 | 158 | export default { 159 | QueryEmployee, 160 | OperateEmployeeSalaryByType 161 | } 162 | 163 | ``` 164 | 165 | ## 问题 166 | 167 | ### 要导入的Request是什么 168 | 169 | 本工具生成的文件包括一份RPC服务以及相关参数类型定义文档,这里会将Thrift的service下的所有方法转化为返回值为Promise的可直接引用的远程请求方法。为了灵活起见,代码不会实现如何去请求一个远端服务器。不论你用AJAX, Fetch抑或WebSocket网络请求方式,也不论你是用axios或者jQuery等哪一种网络请求库,或者说直接用本工具生成的Thrift Service Client搭配Thrift的数据传输协议,你直接提供实现了这些内容的模块的导入路径即可。 170 | 171 | 本代码中也提供了一些Request实现的例子 172 | 173 | ##### 不使用Thrift数据传输协议和Thrift RPC Server 174 | 175 | * [HTTP-RPC-Request](./sample/rpc-request.ts) 176 | 177 | * [HTTP-API-Request](./sample/webApi-request.ts) 178 | 179 | ##### 完整使用Thrift数据协议连接Thrift RPC服务端 180 | 181 | * [Thrift-RPC-Request WebSocket connection](./templates/thrift-ws-request.ts) 182 | 183 | * [Thrift-RPC-Request XHR connection](./templates/thrift-xhr-request.ts) 184 | 185 | 186 | ### 必须 187 | 188 | 当要连接Thrift RPC服务时你必须导入 [Browser Thrift](https://www.npmjs.com/package/browser-thrift2) 包, 它定义了 Thrift 数据传输协议。 189 | 190 | 这个包里面也有一份完整的使用本工具生成代码,并与Thrift RPC服务端通信的完整演示。 191 | -------------------------------------------------------------------------------- /src/gen-cli.ts: -------------------------------------------------------------------------------- 1 | import { resolve, dirname, basename, extname } from "path"; 2 | import { readFileSync, writeFileSync, existsSync } from "fs"; 3 | import genAST from "./genAst"; 4 | import genTS from "./genTS"; 5 | import genServiceClient, { clientsIndex } from "./genServiceClient"; 6 | import * as mkdirp from "mkdirp"; 7 | import * as yargs from "yargs"; 8 | 9 | const argv = yargs 10 | .version(function() { 11 | return require("../package.json").version; 12 | }) 13 | .alias("v", "version") 14 | .alias("i", "input") 15 | .alias("o", "output") 16 | .alias("r", "request") 17 | .alias("c", "clients") 18 | .alias("h", "help") 19 | .usage("Usage: $0 [options]") 20 | .example("$0 -i ./member.thrift -o ./services -r ./request", "") 21 | .demandOption(["i"]) 22 | .default("o", "./services") 23 | .default("r", "./request") 24 | .default("c", false) 25 | .describe("i", "Input thrift file path") 26 | .describe("o", "Ouput typescript file folder") 27 | .describe("c", "Whether generate thrift service clients") 28 | .describe( 29 | "r", 30 | "webApi or JSON-RPC request implementation file path, will be imported in generated typescript file" 31 | ) 32 | .epilog("Copyright " + new Date().getFullYear()) 33 | .help().argv; 34 | 35 | // const extName = extname(argv.i); 36 | // const inputFile = resolve(argv.i + (extName ? '' : '.thrift')); 37 | // const baseName = basename(argv.i, extName).replace('.', ''); 38 | // const outputFile = extname(argv.o).replace('.', '').toLowerCase() === 'ts' ? resolve(argv.o) : resolve(argv.o, baseName + 'Service.ts'); 39 | 40 | const resolveInput = (input: string, basePath?: string) => { 41 | if (!input.startsWith(".")) { 42 | input = "./" + input; // thrift can include without relative path chars, e.g include "share.thrift" 43 | } 44 | const extName = extname(input); 45 | let paths = [basePath, input + (extName ? "" : ".thrift")]; 46 | return resolve(...paths.filter(x => !!x)); 47 | }; 48 | 49 | const resolveOutput = (input, primary = false, isClients = false) => { 50 | const extName = extname(input); 51 | const baseName = basename(input, extName).replace(".", ""); 52 | const outputFolder = isClients 53 | ? resolve(argv.o, "clients") 54 | : resolve(argv.o); 55 | if (extname(outputFolder).replace(".", "").toLowerCase() === "ts") { 56 | if (primary) { 57 | return resolve(outputFolder); 58 | } else { 59 | return resolve( 60 | dirname(outputFolder), 61 | baseName + (isClients ? "Client.ts" : "Service.ts") 62 | ); 63 | } 64 | } else { 65 | return resolve( 66 | outputFolder, 67 | baseName + (isClients ? "Client.ts" : "Service.ts") 68 | ); 69 | } 70 | }; 71 | 72 | try { 73 | let clientsIndexPath = null; 74 | let clientsDict = {}; 75 | 76 | let genClientsIndex = () => { 77 | let tsCode = clientsIndex(clientsDict); 78 | writeFileSync(clientsIndexPath, tsCode); 79 | }; 80 | 81 | let gen = () => { 82 | let handledFiles = []; 83 | let primary = true; 84 | const recursiveGen = (input, basePath = null, isClients = false) => { 85 | let inputFile = resolveInput(input, basePath); 86 | if (!basePath) { 87 | basePath = dirname(inputFile); 88 | } 89 | if (!existsSync(inputFile)) { 90 | throw new Error(`The specified file <${input}> is not exists`); 91 | } 92 | 93 | let outputFile = resolveOutput(input, primary, isClients); 94 | primary = false; 95 | if (handledFiles.indexOf(input) > -1) { 96 | return; 97 | } 98 | handledFiles.push(input); 99 | mkdirp(resolve(dirname(outputFile))); 100 | 101 | const ast = genAST(readFileSync(inputFile).toString()); 102 | const tsCode = isClients 103 | ? genServiceClient(ast) 104 | : genTS(ast, argv.r); 105 | writeFileSync(outputFile, tsCode); 106 | 107 | // record service clients and their file name in dict 108 | if (isClients) { 109 | let fileBasename = basename(outputFile, extname(outputFile)); 110 | clientsIndexPath = resolve(dirname(outputFile), "index.ts"); 111 | if (clientsDict[fileBasename] === undefined) { 112 | clientsDict[fileBasename] = Object.keys(ast["service"]); 113 | } else { 114 | clientsDict[fileBasename] = clientsDict[ 115 | fileBasename 116 | ].concat(Object.keys(ast["service"])); 117 | } 118 | } 119 | 120 | if (ast["include"]) { 121 | let includes: object[] = Object.keys(ast["include"]).map( 122 | key => ast["include"][key]["value"] 123 | ); 124 | includes.forEach(include => { 125 | recursiveGen(include, basePath, isClients); 126 | }); 127 | } 128 | }; 129 | return recursiveGen; 130 | }; 131 | gen()(argv.i); 132 | if (argv.c) { 133 | gen()(argv.i, null, true); 134 | genClientsIndex(); 135 | } 136 | } catch (err) { 137 | throw err; 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文说明](./README-CN.md) 2 | 3 | ## Thrift2TS 4 | 5 | Parse Thrift (IDL) to TypeScript, which could be used as typed interface docs for FrontEnd. 6 | 7 | Also as an option, Thrift service clients could be generated. With these clients, a complete RPC call with Thrift data transport and protocol could be done. 8 | 9 | This make up some issues on thrift officially generated RPC service clients for browser javascript app: 10 | 11 | * add sequence support for RPC client so that it can reuse the Websocket connection without incorrect response callback order 12 | 13 | * moduled script file, easy to import and package, rather than many global variables in official generated client for browser 14 | 15 | * all generated TypeScript files introduce types support and result in more convenient development of your app 16 | 17 | [Thrift Doc](https://thrift.apache.org/docs/idl) 18 | 19 | [TypeScript Doc](https://www.typescriptlang.org/docs/home.html) 20 | 21 | ## Demo 22 | 23 | [Thrift2TS Demo Project](https://github.com/thundernet8/Thrift2TS-Flow-Demo) 24 | 25 | ## Install 26 | 27 | npm 28 | ``` TypeScript 29 | npm install thrift2ts -g 30 | ``` 31 | 32 | yarn 33 | ``` TypeScript 34 | yarn global add thrift2ts 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### CLI 40 | ``` TypeScript 41 | t2t -i [thrift file path] -o [typescript file output folder] -r [request method import path] -c 42 | ``` 43 | 44 | sample 45 | ``` TypeScript 46 | t2t -i ./common.thrift -o ./services -r ./request -c 47 | ``` 48 | 49 | ### normal package 50 | ``` TypeScript 51 | var thrift2ts = require('thrift2ts').default; 52 | var thriftCode = 'XXX'; 53 | 54 | var tsCode = thrift2ts(thriftCode, './request') 55 | ``` 56 | 57 | ## Example 58 | 59 | Thrift 60 | 61 | ``` TypeScript 62 | namespace java com.company.javabusz 63 | namespace go com.company.gobusz 64 | 65 | include "./Common.thrift" 66 | 67 | enum EmployeeType { 68 | Junior = 0, 69 | Senior = 1, 70 | Manager, 71 | Director = 0xa 72 | } 73 | 74 | struct Employee { 75 | 1:required string name; 76 | 2:optional i32 age; 77 | 3:required map tasks; 78 | } 79 | 80 | exception NetworkException { 81 | 1:required i32 code; 82 | 2:required string message; 83 | 3:optional string url; 84 | } 85 | 86 | const i32 year = 2017 87 | 88 | const list languages = ['Java', 'Go', 'JavaScript'] 89 | 90 | const map lanAges = {'Java': 20, 'Go': 8, 'JavaScript': 16} 91 | 92 | const bool happy = true 93 | 94 | // This is a map definition 95 | typedef map EmployeesCatlog // a tail comment 96 | 97 | service EmployeeOperator { 98 | list QueryEmployee(1:i32 age) 99 | } 100 | 101 | service EmployeeSalaryOperator extends EmployeeOperator { 102 | bool OperateEmployeeSalaryByType(1:EmployeeType type, 2:i64 amount, 2:string note); 103 | } 104 | ``` 105 | Convert to TypeScript 106 | 107 | ``` TypeScript 108 | /** 109 | * Auto generated by Thrift2Ts. 110 | * 111 | * Mon Jun 19 2017 22:42:06 GMT+0800 (CST) 112 | */ 113 | 114 | import Request from "./request"; 115 | 116 | import * as Common from "./CommonService"; 117 | 118 | export const year: number = 2017; 119 | 120 | export const languages: string[] = ["Java", "Go", "JavaScript"]; 121 | 122 | export const lanAges: {[key: string]: number} = {"Java": 20, "Go": 8, "JavaScript": 16}; 123 | 124 | export const happy: boolean = true; 125 | 126 | export enum EmployeeType { 127 | Junior = 0, 128 | Senior = 1, 129 | Manager = 2, 130 | Director = 10 131 | } 132 | 133 | export interface NetworkException { 134 | code: number; 135 | message: string; 136 | url?: string; 137 | } 138 | 139 | export interface Employee { 140 | name: string; 141 | age?: number; 142 | tasks: {[key: string]: number}; 143 | } 144 | 145 | export function QueryEmployee(age: number): Promise { 146 | return Request("EmployeeOperator.QueryEmployee", { age }) 147 | } 148 | 149 | export function OperateEmployeeSalaryByType(type: EmployeeType, amount: number, note: string): Promise { 150 | return Request("EmployeeSalaryOperator.OperateEmployeeSalaryByType", { type, amount, note }) 151 | } 152 | 153 | 154 | export default { 155 | QueryEmployee, 156 | OperateEmployeeSalaryByType 157 | } 158 | 159 | ``` 160 | 161 | ## Issues 162 | 163 | ### Why import Request 164 | 165 | Thrift service will exploded into functions which are used for RPC-liked call or webApi request, a common request instance is required, and accept method string, POST data as parameters. We donnot concern about which request approach(AJAX, Fetch) or libraries(axios, jQuery, fetch-io) you'd like to use, but you must provide the request implementation file path for importing. 166 | 167 | Please find the examples in sample folder. 168 | 169 | ##### Using normal text transport without thrift rpc server 170 | 171 | * [HTTP-RPC-Request](./sample/rpc-request.ts) 172 | 173 | * [HTTP-API-Request](./sample/webApi-request.ts) 174 | 175 | ##### Using thrift data transport protocol with thrift rpc server 176 | 177 | * [Thrift-RPC-Request WebSocket connection](./templates/thrift-ws-request.ts) 178 | 179 | * [Thrift-RPC-Request XHR connection](./templates/thrift-xhr-request.ts) 180 | 181 | 182 | ### Requirements 183 | 184 | You need import [Browser Thrift](https://www.npmjs.com/package/browser-thrift2) package when trying to communicate with a Thrift RPC server, which defines the thrift data transport protocol. 185 | 186 | Also you could find a demo in this package's introduction. 187 | -------------------------------------------------------------------------------- /lib/genTS.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const helpers_1 = require("./helpers"); 4 | const prettier_config_1 = require("./prettier-config"); 5 | const prettier = require("prettier"); 6 | const NEW_LINE = "\r\n"; 7 | const NEW_LINE_2 = "\r\n\r\n"; 8 | const header = `/**${NEW_LINE} * Auto generated by Thrift2Ts.${NEW_LINE} *${NEW_LINE} * ${new Date().toString()}${NEW_LINE} */${NEW_LINE_2}`; 9 | exports.default = (ast, Request = "./request") => { 10 | let code = ""; 11 | code += header; 12 | const defaultExports = []; 13 | const simplifyType = (type) => { 14 | if (typeof type === "string") { 15 | return type; 16 | } 17 | switch (type.name.toLowerCase()) { 18 | case "map": 19 | case "list": 20 | case "set": 21 | return type; 22 | default: 23 | return type.name.toString(); 24 | } 25 | }; 26 | const valueTypeTransformer = (type) => { 27 | type = simplifyType(type); 28 | if (typeof type === "string") { 29 | switch (type) { 30 | case "i16": 31 | case "i32": 32 | case "i64": 33 | case "double": 34 | return "number"; 35 | case "bool": 36 | return "boolean"; 37 | default: 38 | return type; 39 | } 40 | } 41 | switch (type["name"]) { 42 | case "map": 43 | return `{[key: ${type["keyType"]}]: ${valueTypeTransformer(type["valueType"])}}`; 44 | case "list": 45 | case "set": 46 | return `${valueTypeTransformer(type["valueType"])}[]`; 47 | } 48 | throw new Error(`Unexpected value type: ${JSON.stringify(type)}`); 49 | }; 50 | const valueTransformer = (value, isMap = false) => { 51 | if (typeof value === "string") { 52 | return `\"${value}\"`; 53 | } 54 | if (["number", "boolean"].indexOf(typeof value) > -1) { 55 | return value.toString(); 56 | } 57 | if (value instanceof Array) { 58 | if (isMap) { 59 | return `{${value.map(v => valueTransformer(v)).join(", ")}}`; 60 | } 61 | return `[${value.map(v => valueTransformer(v)).join(", ")}]`; 62 | } 63 | if (typeof value === "object" && 64 | value["key"] !== undefined && 65 | value["value"] !== undefined) { 66 | return `"${value["key"]}": ${valueTransformer(value["value"])}`; 67 | } 68 | throw new Error(`Unexpected value: ${JSON.stringify(value)}`); 69 | }; 70 | const includesHandler = (includes) => { 71 | let imports = ""; 72 | Object.keys(includes).map(key => includes[key]).forEach(include => { 73 | imports += `${NEW_LINE}import * as ${helpers_1.getThriftFileName(include.value)} from "${helpers_1.thrift2TsPath(include.value)}";${NEW_LINE}`; 74 | }); 75 | return imports; 76 | }; 77 | const constsHandler = (consts) => { 78 | let newConsts = ""; 79 | Object.keys(consts).forEach(key => { 80 | newConsts += `${NEW_LINE}export const ${key}: ${valueTypeTransformer(consts[key]["type"])} = ${valueTransformer(consts[key]["value"], typeof consts[key]["type"] === "object" && 81 | consts[key]["type"]["name"] === "map")}; ${NEW_LINE}`; 82 | }); 83 | return newConsts; 84 | }; 85 | const typedefsHandler = (typedefs) => { 86 | let newTypedefs = ""; 87 | Object.keys(typedefs).forEach(key => { 88 | newTypedefs += `${NEW_LINE}export type ${key} = ${valueTypeTransformer(typedefs[key]["type"])}; ${NEW_LINE}`; 89 | }); 90 | return newTypedefs; 91 | }; 92 | const enumsHandler = (enums) => { 93 | let newEnums = ""; 94 | Object.keys(enums).forEach(key => { 95 | newEnums += enumHandler(key, enums[key]["items"]); 96 | }); 97 | return newEnums; 98 | }; 99 | const enumHandler = (name, items) => { 100 | let code = `${NEW_LINE}export enum ${name} {`; 101 | items.forEach((item, index) => { 102 | if (item["value"] === undefined) { 103 | item["value"] = `"${item["name"]}"`; 104 | } 105 | code += `${NEW_LINE}${item["name"]} = ${item["value"]}`; 106 | if (index < items.length - 1) { 107 | code += ","; 108 | } 109 | }); 110 | code += `${NEW_LINE}}${NEW_LINE}`; 111 | return code; 112 | }; 113 | const structsLikeHandler = (values) => { 114 | let interfaces = ""; 115 | Object.keys(values).forEach(key => { 116 | interfaces += structLikeHandler(key, values[key]); 117 | }); 118 | return interfaces; 119 | }; 120 | const structLikeHandler = (name, items) => { 121 | let code = `${NEW_LINE}export interface ${name} {`; 122 | items.forEach((item, index) => { 123 | code += `${NEW_LINE}${item["name"]}`; 124 | if (item["option"] === "optional") { 125 | code += "?"; 126 | } 127 | code += `: ${valueTypeTransformer(item["type"])};`; 128 | }); 129 | code += `${NEW_LINE}}${NEW_LINE}`; 130 | return code; 131 | }; 132 | const servicesHandler = (services) => { 133 | let code = ""; 134 | Object.keys(services).forEach(key => { 135 | code += serviceHandler(key, services[key]); 136 | }); 137 | return code; 138 | }; 139 | const serviceHandler = (name, service) => { 140 | let code = ""; 141 | let functions = service["functions"]; 142 | Object.keys(functions).forEach(key => { 143 | code += serviceFunctionHandler(name, functions[key]); 144 | }); 145 | return code; 146 | }; 147 | const serviceFunctionHandler = (name, serviceFunc) => { 148 | let code = ""; 149 | const method = `${name}.${serviceFunc["name"]}`; 150 | const returnType = valueTypeTransformer(serviceFunc["type"]); 151 | let args = serviceFunc["args"]; 152 | let argNames = []; 153 | let argNameAndTypes = []; 154 | args.forEach(arg => { 155 | argNames.push(arg["name"]); 156 | argNameAndTypes.push(`${arg["name"]}${arg["option"] 157 | ? "?" 158 | : ""}: ${valueTypeTransformer(arg["type"])}`); 159 | }); 160 | code += `${NEW_LINE}export function ${serviceFunc["name"]}(${argNameAndTypes.join(", ")}): Promise<${returnType}> {${NEW_LINE}`; 161 | code += ` return Request<${returnType}>("${method}", { ${argNames.join(", ")} })`; 162 | code += `${NEW_LINE}}${NEW_LINE}`; 163 | defaultExports.push(serviceFunc["name"]); 164 | return code; 165 | }; 166 | const defaultExportsHandler = () => { 167 | let code = `${NEW_LINE_2}export default {${NEW_LINE}`; 168 | defaultExports.forEach((v, i) => { 169 | code += `${v}`; 170 | if (i < defaultExports.length - 1) { 171 | code += ","; 172 | } 173 | code += `${NEW_LINE}`; 174 | }); 175 | code += `}${NEW_LINE}`; 176 | return code; 177 | }; 178 | if (Request && ast.service) { 179 | code += `import Request from "${Request}";${NEW_LINE}`; 180 | } 181 | if (ast.include) { 182 | code += includesHandler(ast.include); 183 | } 184 | if (ast.const) { 185 | code += constsHandler(ast.const); 186 | } 187 | if (ast.typedef) { 188 | code += typedefsHandler(ast.typedef); 189 | } 190 | if (ast.enum) { 191 | code += enumsHandler(ast.enum); 192 | } 193 | if (ast.exception) { 194 | code += structsLikeHandler(ast.exception); 195 | } 196 | if (ast.struct) { 197 | code += structsLikeHandler(ast.struct); 198 | } 199 | if (ast.union) { 200 | code += structsLikeHandler(ast.union); 201 | } 202 | if (Request && ast.service) { 203 | code += servicesHandler(ast.service); 204 | } 205 | if (ast.service) { 206 | code += defaultExportsHandler(); 207 | } 208 | return prettier.format(code, prettier_config_1.default); 209 | }; 210 | -------------------------------------------------------------------------------- /test/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": { 3 | "shared": { 4 | "type": { 5 | "name": "include" 6 | }, 7 | "value": "shared.thrift", 8 | "headComment": "", 9 | "tailComment": "" 10 | } 11 | }, 12 | "namespace": { 13 | "cpp": { 14 | "type": { 15 | "name": "namespace" 16 | }, 17 | "value": "tutorial", 18 | "headComment": "", 19 | "tailComment": "" 20 | }, 21 | "d": { 22 | "type": { 23 | "name": "namespace" 24 | }, 25 | "value": "tutorial", 26 | "headComment": "", 27 | "tailComment": "" 28 | }, 29 | "dart": { 30 | "type": { 31 | "name": "namespace" 32 | }, 33 | "value": "tutorial", 34 | "headComment": "", 35 | "tailComment": "" 36 | }, 37 | "java": { 38 | "type": { 39 | "name": "namespace" 40 | }, 41 | "value": "tutorial", 42 | "headComment": "", 43 | "tailComment": "" 44 | }, 45 | "php": { 46 | "type": { 47 | "name": "namespace" 48 | }, 49 | "value": "tutorial", 50 | "headComment": "", 51 | "tailComment": "" 52 | }, 53 | "perl": { 54 | "type": { 55 | "name": "namespace" 56 | }, 57 | "value": "tutorial", 58 | "headComment": "", 59 | "tailComment": "" 60 | }, 61 | "haxe": { 62 | "type": { 63 | "name": "namespace" 64 | }, 65 | "value": "tutorial", 66 | "headComment": "", 67 | "tailComment": "" 68 | } 69 | }, 70 | "typedef": { 71 | "MyInteger": { 72 | "type": { 73 | "name": "i32" 74 | }, 75 | "headComment": "", 76 | "tailComment": "" 77 | } 78 | }, 79 | "const": { 80 | "INT32CONSTANT": { 81 | "type": { 82 | "name": "i32" 83 | }, 84 | "value": 9853, 85 | "headComment": "", 86 | "tailComment": "" 87 | }, 88 | "MAPCONSTANT": { 89 | "type": { 90 | "name": "map", 91 | "keyType": "string", 92 | "valueType": "string" 93 | }, 94 | "value": [ 95 | { 96 | "key": "hello", 97 | "value": "world" 98 | }, 99 | { 100 | "key": "goodnight", 101 | "value": "moon" 102 | } 103 | ], 104 | "headComment": "", 105 | "tailComment": "" 106 | } 107 | }, 108 | "enum": { 109 | "Operation": { 110 | "type": { 111 | "name": "enum" 112 | }, 113 | "items": [ 114 | { 115 | "name": "ADD", 116 | "type": "enum", 117 | "value": 1, 118 | "headComment": "", 119 | "tailComment": "" 120 | }, 121 | { 122 | "name": "SUBTRACT", 123 | "type": "enum", 124 | "value": 2, 125 | "headComment": "", 126 | "tailComment": "" 127 | }, 128 | { 129 | "name": "MULTIPLY", 130 | "type": "enum", 131 | "value": 3, 132 | "headComment": "", 133 | "tailComment": "" 134 | }, 135 | { 136 | "name": "DIVIDE", 137 | "type": "enum", 138 | "value": 4, 139 | "headComment": "", 140 | "tailComment": "" 141 | } 142 | ], 143 | "headComment": "", 144 | "tailComment": "" 145 | } 146 | }, 147 | "struct": { 148 | "Work": [ 149 | { 150 | "id": 1, 151 | "type": "i32", 152 | "name": "num1", 153 | "headComment": "", 154 | "tailComment": "", 155 | "value": 0 156 | }, 157 | { 158 | "id": 2, 159 | "type": "i32", 160 | "name": "num2", 161 | "headComment": "", 162 | "tailComment": "" 163 | }, 164 | { 165 | "id": 3, 166 | "type": "Operation", 167 | "name": "op", 168 | "headComment": "", 169 | "tailComment": "" 170 | }, 171 | { 172 | "id": 4, 173 | "type": "string", 174 | "name": "comment", 175 | "headComment": "", 176 | "tailComment": "", 177 | "option": "optional" 178 | } 179 | ] 180 | }, 181 | "exception": { 182 | "InvalidOperation": [ 183 | { 184 | "id": 1, 185 | "type": "i32", 186 | "name": "whatOp", 187 | "headComment": "", 188 | "tailComment": "" 189 | }, 190 | { 191 | "id": 2, 192 | "type": "string", 193 | "name": "why", 194 | "headComment": "", 195 | "tailComment": "", 196 | "value": "abc" 197 | } 198 | ] 199 | }, 200 | "service": { 201 | "Calculator": { 202 | "type": { 203 | "name": "service" 204 | }, 205 | "headComment": "", 206 | "tailComment": "", 207 | "extends": "shared.SharedService", 208 | "functions": { 209 | "ping": { 210 | "type": "void", 211 | "name": "ping", 212 | "args": [], 213 | "throws": [], 214 | "oneway": false, 215 | "headComment": "", 216 | "tailComment": "" 217 | }, 218 | "add": { 219 | "type": "i32", 220 | "name": "add", 221 | "args": [ 222 | { 223 | "id": 1, 224 | "type": "i32", 225 | "name": "num1", 226 | "headComment": "", 227 | "tailComment": "" 228 | }, 229 | { 230 | "id": 2, 231 | "type": "i32", 232 | "name": "num2", 233 | "headComment": "", 234 | "tailComment": "", 235 | "option": "optional" 236 | } 237 | ], 238 | "throws": [], 239 | "oneway": false, 240 | "headComment": "", 241 | "tailComment": "" 242 | }, 243 | "calculate": { 244 | "type": "i32", 245 | "name": "calculate", 246 | "args": [ 247 | { 248 | "id": 1, 249 | "type": "i32", 250 | "name": "logid", 251 | "headComment": "", 252 | "tailComment": "" 253 | }, 254 | { 255 | "id": 2, 256 | "type": "Work", 257 | "name": "w", 258 | "headComment": "", 259 | "tailComment": "" 260 | } 261 | ], 262 | "throws": [ 263 | { 264 | "id": 1, 265 | "type": "InvalidOperation", 266 | "name": "ouch", 267 | "headComment": "", 268 | "tailComment": "" 269 | } 270 | ], 271 | "oneway": false, 272 | "headComment": "", 273 | "tailComment": "" 274 | }, 275 | "zip": { 276 | "type": "void", 277 | "name": "zip", 278 | "args": [], 279 | "throws": [], 280 | "oneway": true, 281 | "headComment": "", 282 | "tailComment": "" 283 | } 284 | } 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/genTS.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Principles 4 | 5 | //- filename: name.thrift -> nameService.ts 6 | 7 | //- include 'Common.thrift' -> import * as Common from './CommonService' 8 | //- enum -> enum 9 | //- struct/union/exception -> interface 10 | 11 | //- map -> {[key: string]: type} 12 | //- list/set -> type[] 13 | //- i16/i32/i64/double -> number 14 | //- bool -> boolean 15 | //- string -> string 16 | //- byte -> byte 17 | //- binary -> binary 18 | //- const -> const 19 | //- void -> void 20 | //- typedef/namespace (drop) 21 | //- service (explode functions, (service name).(function name) -> rpc method string) 22 | //- extends (drop) 23 | //- required/optional -> type/type? 24 | 25 | import { thrift2TsPath, getThriftFileName } from "./helpers"; 26 | import prettierConfig from "./prettier-config"; 27 | const prettier = require("prettier"); 28 | 29 | const NEW_LINE = "\r\n"; 30 | const NEW_LINE_2 = "\r\n\r\n"; 31 | 32 | const header = `/**${NEW_LINE} * Auto generated by Thrift2Ts.${NEW_LINE} *${NEW_LINE} * ${new Date().toString()}${NEW_LINE} */${NEW_LINE_2}`; 33 | 34 | export default (ast: any, Request = "./request"): string => { 35 | let code = ""; 36 | 37 | code += header; 38 | 39 | const defaultExports = []; 40 | 41 | const simplifyType = (type): string | object => { 42 | if (typeof type === "string") { 43 | return type; 44 | } 45 | 46 | switch (type.name.toLowerCase()) { 47 | case "map": 48 | case "list": 49 | case "set": 50 | return type; 51 | default: 52 | return type.name.toString(); 53 | } 54 | }; 55 | 56 | const valueTypeTransformer = (type): string => { 57 | type = simplifyType(type); 58 | 59 | if (typeof type === "string") { 60 | switch (type) { 61 | case "i16": 62 | case "i32": 63 | case "i64": 64 | case "double": 65 | return "number"; 66 | case "bool": 67 | return "boolean"; 68 | default: 69 | return type; 70 | } 71 | } 72 | 73 | switch (type["name"]) { 74 | case "map": 75 | return `{[key: ${type["keyType"]}]: ${valueTypeTransformer( 76 | type["valueType"] 77 | )}}`; 78 | case "list": 79 | case "set": 80 | return `${valueTypeTransformer(type["valueType"])}[]`; 81 | } 82 | throw new Error(`Unexpected value type: ${JSON.stringify(type)}`); 83 | }; 84 | 85 | const valueTransformer = (value, isMap = false): string => { 86 | if (typeof value === "string") { 87 | return `\"${value}\"`; 88 | } 89 | if (["number", "boolean"].indexOf(typeof value) > -1) { 90 | return value.toString(); 91 | } 92 | if (value instanceof Array) { 93 | if (isMap) { 94 | return `{${value.map(v => valueTransformer(v)).join(", ")}}`; 95 | } 96 | return `[${value.map(v => valueTransformer(v)).join(", ")}]`; 97 | } 98 | if ( 99 | typeof value === "object" && 100 | value["key"] !== undefined && 101 | value["value"] !== undefined 102 | ) { 103 | return `"${value["key"]}": ${valueTransformer(value["value"])}`; 104 | } 105 | throw new Error(`Unexpected value: ${JSON.stringify(value)}`); 106 | }; 107 | 108 | const includesHandler = (includes: object[]): string => { 109 | let imports = ""; 110 | Object.keys(includes).map(key => includes[key]).forEach(include => { 111 | imports += `${NEW_LINE}import * as ${getThriftFileName( 112 | include.value 113 | )} from "${thrift2TsPath(include.value)}";${NEW_LINE}`; 114 | }); 115 | return imports; 116 | }; 117 | 118 | const constsHandler = (consts: object[]): string => { 119 | let newConsts = ""; 120 | Object.keys(consts).forEach(key => { 121 | newConsts += `${NEW_LINE}export const ${key}: ${valueTypeTransformer( 122 | consts[key]["type"] 123 | )} = ${valueTransformer( 124 | consts[key]["value"], 125 | typeof consts[key]["type"] === "object" && 126 | consts[key]["type"]["name"] === "map" 127 | )}; ${NEW_LINE}`; 128 | }); 129 | return newConsts; 130 | }; 131 | 132 | const typedefsHandler = (typedefs: object[]): string => { 133 | let newTypedefs = ""; 134 | Object.keys(typedefs).forEach(key => { 135 | newTypedefs += `${NEW_LINE}export type ${key} = ${valueTypeTransformer( 136 | typedefs[key]["type"] 137 | )}; ${NEW_LINE}`; 138 | }); 139 | return newTypedefs; 140 | }; 141 | 142 | const enumsHandler = (enums: object[]): string => { 143 | let newEnums = ""; 144 | Object.keys(enums).forEach(key => { 145 | newEnums += enumHandler(key, enums[key]["items"]); 146 | }); 147 | return newEnums; 148 | }; 149 | 150 | const enumHandler = (name, items: object[]): string => { 151 | // let lastValue = -1; 152 | let code = `${NEW_LINE}export enum ${name} {`; 153 | items.forEach((item, index) => { 154 | if (item["value"] === undefined) { 155 | item["value"] = `"${item["name"]}"`; 156 | } 157 | // lastValue = item["value"]; 158 | code += `${NEW_LINE}${item["name"]} = ${item["value"]}`; 159 | if (index < items.length - 1) { 160 | code += ","; 161 | } 162 | }); 163 | code += `${NEW_LINE}}${NEW_LINE}`; 164 | 165 | return code; 166 | }; 167 | 168 | const structsLikeHandler = (values: object[]): string => { 169 | let interfaces = ""; 170 | Object.keys(values).forEach(key => { 171 | interfaces += structLikeHandler(key, values[key]); 172 | }); 173 | return interfaces; 174 | }; 175 | 176 | const structLikeHandler = (name, items: object[]): string => { 177 | let code = `${NEW_LINE}export interface ${name} {`; 178 | items.forEach((item, index) => { 179 | code += `${NEW_LINE}${item["name"]}`; 180 | if (item["option"] === "optional") { 181 | code += "?"; 182 | } 183 | code += `: ${valueTypeTransformer(item["type"])};`; 184 | }); 185 | code += `${NEW_LINE}}${NEW_LINE}`; 186 | 187 | return code; 188 | }; 189 | 190 | const servicesHandler = (services: object[]): string => { 191 | let code = ""; 192 | Object.keys(services).forEach(key => { 193 | code += serviceHandler(key, services[key]); 194 | }); 195 | return code; 196 | }; 197 | 198 | const serviceHandler = (name, service): string => { 199 | let code = ""; 200 | let functions = service["functions"]; 201 | Object.keys(functions).forEach(key => { 202 | code += serviceFunctionHandler(name, functions[key]); 203 | }); 204 | return code; 205 | }; 206 | 207 | const serviceFunctionHandler = (name, serviceFunc): string => { 208 | let code = ""; 209 | const method = `${name}.${serviceFunc["name"]}`; 210 | const returnType = valueTypeTransformer(serviceFunc["type"]); 211 | 212 | // args 213 | let args: object[] = serviceFunc["args"]; 214 | let argNames = []; 215 | let argNameAndTypes = []; 216 | args.forEach(arg => { 217 | argNames.push(arg["name"]); 218 | argNameAndTypes.push( 219 | `${arg["name"]}${arg["option"] 220 | ? "?" 221 | : ""}: ${valueTypeTransformer(arg["type"])}` 222 | ); 223 | }); 224 | 225 | code += `${NEW_LINE}export function ${serviceFunc[ 226 | "name" 227 | ]}(${argNameAndTypes.join(", ")}): Promise<${returnType}> {${NEW_LINE}`; 228 | code += ` return Request<${returnType}>("${method}", { ${argNames.join( 229 | ", " 230 | )} })`; 231 | code += `${NEW_LINE}}${NEW_LINE}`; 232 | 233 | defaultExports.push(serviceFunc["name"]); 234 | return code; 235 | }; 236 | 237 | const defaultExportsHandler = (): string => { 238 | let code = `${NEW_LINE_2}export default {${NEW_LINE}`; 239 | defaultExports.forEach((v, i) => { 240 | code += `${v}`; 241 | if (i < defaultExports.length - 1) { 242 | code += ","; 243 | } 244 | code += `${NEW_LINE}`; 245 | }); 246 | code += `}${NEW_LINE}`; 247 | return code; 248 | }; 249 | 250 | // include Request 251 | if (Request && ast.service) { 252 | code += `import Request from "${Request}";${NEW_LINE}`; 253 | } 254 | 255 | // includes -> import 256 | if (ast.include) { 257 | code += includesHandler(ast.include); 258 | } 259 | 260 | // const -> const 261 | if (ast.const) { 262 | code += constsHandler(ast.const); 263 | } 264 | 265 | // typedef -> type 266 | if (ast.typedef) { 267 | code += typedefsHandler(ast.typedef) 268 | } 269 | 270 | // enum -> interface 271 | if (ast.enum) { 272 | code += enumsHandler(ast.enum); 273 | } 274 | 275 | // struct -> interface 276 | // union -> interface 277 | // exception -> interface 278 | if (ast.exception) { 279 | code += structsLikeHandler(ast.exception); 280 | } 281 | 282 | if (ast.struct) { 283 | code += structsLikeHandler(ast.struct); 284 | } 285 | 286 | if (ast.union) { 287 | code += structsLikeHandler(ast.union); 288 | } 289 | 290 | // service -> functions 291 | if (Request && ast.service) { 292 | code += servicesHandler(ast.service); 293 | } 294 | 295 | // default export 296 | if (ast.service) { 297 | code += defaultExportsHandler(); 298 | } 299 | 300 | return prettier.format(code, prettierConfig); 301 | }; 302 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^7.0.29": 6 | version "7.0.29" 7 | resolved "http://registry.npm.taobao.org/@types/node/download/@types/node-7.0.29.tgz#ccfcec5b7135c7caf6c4ffb8c7f33102340d99df" 8 | 9 | ansi-regex@^2.0.0: 10 | version "2.1.1" 11 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 12 | 13 | builtin-modules@^1.0.0: 14 | version "1.1.1" 15 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 16 | 17 | camelcase@^4.1.0: 18 | version "4.1.0" 19 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" 20 | 21 | cliui@^3.2.0: 22 | version "3.2.0" 23 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" 24 | dependencies: 25 | string-width "^1.0.1" 26 | strip-ansi "^3.0.1" 27 | wrap-ansi "^2.0.0" 28 | 29 | code-point-at@^1.0.0: 30 | version "1.1.0" 31 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 32 | 33 | cross-spawn@^4.0.0: 34 | version "4.0.2" 35 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" 36 | dependencies: 37 | lru-cache "^4.0.1" 38 | which "^1.2.9" 39 | 40 | decamelize@^1.1.1: 41 | version "1.2.0" 42 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 43 | 44 | error-ex@^1.2.0: 45 | version "1.3.1" 46 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" 47 | dependencies: 48 | is-arrayish "^0.2.1" 49 | 50 | execa@^0.5.0: 51 | version "0.5.1" 52 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" 53 | dependencies: 54 | cross-spawn "^4.0.0" 55 | get-stream "^2.2.0" 56 | is-stream "^1.1.0" 57 | npm-run-path "^2.0.0" 58 | p-finally "^1.0.0" 59 | signal-exit "^3.0.0" 60 | strip-eof "^1.0.0" 61 | 62 | find-up@^2.0.0: 63 | version "2.1.0" 64 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" 65 | dependencies: 66 | locate-path "^2.0.0" 67 | 68 | get-caller-file@^1.0.1: 69 | version "1.0.2" 70 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" 71 | 72 | get-stream@^2.2.0: 73 | version "2.3.1" 74 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" 75 | dependencies: 76 | object-assign "^4.0.1" 77 | pinkie-promise "^2.0.0" 78 | 79 | graceful-fs@^4.1.2: 80 | version "4.1.11" 81 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 82 | 83 | hosted-git-info@^2.1.4: 84 | version "2.4.2" 85 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67" 86 | 87 | invert-kv@^1.0.0: 88 | version "1.0.0" 89 | resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" 90 | 91 | is-arrayish@^0.2.1: 92 | version "0.2.1" 93 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 94 | 95 | is-builtin-module@^1.0.0: 96 | version "1.0.0" 97 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" 98 | dependencies: 99 | builtin-modules "^1.0.0" 100 | 101 | is-fullwidth-code-point@^1.0.0: 102 | version "1.0.0" 103 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 104 | dependencies: 105 | number-is-nan "^1.0.0" 106 | 107 | is-fullwidth-code-point@^2.0.0: 108 | version "2.0.0" 109 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 110 | 111 | is-stream@^1.1.0: 112 | version "1.1.0" 113 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 114 | 115 | isexe@^2.0.0: 116 | version "2.0.0" 117 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 118 | 119 | lcid@^1.0.0: 120 | version "1.0.0" 121 | resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" 122 | dependencies: 123 | invert-kv "^1.0.0" 124 | 125 | load-json-file@^2.0.0: 126 | version "2.0.0" 127 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" 128 | dependencies: 129 | graceful-fs "^4.1.2" 130 | parse-json "^2.2.0" 131 | pify "^2.0.0" 132 | strip-bom "^3.0.0" 133 | 134 | locate-path@^2.0.0: 135 | version "2.0.0" 136 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" 137 | dependencies: 138 | p-locate "^2.0.0" 139 | path-exists "^3.0.0" 140 | 141 | lru-cache@^4.0.1: 142 | version "4.1.1" 143 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" 144 | dependencies: 145 | pseudomap "^1.0.2" 146 | yallist "^2.1.2" 147 | 148 | mem@^1.1.0: 149 | version "1.1.0" 150 | resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" 151 | dependencies: 152 | mimic-fn "^1.0.0" 153 | 154 | mimic-fn@^1.0.0: 155 | version "1.1.0" 156 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" 157 | 158 | minimist@0.0.8: 159 | version "0.0.8" 160 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 161 | 162 | mkdirp@^0.5.1: 163 | version "0.5.1" 164 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 165 | dependencies: 166 | minimist "0.0.8" 167 | 168 | normalize-package-data@^2.3.2: 169 | version "2.3.8" 170 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" 171 | dependencies: 172 | hosted-git-info "^2.1.4" 173 | is-builtin-module "^1.0.0" 174 | semver "2 || 3 || 4 || 5" 175 | validate-npm-package-license "^3.0.1" 176 | 177 | npm-run-path@^2.0.0: 178 | version "2.0.2" 179 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 180 | dependencies: 181 | path-key "^2.0.0" 182 | 183 | number-is-nan@^1.0.0: 184 | version "1.0.1" 185 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 186 | 187 | object-assign@^4.0.1: 188 | version "4.1.1" 189 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 190 | 191 | os-locale@^2.0.0: 192 | version "2.0.0" 193 | resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.0.0.tgz#15918ded510522b81ee7ae5a309d54f639fc39a4" 194 | dependencies: 195 | execa "^0.5.0" 196 | lcid "^1.0.0" 197 | mem "^1.1.0" 198 | 199 | p-finally@^1.0.0: 200 | version "1.0.0" 201 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 202 | 203 | p-limit@^1.1.0: 204 | version "1.1.0" 205 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" 206 | 207 | p-locate@^2.0.0: 208 | version "2.0.0" 209 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" 210 | dependencies: 211 | p-limit "^1.1.0" 212 | 213 | parse-json@^2.2.0: 214 | version "2.2.0" 215 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" 216 | dependencies: 217 | error-ex "^1.2.0" 218 | 219 | path-exists@^3.0.0: 220 | version "3.0.0" 221 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 222 | 223 | path-key@^2.0.0: 224 | version "2.0.1" 225 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 226 | 227 | path-type@^2.0.0: 228 | version "2.0.0" 229 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" 230 | dependencies: 231 | pify "^2.0.0" 232 | 233 | pify@^2.0.0: 234 | version "2.3.0" 235 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 236 | 237 | pinkie-promise@^2.0.0: 238 | version "2.0.1" 239 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 240 | dependencies: 241 | pinkie "^2.0.0" 242 | 243 | pinkie@^2.0.0: 244 | version "2.0.4" 245 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 246 | 247 | prettier@^1.5.2: 248 | version "1.5.2" 249 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.5.2.tgz#7ea0751da27b93bfb6cecfcec509994f52d83bb3" 250 | 251 | pseudomap@^1.0.2: 252 | version "1.0.2" 253 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 254 | 255 | read-pkg-up@^2.0.0: 256 | version "2.0.0" 257 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" 258 | dependencies: 259 | find-up "^2.0.0" 260 | read-pkg "^2.0.0" 261 | 262 | read-pkg@^2.0.0: 263 | version "2.0.0" 264 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" 265 | dependencies: 266 | load-json-file "^2.0.0" 267 | normalize-package-data "^2.3.2" 268 | path-type "^2.0.0" 269 | 270 | require-directory@^2.1.1: 271 | version "2.1.1" 272 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 273 | 274 | require-main-filename@^1.0.1: 275 | version "1.0.1" 276 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" 277 | 278 | "semver@2 || 3 || 4 || 5": 279 | version "5.3.0" 280 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 281 | 282 | set-blocking@^2.0.0: 283 | version "2.0.0" 284 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 285 | 286 | signal-exit@^3.0.0: 287 | version "3.0.2" 288 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 289 | 290 | spdx-correct@~1.0.0: 291 | version "1.0.2" 292 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" 293 | dependencies: 294 | spdx-license-ids "^1.0.2" 295 | 296 | spdx-expression-parse@~1.0.0: 297 | version "1.0.4" 298 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" 299 | 300 | spdx-license-ids@^1.0.2: 301 | version "1.2.2" 302 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" 303 | 304 | string-width@^1.0.1: 305 | version "1.0.2" 306 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 307 | dependencies: 308 | code-point-at "^1.0.0" 309 | is-fullwidth-code-point "^1.0.0" 310 | strip-ansi "^3.0.0" 311 | 312 | string-width@^2.0.0: 313 | version "2.0.0" 314 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" 315 | dependencies: 316 | is-fullwidth-code-point "^2.0.0" 317 | strip-ansi "^3.0.0" 318 | 319 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 320 | version "3.0.1" 321 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 322 | dependencies: 323 | ansi-regex "^2.0.0" 324 | 325 | strip-bom@^3.0.0: 326 | version "3.0.0" 327 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 328 | 329 | strip-eof@^1.0.0: 330 | version "1.0.0" 331 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 332 | 333 | typescript@^2.3.4: 334 | version "2.3.4" 335 | resolved "http://registry.npm.taobao.org/typescript/download/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42" 336 | 337 | validate-npm-package-license@^3.0.1: 338 | version "3.0.1" 339 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" 340 | dependencies: 341 | spdx-correct "~1.0.0" 342 | spdx-expression-parse "~1.0.0" 343 | 344 | which-module@^2.0.0: 345 | version "2.0.0" 346 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 347 | 348 | which@^1.2.9: 349 | version "1.2.14" 350 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" 351 | dependencies: 352 | isexe "^2.0.0" 353 | 354 | wrap-ansi@^2.0.0: 355 | version "2.1.0" 356 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" 357 | dependencies: 358 | string-width "^1.0.1" 359 | strip-ansi "^3.0.1" 360 | 361 | y18n@^3.2.1: 362 | version "3.2.1" 363 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" 364 | 365 | yallist@^2.1.2: 366 | version "2.1.2" 367 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 368 | 369 | yargs-parser@^7.0.0: 370 | version "7.0.0" 371 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" 372 | dependencies: 373 | camelcase "^4.1.0" 374 | 375 | yargs@^8.0.2: 376 | version "8.0.2" 377 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" 378 | dependencies: 379 | camelcase "^4.1.0" 380 | cliui "^3.2.0" 381 | decamelize "^1.1.1" 382 | get-caller-file "^1.0.1" 383 | os-locale "^2.0.0" 384 | read-pkg-up "^2.0.0" 385 | require-directory "^2.1.1" 386 | require-main-filename "^1.0.1" 387 | set-blocking "^2.0.0" 388 | string-width "^2.0.0" 389 | which-module "^2.0.0" 390 | y18n "^3.2.1" 391 | yargs-parser "^7.0.0" 392 | -------------------------------------------------------------------------------- /lib/genAst.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const error_1 = require("./error"); 4 | let simplifyDataType = (type) => { 5 | switch (type.name.toLowerCase()) { 6 | case "map": 7 | case "list": 8 | case "set": 9 | return type; 10 | default: 11 | return type.name.toString(); 12 | } 13 | }; 14 | exports.default = (code) => { 15 | code = code.toString(); 16 | let nCount = 0; 17 | let rCount = 0; 18 | let offset = 0; 19 | let stack = []; 20 | let tailCommentQueue = []; 21 | let headCommentQueue = []; 22 | const backup = () => { 23 | stack.push({ offset, nCount, rCount }); 24 | }; 25 | const restore = () => { 26 | let saveCase = stack[stack.length - 1]; 27 | offset = saveCase.offset; 28 | nCount = saveCase.nCount; 29 | rCount = saveCase.rCount; 30 | }; 31 | const drop = () => { 32 | stack.pop(); 33 | }; 34 | const getLineNum = () => { 35 | return Math.max(rCount, nCount) + 1; 36 | }; 37 | const throwError = (message) => { 38 | let line = getLineNum(); 39 | message = `${message}\non Line ${line}`; 40 | let context = code.slice(offset, offset + 100); 41 | throw new error_1.ThriftSyntaxError(message, context, line); 42 | }; 43 | const recordLineNum = (char) => { 44 | if (char === "\n") { 45 | nCount++; 46 | } 47 | else if (char === "\r") { 48 | rCount++; 49 | } 50 | }; 51 | const readSingleLineComment = () => { 52 | let i = 0; 53 | if (["#", "/"].indexOf(code[offset + i++]) < 0 || 54 | code[offset + i++] !== "/") { 55 | return false; 56 | } 57 | let comment = ""; 58 | while (code[offset] !== "\n" && code[offset] !== "\r") { 59 | comment += code[offset++]; 60 | } 61 | return comment; 62 | }; 63 | const readMultiLinesComment = () => { 64 | let i = 0; 65 | if (code[offset + i++] !== "/" || code[offset + i++] !== "*") { 66 | return false; 67 | } 68 | let comment = "/*"; 69 | do { 70 | while (offset + i < code.length && code[offset + i] !== "*") { 71 | recordLineNum(code[offset + i]); 72 | comment += code[offset + i++]; 73 | } 74 | comment += code[offset + i]; 75 | i++; 76 | } while (offset + i < code.length && code[offset + i] !== "/"); 77 | comment += "/"; 78 | offset += ++i; 79 | return comment; 80 | }; 81 | const readCurrentLineSpace = () => { 82 | while (offset < code.length) { 83 | let char = code[offset]; 84 | recordLineNum(char); 85 | if (char === "\n" || char === "\r") { 86 | offset++; 87 | break; 88 | } 89 | if (char === " " || char === "\t") { 90 | offset++; 91 | } 92 | else { 93 | let comment1 = readMultiLinesComment(); 94 | if (comment1) { 95 | readCurrentLineSpace(); 96 | } 97 | let comment2 = readSingleLineComment(); 98 | if (!comment1 && !comment2) { 99 | break; 100 | } 101 | (comment1 || comment2) && 102 | tailCommentQueue.push((comment1 || comment2)); 103 | } 104 | } 105 | return; 106 | }; 107 | const readNewLineSpace = () => { 108 | while (offset < code.length) { 109 | let char = code[offset]; 110 | recordLineNum(char); 111 | if (char === "\n" || 112 | char === "\r" || 113 | char === " " || 114 | char === "\t") { 115 | offset++; 116 | } 117 | else { 118 | let comment = readMultiLinesComment() || readSingleLineComment(); 119 | comment && headCommentQueue.push(comment); 120 | if (!comment) { 121 | break; 122 | } 123 | } 124 | } 125 | return; 126 | }; 127 | const readSpace = () => { 128 | readCurrentLineSpace(); 129 | readNewLineSpace(); 130 | }; 131 | const readCommentFromQueue = (isTail = false) => { 132 | let queue = isTail ? tailCommentQueue : headCommentQueue; 133 | let comments = []; 134 | let comment; 135 | while ((comment = queue.shift())) { 136 | if (comment.startsWith("#")) { 137 | comment = "//" + comment.slice(1); 138 | } 139 | comments.push(comment); 140 | } 141 | return comments.join("\r\n"); 142 | }; 143 | const readUntilThrow = (transaction, key) => { 144 | let container = key ? {} : []; 145 | while (true) { 146 | try { 147 | backup(); 148 | let result = transaction(); 149 | key 150 | ? (container[result[key]] = result) 151 | : container.push(result); 152 | } 153 | catch (exception) { 154 | restore(); 155 | return container; 156 | } 157 | finally { 158 | drop(); 159 | } 160 | } 161 | }; 162 | const readKeyword = (word) => { 163 | for (let i = 0; i < word.length; i++) { 164 | if (code[offset + i] !== word[i]) { 165 | let token = code.substr(offset, word.length); 166 | throw new Error(`Unexpected token ${token} (current call: readKeyword)`); 167 | } 168 | } 169 | offset += word.length; 170 | readSpace(); 171 | return word; 172 | }; 173 | const readChar = (char) => { 174 | if (code[offset] !== char) { 175 | throw new Error(`Unexpected char ${code[offset]} (current call: readChar)`); 176 | } 177 | offset++; 178 | readSpace(); 179 | return char; 180 | }; 181 | const readComma = () => { 182 | let char = code[offset]; 183 | if (/[,|;]/.test(char)) { 184 | offset++; 185 | readSpace(); 186 | return char; 187 | } 188 | }; 189 | const readSubject = () => { 190 | return readWith(readNamespace, readInclude, readTypedef, readConst, readEnum, readStruct, readUnion, readException, readService); 191 | }; 192 | const readTypedef = () => { 193 | let subject = readKeyword("typedef"); 194 | let type = readType(); 195 | let name = readName(); 196 | readComma(); 197 | let headComment = readCommentFromQueue(); 198 | let tailComment = readCommentFromQueue(true); 199 | return { subject, type, name, headComment, tailComment }; 200 | }; 201 | const readConst = () => { 202 | let subject = readKeyword("const"); 203 | let type = readType(); 204 | let name = readName(); 205 | let value = readAssign(); 206 | readComma(); 207 | let headComment = readCommentFromQueue(); 208 | let tailComment = readCommentFromQueue(true); 209 | return { subject, type, name, value, headComment, tailComment }; 210 | }; 211 | const readEnum = () => { 212 | let subject = readKeyword("enum"); 213 | let type = { 214 | name: "enum" 215 | }; 216 | let name = readName(); 217 | let headComment = readCommentFromQueue(); 218 | let tailComment = readCommentFromQueue(true); 219 | let items = readEnumBlock(); 220 | return { subject, type, name, items, headComment, tailComment }; 221 | }; 222 | const readEnumBlock = () => { 223 | readChar("{"); 224 | let items = readUntilThrow(readEnumItem); 225 | readChar("}"); 226 | return items; 227 | }; 228 | const readEnumItem = () => { 229 | let name = readName(); 230 | let type = "enum"; 231 | let value = readAssign(); 232 | readComma(); 233 | let headComment = readCommentFromQueue(); 234 | let tailComment = readCommentFromQueue(true); 235 | return { name, type, value, headComment, tailComment }; 236 | }; 237 | const readStruct = () => { 238 | let subject = readKeyword("struct"); 239 | let type = { 240 | name: "struct" 241 | }; 242 | let name = readName(); 243 | let headComment = readCommentFromQueue(); 244 | let tailComment = readCommentFromQueue(true); 245 | let items = readStructLikeBlock(); 246 | return { subject, type, name, items, headComment, tailComment }; 247 | }; 248 | const readStructLikeBlock = () => { 249 | readChar("{"); 250 | let result = readUntilThrow(readStructLikeItem); 251 | readChar("}"); 252 | return result; 253 | }; 254 | const readStructLikeItem = () => { 255 | let id; 256 | try { 257 | id = readNumValue(); 258 | readChar(":"); 259 | } 260 | catch (exception) { } 261 | let option = readWith(readKeyword.bind(this, "required"), readKeyword.bind(this, "optional"), () => { }); 262 | let type = simplifyDataType(readType()); 263 | let name = readName(); 264 | let value = readAssign(); 265 | readComma(); 266 | let headComment = readCommentFromQueue(); 267 | let tailComment = readCommentFromQueue(true); 268 | let result = { 269 | id, 270 | type, 271 | name, 272 | headComment, 273 | tailComment 274 | }; 275 | if (option !== undefined) { 276 | result.option = option; 277 | } 278 | if (value !== undefined) { 279 | result.value = value; 280 | } 281 | return result; 282 | }; 283 | const readUnion = () => { 284 | let subject = readKeyword("union"); 285 | let type = { 286 | name: "union" 287 | }; 288 | let name = readName(); 289 | let headComment = readCommentFromQueue(); 290 | let tailComment = readCommentFromQueue(true); 291 | let items = readStructLikeBlock(); 292 | return { subject, type, name, items, headComment, tailComment }; 293 | }; 294 | const readException = () => { 295 | let subject = readKeyword("exception"); 296 | let type = { 297 | name: "exception" 298 | }; 299 | let name = readName(); 300 | let headComment = readCommentFromQueue(); 301 | let tailComment = readCommentFromQueue(true); 302 | let items = readStructLikeBlock(); 303 | return { subject, type, name, items, headComment, tailComment }; 304 | }; 305 | const readExtends = () => { 306 | try { 307 | backup(); 308 | readKeyword("extends"); 309 | let name = readRefValue().join("."); 310 | return name; 311 | } 312 | catch (exception) { 313 | restore(); 314 | return; 315 | } 316 | finally { 317 | drop(); 318 | } 319 | }; 320 | const readService = () => { 321 | let subject = readKeyword("service"); 322 | let type = { 323 | name: "service" 324 | }; 325 | let name = readName(); 326 | let extend = readExtends(); 327 | let headComment = readCommentFromQueue(); 328 | let tailComment = readCommentFromQueue(true); 329 | let functions = readServiceBlock(); 330 | let result = { 331 | subject, 332 | type, 333 | name, 334 | headComment, 335 | tailComment 336 | }; 337 | if (extend !== undefined) { 338 | result.extends = extend; 339 | } 340 | if (functions !== undefined) { 341 | result.functions = functions; 342 | } 343 | return result; 344 | }; 345 | const readServiceBlock = () => { 346 | readChar("{"); 347 | let result = readUntilThrow(readServiceItem, "name"); 348 | readChar("}"); 349 | return result; 350 | }; 351 | const readServiceItem = () => { 352 | let oneway = !!readWith(readOneway, () => { }); 353 | let type = simplifyDataType(readType()); 354 | let name = readName(); 355 | let headComment = readCommentFromQueue(); 356 | let args = readServiceArgs(); 357 | let tailComment = readCommentFromQueue(true); 358 | let throws = readServiceThrow(); 359 | readComma(); 360 | return { type, name, args, throws, oneway, headComment, tailComment }; 361 | }; 362 | const readServiceArgs = () => { 363 | readChar("("); 364 | let result = readUntilThrow(readStructLikeItem); 365 | readChar(")"); 366 | readSpace(); 367 | return result; 368 | }; 369 | const readServiceThrow = () => { 370 | try { 371 | backup(); 372 | readKeyword("throws"); 373 | return readServiceArgs(); 374 | } 375 | catch (exception) { 376 | restore(); 377 | return []; 378 | } 379 | finally { 380 | drop(); 381 | } 382 | }; 383 | const readNamespace = () => { 384 | let subject = readKeyword("namespace"); 385 | let type = { 386 | name: "namespace" 387 | }; 388 | let name; 389 | let i = 0; 390 | let char = code[offset]; 391 | while (/[a-zA-Z0-9_\*]/.test(char)) { 392 | if (offset + ++i >= code.length) { 393 | throw new Error("Unexpected end (current call: readNamespace)"); 394 | } 395 | char = code[offset + i]; 396 | } 397 | if (i === 0) { 398 | throw new Error("Unexpected token (current call: readNamespace)"); 399 | } 400 | name = code.slice(offset, (offset += i)); 401 | readSpace(); 402 | let serviceName = readRefValue().join("."); 403 | let headComment = readCommentFromQueue(); 404 | let tailComment = readCommentFromQueue(true); 405 | return { 406 | subject, 407 | type, 408 | name, 409 | value: serviceName, 410 | headComment, 411 | tailComment 412 | }; 413 | }; 414 | const readInclude = () => { 415 | let subject = readKeyword("include"); 416 | let type = { 417 | name: "include" 418 | }; 419 | readSpace(); 420 | let includePath = readQuotation(); 421 | let name = includePath.replace(/^.*?([^/\\]*?)(?:\.thrift)?$/, "$1"); 422 | readSpace(); 423 | let headComment = readCommentFromQueue(); 424 | let tailComment = readCommentFromQueue(true); 425 | return { 426 | subject, 427 | type, 428 | name, 429 | value: includePath, 430 | headComment, 431 | tailComment 432 | }; 433 | }; 434 | const readQuotation = () => { 435 | if (code[offset] === '"' || code[offset] === "'") { 436 | offset++; 437 | } 438 | else { 439 | throw new Error("Unexpected token (current call: readQuotation)"); 440 | } 441 | let end = offset; 442 | while (code[end] !== '"' && code[end] !== "'" && end < code.length) { 443 | end++; 444 | } 445 | if (code[end] === '"' || code[end] === "'") { 446 | let quote = code.slice(offset, end); 447 | offset = end + 1; 448 | return quote; 449 | } 450 | else { 451 | throw new Error("Unexpected token (current call: readQuotation)"); 452 | } 453 | }; 454 | const readOneway = () => { 455 | return readKeyword("oneway"); 456 | }; 457 | const readType = () => { 458 | return readWith(readMapType, readSetOrListType, readNormalType); 459 | }; 460 | const readMapType = () => { 461 | let name = readName(); 462 | readChar("<"); 463 | let keyType = simplifyDataType(readType()); 464 | readComma(); 465 | let valueType = simplifyDataType(readType()); 466 | readChar(">"); 467 | return { name, keyType, valueType }; 468 | }; 469 | const readSetOrListType = () => { 470 | let name = readName(); 471 | readChar("<"); 472 | let valueType = simplifyDataType(readType()); 473 | readChar(">"); 474 | return { name, valueType }; 475 | }; 476 | const readNormalType = () => { 477 | let name = readName(); 478 | return { name }; 479 | }; 480 | const readValue = () => { 481 | return readWith(readHexadecimalValue, readEnotationValue, readNumValue, readBoolValue, readStringValue, readListOrSetValue, readMapValue, readRefValue); 482 | }; 483 | const readNumValue = () => { 484 | let value = []; 485 | if (code[offset] === "-") { 486 | value.push("-"); 487 | offset++; 488 | } 489 | while (true) { 490 | let char = code[offset]; 491 | if (/[0-9\.]/.test(char)) { 492 | offset++; 493 | value.push(char); 494 | } 495 | else if (value.length) { 496 | readSpace(); 497 | return +value.join(""); 498 | } 499 | else { 500 | throw new Error(`Unexpected token ${char} (current call: readNumValue)`); 501 | } 502 | } 503 | }; 504 | const readBoolValue = () => { 505 | return JSON.parse(readWith(readKeyword.bind(this, "true"), readKeyword.bind(this, "false"))); 506 | }; 507 | const readStringValue = () => { 508 | let value = []; 509 | let quote; 510 | while (true) { 511 | let char = code[offset++]; 512 | if (!value.length) { 513 | if (char !== "'" && char !== '"') { 514 | throw new Error("Unexpected token (current call: readStringValue)"); 515 | } 516 | else { 517 | quote = char; 518 | value.push(char); 519 | } 520 | } 521 | else { 522 | if (char === "\\") { 523 | value.push(char); 524 | value.push(code[offset++]); 525 | } 526 | else if (char === quote) { 527 | value.push(char); 528 | readSpace(); 529 | return new Function("return " + value.join(""))(); 530 | } 531 | else { 532 | value.push(char); 533 | } 534 | } 535 | } 536 | }; 537 | const readListOrSetValue = () => { 538 | readChar("["); 539 | let list = readUntilThrow(() => { 540 | let value = readValue(); 541 | readComma(); 542 | return value; 543 | }); 544 | readChar("]"); 545 | return list; 546 | }; 547 | const readMapValue = () => { 548 | readChar("{"); 549 | let map = readUntilThrow(() => { 550 | let key = readValue(); 551 | readChar(":"); 552 | let value = readValue(); 553 | readComma(); 554 | return { key, value }; 555 | }); 556 | readChar("}"); 557 | return map; 558 | }; 559 | const readRefValue = () => { 560 | let list = [readName()]; 561 | let others = readUntilThrow(() => { 562 | readChar("."); 563 | return readName(); 564 | }); 565 | return list.concat(others); 566 | }; 567 | const readEnotationValue = () => { 568 | let value = []; 569 | if (code[offset] === "-") { 570 | value.push("-"); 571 | offset++; 572 | } 573 | while (true) { 574 | let char = code[offset]; 575 | if (/[0-9\.]/.test(char)) { 576 | value.push(char); 577 | offset++; 578 | } 579 | else { 580 | break; 581 | } 582 | } 583 | if (code[offset] !== "e" && code[offset] !== "E") { 584 | throw new Error("Unexpected token (current call: readEnotationValue)"); 585 | } 586 | value.push(code[offset++]); 587 | while (true && offset < code.length) { 588 | let char = code[offset]; 589 | if (/[0-9]/.test(char)) { 590 | offset++; 591 | value.push(char); 592 | } 593 | else { 594 | if (value.length) { 595 | readSpace(); 596 | return +value.join(""); 597 | } 598 | else { 599 | throw new Error(`Unexpect token ${char} (current call: readEnotationValue)`); 600 | } 601 | } 602 | } 603 | }; 604 | const readHexadecimalValue = () => { 605 | let value = []; 606 | if (code[offset] === "-") { 607 | value.push(code[offset++]); 608 | } 609 | if (code[offset] !== "0") { 610 | throw new Error(`Unexpected token ${code[offset]} (current call: readHexadecimalValue)`); 611 | } 612 | value.push(code[offset++]); 613 | while (true) { 614 | let char = code[offset]; 615 | if (/[0-9a-zA-Z]/.test(char)) { 616 | offset++; 617 | value.push(char); 618 | } 619 | else { 620 | if (value.length) { 621 | readSpace(); 622 | return +value.join(""); 623 | } 624 | else { 625 | throw new Error(`Unexpected token ${char} (current call: readHexadecimalValue)`); 626 | } 627 | } 628 | } 629 | }; 630 | const readName = () => { 631 | let i = 0; 632 | let char = code[offset]; 633 | while (/[a-zA-Z0-9_\.]/.test(char)) { 634 | char = code[offset + ++i]; 635 | } 636 | if (i === 0) { 637 | throw new Error("Invalid name string (current call: readName)"); 638 | } 639 | let value = code.slice(offset, (offset += i)); 640 | readSpace(); 641 | return value; 642 | }; 643 | const readAssign = () => { 644 | try { 645 | backup(); 646 | readChar("="); 647 | return readValue(); 648 | } 649 | catch (exception) { 650 | restore(); 651 | } 652 | finally { 653 | drop(); 654 | } 655 | }; 656 | const readWith = (...readers) => { 657 | backup(); 658 | for (let i = 0; i < readers.length; i++) { 659 | try { 660 | let result = readers[i](); 661 | drop(); 662 | return result; 663 | } 664 | catch (exception) { 665 | restore(); 666 | continue; 667 | } 668 | } 669 | drop(); 670 | throw new Error("Unexcepted Token (current call: readWith)"); 671 | }; 672 | const parseThrift = () => { 673 | readSpace(); 674 | let ast = {}; 675 | while (true) { 676 | try { 677 | let block = readSubject(); 678 | let { subject, name } = block; 679 | if (!ast[subject]) { 680 | ast[subject] = {}; 681 | } 682 | delete block.subject; 683 | delete block.name; 684 | switch (subject) { 685 | case "exception": 686 | case "struct": 687 | case "union": 688 | ast[subject][name] = block["items"]; 689 | break; 690 | default: 691 | ast[subject][name] = block; 692 | } 693 | } 694 | catch (exception) { 695 | throwError(exception); 696 | } 697 | finally { 698 | if (code.length === offset) 699 | break; 700 | } 701 | } 702 | return ast; 703 | }; 704 | return parseThrift(); 705 | }; 706 | -------------------------------------------------------------------------------- /lib/genServiceClient.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const helpers_1 = require("./helpers"); 4 | const prettier_config_1 = require("./prettier-config"); 5 | const prettier = require("prettier"); 6 | const thriftTypeMapper = { 7 | byte: "ThriftType.BYTE", 8 | i16: "ThriftType.I16", 9 | i32: "ThriftType.I32", 10 | i64: "ThriftType.I64", 11 | double: "ThriftType.DOUBLE", 12 | string: "ThriftType.STRING", 13 | list: "ThriftType.LIST", 14 | set: "ThriftType.SET", 15 | map: "ThriftType.MAP", 16 | struct: "ThriftType.STRUCT", 17 | bool: "ThriftType.BOOL", 18 | void: "ThriftType.VOID", 19 | stop: "ThriftType.STOP" 20 | }; 21 | const fieldReaderMapper = { 22 | byte: "readByte()", 23 | i16: "readI16()", 24 | i32: "readI32()", 25 | i64: "readI64()", 26 | double: "readDouble()", 27 | string: "readString()", 28 | bool: "readBool()" 29 | }; 30 | const fieldWriterMapper = { 31 | byte: "writeByte", 32 | i16: "writeI16", 33 | i32: "writeI32", 34 | i64: "writeI64", 35 | double: "writeDouble", 36 | string: "writeString", 37 | bool: "writeBool" 38 | }; 39 | const NEW_LINE = "\r\n"; 40 | const NEW_LINE_2 = "\r\n\r\n"; 41 | let header = `/**${NEW_LINE} * This service client is auto-generated by Thrift2Ts.${NEW_LINE} *${NEW_LINE} * ${new Date().toString()}${NEW_LINE} */${NEW_LINE_2}`; 42 | header += `import Thrift from "browser-thrift2"${NEW_LINE}`; 43 | header += `import IProtocol, { ProtocolClass } from "browser-thrift2/src/thrift/interface/IProtocol"${NEW_LINE}`; 44 | header += `import ITransport, { TransportClass } from "browser-thrift2/src/thrift/interface/ITransport"${NEW_LINE_2}`; 45 | header += `const {${NEW_LINE}ThriftType,${NEW_LINE}MessageType,${NEW_LINE}TApplicationException,${NEW_LINE}TException${NEW_LINE}} = Thrift${NEW_LINE_2}`; 46 | exports.default = (ast) => { 47 | let code = ""; 48 | code += header; 49 | const defaultExports = []; 50 | const simplifyType = (type) => { 51 | if (typeof type === "string") { 52 | return type; 53 | } 54 | switch (type.name.toLowerCase()) { 55 | case "map": 56 | case "list": 57 | case "set": 58 | return type; 59 | default: 60 | return type.name.toString(); 61 | } 62 | }; 63 | const mapThriftType = type => { 64 | if (typeof type === "object") { 65 | return type.name; 66 | } 67 | if (Object.keys(thriftTypeMapper).indexOf(type) > -1) { 68 | return type; 69 | } 70 | if (ast["typedef"] && Object.keys(ast["typedef"]).indexOf(type) > -1) { 71 | return ast["typedef"][type]["type"]["name"]; 72 | } 73 | if (ast["enum"] && Object.keys(ast["enum"]).indexOf(type) > -1) { 74 | return "i32"; 75 | } 76 | if (ast["map"] && Object.keys(ast["map"]).indexOf(type) > -1) { 77 | return "map"; 78 | } 79 | if (ast["list"] && Object.keys(ast["list"]).indexOf(type) > -1) { 80 | return "list"; 81 | } 82 | if (ast["set"] && Object.keys(ast["set"]).indexOf(type) > -1) { 83 | return "set"; 84 | } 85 | if (ast["struct"] && Object.keys(ast["struct"]).indexOf(type) > -1) { 86 | return "struct"; 87 | } 88 | if (ast["exception"] && 89 | Object.keys(ast["exception"]).indexOf(type) > -1) { 90 | return "struct"; 91 | } 92 | throw new Error(`Unknown thrift type: ${type}`); 93 | }; 94 | const getThriftTypeStr = type => { 95 | return thriftTypeMapper[mapThriftType(type)]; 96 | }; 97 | const getReaderStr = type => { 98 | return fieldReaderMapper[mapThriftType(type)]; 99 | }; 100 | const getWriterStr = type => { 101 | return fieldWriterMapper[mapThriftType(type)]; 102 | }; 103 | const valueTypeTransformer = (type) => { 104 | type = simplifyType(type); 105 | if (typeof type === "string") { 106 | switch (type) { 107 | case "i16": 108 | case "i32": 109 | case "i64": 110 | case "double": 111 | return "number"; 112 | case "bool": 113 | return "boolean"; 114 | default: 115 | return type; 116 | } 117 | } 118 | switch (type["name"]) { 119 | case "map": 120 | return `{[key: ${type["keyType"]}]: ${valueTypeTransformer(type["valueType"])}}`; 121 | case "list": 122 | case "set": 123 | return `${valueTypeTransformer(type["valueType"])}[]`; 124 | } 125 | throw new Error(`Unexpected value type: ${JSON.stringify(type)}`); 126 | }; 127 | const valueTransformer = (value, isMap = false) => { 128 | if (typeof value === "string") { 129 | return `\"${value}\"`; 130 | } 131 | if (["number", "boolean"].indexOf(typeof value) > -1) { 132 | return value.toString(); 133 | } 134 | if (value instanceof Array) { 135 | if (isMap) { 136 | return `{${value.map(v => valueTransformer(v)).join(", ")}}`; 137 | } 138 | return `[${value.map(v => valueTransformer(v)).join(", ")}]`; 139 | } 140 | if (typeof value === "object" && 141 | value["key"] !== undefined && 142 | value["value"] !== undefined) { 143 | return `"${value["key"]}": ${valueTransformer(value["value"])}`; 144 | } 145 | throw new Error(`Unexpected value: ${JSON.stringify(value)}`); 146 | }; 147 | const includesHandler = (includes) => { 148 | let imports = []; 149 | Object.keys(includes).map(key => includes[key]).forEach(include => { 150 | imports.push(`${NEW_LINE}import * as ${helpers_1.getThriftFileName(include.value)} from "${helpers_1.thrift2TsPath(include.value, false, false)}";${NEW_LINE}`); 151 | }); 152 | return imports.join(""); 153 | }; 154 | const constsHandler = (consts) => { 155 | let newConsts = []; 156 | Object.keys(consts).forEach(key => { 157 | newConsts.push(`${NEW_LINE}export const ${key}: ${valueTypeTransformer(consts[key]["type"])} = ${valueTransformer(consts[key]["value"], typeof consts[key]["type"] === "object" && 158 | consts[key]["type"]["name"] === "map")}; ${NEW_LINE}`); 159 | }); 160 | return newConsts.join(""); 161 | }; 162 | const enumsHandler = (enums) => { 163 | let newEnums = []; 164 | Object.keys(enums).forEach(key => { 165 | newEnums.push(enumHandler(key, enums[key]["items"])); 166 | }); 167 | return newEnums.join(""); 168 | }; 169 | const enumHandler = (name, items) => { 170 | let lastValue = -1; 171 | let codes = [`${NEW_LINE}export enum ${name} {`]; 172 | items.forEach((item, index) => { 173 | if (item["value"] === undefined) { 174 | item["value"] = lastValue + 1; 175 | } 176 | lastValue = item["value"]; 177 | codes.push(`${NEW_LINE}${item["name"]} = ${item["value"]}`); 178 | if (index < items.length - 1) { 179 | codes.push(","); 180 | } 181 | }); 182 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 183 | return codes.join(""); 184 | }; 185 | const exceptionsHandler = (values) => { 186 | let exceptions = []; 187 | Object.keys(values).forEach(key => { 188 | exceptions.push(exceptionHandler(key, values[key])); 189 | }); 190 | return exceptions.join(""); 191 | }; 192 | const exceptionHandler = (name, items) => { 193 | let codes = []; 194 | codes.push(`${NEW_LINE}export class ${name} extends TException {`); 195 | items.forEach((item, index) => { 196 | codes.push(`${NEW_LINE}${item["name"]}`); 197 | if (item["option"] === "optional") { 198 | codes.push("?"); 199 | } 200 | codes.push(`: ${valueTypeTransformer(item["type"])};`); 201 | }); 202 | codes.push(`${NEW_LINE}name: string;`); 203 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 204 | codes.push(`${NEW_LINE}super()`); 205 | codes.push(`${NEW_LINE}this.name = "${name}"`); 206 | items.forEach((item, index) => { 207 | let value = item["value"]; 208 | if (value !== undefined) { 209 | if (item["type"] === "string") { 210 | value = `"${value}"`; 211 | } 212 | codes.push(`${NEW_LINE}this.${item["name"]} = ${value}`); 213 | } 214 | else { 215 | codes.push(`${NEW_LINE}this.${item["name"]} = null`); 216 | } 217 | }); 218 | codes.push(`${NEW_LINE}if (args) {`); 219 | items.forEach((item, index) => { 220 | let paramName = item["name"]; 221 | codes.push(`${NEW_LINE}if (args.${paramName} !== undefined && args.${paramName} !== null) {${NEW_LINE}this.${paramName} = args.${paramName}${NEW_LINE}}`); 222 | }); 223 | codes.push(`${NEW_LINE}}`); 224 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 225 | codes.push(`${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}${NEW_LINE}switch (fid) {`); 226 | items.forEach((item, index) => { 227 | let type = item["type"]; 228 | codes.push(`${NEW_LINE}case ${index + 229 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr(type)}) {${NEW_LINE}this.${item["name"]} = input.${getReaderStr(type)}${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break`); 230 | }); 231 | codes.push(`${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}`); 232 | codes.push(`${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')`); 233 | items.forEach((item, index) => { 234 | let type = item["type"]; 235 | codes.push(`${NEW_LINE}if (this.${item["name"]} !== null && this.${item["name"]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item["name"]}', ${getThriftTypeStr(type)}, ${index + 236 | 1})${NEW_LINE}output.${getWriterStr(type)}(this.${item["name"]})${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}`); 237 | }); 238 | codes.push(`${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}`); 239 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 240 | return codes.join(""); 241 | }; 242 | const structsHandler = (values) => { 243 | let structs = []; 244 | Object.keys(values).forEach(key => { 245 | structs.push(structHandler(key, values[key])); 246 | }); 247 | return structs.join(""); 248 | }; 249 | const structHandler = (name, items) => { 250 | let codes = []; 251 | codes.push(`${NEW_LINE}export class ${name} {`); 252 | items.forEach((item, index) => { 253 | codes.push(`${NEW_LINE}${item["name"]}`); 254 | if (item["option"] === "optional") { 255 | codes.push("?"); 256 | } 257 | codes.push(`: ${valueTypeTransformer(item["type"])};`); 258 | }); 259 | codes.push(`${NEW_LINE}name: string;`); 260 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 261 | codes.push(`${NEW_LINE}this.name = "${name}"`); 262 | items.forEach((item, index) => { 263 | let value = item["value"]; 264 | if (value !== undefined) { 265 | if (item["type"] === "string") { 266 | value = `"${value}"`; 267 | } 268 | codes.push(`${NEW_LINE}this.${item["name"]} = ${value}`); 269 | } 270 | else { 271 | codes.push(`${NEW_LINE}this.${item["name"]} = null`); 272 | } 273 | }); 274 | codes.push(`${NEW_LINE}if (args) {`); 275 | items.forEach((item, index) => { 276 | let paramName = item["name"]; 277 | codes.push(`${NEW_LINE}if (args.${paramName} !== undefined && args.${paramName} !== null) {${NEW_LINE}this.${paramName} = args.${paramName}${NEW_LINE}}`); 278 | }); 279 | codes.push(`${NEW_LINE}}`); 280 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 281 | codes.push(`${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}${NEW_LINE}switch (fid) {`); 282 | items.forEach((item, index) => { 283 | let type = item["type"]; 284 | codes.push(`${NEW_LINE}case ${index + 285 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr(type)}) {${NEW_LINE}this.${item["name"]} = input.${getReaderStr(type)}${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break`); 286 | }); 287 | codes.push(`${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}`); 288 | codes.push(`${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')`); 289 | items.forEach((item, index) => { 290 | let type = item["type"]; 291 | codes.push(`${NEW_LINE}if (this.${item["name"]} !== null && this.${item["name"]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item["name"]}', ${getThriftTypeStr(type)}, ${index + 292 | 1})${NEW_LINE}output.${getWriterStr(type)}(this.${item["name"]})${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}`); 293 | }); 294 | codes.push(`${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}`); 295 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 296 | return codes.join(""); 297 | }; 298 | const structsLikeHandler = (values) => { 299 | let interfaces = []; 300 | Object.keys(values).forEach(key => { 301 | interfaces.push(structLikeHandler(key, values[key])); 302 | }); 303 | return interfaces.join(""); 304 | }; 305 | const structLikeHandler = (name, items) => { 306 | let codes = [`${NEW_LINE}export interface ${name} {`]; 307 | items.forEach((item, index) => { 308 | codes.push(`${NEW_LINE}${item["name"]}`); 309 | if (item["option"] === "optional") { 310 | codes.push("?"); 311 | } 312 | codes.push(`: ${valueTypeTransformer(item["type"])};`); 313 | }); 314 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 315 | return codes.join(""); 316 | }; 317 | const servicesHandler = (services) => { 318 | let codes = []; 319 | Object.keys(services).forEach(key => { 320 | codes.push(serviceArgsndResultsHandler(key, services[key])); 321 | codes.push(serviceHandler(key, services[key])); 322 | }); 323 | return codes.join(""); 324 | }; 325 | const serviceArgsndResultsHandler = (name, service) => { 326 | let codes = []; 327 | let functions = service["functions"]; 328 | Object.keys(functions).forEach(key => { 329 | let func = functions[key]; 330 | codes.push(serviceArgHandler(name, func)); 331 | codes.push(serviceResultHandler(name, func)); 332 | }); 333 | return codes.join(""); 334 | }; 335 | const serviceArgHandler = (service, func) => { 336 | let codes = []; 337 | let args = func["args"]; 338 | let name = `${service}_${func["name"]}_args`; 339 | codes.push(`${NEW_LINE}export class ${name} {`); 340 | args.forEach((arg, index) => { 341 | codes.push(`${NEW_LINE}${arg["name"]}`); 342 | if (arg["option"] === "optional") { 343 | codes.push("?"); 344 | } 345 | codes.push(`: ${valueTypeTransformer(arg["type"])};`); 346 | }); 347 | codes.push(`${NEW_LINE}name: string;`); 348 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 349 | codes.push(`${NEW_LINE}this.name = "${name}"`); 350 | args.forEach((arg, index) => { 351 | let value = arg["value"]; 352 | if (value !== undefined) { 353 | if (arg["type"] === "string") { 354 | value = `"${value}"`; 355 | } 356 | codes.push(`${NEW_LINE}this.${arg["name"]} = ${value}`); 357 | } 358 | else { 359 | codes.push(`${NEW_LINE}this.${arg["name"]} = null`); 360 | } 361 | }); 362 | if (args && args.length) { 363 | codes.push(`${NEW_LINE}if (args) {`); 364 | args.forEach((item, index) => { 365 | let paramName = item["name"]; 366 | let value = `args.${paramName}`; 367 | if (mapThriftType(item["type"]) === "struct") { 368 | value = `new ${item["type"]}(${value})`; 369 | } 370 | codes.push(`${NEW_LINE}if (args.${paramName} !== undefined && args.${paramName} !== null) {${NEW_LINE}this.${paramName} = ${value}${NEW_LINE}}`); 371 | }); 372 | codes.push(`${NEW_LINE}}`); 373 | } 374 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 375 | codes.push(`${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}`); 376 | if (args && args.length) { 377 | codes.push(`${NEW_LINE}switch (fid) {`); 378 | args.forEach((item, index) => { 379 | let type = item["type"]; 380 | codes.push(`${NEW_LINE}case ${index + 381 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr(type)}) {${NEW_LINE}`); 382 | if (mapThriftType(type) === "struct") { 383 | codes.push(`this.${item["name"]} = new ${type}()${NEW_LINE}this.${item["name"]}.read(input)`); 384 | } 385 | else { 386 | codes.push(`this.${item["name"]} = input.${getReaderStr(type)}`); 387 | } 388 | codes.push(`${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break`); 389 | }); 390 | codes.push(`${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}`); 391 | } 392 | else { 393 | codes.push(`${NEW_LINE}input.skip(ftype)`); 394 | } 395 | codes.push(`${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}`); 396 | codes.push(`${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')`); 397 | args.forEach((item, index) => { 398 | let type = item["type"]; 399 | codes.push(`${NEW_LINE}if (this.${item["name"]} !== null && this.${item["name"]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item["name"]}', ${getThriftTypeStr(type)}, ${index + 1})${NEW_LINE}`); 400 | if (mapThriftType(type) === "struct") { 401 | codes.push(`this.${item["name"]}.write(output)`); 402 | } 403 | else { 404 | codes.push(`output.${getWriterStr(type)}(this.${item["name"]})`); 405 | } 406 | codes.push(`${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}`); 407 | }); 408 | codes.push(`${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}`); 409 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 410 | return codes.join(""); 411 | }; 412 | const serviceResultHandler = (service, func) => { 413 | let codes = []; 414 | let throws = func["throws"]; 415 | let name = `${service}_${func["name"]}_result`; 416 | codes.push(`${NEW_LINE}export class ${name} {`); 417 | codes.push(`${NEW_LINE}name: string;`); 418 | codes.push(`${NEW_LINE}success: any;`); 419 | if (throws && throws.length) { 420 | throws.forEach((item, index) => { 421 | codes.push(`${NEW_LINE}${item["name"]}: ${item["type"]};`); 422 | }); 423 | } 424 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 425 | codes.push(`${NEW_LINE}this.name = "${name}"`); 426 | if (func["type"] !== "void") { 427 | codes.push(`${NEW_LINE}this.success = null`); 428 | throws.forEach((item, index) => { 429 | codes.push(`${NEW_LINE}this.${item["name"]} = null`); 430 | }); 431 | throws.forEach((item, index) => { 432 | codes.push(`${NEW_LINE}if (args instanceof ${item["type"]}) {${NEW_LINE}this.${item["name"]} = args${NEW_LINE}return${NEW_LINE}}`); 433 | }); 434 | codes.push(`${NEW_LINE}if (args) {`); 435 | codes.push(`${NEW_LINE}if (args.success !== undefined && args.success !== null) {${NEW_LINE}this.success = args.success${NEW_LINE}}`); 436 | throws.forEach((item, index) => { 437 | codes.push(`${NEW_LINE}if (args.${item["name"]} !== undefined && args.${item["name"]} !== null) {${NEW_LINE}this.${item["name"]} = args.${item["name"]}${NEW_LINE}}`); 438 | }); 439 | codes.push(`${NEW_LINE}}`); 440 | } 441 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 442 | codes.push(`${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}`); 443 | if (func["type"] !== "void") { 444 | codes.push(`${NEW_LINE}switch (fid) {`); 445 | codes.push(`${NEW_LINE}case 0:${NEW_LINE}if (ftype === ${getThriftTypeStr(func["type"])}) {${NEW_LINE}this.success = input.${getReaderStr(func["type"])}${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break`); 446 | if (throws && throws.length) { 447 | throws.forEach((item, index) => { 448 | codes.push(`${NEW_LINE}case ${index + 449 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr(item["type"])}) {${NEW_LINE}this.${item["name"]} = new ${item["type"]}()${NEW_LINE}this.${item["name"]}.read(input)${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break`); 450 | }); 451 | } 452 | codes.push(`${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}`); 453 | } 454 | else { 455 | codes.push(`${NEW_LINE}input.skip(ftype)`); 456 | } 457 | codes.push(`${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}`); 458 | codes.push(`${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')`); 459 | if (func["type"] !== "void") { 460 | codes.push(`${NEW_LINE}if (this.success !== null && this.success !== undefined) {${NEW_LINE}output.writeFieldBegin('success', ${getThriftTypeStr(func["type"])}, 0)${NEW_LINE}output.${getWriterStr(func["type"])}(this.success)${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}`); 461 | } 462 | if (throws && throws.length) { 463 | throws.forEach((item, index) => { 464 | codes.push(`${NEW_LINE}if (this.${item["name"]} !== null && this.${item["name"]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item["name"]}', ${getThriftTypeStr(item["type"])}, ${index + 465 | 1})${NEW_LINE}this.${item["name"]}.write(output)${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}`); 466 | }); 467 | } 468 | codes.push(`${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}`); 469 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 470 | return codes.join(""); 471 | }; 472 | const serviceHandler = (name, service) => { 473 | let codes = []; 474 | let functions = service["functions"]; 475 | codes.push(`${NEW_LINE}export class ${name + "Client"}`); 476 | if (service["extends"]) { 477 | codes.push(` extends ${service["extends"] + "Client"}`); 478 | } 479 | codes.push(` {${NEW_LINE}output: ITransport;${NEW_LINE}pClass: ProtocolClass;${NEW_LINE}id: number;${NEW_LINE}reqs: { [key: string]: any }`); 480 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (output: ITransport, pClass: ProtocolClass) {`); 481 | if (service["extends"]) { 482 | codes.push(`${NEW_LINE}super(output, pClass)`); 483 | } 484 | codes.push(`${NEW_LINE}this.output = output${NEW_LINE}this.pClass = pClass${NEW_LINE}this.id = 0${NEW_LINE}this.reqs = {}${NEW_LINE}}`); 485 | Object.keys(functions).forEach(key => { 486 | let func = functions[key]; 487 | let args = func["args"]; 488 | let argStrs = args.map(x => x.name).join(", "); 489 | codes.push(`${NEW_LINE}${NEW_LINE}${key} = (${argStrs 490 | ? argStrs + ", callback" 491 | : "callback"}) => {${NEW_LINE}if (callback === undefined) {${NEW_LINE}let self = this${NEW_LINE}return new Promise(function (resolve, reject) {${NEW_LINE}self.reqs[self.id] = (err, result) => {${NEW_LINE}if (err) {${NEW_LINE}reject(err)${NEW_LINE}} else {${NEW_LINE}resolve(result)${NEW_LINE}}${NEW_LINE}}${NEW_LINE}self.send_${key}(${argStrs})${NEW_LINE}})${NEW_LINE}} else {${NEW_LINE}this.reqs[this.id] = callback${NEW_LINE}this.send_${key}(${argStrs})${NEW_LINE}}${NEW_LINE}}`); 492 | codes.push(`${NEW_LINE}${NEW_LINE}send_${key} = (${argStrs}) => {${NEW_LINE}let output = new this.pClass(this.output)${NEW_LINE}output.writeMessageBegin('${key}', MessageType.CALL, this.id)${NEW_LINE}let args = new ${name}_${key}_args({${argStrs}})${NEW_LINE}args.write(output)${NEW_LINE}output.writeMessageEnd()${NEW_LINE}return this.output.flush()${NEW_LINE}}`); 493 | if (func["oneway"] !== true) { 494 | codes.push(`${NEW_LINE}${NEW_LINE}recv_${key} = (input, mtype, rseqid) => {${NEW_LINE}let callback = this.reqs[rseqid] || function () { }${NEW_LINE}delete this.reqs[rseqid]${NEW_LINE}if (mtype === MessageType.EXCEPTION) {${NEW_LINE}let x = new TApplicationException()${NEW_LINE}x.read(input)${NEW_LINE}input.readMessageEnd()${NEW_LINE}return callback(x)${NEW_LINE}}${NEW_LINE}let result = new ${name}_${key}_result()${NEW_LINE}result.read(input)${NEW_LINE}input.readMessageEnd()${NEW_LINE}`); 495 | let throws = func["throws"]; 496 | throws.forEach((item, index) => { 497 | codes.push(`${NEW_LINE}if (null !== result.${item["name"]}) {${NEW_LINE}throw result.${item["name"]}${NEW_LINE}}`); 498 | }); 499 | codes.push(`${NEW_LINE}if (null !== result.success) {${NEW_LINE}return callback(null, result.success)${NEW_LINE}}${NEW_LINE}return callback('${key} failed: unknown result')${NEW_LINE}}`); 500 | } 501 | }); 502 | codes.push(`${NEW_LINE}}`); 503 | defaultExports.push(name + "Client"); 504 | return codes.join(""); 505 | }; 506 | const defaultExportsHandler = () => { 507 | let code = `${NEW_LINE_2}export default {${NEW_LINE}`; 508 | defaultExports.forEach((v, i) => { 509 | code += v; 510 | if (i < defaultExports.length - 1) { 511 | code += ","; 512 | } 513 | code += `${NEW_LINE}`; 514 | }); 515 | code += `}${NEW_LINE}`; 516 | return code; 517 | }; 518 | if (ast.include) { 519 | code += includesHandler(ast.include); 520 | } 521 | if (ast.const) { 522 | code += constsHandler(ast.const); 523 | } 524 | if (ast.enum) { 525 | code += enumsHandler(ast.enum); 526 | } 527 | if (ast.exception) { 528 | code += exceptionsHandler(ast.exception); 529 | } 530 | if (ast.struct) { 531 | code += structsHandler(ast.struct); 532 | } 533 | if (ast.union) { 534 | code += structsLikeHandler(ast.union); 535 | } 536 | if (ast.service) { 537 | code += servicesHandler(ast.service); 538 | } 539 | if (ast.service) { 540 | code += defaultExportsHandler(); 541 | } 542 | return prettier.format(code, prettier_config_1.default); 543 | }; 544 | exports.clientsIndex = clientsDict => { 545 | let codes = [ 546 | `/**${NEW_LINE} * This file is auto-generated by Thrift2Ts.${NEW_LINE} *${NEW_LINE} * ${new Date().toString()}${NEW_LINE} */${NEW_LINE_2}` 547 | ]; 548 | const files = Object.keys(clientsDict); 549 | if (files.length < 1) { 550 | return codes.join(""); 551 | } 552 | let allClients = []; 553 | files.forEach(file => { 554 | let clients = clientsDict[file]; 555 | if (clients.length > 0) { 556 | allClients = allClients.concat(clients); 557 | codes.push(`import { ${clients.map(x => x + "Client")} } from "./${file}"${NEW_LINE}`); 558 | } 559 | }); 560 | codes.push(`${NEW_LINE_2}export default {${NEW_LINE}${allClients 561 | .map(x => x + ":" + x + "Client") 562 | .join(`,${NEW_LINE}`)}${NEW_LINE}}${NEW_LINE}`); 563 | return prettier.format(codes.join(""), prettier_config_1.default); 564 | }; 565 | -------------------------------------------------------------------------------- /src/genAst.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { ThriftSyntaxError } from "./error"; 4 | 5 | // Thrift keywords 6 | // 1. Basis 7 | // bool 8 | // byte 9 | // i16 10 | // i32 11 | // i64 12 | // double 13 | // string 14 | // 2. Specials 15 | // binary 16 | // 3. Struct 17 | // struct 18 | // 4. Containers 19 | // list 20 | // set 21 | // map 22 | // 5. Enum 23 | // enum 24 | // 6. Constant & Type Definition 25 | // const 26 | // typedef 27 | // 7. Exception 28 | // exception 29 | // 8. Service 30 | // service 31 | // oneway 32 | // 9. Namespace 33 | // namespace 34 | // 10. Include 35 | // include 36 | // 11. Others 37 | // void 38 | // required 39 | // optional 40 | // union 41 | // extends 42 | 43 | // Valid chars 44 | // a-z A-Z 0-9 _ : , ; ' " { } [ ] ( ) 45 | 46 | // Interfaces 47 | interface ISubjectBlock { 48 | subject: string; 49 | type: IDataType; 50 | name: string; 51 | value?: any; 52 | headComment?: string; 53 | tailComment?: string; 54 | } 55 | 56 | // Enum/Struct/Union/Exception 57 | interface IListSubjectBlock extends ISubjectBlock { 58 | items: IListSubjectItem[]; 59 | } 60 | 61 | interface IListSubjectItem { 62 | id?: number; 63 | name: string; 64 | type: string | IDataType; 65 | value?: any; 66 | option?: string; 67 | headComment?: string; 68 | tailComment?: string; 69 | } 70 | 71 | // Service 72 | interface IServiceSubjectBlock extends ISubjectBlock { 73 | functions?: IServiceSubjectFunction[]; 74 | extends?: string; 75 | } 76 | 77 | interface IServiceSubjectFunction { 78 | type: string | IDataType; 79 | name: string; 80 | args: IServiceSubjectFunctionArg[]; 81 | throws: any[]; 82 | oneway: boolean; 83 | headComment?: string; 84 | tailComment?: string; 85 | } 86 | 87 | interface IServiceSubjectFunctionArg { 88 | index: number; 89 | type: string | IDataType; 90 | name: string; 91 | } 92 | 93 | // Types 94 | // e.g map list set i32 string.. 95 | interface IDataType { 96 | name: string; 97 | keyType?: string; // for map 98 | valueType?: string; // for map set list 99 | } 100 | 101 | let simplifyDataType = (type: IDataType): string | IDataType => { 102 | switch (type.name.toLowerCase()) { 103 | case "map": 104 | //return `map<${type.keyType}, ${type.valueType}>`; 105 | case "list": 106 | //return `list<${type.valueType}>`; 107 | case "set": 108 | //return `set<${type.valueType}>`; 109 | return type; 110 | default: 111 | return type.name.toString(); 112 | } 113 | }; 114 | 115 | export default (code: string): object => { 116 | code = code.toString(); 117 | 118 | let nCount = 0; // count of \n 119 | let rCount = 0; // count of \r 120 | let offset = 0; 121 | let stack: { offset: number; nCount: number; rCount: number }[] = []; 122 | let tailCommentQueue: string[] = []; 123 | let headCommentQueue: string[] = []; 124 | 125 | const backup = (): void => { 126 | stack.push({ offset, nCount, rCount }); 127 | }; 128 | 129 | const restore = (): void => { 130 | let saveCase = stack[stack.length - 1]; 131 | offset = saveCase.offset; 132 | nCount = saveCase.nCount; 133 | rCount = saveCase.rCount; 134 | }; 135 | 136 | const drop = (): void => { 137 | stack.pop(); 138 | }; 139 | 140 | const getLineNum = (): number => { 141 | return Math.max(rCount, nCount) + 1; 142 | }; 143 | 144 | const throwError = (message: string): void => { 145 | let line = getLineNum(); 146 | message = `${message}\non Line ${line}`; 147 | let context = code.slice(offset, offset + 100); 148 | throw new ThriftSyntaxError(message, context, line); 149 | }; 150 | 151 | // record line 152 | // note \r \n \r\n 153 | // line = Max(rCount, nCount) + 1 154 | const recordLineNum = (char: string): void => { 155 | if (char === "\n") { 156 | nCount++; 157 | } else if (char === "\r") { 158 | rCount++; 159 | } 160 | }; 161 | 162 | // parse single line comment, start with # or //, stop utill line end 163 | const readSingleLineComment = (): boolean | string => { 164 | let i = 0; 165 | if ( 166 | ["#", "/"].indexOf(code[offset + i++]) < 0 || 167 | code[offset + i++] !== "/" 168 | ) { 169 | return false; 170 | } 171 | 172 | let comment = ""; 173 | while (code[offset] !== "\n" && code[offset] !== "\r") { 174 | comment += code[offset++]; 175 | } 176 | return comment; 177 | }; 178 | 179 | // parse multiple lines comment, start with /*, end with */ 180 | const readMultiLinesComment = (): boolean | string => { 181 | let i = 0; 182 | if (code[offset + i++] !== "/" || code[offset + i++] !== "*") { 183 | return false; 184 | } 185 | 186 | let comment = "/*"; 187 | do { 188 | while (offset + i < code.length && code[offset + i] !== "*") { 189 | recordLineNum(code[offset + i]); 190 | comment += code[offset + i++]; 191 | } 192 | comment += code[offset + i]; 193 | i++; 194 | } while (offset + i < code.length && code[offset + i] !== "/"); 195 | 196 | comment += "/"; 197 | offset += ++i; 198 | return comment; 199 | }; 200 | 201 | // read space to the end of current line or end of a `/* xx */` comment 202 | // to find a tail comment 203 | const readCurrentLineSpace = (): void => { 204 | while (offset < code.length) { 205 | let char = code[offset]; 206 | recordLineNum(char); 207 | if (char === "\n" || char === "\r") { 208 | offset++; 209 | break; 210 | } 211 | if (char === " " || char === "\t") { 212 | offset++; 213 | } else { 214 | // sometimes multiple comment was used as single line comment 215 | // e.g `/*comment1*/ //comment2` 216 | let comment1 = readMultiLinesComment(); 217 | if (comment1) { 218 | readCurrentLineSpace(); 219 | } 220 | let comment2 = readSingleLineComment(); 221 | if (!comment1 && !comment2) { 222 | break; 223 | } 224 | (comment1 || comment2) && 225 | tailCommentQueue.push((comment1 || comment2)); 226 | } 227 | } 228 | return; 229 | }; 230 | 231 | // read space from new line 232 | // to find head comments 233 | // this method should be used after `readCurrentLineSpace` 234 | const readNewLineSpace = (): void => { 235 | while (offset < code.length) { 236 | let char = code[offset]; 237 | recordLineNum(char); 238 | if ( 239 | char === "\n" || 240 | char === "\r" || 241 | char === " " || 242 | char === "\t" 243 | ) { 244 | offset++; 245 | } else { 246 | let comment = 247 | readMultiLinesComment() || readSingleLineComment(); 248 | comment && headCommentQueue.push(comment); 249 | if (!comment) { 250 | break; 251 | } 252 | } 253 | } 254 | return; 255 | }; 256 | 257 | const readSpace = (): void => { 258 | readCurrentLineSpace(); 259 | readNewLineSpace(); 260 | }; 261 | 262 | const readCommentFromQueue = (isTail = false): string => { 263 | let queue = isTail ? tailCommentQueue : headCommentQueue; 264 | let comments = []; 265 | let comment: string; 266 | while ((comment = queue.shift())) { 267 | if (comment.startsWith("#")) { 268 | comment = "//" + comment.slice(1); 269 | } 270 | comments.push(comment); 271 | } 272 | return comments.join("\r\n"); 273 | }; 274 | 275 | const readUntilThrow = (transaction: () => void, key?: string): any => { 276 | let container: any = key ? {} : []; 277 | while (true) { 278 | try { 279 | backup(); 280 | let result = transaction(); 281 | key 282 | ? (container[result[key]] = result) 283 | : container.push(result); 284 | } catch (exception) { 285 | restore(); 286 | return container; 287 | } finally { 288 | drop(); 289 | } 290 | } 291 | }; 292 | 293 | const readKeyword = (word: string): string => { 294 | for (let i = 0; i < word.length; i++) { 295 | if (code[offset + i] !== word[i]) { 296 | let token = code.substr(offset, word.length); 297 | throw new Error( 298 | `Unexpected token ${token} (current call: readKeyword)` 299 | ); 300 | } 301 | } 302 | offset += word.length; 303 | readSpace(); 304 | return word; 305 | }; 306 | 307 | const readChar = (char: string): string => { 308 | if (code[offset] !== char) { 309 | throw new Error( 310 | `Unexpected char ${code[offset]} (current call: readChar)` 311 | ); 312 | } 313 | offset++; 314 | readSpace(); 315 | return char; 316 | }; 317 | 318 | const readComma = (): string => { 319 | let char = code[offset]; 320 | if (/[,|;]/.test(char)) { 321 | offset++; 322 | readSpace(); 323 | return char; 324 | } 325 | }; 326 | 327 | // read a subject block, e.g struct {} 328 | const readSubject = (): ISubjectBlock => { 329 | return readWith( 330 | readNamespace, 331 | readInclude, 332 | readTypedef, 333 | readConst, 334 | readEnum, 335 | readStruct, 336 | readUnion, 337 | readException, 338 | readService 339 | ); 340 | }; 341 | 342 | // subject readers 343 | // typedef/const/enum/struct/union/exception/service/namespace/include 344 | 345 | const readTypedef = (): ISubjectBlock => { 346 | let subject = readKeyword("typedef"); 347 | let type = readType(); 348 | let name = readName(); 349 | readComma(); 350 | let headComment = readCommentFromQueue(); 351 | let tailComment = readCommentFromQueue(true); 352 | return { subject, type, name, headComment, tailComment }; 353 | }; 354 | 355 | const readConst = (): ISubjectBlock => { 356 | let subject = readKeyword("const"); 357 | let type = readType(); 358 | let name = readName(); 359 | let value = readAssign(); 360 | readComma(); 361 | let headComment = readCommentFromQueue(); 362 | let tailComment = readCommentFromQueue(true); 363 | return { subject, type, name, value, headComment, tailComment }; 364 | }; 365 | 366 | const readEnum = (): IListSubjectBlock => { 367 | let subject = readKeyword("enum"); 368 | let type = { 369 | name: "enum" 370 | }; 371 | let name = readName(); 372 | let headComment = readCommentFromQueue(); 373 | let tailComment = readCommentFromQueue(true); 374 | let items = readEnumBlock(); 375 | return { subject, type, name, items, headComment, tailComment }; 376 | }; 377 | 378 | const readEnumBlock = (): IListSubjectItem[] => { 379 | readChar("{"); 380 | let items = readUntilThrow(readEnumItem); 381 | readChar("}"); 382 | return items; 383 | }; 384 | 385 | const readEnumItem = (): IListSubjectItem => { 386 | let name = readName(); 387 | let type = "enum"; 388 | let value = readAssign(); 389 | readComma(); 390 | let headComment = readCommentFromQueue(); 391 | let tailComment = readCommentFromQueue(true); 392 | return { name, type, value, headComment, tailComment }; 393 | }; 394 | 395 | const readStruct = (): IListSubjectBlock => { 396 | let subject = readKeyword("struct"); 397 | let type = { 398 | name: "struct" 399 | }; 400 | let name = readName(); 401 | let headComment = readCommentFromQueue(); 402 | let tailComment = readCommentFromQueue(true); 403 | let items = readStructLikeBlock(); 404 | return { subject, type, name, items, headComment, tailComment }; 405 | }; 406 | 407 | const readStructLikeBlock = (): IListSubjectItem[] => { 408 | readChar("{"); 409 | let result = readUntilThrow(readStructLikeItem); 410 | readChar("}"); 411 | return result; 412 | }; 413 | 414 | const readStructLikeItem = (): IListSubjectItem => { 415 | let id; 416 | // struct MyStruct { 417 | // 1: required int id, 418 | // 2: required bool name 419 | // } 420 | try { 421 | id = readNumValue(); 422 | readChar(":"); 423 | } catch (exception) {} 424 | 425 | let option = readWith( 426 | readKeyword.bind(this, "required"), 427 | readKeyword.bind(this, "optional"), 428 | () => {} 429 | ); 430 | let type = simplifyDataType(readType()); 431 | let name = readName(); 432 | let value = readAssign(); 433 | readComma(); 434 | let headComment = readCommentFromQueue(); 435 | let tailComment = readCommentFromQueue(true); 436 | let result: IListSubjectItem = { 437 | id, 438 | type, 439 | name, 440 | headComment, 441 | tailComment 442 | }; 443 | if (option !== undefined) { 444 | result.option = option; 445 | } 446 | if (value !== undefined) { 447 | result.value = value; 448 | } 449 | return result; 450 | }; 451 | 452 | const readUnion = (): IListSubjectBlock => { 453 | let subject = readKeyword("union"); 454 | let type = { 455 | name: "union" 456 | }; 457 | let name = readName(); 458 | let headComment = readCommentFromQueue(); 459 | let tailComment = readCommentFromQueue(true); 460 | let items = readStructLikeBlock(); 461 | return { subject, type, name, items, headComment, tailComment }; 462 | }; 463 | 464 | const readException = (): IListSubjectBlock => { 465 | let subject = readKeyword("exception"); 466 | let type = { 467 | name: "exception" 468 | }; 469 | let name = readName(); 470 | let headComment = readCommentFromQueue(); 471 | let tailComment = readCommentFromQueue(true); 472 | let items = readStructLikeBlock(); 473 | return { subject, type, name, items, headComment, tailComment }; 474 | }; 475 | 476 | const readExtends = (): string => { 477 | try { 478 | backup(); 479 | readKeyword("extends"); 480 | let name = readRefValue().join("."); 481 | return name; 482 | } catch (exception) { 483 | restore(); 484 | return; 485 | } finally { 486 | drop(); 487 | } 488 | }; 489 | 490 | const readService = (): IServiceSubjectBlock => { 491 | let subject = readKeyword("service"); 492 | let type = { 493 | name: "service" 494 | }; 495 | let name = readName(); 496 | let extend = readExtends(); 497 | let headComment = readCommentFromQueue(); 498 | let tailComment = readCommentFromQueue(true); 499 | let functions = readServiceBlock(); 500 | let result: IServiceSubjectBlock = { 501 | subject, 502 | type, 503 | name, 504 | headComment, 505 | tailComment 506 | }; 507 | if (extend !== undefined) { 508 | result.extends = extend; 509 | } 510 | if (functions !== undefined) { 511 | result.functions = functions; 512 | } 513 | return result; 514 | }; 515 | 516 | const readServiceBlock = (): IServiceSubjectFunction[] => { 517 | readChar("{"); 518 | let result = readUntilThrow(readServiceItem, "name"); 519 | readChar("}"); 520 | return result; 521 | }; 522 | 523 | const readServiceItem = (): IServiceSubjectFunction => { 524 | let oneway = !!readWith(readOneway, () => {}); 525 | let type = simplifyDataType(readType()); // function return type 526 | let name = readName(); 527 | let headComment = readCommentFromQueue(); 528 | let args = readServiceArgs(); 529 | let tailComment = readCommentFromQueue(true); 530 | let throws = readServiceThrow(); 531 | readComma(); 532 | return { type, name, args, throws, oneway, headComment, tailComment }; 533 | }; 534 | 535 | const readServiceArgs = (): any[] => { 536 | readChar("("); 537 | let result = readUntilThrow(readStructLikeItem); 538 | readChar(")"); 539 | readSpace(); 540 | return result; 541 | }; 542 | 543 | const readServiceThrow = (): any[] => { 544 | try { 545 | backup(); 546 | readKeyword("throws"); 547 | return readServiceArgs(); 548 | } catch (exception) { 549 | restore(); 550 | return []; 551 | } finally { 552 | drop(); 553 | } 554 | }; 555 | 556 | const readNamespace = (): ISubjectBlock => { 557 | // e.g -> namespace java com.company.service 558 | let subject = readKeyword("namespace"); 559 | let type = { 560 | name: "namespace" 561 | }; 562 | let name; 563 | // now read the `java` in sample 564 | let i = 0; 565 | let char = code[offset]; 566 | while (/[a-zA-Z0-9_\*]/.test(char)) { 567 | if (offset + ++i >= code.length) { 568 | throw new Error("Unexpected end (current call: readNamespace)"); 569 | } 570 | char = code[offset + i]; 571 | } 572 | if (i === 0) { 573 | throw new Error("Unexpected token (current call: readNamespace)"); 574 | } 575 | name = code.slice(offset, (offset += i)); 576 | readSpace(); 577 | // read `com.company.service` in sample 578 | let serviceName = readRefValue().join("."); 579 | let headComment = readCommentFromQueue(); 580 | let tailComment = readCommentFromQueue(true); 581 | return { 582 | subject, 583 | type, 584 | name, 585 | value: serviceName, 586 | headComment, 587 | tailComment 588 | }; 589 | }; 590 | 591 | const readInclude = (): ISubjectBlock => { 592 | let subject = readKeyword("include"); 593 | let type = { 594 | name: "include" 595 | }; 596 | readSpace(); 597 | let includePath = readQuotation(); 598 | let name = includePath.replace(/^.*?([^/\\]*?)(?:\.thrift)?$/, "$1"); 599 | readSpace(); 600 | let headComment = readCommentFromQueue(); 601 | let tailComment = readCommentFromQueue(true); 602 | return { 603 | subject, 604 | type, 605 | name, 606 | value: includePath, 607 | headComment, 608 | tailComment 609 | }; 610 | }; 611 | 612 | const readQuotation = (): string => { 613 | if (code[offset] === '"' || code[offset] === "'") { 614 | offset++; 615 | } else { 616 | throw new Error("Unexpected token (current call: readQuotation)"); 617 | } 618 | 619 | let end = offset; 620 | while (code[end] !== '"' && code[end] !== "'" && end < code.length) { 621 | end++; 622 | } 623 | if (code[end] === '"' || code[end] === "'") { 624 | let quote = code.slice(offset, end); 625 | offset = end + 1; 626 | return quote; 627 | } else { 628 | throw new Error("Unexpected token (current call: readQuotation)"); 629 | } 630 | }; 631 | 632 | const readOneway = (): string => { 633 | return readKeyword("oneway"); 634 | }; 635 | 636 | // subject item readers 637 | // e.g Enum item, Struct item 638 | 639 | const readType = (): IDataType => { 640 | return readWith(readMapType, readSetOrListType, readNormalType); 641 | }; 642 | 643 | const readMapType = (): IDataType => { 644 | let name = readName(); // map 645 | readChar("<"); 646 | let keyType = simplifyDataType(readType()) as string; 647 | readComma(); 648 | let valueType = simplifyDataType(readType()) as string; 649 | readChar(">"); 650 | return { name, keyType, valueType }; 651 | }; 652 | 653 | const readSetOrListType = (): IDataType => { 654 | let name = readName(); // list/set 655 | readChar("<"); 656 | let valueType = simplifyDataType(readType()) as string; 657 | readChar(">"); 658 | return { name, valueType }; 659 | }; 660 | 661 | const readNormalType = (): IDataType => { 662 | let name = readName(); 663 | return { name }; 664 | }; 665 | 666 | const readValue = (): any => { 667 | return readWith( 668 | readHexadecimalValue, 669 | readEnotationValue, 670 | readNumValue, 671 | readBoolValue, 672 | readStringValue, 673 | readListOrSetValue, 674 | readMapValue, 675 | readRefValue 676 | ); 677 | }; 678 | 679 | const readNumValue = (): number => { 680 | let value = []; 681 | if (code[offset] === "-") { 682 | value.push("-"); 683 | offset++; 684 | } 685 | 686 | while (true) { 687 | let char = code[offset]; 688 | if (/[0-9\.]/.test(char)) { 689 | offset++; 690 | value.push(char); 691 | } else if (value.length) { 692 | readSpace(); 693 | return +value.join(""); 694 | } else { 695 | throw new Error( 696 | `Unexpected token ${char} (current call: readNumValue)` 697 | ); 698 | } 699 | } 700 | }; 701 | 702 | const readBoolValue = (): boolean => { 703 | return JSON.parse( 704 | readWith( 705 | readKeyword.bind(this, "true"), 706 | readKeyword.bind(this, "false") 707 | ) 708 | ); 709 | }; 710 | 711 | const readStringValue = (): string => { 712 | let value = []; 713 | let quote; 714 | while (true) { 715 | let char = code[offset++]; 716 | if (!value.length) { 717 | if (char !== "'" && char !== '"') { 718 | throw new Error( 719 | "Unexpected token (current call: readStringValue)" 720 | ); 721 | } else { 722 | quote = char; 723 | value.push(char); 724 | } 725 | } else { 726 | if (char === "\\") { 727 | value.push(char); 728 | value.push(code[offset++]); 729 | } else if (char === quote) { 730 | value.push(char); 731 | readSpace(); 732 | return new Function("return " + value.join(""))(); 733 | } else { 734 | value.push(char); 735 | } 736 | } 737 | } 738 | }; 739 | 740 | const readListOrSetValue = (): any[] => { 741 | readChar("["); 742 | let list = readUntilThrow((): any => { 743 | let value = readValue(); 744 | readComma(); 745 | return value; 746 | }); 747 | readChar("]"); 748 | return list; 749 | }; 750 | 751 | const readMapValue = (): object => { 752 | readChar("{"); 753 | let map = readUntilThrow((): { key: string; value: any } => { 754 | let key = readValue(); 755 | readChar(":"); 756 | let value = readValue(); 757 | readComma(); 758 | return { key, value }; 759 | }); 760 | readChar("}"); 761 | return map; 762 | }; 763 | 764 | // e.g read `com.company.service` in `namespace go com.company.service` 765 | const readRefValue = (): string[] => { 766 | let list = [readName()]; 767 | let others = readUntilThrow((): string => { 768 | readChar("."); 769 | return readName(); 770 | }); 771 | return list.concat(others); 772 | }; 773 | 774 | // e.g read -1.0e6 2.1e-1 775 | const readEnotationValue = (): number => { 776 | let value = []; 777 | if (code[offset] === "-") { 778 | value.push("-"); 779 | offset++; 780 | } 781 | 782 | while (true) { 783 | let char = code[offset]; 784 | if (/[0-9\.]/.test(char)) { 785 | value.push(char); 786 | offset++; 787 | } else { 788 | break; 789 | } 790 | } 791 | 792 | if (code[offset] !== "e" && code[offset] !== "E") { 793 | throw new Error( 794 | "Unexpected token (current call: readEnotationValue)" 795 | ); 796 | } 797 | value.push(code[offset++]); 798 | 799 | while (true && offset < code.length) { 800 | let char = code[offset]; 801 | if (/[0-9]/.test(char)) { 802 | offset++; 803 | value.push(char); 804 | } else { 805 | if (value.length) { 806 | readSpace(); 807 | return +value.join(""); 808 | } else { 809 | throw new Error( 810 | `Unexpect token ${char} (current call: readEnotationValue)` 811 | ); 812 | } 813 | } 814 | } 815 | }; 816 | 817 | // e.g 0x0000ff 818 | const readHexadecimalValue = (): number => { 819 | let value = []; 820 | if (code[offset] === "-") { 821 | value.push(code[offset++]); 822 | } 823 | 824 | if (code[offset] !== "0") { 825 | throw new Error( 826 | `Unexpected token ${code[ 827 | offset 828 | ]} (current call: readHexadecimalValue)` 829 | ); 830 | } 831 | value.push(code[offset++]); 832 | 833 | while (true) { 834 | let char = code[offset]; 835 | if (/[0-9a-zA-Z]/.test(char)) { 836 | offset++; 837 | value.push(char); 838 | } else { 839 | if (value.length) { 840 | readSpace(); 841 | return +value.join(""); 842 | } else { 843 | throw new Error( 844 | `Unexpected token ${char} (current call: readHexadecimalValue)` 845 | ); 846 | } 847 | } 848 | } 849 | }; 850 | 851 | const readName = (): string => { 852 | let i = 0; 853 | let char = code[offset]; 854 | while (/[a-zA-Z0-9_\.]/.test(char)) { 855 | char = code[offset + ++i]; 856 | } 857 | 858 | if (i === 0) { 859 | throw new Error("Invalid name string (current call: readName)"); 860 | } 861 | 862 | let value = code.slice(offset, (offset += i)); 863 | readSpace(); 864 | return value; 865 | }; 866 | 867 | const readAssign = (): any => { 868 | try { 869 | backup(); 870 | readChar("="); 871 | return readValue(); 872 | } catch (exception) { 873 | restore(); 874 | } finally { 875 | drop(); 876 | } 877 | }; 878 | 879 | // read specified content in the code 880 | const readWith = (...readers): any => { 881 | backup(); 882 | for (let i = 0; i < readers.length; i++) { 883 | try { 884 | let result = readers[i](); 885 | drop(); 886 | return result; 887 | } catch (exception) { 888 | restore(); 889 | continue; 890 | } 891 | } 892 | drop(); 893 | throw new Error("Unexcepted Token (current call: readWith)"); 894 | }; 895 | 896 | // main function 897 | const parseThrift = (): object => { 898 | readSpace(); 899 | let ast = {}; 900 | while (true) { 901 | try { 902 | let block = readSubject(); 903 | let { subject, name } = block; 904 | if (!ast[subject]) { 905 | ast[subject] = {}; 906 | } 907 | 908 | delete block.subject; 909 | delete block.name; 910 | switch (subject) { 911 | case "exception": 912 | case "struct": 913 | case "union": 914 | ast[subject][name] = block["items"]; 915 | break; 916 | default: 917 | ast[subject][name] = block; 918 | } 919 | } catch (exception) { 920 | throwError(exception); 921 | } finally { 922 | if (code.length === offset) break; 923 | } 924 | } 925 | return ast; 926 | }; 927 | 928 | return parseThrift(); 929 | }; 930 | -------------------------------------------------------------------------------- /src/genServiceClient.ts: -------------------------------------------------------------------------------- 1 | import { thrift2TsPath, getThriftFileName } from "./helpers"; 2 | import prettierConfig from "./prettier-config"; 3 | const prettier = require("prettier"); 4 | 5 | const thriftTypeMapper = { 6 | byte: "ThriftType.BYTE", 7 | i16: "ThriftType.I16", 8 | i32: "ThriftType.I32", 9 | i64: "ThriftType.I64", 10 | double: "ThriftType.DOUBLE", 11 | string: "ThriftType.STRING", 12 | list: "ThriftType.LIST", 13 | set: "ThriftType.SET", 14 | map: "ThriftType.MAP", 15 | struct: "ThriftType.STRUCT", 16 | bool: "ThriftType.BOOL", 17 | void: "ThriftType.VOID", 18 | stop: "ThriftType.STOP" 19 | }; 20 | 21 | const fieldReaderMapper = { 22 | byte: "readByte()", 23 | i16: "readI16()", 24 | i32: "readI32()", 25 | i64: "readI64()", 26 | double: "readDouble()", 27 | string: "readString()", 28 | bool: "readBool()" 29 | }; 30 | 31 | const fieldWriterMapper = { 32 | byte: "writeByte", 33 | i16: "writeI16", 34 | i32: "writeI32", 35 | i64: "writeI64", 36 | double: "writeDouble", 37 | string: "writeString", 38 | bool: "writeBool" 39 | }; 40 | 41 | const NEW_LINE = "\r\n"; 42 | const NEW_LINE_2 = "\r\n\r\n"; 43 | 44 | let header = `/**${NEW_LINE} * This service client is auto-generated by Thrift2Ts.${NEW_LINE} *${NEW_LINE} * ${new Date().toString()}${NEW_LINE} */${NEW_LINE_2}`; 45 | 46 | // import TException from browser-thrift package 47 | header += `import Thrift from "browser-thrift2"${NEW_LINE}`; 48 | header += `import IProtocol, { ProtocolClass } from "browser-thrift2/src/thrift/interface/IProtocol"${NEW_LINE}`; 49 | header += `import ITransport, { TransportClass } from "browser-thrift2/src/thrift/interface/ITransport"${NEW_LINE_2}`; 50 | header += `const {${NEW_LINE}ThriftType,${NEW_LINE}MessageType,${NEW_LINE}TApplicationException,${NEW_LINE}TException${NEW_LINE}} = Thrift${NEW_LINE_2}`; 51 | 52 | export default (ast: any): string => { 53 | let code = ""; 54 | 55 | code += header; 56 | 57 | const defaultExports = []; 58 | 59 | const simplifyType = (type): string | object => { 60 | if (typeof type === "string") { 61 | return type; 62 | } 63 | 64 | switch (type.name.toLowerCase()) { 65 | case "map": 66 | case "list": 67 | case "set": 68 | return type; 69 | default: 70 | return type.name.toString(); 71 | } 72 | }; 73 | 74 | const mapThriftType = type => { 75 | if (typeof type === "object") { 76 | return type.name; 77 | } 78 | 79 | if (Object.keys(thriftTypeMapper).indexOf(type) > -1) { 80 | return type; 81 | } 82 | 83 | // maybe a custom typedef 84 | if (ast["typedef"] && Object.keys(ast["typedef"]).indexOf(type) > -1) { 85 | return ast["typedef"][type]["type"]["name"]; 86 | } 87 | 88 | // maybe a custom enum 89 | if (ast["enum"] && Object.keys(ast["enum"]).indexOf(type) > -1) { 90 | return "i32"; 91 | } 92 | 93 | // maybe a custom map 94 | if (ast["map"] && Object.keys(ast["map"]).indexOf(type) > -1) { 95 | return "map"; 96 | } 97 | 98 | // maybe a custom list 99 | if (ast["list"] && Object.keys(ast["list"]).indexOf(type) > -1) { 100 | return "list"; 101 | } 102 | 103 | // maybe a custom set 104 | if (ast["set"] && Object.keys(ast["set"]).indexOf(type) > -1) { 105 | return "set"; 106 | } 107 | 108 | // maybe a custom struct 109 | if (ast["struct"] && Object.keys(ast["struct"]).indexOf(type) > -1) { 110 | return "struct"; 111 | } 112 | 113 | // maybe a custom exception, recognize as struct 114 | if ( 115 | ast["exception"] && 116 | Object.keys(ast["exception"]).indexOf(type) > -1 117 | ) { 118 | return "struct"; 119 | } 120 | 121 | throw new Error(`Unknown thrift type: ${type}`); 122 | }; 123 | 124 | const getThriftTypeStr = type => { 125 | return thriftTypeMapper[mapThriftType(type)]; 126 | }; 127 | 128 | const getReaderStr = type => { 129 | return fieldReaderMapper[mapThriftType(type)]; 130 | }; 131 | 132 | const getWriterStr = type => { 133 | return fieldWriterMapper[mapThriftType(type)]; 134 | }; 135 | 136 | const valueTypeTransformer = (type): string => { 137 | type = simplifyType(type); 138 | 139 | if (typeof type === "string") { 140 | switch (type) { 141 | case "i16": 142 | case "i32": 143 | case "i64": 144 | case "double": 145 | return "number"; 146 | case "bool": 147 | return "boolean"; 148 | default: 149 | return type; 150 | } 151 | } 152 | 153 | switch (type["name"]) { 154 | case "map": 155 | return `{[key: ${type["keyType"]}]: ${valueTypeTransformer( 156 | type["valueType"] 157 | )}}`; 158 | case "list": 159 | case "set": 160 | return `${valueTypeTransformer(type["valueType"])}[]`; 161 | } 162 | throw new Error(`Unexpected value type: ${JSON.stringify(type)}`); 163 | }; 164 | 165 | const valueTransformer = (value, isMap = false): string => { 166 | if (typeof value === "string") { 167 | return `\"${value}\"`; 168 | } 169 | if (["number", "boolean"].indexOf(typeof value) > -1) { 170 | return value.toString(); 171 | } 172 | if (value instanceof Array) { 173 | if (isMap) { 174 | return `{${value.map(v => valueTransformer(v)).join(", ")}}`; 175 | } 176 | return `[${value.map(v => valueTransformer(v)).join(", ")}]`; 177 | } 178 | if ( 179 | typeof value === "object" && 180 | value["key"] !== undefined && 181 | value["value"] !== undefined 182 | ) { 183 | return `"${value["key"]}": ${valueTransformer(value["value"])}`; 184 | } 185 | throw new Error(`Unexpected value: ${JSON.stringify(value)}`); 186 | }; 187 | 188 | const includesHandler = (includes: object[]): string => { 189 | let imports = []; 190 | Object.keys(includes).map(key => includes[key]).forEach(include => { 191 | imports.push( 192 | `${NEW_LINE}import * as ${getThriftFileName( 193 | include.value 194 | )} from "${thrift2TsPath( 195 | include.value, 196 | false, 197 | false 198 | )}";${NEW_LINE}` 199 | ); 200 | }); 201 | return imports.join(""); 202 | }; 203 | 204 | const constsHandler = (consts: object[]): string => { 205 | let newConsts = []; 206 | Object.keys(consts).forEach(key => { 207 | newConsts.push( 208 | `${NEW_LINE}export const ${key}: ${valueTypeTransformer( 209 | consts[key]["type"] 210 | )} = ${valueTransformer( 211 | consts[key]["value"], 212 | typeof consts[key]["type"] === "object" && 213 | consts[key]["type"]["name"] === "map" 214 | )}; ${NEW_LINE}` 215 | ); 216 | }); 217 | return newConsts.join(""); 218 | }; 219 | 220 | const enumsHandler = (enums: object[]): string => { 221 | let newEnums = []; 222 | Object.keys(enums).forEach(key => { 223 | newEnums.push(enumHandler(key, enums[key]["items"])); 224 | }); 225 | return newEnums.join(""); 226 | }; 227 | 228 | const enumHandler = (name, items: object[]): string => { 229 | let lastValue = -1; 230 | let codes = [`${NEW_LINE}export enum ${name} {`]; 231 | items.forEach((item, index) => { 232 | if (item["value"] === undefined) { 233 | item["value"] = lastValue + 1; 234 | } 235 | lastValue = item["value"]; 236 | codes.push(`${NEW_LINE}${item["name"]} = ${item["value"]}`); 237 | if (index < items.length - 1) { 238 | codes.push(","); 239 | } 240 | }); 241 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 242 | 243 | return codes.join(""); 244 | }; 245 | 246 | // Exceptions 247 | const exceptionsHandler = (values: object[]): string => { 248 | let exceptions = []; 249 | Object.keys(values).forEach(key => { 250 | exceptions.push(exceptionHandler(key, values[key])); 251 | }); 252 | return exceptions.join(""); 253 | }; 254 | 255 | const exceptionHandler = (name, items: object[]): string => { 256 | let codes = []; 257 | codes.push(`${NEW_LINE}export class ${name} extends TException {`); 258 | items.forEach((item, index) => { 259 | codes.push(`${NEW_LINE}${item["name"]}`); 260 | if (item["option"] === "optional") { 261 | codes.push("?"); 262 | } 263 | codes.push(`: ${valueTypeTransformer(item["type"])};`); 264 | }); 265 | codes.push(`${NEW_LINE}name: string;`); 266 | 267 | // add constructor 268 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 269 | codes.push(`${NEW_LINE}super()`); 270 | codes.push(`${NEW_LINE}this.name = "${name}"`); 271 | items.forEach((item, index) => { 272 | let value = item["value"]; 273 | if (value !== undefined) { 274 | if (item["type"] === "string") { 275 | value = `"${value}"`; 276 | } 277 | codes.push(`${NEW_LINE}this.${item["name"]} = ${value}`); 278 | } else { 279 | codes.push(`${NEW_LINE}this.${item["name"]} = null`); 280 | } 281 | }); 282 | codes.push(`${NEW_LINE}if (args) {`); 283 | items.forEach((item, index) => { 284 | let paramName = item["name"]; 285 | codes.push( 286 | `${NEW_LINE}if (args.${paramName} !== undefined && args.${paramName} !== null) {${NEW_LINE}this.${paramName} = args.${paramName}${NEW_LINE}}` 287 | ); 288 | }); 289 | codes.push(`${NEW_LINE}}`); 290 | // close constructor 291 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 292 | 293 | // add read function 294 | codes.push( 295 | `${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}${NEW_LINE}switch (fid) {` 296 | ); 297 | items.forEach((item, index) => { 298 | let type = item["type"]; 299 | codes.push( 300 | `${NEW_LINE}case ${index + 301 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr( 302 | type 303 | )}) {${NEW_LINE}this.${item["name"]} = input.${getReaderStr( 304 | type 305 | )}${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break` 306 | ); 307 | }); 308 | codes.push( 309 | `${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}` 310 | ); 311 | 312 | // add write function 313 | codes.push( 314 | `${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')` 315 | ); 316 | items.forEach((item, index) => { 317 | let type = item["type"]; 318 | codes.push( 319 | `${NEW_LINE}if (this.${item["name"]} !== null && this.${item[ 320 | "name" 321 | ]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item[ 322 | "name" 323 | ]}', ${getThriftTypeStr(type)}, ${index + 324 | 1})${NEW_LINE}output.${getWriterStr(type)}(this.${item[ 325 | "name" 326 | ]})${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}` 327 | ); 328 | }); 329 | codes.push( 330 | `${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}` 331 | ); 332 | 333 | // close block 334 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 335 | 336 | return codes.join(""); 337 | }; 338 | 339 | // Structs 340 | const structsHandler = (values: object[]): string => { 341 | let structs = []; 342 | Object.keys(values).forEach(key => { 343 | structs.push(structHandler(key, values[key])); 344 | }); 345 | return structs.join(""); 346 | }; 347 | 348 | const structHandler = (name, items: object[]): string => { 349 | let codes = []; 350 | codes.push(`${NEW_LINE}export class ${name} {`); 351 | items.forEach((item, index) => { 352 | codes.push(`${NEW_LINE}${item["name"]}`); 353 | if (item["option"] === "optional") { 354 | codes.push("?"); 355 | } 356 | codes.push(`: ${valueTypeTransformer(item["type"])};`); 357 | }); 358 | codes.push(`${NEW_LINE}name: string;`); 359 | 360 | // add constructor 361 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 362 | codes.push(`${NEW_LINE}this.name = "${name}"`); 363 | items.forEach((item, index) => { 364 | let value = item["value"]; 365 | if (value !== undefined) { 366 | if (item["type"] === "string") { 367 | value = `"${value}"`; 368 | } 369 | codes.push(`${NEW_LINE}this.${item["name"]} = ${value}`); 370 | } else { 371 | codes.push(`${NEW_LINE}this.${item["name"]} = null`); 372 | } 373 | }); 374 | codes.push(`${NEW_LINE}if (args) {`); 375 | items.forEach((item, index) => { 376 | let paramName = item["name"]; 377 | codes.push( 378 | `${NEW_LINE}if (args.${paramName} !== undefined && args.${paramName} !== null) {${NEW_LINE}this.${paramName} = args.${paramName}${NEW_LINE}}` 379 | ); 380 | }); 381 | codes.push(`${NEW_LINE}}`); 382 | // close constructor 383 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 384 | 385 | // add read function 386 | codes.push( 387 | `${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}${NEW_LINE}switch (fid) {` 388 | ); 389 | items.forEach((item, index) => { 390 | let type = item["type"]; 391 | codes.push( 392 | `${NEW_LINE}case ${index + 393 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr( 394 | type 395 | )}) {${NEW_LINE}this.${item["name"]} = input.${getReaderStr( 396 | type 397 | )}${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break` 398 | ); 399 | }); 400 | codes.push( 401 | `${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}` 402 | ); 403 | 404 | // add write function 405 | codes.push( 406 | `${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')` 407 | ); 408 | items.forEach((item, index) => { 409 | let type = item["type"]; 410 | codes.push( 411 | `${NEW_LINE}if (this.${item["name"]} !== null && this.${item[ 412 | "name" 413 | ]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item[ 414 | "name" 415 | ]}', ${getThriftTypeStr(type)}, ${index + 416 | 1})${NEW_LINE}output.${getWriterStr(type)}(this.${item[ 417 | "name" 418 | ]})${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}` 419 | ); 420 | }); 421 | codes.push( 422 | `${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}` 423 | ); 424 | 425 | // close block 426 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 427 | 428 | return codes.join(""); 429 | }; 430 | 431 | const structsLikeHandler = (values: object[]): string => { 432 | let interfaces = []; 433 | Object.keys(values).forEach(key => { 434 | interfaces.push(structLikeHandler(key, values[key])); 435 | }); 436 | return interfaces.join(""); 437 | }; 438 | 439 | const structLikeHandler = (name, items: object[]): string => { 440 | let codes = [`${NEW_LINE}export interface ${name} {`]; 441 | items.forEach((item, index) => { 442 | codes.push(`${NEW_LINE}${item["name"]}`); 443 | if (item["option"] === "optional") { 444 | codes.push("?"); 445 | } 446 | codes.push(`: ${valueTypeTransformer(item["type"])};`); 447 | }); 448 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 449 | 450 | return codes.join(""); 451 | }; 452 | 453 | const servicesHandler = (services: object[]): string => { 454 | let codes = []; 455 | Object.keys(services).forEach(key => { 456 | codes.push(serviceArgsndResultsHandler(key, services[key])); 457 | codes.push(serviceHandler(key, services[key])); 458 | }); 459 | return codes.join(""); 460 | }; 461 | 462 | const serviceArgsndResultsHandler = (name, service): string => { 463 | let codes = []; 464 | let functions = service["functions"]; 465 | 466 | // function blocks 467 | Object.keys(functions).forEach(key => { 468 | let func = functions[key]; 469 | // args 470 | codes.push(serviceArgHandler(name, func)); 471 | 472 | // result 473 | codes.push(serviceResultHandler(name, func)); 474 | }); 475 | 476 | return codes.join(""); 477 | }; 478 | 479 | const serviceArgHandler = (service, func): string => { 480 | let codes = []; 481 | let args = func["args"]; 482 | let name = `${service}_${func["name"]}_args`; 483 | 484 | codes.push(`${NEW_LINE}export class ${name} {`); 485 | 486 | args.forEach((arg, index) => { 487 | codes.push(`${NEW_LINE}${arg["name"]}`); 488 | if (arg["option"] === "optional") { 489 | codes.push("?"); 490 | } 491 | codes.push(`: ${valueTypeTransformer(arg["type"])};`); 492 | }); 493 | codes.push(`${NEW_LINE}name: string;`); 494 | 495 | // add constructor 496 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 497 | codes.push(`${NEW_LINE}this.name = "${name}"`); 498 | args.forEach((arg, index) => { 499 | let value = arg["value"]; 500 | if (value !== undefined) { 501 | if (arg["type"] === "string") { 502 | value = `"${value}"`; 503 | } 504 | codes.push(`${NEW_LINE}this.${arg["name"]} = ${value}`); 505 | } else { 506 | codes.push(`${NEW_LINE}this.${arg["name"]} = null`); 507 | } 508 | }); 509 | 510 | if (args && args.length) { 511 | codes.push(`${NEW_LINE}if (args) {`); 512 | args.forEach((item, index) => { 513 | let paramName = item["name"]; 514 | let value = `args.${paramName}`; 515 | 516 | if (mapThriftType(item["type"]) === "struct") { 517 | // TODO check more 518 | value = `new ${item["type"]}(${value})`; 519 | } 520 | codes.push( 521 | `${NEW_LINE}if (args.${paramName} !== undefined && args.${paramName} !== null) {${NEW_LINE}this.${paramName} = ${value}${NEW_LINE}}` 522 | ); 523 | }); 524 | codes.push(`${NEW_LINE}}`); 525 | } 526 | 527 | // close constructor 528 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 529 | 530 | // add read function 531 | codes.push( 532 | `${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}` 533 | ); 534 | 535 | if (args && args.length) { 536 | codes.push(`${NEW_LINE}switch (fid) {`); 537 | args.forEach((item, index) => { 538 | let type = item["type"]; 539 | codes.push( 540 | `${NEW_LINE}case ${index + 541 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr( 542 | type 543 | )}) {${NEW_LINE}` 544 | ); 545 | if (mapThriftType(type) === "struct") { 546 | codes.push( 547 | `this.${item[ 548 | "name" 549 | ]} = new ${type}()${NEW_LINE}this.${item[ 550 | "name" 551 | ]}.read(input)` 552 | ); 553 | } else { 554 | codes.push( 555 | `this.${item["name"]} = input.${getReaderStr(type)}` 556 | ); 557 | } 558 | 559 | codes.push( 560 | `${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break` 561 | ); 562 | }); 563 | codes.push( 564 | `${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}` 565 | ); 566 | } else { 567 | codes.push(`${NEW_LINE}input.skip(ftype)`); 568 | } 569 | 570 | codes.push( 571 | `${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}` 572 | ); 573 | 574 | // add write function 575 | codes.push( 576 | `${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')` 577 | ); 578 | args.forEach((item, index) => { 579 | let type = item["type"]; 580 | codes.push( 581 | `${NEW_LINE}if (this.${item["name"]} !== null && this.${item[ 582 | "name" 583 | ]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item[ 584 | "name" 585 | ]}', ${getThriftTypeStr(type)}, ${index + 1})${NEW_LINE}` 586 | ); 587 | 588 | if (mapThriftType(type) === "struct") { 589 | codes.push(`this.${item["name"]}.write(output)`); 590 | } else { 591 | codes.push( 592 | `output.${getWriterStr(type)}(this.${item["name"]})` 593 | ); 594 | } 595 | codes.push(`${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}`); 596 | }); 597 | codes.push( 598 | `${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}` 599 | ); 600 | 601 | // close block 602 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 603 | 604 | return codes.join(""); 605 | }; 606 | 607 | const serviceResultHandler = (service, func): string => { 608 | let codes = []; 609 | let throws = func["throws"]; 610 | let name = `${service}_${func["name"]}_result`; 611 | 612 | codes.push(`${NEW_LINE}export class ${name} {`); 613 | 614 | codes.push(`${NEW_LINE}name: string;`); 615 | codes.push(`${NEW_LINE}success: any;`); 616 | if (throws && throws.length) { 617 | throws.forEach((item, index) => { 618 | codes.push(`${NEW_LINE}${item["name"]}: ${item["type"]};`); 619 | }); 620 | } 621 | 622 | // add constructor 623 | codes.push(`${NEW_LINE}${NEW_LINE}constructor (args?) {`); 624 | codes.push(`${NEW_LINE}this.name = "${name}"`); 625 | if (func["type"] !== "void") { 626 | codes.push(`${NEW_LINE}this.success = null`); 627 | throws.forEach((item, index) => { 628 | codes.push(`${NEW_LINE}this.${item["name"]} = null`); 629 | }); 630 | throws.forEach((item, index) => { 631 | codes.push( 632 | `${NEW_LINE}if (args instanceof ${item[ 633 | "type" 634 | ]}) {${NEW_LINE}this.${item[ 635 | "name" 636 | ]} = args${NEW_LINE}return${NEW_LINE}}` 637 | ); 638 | }); 639 | codes.push(`${NEW_LINE}if (args) {`); 640 | codes.push( 641 | `${NEW_LINE}if (args.success !== undefined && args.success !== null) {${NEW_LINE}this.success = args.success${NEW_LINE}}` 642 | ); 643 | throws.forEach((item, index) => { 644 | codes.push( 645 | `${NEW_LINE}if (args.${item[ 646 | "name" 647 | ]} !== undefined && args.${item[ 648 | "name" 649 | ]} !== null) {${NEW_LINE}this.${item["name"]} = args.${item[ 650 | "name" 651 | ]}${NEW_LINE}}` 652 | ); 653 | }); 654 | codes.push(`${NEW_LINE}}`); 655 | } 656 | // close constructor 657 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 658 | 659 | // add read function 660 | codes.push( 661 | `${NEW_LINE}read = (input: IProtocol) => {${NEW_LINE}input.readStructBegin()${NEW_LINE}while (true) {${NEW_LINE}let ret = input.readFieldBegin()${NEW_LINE}let fname = ret.fname${NEW_LINE}let ftype = ret.ftype${NEW_LINE}let fid = ret.fid${NEW_LINE}if (ftype === ThriftType.STOP) {${NEW_LINE}break${NEW_LINE}}` 662 | ); 663 | if (func["type"] !== "void") { 664 | codes.push(`${NEW_LINE}switch (fid) {`); 665 | codes.push( 666 | `${NEW_LINE}case 0:${NEW_LINE}if (ftype === ${getThriftTypeStr( 667 | func["type"] 668 | )}) {${NEW_LINE}this.success = input.${getReaderStr( 669 | func["type"] 670 | )}${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break` 671 | ); 672 | 673 | if (throws && throws.length) { 674 | throws.forEach((item, index) => { 675 | codes.push( 676 | `${NEW_LINE}case ${index + 677 | 1}:${NEW_LINE}if (ftype === ${getThriftTypeStr( 678 | item["type"] 679 | )}) {${NEW_LINE}this.${item["name"]} = new ${item[ 680 | "type" 681 | ]}()${NEW_LINE}this.${item[ 682 | "name" 683 | ]}.read(input)${NEW_LINE}} else {${NEW_LINE}input.skip(ftype)${NEW_LINE}}${NEW_LINE}break` 684 | ); 685 | }); 686 | } 687 | codes.push( 688 | `${NEW_LINE}default:${NEW_LINE}input.skip(ftype)${NEW_LINE}}` 689 | ); 690 | } else { 691 | codes.push(`${NEW_LINE}input.skip(ftype)`); 692 | } 693 | codes.push( 694 | `${NEW_LINE}input.readFieldEnd()${NEW_LINE}}${NEW_LINE}input.readStructEnd()${NEW_LINE}return${NEW_LINE}}` 695 | ); 696 | 697 | // add write function 698 | codes.push( 699 | `${NEW_LINE}${NEW_LINE}write = (output: IProtocol) => {${NEW_LINE}output.writeStructBegin('${name}')` 700 | ); 701 | 702 | if (func["type"] !== "void") { 703 | codes.push( 704 | `${NEW_LINE}if (this.success !== null && this.success !== undefined) {${NEW_LINE}output.writeFieldBegin('success', ${getThriftTypeStr( 705 | func["type"] 706 | )}, 0)${NEW_LINE}output.${getWriterStr( 707 | func["type"] 708 | )}(this.success)${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}` 709 | ); 710 | } 711 | if (throws && throws.length) { 712 | throws.forEach((item, index) => { 713 | codes.push( 714 | `${NEW_LINE}if (this.${item[ 715 | "name" 716 | ]} !== null && this.${item[ 717 | "name" 718 | ]} !== undefined) {${NEW_LINE}output.writeFieldBegin('${item[ 719 | "name" 720 | ]}', ${getThriftTypeStr(item["type"])}, ${index + 721 | 1})${NEW_LINE}this.${item[ 722 | "name" 723 | ]}.write(output)${NEW_LINE}output.writeFieldEnd()${NEW_LINE}}` 724 | ); 725 | }); 726 | } 727 | 728 | codes.push( 729 | `${NEW_LINE}output.writeFieldStop()${NEW_LINE}output.writeStructEnd()${NEW_LINE}return${NEW_LINE}}` 730 | ); 731 | 732 | // close block 733 | codes.push(`${NEW_LINE}}${NEW_LINE}`); 734 | 735 | return codes.join(""); 736 | }; 737 | 738 | const serviceHandler = (name, service): string => { 739 | let codes = []; 740 | let functions = service["functions"]; 741 | codes.push(`${NEW_LINE}export class ${name + "Client"}`); 742 | if (service["extends"]) { 743 | codes.push(` extends ${service["extends"] + "Client"}`); 744 | } 745 | codes.push( 746 | ` {${NEW_LINE}output: ITransport;${NEW_LINE}pClass: ProtocolClass;${NEW_LINE}id: number;${NEW_LINE}reqs: { [key: string]: any }` 747 | ); 748 | 749 | // constructor block 750 | codes.push( 751 | `${NEW_LINE}${NEW_LINE}constructor (output: ITransport, pClass: ProtocolClass) {` 752 | ); 753 | if (service["extends"]) { 754 | codes.push(`${NEW_LINE}super(output, pClass)`); 755 | } 756 | codes.push( 757 | `${NEW_LINE}this.output = output${NEW_LINE}this.pClass = pClass${NEW_LINE}this.id = 0${NEW_LINE}this.reqs = {}${NEW_LINE}}` 758 | ); 759 | 760 | // function blocks 761 | Object.keys(functions).forEach(key => { 762 | let func = functions[key]; 763 | let args = func["args"]; 764 | let argStrs = args.map(x => x.name).join(", "); 765 | // func 766 | codes.push( 767 | `${NEW_LINE}${NEW_LINE}${key} = (${argStrs 768 | ? argStrs + ", callback" 769 | : "callback"}) => {${NEW_LINE}if (callback === undefined) {${NEW_LINE}let self = this${NEW_LINE}return new Promise(function (resolve, reject) {${NEW_LINE}self.reqs[self.id] = (err, result) => {${NEW_LINE}if (err) {${NEW_LINE}reject(err)${NEW_LINE}} else {${NEW_LINE}resolve(result)${NEW_LINE}}${NEW_LINE}}${NEW_LINE}self.send_${key}(${argStrs})${NEW_LINE}})${NEW_LINE}} else {${NEW_LINE}this.reqs[this.id] = callback${NEW_LINE}this.send_${key}(${argStrs})${NEW_LINE}}${NEW_LINE}}` 770 | ); 771 | 772 | // send_func 773 | codes.push( 774 | `${NEW_LINE}${NEW_LINE}send_${key} = (${argStrs}) => {${NEW_LINE}let output = new this.pClass(this.output)${NEW_LINE}output.writeMessageBegin('${key}', MessageType.CALL, this.id)${NEW_LINE}let args = new ${name}_${key}_args({${argStrs}})${NEW_LINE}args.write(output)${NEW_LINE}output.writeMessageEnd()${NEW_LINE}return this.output.flush()${NEW_LINE}}` 775 | ); 776 | 777 | // recv_func 778 | if (func["oneway"] !== true) { 779 | codes.push( 780 | `${NEW_LINE}${NEW_LINE}recv_${key} = (input, mtype, rseqid) => {${NEW_LINE}let callback = this.reqs[rseqid] || function () { }${NEW_LINE}delete this.reqs[rseqid]${NEW_LINE}if (mtype === MessageType.EXCEPTION) {${NEW_LINE}let x = new TApplicationException()${NEW_LINE}x.read(input)${NEW_LINE}input.readMessageEnd()${NEW_LINE}return callback(x)${NEW_LINE}}${NEW_LINE}let result = new ${name}_${key}_result()${NEW_LINE}result.read(input)${NEW_LINE}input.readMessageEnd()${NEW_LINE}` 781 | ); 782 | ////// -- throws 783 | let throws = func["throws"]; 784 | throws.forEach((item, index) => { 785 | codes.push( 786 | `${NEW_LINE}if (null !== result.${item[ 787 | "name" 788 | ]}) {${NEW_LINE}throw result.${item[ 789 | "name" 790 | ]}${NEW_LINE}}` 791 | ); 792 | }); 793 | 794 | codes.push( 795 | `${NEW_LINE}if (null !== result.success) {${NEW_LINE}return callback(null, result.success)${NEW_LINE}}${NEW_LINE}return callback('${key} failed: unknown result')${NEW_LINE}}` 796 | ); 797 | } 798 | }); 799 | 800 | // close class block 801 | codes.push(`${NEW_LINE}}`); 802 | 803 | defaultExports.push(name + "Client"); 804 | 805 | return codes.join(""); 806 | }; 807 | 808 | const defaultExportsHandler = (): string => { 809 | let code = `${NEW_LINE_2}export default {${NEW_LINE}`; 810 | defaultExports.forEach((v, i) => { 811 | code += v; 812 | if (i < defaultExports.length - 1) { 813 | code += ","; 814 | } 815 | code += `${NEW_LINE}`; 816 | }); 817 | code += `}${NEW_LINE}`; 818 | return code; 819 | }; 820 | 821 | // includes -> import 822 | if (ast.include) { 823 | code += includesHandler(ast.include); 824 | } 825 | 826 | // const -> const 827 | if (ast.const) { 828 | code += constsHandler(ast.const); 829 | } 830 | 831 | // enum -> interface 832 | if (ast.enum) { 833 | code += enumsHandler(ast.enum); 834 | } 835 | 836 | // exception -> class extends TException 837 | if (ast.exception) { 838 | code += exceptionsHandler(ast.exception); 839 | } 840 | 841 | // struct -> class 842 | if (ast.struct) { 843 | code += structsHandler(ast.struct); 844 | } 845 | 846 | // union -> interface 847 | if (ast.union) { 848 | code += structsLikeHandler(ast.union); 849 | } 850 | 851 | // service -> functions 852 | if (ast.service) { 853 | code += servicesHandler(ast.service); 854 | } 855 | 856 | // default export 857 | if (ast.service) { 858 | code += defaultExportsHandler(); 859 | } 860 | 861 | return prettier.format(code, prettierConfig); 862 | }; 863 | 864 | export const clientsIndex = clientsDict => { 865 | let codes = [ 866 | `/**${NEW_LINE} * This file is auto-generated by Thrift2Ts.${NEW_LINE} *${NEW_LINE} * ${new Date().toString()}${NEW_LINE} */${NEW_LINE_2}` 867 | ]; 868 | 869 | const files = Object.keys(clientsDict); 870 | if (files.length < 1) { 871 | return codes.join(""); 872 | } 873 | 874 | let allClients = []; 875 | // import clients 876 | files.forEach(file => { 877 | let clients = clientsDict[file]; 878 | if (clients.length > 0) { 879 | allClients = allClients.concat(clients); 880 | codes.push( 881 | `import { ${clients.map( 882 | x => x + "Client" 883 | )} } from "./${file}"${NEW_LINE}` 884 | ); 885 | } 886 | }); 887 | 888 | // export 889 | codes.push( 890 | `${NEW_LINE_2}export default {${NEW_LINE}${allClients 891 | .map(x => x + ":" + x + "Client") 892 | .join(`,${NEW_LINE}`)}${NEW_LINE}}${NEW_LINE}` 893 | ); 894 | 895 | return prettier.format(codes.join(""), prettierConfig); 896 | }; 897 | --------------------------------------------------------------------------------