├── .gitignore ├── tests ├── integration │ ├── invalid-number.thrift │ ├── test.sh │ ├── test-parse.js │ ├── test-demo.js │ ├── test-demo.expection.json │ ├── test.thrift │ └── test-parse.expection.json ├── .eslintrc.yaml ├── index.js ├── includes.js ├── namespaces.js ├── typedefs.js ├── enums.js ├── consts.js ├── unions.js ├── structs.js ├── exceptions.js └── services.js ├── .eslintrc.yaml ├── bin └── thrift-parser.js ├── package.json ├── LICENSE.txt ├── format.txt ├── types └── index.d.ts ├── README.md └── thrift-parser.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .* 4 | -------------------------------------------------------------------------------- /tests/integration/invalid-number.thrift: -------------------------------------------------------------------------------- 1 | const int32 invalid = 1-2 2 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | es6: true 4 | node: true 5 | rules: 6 | no-throw-literal: 0 7 | extends: elemefe 8 | -------------------------------------------------------------------------------- /tests/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | es6: true 4 | node: true 5 | mocha: true 6 | rules: 7 | no-throw-literal: 0 8 | extends: elemefe 9 | -------------------------------------------------------------------------------- /tests/integration/test.sh: -------------------------------------------------------------------------------- 1 | for js in $(find $(dirname $0) -name 'test-*.js') 2 | do 3 | if node $js 4 | then echo "[Done] $js" 5 | else echo "[Fail] $js" 6 | fi 7 | done 8 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let expect = require('expect'); 4 | 5 | let thriftParser = require('../'); 6 | 7 | describe('thriftParser', function() { 8 | 9 | describe('regressions', function() { 10 | 11 | it('throws on number with `-` in the middle', function(done) { 12 | const content = ` 13 | const int32 invalid = 1-2 14 | `; 15 | 16 | expect(() => thriftParser(content)).toThrow(); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/integration/test-parse.js: -------------------------------------------------------------------------------- 1 | const thriftParser = require('../../thrift-parser'); 2 | const assert = require('assert'); 3 | const fs = require('fs'); 4 | const expectoin = require('./test-parse.expection.json'); 5 | 6 | fs.readFile('./test.thrift', (error, buffer) => { 7 | let ast = thriftParser(buffer); 8 | // console.log(JSON.stringify(ast, null, 2)); 9 | assert.deepEqual(ast, expectoin); 10 | }); 11 | 12 | fs.readFile('./invalid-number.thrift', (error, buffer) => { 13 | assert.throws(() => thriftParser(buffer)); 14 | }); 15 | -------------------------------------------------------------------------------- /bin/thrift-parser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const parser = require('../thrift-parser'); 4 | const fs = require('fs'); 5 | const args = process.argv.slice(2); 6 | 7 | if (args.length) { 8 | args.forEach(path => { 9 | console.log(JSON.stringify(parser(fs.readFileSync(path)), null, 2)); // eslint-disable-line 10 | }); 11 | } else { 12 | let receiver = []; 13 | process.stdin.on('data', data => receiver.push(data)); 14 | process.stdin.on('end', () => { 15 | console.log(JSON.stringify(parser(Buffer.concat(receiver)), null, 2)); // eslint-disable-line 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thrift-parser", 3 | "version": "0.4.2", 4 | "description": "To parse thrift file to a AST.", 5 | "main": "thrift-parser.js", 6 | "author": "rongyi.liu@ele.me", 7 | "license": "MIT", 8 | "scripts": { 9 | "test": "mocha tests && cd tests/integration && ./test.sh" 10 | }, 11 | "types": "./types/index.d.ts", 12 | "devDependencies": { 13 | "@types/expect": "^1.20.1", 14 | "@types/mocha": "^2.2.41", 15 | "@types/node": "^7.0.13", 16 | "eslint": "^3.19.0", 17 | "eslint-config-elemefe": "^0.3.0", 18 | "expect": "^1.20.2", 19 | "mocha": "^3.2.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/integration/test-demo.js: -------------------------------------------------------------------------------- 1 | const thriftParser = require('../../thrift-parser'); 2 | const assert = require('assert'); 3 | const expection = require('./test-demo.expection.json'); 4 | 5 | let ast = thriftParser(` 6 | 7 | struct MyStruct { 8 | 1: required int id, 9 | 2: required bool field1, 10 | # 3: required string field, 11 | 4: required i16 field, 12 | } 13 | exception Exception1 { 14 | 1: required i32 error_code, 15 | 2: required string error_name, 16 | 3: optional string message, 17 | } 18 | exception Exception2 { 19 | 1: required i32 error_code, 20 | 2: required string error_name, 21 | 3: optional string message, 22 | } 23 | service Service1 { 24 | bool ping() throws (1: Exception1 user_exception, 2: Exception2 system_exception) 25 | list test(1: MyStruct ms) 26 | throws (1: Exception1 user_exception, 2: Exception2 system_exception) 27 | } 28 | 29 | `); 30 | 31 | // console.log(JSON.stringify(ast, null, 2)); 32 | assert.deepEqual(ast, expection); 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) <2016-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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /format.txt: -------------------------------------------------------------------------------- 1 | := * * 2 | 3 | 4 | := 0x10 | 0x13 | 0x20 | 5 | 6 | "/*" ... "*/" | "#" ... "\r\n" 7 | 8 | := \w+ 9 | 10 | := | "<" ">" | "<" "," ">" 11 | 12 | := JSON 13 | 14 | := \d+ 15 | 16 | 17 | := | | | | | 18 | 19 | := "const" * * * "=" * * 20 | 21 | := "typedef" * * * 22 | 23 | := "enum" * * * 24 | 25 | := "struct" * * * 26 | 27 | := "exception" * * * 28 | 29 | := "service" * * * 30 | 31 | 32 | := "{" * * "}" 33 | 34 | := * "=" * * "," * 35 | 36 | 37 | := "{" * * "}" 38 | 39 | := * ":" * * * * "," * 40 | 41 | := "required" | "optional" 42 | 43 | 44 | := "{" * * "}" 45 | 46 | 47 | := "{" * * "}" 48 | 49 | := * * "(" * * ")" * * "," * 50 | 51 | := * ":" * * * "," * 52 | 53 | := "throws" * "(" * * ")" 54 | -------------------------------------------------------------------------------- /tests/includes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('includes', function() { 8 | 9 | it('parses a basic include', function(done) { 10 | const content = ` 11 | include "test" 12 | `; 13 | 14 | const expected = { 15 | include: { 16 | test: { 17 | path: 'test' 18 | } 19 | } 20 | }; 21 | 22 | const ast = thriftParser(content); 23 | 24 | expect(ast).toEqual(expected); 25 | done(); 26 | }); 27 | 28 | it('drops .thrift extension for key', function(done) { 29 | const content = ` 30 | include "test.thrift" 31 | `; 32 | 33 | const expected = { 34 | include: { 35 | test: { 36 | path: 'test.thrift' 37 | } 38 | } 39 | }; 40 | 41 | const ast = thriftParser(content); 42 | 43 | expect(ast).toEqual(expected); 44 | done(); 45 | }); 46 | 47 | it('parses paths wrapped in single-quotes', function(done) { 48 | const content = ` 49 | include 'test' 50 | `; 51 | 52 | const expected = { 53 | include: { 54 | test: { 55 | path: 'test' 56 | } 57 | } 58 | }; 59 | 60 | const ast = thriftParser(content); 61 | 62 | expect(ast).toEqual(expected); 63 | done(); 64 | }); 65 | 66 | it('does not parse paths wrapped in mixed-quotes (Literal)', function(done) { 67 | const content = ` 68 | include 'test" 69 | `; 70 | 71 | expect(() => thriftParser(content)).toThrow(); 72 | done(); 73 | }); 74 | 75 | it('parses a double-quote inside a single-quoted value (Literal)', function(done) { 76 | const content = ` 77 | include 'te"st' 78 | `; 79 | 80 | const expected = { 81 | include: { 82 | 'te"st': { 83 | path: 'te"st' 84 | } 85 | } 86 | }; 87 | 88 | const ast = thriftParser(content); 89 | 90 | expect(ast).toEqual(expected); 91 | done(); 92 | }); 93 | 94 | it('parses a single-quote inside a double-quoted value (Literal)', function(done) { 95 | const content = ` 96 | include "te'st" 97 | `; 98 | 99 | const expected = { 100 | include: { 101 | "te'st": { 102 | path: "te'st" 103 | } 104 | } 105 | }; 106 | 107 | const ast = thriftParser(content); 108 | 109 | expect(ast).toEqual(expected); 110 | done(); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | type SetType = { 4 | name: 'set', 5 | valueType: ValueType, 6 | } 7 | type ListType = { 8 | name: 'list', 9 | valueType: ValueType, 10 | } 11 | type MapType = { 12 | name: 'map', 13 | keyType: ValueType, 14 | valueType: ValueType, 15 | } 16 | type ValueType = string | SetType | ListType | MapType; 17 | type ThriftType = 'int' | 'bool' | 'i16' | 'i32' | 'i64' | 'string'; 18 | type FieldOption = 'required' | 'optional'; 19 | type Field = { 20 | id: string, 21 | option: FieldOption, 22 | type: ThriftType, 23 | name: string, 24 | }; 25 | 26 | type ArgOrExecption = { 27 | id: string, 28 | type: ValueType, 29 | name: string, 30 | } 31 | 32 | type Method = { 33 | type: ValueType, 34 | name: string, 35 | args: ArgOrExecption[], 36 | throws: ArgOrExecption[], 37 | } 38 | 39 | type Structs = { 40 | [name: string]: Field[], 41 | } 42 | 43 | type Unions = { 44 | [name: string]: Field[], 45 | } 46 | 47 | type Exceptions = { 48 | [name: string]: Field[], 49 | } 50 | 51 | type Services = { 52 | [serviceName: string]: { 53 | functions: { 54 | [methodName: string]: Method, 55 | }, 56 | }, 57 | } 58 | 59 | type Namespaces = { 60 | [name: string]: { 61 | serviceName: string, 62 | } 63 | } 64 | 65 | type Includes = { 66 | [name: string]: { 67 | path: string, 68 | } 69 | } 70 | 71 | type TypeDefs = { 72 | [name: string]: { 73 | type: string, 74 | } 75 | } 76 | 77 | type StaticConst = { 78 | type: string, 79 | value: any, 80 | } 81 | 82 | type ListConst = { 83 | type: ListType, 84 | value: any, 85 | } 86 | 87 | type MapConst = { 88 | type: MapType, 89 | value: { 90 | key: any, 91 | value: any, 92 | }[], 93 | } 94 | 95 | type SetConst = { 96 | type: SetType, 97 | value: any, 98 | } 99 | 100 | type Consts = { 101 | [name: string]: StaticConst | ListConst | MapConst | SetConst, 102 | } 103 | 104 | type Enums = { 105 | [name: string]: { 106 | items: { 107 | name: string, 108 | value: string | number | boolean, 109 | }[] 110 | } 111 | } 112 | 113 | type JsonAST = { 114 | namespace?: Namespaces, 115 | typedef?: TypeDefs, 116 | include?: Includes, 117 | const?: Consts, 118 | enum?: Enums, 119 | struct?: Structs, 120 | union?: Unions, 121 | exception?: Exceptions, 122 | service?: Services, 123 | }; 124 | 125 | declare module 'thrift-parser' { 126 | interface ThriftFileParsingError extends Error { 127 | messgae: string; 128 | name: 'THRIFT_FILE_PARSING_ERROR'; 129 | } 130 | 131 | function parser (str: string | Buffer): JsonAST; 132 | 133 | export = parser; 134 | } 135 | -------------------------------------------------------------------------------- /tests/integration/test-demo.expection.json: -------------------------------------------------------------------------------- 1 | { 2 | "struct": { 3 | "MyStruct": [ 4 | { 5 | "id": 1, 6 | "option": "required", 7 | "type": "int", 8 | "name": "id" 9 | }, 10 | { 11 | "id": 2, 12 | "option": "required", 13 | "type": "bool", 14 | "name": "field1" 15 | }, 16 | { 17 | "id": 4, 18 | "option": "required", 19 | "type": "i16", 20 | "name": "field" 21 | } 22 | ] 23 | }, 24 | "exception": { 25 | "Exception1": [ 26 | { 27 | "id": 1, 28 | "option": "required", 29 | "type": "i32", 30 | "name": "error_code" 31 | }, 32 | { 33 | "id": 2, 34 | "option": "required", 35 | "type": "string", 36 | "name": "error_name" 37 | }, 38 | { 39 | "id": 3, 40 | "option": "optional", 41 | "type": "string", 42 | "name": "message" 43 | } 44 | ], 45 | "Exception2": [ 46 | { 47 | "id": 1, 48 | "option": "required", 49 | "type": "i32", 50 | "name": "error_code" 51 | }, 52 | { 53 | "id": 2, 54 | "option": "required", 55 | "type": "string", 56 | "name": "error_name" 57 | }, 58 | { 59 | "id": 3, 60 | "option": "optional", 61 | "type": "string", 62 | "name": "message" 63 | } 64 | ] 65 | }, 66 | "service": { 67 | "Service1": { 68 | "functions": { 69 | "ping": { 70 | "type": "bool", 71 | "name": "ping", 72 | "args": [], 73 | "throws": [ 74 | { 75 | "id": 1, 76 | "type": "Exception1", 77 | "name": "user_exception" 78 | }, 79 | { 80 | "id": 2, 81 | "type": "Exception2", 82 | "name": "system_exception" 83 | } 84 | ], 85 | "oneway": false 86 | }, 87 | "test": { 88 | "type": { 89 | "name": "list", 90 | "valueType": "MyStruct" 91 | }, 92 | "name": "test", 93 | "args": [ 94 | { 95 | "id": 1, 96 | "type": "MyStruct", 97 | "name": "ms" 98 | } 99 | ], 100 | "throws": [ 101 | { 102 | "id": 1, 103 | "type": "Exception1", 104 | "name": "user_exception" 105 | }, 106 | { 107 | "id": 2, 108 | "type": "Exception2", 109 | "name": "system_exception" 110 | } 111 | ], 112 | "oneway": false 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/integration/test.thrift: -------------------------------------------------------------------------------- 1 | namespace php hehe 2 | namespace * haha 3 | include "Status.Type" 4 | include "dotThrift.thrift" 5 | include "a/b/basename" 6 | include "a\b\basenameInWindows" 7 | /****** 8 | * Types 9 | *****/ 10 | typedef string Json 11 | typedef i64 Mobile 12 | typedef string ListSep1; 13 | typedef string ListSep2, 14 | typedef binary Buffer 15 | 16 | /** 17 | * Const 18 | */const i16 C1 = 123 19 | const string C2 = "456" 20 | const list C3 = [ 1, 2, 3 ] 21 | const map C4 = { 1: 'a', 2: 'b', 3: 'c' } 22 | const bool C5 = true 23 | const bool C6 = false 24 | const set C7 = [ 1, 2, 3 ] 25 | const bool C8 = true; 26 | const bool C9 = false, 27 | const i16 C10 = 0x7fff 28 | const i32 C11 = 0x7fffffff 29 | const i16 C12 = -3e4 30 | const i32 C13 = 2.147483647e9 31 | 32 | const string chinese_test = "中文测试" 33 | const string japanese_test = "日本語テスト" 34 | const string emoji_test = "😂 " 35 | 36 | /** 37 | * Enum 38 | */ 39 | enum Enum1 { 40 | TYPE_DISCOVER = 1, 41 | TYPE_NOTIFY = 2, 42 | } 43 | enum Enum2 { 44 | OPERATE_TYPE_MANUAL = 1, 45 | OPERATE_TYPE_AUTO = 2, 46 | } 47 | 48 | struct Struct1 { 49 | 1: required int id, 50 | 2: required bool field1, 51 | # 3: required string field, 52 | 4: required i16 field, 53 | 5: required binary field2 54 | } 55 | 56 | struct Struct2 { 57 | 1: required int id, 58 | 2: required Json field1, 59 | 3: required Mobile field2, 60 | 4: required i16 field3, 61 | 5: string field4 = "default value" 62 | } 63 | 64 | union Union1 { 65 | 1: string field1; 66 | 2: int field2; 67 | 3: required string withOption; 68 | } 69 | 70 | exception Exception1 { 71 | 1: required i32 error_code, 72 | 2: required string error_name, 73 | 3: optional string message, 74 | } 75 | 76 | exception Exception2 { 77 | 1: required i32 error_code, 78 | 2: required string error_name, 79 | 3: optional string message, 80 | } 81 | 82 | /** 83 | * Services 84 | */ 85 | service Service1 { 86 | /** 87 | * Base services 88 | */ 89 | bool ping() throws (1: Exception1 user_exception, 2: Exception2 system_exception) 90 | 91 | /** 92 | * TEST 93 | */ 94 | list> test(1: Struct1 s1, 2: Struct2 s2) 95 | throws (1: Exception1 user_exception, 2: Exception2 system_exception) 96 | list> test2(1: Struct1 s1) 97 | throws (1: Exception1 user_exception, 2: Exception2 system_exception) 98 | } 99 | 100 | /** 101 | * Services 102 | */ 103 | service Service2 { 104 | /** 105 | * Base services 106 | */ 107 | bool ping(), 108 | 109 | oneway void foo(), 110 | 111 | /** 112 | * TEST 113 | */ 114 | Json test(1: Struct1 s1, 2: Struct2 s2) 115 | throws (1: Exception1 user_exception, 2: Exception2 system_exception) 116 | } 117 | 118 | service ExtendedService extends Service1 { 119 | bool pong() 120 | } 121 | 122 | service ExternalExtends extends external.Service1 { 123 | bool pong() 124 | } -------------------------------------------------------------------------------- /tests/namespaces.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('namespaces', function() { 8 | 9 | it('parses a basic namespace', function(done) { 10 | const content = ` 11 | namespace js test 12 | `; 13 | 14 | const expected = { 15 | namespace: { 16 | js: { 17 | serviceName: 'test' 18 | } 19 | } 20 | }; 21 | 22 | const ast = thriftParser(content); 23 | 24 | expect(ast).toEqual(expected); 25 | done(); 26 | }); 27 | 28 | it('parses the * scope', function(done) { 29 | const content = ` 30 | namespace * test 31 | `; 32 | 33 | const expected = { 34 | namespace: { 35 | '*': { 36 | serviceName: 'test' 37 | } 38 | } 39 | }; 40 | 41 | const ast = thriftParser(content); 42 | 43 | expect(ast).toEqual(expected); 44 | done(); 45 | }); 46 | 47 | it('parses a dot.separated scope', function(done) { 48 | const content = ` 49 | namespace js.noexist test 50 | `; 51 | 52 | const expected = { 53 | namespace: { 54 | 'js.noexist': { 55 | serviceName: 'test' 56 | } 57 | } 58 | }; 59 | 60 | const ast = thriftParser(content); 61 | 62 | expect(ast).toEqual(expected); 63 | done(); 64 | }); 65 | 66 | it('parses a dot.separated namespace (Identifier)', function(done) { 67 | const content = ` 68 | namespace js test.sub 69 | `; 70 | 71 | const expected = { 72 | namespace: { 73 | js: { 74 | serviceName: 'test.sub' 75 | } 76 | } 77 | }; 78 | 79 | const ast = thriftParser(content); 80 | 81 | expect(ast).toEqual(expected); 82 | done(); 83 | }); 84 | 85 | it('parses a namespace that starts with `_` (Identifier)', function(done) { 86 | const content = ` 87 | namespace js _test 88 | `; 89 | 90 | const expected = { 91 | namespace: { 92 | js: { 93 | serviceName: '_test' 94 | } 95 | } 96 | }; 97 | 98 | const ast = thriftParser(content); 99 | 100 | expect(ast).toEqual(expected); 101 | done(); 102 | }); 103 | 104 | it('parses a namespace containing `_` (Identifier)', function(done) { 105 | const content = ` 106 | namespace js test_sub 107 | `; 108 | 109 | const expected = { 110 | namespace: { 111 | js: { 112 | serviceName: 'test_sub' 113 | } 114 | } 115 | }; 116 | 117 | const ast = thriftParser(content); 118 | 119 | expect(ast).toEqual(expected); 120 | done(); 121 | }); 122 | 123 | it('parses a namespace containing `.`, `_`, letters and numbers (Identifier)', function(done) { 124 | const content = ` 125 | namespace js test.sub_123 126 | `; 127 | 128 | const expected = { 129 | namespace: { 130 | js: { 131 | serviceName: 'test.sub_123' 132 | } 133 | } 134 | }; 135 | 136 | const ast = thriftParser(content); 137 | 138 | expect(ast).toEqual(expected); 139 | done(); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## thrift-parser 2 | 3 | To parse thrift file to a AST. 4 | 5 | ### Demo 6 | 7 | ```javascript 8 | let thriftParser = require('thrift-parser'); 9 | 10 | let ast = thriftParser(` 11 | 12 | struct MyStruct { 13 | 1: required int id, 14 | 2: required bool field1, 15 | # 3: required string field, 16 | 4: required i16 field, 17 | } 18 | exception Exception1 { 19 | 1: required i32 error_code, 20 | 2: required string error_name, 21 | 3: optional string message, 22 | } 23 | exception Exception2 { 24 | 1: required i32 error_code, 25 | 2: required string error_name, 26 | 3: optional string message, 27 | } 28 | service Service1 { 29 | bool ping() throws (1: Exception1 user_exception, 2: Exception2 system_exception) 30 | list test(1: MyStruct ms) 31 | throws (1: Exception1 user_exception, 2: Exception2 system_exception) 32 | } 33 | 34 | `); 35 | ``` 36 | 37 | Results 38 | 39 | ```json 40 | { 41 | "struct": { 42 | "MyStruct": [ 43 | { 44 | "id": 1, 45 | "option": "required", 46 | "type": "int", 47 | "name": "id" 48 | }, 49 | { 50 | "id": 2, 51 | "option": "required", 52 | "type": "bool", 53 | "name": "field1" 54 | }, 55 | { 56 | "id": 4, 57 | "option": "required", 58 | "type": "i16", 59 | "name": "field" 60 | } 61 | ] 62 | }, 63 | "exception": { 64 | "Exception1": [ 65 | { 66 | "id": 1, 67 | "option": "required", 68 | "type": "i32", 69 | "name": "error_code" 70 | }, 71 | { 72 | "id": 2, 73 | "option": "required", 74 | "type": "string", 75 | "name": "error_name" 76 | }, 77 | { 78 | "id": 3, 79 | "option": "optional", 80 | "type": "string", 81 | "name": "message" 82 | } 83 | ], 84 | "Exception2": [ 85 | { 86 | "id": 1, 87 | "option": "required", 88 | "type": "i32", 89 | "name": "error_code" 90 | }, 91 | { 92 | "id": 2, 93 | "option": "required", 94 | "type": "string", 95 | "name": "error_name" 96 | }, 97 | { 98 | "id": 3, 99 | "option": "optional", 100 | "type": "string", 101 | "name": "message" 102 | } 103 | ] 104 | }, 105 | "service": { 106 | "Service1": { 107 | "functions": { 108 | "ping": { 109 | "type": "bool", 110 | "name": "ping", 111 | "args": [], 112 | "throws": [ 113 | { 114 | "id": 1, 115 | "type": "Exception1", 116 | "name": "user_exception" 117 | }, 118 | { 119 | "id": 2, 120 | "type": "Exception2", 121 | "name": "system_exception" 122 | } 123 | ], 124 | "oneway": false 125 | }, 126 | "test": { 127 | "type": { 128 | "name": "list", 129 | "valueType": "MyStruct" 130 | }, 131 | "name": "test", 132 | "args": [ 133 | { 134 | "id": 1, 135 | "type": "MyStruct", 136 | "name": "ms" 137 | } 138 | ], 139 | "throws": [ 140 | { 141 | "id": 1, 142 | "type": "Exception1", 143 | "name": "user_exception" 144 | }, 145 | { 146 | "id": 2, 147 | "type": "Exception2", 148 | "name": "system_exception" 149 | } 150 | ], 151 | "oneway": false 152 | } 153 | } 154 | } 155 | } 156 | } 157 | ``` 158 | -------------------------------------------------------------------------------- /tests/typedefs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('typedefs', function() { 8 | 9 | it('parses a simple typedef', function(done) { 10 | const content = ` 11 | typedef string Test 12 | `; 13 | 14 | const expected = { 15 | typedef: { 16 | Test: { 17 | type: 'string' 18 | } 19 | } 20 | }; 21 | 22 | const ast = thriftParser(content); 23 | 24 | expect(ast).toEqual(expected); 25 | done(); 26 | }); 27 | 28 | it('parses a typedef of another typedef type', function(done) { 29 | const content = ` 30 | typedef string Json 31 | typedef Json Test 32 | `; 33 | 34 | const expected = { 35 | typedef: { 36 | Json: { 37 | type: 'string' 38 | }, 39 | Test: { 40 | type: 'Json' 41 | } 42 | } 43 | }; 44 | 45 | const ast = thriftParser(content); 46 | 47 | expect(ast).toEqual(expected); 48 | done(); 49 | }); 50 | 51 | it('parses a typedef that ends in `,` (ListSeparator)', function(done) { 52 | const content = ` 53 | typedef string Test, 54 | `; 55 | 56 | const expected = { 57 | typedef: { 58 | Test: { 59 | type: 'string' 60 | } 61 | } 62 | }; 63 | 64 | const ast = thriftParser(content); 65 | 66 | expect(ast).toEqual(expected); 67 | done(); 68 | }); 69 | 70 | it('parses a typedef that ends in `;` (ListSeparator)', function(done) { 71 | const content = ` 72 | typedef string Test; 73 | `; 74 | 75 | const expected = { 76 | typedef: { 77 | Test: { 78 | type: 'string' 79 | } 80 | } 81 | }; 82 | 83 | const ast = thriftParser(content); 84 | 85 | expect(ast).toEqual(expected); 86 | done(); 87 | }); 88 | 89 | it('parses a Map typedef', function(done) { 90 | const content = ` 91 | typedef map Test; 92 | `; 93 | 94 | const expected = { 95 | typedef: { 96 | Test: { 97 | type: { 98 | name: 'map', 99 | keyType: 'string', 100 | valueType: 'string' 101 | } 102 | } 103 | } 104 | }; 105 | 106 | const ast = thriftParser(content); 107 | 108 | expect(ast).toEqual(expected); 109 | done(); 110 | }); 111 | 112 | it('does not parse an invalid Map typedef', function(done) { 113 | const content = ` 114 | typedef map Test; 115 | `; 116 | 117 | expect(() => thriftParser(content)).toThrow(); 118 | done(); 119 | }); 120 | 121 | it('does not parse an unclosed Map typedef', function(done) { 122 | const content = ` 123 | typedef map thriftParser(content)).toThrow(); 127 | done(); 128 | }); 129 | 130 | it('parses a Set typedef', function(done) { 131 | const content = ` 132 | typedef set Test; 133 | `; 134 | 135 | const expected = { 136 | typedef: { 137 | Test: { 138 | type: { 139 | name: 'set', 140 | valueType: 'string' 141 | } 142 | } 143 | } 144 | }; 145 | 146 | const ast = thriftParser(content); 147 | 148 | expect(ast).toEqual(expected); 149 | done(); 150 | }); 151 | 152 | it('does not parse an invalid Set typedef', function(done) { 153 | const content = ` 154 | typedef set Test; 155 | `; 156 | 157 | expect(() => thriftParser(content)).toThrow(); 158 | done(); 159 | }); 160 | 161 | it('does not parse an unclosed Set typedef', function(done) { 162 | const content = ` 163 | typedef set thriftParser(content)).toThrow(); 167 | done(); 168 | }); 169 | 170 | it('parses a List typedef', function(done) { 171 | const content = ` 172 | typedef list Test; 173 | `; 174 | 175 | const expected = { 176 | typedef: { 177 | Test: { 178 | type: { 179 | name: 'list', 180 | valueType: 'string' 181 | } 182 | } 183 | } 184 | }; 185 | 186 | const ast = thriftParser(content); 187 | 188 | expect(ast).toEqual(expected); 189 | done(); 190 | }); 191 | 192 | it('does not parse an invalid List typedef', function(done) { 193 | const content = ` 194 | typedef list Test; 195 | `; 196 | 197 | expect(() => thriftParser(content)).toThrow(); 198 | done(); 199 | }); 200 | 201 | it('does not parse an unclosed List typedef', function(done) { 202 | const content = ` 203 | typedef list thriftParser(content)).toThrow(); 207 | done(); 208 | }); 209 | 210 | it('parses nested Lists/Maps/Sets in typedef', function(done) { 211 | const content = ` 212 | typedef list>> Test; 213 | `; 214 | 215 | const expected = { 216 | typedef: { 217 | Test: { 218 | type: { 219 | name: 'list', 220 | valueType: { 221 | name: 'set', 222 | valueType: { 223 | name: 'map', 224 | keyType: 'string', 225 | valueType: 'string' 226 | } 227 | } 228 | } 229 | } 230 | } 231 | }; 232 | 233 | const ast = thriftParser(content); 234 | 235 | expect(ast).toEqual(expected); 236 | done(); 237 | }); 238 | 239 | it('does not parse unclosed nested Lists/Maps/Sets in typedef', function(done) { 240 | const content = ` 241 | typedef list> Test; 242 | `; 243 | 244 | expect(() => thriftParser(content)).toThrow(); 245 | done(); 246 | }); 247 | }); 248 | -------------------------------------------------------------------------------- /tests/integration/test-parse.expection.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": { 3 | "php": { 4 | "serviceName": "hehe" 5 | }, 6 | "*": { 7 | "serviceName": "haha" 8 | } 9 | }, 10 | "include": { 11 | "Status.Type": { 12 | "path": "Status.Type" 13 | }, 14 | "dotThrift": { 15 | "path": "dotThrift.thrift" 16 | }, 17 | "basename": { 18 | "path": "a/b/basename" 19 | }, 20 | "basenameInWindows": { 21 | "path": "a\\b\\basenameInWindows" 22 | } 23 | }, 24 | "typedef": { 25 | "Json": { 26 | "type": "string" 27 | }, 28 | "Mobile": { 29 | "type": "i64" 30 | }, 31 | "ListSep1": { 32 | "type": "string" 33 | }, 34 | "ListSep2": { 35 | "type": "string" 36 | }, 37 | "Buffer": { 38 | "type": "binary" 39 | } 40 | }, 41 | "const": { 42 | "C1": { 43 | "type": "i16", 44 | "value": 123 45 | }, 46 | "C2": { 47 | "type": "string", 48 | "value": "456" 49 | }, 50 | "C3": { 51 | "type": { 52 | "name": "list", 53 | "valueType": "i32" 54 | }, 55 | "value": [ 56 | 1, 57 | 2, 58 | 3 59 | ] 60 | }, 61 | "C4": { 62 | "type": { 63 | "name": "map", 64 | "keyType": "i32", 65 | "valueType": "string" 66 | }, 67 | "value": [ 68 | { 69 | "key": 1, 70 | "value": "a" 71 | }, 72 | { 73 | "key": 2, 74 | "value": "b" 75 | }, 76 | { 77 | "key": 3, 78 | "value": "c" 79 | } 80 | ] 81 | }, 82 | "C5": { 83 | "type": "bool", 84 | "value": true 85 | }, 86 | "C6": { 87 | "type": "bool", 88 | "value": false 89 | }, 90 | "C7": { 91 | "type": { 92 | "name": "set", 93 | "valueType": "i32" 94 | }, 95 | "value": [ 96 | 1, 97 | 2, 98 | 3 99 | ] 100 | }, 101 | "C8": { 102 | "type": "bool", 103 | "value": true 104 | }, 105 | "C9": { 106 | "type": "bool", 107 | "value": false 108 | }, 109 | "C10": { 110 | "type": "i16", 111 | "value": 32767 112 | }, 113 | "C11": { 114 | "type": "i32", 115 | "value": 2147483647 116 | }, 117 | "C12": { 118 | "type": "i16", 119 | "value": -30000 120 | }, 121 | "C13": { 122 | "type": "i32", 123 | "value": 2147483647 124 | }, 125 | "chinese_test": { 126 | "type": "string", 127 | "value": "中文测试" 128 | }, 129 | "japanese_test": { 130 | "type": "string", 131 | "value": "日本語テスト" 132 | }, 133 | "emoji_test": { 134 | "type": "string", 135 | "value": "😂 " 136 | } 137 | }, 138 | "enum": { 139 | "Enum1": { 140 | "items": [ 141 | { 142 | "name": "TYPE_DISCOVER", 143 | "value": 1 144 | }, 145 | { 146 | "name": "TYPE_NOTIFY", 147 | "value": 2 148 | } 149 | ] 150 | }, 151 | "Enum2": { 152 | "items": [ 153 | { 154 | "name": "OPERATE_TYPE_MANUAL", 155 | "value": 1 156 | }, 157 | { 158 | "name": "OPERATE_TYPE_AUTO", 159 | "value": 2 160 | } 161 | ] 162 | } 163 | }, 164 | "struct": { 165 | "Struct1": [ 166 | { 167 | "id": 1, 168 | "option": "required", 169 | "type": "int", 170 | "name": "id" 171 | }, 172 | { 173 | "id": 2, 174 | "option": "required", 175 | "type": "bool", 176 | "name": "field1" 177 | }, 178 | { 179 | "id": 4, 180 | "option": "required", 181 | "type": "i16", 182 | "name": "field" 183 | }, 184 | { 185 | "id": 5, 186 | "type": "binary", 187 | "name": "field2", 188 | "option": "required" 189 | } 190 | ], 191 | "Struct2": [ 192 | { 193 | "id": 1, 194 | "option": "required", 195 | "type": "int", 196 | "name": "id" 197 | }, 198 | { 199 | "id": 2, 200 | "option": "required", 201 | "type": "Json", 202 | "name": "field1" 203 | }, 204 | { 205 | "id": 3, 206 | "option": "required", 207 | "type": "Mobile", 208 | "name": "field2" 209 | }, 210 | { 211 | "id": 4, 212 | "option": "required", 213 | "type": "i16", 214 | "name": "field3" 215 | }, 216 | { 217 | "id": 5, 218 | "type": "string", 219 | "name": "field4", 220 | "defaultValue": "default value" 221 | } 222 | ] 223 | }, 224 | "union": { 225 | "Union1": [ 226 | { 227 | "id": 1, 228 | "type": "string", 229 | "name": "field1" 230 | }, 231 | { 232 | "id": 2, 233 | "type": "int", 234 | "name": "field2" 235 | }, 236 | { 237 | "id": 3, 238 | "type": "string", 239 | "name": "withOption", 240 | "option": "required" 241 | } 242 | ] 243 | }, 244 | "exception": { 245 | "Exception1": [ 246 | { 247 | "id": 1, 248 | "option": "required", 249 | "type": "i32", 250 | "name": "error_code" 251 | }, 252 | { 253 | "id": 2, 254 | "option": "required", 255 | "type": "string", 256 | "name": "error_name" 257 | }, 258 | { 259 | "id": 3, 260 | "option": "optional", 261 | "type": "string", 262 | "name": "message" 263 | } 264 | ], 265 | "Exception2": [ 266 | { 267 | "id": 1, 268 | "option": "required", 269 | "type": "i32", 270 | "name": "error_code" 271 | }, 272 | { 273 | "id": 2, 274 | "option": "required", 275 | "type": "string", 276 | "name": "error_name" 277 | }, 278 | { 279 | "id": 3, 280 | "option": "optional", 281 | "type": "string", 282 | "name": "message" 283 | } 284 | ] 285 | }, 286 | "service": { 287 | "Service1": { 288 | "functions": { 289 | "ping": { 290 | "type": "bool", 291 | "name": "ping", 292 | "args": [], 293 | "throws": [ 294 | { 295 | "id": 1, 296 | "type": "Exception1", 297 | "name": "user_exception" 298 | }, 299 | { 300 | "id": 2, 301 | "type": "Exception2", 302 | "name": "system_exception" 303 | } 304 | ], 305 | "oneway": false 306 | }, 307 | "test": { 308 | "type": { 309 | "name": "list", 310 | "valueType": { 311 | "name": "map", 312 | "keyType": "Struct1", 313 | "valueType": "Struct2" 314 | } 315 | }, 316 | "name": "test", 317 | "args": [ 318 | { 319 | "id": 1, 320 | "type": "Struct1", 321 | "name": "s1" 322 | }, 323 | { 324 | "id": 2, 325 | "type": "Struct2", 326 | "name": "s2" 327 | } 328 | ], 329 | "throws": [ 330 | { 331 | "id": 1, 332 | "type": "Exception1", 333 | "name": "user_exception" 334 | }, 335 | { 336 | "id": 2, 337 | "type": "Exception2", 338 | "name": "system_exception" 339 | } 340 | ], 341 | "oneway": false 342 | }, 343 | "test2": { 344 | "type": { 345 | "name": "list", 346 | "valueType": { 347 | "name": "set", 348 | "valueType": "Struct1" 349 | } 350 | }, 351 | "name": "test2", 352 | "args": [ 353 | { 354 | "id": 1, 355 | "type": "Struct1", 356 | "name": "s1" 357 | } 358 | ], 359 | "throws": [ 360 | { 361 | "id": 1, 362 | "type": "Exception1", 363 | "name": "user_exception" 364 | }, 365 | { 366 | "id": 2, 367 | "type": "Exception2", 368 | "name": "system_exception" 369 | } 370 | ], 371 | "oneway": false 372 | } 373 | } 374 | }, 375 | "Service2": { 376 | "functions": { 377 | "ping": { 378 | "type": "bool", 379 | "name": "ping", 380 | "args": [], 381 | "throws": [], 382 | "oneway": false 383 | }, 384 | "foo": { 385 | "type": "void", 386 | "name": "foo", 387 | "args": [], 388 | "throws": [], 389 | "oneway": true 390 | }, 391 | "test": { 392 | "type": "Json", 393 | "name": "test", 394 | "args": [ 395 | { 396 | "id": 1, 397 | "type": "Struct1", 398 | "name": "s1" 399 | }, 400 | { 401 | "id": 2, 402 | "type": "Struct2", 403 | "name": "s2" 404 | } 405 | ], 406 | "throws": [ 407 | { 408 | "id": 1, 409 | "type": "Exception1", 410 | "name": "user_exception" 411 | }, 412 | { 413 | "id": 2, 414 | "type": "Exception2", 415 | "name": "system_exception" 416 | } 417 | ], 418 | "oneway": false 419 | } 420 | } 421 | }, 422 | "ExtendedService": { 423 | "extends": "Service1", 424 | "functions": { 425 | "pong": { 426 | "type": "bool", 427 | "name": "pong", 428 | "args": [], 429 | "throws": [], 430 | "oneway": false 431 | } 432 | } 433 | }, 434 | "ExternalExtends": { 435 | "extends": "external.Service1", 436 | "functions": { 437 | "pong": { 438 | "type": "bool", 439 | "name": "pong", 440 | "args": [], 441 | "throws": [], 442 | "oneway": false 443 | } 444 | } 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /tests/enums.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('enums', function() { 8 | 9 | it('parses a simple enum', function(done) { 10 | const content = ` 11 | enum Test { 12 | test = 1 13 | } 14 | `; 15 | 16 | const expected = { 17 | enum: { 18 | Test: { 19 | items: [ 20 | { 21 | name: 'test', 22 | value: 1 23 | } 24 | ] 25 | } 26 | } 27 | }; 28 | 29 | const ast = thriftParser(content); 30 | 31 | expect(ast).toEqual(expected); 32 | done(); 33 | }); 34 | 35 | it('parses an enum with multiple values', function(done) { 36 | const content = ` 37 | enum Test { 38 | test1 = 1 39 | test2 = 2 40 | test3 = 3 41 | } 42 | `; 43 | 44 | const expected = { 45 | enum: { 46 | Test: { 47 | items: [ 48 | { 49 | name: 'test1', 50 | value: 1 51 | }, 52 | { 53 | name: 'test2', 54 | value: 2 55 | }, 56 | { 57 | name: 'test3', 58 | value: 3 59 | } 60 | ] 61 | } 62 | } 63 | }; 64 | 65 | const ast = thriftParser(content); 66 | 67 | expect(ast).toEqual(expected); 68 | done(); 69 | }); 70 | 71 | it('parses an enum without values', function(done) { 72 | const content = ` 73 | enum Test { 74 | test1 75 | test2 76 | test3 77 | } 78 | `; 79 | 80 | const expected = { 81 | enum: { 82 | Test: { 83 | items: [ 84 | { 85 | name: 'test1' 86 | }, 87 | { 88 | name: 'test2' 89 | }, 90 | { 91 | name: 'test3' 92 | } 93 | ] 94 | } 95 | } 96 | }; 97 | 98 | const ast = thriftParser(content); 99 | 100 | expect(ast).toEqual(expected); 101 | done(); 102 | }); 103 | 104 | it('parses an enum with mixed values', function(done) { 105 | const content = ` 106 | enum Test { 107 | test1 = 1 108 | test2 109 | test3 = 3 110 | } 111 | `; 112 | 113 | const expected = { 114 | enum: { 115 | Test: { 116 | items: [ 117 | { 118 | name: 'test1', 119 | value: 1 120 | }, 121 | { 122 | name: 'test2' 123 | }, 124 | { 125 | name: 'test3', 126 | value: 3 127 | } 128 | ] 129 | } 130 | } 131 | }; 132 | 133 | const ast = thriftParser(content); 134 | 135 | expect(ast).toEqual(expected); 136 | done(); 137 | }); 138 | 139 | it('parses an enum with hex value assigmnet', function(done) { 140 | const content = ` 141 | enum Test { 142 | test1 = 0x01 143 | } 144 | `; 145 | 146 | const expected = { 147 | enum: { 148 | Test: { 149 | items: [ 150 | { 151 | name: 'test1', 152 | value: 1 153 | } 154 | ] 155 | } 156 | } 157 | }; 158 | 159 | const ast = thriftParser(content); 160 | 161 | expect(ast).toEqual(expected); 162 | done(); 163 | }); 164 | 165 | it('parses an enum with values that end in `,` (ListSeparator)', function(done) { 166 | const content = ` 167 | enum Test { 168 | test1 = 1, 169 | test2 = 2, 170 | } 171 | `; 172 | 173 | const expected = { 174 | enum: { 175 | Test: { 176 | items: [ 177 | { 178 | name: 'test1', 179 | value: 1 180 | }, 181 | { 182 | name: 'test2', 183 | value: 2 184 | } 185 | ] 186 | } 187 | } 188 | }; 189 | 190 | const ast = thriftParser(content); 191 | 192 | expect(ast).toEqual(expected); 193 | done(); 194 | }); 195 | 196 | it('parses an enum with values that end in `;` (ListSeparator)', function(done) { 197 | const content = ` 198 | enum Test { 199 | test1 = 1; 200 | test2 = 2; 201 | } 202 | `; 203 | 204 | const expected = { 205 | enum: { 206 | Test: { 207 | items: [ 208 | { 209 | name: 'test1', 210 | value: 1 211 | }, 212 | { 213 | name: 'test2', 214 | value: 2 215 | } 216 | ] 217 | } 218 | } 219 | }; 220 | 221 | const ast = thriftParser(content); 222 | 223 | expect(ast).toEqual(expected); 224 | done(); 225 | }); 226 | 227 | it('parses an enum with values that end in `;` and `,` (ListSeparator)', function(done) { 228 | const content = ` 229 | enum Test { 230 | test1 = 1, 231 | test2 = 2; 232 | } 233 | `; 234 | 235 | const expected = { 236 | enum: { 237 | Test: { 238 | items: [ 239 | { 240 | name: 'test1', 241 | value: 1 242 | }, 243 | { 244 | name: 'test2', 245 | value: 2 246 | } 247 | ] 248 | } 249 | } 250 | }; 251 | 252 | const ast = thriftParser(content); 253 | 254 | expect(ast).toEqual(expected); 255 | done(); 256 | }); 257 | 258 | it('parses an enum that starts with `_` (Identifier)', function(done) { 259 | const content = ` 260 | enum _Test { 261 | test1 = 1 262 | } 263 | `; 264 | 265 | const expected = { 266 | enum: { 267 | _Test: { 268 | items: [ 269 | { 270 | name: 'test1', 271 | value: 1 272 | } 273 | ] 274 | } 275 | } 276 | }; 277 | 278 | const ast = thriftParser(content); 279 | 280 | expect(ast).toEqual(expected); 281 | done(); 282 | }); 283 | 284 | it('parses an enum containing `_` (Identifier)', function(done) { 285 | const content = ` 286 | enum Te_st { 287 | test1 = 1 288 | } 289 | `; 290 | 291 | const expected = { 292 | enum: { 293 | Te_st: { 294 | items: [ 295 | { 296 | name: 'test1', 297 | value: 1 298 | } 299 | ] 300 | } 301 | } 302 | }; 303 | 304 | const ast = thriftParser(content); 305 | 306 | expect(ast).toEqual(expected); 307 | done(); 308 | }); 309 | 310 | it('parses an enum containing `.` (Identifier)', function(done) { 311 | const content = ` 312 | enum Te.st { 313 | test1 = 1 314 | } 315 | `; 316 | 317 | const expected = { 318 | enum: { 319 | 'Te.st': { 320 | items: [ 321 | { 322 | name: 'test1', 323 | value: 1 324 | } 325 | ] 326 | } 327 | } 328 | }; 329 | 330 | const ast = thriftParser(content); 331 | 332 | expect(ast).toEqual(expected); 333 | done(); 334 | }); 335 | 336 | it('parses an enum containing `.`, `_`, letters and numbers (Identifier)', function(done) { 337 | const content = ` 338 | enum Te.st_123 { 339 | test1 = 1 340 | } 341 | `; 342 | 343 | const expected = { 344 | enum: { 345 | 'Te.st_123': { 346 | items: [ 347 | { 348 | name: 'test1', 349 | value: 1 350 | } 351 | ] 352 | } 353 | } 354 | }; 355 | 356 | const ast = thriftParser(content); 357 | 358 | expect(ast).toEqual(expected); 359 | done(); 360 | }); 361 | 362 | it('parses an enum with values that starts with `_` (Identifier)', function(done) { 363 | const content = ` 364 | enum Test { 365 | _test1 = 1 366 | } 367 | `; 368 | 369 | const expected = { 370 | enum: { 371 | Test: { 372 | items: [ 373 | { 374 | name: '_test1', 375 | value: 1 376 | } 377 | ] 378 | } 379 | } 380 | }; 381 | 382 | const ast = thriftParser(content); 383 | 384 | expect(ast).toEqual(expected); 385 | done(); 386 | }); 387 | 388 | it('parses an enum with values containing `_` (Identifier)', function(done) { 389 | const content = ` 390 | enum Test { 391 | test_1 = 1 392 | } 393 | `; 394 | 395 | const expected = { 396 | enum: { 397 | Test: { 398 | items: [ 399 | { 400 | name: 'test_1', 401 | value: 1 402 | } 403 | ] 404 | } 405 | } 406 | }; 407 | 408 | const ast = thriftParser(content); 409 | 410 | expect(ast).toEqual(expected); 411 | done(); 412 | }); 413 | 414 | it('parses an enum with values containing `.` (Identifier)', function(done) { 415 | const content = ` 416 | enum Test { 417 | test.1 = 1 418 | } 419 | `; 420 | 421 | const expected = { 422 | enum: { 423 | Test: { 424 | items: [ 425 | { 426 | name: 'test.1', 427 | value: 1 428 | } 429 | ] 430 | } 431 | } 432 | }; 433 | 434 | const ast = thriftParser(content); 435 | 436 | expect(ast).toEqual(expected); 437 | done(); 438 | }); 439 | 440 | it('parses an enum with values containing `.`, `_`, letters and numbers (Identifier)', function(done) { 441 | const content = ` 442 | enum Test { 443 | te.st_1 = 1 444 | } 445 | `; 446 | 447 | const expected = { 448 | enum: { 449 | Test: { 450 | items: [ 451 | { 452 | name: 'te.st_1', 453 | value: 1 454 | } 455 | ] 456 | } 457 | } 458 | }; 459 | 460 | const ast = thriftParser(content); 461 | 462 | expect(ast).toEqual(expected); 463 | done(); 464 | }); 465 | 466 | it('does not parse an enum with invalid value assignment', function(done) { 467 | const content = ` 468 | enum Test { 469 | test1 = 470 | } 471 | `; 472 | 473 | expect(() => thriftParser(content)).toThrow(); 474 | done(); 475 | }); 476 | 477 | it('does not parse an enum with a string value assignment', function(done) { 478 | const content = ` 479 | enum Test { 480 | test1 = 'test' 481 | } 482 | `; 483 | 484 | expect(() => thriftParser(content)).toThrow(); 485 | done(); 486 | }); 487 | 488 | it('does not parse an enum with a decimal value assignment', function(done) { 489 | const content = ` 490 | enum Test { 491 | test1 = 1.2 492 | } 493 | `; 494 | 495 | expect(() => thriftParser(content)).toThrow(); 496 | done(); 497 | }); 498 | 499 | it('does not parse an enum with an e-notation value assignment', function(done) { 500 | const content = ` 501 | enum Test { 502 | test1 = 1e2 503 | } 504 | `; 505 | 506 | expect(() => thriftParser(content)).toThrow(); 507 | done(); 508 | }); 509 | 510 | it('does not parse an enum with a Map value assignment', function(done) { 511 | const content = ` 512 | enum Test { 513 | test1 = {'test':'test'} 514 | } 515 | `; 516 | 517 | expect(() => thriftParser(content)).toThrow(); 518 | done(); 519 | }); 520 | 521 | it('does not parse an enum with a Set/List value assignment', function(done) { 522 | const content = ` 523 | enum Test { 524 | test1 = [1,2,3] 525 | } 526 | `; 527 | 528 | expect(() => thriftParser(content)).toThrow(); 529 | done(); 530 | }); 531 | }); 532 | -------------------------------------------------------------------------------- /tests/consts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('consts', function() { 8 | 9 | it('parses a simple const', function(done) { 10 | const content = ` 11 | const string test = 'hello world' 12 | `; 13 | 14 | const expected = { 15 | const: { 16 | test: { 17 | type: 'string', 18 | value: 'hello world' 19 | } 20 | } 21 | }; 22 | 23 | const ast = thriftParser(content); 24 | 25 | expect(ast).toEqual(expected); 26 | done(); 27 | }); 28 | 29 | it('does not parse a const without assignment', function(done) { 30 | const content = ` 31 | const string test 32 | `; 33 | 34 | expect(() => thriftParser(content)).toThrow(); 35 | done(); 36 | }); 37 | 38 | it('parses a const as a number for number types', function(done) { 39 | const content = ` 40 | const i32 test = 123 41 | `; 42 | 43 | const expected = { 44 | const: { 45 | test: { 46 | type: 'i32', 47 | value: 123 48 | } 49 | } 50 | }; 51 | 52 | const ast = thriftParser(content); 53 | 54 | expect(ast).toEqual(expected); 55 | done(); 56 | }); 57 | 58 | it('parses a const with a hex value as a number', function(done) { 59 | const content = ` 60 | const i16 testSmall = 0x7fff 61 | const i32 testLarge = 0x7fffffff 62 | `; 63 | 64 | const expected = { 65 | const: { 66 | testSmall: { 67 | type: 'i16', 68 | value: 32767 69 | }, 70 | testLarge: { 71 | type: 'i32', 72 | value: 2147483647 73 | } 74 | } 75 | }; 76 | 77 | const ast = thriftParser(content); 78 | 79 | expect(ast).toEqual(expected); 80 | done(); 81 | }); 82 | 83 | it('parses a const with an e-notation value as a number', function(done) { 84 | const content = ` 85 | const i16 testSmall = -3e4 86 | const i32 testLarge = 2.147483647e9 87 | `; 88 | 89 | const expected = { 90 | const: { 91 | testSmall: { 92 | type: 'i16', 93 | value: -30000 94 | }, 95 | testLarge: { 96 | type: 'i32', 97 | value: 2147483647 98 | } 99 | } 100 | }; 101 | 102 | const ast = thriftParser(content); 103 | 104 | expect(ast).toEqual(expected); 105 | done(); 106 | }); 107 | 108 | it('parses a const as a boolean for boolean types', function(done) { 109 | const content = ` 110 | const bool testTrue = true 111 | const bool testFalse = false 112 | `; 113 | 114 | const expected = { 115 | const: { 116 | testTrue: { 117 | type: 'bool', 118 | value: true 119 | }, 120 | testFalse: { 121 | type: 'bool', 122 | value: false 123 | } 124 | } 125 | }; 126 | 127 | const ast = thriftParser(content); 128 | 129 | expect(ast).toEqual(expected); 130 | done(); 131 | }); 132 | 133 | it('parses a const that ends in `,` (ListSeparator)', function(done) { 134 | const content = ` 135 | const string test = 'hello world', 136 | `; 137 | 138 | const expected = { 139 | const: { 140 | test: { 141 | type: 'string', 142 | value: 'hello world' 143 | } 144 | } 145 | }; 146 | 147 | const ast = thriftParser(content); 148 | 149 | expect(ast).toEqual(expected); 150 | done(); 151 | }); 152 | 153 | it('parses a const that ends in `;` (ListSeparator)', function(done) { 154 | const content = ` 155 | const string test = 'hello world'; 156 | `; 157 | 158 | const expected = { 159 | const: { 160 | test: { 161 | type: 'string', 162 | value: 'hello world' 163 | } 164 | } 165 | }; 166 | 167 | const ast = thriftParser(content); 168 | 169 | expect(ast).toEqual(expected); 170 | done(); 171 | }); 172 | 173 | it('parses a const with a value wrapped in double-quotes', function(done) { 174 | const content = ` 175 | const string test = "hello world" 176 | `; 177 | 178 | const expected = { 179 | const: { 180 | test: { 181 | type: 'string', 182 | value: 'hello world' 183 | } 184 | } 185 | }; 186 | 187 | const ast = thriftParser(content); 188 | 189 | expect(ast).toEqual(expected); 190 | done(); 191 | }); 192 | 193 | it('does not parse a const with a value wrapped in mixed-quotes', function(done) { 194 | const content = ` 195 | const string test = "hello world' 196 | `; 197 | 198 | expect(() => thriftParser(content)).toThrow(); 199 | done(); 200 | }); 201 | 202 | it('parses a const as an array of objects for Map types', function(done) { 203 | const content = ` 204 | const map test = { 1: 'a', 2: 'b', 3: 'c' } 205 | `; 206 | 207 | const expected = { 208 | const: { 209 | test: { 210 | type: { 211 | name: 'map', 212 | keyType: 'i32', 213 | valueType: 'string' 214 | }, 215 | value: [ 216 | { 217 | key: 1, 218 | value: 'a' 219 | }, 220 | { 221 | key: 2, 222 | value: 'b' 223 | }, 224 | { 225 | key: 3, 226 | value: 'c' 227 | } 228 | ] 229 | } 230 | } 231 | }; 232 | 233 | const ast = thriftParser(content); 234 | 235 | expect(ast).toEqual(expected); 236 | done(); 237 | }); 238 | 239 | it('parses a const for Map type with `;` separator (ListSeparator)', function(done) { 240 | const content = ` 241 | const map test = { 1: 'a'; 2: 'b'; 3: 'c' } 242 | `; 243 | 244 | const expected = { 245 | const: { 246 | test: { 247 | type: { 248 | name: 'map', 249 | keyType: 'i32', 250 | valueType: 'string' 251 | }, 252 | value: [ 253 | { 254 | key: 1, 255 | value: 'a' 256 | }, 257 | { 258 | key: 2, 259 | value: 'b' 260 | }, 261 | { 262 | key: 3, 263 | value: 'c' 264 | } 265 | ] 266 | } 267 | } 268 | }; 269 | 270 | const ast = thriftParser(content); 271 | 272 | expect(ast).toEqual(expected); 273 | done(); 274 | }); 275 | 276 | it('does not parse an invalid Map type', function(done) { 277 | const content = ` 278 | const map test = { 1: 'a', 2: 'b', 3: 'c' } 279 | `; 280 | 281 | expect(() => thriftParser(content)).toThrow(); 282 | done(); 283 | }); 284 | 285 | it.skip('does not parse an invalid Map value', function(done) { 286 | const content = ` 287 | const map test = [ 1, 2, 3] 288 | `; 289 | 290 | expect(() => thriftParser(content)).toThrow(); 291 | done(); 292 | }); 293 | 294 | it('parses a const as an array for Set types', function(done) { 295 | const content = ` 296 | const set test = [ 1, 2, 3 ] 297 | `; 298 | 299 | const expected = { 300 | const: { 301 | test: { 302 | type: { 303 | name: 'set', 304 | valueType: 'i32' 305 | }, 306 | value: [1, 2, 3] 307 | } 308 | } 309 | }; 310 | 311 | const ast = thriftParser(content); 312 | 313 | expect(ast).toEqual(expected); 314 | done(); 315 | }); 316 | 317 | it('parses a const for Set type with `;` separator (ListSeparator)', function(done) { 318 | const content = ` 319 | const set test = [ 1; 2; 3 ] 320 | `; 321 | 322 | const expected = { 323 | const: { 324 | test: { 325 | type: { 326 | name: 'set', 327 | valueType: 'i32' 328 | }, 329 | value: [1, 2, 3] 330 | } 331 | } 332 | }; 333 | 334 | const ast = thriftParser(content); 335 | 336 | expect(ast).toEqual(expected); 337 | done(); 338 | }); 339 | 340 | it('does not parse an invalid Set type', function(done) { 341 | const content = ` 342 | const set test = [ 1, 2, 3 ] 343 | `; 344 | 345 | expect(() => thriftParser(content)).toThrow(); 346 | done(); 347 | }); 348 | 349 | it.skip('does not parse an invalid Set value', function(done) { 350 | const content = ` 351 | const set test = { 1: 'a', 2: 'b', 3: 'c' } 352 | `; 353 | 354 | expect(() => thriftParser(content)).toThrow(); 355 | done(); 356 | }); 357 | 358 | it('parses a const as an array for List types', function(done) { 359 | const content = ` 360 | const list test = [ 1, 2, 3 ] 361 | `; 362 | 363 | const expected = { 364 | const: { 365 | test: { 366 | type: { 367 | name: 'list', 368 | valueType: 'i32' 369 | }, 370 | value: [1, 2, 3] 371 | } 372 | } 373 | }; 374 | 375 | const ast = thriftParser(content); 376 | 377 | expect(ast).toEqual(expected); 378 | done(); 379 | }); 380 | 381 | it('parses a const for List type with `;` separator (ListSeparator)', function(done) { 382 | const content = ` 383 | const list test = [ 1; 2; 3 ] 384 | `; 385 | 386 | const expected = { 387 | const: { 388 | test: { 389 | type: { 390 | name: 'list', 391 | valueType: 'i32' 392 | }, 393 | value: [1, 2, 3] 394 | } 395 | } 396 | }; 397 | 398 | const ast = thriftParser(content); 399 | 400 | expect(ast).toEqual(expected); 401 | done(); 402 | }); 403 | 404 | it('does not parse an invalid List type', function(done) { 405 | const content = ` 406 | const list test = [ 1, 2, 3 ] 407 | `; 408 | 409 | expect(() => thriftParser(content)).toThrow(); 410 | done(); 411 | }); 412 | 413 | it.skip('does not parse an invalid List value', function(done) { 414 | const content = ` 415 | const list test = { 1: 'a', 2: 'b', 3: 'c' } 416 | `; 417 | 418 | expect(() => thriftParser(content)).toThrow(); 419 | done(); 420 | }); 421 | 422 | it('parses a set of sets', function(done) { 423 | const content = ` 424 | const set> test = [['hello', 'world'], ['foo', 'bar']] 425 | `; 426 | 427 | const expected = { 428 | const: { 429 | test: { 430 | type: { 431 | name: 'set', 432 | valueType: { 433 | name: 'set', 434 | valueType: 'string' 435 | } 436 | }, 437 | value: [ 438 | ['hello', 'world'], 439 | ['foo', 'bar'] 440 | ] 441 | } 442 | } 443 | }; 444 | 445 | const ast = thriftParser(content); 446 | 447 | expect(ast).toEqual(expected); 448 | done(); 449 | }); 450 | 451 | it('parses a list of lists', function(done) { 452 | const content = ` 453 | const list> test = [['hello', 'world'], ['foo', 'bar']] 454 | `; 455 | 456 | const expected = { 457 | const: { 458 | test: { 459 | type: { 460 | name: 'list', 461 | valueType: { 462 | name: 'list', 463 | valueType: 'string' 464 | } 465 | }, 466 | value: [ 467 | ['hello', 'world'], 468 | ['foo', 'bar'] 469 | ] 470 | } 471 | } 472 | }; 473 | 474 | const ast = thriftParser(content); 475 | 476 | expect(ast).toEqual(expected); 477 | done(); 478 | }); 479 | 480 | it('parses a map of maps', function(done) { 481 | const content = ` 482 | const map> test = {'foo': {'hello': 'world'}} 483 | `; 484 | 485 | const expected = { 486 | const: { 487 | test: { 488 | type: { 489 | name: 'map', 490 | keyType: 'string', 491 | valueType: { 492 | name: 'map', 493 | keyType: 'string', 494 | valueType: 'string' 495 | } 496 | }, 497 | value: [ 498 | { 499 | key: 'foo', 500 | value: [ 501 | { 502 | key: 'hello', 503 | value: 'world' 504 | } 505 | ] 506 | } 507 | ] 508 | } 509 | } 510 | }; 511 | 512 | const ast = thriftParser(content); 513 | 514 | expect(ast).toEqual(expected); 515 | done(); 516 | }); 517 | 518 | it('parses a list of sets of maps', function(done) { 519 | const content = ` 520 | const list>> test = [[{'hello': 'world'}, {'foo': 'bar'}]] 521 | `; 522 | 523 | const expected = { 524 | const: { 525 | test: { 526 | type: { 527 | name: 'list', 528 | valueType: { 529 | name: 'set', 530 | valueType: { 531 | name: 'map', 532 | keyType: 'string', 533 | valueType: 'string' 534 | } 535 | } 536 | }, 537 | value: [ 538 | [ 539 | [ 540 | { 541 | key: 'hello', 542 | value: 'world' 543 | } 544 | ], 545 | [ 546 | { 547 | key: 'foo', 548 | value: 'bar' 549 | } 550 | ] 551 | ] 552 | ] 553 | } 554 | } 555 | }; 556 | 557 | const ast = thriftParser(content); 558 | 559 | expect(ast).toEqual(expected); 560 | done(); 561 | }); 562 | }); 563 | -------------------------------------------------------------------------------- /thrift-parser.js: -------------------------------------------------------------------------------- 1 | class ThriftFileParsingError extends SyntaxError { 2 | constructor({ message, context, line }) { 3 | super(message); 4 | this.context = context; 5 | this.line = line; 6 | this.name = 'THRIFT_FILE_PARSING_ERROR'; 7 | } 8 | } 9 | 10 | module.exports = (source, offset = 0) => { 11 | 12 | source += ''; 13 | 14 | let nCount = 0; 15 | let rCount = 0; 16 | 17 | let stack = []; 18 | 19 | const record = char => { 20 | if (char === '\r') rCount++; 21 | else if (char === '\n') nCount++; 22 | }; 23 | const save = () => stack.push({ offset, nCount, rCount }); 24 | const restore = () => ({ offset, nCount, rCount } = stack[stack.length - 1]); 25 | const drop = () => stack.pop(); 26 | 27 | const readAnyOne = (...args) => { 28 | save(); 29 | for (let i = 0; i < args.length; i++) { 30 | try { 31 | let result = args[i](); 32 | drop(); 33 | return result; 34 | } catch (ignore) { 35 | restore(); 36 | continue; 37 | } 38 | } 39 | drop(); 40 | throw 'Unexcepted Token'; 41 | }; 42 | 43 | const readUntilThrow = (transaction, key) => { 44 | let receiver = key ? {} : []; 45 | for (;;) { 46 | try { 47 | save(); 48 | let result = transaction(); 49 | key ? receiver[result[key]] = result : receiver.push(result); 50 | } catch (ignore) { 51 | restore(); 52 | return receiver; 53 | } finally { 54 | drop(); 55 | } 56 | } 57 | }; 58 | 59 | const readKeyword = word => { 60 | for (let i = 0; i < word.length; i++) { 61 | if (source[offset + i] !== word[i]) { 62 | throw 'Unexpected token "' + word + '"'; 63 | } 64 | } 65 | offset += word.length; 66 | readSpace(); 67 | return word; 68 | }; 69 | 70 | const readChar = (char) => { 71 | if (source[offset] !== char) throw 'Unexpected char "' + char + '"'; 72 | offset++; 73 | readSpace(); 74 | return char; 75 | }; 76 | 77 | const readNoop = () => {}; 78 | 79 | const readCommentMultiple = () => { 80 | let i = 0; 81 | if (source[offset + i++] !== '/' || source[offset + i++] !== '*') return false; 82 | do { 83 | record(source[offset + i]); 84 | while (offset + i < source.length && source[offset + i++] !== '*') { 85 | record(source[offset + i]); 86 | } 87 | } while (offset + i < source.length && source[offset + i] !== '/'); 88 | offset += i + 1; 89 | return true; 90 | }; 91 | 92 | const readCommentSharp = () => { 93 | let i = 0; 94 | if (source[offset + i++] !== '#') return false; 95 | while (source[offset + i] !== '\n' && source[offset + i] !== '\r') offset++; 96 | offset += i; 97 | return true; 98 | }; 99 | 100 | const readCommentDoubleSlash = () => { 101 | let i = 0; 102 | if (source[offset + i++] !== '/' || source[offset + i++] !== '/') return false; 103 | while (source[offset + i] !== '\n' && source[offset + i] !== '\r') offset++; 104 | offset += i; 105 | return true; 106 | }; 107 | 108 | const readSpace = () => { 109 | for (;;) { 110 | let byte = source[offset]; 111 | record(byte); 112 | if (byte === '\n' || byte === '\r' || byte === ' ' || byte === '\t') { 113 | offset++; 114 | } else { 115 | if (!readCommentMultiple() && !readCommentSharp() && !readCommentDoubleSlash()) return; 116 | } 117 | } 118 | }; 119 | 120 | const readComma = () => { 121 | if (source[offset] === ',' || source[offset] === ';') { 122 | offset++; 123 | readSpace(); 124 | return ','; 125 | } 126 | }; 127 | 128 | const readTypedef = () => { 129 | let subject = readKeyword('typedef'); 130 | let type = readType(); 131 | let name = readName(); 132 | readComma(); 133 | return {subject, type, name}; 134 | }; 135 | 136 | const readType = () => readAnyOne(readTypeMap, readTypeList, readTypeNormal); 137 | 138 | const readTypeMap = () => { 139 | let name = readKeyword('map'); 140 | readChar('<'); 141 | let keyType = readType(); 142 | readComma(); 143 | let valueType = readType(); 144 | readChar('>'); 145 | return {name, keyType, valueType}; 146 | }; 147 | 148 | const readTypeList = () => { 149 | let name = readAnyOne(() => readKeyword('list'), () => readKeyword('set')); 150 | readChar('<'); 151 | let valueType = readType(); 152 | readChar('>'); 153 | return {name, valueType}; 154 | }; 155 | 156 | const readTypeNormal = () => readName(); 157 | 158 | const readName = () => { 159 | let i = 0; 160 | let byte = source[offset]; 161 | while ( 162 | (byte >= 'a' && byte <= 'z') || 163 | byte === '.' || 164 | byte === '_' || 165 | (byte >= 'A' && byte <= 'Z') || 166 | (byte >= '0' && byte <= '9') 167 | ) byte = source[offset + ++i]; 168 | if (i === 0) throw 'Unexpected token on readName'; 169 | let value = source.slice(offset, offset += i); 170 | readSpace(); 171 | return value; 172 | }; 173 | 174 | const readScope = () => { 175 | let i = 0; 176 | let byte = source[offset]; 177 | while ( 178 | (byte >= 'a' && byte <= 'z') || 179 | byte === '_' || 180 | (byte >= 'A' && byte <= 'Z') || 181 | (byte >= '0' && byte <= '9') || 182 | (byte === '*') || 183 | (byte === '.') 184 | ) byte = source[offset + ++i]; 185 | if (i === 0) throw 'Unexpected token on readScope'; 186 | let value = source.slice(offset, offset += i); 187 | readSpace(); 188 | return value; 189 | }; 190 | 191 | const readNumberSign = () => { 192 | let result; 193 | if (source[offset] === '+' || source[offset] === '-') { 194 | result = source[offset]; 195 | offset++; 196 | } 197 | return result; 198 | }; 199 | 200 | const readIntegerValue = () => { 201 | let result = []; 202 | let sign = readNumberSign(); 203 | if (sign !== void 0) result.push(sign); 204 | 205 | for (; ;) { 206 | let byte = source[offset]; 207 | if ((byte >= '0' && byte <= '9')) { 208 | offset++; 209 | result.push(byte); 210 | } else if ( 211 | byte === 'E' || byte === 'e' || 212 | byte === 'X' || byte === 'x' || 213 | byte === '.' 214 | ) { 215 | throw `Unexpected token ${byte} for integer value`; 216 | } else { 217 | if (result.length) { 218 | readSpace(); 219 | return +result.join(''); 220 | } else { 221 | throw 'Unexpected token ' + byte; 222 | } 223 | } 224 | } 225 | }; 226 | 227 | const readDecimalValue = () => { 228 | let result = []; 229 | let sign = readNumberSign(); 230 | if (sign !== void 0) result.push(sign); 231 | 232 | for (;;) { 233 | let byte = source[offset]; 234 | if ((byte >= '0' && byte <= '9') || byte === '.') { 235 | offset++; 236 | result.push(byte); 237 | } else { 238 | if (result.length) { 239 | readSpace(); 240 | return +result.join(''); 241 | } else { 242 | throw 'Unexpected token ' + byte; 243 | } 244 | } 245 | } 246 | }; 247 | 248 | const readEnotationValue = () => { 249 | let result = []; 250 | if (source[offset] === '-') { 251 | result.push(source[offset]); 252 | offset++; 253 | } 254 | 255 | for (;;) { 256 | let byte = source[offset]; 257 | if ((byte >= '0' && byte <= '9') || byte === '.') { 258 | result.push(byte); 259 | offset++; 260 | } else { 261 | break; 262 | } 263 | } 264 | 265 | if (source[offset] !== 'e' && source[offset] !== 'E') throw 'Unexpected token'; 266 | result.push(source[offset]); 267 | offset++; 268 | 269 | for (;;) { 270 | let byte = source[offset]; 271 | if (byte >= '0' && byte <= '9') { 272 | offset++; 273 | result.push(byte); 274 | } else { 275 | if (result.length) { 276 | readSpace(); 277 | return +result.join(''); 278 | } else { 279 | throw 'Unexpected token ' + byte; 280 | } 281 | } 282 | } 283 | }; 284 | 285 | const readHexadecimalValue = () => { 286 | let result = []; 287 | if (source[offset] === '-') { 288 | result.push(source[offset]); 289 | offset++; 290 | } 291 | 292 | if (source[offset] !== '0') throw 'Unexpected token'; 293 | result.push(source[offset]); 294 | offset++; 295 | 296 | if (source[offset] !== 'x' && source[offset] !== 'X') throw 'Unexpected token'; 297 | result.push(source[offset]); 298 | offset++; 299 | 300 | for (;;) { 301 | let byte = source[offset]; 302 | if ( 303 | (byte >= '0' && byte <= '9') || 304 | (byte >= 'A' && byte <= 'F') || 305 | (byte >= 'a' && byte <= 'f') 306 | ) { 307 | offset++; 308 | result.push(byte); 309 | } else { 310 | if (result.length) { 311 | readSpace(); 312 | return +result.join(''); 313 | } else { 314 | throw 'Unexpected token ' + byte; 315 | } 316 | } 317 | } 318 | }; 319 | 320 | const readBooleanValue = () => JSON.parse(readAnyOne(() => readKeyword('true'), () => readKeyword('false'))); 321 | 322 | const readRefValue = () => { 323 | let list = [readName()]; 324 | readUntilThrow(() => { 325 | readChar('.'); 326 | list.push(readName()); 327 | }); 328 | return {'=': list}; 329 | }; 330 | 331 | const readStringValue = () => { 332 | let receiver = []; 333 | let start; 334 | while (source[offset] != null) { 335 | let byte = source[offset++]; 336 | if (receiver.length) { 337 | if (byte === start) { 338 | receiver.push(byte); 339 | readSpace(); 340 | return receiver.slice(1, -1).join(''); 341 | } else if (byte === '\\') { 342 | receiver.push(byte); 343 | offset++; 344 | receiver.push(source[offset++]); 345 | } else { 346 | receiver.push(byte); 347 | } 348 | } else { 349 | if (byte === '"' || byte === '\'') { 350 | start = byte; 351 | receiver.push(byte); 352 | } else { 353 | throw 'Unexpected token ILLEGAL'; 354 | } 355 | } 356 | } 357 | throw 'Unterminated string value'; 358 | }; 359 | 360 | const readListValue = () => { 361 | readChar('['); 362 | let list = readUntilThrow(() => { 363 | let value = readValue(); 364 | readComma(); 365 | return value; 366 | }); 367 | readChar(']'); 368 | return list; 369 | }; 370 | 371 | const readMapValue = () => { 372 | readChar('{'); 373 | let list = readUntilThrow(() => { 374 | let key = readValue(); 375 | readChar(':'); 376 | let value = readValue(); 377 | readComma(); 378 | return {key, value}; 379 | }); 380 | readChar('}'); 381 | return list; 382 | }; 383 | 384 | const readValue = () => readAnyOne( 385 | readHexadecimalValue, // This coming before readNumberValue is important, unfortunately 386 | readEnotationValue, // This also needs to come before readNumberValue 387 | readDecimalValue, 388 | readIntegerValue, 389 | readStringValue, 390 | readBooleanValue, 391 | readListValue, 392 | readMapValue, 393 | readRefValue 394 | ); 395 | 396 | const readConst = () => { 397 | let subject = readKeyword('const'); 398 | let type = readType(); 399 | let name = readName(); 400 | readChar('='); 401 | let value = readValue(); 402 | readComma(); 403 | return {subject, type, name, value}; 404 | }; 405 | 406 | const readEnum = () => { 407 | let subject = readKeyword('enum'); 408 | let name = readName(); 409 | let items = readEnumBlock(); 410 | return {subject, name, items}; 411 | }; 412 | 413 | const readEnumBlock = () => { 414 | readChar('{'); 415 | let receiver = readUntilThrow(readEnumItem); 416 | readChar('}'); 417 | return receiver; 418 | }; 419 | 420 | const readEnumItem = () => { 421 | let name = readName(); 422 | let value = readEnumValue(); 423 | readComma(); 424 | let result = {name}; 425 | if (value !== void 0) result.value = value; 426 | return result; 427 | }; 428 | 429 | const readEnumValue = () => { 430 | let beginning = offset; 431 | try { 432 | readChar('='); 433 | } catch (ignore) { 434 | offset = beginning; 435 | return; 436 | } 437 | return readAnyOne(readHexadecimalValue, readIntegerValue); 438 | }; 439 | 440 | const readAssign = () => { 441 | try { 442 | save(); 443 | readChar('='); 444 | return readValue(); 445 | } catch (ignore) { 446 | restore(); 447 | } finally { 448 | drop(); 449 | } 450 | }; 451 | 452 | const readStruct = () => { 453 | let subject = readKeyword('struct'); 454 | let name = readName(); 455 | let items = readStructLikeBlock(); 456 | return {subject, name, items}; 457 | }; 458 | 459 | const readStructLikeBlock = () => { 460 | readChar('{'); 461 | let receiver = readUntilThrow(readStructLikeItem); 462 | readChar('}'); 463 | return receiver; 464 | }; 465 | 466 | const readStructLikeItem = () => { 467 | let id; 468 | try { 469 | id = readAnyOne(readHexadecimalValue, readIntegerValue); 470 | readChar(':'); 471 | } catch (err) { 472 | 473 | } 474 | 475 | let option = readAnyOne(() => readKeyword('required'), () => readKeyword('optional'), readNoop); 476 | let type = readType(); 477 | let name = readName(); 478 | let defaultValue = readAssign(); 479 | readComma(); 480 | let result = {type, name}; 481 | if (id !== void 0) result.id = id; 482 | if (option !== void 0) result.option = option; 483 | if (defaultValue !== void 0) result.defaultValue = defaultValue; 484 | return result; 485 | }; 486 | 487 | const readUnion = () => { 488 | let subject = readKeyword('union'); 489 | let name = readName(); 490 | let items = readStructLikeBlock(); 491 | return {subject, name, items}; 492 | }; 493 | 494 | const readException = () => { 495 | let subject = readKeyword('exception'); 496 | let name = readName(); 497 | let items = readStructLikeBlock(); 498 | return {subject, name, items}; 499 | }; 500 | 501 | const readExtends = () => { 502 | try { 503 | save(); 504 | readKeyword('extends'); 505 | let name = readRefValue()['='].join('.'); 506 | return name; 507 | } catch (ignore) { 508 | restore(); 509 | return; 510 | } finally { 511 | drop(); 512 | } 513 | }; 514 | 515 | const readService = () => { 516 | let subject = readKeyword('service'); 517 | let name = readName(); 518 | let extend = readExtends(); // extends is a reserved keyword 519 | let functions = readServiceBlock(); 520 | let result = {subject, name}; 521 | if (extend !== void 0) result.extends = extend; 522 | if (functions !== void 0) result.functions = functions; 523 | return result; 524 | }; 525 | 526 | const readNamespace = () => { 527 | let subject = readKeyword('namespace'); 528 | let name = readScope(); 529 | let serviceName = readRefValue()['='].join('.'); 530 | return {subject, name, serviceName}; 531 | }; 532 | 533 | const readInclude = () => { 534 | let subject = readKeyword('include'); 535 | readSpace(); 536 | let includePath = readQuotation(); 537 | let name = includePath.replace(/^.*?([^/\\]*?)(?:\.thrift)?$/, '$1'); 538 | readSpace(); 539 | return {subject, name, path: includePath}; 540 | }; 541 | 542 | const readQuotation = () => { 543 | let quoteMatch; 544 | if (source[offset] === '"' || source[offset] === '\'') { 545 | quoteMatch = source[offset]; 546 | offset++; 547 | } else { 548 | throw 'include error'; 549 | } 550 | let i = offset; 551 | // Read until it finds a matching quote or end-of-file 552 | while (source[i] !== quoteMatch && source[i] != null) { 553 | i++; 554 | } 555 | if (source[i] === quoteMatch) { 556 | let value = source.slice(offset, i); 557 | offset = i + 1; 558 | return value; 559 | } else { 560 | throw 'include error'; 561 | } 562 | }; 563 | 564 | const readServiceBlock = () => { 565 | readChar('{'); 566 | let receiver = readUntilThrow(readServiceItem, 'name'); 567 | readChar('}'); 568 | return receiver; 569 | }; 570 | 571 | const readOneway = () => readKeyword('oneway'); 572 | 573 | const readServiceItem = () => { 574 | let oneway = !!readAnyOne(readOneway, readNoop); 575 | let type = readType(); 576 | let name = readName(); 577 | let args = readServiceArgs(); 578 | let throws = readServiceThrow(); 579 | readComma(); 580 | return {type, name, args, throws, oneway}; 581 | }; 582 | 583 | const readServiceArgs = () => { 584 | readChar('('); 585 | let receiver = readUntilThrow(readStructLikeItem); 586 | readChar(')'); 587 | readSpace(); 588 | return receiver; 589 | }; 590 | 591 | const readServiceThrow = () => { 592 | try { 593 | save(); 594 | readKeyword('throws'); 595 | return readServiceArgs(); 596 | } catch (ignore) { 597 | restore(); 598 | return []; 599 | } finally { 600 | drop(); 601 | } 602 | }; 603 | 604 | const readSubject = () => { 605 | return readAnyOne(readTypedef, readConst, readEnum, readStruct, readUnion, readException, readService, readNamespace, readInclude); 606 | }; 607 | 608 | const readThrift = () => { 609 | readSpace(); 610 | let storage = {}; 611 | for (;;) { 612 | try { 613 | let block = readSubject(); 614 | let {subject, name} = block; 615 | if (!storage[subject]) storage[subject] = {}; 616 | delete block.subject; 617 | delete block.name; 618 | switch (subject) { 619 | case 'exception': 620 | case 'struct': 621 | case 'union': 622 | storage[subject][name] = block.items; 623 | break; 624 | default: 625 | storage[subject][name] = block; 626 | } 627 | } catch (message) { 628 | let context = source.slice(offset, offset + 50); 629 | let line = Math.max(nCount, rCount) + 1; 630 | throw new ThriftFileParsingError({ message, context, line }); 631 | } finally { 632 | if (source.length === offset) break; 633 | } 634 | } 635 | return storage; 636 | }; 637 | 638 | return readThrift(); 639 | 640 | }; 641 | -------------------------------------------------------------------------------- /tests/unions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('unions', function() { 8 | 9 | it('parses simple union', function(done) { 10 | const content = ` 11 | union Test { 12 | 1: i16 test1 13 | } 14 | `; 15 | 16 | const expected = { 17 | union: { 18 | Test: [ 19 | { 20 | id: 1, 21 | type: 'i16', 22 | name: 'test1' 23 | } 24 | ] 25 | } 26 | }; 27 | 28 | const ast = thriftParser(content); 29 | 30 | expect(ast).toEqual(expected); 31 | done(); 32 | }); 33 | 34 | it('parses a union with a required field', function(done) { 35 | const content = ` 36 | union Test { 37 | 1: required i16 test1 38 | } 39 | `; 40 | 41 | const expected = { 42 | union: { 43 | Test: [ 44 | { 45 | id: 1, 46 | type: 'i16', 47 | name: 'test1', 48 | option: 'required' 49 | } 50 | ] 51 | } 52 | }; 53 | 54 | const ast = thriftParser(content); 55 | 56 | expect(ast).toEqual(expected); 57 | done(); 58 | }); 59 | 60 | it('parses a union with an optional field', function(done) { 61 | const content = ` 62 | union Test { 63 | 1: optional i16 test1 64 | } 65 | `; 66 | 67 | const expected = { 68 | union: { 69 | Test: [ 70 | { 71 | id: 1, 72 | type: 'i16', 73 | name: 'test1', 74 | option: 'optional' 75 | } 76 | ] 77 | } 78 | }; 79 | 80 | const ast = thriftParser(content); 81 | 82 | expect(ast).toEqual(expected); 83 | done(); 84 | }); 85 | 86 | it('parses a union with mixed option fields', function(done) { 87 | const content = ` 88 | union Test { 89 | 1: required i16 test1 90 | 2: i16 test2 91 | 3: optional i16 test3 92 | } 93 | `; 94 | 95 | const expected = { 96 | union: { 97 | Test: [ 98 | { 99 | id: 1, 100 | type: 'i16', 101 | name: 'test1', 102 | option: 'required' 103 | }, 104 | { 105 | id: 2, 106 | type: 'i16', 107 | name: 'test2' 108 | }, 109 | { 110 | id: 3, 111 | type: 'i16', 112 | name: 'test3', 113 | option: 'optional' 114 | } 115 | ] 116 | } 117 | }; 118 | 119 | const ast = thriftParser(content); 120 | 121 | expect(ast).toEqual(expected); 122 | done(); 123 | }); 124 | 125 | it('parses custom types in union field', function(done) { 126 | const content = ` 127 | union Test { 128 | 1: TestType test1 129 | } 130 | `; 131 | 132 | const expected = { 133 | union: { 134 | Test: [ 135 | { 136 | id: 1, 137 | type: 'TestType', 138 | name: 'test1' 139 | } 140 | ] 141 | } 142 | }; 143 | 144 | const ast = thriftParser(content); 145 | 146 | expect(ast).toEqual(expected); 147 | done(); 148 | }); 149 | 150 | it('parses a union with default values', function(done) { 151 | const content = ` 152 | union Test { 153 | 1: string test1 = 'test' 154 | } 155 | `; 156 | 157 | const expected = { 158 | union: { 159 | Test: [ 160 | { 161 | id: 1, 162 | type: 'string', 163 | name: 'test1', 164 | defaultValue: 'test' 165 | } 166 | ] 167 | } 168 | }; 169 | 170 | const ast = thriftParser(content); 171 | 172 | expect(ast).toEqual(expected); 173 | done(); 174 | }); 175 | 176 | it('parses a union with mixed default/no-default values', function(done) { 177 | const content = ` 178 | union Test { 179 | 1: string test1 = 'test' 180 | 2: i16 test2 181 | } 182 | `; 183 | 184 | const expected = { 185 | union: { 186 | Test: [ 187 | { 188 | id: 1, 189 | type: 'string', 190 | name: 'test1', 191 | defaultValue: 'test' 192 | }, 193 | { 194 | id: 2, 195 | type: 'i16', 196 | name: 'test2' 197 | } 198 | ] 199 | } 200 | }; 201 | 202 | const ast = thriftParser(content); 203 | 204 | expect(ast).toEqual(expected); 205 | done(); 206 | }); 207 | 208 | it('parses a union containing a field with a hex FieldID', function(done) { 209 | const content = ` 210 | union Test { 211 | 0x01: string test1 212 | } 213 | `; 214 | 215 | const expected = { 216 | union: { 217 | Test: [ 218 | { 219 | id: 1, 220 | type: 'string', 221 | name: 'test1' 222 | } 223 | ] 224 | } 225 | }; 226 | 227 | const ast = thriftParser(content); 228 | 229 | expect(ast).toEqual(expected); 230 | done(); 231 | }); 232 | 233 | it('parses a union containing a field with a negative FieldID', function(done) { 234 | const content = ` 235 | union Test { 236 | -1: string test1 237 | } 238 | `; 239 | 240 | const expected = { 241 | union: { 242 | Test: [ 243 | { 244 | id: -1, 245 | type: 'string', 246 | name: 'test1' 247 | } 248 | ] 249 | } 250 | }; 251 | 252 | const ast = thriftParser(content); 253 | 254 | expect(ast).toEqual(expected); 255 | done(); 256 | }); 257 | 258 | it('parses a union containing a field with a positive FieldID with `+`', function(done) { 259 | const content = ` 260 | union Test { 261 | +1: string test1 262 | } 263 | `; 264 | 265 | const expected = { 266 | union: { 267 | Test: [ 268 | { 269 | id: 1, 270 | type: 'string', 271 | name: 'test1' 272 | } 273 | ] 274 | } 275 | }; 276 | 277 | const ast = thriftParser(content); 278 | 279 | expect(ast).toEqual(expected); 280 | done(); 281 | }); 282 | 283 | it('parses a union containing a field without a FieldID', function(done) { 284 | const content = ` 285 | union Test { 286 | string test1 287 | } 288 | `; 289 | 290 | const expected = { 291 | union: { 292 | Test: [ 293 | { 294 | type: 'string', 295 | name: 'test1' 296 | } 297 | ] 298 | } 299 | }; 300 | 301 | const ast = thriftParser(content); 302 | 303 | expect(ast).toEqual(expected); 304 | done(); 305 | }); 306 | 307 | it('parses a union containing a field without a FieldID but with required', function(done) { 308 | const content = ` 309 | union Test { 310 | required string test1 311 | } 312 | `; 313 | 314 | const expected = { 315 | union: { 316 | Test: [ 317 | { 318 | type: 'string', 319 | name: 'test1', 320 | option: 'required' 321 | } 322 | ] 323 | } 324 | }; 325 | 326 | const ast = thriftParser(content); 327 | 328 | expect(ast).toEqual(expected); 329 | done(); 330 | }); 331 | 332 | it('parses a union containing mixed fields with/without a FieldID', function(done) { 333 | const content = ` 334 | union Test { 335 | string test1 336 | 2: string test2 337 | } 338 | `; 339 | 340 | const expected = { 341 | union: { 342 | Test: [ 343 | { 344 | type: 'string', 345 | name: 'test1' 346 | }, 347 | { 348 | id: 2, 349 | type: 'string', 350 | name: 'test2' 351 | } 352 | ] 353 | } 354 | }; 355 | 356 | const ast = thriftParser(content); 357 | 358 | expect(ast).toEqual(expected); 359 | done(); 360 | }); 361 | 362 | it('parses a union with values that end in `,` (ListSeparator)', function(done) { 363 | const content = ` 364 | union Test { 365 | 1: string test1, 366 | 2: string test2 = 'test', 367 | } 368 | `; 369 | 370 | const expected = { 371 | union: { 372 | Test: [ 373 | { 374 | id: 1, 375 | type: 'string', 376 | name: 'test1' 377 | }, 378 | { 379 | id: 2, 380 | type: 'string', 381 | name: 'test2', 382 | defaultValue: 'test' 383 | } 384 | ] 385 | } 386 | }; 387 | 388 | const ast = thriftParser(content); 389 | 390 | expect(ast).toEqual(expected); 391 | done(); 392 | }); 393 | 394 | it('parses a union with values that end in `;` (ListSeparator)', function(done) { 395 | const content = ` 396 | union Test { 397 | 1: string test1; 398 | 2: string test2 = 'test'; 399 | } 400 | `; 401 | 402 | const expected = { 403 | union: { 404 | Test: [ 405 | { 406 | id: 1, 407 | type: 'string', 408 | name: 'test1' 409 | }, 410 | { 411 | id: 2, 412 | type: 'string', 413 | name: 'test2', 414 | defaultValue: 'test' 415 | } 416 | ] 417 | } 418 | }; 419 | 420 | const ast = thriftParser(content); 421 | 422 | expect(ast).toEqual(expected); 423 | done(); 424 | }); 425 | 426 | it('parses a union with values that end in `;` and `,` (ListSeparator)', function(done) { 427 | const content = ` 428 | union Test { 429 | 1: string test1, 430 | 2: string test2 = 'test'; 431 | } 432 | `; 433 | 434 | const expected = { 435 | union: { 436 | Test: [ 437 | { 438 | id: 1, 439 | type: 'string', 440 | name: 'test1' 441 | }, 442 | { 443 | id: 2, 444 | type: 'string', 445 | name: 'test2', 446 | defaultValue: 'test' 447 | } 448 | ] 449 | } 450 | }; 451 | 452 | const ast = thriftParser(content); 453 | 454 | expect(ast).toEqual(expected); 455 | done(); 456 | }); 457 | 458 | it('does not parse a union containing a field without a type', function(done) { 459 | const content = ` 460 | union Test { 461 | 1: test 462 | } 463 | `; 464 | 465 | expect(() => thriftParser(content)).toThrow(); 466 | done(); 467 | }); 468 | 469 | it('does not parse a union containing a field with required & without a type', function(done) { 470 | const content = ` 471 | union Test { 472 | 1: required test 473 | } 474 | `; 475 | 476 | expect(() => thriftParser(content)).toThrow(); 477 | done(); 478 | }); 479 | 480 | it('does not parse a union containing a field with default & without a type', function(done) { 481 | const content = ` 482 | union Test { 483 | 1: test = 'test' 484 | } 485 | `; 486 | 487 | expect(() => thriftParser(content)).toThrow(); 488 | done(); 489 | }); 490 | 491 | it('does not parse a union containing a field with invalid default', function(done) { 492 | const content = ` 493 | union Test { 494 | 1: string test = 'test 495 | } 496 | `; 497 | 498 | expect(() => thriftParser(content)).toThrow(); 499 | done(); 500 | }); 501 | 502 | it('does not parse a union containing a field with default containing mixed quotes', function(done) { 503 | const content = ` 504 | union Test { 505 | 1: string test = 'test" 506 | } 507 | `; 508 | 509 | expect(() => thriftParser(content)).toThrow(); 510 | done(); 511 | }); 512 | 513 | it('does not parse a union containing a field with string FieldID', function(done) { 514 | const content = ` 515 | union Test { 516 | test: string test 517 | } 518 | `; 519 | 520 | expect(() => thriftParser(content)).toThrow(); 521 | done(); 522 | }); 523 | 524 | it('does not parse a union containing a field with e-notation FieldID', function(done) { 525 | const content = ` 526 | union Test { 527 | 1e2: string test 528 | } 529 | `; 530 | 531 | expect(() => thriftParser(content)).toThrow(); 532 | done(); 533 | }); 534 | 535 | it('does not parse a union containing a field with decimal FieldID', function(done) { 536 | const content = ` 537 | union Test { 538 | 1.2: string test 539 | } 540 | `; 541 | 542 | expect(() => thriftParser(content)).toThrow(); 543 | done(); 544 | }); 545 | 546 | it('does not parse a union containing a field with invalid option', function(done) { 547 | const content = ` 548 | union Test { 549 | 1: failure string test 550 | } 551 | `; 552 | 553 | expect(() => thriftParser(content)).toThrow(); 554 | done(); 555 | }); 556 | 557 | it('parses a union containing a field with a Map type', function(done) { 558 | const content = ` 559 | union Test { 560 | 1: map test = { 1: 'a', 2: 'b' } 561 | } 562 | `; 563 | 564 | const expected = { 565 | union: { 566 | Test: [ 567 | { 568 | id: 1, 569 | type: { 570 | name: 'map', 571 | keyType: 'i16', 572 | valueType: 'string' 573 | }, 574 | name: 'test', 575 | defaultValue: [ 576 | { 577 | key: 1, 578 | value: 'a' 579 | }, 580 | { 581 | key: 2, 582 | value: 'b' 583 | } 584 | ] 585 | } 586 | ] 587 | } 588 | }; 589 | 590 | const ast = thriftParser(content); 591 | 592 | expect(ast).toEqual(expected); 593 | done(); 594 | }); 595 | 596 | it('does not parse a union containing a field with an invalid Map type', function(done) { 597 | const content = ` 598 | union Test { 599 | 1: map test 600 | } 601 | `; 602 | 603 | expect(() => thriftParser(content)).toThrow(); 604 | done(); 605 | }); 606 | 607 | it.skip('does not parse a union containing a field with a Map type but invalid default', function(done) { 608 | const content = ` 609 | union Test { 610 | 1: map test = [1,2] 611 | } 612 | `; 613 | 614 | expect(() => thriftParser(content)).toThrow(); 615 | done(); 616 | }); 617 | 618 | it('parses a union containing a field with a Set type', function(done) { 619 | const content = ` 620 | union Test { 621 | 1: set test = [1,2] 622 | } 623 | `; 624 | 625 | const expected = { 626 | union: { 627 | Test: [ 628 | { 629 | id: 1, 630 | type: { 631 | name: 'set', 632 | valueType: 'i16' 633 | }, 634 | name: 'test', 635 | defaultValue: [ 636 | 1, 637 | 2 638 | ] 639 | } 640 | ] 641 | } 642 | }; 643 | 644 | const ast = thriftParser(content); 645 | 646 | expect(ast).toEqual(expected); 647 | done(); 648 | }); 649 | 650 | it('does not parse a union containing a field with an invalid Set type', function(done) { 651 | const content = ` 652 | union Test { 653 | 1: set test = [1,2] 654 | } 655 | `; 656 | 657 | expect(() => thriftParser(content)).toThrow(); 658 | done(); 659 | }); 660 | 661 | it.skip('does not parse a union containing a field with a Set type but invalid default', function(done) { 662 | const content = ` 663 | union Test { 664 | 1: set test = { 1: 'a', 2: 'b' } 665 | } 666 | `; 667 | 668 | expect(() => thriftParser(content)).toThrow(); 669 | done(); 670 | }); 671 | 672 | it('parses a union containing a field with a List type', function(done) { 673 | const content = ` 674 | union Test { 675 | 1: list test = [1,2] 676 | } 677 | `; 678 | 679 | const expected = { 680 | union: { 681 | Test: [ 682 | { 683 | id: 1, 684 | type: { 685 | name: 'list', 686 | valueType: 'i16' 687 | }, 688 | name: 'test', 689 | defaultValue: [ 690 | 1, 691 | 2 692 | ] 693 | } 694 | ] 695 | } 696 | }; 697 | 698 | const ast = thriftParser(content); 699 | 700 | expect(ast).toEqual(expected); 701 | done(); 702 | }); 703 | 704 | it('does not parse a union containing a field with an invalid List type', function(done) { 705 | const content = ` 706 | union Test { 707 | 1: list test = [1,2] 708 | } 709 | `; 710 | 711 | expect(() => thriftParser(content)).toThrow(); 712 | done(); 713 | }); 714 | 715 | it.skip('does not parse a union containing a field with a List type but invalid default', function(done) { 716 | const content = ` 717 | union Test { 718 | 1: list test = { 1: 'a', 2: 'b' } 719 | } 720 | `; 721 | 722 | expect(() => thriftParser(content)).toThrow(); 723 | done(); 724 | }); 725 | 726 | it('does not parse a union containing a field with an invalid default assignment', function(done) { 727 | const content = ` 728 | union Test { 729 | 1: string test = 730 | } 731 | `; 732 | 733 | expect(() => thriftParser(content)).toThrow(); 734 | done(); 735 | }); 736 | 737 | it('parses a union that starts with `_` (Identifier)', function(done) { 738 | const content = ` 739 | union _Test { 740 | 1: string test1 741 | } 742 | `; 743 | 744 | const expected = { 745 | union: { 746 | _Test: [ 747 | { 748 | id: 1, 749 | type: 'string', 750 | name: 'test1' 751 | } 752 | ] 753 | } 754 | }; 755 | 756 | const ast = thriftParser(content); 757 | 758 | expect(ast).toEqual(expected); 759 | done(); 760 | }); 761 | 762 | it('parses a union containing `_` (Identifier)', function(done) { 763 | const content = ` 764 | union Te_st { 765 | 1: string test1 766 | } 767 | `; 768 | 769 | const expected = { 770 | union: { 771 | Te_st: [ 772 | { 773 | id: 1, 774 | type: 'string', 775 | name: 'test1' 776 | } 777 | ] 778 | } 779 | }; 780 | 781 | const ast = thriftParser(content); 782 | 783 | expect(ast).toEqual(expected); 784 | done(); 785 | }); 786 | 787 | it('parses a union containing `.` (Identifier)', function(done) { 788 | const content = ` 789 | union Te.st { 790 | 1: string test1 791 | } 792 | `; 793 | 794 | const expected = { 795 | union: { 796 | 'Te.st': [ 797 | { 798 | id: 1, 799 | type: 'string', 800 | name: 'test1' 801 | } 802 | ] 803 | } 804 | }; 805 | 806 | const ast = thriftParser(content); 807 | 808 | expect(ast).toEqual(expected); 809 | done(); 810 | }); 811 | 812 | it('parses a union containing `.`, `_`, letters and numbers (Identifier)', function(done) { 813 | const content = ` 814 | union Te.st_123 { 815 | 1: string test1 816 | } 817 | `; 818 | 819 | const expected = { 820 | union: { 821 | 'Te.st_123': [ 822 | { 823 | id: 1, 824 | type: 'string', 825 | name: 'test1' 826 | } 827 | ] 828 | } 829 | }; 830 | 831 | const ast = thriftParser(content); 832 | 833 | expect(ast).toEqual(expected); 834 | done(); 835 | }); 836 | 837 | it('parses a union with a field that starts with `_` (Identifier)', function(done) { 838 | const content = ` 839 | union Test { 840 | 1: string _test1 841 | } 842 | `; 843 | 844 | const expected = { 845 | union: { 846 | Test: [ 847 | { 848 | id: 1, 849 | type: 'string', 850 | name: '_test1' 851 | } 852 | ] 853 | } 854 | }; 855 | 856 | const ast = thriftParser(content); 857 | 858 | expect(ast).toEqual(expected); 859 | done(); 860 | }); 861 | 862 | it('parses a union with a field containing `_` (Identifier)', function(done) { 863 | const content = ` 864 | union Test { 865 | 1: string test_1 866 | } 867 | `; 868 | 869 | const expected = { 870 | union: { 871 | Test: [ 872 | { 873 | id: 1, 874 | type: 'string', 875 | name: 'test_1' 876 | } 877 | ] 878 | } 879 | }; 880 | 881 | const ast = thriftParser(content); 882 | 883 | expect(ast).toEqual(expected); 884 | done(); 885 | }); 886 | 887 | it('parses a union with a field containing `.` (Identifier)', function(done) { 888 | const content = ` 889 | union Test { 890 | 1: string test.1 891 | } 892 | `; 893 | 894 | const expected = { 895 | union: { 896 | Test: [ 897 | { 898 | id: 1, 899 | type: 'string', 900 | name: 'test.1' 901 | } 902 | ] 903 | } 904 | }; 905 | 906 | const ast = thriftParser(content); 907 | 908 | expect(ast).toEqual(expected); 909 | done(); 910 | }); 911 | 912 | it('parses a union with a field containing `.`, `_`, letters and numbers (Identifier)', function(done) { 913 | const content = ` 914 | union Test { 915 | 1: string te.st_1 916 | } 917 | `; 918 | 919 | const expected = { 920 | union: { 921 | Test: [ 922 | { 923 | id: 1, 924 | type: 'string', 925 | name: 'te.st_1' 926 | } 927 | ] 928 | } 929 | }; 930 | 931 | const ast = thriftParser(content); 932 | 933 | expect(ast).toEqual(expected); 934 | done(); 935 | }); 936 | }); 937 | -------------------------------------------------------------------------------- /tests/structs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('structs', function() { 8 | 9 | it('parses simple struct', function(done) { 10 | const content = ` 11 | struct Test { 12 | 1: i16 test1 13 | } 14 | `; 15 | 16 | const expected = { 17 | struct: { 18 | Test: [ 19 | { 20 | id: 1, 21 | type: 'i16', 22 | name: 'test1' 23 | } 24 | ] 25 | } 26 | }; 27 | 28 | const ast = thriftParser(content); 29 | 30 | expect(ast).toEqual(expected); 31 | done(); 32 | }); 33 | 34 | it('parses a struct with a required field', function(done) { 35 | const content = ` 36 | struct Test { 37 | 1: required i16 test1 38 | } 39 | `; 40 | 41 | const expected = { 42 | struct: { 43 | Test: [ 44 | { 45 | id: 1, 46 | type: 'i16', 47 | name: 'test1', 48 | option: 'required' 49 | } 50 | ] 51 | } 52 | }; 53 | 54 | const ast = thriftParser(content); 55 | 56 | expect(ast).toEqual(expected); 57 | done(); 58 | }); 59 | 60 | it('parses a struct with an optional field', function(done) { 61 | const content = ` 62 | struct Test { 63 | 1: optional i16 test1 64 | } 65 | `; 66 | 67 | const expected = { 68 | struct: { 69 | Test: [ 70 | { 71 | id: 1, 72 | type: 'i16', 73 | name: 'test1', 74 | option: 'optional' 75 | } 76 | ] 77 | } 78 | }; 79 | 80 | const ast = thriftParser(content); 81 | 82 | expect(ast).toEqual(expected); 83 | done(); 84 | }); 85 | 86 | it('parses a struct with mixed option fields', function(done) { 87 | const content = ` 88 | struct Test { 89 | 1: required i16 test1 90 | 2: i16 test2 91 | 3: optional i16 test3 92 | } 93 | `; 94 | 95 | const expected = { 96 | struct: { 97 | Test: [ 98 | { 99 | id: 1, 100 | type: 'i16', 101 | name: 'test1', 102 | option: 'required' 103 | }, 104 | { 105 | id: 2, 106 | type: 'i16', 107 | name: 'test2' 108 | }, 109 | { 110 | id: 3, 111 | type: 'i16', 112 | name: 'test3', 113 | option: 'optional' 114 | } 115 | ] 116 | } 117 | }; 118 | 119 | const ast = thriftParser(content); 120 | 121 | expect(ast).toEqual(expected); 122 | done(); 123 | }); 124 | 125 | it('parses custom types in struct field', function(done) { 126 | const content = ` 127 | struct Test { 128 | 1: TestType test1 129 | } 130 | `; 131 | 132 | const expected = { 133 | struct: { 134 | Test: [ 135 | { 136 | id: 1, 137 | type: 'TestType', 138 | name: 'test1' 139 | } 140 | ] 141 | } 142 | }; 143 | 144 | const ast = thriftParser(content); 145 | 146 | expect(ast).toEqual(expected); 147 | done(); 148 | }); 149 | 150 | it('parses a struct with default values', function(done) { 151 | const content = ` 152 | struct Test { 153 | 1: string test1 = 'test' 154 | } 155 | `; 156 | 157 | const expected = { 158 | struct: { 159 | Test: [ 160 | { 161 | id: 1, 162 | type: 'string', 163 | name: 'test1', 164 | defaultValue: 'test' 165 | } 166 | ] 167 | } 168 | }; 169 | 170 | const ast = thriftParser(content); 171 | 172 | expect(ast).toEqual(expected); 173 | done(); 174 | }); 175 | 176 | it('parses a struct with mixed default/no-default values', function(done) { 177 | const content = ` 178 | struct Test { 179 | 1: string test1 = 'test' 180 | 2: i16 test2 181 | } 182 | `; 183 | 184 | const expected = { 185 | struct: { 186 | Test: [ 187 | { 188 | id: 1, 189 | type: 'string', 190 | name: 'test1', 191 | defaultValue: 'test' 192 | }, 193 | { 194 | id: 2, 195 | type: 'i16', 196 | name: 'test2' 197 | } 198 | ] 199 | } 200 | }; 201 | 202 | const ast = thriftParser(content); 203 | 204 | expect(ast).toEqual(expected); 205 | done(); 206 | }); 207 | 208 | it('parses a struct containing a field with a hex FieldID', function(done) { 209 | const content = ` 210 | struct Test { 211 | 0x01: string test1 212 | } 213 | `; 214 | 215 | const expected = { 216 | struct: { 217 | Test: [ 218 | { 219 | id: 1, 220 | type: 'string', 221 | name: 'test1' 222 | } 223 | ] 224 | } 225 | }; 226 | 227 | const ast = thriftParser(content); 228 | 229 | expect(ast).toEqual(expected); 230 | done(); 231 | }); 232 | 233 | it('parses a struct containing a field with a negative FieldID', function(done) { 234 | const content = ` 235 | struct Test { 236 | -1: string test1 237 | } 238 | `; 239 | 240 | const expected = { 241 | struct: { 242 | Test: [ 243 | { 244 | id: -1, 245 | type: 'string', 246 | name: 'test1' 247 | } 248 | ] 249 | } 250 | }; 251 | 252 | const ast = thriftParser(content); 253 | 254 | expect(ast).toEqual(expected); 255 | done(); 256 | }); 257 | 258 | it('parses a struct containing a field with a positive FieldID with `+`', function(done) { 259 | const content = ` 260 | struct Test { 261 | +1: string test1 262 | } 263 | `; 264 | 265 | const expected = { 266 | struct: { 267 | Test: [ 268 | { 269 | id: 1, 270 | type: 'string', 271 | name: 'test1' 272 | } 273 | ] 274 | } 275 | }; 276 | 277 | const ast = thriftParser(content); 278 | 279 | expect(ast).toEqual(expected); 280 | done(); 281 | }); 282 | 283 | it('parses a struct containing a field without a FieldID', function(done) { 284 | const content = ` 285 | struct Test { 286 | string test1 287 | } 288 | `; 289 | 290 | const expected = { 291 | struct: { 292 | Test: [ 293 | { 294 | type: 'string', 295 | name: 'test1' 296 | } 297 | ] 298 | } 299 | }; 300 | 301 | const ast = thriftParser(content); 302 | 303 | expect(ast).toEqual(expected); 304 | done(); 305 | }); 306 | 307 | it('parses a struct containing a field without a FieldID but with required', function(done) { 308 | const content = ` 309 | struct Test { 310 | required string test1 311 | } 312 | `; 313 | 314 | const expected = { 315 | struct: { 316 | Test: [ 317 | { 318 | type: 'string', 319 | name: 'test1', 320 | option: 'required' 321 | } 322 | ] 323 | } 324 | }; 325 | 326 | const ast = thriftParser(content); 327 | 328 | expect(ast).toEqual(expected); 329 | done(); 330 | }); 331 | 332 | it('parses a struct containing mixed fields with/without a FieldID', function(done) { 333 | const content = ` 334 | struct Test { 335 | string test1 336 | 2: string test2 337 | } 338 | `; 339 | 340 | const expected = { 341 | struct: { 342 | Test: [ 343 | { 344 | type: 'string', 345 | name: 'test1' 346 | }, 347 | { 348 | id: 2, 349 | type: 'string', 350 | name: 'test2' 351 | } 352 | ] 353 | } 354 | }; 355 | 356 | const ast = thriftParser(content); 357 | 358 | expect(ast).toEqual(expected); 359 | done(); 360 | }); 361 | 362 | it('parses a struct with values that end in `,` (ListSeparator)', function(done) { 363 | const content = ` 364 | struct Test { 365 | 1: string test1, 366 | 2: string test2 = 'test', 367 | } 368 | `; 369 | 370 | const expected = { 371 | struct: { 372 | Test: [ 373 | { 374 | id: 1, 375 | type: 'string', 376 | name: 'test1' 377 | }, 378 | { 379 | id: 2, 380 | type: 'string', 381 | name: 'test2', 382 | defaultValue: 'test' 383 | } 384 | ] 385 | } 386 | }; 387 | 388 | const ast = thriftParser(content); 389 | 390 | expect(ast).toEqual(expected); 391 | done(); 392 | }); 393 | 394 | it('parses a struct with values that end in `;` (ListSeparator)', function(done) { 395 | const content = ` 396 | struct Test { 397 | 1: string test1; 398 | 2: string test2 = 'test'; 399 | } 400 | `; 401 | 402 | const expected = { 403 | struct: { 404 | Test: [ 405 | { 406 | id: 1, 407 | type: 'string', 408 | name: 'test1' 409 | }, 410 | { 411 | id: 2, 412 | type: 'string', 413 | name: 'test2', 414 | defaultValue: 'test' 415 | } 416 | ] 417 | } 418 | }; 419 | 420 | const ast = thriftParser(content); 421 | 422 | expect(ast).toEqual(expected); 423 | done(); 424 | }); 425 | 426 | it('parses a struct with values that end in `;` and `,` (ListSeparator)', function(done) { 427 | const content = ` 428 | struct Test { 429 | 1: string test1, 430 | 2: string test2 = 'test'; 431 | } 432 | `; 433 | 434 | const expected = { 435 | struct: { 436 | Test: [ 437 | { 438 | id: 1, 439 | type: 'string', 440 | name: 'test1' 441 | }, 442 | { 443 | id: 2, 444 | type: 'string', 445 | name: 'test2', 446 | defaultValue: 'test' 447 | } 448 | ] 449 | } 450 | }; 451 | 452 | const ast = thriftParser(content); 453 | 454 | expect(ast).toEqual(expected); 455 | done(); 456 | }); 457 | 458 | it('does not parse a struct containing a field without a type', function(done) { 459 | const content = ` 460 | struct Test { 461 | 1: test 462 | } 463 | `; 464 | 465 | expect(() => thriftParser(content)).toThrow(); 466 | done(); 467 | }); 468 | 469 | it('does not parse a struct containing a field with required & without a type', function(done) { 470 | const content = ` 471 | struct Test { 472 | 1: required test 473 | } 474 | `; 475 | 476 | expect(() => thriftParser(content)).toThrow(); 477 | done(); 478 | }); 479 | 480 | it('does not parse a struct containing a field with default & without a type', function(done) { 481 | const content = ` 482 | struct Test { 483 | 1: test = 'test' 484 | } 485 | `; 486 | 487 | expect(() => thriftParser(content)).toThrow(); 488 | done(); 489 | }); 490 | 491 | it('does not parse a struct containing a field with invalid default', function(done) { 492 | const content = ` 493 | struct Test { 494 | 1: string test = 'test 495 | } 496 | `; 497 | 498 | expect(() => thriftParser(content)).toThrow(); 499 | done(); 500 | }); 501 | 502 | it('does not parse a struct containing a field with default containing mixed quotes', function(done) { 503 | const content = ` 504 | struct Test { 505 | 1: string test = 'test" 506 | } 507 | `; 508 | 509 | expect(() => thriftParser(content)).toThrow(); 510 | done(); 511 | }); 512 | 513 | it('does not parse a struct containing a field with string FieldID', function(done) { 514 | const content = ` 515 | struct Test { 516 | test: string test 517 | } 518 | `; 519 | 520 | expect(() => thriftParser(content)).toThrow(); 521 | done(); 522 | }); 523 | 524 | it('does not parse a struct containing a field with e-notation FieldID', function(done) { 525 | const content = ` 526 | struct Test { 527 | 1e2: string test 528 | } 529 | `; 530 | 531 | expect(() => thriftParser(content)).toThrow(); 532 | done(); 533 | }); 534 | 535 | it('does not parse a struct containing a field with decimal FieldID', function(done) { 536 | const content = ` 537 | struct Test { 538 | 1.2: string test 539 | } 540 | `; 541 | 542 | expect(() => thriftParser(content)).toThrow(); 543 | done(); 544 | }); 545 | 546 | it('does not parse a struct containing a field with invalid option', function(done) { 547 | const content = ` 548 | struct Test { 549 | 1: failure string test 550 | } 551 | `; 552 | 553 | expect(() => thriftParser(content)).toThrow(); 554 | done(); 555 | }); 556 | 557 | it('parses a struct containing a field with a Map type', function(done) { 558 | const content = ` 559 | struct Test { 560 | 1: map test = { 1: 'a', 2: 'b' } 561 | } 562 | `; 563 | 564 | const expected = { 565 | struct: { 566 | Test: [ 567 | { 568 | id: 1, 569 | type: { 570 | name: 'map', 571 | keyType: 'i16', 572 | valueType: 'string' 573 | }, 574 | name: 'test', 575 | defaultValue: [ 576 | { 577 | key: 1, 578 | value: 'a' 579 | }, 580 | { 581 | key: 2, 582 | value: 'b' 583 | } 584 | ] 585 | } 586 | ] 587 | } 588 | }; 589 | 590 | const ast = thriftParser(content); 591 | 592 | expect(ast).toEqual(expected); 593 | done(); 594 | }); 595 | 596 | it('does not parse a struct containing a field with an invalid Map type', function(done) { 597 | const content = ` 598 | struct Test { 599 | 1: map test 600 | } 601 | `; 602 | 603 | expect(() => thriftParser(content)).toThrow(); 604 | done(); 605 | }); 606 | 607 | it.skip('does not parse a struct containing a field with a Map type but invalid default', function(done) { 608 | const content = ` 609 | struct Test { 610 | 1: map test = [1,2] 611 | } 612 | `; 613 | 614 | expect(() => thriftParser(content)).toThrow(); 615 | done(); 616 | }); 617 | 618 | it('parses a struct containing a field with a Set type', function(done) { 619 | const content = ` 620 | struct Test { 621 | 1: set test = [1,2] 622 | } 623 | `; 624 | 625 | const expected = { 626 | struct: { 627 | Test: [ 628 | { 629 | id: 1, 630 | type: { 631 | name: 'set', 632 | valueType: 'i16' 633 | }, 634 | name: 'test', 635 | defaultValue: [ 636 | 1, 637 | 2 638 | ] 639 | } 640 | ] 641 | } 642 | }; 643 | 644 | const ast = thriftParser(content); 645 | 646 | expect(ast).toEqual(expected); 647 | done(); 648 | }); 649 | 650 | it('does not parse a struct containing a field with an invalid Set type', function(done) { 651 | const content = ` 652 | struct Test { 653 | 1: set test = [1,2] 654 | } 655 | `; 656 | 657 | expect(() => thriftParser(content)).toThrow(); 658 | done(); 659 | }); 660 | 661 | it.skip('does not parse a struct containing a field with a Set type but invalid default', function(done) { 662 | const content = ` 663 | struct Test { 664 | 1: set test = { 1: 'a', 2: 'b' } 665 | } 666 | `; 667 | 668 | expect(() => thriftParser(content)).toThrow(); 669 | done(); 670 | }); 671 | 672 | it('parses a struct containing a field with a List type', function(done) { 673 | const content = ` 674 | struct Test { 675 | 1: list test = [1,2] 676 | } 677 | `; 678 | 679 | const expected = { 680 | struct: { 681 | Test: [ 682 | { 683 | id: 1, 684 | type: { 685 | name: 'list', 686 | valueType: 'i16' 687 | }, 688 | name: 'test', 689 | defaultValue: [ 690 | 1, 691 | 2 692 | ] 693 | } 694 | ] 695 | } 696 | }; 697 | 698 | const ast = thriftParser(content); 699 | 700 | expect(ast).toEqual(expected); 701 | done(); 702 | }); 703 | 704 | it('does not parse a struct containing a field with an invalid List type', function(done) { 705 | const content = ` 706 | struct Test { 707 | 1: list test = [1,2] 708 | } 709 | `; 710 | 711 | expect(() => thriftParser(content)).toThrow(); 712 | done(); 713 | }); 714 | 715 | it.skip('does not parse a struct containing a field with a List type but invalid default', function(done) { 716 | const content = ` 717 | struct Test { 718 | 1: list test = { 1: 'a', 2: 'b' } 719 | } 720 | `; 721 | 722 | expect(() => thriftParser(content)).toThrow(); 723 | done(); 724 | }); 725 | 726 | it('does not parse a struct containing a field with an invalid default assignment', function(done) { 727 | const content = ` 728 | struct Test { 729 | 1: string test = 730 | } 731 | `; 732 | 733 | expect(() => thriftParser(content)).toThrow(); 734 | done(); 735 | }); 736 | 737 | it('parses a struct that starts with `_` (Identifier)', function(done) { 738 | const content = ` 739 | struct _Test { 740 | 1: string test1 741 | } 742 | `; 743 | 744 | const expected = { 745 | struct: { 746 | _Test: [ 747 | { 748 | id: 1, 749 | type: 'string', 750 | name: 'test1' 751 | } 752 | ] 753 | } 754 | }; 755 | 756 | const ast = thriftParser(content); 757 | 758 | expect(ast).toEqual(expected); 759 | done(); 760 | }); 761 | 762 | it('parses a struct containing `_` (Identifier)', function(done) { 763 | const content = ` 764 | struct Te_st { 765 | 1: string test1 766 | } 767 | `; 768 | 769 | const expected = { 770 | struct: { 771 | Te_st: [ 772 | { 773 | id: 1, 774 | type: 'string', 775 | name: 'test1' 776 | } 777 | ] 778 | } 779 | }; 780 | 781 | const ast = thriftParser(content); 782 | 783 | expect(ast).toEqual(expected); 784 | done(); 785 | }); 786 | 787 | it('parses a struct containing `.` (Identifier)', function(done) { 788 | const content = ` 789 | struct Te.st { 790 | 1: string test1 791 | } 792 | `; 793 | 794 | const expected = { 795 | struct: { 796 | 'Te.st': [ 797 | { 798 | id: 1, 799 | type: 'string', 800 | name: 'test1' 801 | } 802 | ] 803 | } 804 | }; 805 | 806 | const ast = thriftParser(content); 807 | 808 | expect(ast).toEqual(expected); 809 | done(); 810 | }); 811 | 812 | it('parses a struct containing `.`, `_`, letters and numbers (Identifier)', function(done) { 813 | const content = ` 814 | struct Te.st_123 { 815 | 1: string test1 816 | } 817 | `; 818 | 819 | const expected = { 820 | struct: { 821 | 'Te.st_123': [ 822 | { 823 | id: 1, 824 | type: 'string', 825 | name: 'test1' 826 | } 827 | ] 828 | } 829 | }; 830 | 831 | const ast = thriftParser(content); 832 | 833 | expect(ast).toEqual(expected); 834 | done(); 835 | }); 836 | 837 | it('parses a struct with a field that starts with `_` (Identifier)', function(done) { 838 | const content = ` 839 | struct Test { 840 | 1: string _test1 841 | } 842 | `; 843 | 844 | const expected = { 845 | struct: { 846 | Test: [ 847 | { 848 | id: 1, 849 | type: 'string', 850 | name: '_test1' 851 | } 852 | ] 853 | } 854 | }; 855 | 856 | const ast = thriftParser(content); 857 | 858 | expect(ast).toEqual(expected); 859 | done(); 860 | }); 861 | 862 | it('parses a struct with a field containing `_` (Identifier)', function(done) { 863 | const content = ` 864 | struct Test { 865 | 1: string test_1 866 | } 867 | `; 868 | 869 | const expected = { 870 | struct: { 871 | Test: [ 872 | { 873 | id: 1, 874 | type: 'string', 875 | name: 'test_1' 876 | } 877 | ] 878 | } 879 | }; 880 | 881 | const ast = thriftParser(content); 882 | 883 | expect(ast).toEqual(expected); 884 | done(); 885 | }); 886 | 887 | it('parses a struct with a field containing `.` (Identifier)', function(done) { 888 | const content = ` 889 | struct Test { 890 | 1: string test.1 891 | } 892 | `; 893 | 894 | const expected = { 895 | struct: { 896 | Test: [ 897 | { 898 | id: 1, 899 | type: 'string', 900 | name: 'test.1' 901 | } 902 | ] 903 | } 904 | }; 905 | 906 | const ast = thriftParser(content); 907 | 908 | expect(ast).toEqual(expected); 909 | done(); 910 | }); 911 | 912 | it('parses a struct with a field containing `.`, `_`, letters and numbers (Identifier)', function(done) { 913 | const content = ` 914 | struct Test { 915 | 1: string te.st_1 916 | } 917 | `; 918 | 919 | const expected = { 920 | struct: { 921 | Test: [ 922 | { 923 | id: 1, 924 | type: 'string', 925 | name: 'te.st_1' 926 | } 927 | ] 928 | } 929 | }; 930 | 931 | const ast = thriftParser(content); 932 | 933 | expect(ast).toEqual(expected); 934 | done(); 935 | }); 936 | }); 937 | -------------------------------------------------------------------------------- /tests/exceptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('exceptions', function() { 8 | 9 | it('parses simple exception', function(done) { 10 | const content = ` 11 | exception Test { 12 | 1: i16 test1 13 | } 14 | `; 15 | 16 | const expected = { 17 | exception: { 18 | Test: [ 19 | { 20 | id: 1, 21 | type: 'i16', 22 | name: 'test1' 23 | } 24 | ] 25 | } 26 | }; 27 | 28 | const ast = thriftParser(content); 29 | 30 | expect(ast).toEqual(expected); 31 | done(); 32 | }); 33 | 34 | it('parses a exception with a required field', function(done) { 35 | const content = ` 36 | exception Test { 37 | 1: required i16 test1 38 | } 39 | `; 40 | 41 | const expected = { 42 | exception: { 43 | Test: [ 44 | { 45 | id: 1, 46 | type: 'i16', 47 | name: 'test1', 48 | option: 'required' 49 | } 50 | ] 51 | } 52 | }; 53 | 54 | const ast = thriftParser(content); 55 | 56 | expect(ast).toEqual(expected); 57 | done(); 58 | }); 59 | 60 | it('parses a exception with an optional field', function(done) { 61 | const content = ` 62 | exception Test { 63 | 1: optional i16 test1 64 | } 65 | `; 66 | 67 | const expected = { 68 | exception: { 69 | Test: [ 70 | { 71 | id: 1, 72 | type: 'i16', 73 | name: 'test1', 74 | option: 'optional' 75 | } 76 | ] 77 | } 78 | }; 79 | 80 | const ast = thriftParser(content); 81 | 82 | expect(ast).toEqual(expected); 83 | done(); 84 | }); 85 | 86 | it('parses a exception with mixed option fields', function(done) { 87 | const content = ` 88 | exception Test { 89 | 1: required i16 test1 90 | 2: i16 test2 91 | 3: optional i16 test3 92 | } 93 | `; 94 | 95 | const expected = { 96 | exception: { 97 | Test: [ 98 | { 99 | id: 1, 100 | type: 'i16', 101 | name: 'test1', 102 | option: 'required' 103 | }, 104 | { 105 | id: 2, 106 | type: 'i16', 107 | name: 'test2' 108 | }, 109 | { 110 | id: 3, 111 | type: 'i16', 112 | name: 'test3', 113 | option: 'optional' 114 | } 115 | ] 116 | } 117 | }; 118 | 119 | const ast = thriftParser(content); 120 | 121 | expect(ast).toEqual(expected); 122 | done(); 123 | }); 124 | 125 | it('parses custom types in exception field', function(done) { 126 | const content = ` 127 | exception Test { 128 | 1: TestType test1 129 | } 130 | `; 131 | 132 | const expected = { 133 | exception: { 134 | Test: [ 135 | { 136 | id: 1, 137 | type: 'TestType', 138 | name: 'test1' 139 | } 140 | ] 141 | } 142 | }; 143 | 144 | const ast = thriftParser(content); 145 | 146 | expect(ast).toEqual(expected); 147 | done(); 148 | }); 149 | 150 | it('parses a exception with default values', function(done) { 151 | const content = ` 152 | exception Test { 153 | 1: string test1 = 'test' 154 | } 155 | `; 156 | 157 | const expected = { 158 | exception: { 159 | Test: [ 160 | { 161 | id: 1, 162 | type: 'string', 163 | name: 'test1', 164 | defaultValue: 'test' 165 | } 166 | ] 167 | } 168 | }; 169 | 170 | const ast = thriftParser(content); 171 | 172 | expect(ast).toEqual(expected); 173 | done(); 174 | }); 175 | 176 | it('parses a exception with mixed default/no-default values', function(done) { 177 | const content = ` 178 | exception Test { 179 | 1: string test1 = 'test' 180 | 2: i16 test2 181 | } 182 | `; 183 | 184 | const expected = { 185 | exception: { 186 | Test: [ 187 | { 188 | id: 1, 189 | type: 'string', 190 | name: 'test1', 191 | defaultValue: 'test' 192 | }, 193 | { 194 | id: 2, 195 | type: 'i16', 196 | name: 'test2' 197 | } 198 | ] 199 | } 200 | }; 201 | 202 | const ast = thriftParser(content); 203 | 204 | expect(ast).toEqual(expected); 205 | done(); 206 | }); 207 | 208 | it('parses a exception containing a field with a hex FieldID', function(done) { 209 | const content = ` 210 | exception Test { 211 | 0x01: string test1 212 | } 213 | `; 214 | 215 | const expected = { 216 | exception: { 217 | Test: [ 218 | { 219 | id: 1, 220 | type: 'string', 221 | name: 'test1' 222 | } 223 | ] 224 | } 225 | }; 226 | 227 | const ast = thriftParser(content); 228 | 229 | expect(ast).toEqual(expected); 230 | done(); 231 | }); 232 | 233 | it('parses a exception containing a field with a negative FieldID', function(done) { 234 | const content = ` 235 | exception Test { 236 | -1: string test1 237 | } 238 | `; 239 | 240 | const expected = { 241 | exception: { 242 | Test: [ 243 | { 244 | id: -1, 245 | type: 'string', 246 | name: 'test1' 247 | } 248 | ] 249 | } 250 | }; 251 | 252 | const ast = thriftParser(content); 253 | 254 | expect(ast).toEqual(expected); 255 | done(); 256 | }); 257 | 258 | it('parses a exception containing a field with a positive FieldID with `+`', function(done) { 259 | const content = ` 260 | exception Test { 261 | +1: string test1 262 | } 263 | `; 264 | 265 | const expected = { 266 | exception: { 267 | Test: [ 268 | { 269 | id: 1, 270 | type: 'string', 271 | name: 'test1' 272 | } 273 | ] 274 | } 275 | }; 276 | 277 | const ast = thriftParser(content); 278 | 279 | expect(ast).toEqual(expected); 280 | done(); 281 | }); 282 | 283 | it('parses a exception containing a field without a FieldID', function(done) { 284 | const content = ` 285 | exception Test { 286 | string test1 287 | } 288 | `; 289 | 290 | const expected = { 291 | exception: { 292 | Test: [ 293 | { 294 | type: 'string', 295 | name: 'test1' 296 | } 297 | ] 298 | } 299 | }; 300 | 301 | const ast = thriftParser(content); 302 | 303 | expect(ast).toEqual(expected); 304 | done(); 305 | }); 306 | 307 | it('parses a exception containing a field without a FieldID but with required', function(done) { 308 | const content = ` 309 | exception Test { 310 | required string test1 311 | } 312 | `; 313 | 314 | const expected = { 315 | exception: { 316 | Test: [ 317 | { 318 | type: 'string', 319 | name: 'test1', 320 | option: 'required' 321 | } 322 | ] 323 | } 324 | }; 325 | 326 | const ast = thriftParser(content); 327 | 328 | expect(ast).toEqual(expected); 329 | done(); 330 | }); 331 | 332 | it('parses a exception containing mixed fields with/without a FieldID', function(done) { 333 | const content = ` 334 | exception Test { 335 | string test1 336 | 2: string test2 337 | } 338 | `; 339 | 340 | const expected = { 341 | exception: { 342 | Test: [ 343 | { 344 | type: 'string', 345 | name: 'test1' 346 | }, 347 | { 348 | id: 2, 349 | type: 'string', 350 | name: 'test2' 351 | } 352 | ] 353 | } 354 | }; 355 | 356 | const ast = thriftParser(content); 357 | 358 | expect(ast).toEqual(expected); 359 | done(); 360 | }); 361 | 362 | it('parses a exception with values that end in `,` (ListSeparator)', function(done) { 363 | const content = ` 364 | exception Test { 365 | 1: string test1, 366 | 2: string test2 = 'test', 367 | } 368 | `; 369 | 370 | const expected = { 371 | exception: { 372 | Test: [ 373 | { 374 | id: 1, 375 | type: 'string', 376 | name: 'test1' 377 | }, 378 | { 379 | id: 2, 380 | type: 'string', 381 | name: 'test2', 382 | defaultValue: 'test' 383 | } 384 | ] 385 | } 386 | }; 387 | 388 | const ast = thriftParser(content); 389 | 390 | expect(ast).toEqual(expected); 391 | done(); 392 | }); 393 | 394 | it('parses a exception with values that end in `;` (ListSeparator)', function(done) { 395 | const content = ` 396 | exception Test { 397 | 1: string test1; 398 | 2: string test2 = 'test'; 399 | } 400 | `; 401 | 402 | const expected = { 403 | exception: { 404 | Test: [ 405 | { 406 | id: 1, 407 | type: 'string', 408 | name: 'test1' 409 | }, 410 | { 411 | id: 2, 412 | type: 'string', 413 | name: 'test2', 414 | defaultValue: 'test' 415 | } 416 | ] 417 | } 418 | }; 419 | 420 | const ast = thriftParser(content); 421 | 422 | expect(ast).toEqual(expected); 423 | done(); 424 | }); 425 | 426 | it('parses a exception with values that end in `;` and `,` (ListSeparator)', function(done) { 427 | const content = ` 428 | exception Test { 429 | 1: string test1, 430 | 2: string test2 = 'test'; 431 | } 432 | `; 433 | 434 | const expected = { 435 | exception: { 436 | Test: [ 437 | { 438 | id: 1, 439 | type: 'string', 440 | name: 'test1' 441 | }, 442 | { 443 | id: 2, 444 | type: 'string', 445 | name: 'test2', 446 | defaultValue: 'test' 447 | } 448 | ] 449 | } 450 | }; 451 | 452 | const ast = thriftParser(content); 453 | 454 | expect(ast).toEqual(expected); 455 | done(); 456 | }); 457 | 458 | it('does not parse a exception containing a field without a type', function(done) { 459 | const content = ` 460 | exception Test { 461 | 1: test 462 | } 463 | `; 464 | 465 | expect(() => thriftParser(content)).toThrow(); 466 | done(); 467 | }); 468 | 469 | it('does not parse a exception containing a field with required & without a type', function(done) { 470 | const content = ` 471 | exception Test { 472 | 1: required test 473 | } 474 | `; 475 | 476 | expect(() => thriftParser(content)).toThrow(); 477 | done(); 478 | }); 479 | 480 | it('does not parse a exception containing a field with default & without a type', function(done) { 481 | const content = ` 482 | exception Test { 483 | 1: test = 'test' 484 | } 485 | `; 486 | 487 | expect(() => thriftParser(content)).toThrow(); 488 | done(); 489 | }); 490 | 491 | it('does not parse a exception containing a field with invalid default', function(done) { 492 | const content = ` 493 | exception Test { 494 | 1: string test = 'test 495 | } 496 | `; 497 | 498 | expect(() => thriftParser(content)).toThrow(); 499 | done(); 500 | }); 501 | 502 | it('does not parse a exception containing a field with default containing mixed quotes', function(done) { 503 | const content = ` 504 | exception Test { 505 | 1: string test = 'test" 506 | } 507 | `; 508 | 509 | expect(() => thriftParser(content)).toThrow(); 510 | done(); 511 | }); 512 | 513 | it('does not parse a exception containing a field with string FieldID', function(done) { 514 | const content = ` 515 | exception Test { 516 | test: string test 517 | } 518 | `; 519 | 520 | expect(() => thriftParser(content)).toThrow(); 521 | done(); 522 | }); 523 | 524 | it('does not parse a exception containing a field with e-notation FieldID', function(done) { 525 | const content = ` 526 | exception Test { 527 | 1e2: string test 528 | } 529 | `; 530 | 531 | expect(() => thriftParser(content)).toThrow(); 532 | done(); 533 | }); 534 | 535 | it('does not parse a exception containing a field with decimal FieldID', function(done) { 536 | const content = ` 537 | exception Test { 538 | 1.2: string test 539 | } 540 | `; 541 | 542 | expect(() => thriftParser(content)).toThrow(); 543 | done(); 544 | }); 545 | 546 | it('does not parse a exception containing a field with invalid option', function(done) { 547 | const content = ` 548 | exception Test { 549 | 1: failure string test 550 | } 551 | `; 552 | 553 | expect(() => thriftParser(content)).toThrow(); 554 | done(); 555 | }); 556 | 557 | it('parses a exception containing a field with a Map type', function(done) { 558 | const content = ` 559 | exception Test { 560 | 1: map test = { 1: 'a', 2: 'b' } 561 | } 562 | `; 563 | 564 | const expected = { 565 | exception: { 566 | Test: [ 567 | { 568 | id: 1, 569 | type: { 570 | name: 'map', 571 | keyType: 'i16', 572 | valueType: 'string' 573 | }, 574 | name: 'test', 575 | defaultValue: [ 576 | { 577 | key: 1, 578 | value: 'a' 579 | }, 580 | { 581 | key: 2, 582 | value: 'b' 583 | } 584 | ] 585 | } 586 | ] 587 | } 588 | }; 589 | 590 | const ast = thriftParser(content); 591 | 592 | expect(ast).toEqual(expected); 593 | done(); 594 | }); 595 | 596 | it('does not parse a exception containing a field with an invalid Map type', function(done) { 597 | const content = ` 598 | exception Test { 599 | 1: map test 600 | } 601 | `; 602 | 603 | expect(() => thriftParser(content)).toThrow(); 604 | done(); 605 | }); 606 | 607 | it.skip('does not parse a exception containing a field with a Map type but invalid default', function(done) { 608 | const content = ` 609 | exception Test { 610 | 1: map test = [1,2] 611 | } 612 | `; 613 | 614 | expect(() => thriftParser(content)).toThrow(); 615 | done(); 616 | }); 617 | 618 | it('parses a exception containing a field with a Set type', function(done) { 619 | const content = ` 620 | exception Test { 621 | 1: set test = [1,2] 622 | } 623 | `; 624 | 625 | const expected = { 626 | exception: { 627 | Test: [ 628 | { 629 | id: 1, 630 | type: { 631 | name: 'set', 632 | valueType: 'i16' 633 | }, 634 | name: 'test', 635 | defaultValue: [ 636 | 1, 637 | 2 638 | ] 639 | } 640 | ] 641 | } 642 | }; 643 | 644 | const ast = thriftParser(content); 645 | 646 | expect(ast).toEqual(expected); 647 | done(); 648 | }); 649 | 650 | it('does not parse a exception containing a field with an invalid Set type', function(done) { 651 | const content = ` 652 | exception Test { 653 | 1: set test = [1,2] 654 | } 655 | `; 656 | 657 | expect(() => thriftParser(content)).toThrow(); 658 | done(); 659 | }); 660 | 661 | it.skip('does not parse a exception containing a field with a Set type but invalid default', function(done) { 662 | const content = ` 663 | exception Test { 664 | 1: set test = { 1: 'a', 2: 'b' } 665 | } 666 | `; 667 | 668 | expect(() => thriftParser(content)).toThrow(); 669 | done(); 670 | }); 671 | 672 | it('parses a exception containing a field with a List type', function(done) { 673 | const content = ` 674 | exception Test { 675 | 1: list test = [1,2] 676 | } 677 | `; 678 | 679 | const expected = { 680 | exception: { 681 | Test: [ 682 | { 683 | id: 1, 684 | type: { 685 | name: 'list', 686 | valueType: 'i16' 687 | }, 688 | name: 'test', 689 | defaultValue: [ 690 | 1, 691 | 2 692 | ] 693 | } 694 | ] 695 | } 696 | }; 697 | 698 | const ast = thriftParser(content); 699 | 700 | expect(ast).toEqual(expected); 701 | done(); 702 | }); 703 | 704 | it('does not parse a exception containing a field with an invalid List type', function(done) { 705 | const content = ` 706 | exception Test { 707 | 1: list test = [1,2] 708 | } 709 | `; 710 | 711 | expect(() => thriftParser(content)).toThrow(); 712 | done(); 713 | }); 714 | 715 | it.skip('does not parse a exception containing a field with a List type but invalid default', function(done) { 716 | const content = ` 717 | exception Test { 718 | 1: list test = { 1: 'a', 2: 'b' } 719 | } 720 | `; 721 | 722 | expect(() => thriftParser(content)).toThrow(); 723 | done(); 724 | }); 725 | 726 | it('does not parse a exception containing a field with an invalid default assignment', function(done) { 727 | const content = ` 728 | exception Test { 729 | 1: string test = 730 | } 731 | `; 732 | 733 | expect(() => thriftParser(content)).toThrow(); 734 | done(); 735 | }); 736 | 737 | it('parses a exception that starts with `_` (Identifier)', function(done) { 738 | const content = ` 739 | exception _Test { 740 | 1: string test1 741 | } 742 | `; 743 | 744 | const expected = { 745 | exception: { 746 | _Test: [ 747 | { 748 | id: 1, 749 | type: 'string', 750 | name: 'test1' 751 | } 752 | ] 753 | } 754 | }; 755 | 756 | const ast = thriftParser(content); 757 | 758 | expect(ast).toEqual(expected); 759 | done(); 760 | }); 761 | 762 | it('parses a exception containing `_` (Identifier)', function(done) { 763 | const content = ` 764 | exception Te_st { 765 | 1: string test1 766 | } 767 | `; 768 | 769 | const expected = { 770 | exception: { 771 | Te_st: [ 772 | { 773 | id: 1, 774 | type: 'string', 775 | name: 'test1' 776 | } 777 | ] 778 | } 779 | }; 780 | 781 | const ast = thriftParser(content); 782 | 783 | expect(ast).toEqual(expected); 784 | done(); 785 | }); 786 | 787 | it('parses a exception containing `.` (Identifier)', function(done) { 788 | const content = ` 789 | exception Te.st { 790 | 1: string test1 791 | } 792 | `; 793 | 794 | const expected = { 795 | exception: { 796 | 'Te.st': [ 797 | { 798 | id: 1, 799 | type: 'string', 800 | name: 'test1' 801 | } 802 | ] 803 | } 804 | }; 805 | 806 | const ast = thriftParser(content); 807 | 808 | expect(ast).toEqual(expected); 809 | done(); 810 | }); 811 | 812 | it('parses a exception containing `.`, `_`, letters and numbers (Identifier)', function(done) { 813 | const content = ` 814 | exception Te.st_123 { 815 | 1: string test1 816 | } 817 | `; 818 | 819 | const expected = { 820 | exception: { 821 | 'Te.st_123': [ 822 | { 823 | id: 1, 824 | type: 'string', 825 | name: 'test1' 826 | } 827 | ] 828 | } 829 | }; 830 | 831 | const ast = thriftParser(content); 832 | 833 | expect(ast).toEqual(expected); 834 | done(); 835 | }); 836 | 837 | it('parses a exception with a field that starts with `_` (Identifier)', function(done) { 838 | const content = ` 839 | exception Test { 840 | 1: string _test1 841 | } 842 | `; 843 | 844 | const expected = { 845 | exception: { 846 | Test: [ 847 | { 848 | id: 1, 849 | type: 'string', 850 | name: '_test1' 851 | } 852 | ] 853 | } 854 | }; 855 | 856 | const ast = thriftParser(content); 857 | 858 | expect(ast).toEqual(expected); 859 | done(); 860 | }); 861 | 862 | it('parses a exception with a field containing `_` (Identifier)', function(done) { 863 | const content = ` 864 | exception Test { 865 | 1: string test_1 866 | } 867 | `; 868 | 869 | const expected = { 870 | exception: { 871 | Test: [ 872 | { 873 | id: 1, 874 | type: 'string', 875 | name: 'test_1' 876 | } 877 | ] 878 | } 879 | }; 880 | 881 | const ast = thriftParser(content); 882 | 883 | expect(ast).toEqual(expected); 884 | done(); 885 | }); 886 | 887 | it('parses a exception with a field containing `.` (Identifier)', function(done) { 888 | const content = ` 889 | exception Test { 890 | 1: string test.1 891 | } 892 | `; 893 | 894 | const expected = { 895 | exception: { 896 | Test: [ 897 | { 898 | id: 1, 899 | type: 'string', 900 | name: 'test.1' 901 | } 902 | ] 903 | } 904 | }; 905 | 906 | const ast = thriftParser(content); 907 | 908 | expect(ast).toEqual(expected); 909 | done(); 910 | }); 911 | 912 | it('parses a exception with a field containing `.`, `_`, letters and numbers (Identifier)', function(done) { 913 | const content = ` 914 | exception Test { 915 | 1: string te.st_1 916 | } 917 | `; 918 | 919 | const expected = { 920 | exception: { 921 | Test: [ 922 | { 923 | id: 1, 924 | type: 'string', 925 | name: 'te.st_1' 926 | } 927 | ] 928 | } 929 | }; 930 | 931 | const ast = thriftParser(content); 932 | 933 | expect(ast).toEqual(expected); 934 | done(); 935 | }); 936 | }); 937 | -------------------------------------------------------------------------------- /tests/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('expect'); 4 | 5 | const thriftParser = require('../'); 6 | 7 | describe('services', function() { 8 | 9 | it('parses a basic service', function(done) { 10 | const content = ` 11 | service Test { 12 | bool test() 13 | } 14 | `; 15 | 16 | const expected = { 17 | service: { 18 | Test: { 19 | functions: { 20 | test: { 21 | args: [], 22 | name: 'test', 23 | oneway: false, 24 | throws: [], 25 | type: 'bool' 26 | } 27 | } 28 | } 29 | } 30 | }; 31 | 32 | const ast = thriftParser(content); 33 | 34 | expect(ast).toEqual(expected); 35 | done(); 36 | }); 37 | 38 | it('parses a service containing a function with void type', function(done) { 39 | const content = ` 40 | service Test { 41 | void test() 42 | } 43 | `; 44 | 45 | const expected = { 46 | service: { 47 | Test: { 48 | functions: { 49 | test: { 50 | args: [], 51 | name: 'test', 52 | oneway: false, 53 | throws: [], 54 | type: 'void' 55 | } 56 | } 57 | } 58 | } 59 | }; 60 | 61 | const ast = thriftParser(content); 62 | 63 | expect(ast).toEqual(expected); 64 | done(); 65 | }); 66 | 67 | it('parses a service containing a function with custom type', function(done) { 68 | const content = ` 69 | service Test { 70 | TestType test() 71 | } 72 | `; 73 | 74 | const expected = { 75 | service: { 76 | Test: { 77 | functions: { 78 | test: { 79 | args: [], 80 | name: 'test', 81 | oneway: false, 82 | throws: [], 83 | type: 'TestType' 84 | } 85 | } 86 | } 87 | } 88 | }; 89 | 90 | const ast = thriftParser(content); 91 | 92 | expect(ast).toEqual(expected); 93 | done(); 94 | }); 95 | 96 | it('parses a service containing a function with Map type', function(done) { 97 | const content = ` 98 | service Test { 99 | map test() 100 | } 101 | `; 102 | 103 | const expected = { 104 | service: { 105 | Test: { 106 | functions: { 107 | test: { 108 | args: [], 109 | name: 'test', 110 | oneway: false, 111 | throws: [], 112 | type: { 113 | name: 'map', 114 | keyType: 'i16', 115 | valueType: 'string' 116 | } 117 | } 118 | } 119 | } 120 | } 121 | }; 122 | 123 | const ast = thriftParser(content); 124 | 125 | expect(ast).toEqual(expected); 126 | done(); 127 | }); 128 | 129 | it('does not parse a service containing a function with invalid Map type', function(done) { 130 | const content = ` 131 | service Test { 132 | map test() 133 | } 134 | `; 135 | 136 | expect(() => thriftParser(content)).toThrow(); 137 | done(); 138 | }); 139 | 140 | it('parses a service containing a function with Set type', function(done) { 141 | const content = ` 142 | service Test { 143 | set test() 144 | } 145 | `; 146 | 147 | const expected = { 148 | service: { 149 | Test: { 150 | functions: { 151 | test: { 152 | args: [], 153 | name: 'test', 154 | oneway: false, 155 | throws: [], 156 | type: { 157 | name: 'set', 158 | valueType: 'i16' 159 | } 160 | } 161 | } 162 | } 163 | } 164 | }; 165 | 166 | const ast = thriftParser(content); 167 | 168 | expect(ast).toEqual(expected); 169 | done(); 170 | }); 171 | 172 | it('does not parse a service containing a function with invalid Set type', function(done) { 173 | const content = ` 174 | service Test { 175 | set test() 176 | } 177 | `; 178 | 179 | expect(() => thriftParser(content)).toThrow(); 180 | done(); 181 | }); 182 | 183 | it('parses a service containing a function with List type', function(done) { 184 | const content = ` 185 | service Test { 186 | list test() 187 | } 188 | `; 189 | 190 | const expected = { 191 | service: { 192 | Test: { 193 | functions: { 194 | test: { 195 | args: [], 196 | name: 'test', 197 | oneway: false, 198 | throws: [], 199 | type: { 200 | name: 'list', 201 | valueType: 'i16' 202 | } 203 | } 204 | } 205 | } 206 | } 207 | }; 208 | 209 | const ast = thriftParser(content); 210 | 211 | expect(ast).toEqual(expected); 212 | done(); 213 | }); 214 | 215 | it('does not parse a service containing a function with invalid List type', function(done) { 216 | const content = ` 217 | service Test { 218 | list test() 219 | } 220 | `; 221 | 222 | expect(() => thriftParser(content)).toThrow(); 223 | done(); 224 | }); 225 | 226 | it('parses a service containing a function with nested Map/Set/List type', function(done) { 227 | const content = ` 228 | service Test { 229 | list>> test() 230 | } 231 | `; 232 | 233 | const expected = { 234 | service: { 235 | Test: { 236 | functions: { 237 | test: { 238 | args: [], 239 | name: 'test', 240 | oneway: false, 241 | throws: [], 242 | type: { 243 | name: 'list', 244 | valueType: { 245 | name: 'set', 246 | valueType: { 247 | name: 'map', 248 | keyType: 'string', 249 | valueType: 'string' 250 | } 251 | } 252 | } 253 | } 254 | } 255 | } 256 | } 257 | }; 258 | 259 | const ast = thriftParser(content); 260 | 261 | expect(ast).toEqual(expected); 262 | done(); 263 | }); 264 | 265 | it('parses a service containing a function with `oneway`', function(done) { 266 | const content = ` 267 | service Test { 268 | oneway void test() 269 | } 270 | `; 271 | 272 | const expected = { 273 | service: { 274 | Test: { 275 | functions: { 276 | test: { 277 | args: [], 278 | name: 'test', 279 | oneway: true, 280 | throws: [], 281 | type: 'void' 282 | } 283 | } 284 | } 285 | } 286 | }; 287 | 288 | const ast = thriftParser(content); 289 | 290 | expect(ast).toEqual(expected); 291 | done(); 292 | }); 293 | 294 | it.skip('does not parse a service containing a function with `oneway` but not void type', function(done) { 295 | const content = ` 296 | service Test { 297 | oneway bool test() 298 | } 299 | `; 300 | 301 | expect(() => thriftParser(content)).toThrow(); 302 | done(); 303 | }); 304 | 305 | it('parses a service containing functions that end in `,` (ListSeparator)', function(done) { 306 | const content = ` 307 | service Test { 308 | void test1(), 309 | void test2(), 310 | } 311 | `; 312 | 313 | const expected = { 314 | service: { 315 | Test: { 316 | functions: { 317 | test1: { 318 | args: [], 319 | name: 'test1', 320 | oneway: false, 321 | throws: [], 322 | type: 'void' 323 | }, 324 | test2: { 325 | args: [], 326 | name: 'test2', 327 | oneway: false, 328 | throws: [], 329 | type: 'void' 330 | } 331 | } 332 | } 333 | } 334 | }; 335 | 336 | const ast = thriftParser(content); 337 | 338 | expect(ast).toEqual(expected); 339 | done(); 340 | }); 341 | 342 | it('parses a service containing functions that end in `;` (ListSeparator)', function(done) { 343 | const content = ` 344 | service Test { 345 | void test1(); 346 | void test2(); 347 | } 348 | `; 349 | 350 | const expected = { 351 | service: { 352 | Test: { 353 | functions: { 354 | test1: { 355 | args: [], 356 | name: 'test1', 357 | oneway: false, 358 | throws: [], 359 | type: 'void' 360 | }, 361 | test2: { 362 | args: [], 363 | name: 'test2', 364 | oneway: false, 365 | throws: [], 366 | type: 'void' 367 | } 368 | } 369 | } 370 | } 371 | }; 372 | 373 | const ast = thriftParser(content); 374 | 375 | expect(ast).toEqual(expected); 376 | done(); 377 | }); 378 | 379 | it('parses a service containing functions that end in mixed `,` and `;` (ListSeparator)', function(done) { 380 | const content = ` 381 | service Test { 382 | void test1(); 383 | void test2(), 384 | } 385 | `; 386 | 387 | const expected = { 388 | service: { 389 | Test: { 390 | functions: { 391 | test1: { 392 | args: [], 393 | name: 'test1', 394 | oneway: false, 395 | throws: [], 396 | type: 'void' 397 | }, 398 | test2: { 399 | args: [], 400 | name: 'test2', 401 | oneway: false, 402 | throws: [], 403 | type: 'void' 404 | } 405 | } 406 | } 407 | } 408 | }; 409 | 410 | const ast = thriftParser(content); 411 | 412 | expect(ast).toEqual(expected); 413 | done(); 414 | }); 415 | 416 | it('parses a service with extends', function(done) { 417 | const content = ` 418 | service Test2 extends Test1 { 419 | } 420 | `; 421 | 422 | const expected = { 423 | service: { 424 | Test2: { 425 | extends: 'Test1', 426 | functions: {} 427 | } 428 | } 429 | }; 430 | 431 | const ast = thriftParser(content); 432 | 433 | expect(ast).toEqual(expected); 434 | done(); 435 | }); 436 | 437 | it('parses a service that starts with `_` (Identifier)', function(done) { 438 | const content = ` 439 | service _Test { 440 | void test() 441 | } 442 | `; 443 | 444 | const expected = { 445 | service: { 446 | _Test: { 447 | functions: { 448 | test: { 449 | args: [], 450 | name: 'test', 451 | oneway: false, 452 | throws: [], 453 | type: 'void' 454 | } 455 | } 456 | } 457 | } 458 | }; 459 | 460 | const ast = thriftParser(content); 461 | 462 | expect(ast).toEqual(expected); 463 | done(); 464 | }); 465 | 466 | it('parses a service containing `_` (Identifier)', function(done) { 467 | const content = ` 468 | service Te_st { 469 | void test() 470 | } 471 | `; 472 | 473 | const expected = { 474 | service: { 475 | Te_st: { 476 | functions: { 477 | test: { 478 | args: [], 479 | name: 'test', 480 | oneway: false, 481 | throws: [], 482 | type: 'void' 483 | } 484 | } 485 | } 486 | } 487 | }; 488 | 489 | const ast = thriftParser(content); 490 | 491 | expect(ast).toEqual(expected); 492 | done(); 493 | }); 494 | 495 | it('parses a service containing `.` (Identifier)', function(done) { 496 | const content = ` 497 | service Te.st { 498 | void test() 499 | } 500 | `; 501 | 502 | const expected = { 503 | service: { 504 | 'Te.st': { 505 | functions: { 506 | test: { 507 | args: [], 508 | name: 'test', 509 | oneway: false, 510 | throws: [], 511 | type: 'void' 512 | } 513 | } 514 | } 515 | } 516 | }; 517 | 518 | const ast = thriftParser(content); 519 | 520 | expect(ast).toEqual(expected); 521 | done(); 522 | }); 523 | 524 | it('parses a service containing `.`, `_`, letters and numbers (Identifier)', function(done) { 525 | const content = ` 526 | service Te.st_123 { 527 | void test() 528 | } 529 | `; 530 | 531 | const expected = { 532 | service: { 533 | 'Te.st_123': { 534 | functions: { 535 | test: { 536 | args: [], 537 | name: 'test', 538 | oneway: false, 539 | throws: [], 540 | type: 'void' 541 | } 542 | } 543 | } 544 | } 545 | }; 546 | 547 | const ast = thriftParser(content); 548 | 549 | expect(ast).toEqual(expected); 550 | done(); 551 | }); 552 | 553 | it('parses a service containing a function that starts with `_` (Identifier)', function(done) { 554 | const content = ` 555 | service Test { 556 | void _test() 557 | } 558 | `; 559 | 560 | const expected = { 561 | service: { 562 | Test: { 563 | functions: { 564 | _test: { 565 | args: [], 566 | name: '_test', 567 | oneway: false, 568 | throws: [], 569 | type: 'void' 570 | } 571 | } 572 | } 573 | } 574 | }; 575 | 576 | const ast = thriftParser(content); 577 | 578 | expect(ast).toEqual(expected); 579 | done(); 580 | }); 581 | 582 | it('parses a service containing a function containing `_` (Identifier)', function(done) { 583 | const content = ` 584 | service Test { 585 | void te_st() 586 | } 587 | `; 588 | 589 | const expected = { 590 | service: { 591 | Test: { 592 | functions: { 593 | te_st: { 594 | args: [], 595 | name: 'te_st', 596 | oneway: false, 597 | throws: [], 598 | type: 'void' 599 | } 600 | } 601 | } 602 | } 603 | }; 604 | 605 | const ast = thriftParser(content); 606 | 607 | expect(ast).toEqual(expected); 608 | done(); 609 | }); 610 | 611 | it('parses a service containing a function containing `.` (Identifier)', function(done) { 612 | const content = ` 613 | service Test { 614 | void te.st() 615 | } 616 | `; 617 | 618 | const expected = { 619 | service: { 620 | Test: { 621 | functions: { 622 | 'te.st': { 623 | args: [], 624 | name: 'te.st', 625 | oneway: false, 626 | throws: [], 627 | type: 'void' 628 | } 629 | } 630 | } 631 | } 632 | }; 633 | 634 | const ast = thriftParser(content); 635 | 636 | expect(ast).toEqual(expected); 637 | done(); 638 | }); 639 | 640 | it('parses a service containing a function containing `.`, `_`, letters and numbers (Identifier)', function(done) { 641 | const content = ` 642 | service Test { 643 | void te.st_123() 644 | } 645 | `; 646 | 647 | const expected = { 648 | service: { 649 | Test: { 650 | functions: { 651 | 'te.st_123': { 652 | args: [], 653 | name: 'te.st_123', 654 | oneway: false, 655 | throws: [], 656 | type: 'void' 657 | } 658 | } 659 | } 660 | } 661 | }; 662 | 663 | const ast = thriftParser(content); 664 | 665 | expect(ast).toEqual(expected); 666 | done(); 667 | }); 668 | 669 | it('does not parse a service containing a function without a type', function(done) { 670 | const content = ` 671 | service Test { 672 | test() 673 | } 674 | `; 675 | 676 | expect(() => thriftParser(content)).toThrow(); 677 | done(); 678 | }); 679 | 680 | it('does not parse a service containing a function without parens', function(done) { 681 | const content = ` 682 | service Test { 683 | void test 684 | } 685 | `; 686 | 687 | expect(() => thriftParser(content)).toThrow(); 688 | done(); 689 | }); 690 | 691 | it('does not parse a service containing a function without closing paren', function(done) { 692 | const content = ` 693 | service Test { 694 | void test( 695 | } 696 | `; 697 | 698 | expect(() => thriftParser(content)).toThrow(); 699 | done(); 700 | }); 701 | 702 | it('does not parse a service containing a function without opening paren', function(done) { 703 | const content = ` 704 | service Test { 705 | void test) 706 | } 707 | `; 708 | 709 | expect(() => thriftParser(content)).toThrow(); 710 | done(); 711 | }); 712 | 713 | it('parses a service containing a function with a lot of whitespace between parens', function(done) { 714 | const content = ` 715 | service Test { 716 | void test( ) 717 | } 718 | `; 719 | 720 | const expected = { 721 | service: { 722 | Test: { 723 | functions: { 724 | test: { 725 | args: [], 726 | name: 'test', 727 | oneway: false, 728 | throws: [], 729 | type: 'void' 730 | } 731 | } 732 | } 733 | } 734 | }; 735 | 736 | const ast = thriftParser(content); 737 | 738 | expect(ast).toEqual(expected); 739 | done(); 740 | }); 741 | 742 | it('does not parse a service containing throws with no parens', function(done) { 743 | const content = ` 744 | service Test { 745 | void test() throws 746 | } 747 | `; 748 | 749 | expect(() => thriftParser(content)).toThrow(); 750 | done(); 751 | }); 752 | 753 | it('does not parse a service containing throws with no opening paren', function(done) { 754 | const content = ` 755 | service Test { 756 | void test() throws ) 757 | } 758 | `; 759 | 760 | expect(() => thriftParser(content)).toThrow(); 761 | done(); 762 | }); 763 | 764 | it('does not parse a service containing throws with no closing paren', function(done) { 765 | const content = ` 766 | service Test { 767 | void test() throws ( 768 | } 769 | `; 770 | 771 | expect(() => thriftParser(content)).toThrow(); 772 | done(); 773 | }); 774 | 775 | it('parses a service containing a function with a lot of whitespace between throw parens', function(done) { 776 | const content = ` 777 | service Test { 778 | void test() throws ( ) 779 | } 780 | `; 781 | 782 | const expected = { 783 | service: { 784 | Test: { 785 | functions: { 786 | test: { 787 | args: [], 788 | name: 'test', 789 | oneway: false, 790 | throws: [], 791 | type: 'void' 792 | } 793 | } 794 | } 795 | } 796 | }; 797 | 798 | const ast = thriftParser(content); 799 | 800 | expect(ast).toEqual(expected); 801 | done(); 802 | }); 803 | }); 804 | 805 | describe('service function arguments', function() { 806 | 807 | it('parses a service containing a function with arguments', function(done) { 808 | const content = ` 809 | service Test { 810 | void test(1: string test1, 2: bool test2) 811 | } 812 | `; 813 | 814 | const expected = { 815 | service: { 816 | Test: { 817 | functions: { 818 | test: { 819 | args: [ 820 | { 821 | id: 1, 822 | name: 'test1', 823 | type: 'string' 824 | }, 825 | { 826 | id: 2, 827 | name: 'test2', 828 | type: 'bool' 829 | } 830 | ], 831 | name: 'test', 832 | oneway: false, 833 | throws: [], 834 | type: 'void' 835 | } 836 | } 837 | } 838 | } 839 | }; 840 | 841 | const ast = thriftParser(content); 842 | 843 | expect(ast).toEqual(expected); 844 | done(); 845 | }); 846 | 847 | it('parses a service containing a function with arguments without FieldIDs', function(done) { 848 | const content = ` 849 | service Test { 850 | void test(string test1, bool test2) 851 | } 852 | `; 853 | 854 | const expected = { 855 | service: { 856 | Test: { 857 | functions: { 858 | test: { 859 | args: [ 860 | { 861 | name: 'test1', 862 | type: 'string' 863 | }, 864 | { 865 | name: 'test2', 866 | type: 'bool' 867 | } 868 | ], 869 | name: 'test', 870 | oneway: false, 871 | throws: [], 872 | type: 'void' 873 | } 874 | } 875 | } 876 | } 877 | }; 878 | 879 | const ast = thriftParser(content); 880 | 881 | expect(ast).toEqual(expected); 882 | done(); 883 | }); 884 | 885 | it('parses a service containing a function with arguments with mixed FieldIDs', function(done) { 886 | const content = ` 887 | service Test { 888 | void test(string test1, 1: bool test2) 889 | } 890 | `; 891 | 892 | const expected = { 893 | service: { 894 | Test: { 895 | functions: { 896 | test: { 897 | args: [ 898 | { 899 | name: 'test1', 900 | type: 'string' 901 | }, 902 | { 903 | id: 1, 904 | name: 'test2', 905 | type: 'bool' 906 | } 907 | ], 908 | name: 'test', 909 | oneway: false, 910 | throws: [], 911 | type: 'void' 912 | } 913 | } 914 | } 915 | } 916 | }; 917 | 918 | const ast = thriftParser(content); 919 | 920 | expect(ast).toEqual(expected); 921 | done(); 922 | }); 923 | 924 | it('parses a service containing a function with arguments using hexadecimal FieldIDs', function(done) { 925 | const content = ` 926 | service Test { 927 | void test(0x01: string test1) 928 | } 929 | `; 930 | 931 | const expected = { 932 | service: { 933 | Test: { 934 | functions: { 935 | test: { 936 | args: [ 937 | { 938 | id: 1, 939 | name: 'test1', 940 | type: 'string' 941 | } 942 | ], 943 | name: 'test', 944 | oneway: false, 945 | throws: [], 946 | type: 'void' 947 | } 948 | } 949 | } 950 | } 951 | }; 952 | 953 | const ast = thriftParser(content); 954 | 955 | expect(ast).toEqual(expected); 956 | done(); 957 | }); 958 | 959 | it('parses a service containing a function argument with a negative FieldID', function(done) { 960 | const content = ` 961 | service Test { 962 | void test(-1: string test1) 963 | } 964 | `; 965 | 966 | const expected = { 967 | service: { 968 | Test: { 969 | functions: { 970 | test: { 971 | args: [ 972 | { 973 | id: -1, 974 | name: 'test1', 975 | type: 'string' 976 | } 977 | ], 978 | name: 'test', 979 | oneway: false, 980 | throws: [], 981 | type: 'void' 982 | } 983 | } 984 | } 985 | } 986 | }; 987 | 988 | const ast = thriftParser(content); 989 | 990 | expect(ast).toEqual(expected); 991 | done(); 992 | }); 993 | 994 | it('parses a service containing a function argument with a positive FieldID with `+`', function(done) { 995 | const content = ` 996 | service Test { 997 | void test(+1: string test1) 998 | } 999 | `; 1000 | 1001 | const expected = { 1002 | service: { 1003 | Test: { 1004 | functions: { 1005 | test: { 1006 | args: [ 1007 | { 1008 | id: 1, 1009 | name: 'test1', 1010 | type: 'string' 1011 | } 1012 | ], 1013 | name: 'test', 1014 | oneway: false, 1015 | throws: [], 1016 | type: 'void' 1017 | } 1018 | } 1019 | } 1020 | } 1021 | }; 1022 | 1023 | const ast = thriftParser(content); 1024 | 1025 | expect(ast).toEqual(expected); 1026 | done(); 1027 | }); 1028 | 1029 | it('does not parse a service containing a function with arguments using decimal FieldIDs', function(done) { 1030 | const content = ` 1031 | service Test { 1032 | void test(1.2: string test) 1033 | } 1034 | `; 1035 | 1036 | expect(() => thriftParser(content)).toThrow(); 1037 | done(); 1038 | }); 1039 | 1040 | it('does not parse a service containing a function with arguments using e-notation FieldIDs', function(done) { 1041 | const content = ` 1042 | service Test { 1043 | void test(1e2: string test) 1044 | } 1045 | `; 1046 | 1047 | expect(() => thriftParser(content)).toThrow(); 1048 | done(); 1049 | }); 1050 | 1051 | it('parses a service containing a function with required arguments', function(done) { 1052 | const content = ` 1053 | service Test { 1054 | void test(1: required string test) 1055 | } 1056 | `; 1057 | 1058 | const expected = { 1059 | service: { 1060 | Test: { 1061 | functions: { 1062 | test: { 1063 | args: [ 1064 | { 1065 | id: 1, 1066 | name: 'test', 1067 | type: 'string', 1068 | option: 'required' 1069 | } 1070 | ], 1071 | name: 'test', 1072 | oneway: false, 1073 | throws: [], 1074 | type: 'void' 1075 | } 1076 | } 1077 | } 1078 | } 1079 | }; 1080 | 1081 | const ast = thriftParser(content); 1082 | 1083 | expect(ast).toEqual(expected); 1084 | done(); 1085 | }); 1086 | 1087 | it('parses a service containing a function with optional arguments', function(done) { 1088 | const content = ` 1089 | service Test { 1090 | void test(1: optional string test) 1091 | } 1092 | `; 1093 | 1094 | const expected = { 1095 | service: { 1096 | Test: { 1097 | functions: { 1098 | test: { 1099 | args: [ 1100 | { 1101 | id: 1, 1102 | name: 'test', 1103 | type: 'string', 1104 | option: 'optional' 1105 | } 1106 | ], 1107 | name: 'test', 1108 | oneway: false, 1109 | throws: [], 1110 | type: 'void' 1111 | } 1112 | } 1113 | } 1114 | } 1115 | }; 1116 | 1117 | const ast = thriftParser(content); 1118 | 1119 | expect(ast).toEqual(expected); 1120 | done(); 1121 | }); 1122 | 1123 | it('parses a service containing a function with mixed option arguments', function(done) { 1124 | const content = ` 1125 | service Test { 1126 | void test(1: optional string test1, 2: string test2, 3: required string test3) 1127 | } 1128 | `; 1129 | 1130 | const expected = { 1131 | service: { 1132 | Test: { 1133 | functions: { 1134 | test: { 1135 | args: [ 1136 | { 1137 | id: 1, 1138 | name: 'test1', 1139 | type: 'string', 1140 | option: 'optional' 1141 | }, 1142 | { 1143 | id: 2, 1144 | name: 'test2', 1145 | type: 'string' 1146 | }, 1147 | { 1148 | id: 3, 1149 | name: 'test3', 1150 | type: 'string', 1151 | option: 'required' 1152 | } 1153 | ], 1154 | name: 'test', 1155 | oneway: false, 1156 | throws: [], 1157 | type: 'void' 1158 | } 1159 | } 1160 | } 1161 | } 1162 | }; 1163 | 1164 | const ast = thriftParser(content); 1165 | 1166 | expect(ast).toEqual(expected); 1167 | done(); 1168 | }); 1169 | 1170 | it('parses a service containing arguments separated by `;` (ListSeparator)', function(done) { 1171 | const content = ` 1172 | service Test { 1173 | void test(1: string test1; 2: bool test2;) 1174 | } 1175 | `; 1176 | 1177 | const expected = { 1178 | service: { 1179 | Test: { 1180 | functions: { 1181 | test: { 1182 | args: [ 1183 | { 1184 | id: 1, 1185 | name: 'test1', 1186 | type: 'string' 1187 | }, 1188 | { 1189 | id: 2, 1190 | name: 'test2', 1191 | type: 'bool' 1192 | } 1193 | ], 1194 | name: 'test', 1195 | oneway: false, 1196 | throws: [], 1197 | type: 'void' 1198 | } 1199 | } 1200 | } 1201 | } 1202 | }; 1203 | 1204 | const ast = thriftParser(content); 1205 | 1206 | expect(ast).toEqual(expected); 1207 | done(); 1208 | }); 1209 | 1210 | it('parses a service containing arguments separated by mixed `,` and `;` (ListSeparator)', function(done) { 1211 | const content = ` 1212 | service Test { 1213 | void test(1: string test1; 2: bool test2,) 1214 | } 1215 | `; 1216 | 1217 | const expected = { 1218 | service: { 1219 | Test: { 1220 | functions: { 1221 | test: { 1222 | args: [ 1223 | { 1224 | id: 1, 1225 | name: 'test1', 1226 | type: 'string' 1227 | }, 1228 | { 1229 | id: 2, 1230 | name: 'test2', 1231 | type: 'bool' 1232 | } 1233 | ], 1234 | name: 'test', 1235 | oneway: false, 1236 | throws: [], 1237 | type: 'void' 1238 | } 1239 | } 1240 | } 1241 | } 1242 | }; 1243 | 1244 | const ast = thriftParser(content); 1245 | 1246 | expect(ast).toEqual(expected); 1247 | done(); 1248 | }); 1249 | 1250 | it('parses a service containing an argument that starts with `_` (Identifier)', function(done) { 1251 | const content = ` 1252 | service Test { 1253 | void test(1: string _test) 1254 | } 1255 | `; 1256 | 1257 | const expected = { 1258 | service: { 1259 | Test: { 1260 | functions: { 1261 | test: { 1262 | args: [ 1263 | { 1264 | id: 1, 1265 | name: '_test', 1266 | type: 'string' 1267 | } 1268 | ], 1269 | name: 'test', 1270 | oneway: false, 1271 | throws: [], 1272 | type: 'void' 1273 | } 1274 | } 1275 | } 1276 | } 1277 | }; 1278 | 1279 | const ast = thriftParser(content); 1280 | 1281 | expect(ast).toEqual(expected); 1282 | done(); 1283 | }); 1284 | 1285 | it('parses a service containing an argument containing `_` (Identifier)', function(done) { 1286 | const content = ` 1287 | service Test { 1288 | void test(1: string te_st) 1289 | } 1290 | `; 1291 | 1292 | const expected = { 1293 | service: { 1294 | Test: { 1295 | functions: { 1296 | test: { 1297 | args: [ 1298 | { 1299 | id: 1, 1300 | name: 'te_st', 1301 | type: 'string' 1302 | } 1303 | ], 1304 | name: 'test', 1305 | oneway: false, 1306 | throws: [], 1307 | type: 'void' 1308 | } 1309 | } 1310 | } 1311 | } 1312 | }; 1313 | 1314 | const ast = thriftParser(content); 1315 | 1316 | expect(ast).toEqual(expected); 1317 | done(); 1318 | }); 1319 | 1320 | it('parses a service containing an argument containing `.` (Identifier)', function(done) { 1321 | const content = ` 1322 | service Test { 1323 | void test(1: string te.st) 1324 | } 1325 | `; 1326 | 1327 | const expected = { 1328 | service: { 1329 | Test: { 1330 | functions: { 1331 | test: { 1332 | args: [ 1333 | { 1334 | id: 1, 1335 | name: 'te.st', 1336 | type: 'string' 1337 | } 1338 | ], 1339 | name: 'test', 1340 | oneway: false, 1341 | throws: [], 1342 | type: 'void' 1343 | } 1344 | } 1345 | } 1346 | } 1347 | }; 1348 | 1349 | const ast = thriftParser(content); 1350 | 1351 | expect(ast).toEqual(expected); 1352 | done(); 1353 | }); 1354 | 1355 | it('parses a service containing an argument containing `.`, `_`, letters and numbers (Identifier)', function(done) { 1356 | const content = ` 1357 | service Test { 1358 | void test(1: string te.st_123) 1359 | } 1360 | `; 1361 | 1362 | const expected = { 1363 | service: { 1364 | Test: { 1365 | functions: { 1366 | test: { 1367 | args: [ 1368 | { 1369 | id: 1, 1370 | name: 'te.st_123', 1371 | type: 'string' 1372 | } 1373 | ], 1374 | name: 'test', 1375 | oneway: false, 1376 | throws: [], 1377 | type: 'void' 1378 | } 1379 | } 1380 | } 1381 | } 1382 | }; 1383 | 1384 | const ast = thriftParser(content); 1385 | 1386 | expect(ast).toEqual(expected); 1387 | done(); 1388 | }); 1389 | 1390 | it('parses a service containing a function with whitespace around arguments', function(done) { 1391 | const content = ` 1392 | service Test { 1393 | void test( 1: string test ) 1394 | } 1395 | `; 1396 | 1397 | const expected = { 1398 | service: { 1399 | Test: { 1400 | functions: { 1401 | test: { 1402 | args: [ 1403 | { 1404 | id: 1, 1405 | name: 'test', 1406 | type: 'string' 1407 | } 1408 | ], 1409 | name: 'test', 1410 | oneway: false, 1411 | throws: [], 1412 | type: 'void' 1413 | } 1414 | } 1415 | } 1416 | } 1417 | }; 1418 | 1419 | const ast = thriftParser(content); 1420 | 1421 | expect(ast).toEqual(expected); 1422 | done(); 1423 | }); 1424 | 1425 | it('parses a service containing a function with defaulted arguments', function(done) { 1426 | const content = ` 1427 | service Test { 1428 | void test(1: string test1 = 'test', 2: i16 test2 = 123) 1429 | } 1430 | `; 1431 | 1432 | const expected = { 1433 | service: { 1434 | Test: { 1435 | functions: { 1436 | test: { 1437 | args: [ 1438 | { 1439 | id: 1, 1440 | name: 'test1', 1441 | type: 'string', 1442 | defaultValue: 'test' 1443 | }, 1444 | { 1445 | id: 2, 1446 | name: 'test2', 1447 | type: 'i16', 1448 | defaultValue: 123 1449 | } 1450 | ], 1451 | name: 'test', 1452 | oneway: false, 1453 | throws: [], 1454 | type: 'void' 1455 | } 1456 | } 1457 | } 1458 | } 1459 | }; 1460 | 1461 | const ast = thriftParser(content); 1462 | 1463 | expect(ast).toEqual(expected); 1464 | done(); 1465 | }); 1466 | }); 1467 | 1468 | describe('service function throws', function() { 1469 | 1470 | it('parses a service containing a function with throws', function(done) { 1471 | const content = ` 1472 | service Test { 1473 | void test() throws (1: TestException1 test1, 2: TestException2 test2) 1474 | } 1475 | `; 1476 | 1477 | const expected = { 1478 | service: { 1479 | Test: { 1480 | functions: { 1481 | test: { 1482 | args: [], 1483 | name: 'test', 1484 | oneway: false, 1485 | throws: [ 1486 | { 1487 | id: 1, 1488 | name: 'test1', 1489 | type: 'TestException1' 1490 | }, 1491 | { 1492 | id: 2, 1493 | name: 'test2', 1494 | type: 'TestException2' 1495 | } 1496 | ], 1497 | type: 'void' 1498 | } 1499 | } 1500 | } 1501 | } 1502 | }; 1503 | 1504 | const ast = thriftParser(content); 1505 | 1506 | expect(ast).toEqual(expected); 1507 | done(); 1508 | }); 1509 | 1510 | it('parses a service containing a function with throws without FieldIDs', function(done) { 1511 | const content = ` 1512 | service Test { 1513 | void test() throws (TestException1 test1, TestException2 test2) 1514 | } 1515 | `; 1516 | 1517 | const expected = { 1518 | service: { 1519 | Test: { 1520 | functions: { 1521 | test: { 1522 | args: [], 1523 | name: 'test', 1524 | oneway: false, 1525 | throws: [ 1526 | { 1527 | name: 'test1', 1528 | type: 'TestException1' 1529 | }, 1530 | { 1531 | name: 'test2', 1532 | type: 'TestException2' 1533 | } 1534 | ], 1535 | type: 'void' 1536 | } 1537 | } 1538 | } 1539 | } 1540 | }; 1541 | 1542 | const ast = thriftParser(content); 1543 | 1544 | expect(ast).toEqual(expected); 1545 | done(); 1546 | }); 1547 | 1548 | it('parses a service containing a function with throws with mixed FieldIDs', function(done) { 1549 | const content = ` 1550 | service Test { 1551 | void test() throws (TestException1 test1, 1: TestException2 test2) 1552 | } 1553 | `; 1554 | 1555 | const expected = { 1556 | service: { 1557 | Test: { 1558 | functions: { 1559 | test: { 1560 | args: [], 1561 | name: 'test', 1562 | oneway: false, 1563 | throws: [ 1564 | { 1565 | name: 'test1', 1566 | type: 'TestException1' 1567 | }, 1568 | { 1569 | id: 1, 1570 | name: 'test2', 1571 | type: 'TestException2' 1572 | } 1573 | ], 1574 | type: 'void' 1575 | } 1576 | } 1577 | } 1578 | } 1579 | }; 1580 | 1581 | const ast = thriftParser(content); 1582 | 1583 | expect(ast).toEqual(expected); 1584 | done(); 1585 | }); 1586 | 1587 | it('parses a service containing a function with throws using hexadecimal FieldIDs', function(done) { 1588 | const content = ` 1589 | service Test { 1590 | void test() throws (0x01: string test1) 1591 | } 1592 | `; 1593 | 1594 | const expected = { 1595 | service: { 1596 | Test: { 1597 | functions: { 1598 | test: { 1599 | args: [], 1600 | name: 'test', 1601 | oneway: false, 1602 | throws: [ 1603 | { 1604 | id: 1, 1605 | name: 'test1', 1606 | type: 'string' 1607 | } 1608 | ], 1609 | type: 'void' 1610 | } 1611 | } 1612 | } 1613 | } 1614 | }; 1615 | 1616 | const ast = thriftParser(content); 1617 | 1618 | expect(ast).toEqual(expected); 1619 | done(); 1620 | }); 1621 | 1622 | it('parses a service containing a function with throws using a negative FieldID', function(done) { 1623 | const content = ` 1624 | service Test { 1625 | void test() throws (-1: string test1) 1626 | } 1627 | `; 1628 | 1629 | const expected = { 1630 | service: { 1631 | Test: { 1632 | functions: { 1633 | test: { 1634 | args: [], 1635 | name: 'test', 1636 | oneway: false, 1637 | throws: [ 1638 | { 1639 | id: -1, 1640 | name: 'test1', 1641 | type: 'string' 1642 | } 1643 | ], 1644 | type: 'void' 1645 | } 1646 | } 1647 | } 1648 | } 1649 | }; 1650 | 1651 | const ast = thriftParser(content); 1652 | 1653 | expect(ast).toEqual(expected); 1654 | done(); 1655 | }); 1656 | 1657 | it('parses a service containing a function with throws using a positive FieldID with `+`', function(done) { 1658 | const content = ` 1659 | service Test { 1660 | void test() throws (+1: string test1) 1661 | } 1662 | `; 1663 | 1664 | const expected = { 1665 | service: { 1666 | Test: { 1667 | functions: { 1668 | test: { 1669 | args: [], 1670 | name: 'test', 1671 | oneway: false, 1672 | throws: [ 1673 | { 1674 | id: 1, 1675 | name: 'test1', 1676 | type: 'string' 1677 | } 1678 | ], 1679 | type: 'void' 1680 | } 1681 | } 1682 | } 1683 | } 1684 | }; 1685 | 1686 | const ast = thriftParser(content); 1687 | 1688 | expect(ast).toEqual(expected); 1689 | done(); 1690 | }); 1691 | 1692 | it('does not parse a service containing a function with throws with decimal FieldIDs', function(done) { 1693 | const content = ` 1694 | service Test { 1695 | void test() throws (1.2: string test) 1696 | } 1697 | `; 1698 | 1699 | expect(() => thriftParser(content)).toThrow(); 1700 | done(); 1701 | }); 1702 | 1703 | it('does not parse a service containing a function with throws with e-notation FieldIDs', function(done) { 1704 | const content = ` 1705 | service Test { 1706 | void test() throws (1e2: string test) 1707 | } 1708 | `; 1709 | 1710 | expect(() => thriftParser(content)).toThrow(); 1711 | done(); 1712 | }); 1713 | 1714 | it('parses a service containing a function with required throws', function(done) { 1715 | const content = ` 1716 | service Test { 1717 | void test() throws (1: required string test) 1718 | } 1719 | `; 1720 | 1721 | const expected = { 1722 | service: { 1723 | Test: { 1724 | functions: { 1725 | test: { 1726 | args: [], 1727 | name: 'test', 1728 | oneway: false, 1729 | throws: [ 1730 | { 1731 | id: 1, 1732 | name: 'test', 1733 | type: 'string', 1734 | option: 'required' 1735 | } 1736 | ], 1737 | type: 'void' 1738 | } 1739 | } 1740 | } 1741 | } 1742 | }; 1743 | 1744 | const ast = thriftParser(content); 1745 | 1746 | expect(ast).toEqual(expected); 1747 | done(); 1748 | }); 1749 | 1750 | it('parses a service containing a function with optional throws', function(done) { 1751 | const content = ` 1752 | service Test { 1753 | void test() throws (1: optional string test) 1754 | } 1755 | `; 1756 | 1757 | const expected = { 1758 | service: { 1759 | Test: { 1760 | functions: { 1761 | test: { 1762 | args: [], 1763 | name: 'test', 1764 | oneway: false, 1765 | throws: [ 1766 | { 1767 | id: 1, 1768 | name: 'test', 1769 | type: 'string', 1770 | option: 'optional' 1771 | } 1772 | ], 1773 | type: 'void' 1774 | } 1775 | } 1776 | } 1777 | } 1778 | }; 1779 | 1780 | const ast = thriftParser(content); 1781 | 1782 | expect(ast).toEqual(expected); 1783 | done(); 1784 | }); 1785 | 1786 | it('parses a service containing a function with mixed option throws', function(done) { 1787 | const content = ` 1788 | service Test { 1789 | void test() throws (1: optional string test1, 2: string test2, 3: required string test3) 1790 | } 1791 | `; 1792 | 1793 | const expected = { 1794 | service: { 1795 | Test: { 1796 | functions: { 1797 | test: { 1798 | args: [], 1799 | name: 'test', 1800 | oneway: false, 1801 | throws: [ 1802 | { 1803 | id: 1, 1804 | name: 'test1', 1805 | type: 'string', 1806 | option: 'optional' 1807 | }, 1808 | { 1809 | id: 2, 1810 | name: 'test2', 1811 | type: 'string' 1812 | }, 1813 | { 1814 | id: 3, 1815 | name: 'test3', 1816 | type: 'string', 1817 | option: 'required' 1818 | } 1819 | ], 1820 | type: 'void' 1821 | } 1822 | } 1823 | } 1824 | } 1825 | }; 1826 | 1827 | const ast = thriftParser(content); 1828 | 1829 | expect(ast).toEqual(expected); 1830 | done(); 1831 | }); 1832 | 1833 | it('parses a service containing throws separated by `;` (ListSeparator)', function(done) { 1834 | const content = ` 1835 | service Test { 1836 | void test() throws (1: string test1; 2: bool test2;) 1837 | } 1838 | `; 1839 | 1840 | const expected = { 1841 | service: { 1842 | Test: { 1843 | functions: { 1844 | test: { 1845 | args: [], 1846 | name: 'test', 1847 | oneway: false, 1848 | throws: [ 1849 | { 1850 | id: 1, 1851 | name: 'test1', 1852 | type: 'string' 1853 | }, 1854 | { 1855 | id: 2, 1856 | name: 'test2', 1857 | type: 'bool' 1858 | } 1859 | ], 1860 | type: 'void' 1861 | } 1862 | } 1863 | } 1864 | } 1865 | }; 1866 | 1867 | const ast = thriftParser(content); 1868 | 1869 | expect(ast).toEqual(expected); 1870 | done(); 1871 | }); 1872 | 1873 | it('parses a service containing throws separated by mixed `,` and `;` (ListSeparator)', function(done) { 1874 | const content = ` 1875 | service Test { 1876 | void test() throws (1: string test1; 2: bool test2,) 1877 | } 1878 | `; 1879 | 1880 | const expected = { 1881 | service: { 1882 | Test: { 1883 | functions: { 1884 | test: { 1885 | args: [], 1886 | name: 'test', 1887 | oneway: false, 1888 | throws: [ 1889 | { 1890 | id: 1, 1891 | name: 'test1', 1892 | type: 'string' 1893 | }, 1894 | { 1895 | id: 2, 1896 | name: 'test2', 1897 | type: 'bool' 1898 | } 1899 | ], 1900 | type: 'void' 1901 | } 1902 | } 1903 | } 1904 | } 1905 | }; 1906 | 1907 | const ast = thriftParser(content); 1908 | 1909 | expect(ast).toEqual(expected); 1910 | done(); 1911 | }); 1912 | 1913 | it('parses a service containing a throws that starts with `_` (Identifier)', function(done) { 1914 | const content = ` 1915 | service Test { 1916 | void test() throws (1: string _test) 1917 | } 1918 | `; 1919 | 1920 | const expected = { 1921 | service: { 1922 | Test: { 1923 | functions: { 1924 | test: { 1925 | args: [], 1926 | name: 'test', 1927 | oneway: false, 1928 | throws: [ 1929 | { 1930 | id: 1, 1931 | name: '_test', 1932 | type: 'string' 1933 | } 1934 | ], 1935 | type: 'void' 1936 | } 1937 | } 1938 | } 1939 | } 1940 | }; 1941 | 1942 | const ast = thriftParser(content); 1943 | 1944 | expect(ast).toEqual(expected); 1945 | done(); 1946 | }); 1947 | 1948 | it('parses a service containing a throws containing `_` (Identifier)', function(done) { 1949 | const content = ` 1950 | service Test { 1951 | void test() throws (1: string te_st) 1952 | } 1953 | `; 1954 | 1955 | const expected = { 1956 | service: { 1957 | Test: { 1958 | functions: { 1959 | test: { 1960 | args: [], 1961 | name: 'test', 1962 | oneway: false, 1963 | throws: [ 1964 | { 1965 | id: 1, 1966 | name: 'te_st', 1967 | type: 'string' 1968 | } 1969 | ], 1970 | type: 'void' 1971 | } 1972 | } 1973 | } 1974 | } 1975 | }; 1976 | 1977 | const ast = thriftParser(content); 1978 | 1979 | expect(ast).toEqual(expected); 1980 | done(); 1981 | }); 1982 | 1983 | it('parses a service containing a throws containing `.` (Identifier)', function(done) { 1984 | const content = ` 1985 | service Test { 1986 | void test() throws (1: string te.st) 1987 | } 1988 | `; 1989 | 1990 | const expected = { 1991 | service: { 1992 | Test: { 1993 | functions: { 1994 | test: { 1995 | args: [], 1996 | name: 'test', 1997 | oneway: false, 1998 | throws: [ 1999 | { 2000 | id: 1, 2001 | name: 'te.st', 2002 | type: 'string' 2003 | } 2004 | ], 2005 | type: 'void' 2006 | } 2007 | } 2008 | } 2009 | } 2010 | }; 2011 | 2012 | const ast = thriftParser(content); 2013 | 2014 | expect(ast).toEqual(expected); 2015 | done(); 2016 | }); 2017 | 2018 | it('parses a service containing a throws containing `.`, `_`, letters and numbers (Identifier)', function(done) { 2019 | const content = ` 2020 | service Test { 2021 | void test() throws (1: string te.st_123) 2022 | } 2023 | `; 2024 | 2025 | const expected = { 2026 | service: { 2027 | Test: { 2028 | functions: { 2029 | test: { 2030 | args: [], 2031 | name: 'test', 2032 | oneway: false, 2033 | throws: [ 2034 | { 2035 | id: 1, 2036 | name: 'te.st_123', 2037 | type: 'string' 2038 | } 2039 | ], 2040 | type: 'void' 2041 | } 2042 | } 2043 | } 2044 | } 2045 | }; 2046 | 2047 | const ast = thriftParser(content); 2048 | 2049 | expect(ast).toEqual(expected); 2050 | done(); 2051 | }); 2052 | 2053 | it('parses a service containing a function with whitespace around throws', function(done) { 2054 | const content = ` 2055 | service Test { 2056 | void test() throws ( 1: string test ) 2057 | } 2058 | `; 2059 | 2060 | const expected = { 2061 | service: { 2062 | Test: { 2063 | functions: { 2064 | test: { 2065 | args: [], 2066 | name: 'test', 2067 | oneway: false, 2068 | throws: [ 2069 | { 2070 | id: 1, 2071 | name: 'test', 2072 | type: 'string' 2073 | } 2074 | ], 2075 | type: 'void' 2076 | } 2077 | } 2078 | } 2079 | } 2080 | }; 2081 | 2082 | const ast = thriftParser(content); 2083 | 2084 | expect(ast).toEqual(expected); 2085 | done(); 2086 | }); 2087 | 2088 | it('parses a service containing a function with defaulted throws', function(done) { 2089 | const content = ` 2090 | service Test { 2091 | void test() throws (1: string test1 = 'test', 2: i16 test2 = 123) 2092 | } 2093 | `; 2094 | 2095 | const expected = { 2096 | service: { 2097 | Test: { 2098 | functions: { 2099 | test: { 2100 | args: [], 2101 | name: 'test', 2102 | oneway: false, 2103 | throws: [ 2104 | { 2105 | id: 1, 2106 | name: 'test1', 2107 | type: 'string', 2108 | defaultValue: 'test' 2109 | }, 2110 | { 2111 | id: 2, 2112 | name: 'test2', 2113 | type: 'i16', 2114 | defaultValue: 123 2115 | } 2116 | ], 2117 | type: 'void' 2118 | } 2119 | } 2120 | } 2121 | } 2122 | }; 2123 | 2124 | const ast = thriftParser(content); 2125 | 2126 | expect(ast).toEqual(expected); 2127 | done(); 2128 | }); 2129 | }); 2130 | --------------------------------------------------------------------------------