1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #include <map>
21 #include <string>
22 #include <fstream>
23 #include <iomanip>
24 #include <iostream>
25 #include <limits>
26 #include <vector>
27 #include <list>
28 #include <cassert>
29 #include <unordered_map>
30 
31 #include <stdlib.h>
32 #include <sys/stat.h>
33 #include <sstream>
34 #include "thrift/platform.h"
35 #include "thrift/version.h"
36 
37 using std::map;
38 using std::ostream;
39 using std::ostringstream;
40 using std::string;
41 using std::stringstream;
42 using std::unordered_map;
43 using std::vector;
44 
45 static const string endl = "\n"; // avoid ostream << std::endl flushes
46 static const string episode_file_name = "thrift.js.episode";
47 // largest consecutive integer representable by a double (2 ^ 53 - 1)
48 static const int64_t max_safe_integer = 0x1fffffffffffff;
49 // smallest consecutive number representable by a double (-2 ^ 53 + 1)
50 static const int64_t min_safe_integer = -max_safe_integer;
51 
52 #include "thrift/generate/t_oop_generator.h"
53 
54 
55 /**
56  * JS code generator.
57  */
58 class t_js_generator : public t_oop_generator {
59 public:
t_js_generator(t_program * program,const std::map<std::string,std::string> & parsed_options,const std::string & option_string)60   t_js_generator(t_program* program,
61                  const std::map<std::string, std::string>& parsed_options,
62                  const std::string& option_string)
63     : t_oop_generator(program) {
64     (void)option_string;
65     std::map<std::string, std::string>::const_iterator iter;
66 
67     gen_node_ = false;
68     gen_jquery_ = false;
69     gen_ts_ = false;
70     gen_es6_ = false;
71     gen_episode_file_ = false;
72 
73     bool with_ns_ = false;
74 
75     for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) {
76       if( iter->first.compare("node") == 0) {
77         gen_node_ = true;
78       } else if( iter->first.compare("jquery") == 0) {
79         gen_jquery_ = true;
80       } else if( iter->first.compare("ts") == 0) {
81         gen_ts_ = true;
82       } else if( iter->first.compare("with_ns") == 0) {
83         with_ns_ = true;
84       } else if( iter->first.compare("es6") == 0) {
85         gen_es6_ = true;
86       } else if( iter->first.compare("imports") == 0) {
87         parse_imports(program, iter->second);
88       } else if (iter->first.compare("thrift_package_output_directory") == 0) {
89         parse_thrift_package_output_directory(iter->second);
90       } else {
91         throw std::invalid_argument("unknown option js:" + iter->first);
92       }
93     }
94 
95     if (gen_es6_ && gen_jquery_) {
96       throw std::invalid_argument("invalid switch: [-gen js:es6,jquery] options not compatible");
97     }
98 
99     if (gen_node_ && gen_jquery_) {
100       throw std::invalid_argument("invalid switch: [-gen js:node,jquery] options not compatible, try: [-gen js:node -gen "
101             "js:jquery]");
102     }
103 
104     if (!gen_node_ && with_ns_) {
105       throw std::invalid_argument("invalid switch: [-gen js:with_ns] is only valid when using node.js");
106     }
107 
108     // Depending on the processing flags, we will update these to be ES6 compatible
109     js_const_type_ = "var ";
110     js_let_type_ = "var ";
111     js_var_type_ = "var ";
112     if (gen_es6_) {
113       js_const_type_ = "const ";
114       js_let_type_ = "let ";
115     }
116 
117     if (gen_node_) {
118       out_dir_base_ = "gen-nodejs";
119       no_ns_ = !with_ns_;
120     } else {
121       out_dir_base_ = "gen-js";
122       no_ns_ = false;
123     }
124 
125     escape_['\''] = "\\'";
126   }
127 
128   /**
129    * Init and close methods
130    */
131 
132   void init_generator() override;
133   void close_generator() override;
134   std::string display_name() const override;
135 
136   /**
137    * Program-level generation functions
138    */
139 
140   void generate_typedef(t_typedef* ttypedef) override;
141   void generate_enum(t_enum* tenum) override;
142   void generate_const(t_const* tconst) override;
143   void generate_struct(t_struct* tstruct) override;
144   void generate_xception(t_struct* txception) override;
145   void generate_service(t_service* tservice) override;
146 
147   std::string render_recv_throw(std::string var);
148   std::string render_recv_return(std::string var);
149 
150   std::string render_const_value(t_type* type, t_const_value* value);
151 
152   /**
153    * Structs!
154    */
155 
156   void generate_js_struct(t_struct* tstruct, bool is_exception);
157   void generate_js_struct_definition(std::ostream& out,
158                                      t_struct* tstruct,
159                                      bool is_xception = false,
160                                      bool is_exported = true);
161   void generate_js_struct_reader(std::ostream& out, t_struct* tstruct);
162   void generate_js_struct_writer(std::ostream& out, t_struct* tstruct);
163   void generate_js_function_helpers(t_function* tfunction);
164 
165   /**
166    * Service-level generation functions
167    */
168 
169   void generate_service_helpers(t_service* tservice);
170   void generate_service_interface(t_service* tservice);
171   void generate_service_rest(t_service* tservice);
172   void generate_service_client(t_service* tservice);
173   void generate_service_processor(t_service* tservice);
174   void generate_process_function(t_service* tservice, t_function* tfunction);
175 
176   /**
177    * Serialization constructs
178    */
179 
180   void generate_deserialize_field(std::ostream& out,
181                                   t_field* tfield,
182                                   std::string prefix = "",
183                                   bool inclass = false);
184 
185   void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = "");
186 
187   void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = "");
188 
189   void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = "");
190 
191   void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = "");
192 
193   void generate_deserialize_list_element(std::ostream& out,
194                                          t_list* tlist,
195                                          std::string prefix = "");
196 
197   void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = "");
198 
199   void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = "");
200 
201   void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = "");
202 
203   void generate_serialize_map_element(std::ostream& out,
204                                       t_map* tmap,
205                                       std::string kiter,
206                                       std::string viter);
207 
208   void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter);
209 
210   void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter);
211 
212   /**
213    * Helper rendering functions
214    */
215 
216   std::string js_includes();
217   std::string ts_includes();
218   std::string ts_service_includes();
219   std::string render_includes();
220   std::string render_ts_includes();
221   std::string get_import_path(t_program* program);
222   std::string declare_field(t_field* tfield, bool init = false, bool obj = false);
223   std::string function_signature(t_function* tfunction,
224                                  std::string prefix = "",
225                                  bool include_callback = false);
226   std::string argument_list(t_struct* tstruct, bool include_callback = false);
227   std::string type_to_enum(t_type* ttype);
228   std::string make_valid_nodeJs_identifier(std::string const& name);
229   std::string next_identifier_name(std::vector<t_field*> const& fields, std::string const& base_name);
230   bool find_field(std::vector<t_field*> const& fields, std::string const& name);
231 
232   /**
233    * Helper parser functions
234    */
235 
236   void parse_imports(t_program* program, const std::string& imports_string);
237   void parse_thrift_package_output_directory(const std::string& thrift_package_output_directory);
238 
autogen_comment()239   std::string autogen_comment() override {
240     return std::string("//\n") + "// Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n"
241            + "//\n" + "// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n"
242            + "//\n";
243   }
244 
245   t_type* get_contained_type(t_type* t);
246 
js_namespace_pieces(t_program * p)247   std::vector<std::string> js_namespace_pieces(t_program* p) {
248     std::string ns = p->get_namespace("js");
249 
250     std::string::size_type loc;
251     std::vector<std::string> pieces;
252 
253     if (no_ns_) {
254       return pieces;
255     }
256 
257     if (ns.size() > 0) {
258       while ((loc = ns.find(".")) != std::string::npos) {
259         pieces.push_back(ns.substr(0, loc));
260         ns = ns.substr(loc + 1);
261       }
262     }
263 
264     if (ns.size() > 0) {
265       pieces.push_back(ns);
266     }
267 
268     return pieces;
269   }
270 
js_type_namespace(t_program * p)271   std::string js_type_namespace(t_program* p) {
272     if (gen_node_) {
273       if (p != nullptr && p != program_) {
274         return make_valid_nodeJs_identifier(p->get_name()) + "_ttypes.";
275       }
276       return "ttypes.";
277     }
278     return js_namespace(p);
279   }
280 
js_export_namespace(t_program * p)281   std::string js_export_namespace(t_program* p) {
282     if (gen_node_) {
283       return "exports.";
284     }
285     return js_namespace(p);
286   }
287 
has_js_namespace(t_program * p)288   bool has_js_namespace(t_program* p) {
289     if (no_ns_) {
290       return false;
291     }
292     std::string ns = p->get_namespace("js");
293     return (ns.size() > 0);
294   }
295 
js_namespace(t_program * p)296   std::string js_namespace(t_program* p) {
297     if (no_ns_) {
298       return "";
299     }
300     std::string ns = p->get_namespace("js");
301     if (ns.size() > 0) {
302       ns += ".";
303     }
304 
305     return ns;
306   }
307 
308   /**
309    * TypeScript Definition File helper functions
310    */
311 
312   string ts_function_signature(t_function* tfunction, bool include_callback);
313   string ts_get_type(t_type* type);
314 
315   /**
316    * Special indentation for TypeScript Definitions because of the module.
317    * Returns the normal indentation + "  " if a module was defined.
318    * @return string
319    */
ts_indent()320   string ts_indent() { return indent() + (!ts_module_.empty() ? "  " : ""); }
321 
322   /**
323    * Returns "declare " if no module was defined.
324    * @return string
325    */
ts_declare()326   string ts_declare() { return (ts_module_.empty() ? (gen_node_ ? "declare " : "export declare ") : ""); }
327 
328   /**
329    * Returns "?" if the given field is optional or has a default value.
330    * @param t_field The field to check
331    * @return string
332    */
ts_get_req(t_field * field)333   string ts_get_req(t_field* field) {return (field->get_req() == t_field::T_OPTIONAL || field->get_value() != nullptr ? "?" : ""); }
334 
335   /**
336    * Returns the documentation, if the provided documentable object has one.
337    * @param t_doc The object to get the documentation from
338    * @return string The documentation
339    */
ts_print_doc(t_doc * tdoc)340   string ts_print_doc(t_doc* tdoc) {
341     string result = endl;
342 
343     if (tdoc->has_doc()) {
344       std::stringstream doc(tdoc->get_doc());
345       string item;
346 
347       result += ts_indent() + "/**" + endl;
348       while (std::getline(doc, item)) {
349         result += ts_indent() + " * " + item + endl;
350       }
351       result += ts_indent() + " */" + endl;
352     }
353     return result;
354   }
355 
356 private:
357   /**
358    * True if we should generate NodeJS-friendly RPC services.
359    */
360   bool gen_node_;
361 
362   /**
363    * True if we should generate services that use jQuery ajax (async/sync).
364    */
365   bool gen_jquery_;
366 
367   /**
368    * True if we should generate a TypeScript Definition File for each service.
369    */
370   bool gen_ts_;
371 
372   /**
373    * True if we should generate ES6 code, i.e. with Promises
374    */
375   bool gen_es6_;
376 
377   /**
378    * True if we will generate an episode file.
379    */
380   bool gen_episode_file_;
381 
382   /**
383    * The name of the defined module(s), for TypeScript Definition Files.
384    */
385   string ts_module_;
386 
387   /**
388    * True if we should not generate namespace objects for node.
389    */
390   bool no_ns_;
391 
392   /**
393    * The node modules to use when importing the previously generated files.
394    */
395   vector<string> imports;
396 
397   /**
398    * Cache for imported modules.
399    */
400   unordered_map<string, string> module_name_2_import_path;
401 
402   /**
403    * Cache for TypeScript includes to generated import name.
404    */
405   unordered_map<t_program*, string> include_2_import_name;
406 
407   /**
408    * The prefix to use when generating the episode file.
409    */
410   string thrift_package_output_directory_;
411 
412   /**
413    * The variable decorator for "const" variables. Will default to "var" if in an incompatible language.
414    */
415   string js_const_type_;
416 
417    /**
418    * The variable decorator for "let" variables. Will default to "var" if in an incompatible language.
419    */
420   string js_let_type_;
421 
422    /**
423    * The default variable decorator. Supports all javascript languages, but is not scoped to functions or closures.
424    */
425   string js_var_type_;
426 
427   /**
428    * File streams
429    */
430   ofstream_with_content_based_conditional_update f_episode_;
431   ofstream_with_content_based_conditional_update f_types_;
432   ofstream_with_content_based_conditional_update f_service_;
433   ofstream_with_content_based_conditional_update f_types_ts_;
434   ofstream_with_content_based_conditional_update f_service_ts_;
435 };
436 
437 /**
438  * Prepares for file generation by opening up the necessary file output
439  * streams.
440  *
441  * @param tprogram The program to generate
442  */
init_generator()443 void t_js_generator::init_generator() {
444   // Make output directory
445   MKDIR(get_out_dir().c_str());
446 
447   const auto outdir = get_out_dir();
448 
449   // Make output file(s)
450   if (gen_episode_file_) {
451     const auto f_episode_file_path = outdir + episode_file_name;
452     f_episode_.open(f_episode_file_path);
453   }
454 
455   const auto f_types_name = outdir + program_->get_name() + "_types.js";
456   f_types_.open(f_types_name.c_str());
457   if (gen_episode_file_) {
458     const auto types_module = program_->get_name() + "_types";
459     f_episode_ << types_module << ":" << thrift_package_output_directory_ << "/" << types_module << endl;
460   }
461 
462   if (gen_ts_) {
463     const auto f_types_ts_name = outdir + program_->get_name() + "_types.d.ts";
464     f_types_ts_.open(f_types_ts_name.c_str());
465   }
466 
467   // Print header
468   f_types_ << autogen_comment();
469 
470   if ((gen_node_ || gen_es6_) && no_ns_) {
471     f_types_ << "\"use strict\";" << endl << endl;
472   }
473 
474   f_types_ << js_includes() << endl << render_includes() << endl;
475 
476   if (gen_ts_) {
477     f_types_ts_ << autogen_comment() << ts_includes() << endl << render_ts_includes() << endl;
478   }
479 
480   if (gen_node_) {
481     f_types_ << js_const_type_ << "ttypes = module.exports = {};" << endl;
482   }
483 
484   string pns;
485 
486   // setup the namespace
487   // TODO should the namespace just be in the directory structure for node?
488   vector<string> ns_pieces = js_namespace_pieces(program_);
489   if (ns_pieces.size() > 0) {
490     for (size_t i = 0; i < ns_pieces.size(); ++i) {
491       pns += ((i == 0) ? "" : ".") + ns_pieces[i];
492       f_types_ << "if (typeof " << pns << " === 'undefined') {" << endl;
493       f_types_ << "  " << pns << " = {};" << endl;
494       f_types_ << "}" << endl;
495       f_types_ << "" << "if (typeof module !== 'undefined' && module.exports) {" << endl;
496       f_types_ << "  module.exports." << pns << " = " << pns << ";" << endl << "}" << endl;
497     }
498     if (gen_ts_) {
499       ts_module_ = pns;
500       f_types_ts_ << "declare module " << ts_module_ << " {";
501     }
502   }
503 }
504 
505 /**
506  * Prints standard js imports
507  */
js_includes()508 string t_js_generator::js_includes() {
509   if (gen_node_) {
510     string result = js_const_type_ + "thrift = require('thrift');\n"
511         + js_const_type_ + "Thrift = thrift.Thrift;\n";
512     if (!gen_es6_) {
513       result += js_const_type_ + "Q = thrift.Q;\n";
514     }
515     result += js_const_type_ + "Int64 = require('node-int64');\n";
516     return result;
517   }
518   string result = "if (typeof Int64 === 'undefined' && typeof require === 'function') {\n  " + js_const_type_ + "Int64 = require('node-int64');\n}\n";
519   return result;
520 }
521 
522 /**
523  * Prints standard ts imports
524  */
ts_includes()525 string t_js_generator::ts_includes() {
526   if (gen_node_) {
527     return string(
528         "import thrift = require('thrift');\n"
529         "import Thrift = thrift.Thrift;\n"
530         "import Q = thrift.Q;\n"
531         "import Int64 = require('node-int64');");
532   }
533   return string("import Int64 = require('node-int64');");
534 }
535 
536 /**
537  * Prints service ts imports
538  */
ts_service_includes()539 string t_js_generator::ts_service_includes() {
540   if (gen_node_) {
541     return string(
542         "import thrift = require('thrift');\n"
543         "import Thrift = thrift.Thrift;\n"
544         "import Q = thrift.Q;\n"
545         "import Int64 = require('node-int64');");
546   }
547   return string("import Int64 = require('node-int64');");
548 }
549 
550 /**
551  * Renders all the imports necessary for including another Thrift program
552  */
render_includes()553 string t_js_generator::render_includes() {
554   string result = "";
555 
556   if (gen_node_) {
557     const vector<t_program*>& includes = program_->get_includes();
558     for (auto include : includes) {
559       result += js_const_type_ + make_valid_nodeJs_identifier(include->get_name()) + "_ttypes = require('" + get_import_path(include) + "');\n";
560     }
561     if (includes.size() > 0) {
562       result += "\n";
563     }
564   }
565 
566   return result;
567 }
568 
569 /**
570  * Renders all the imports necessary for including another Thrift program
571  */
render_ts_includes()572 string t_js_generator::render_ts_includes() {
573   string result;
574 
575   if (!gen_node_) {
576     return result;
577   }
578   const vector<t_program*>& includes = program_->get_includes();
579   for (auto include : includes) {
580     string include_name = make_valid_nodeJs_identifier(include->get_name()) + "_ttypes";
581     include_2_import_name.insert({include, include_name});
582     result += "import " + include_name + " = require('" + get_import_path(include) + "');\n";
583   }
584   if (includes.size() > 0) {
585     result += "\n";
586   }
587 
588   return result;
589 }
590 
get_import_path(t_program * program)591 string t_js_generator::get_import_path(t_program* program) {
592   const string import_file_name(program->get_name() + "_types");
593   if (program->get_recursive()) {
594     return "./" + import_file_name;
595   }
596 
597   const string import_file_name_with_extension = import_file_name + ".js";
598 
599     auto module_name_and_import_path_iterator = module_name_2_import_path.find(import_file_name);
600     if (module_name_and_import_path_iterator != module_name_2_import_path.end()) {
601       return module_name_and_import_path_iterator->second;
602     }
603     return "./" + import_file_name;
604 }
605 
606 /**
607  * Close up (or down) some filez.
608  */
close_generator()609 void t_js_generator::close_generator() {
610   // Close types file(s)
611 
612   f_types_.close();
613 
614   if (gen_ts_) {
615     if (!ts_module_.empty()) {
616       f_types_ts_ << "}";
617     }
618     f_types_ts_.close();
619   }
620   if (gen_episode_file_){
621     f_episode_.close();
622   }
623 }
624 
625 /**
626  * Generates a typedef. This is not done in JS, types are all implicit.
627  *
628  * @param ttypedef The type definition
629  */
generate_typedef(t_typedef * ttypedef)630 void t_js_generator::generate_typedef(t_typedef* ttypedef) {
631   (void)ttypedef;
632 }
633 
634 /**
635  * Generates code for an enumerated type. Since define is expensive to lookup
636  * in JS, we use a global array for this.
637  *
638  * @param tenum The enumeration
639  */
generate_enum(t_enum * tenum)640 void t_js_generator::generate_enum(t_enum* tenum) {
641   f_types_ << js_type_namespace(tenum->get_program()) << tenum->get_name() << " = {" << endl;
642 
643   if (gen_ts_) {
644     f_types_ts_ << ts_print_doc(tenum) << ts_indent() << ts_declare() << "enum "
645                 << tenum->get_name() << " {" << endl;
646   }
647 
648   indent_up();
649 
650   vector<t_enum_value*> const& constants = tenum->get_constants();
651   vector<t_enum_value*>::const_iterator c_iter;
652   for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
653     int value = (*c_iter)->get_value();
654     if (gen_ts_) {
655       f_types_ts_ << ts_indent() << (*c_iter)->get_name() << " = " << value << "," << endl;
656       // add 'value: key' in addition to 'key: value' for TypeScript enums
657       f_types_ << indent() << "'" << value << "' : '" << (*c_iter)->get_name() << "'," << endl;
658     }
659     f_types_ << indent() << "'" << (*c_iter)->get_name() << "' : " << value;
660     if (c_iter != constants.end() - 1) {
661       f_types_ << ",";
662     }
663     f_types_ << endl;
664   }
665 
666   indent_down();
667 
668   f_types_ << "};" << endl;
669 
670   if (gen_ts_) {
671     f_types_ts_ << ts_indent() << "}" << endl;
672   }
673 }
674 
675 /**
676  * Generate a constant value
677  */
generate_const(t_const * tconst)678 void t_js_generator::generate_const(t_const* tconst) {
679   t_type* type = tconst->get_type();
680   string name = tconst->get_name();
681   t_const_value* value = tconst->get_value();
682 
683   f_types_ << js_type_namespace(program_) << name << " = ";
684   f_types_ << render_const_value(type, value) << ";" << endl;
685 
686   if (gen_ts_) {
687     f_types_ts_ << ts_print_doc(tconst) << ts_indent() << ts_declare() << js_const_type_ << name << ": "
688                 << ts_get_type(type) << ";" << endl;
689   }
690 }
691 
692 /**
693  * Prints the value of a constant with the given type. Note that type checking
694  * is NOT performed in this function as it is always run beforehand using the
695  * validate_types method in main.cc
696  */
render_const_value(t_type * type,t_const_value * value)697 string t_js_generator::render_const_value(t_type* type, t_const_value* value) {
698   std::ostringstream out;
699 
700   type = get_true_type(type);
701 
702   if (type->is_base_type()) {
703     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
704     switch (tbase) {
705     case t_base_type::TYPE_STRING:
706       out << "'" << get_escaped_string(value) << "'";
707       break;
708     case t_base_type::TYPE_BOOL:
709       out << (value->get_integer() > 0 ? "true" : "false");
710       break;
711     case t_base_type::TYPE_I8:
712     case t_base_type::TYPE_I16:
713     case t_base_type::TYPE_I32:
714       out << value->get_integer();
715       break;
716     case t_base_type::TYPE_I64:
717       {
718         int64_t const& integer_value = value->get_integer();
719         if (integer_value <= max_safe_integer && integer_value >= min_safe_integer) {
720           out << "new Int64(" << integer_value << ")";
721         } else {
722           out << "new Int64('" << std::hex << integer_value << std::dec << "')";
723         }
724       }
725       break;
726     case t_base_type::TYPE_DOUBLE:
727       if (value->get_type() == t_const_value::CV_INTEGER) {
728         out << value->get_integer();
729       } else {
730         out << emit_double_as_string(value->get_double());
731       }
732       break;
733     default:
734       throw std::runtime_error("compiler error: no const of base type " + t_base_type::t_base_name(tbase));
735     }
736   } else if (type->is_enum()) {
737     out << value->get_integer();
738   } else if (type->is_struct() || type->is_xception()) {
739     out << "new " << js_type_namespace(type->get_program()) << type->get_name() << "({";
740     indent_up();
741     const vector<t_field*>& fields = ((t_struct*)type)->get_members();
742     vector<t_field*>::const_iterator f_iter;
743     const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map();
744     map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter;
745     for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
746       t_type* field_type = nullptr;
747       for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
748         if ((*f_iter)->get_name() == v_iter->first->get_string()) {
749           field_type = (*f_iter)->get_type();
750         }
751       }
752       if (field_type == nullptr) {
753         throw std::runtime_error("type error: " + type->get_name() + " has no field " + v_iter->first->get_string());
754       }
755       if (v_iter != val.begin())
756         out << ",";
757       out << endl << indent() << render_const_value(g_type_string, v_iter->first);
758       out << " : ";
759       out << render_const_value(field_type, v_iter->second);
760     }
761     indent_down();
762     out << endl << indent() << "})";
763   } else if (type->is_map()) {
764     t_type* ktype = ((t_map*)type)->get_key_type();
765 
766     t_type* vtype = ((t_map*)type)->get_val_type();
767     out << "{" << endl;
768     indent_up();
769 
770     const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map();
771     map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter;
772     for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
773       if (v_iter != val.begin())
774         out << "," << endl;
775 
776       if (ktype->is_base_type() && ((t_base_type*)get_true_type(ktype))->get_base() == t_base_type::TYPE_I64){
777         out << indent() << "\"" << v_iter->first->get_integer() << "\"";
778       } else {
779         out << indent() << render_const_value(ktype, v_iter->first);
780       }
781 
782       out << " : ";
783       out << render_const_value(vtype, v_iter->second);
784     }
785     indent_down();
786     out << endl << indent() << "}";
787   } else if (type->is_list() || type->is_set()) {
788     t_type* etype;
789     if (type->is_list()) {
790       etype = ((t_list*)type)->get_elem_type();
791     } else {
792       etype = ((t_set*)type)->get_elem_type();
793     }
794     out << "[";
795     const vector<t_const_value*>& val = value->get_list();
796     vector<t_const_value*>::const_iterator v_iter;
797     for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
798       if (v_iter != val.begin())
799         out << ",";
800       out << render_const_value(etype, *v_iter);
801     }
802     out << "]";
803   }
804   return out.str();
805 }
806 
807 /**
808  * Make a struct
809  */
generate_struct(t_struct * tstruct)810 void t_js_generator::generate_struct(t_struct* tstruct) {
811   generate_js_struct(tstruct, false);
812 }
813 
814 /**
815  * Generates a struct definition for a thrift exception. Basically the same
816  * as a struct but extends the Exception class.
817  *
818  * @param txception The struct definition
819  */
generate_xception(t_struct * txception)820 void t_js_generator::generate_xception(t_struct* txception) {
821   generate_js_struct(txception, true);
822 }
823 
824 /**
825  * Structs can be normal or exceptions.
826  */
generate_js_struct(t_struct * tstruct,bool is_exception)827 void t_js_generator::generate_js_struct(t_struct* tstruct, bool is_exception) {
828   generate_js_struct_definition(f_types_, tstruct, is_exception);
829 }
830 
831 /**
832  * Return type of contained elements for a container type. For maps
833  * this is type of value (keys are always strings in js)
834  */
get_contained_type(t_type * t)835 t_type* t_js_generator::get_contained_type(t_type* t) {
836   t_type* etype;
837   if (t->is_list()) {
838     etype = ((t_list*)t)->get_elem_type();
839   } else if (t->is_set()) {
840     etype = ((t_set*)t)->get_elem_type();
841   } else {
842     etype = ((t_map*)t)->get_val_type();
843   }
844   return etype;
845 }
846 
847 /**
848  * Generates a struct definition for a thrift data type. This is nothing in JS
849  * where the objects are all just associative arrays (unless of course we
850  * decide to start using objects for them...)
851  *
852  * @param tstruct The struct definition
853  */
generate_js_struct_definition(ostream & out,t_struct * tstruct,bool is_exception,bool is_exported)854 void t_js_generator::generate_js_struct_definition(ostream& out,
855                                                    t_struct* tstruct,
856                                                    bool is_exception,
857                                                    bool is_exported) {
858   const vector<t_field*>& members = tstruct->get_members();
859   vector<t_field*>::const_iterator m_iter;
860 
861   if (gen_node_) {
862     string prefix = has_js_namespace(tstruct->get_program()) ? js_namespace(tstruct->get_program()) : js_const_type_;
863     out << prefix << tstruct->get_name() <<
864       (is_exported ? " = module.exports." + tstruct->get_name() : "");
865     if (gen_ts_) {
866       f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class "
867                   << tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "")
868                   << " {" << endl;
869     }
870   } else {
871     out << js_namespace(tstruct->get_program()) << tstruct->get_name();
872     if (gen_ts_) {
873       f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class "
874                   << tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "")
875                   << " {" << endl;
876     }
877   }
878 
879   if (gen_es6_) {
880     if (gen_node_ && is_exception) {
881       out << " = class extends Thrift.TException {" << endl;
882     } else {
883       out << " = class {" << endl;
884     }
885     indent_up();
886     indent(out) << "constructor(args) {" << endl;
887   } else {
888     out << " = function(args) {" << endl;
889   }
890 
891   indent_up();
892 
893   // Call super() method on inherited Error class
894   if (gen_node_ && is_exception) {
895     if (gen_es6_) {
896       indent(out) << "super(args);" << endl;
897     } else {
898       indent(out) << "Thrift.TException.call(this, \"" << js_namespace(tstruct->get_program())
899         << tstruct->get_name() << "\");" << endl;
900     }
901     out << indent() << "this.name = \"" << js_namespace(tstruct->get_program())
902         << tstruct->get_name() << "\";" << endl;
903   }
904 
905   // members with arguments
906   for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
907     string dval = declare_field(*m_iter, false, true);
908     t_type* t = get_true_type((*m_iter)->get_type());
909     if ((*m_iter)->get_value() != nullptr && !(t->is_struct() || t->is_xception())) {
910       dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value());
911       out << indent() << "this." << (*m_iter)->get_name() << " = " << dval << ";" << endl;
912     } else {
913       out << indent() << dval << ";" << endl;
914     }
915     if (gen_ts_) {
916       string ts_access = gen_node_ ? "public " : "";
917       string member_name = (*m_iter)->get_name();
918 
919       // Special case. Exceptions derive from Error, and error has a non optional message field.
920       // Ignore the optional flag in this case, otherwise we will generate a incompatible field
921       // in the eyes of typescript.
922       string optional_flag = is_exception && member_name == "message" ? "" : ts_get_req(*m_iter);
923 
924       f_types_ts_ << ts_indent() << ts_access << member_name << optional_flag << ": "
925                   << ts_get_type((*m_iter)->get_type()) << ";" << endl;
926     }
927   }
928 
929   // Generate constructor from array
930   if (members.size() > 0) {
931 
932     for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
933       t_type* t = get_true_type((*m_iter)->get_type());
934       if ((*m_iter)->get_value() != nullptr && (t->is_struct() || t->is_xception())) {
935         indent(out) << "this." << (*m_iter)->get_name() << " = "
936                     << render_const_value(t, (*m_iter)->get_value()) << ";" << endl;
937       }
938     }
939 
940     // Early returns for exceptions
941     for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
942       t_type* t = get_true_type((*m_iter)->get_type());
943       if (t->is_xception()) {
944         out << indent() << "if (args instanceof " << js_type_namespace(t->get_program())
945             << t->get_name() << ") {" << endl << indent() << indent() << "this."
946             << (*m_iter)->get_name() << " = args;" << endl << indent() << indent() << "return;"
947             << endl << indent() << "}" << endl;
948       }
949     }
950 
951     indent(out) << "if (args) {" << endl;
952     indent_up();
953     if (gen_ts_) {
954       f_types_ts_ << endl << ts_indent() << "constructor(args?: { ";
955     }
956 
957     for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
958       t_type* t = get_true_type((*m_iter)->get_type());
959       indent(out) << "if (args." << (*m_iter)->get_name() << " !== undefined && args." << (*m_iter)->get_name() << " !== null) {" << endl;
960       indent_up();
961       indent(out) << "this." << (*m_iter)->get_name();
962 
963       if (t->is_struct()) {
964         out << (" = new " + js_type_namespace(t->get_program()) + t->get_name() +
965                 "(args."+(*m_iter)->get_name() +");");
966         out << endl;
967       } else if (t->is_container()) {
968         t_type* etype = get_contained_type(t);
969         string copyFunc = t->is_map() ? "Thrift.copyMap" : "Thrift.copyList";
970         string type_list = "";
971 
972         while (etype->is_container()) {
973           if (type_list.length() > 0) {
974             type_list += ", ";
975           }
976           type_list += etype->is_map() ? "Thrift.copyMap" : "Thrift.copyList";
977           etype = get_contained_type(etype);
978         }
979 
980         if (etype->is_struct()) {
981           if (type_list.length() > 0) {
982             type_list += ", ";
983           }
984           type_list += js_type_namespace(etype->get_program()) + etype->get_name();
985         }
986         else {
987           if (type_list.length() > 0) {
988             type_list += ", ";
989           }
990           type_list += "null";
991         }
992 
993         out << (" = " + copyFunc + "(args." + (*m_iter)->get_name() +
994                 ", [" + type_list + "]);");
995         out << endl;
996       } else {
997         out << " = args." << (*m_iter)->get_name() << ";" << endl;
998       }
999 
1000       indent_down();
1001       if (!(*m_iter)->get_req()) {
1002         indent(out) << "} else {" << endl;
1003          indent(out)
1004             << "  throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.UNKNOWN, "
1005                "'Required field " << (*m_iter)->get_name() << " is unset!');" << endl;
1006       }
1007       indent(out) << "}" << endl;
1008       if (gen_ts_) {
1009         f_types_ts_ << (*m_iter)->get_name() << ts_get_req(*m_iter) << ": "
1010                     << ts_get_type((*m_iter)->get_type()) << "; ";
1011       }
1012     }
1013     indent_down();
1014     out << indent() << "}" << endl;
1015     if (gen_ts_) {
1016       f_types_ts_ << "});" << endl;
1017     }
1018   }
1019 
1020   // Done with constructor
1021   indent_down();
1022   if (gen_es6_) {
1023     indent(out) << "}" << endl << endl;
1024   } else {
1025     indent(out) << "};" << endl;
1026   }
1027 
1028   if (gen_ts_) {
1029     f_types_ts_ << ts_indent() << "}" << endl;
1030   }
1031 
1032   if (!gen_es6_) {
1033     if (is_exception) {
1034       out << "Thrift.inherits(" << js_namespace(tstruct->get_program()) << tstruct->get_name()
1035           << ", Thrift.TException);" << endl;
1036       out << js_namespace(tstruct->get_program()) << tstruct->get_name() << ".prototype.name = '"
1037           << tstruct->get_name() << "';" << endl;
1038     } else {
1039       // init prototype manually if we aren't using es6
1040       out << js_namespace(tstruct->get_program()) << tstruct->get_name() << ".prototype = {};"
1041           << endl;
1042     }
1043 
1044   }
1045 
1046   generate_js_struct_reader(out, tstruct);
1047   generate_js_struct_writer(out, tstruct);
1048 
1049   // Close out the class definition
1050   if (gen_es6_) {
1051     indent_down();
1052     indent(out) << "};" << endl;
1053   }
1054 }
1055 
1056 /**
1057  * Generates the read() method for a struct
1058  */
generate_js_struct_reader(ostream & out,t_struct * tstruct)1059 void t_js_generator::generate_js_struct_reader(ostream& out, t_struct* tstruct) {
1060   const vector<t_field*>& fields = tstruct->get_members();
1061   vector<t_field*>::const_iterator f_iter;
1062 
1063   if (gen_es6_) {
1064     indent(out) << "read (input) {" << endl;
1065   } else {
1066     indent(out) << js_namespace(tstruct->get_program()) << tstruct->get_name()
1067         << ".prototype.read = function(input) {" << endl;
1068   }
1069 
1070   indent_up();
1071 
1072   indent(out) << "input.readStructBegin();" << endl;
1073 
1074   // Loop over reading in fields
1075   indent(out) << "while (true) {" << endl;
1076 
1077   indent_up();
1078 
1079   indent(out) << js_const_type_ << "ret = input.readFieldBegin();" << endl;
1080   indent(out) << js_const_type_ << "ftype = ret.ftype;" << endl;
1081   if (!fields.empty()) {
1082     indent(out) << js_const_type_ << "fid = ret.fid;" << endl;
1083   }
1084 
1085   // Check for field STOP marker and break
1086   indent(out) << "if (ftype == Thrift.Type.STOP) {" << endl;
1087   indent_up();
1088   indent(out) << "break;" << endl;
1089   indent_down();
1090   indent(out) << "}" << endl;
1091   if (!fields.empty()) {
1092     // Switch statement on the field we are reading
1093     indent(out) << "switch (fid) {" << endl;
1094 
1095     indent_up();
1096 
1097     // Generate deserialization code for known cases
1098     for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1099 
1100       indent(out) << "case " << (*f_iter)->get_key() << ":" << endl;
1101       indent(out) << "if (ftype == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl;
1102 
1103       indent_up();
1104       generate_deserialize_field(out, *f_iter, "this.");
1105       indent_down();
1106 
1107       indent(out) << "} else {" << endl;
1108 
1109       indent(out) << "  input.skip(ftype);" << endl;
1110 
1111       out << indent() << "}" << endl << indent() << "break;" << endl;
1112     }
1113     if (fields.size() == 1) {
1114       // pseudo case to make jslint happy
1115       indent(out) << "case 0:" << endl;
1116       indent(out) << "  input.skip(ftype);" << endl;
1117       indent(out) << "  break;" << endl;
1118     }
1119     // In the default case we skip the field
1120     indent(out) << "default:" << endl;
1121     indent(out) << "  input.skip(ftype);" << endl;
1122 
1123     scope_down(out);
1124   } else {
1125     indent(out) << "input.skip(ftype);" << endl;
1126   }
1127 
1128   indent(out) << "input.readFieldEnd();" << endl;
1129 
1130   scope_down(out);
1131 
1132   indent(out) << "input.readStructEnd();" << endl;
1133 
1134   indent(out) << "return;" << endl;
1135 
1136   indent_down();
1137 
1138   if (gen_es6_) {
1139     indent(out) << "}" << endl << endl;
1140   } else {
1141     indent(out) << "};" << endl << endl;
1142   }
1143 }
1144 
1145 /**
1146  * Generates the write() method for a struct
1147  */
generate_js_struct_writer(ostream & out,t_struct * tstruct)1148 void t_js_generator::generate_js_struct_writer(ostream& out, t_struct* tstruct) {
1149   string name = tstruct->get_name();
1150   const vector<t_field*>& fields = tstruct->get_members();
1151   vector<t_field*>::const_iterator f_iter;
1152 
1153   if (gen_es6_) {
1154     indent(out) << "write (output) {" << endl;
1155   } else {
1156     indent(out) << js_namespace(tstruct->get_program()) << tstruct->get_name()
1157         << ".prototype.write = function(output) {" << endl;
1158   }
1159 
1160   indent_up();
1161 
1162   indent(out) << "output.writeStructBegin('" << name << "');" << endl;
1163 
1164   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1165     out << indent() << "if (this." << (*f_iter)->get_name() << " !== null && this."
1166         << (*f_iter)->get_name() << " !== undefined) {" << endl;
1167     indent_up();
1168 
1169     indent(out) << "output.writeFieldBegin("
1170                 << "'" << (*f_iter)->get_name() << "', " << type_to_enum((*f_iter)->get_type())
1171                 << ", " << (*f_iter)->get_key() << ");" << endl;
1172 
1173     // Write field contents
1174     generate_serialize_field(out, *f_iter, "this.");
1175 
1176     indent(out) << "output.writeFieldEnd();" << endl;
1177 
1178     indent_down();
1179     indent(out) << "}" << endl;
1180   }
1181 
1182   out << indent() << "output.writeFieldStop();" << endl << indent() << "output.writeStructEnd();"
1183       << endl;
1184 
1185   out << indent() << "return;" << endl;
1186 
1187   indent_down();
1188   if (gen_es6_) {
1189     out << indent() << "}" << endl << endl;
1190   } else {
1191     out << indent() << "};" << endl << endl;
1192   }
1193 }
1194 
1195 /**
1196  * Generates a thrift service.
1197  *
1198  * @param tservice The service definition
1199  */
generate_service(t_service * tservice)1200 void t_js_generator::generate_service(t_service* tservice) {
1201   string f_service_name = get_out_dir() + service_name_ + ".js";
1202   f_service_.open(f_service_name.c_str());
1203   if (gen_episode_file_) {
1204     f_episode_ << service_name_ << ":" << thrift_package_output_directory_ << "/" << service_name_ << endl;
1205   }
1206 
1207   if (gen_ts_) {
1208     string f_service_ts_name = get_out_dir() + service_name_ + ".d.ts";
1209     f_service_ts_.open(f_service_ts_name.c_str());
1210   }
1211 
1212   f_service_ << autogen_comment();
1213 
1214   if ((gen_node_ || gen_es6_) && no_ns_) {
1215     f_service_ << "\"use strict\";" << endl << endl;
1216   }
1217 
1218   f_service_ << js_includes() << endl << render_includes() << endl;
1219 
1220   if (gen_ts_) {
1221     if (tservice->get_extends() != nullptr) {
1222       f_service_ts_ << "/// <reference path=\"" << tservice->get_extends()->get_name()
1223                     << ".d.ts\" />" << endl;
1224     }
1225     f_service_ts_ << autogen_comment() << endl << ts_includes() << endl << render_ts_includes() << endl;
1226     if (gen_node_) {
1227       f_service_ts_ << "import ttypes = require('./" + program_->get_name() + "_types');" << endl;
1228       // Generate type aliases
1229       // enum
1230       vector<t_enum*> const& enums = program_->get_enums();
1231       vector<t_enum*>::const_iterator e_iter;
1232       for (e_iter = enums.begin(); e_iter != enums.end(); ++e_iter) {
1233         f_service_ts_ << "import " << (*e_iter)->get_name() << " = ttypes."
1234                   << js_namespace(program_) << (*e_iter)->get_name() << endl;
1235       }
1236       // const
1237       vector<t_const*> const& consts = program_->get_consts();
1238       vector<t_const*>::const_iterator c_iter;
1239       for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) {
1240         f_service_ts_ << "import " << (*c_iter)->get_name() << " = ttypes."
1241                   << js_namespace(program_) << (*c_iter)->get_name() << endl;
1242       }
1243       // exception
1244       vector<t_struct*> const& exceptions = program_->get_xceptions();
1245       vector<t_struct*>::const_iterator x_iter;
1246       for (x_iter = exceptions.begin(); x_iter != exceptions.end(); ++x_iter) {
1247         f_service_ts_ << "import " << (*x_iter)->get_name() << " = ttypes."
1248                   << js_namespace(program_) << (*x_iter)->get_name() << endl;
1249       }
1250       // structs
1251       vector<t_struct*> const& structs = program_->get_structs();
1252       vector<t_struct*>::const_iterator s_iter;
1253       for (s_iter = structs.begin(); s_iter != structs.end(); ++s_iter) {
1254         f_service_ts_ << "import " << (*s_iter)->get_name() << " = ttypes."
1255                   << js_namespace(program_) << (*s_iter)->get_name() << endl;
1256       }
1257     } else {
1258       f_service_ts_ << "import { " << program_->get_name() << " } from \"./" << program_->get_name() << "_types\";" << endl << endl;
1259     }
1260     if (!ts_module_.empty()) {
1261       if (gen_node_) {
1262         f_service_ts_ << "declare module " << ts_module_ << " {";
1263       } else {
1264         f_service_ts_ << "declare module \"./" << program_->get_name() << "_types\" {" << endl;
1265         indent_up();
1266         f_service_ts_ << ts_indent() << "module " << program_->get_name() << " {" << endl;
1267         indent_up();
1268       }
1269     }
1270   }
1271 
1272   if (gen_node_) {
1273     if (tservice->get_extends() != nullptr) {
1274       f_service_ << js_const_type_ <<  tservice->get_extends()->get_name() << " = require('./"
1275                  << tservice->get_extends()->get_name() << "');" << endl << js_const_type_
1276                  << tservice->get_extends()->get_name()
1277                  << "Client = " << tservice->get_extends()->get_name() << ".Client;" << endl
1278                  << js_const_type_ << tservice->get_extends()->get_name()
1279                  << "Processor = " << tservice->get_extends()->get_name() << ".Processor;" << endl;
1280 
1281       f_service_ts_ << "import " << tservice->get_extends()->get_name() << " = require('./"
1282                     << tservice->get_extends()->get_name() << "');" << endl;
1283     }
1284 
1285     f_service_ << js_const_type_ << "ttypes = require('./" + program_->get_name() + "_types');" << endl;
1286   }
1287 
1288   generate_service_helpers(tservice);
1289   generate_service_interface(tservice);
1290   generate_service_client(tservice);
1291 
1292   if (gen_node_) {
1293     generate_service_processor(tservice);
1294   }
1295 
1296   f_service_.close();
1297   if (gen_ts_) {
1298     if (!ts_module_.empty()) {
1299       if (gen_node_) {
1300         f_service_ts_ << "}" << endl;
1301       } else {
1302         indent_down();
1303         f_service_ts_ << ts_indent() << "}" << endl;
1304         f_service_ts_ << "}" << endl;
1305       }
1306     }
1307     f_service_ts_.close();
1308   }
1309 }
1310 
1311 /**
1312  * Generates a service server definition.
1313  *
1314  * @param tservice The service to generate a server for.
1315  */
generate_service_processor(t_service * tservice)1316 void t_js_generator::generate_service_processor(t_service* tservice) {
1317   vector<t_function*> functions = tservice->get_functions();
1318   vector<t_function*>::iterator f_iter;
1319 
1320   if (gen_node_) {
1321     string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_;
1322     f_service_ << prefix << service_name_ << "Processor = " << "exports.Processor";
1323     if (gen_ts_) {
1324       f_service_ts_ << endl << "declare class Processor ";
1325       if (tservice->get_extends() != nullptr) {
1326         f_service_ts_ << "extends " << tservice->get_extends()->get_name() << ".Processor ";
1327       }
1328       f_service_ts_ << "{" << endl;
1329       indent_up();
1330 
1331       if(tservice->get_extends() == nullptr) {
1332         f_service_ts_ << ts_indent() << "private _handler: object;" << endl << endl;
1333       }
1334       f_service_ts_ << ts_indent() << "constructor(handler: object);" << endl;
1335       f_service_ts_ << ts_indent() << "process(input: thrift.TProtocol, output: thrift.TProtocol): void;" << endl;
1336       indent_down();
1337     }
1338   } else {
1339     f_service_ << js_namespace(tservice->get_program()) << service_name_ << "Processor = "
1340              << "exports.Processor";
1341   }
1342 
1343   bool is_subclass_service = tservice->get_extends() != nullptr;
1344 
1345   // ES6 Constructor
1346   if (gen_es6_) {
1347     if (is_subclass_service) {
1348       f_service_ << " = class " << service_name_ << "Processor extends " << tservice->get_extends()->get_name() << "Processor {" << endl;
1349     } else {
1350       f_service_ << " = class " << service_name_ << "Processor {" << endl;
1351     }
1352     indent_up();
1353     indent(f_service_) << "constructor(handler) {" << endl;
1354   } else {
1355     f_service_ << " = function(handler) {" << endl;
1356   }
1357 
1358   indent_up();
1359   if (gen_es6_ && is_subclass_service) {
1360     indent(f_service_) << "super(handler);" << endl;
1361   }
1362   indent(f_service_) << "this._handler = handler;" << endl;
1363   indent_down();
1364 
1365   // Done with constructor
1366   if (gen_es6_) {
1367     indent(f_service_) << "}" << endl;
1368   } else {
1369     indent(f_service_) << "};" << endl;
1370   }
1371 
1372   // ES5 service inheritance
1373   if (!gen_es6_ && is_subclass_service) {
1374     indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program())
1375                        << service_name_ << "Processor, " << tservice->get_extends()->get_name()
1376                        << "Processor);" << endl;
1377   }
1378 
1379   // Generate the server implementation
1380   if (gen_es6_) {
1381     indent(f_service_) << "process (input, output) {" << endl;
1382   } else {
1383     indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
1384                       << "Processor.prototype.process = function(input, output) {" << endl;
1385   }
1386 
1387   indent_up();
1388 
1389   indent(f_service_) << js_const_type_ << "r = input.readMessageBegin();" << endl << indent()
1390              << "if (this['process_' + r.fname]) {" << endl << indent()
1391              << "  return this['process_' + r.fname].call(this, r.rseqid, input, output);" << endl
1392              << indent() << "} else {" << endl << indent() << "  input.skip(Thrift.Type.STRUCT);"
1393              << endl << indent() << "  input.readMessageEnd();" << endl << indent()
1394              << "  " << js_const_type_ << "x = new "
1395                 "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN_METHOD, "
1396                 "'Unknown function ' + r.fname);" << endl << indent()
1397              << "  output.writeMessageBegin(r.fname, Thrift.MessageType.EXCEPTION, r.rseqid);"
1398              << endl << indent() << "  x.write(output);" << endl << indent()
1399              << "  output.writeMessageEnd();" << endl << indent() << "  output.flush();" << endl
1400              << indent() << "}" << endl;
1401 
1402   indent_down();
1403   if (gen_es6_) {
1404     indent(f_service_) << "}" << endl;
1405   } else {
1406     indent(f_service_) << "};" << endl;
1407   }
1408 
1409   // Generate the process subfunctions
1410   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
1411     generate_process_function(tservice, *f_iter);
1412   }
1413 
1414   // Close off the processor class definition
1415   if (gen_es6_) {
1416     indent_down();
1417     indent(f_service_) << "};" << endl;
1418   }
1419   if (gen_node_ && gen_ts_) {
1420     f_service_ts_ << "}" << endl;
1421   }
1422 }
1423 
1424 /**
1425  * Generates a process function definition.
1426  *
1427  * @param tfunction The function to write a dispatcher for
1428  */
generate_process_function(t_service * tservice,t_function * tfunction)1429 void t_js_generator::generate_process_function(t_service* tservice, t_function* tfunction) {
1430   if (gen_es6_) {
1431     indent(f_service_) << "process_" + tfunction->get_name() + " (seqid, input, output) {" << endl;
1432   } else {
1433     indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
1434                       << "Processor.prototype.process_" + tfunction->get_name()
1435                           + " = function(seqid, input, output) {" << endl;
1436   }
1437   if (gen_ts_) {
1438     indent_up();
1439     f_service_ts_ << ts_indent() << "process_" << tfunction->get_name() << "(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;" << endl;
1440     indent_down();
1441   }
1442 
1443   indent_up();
1444 
1445   string argsname = js_namespace(program_) + service_name_ + "_" + tfunction->get_name() + "_args";
1446   string resultname = js_namespace(program_) + service_name_ + "_" + tfunction->get_name()
1447                       + "_result";
1448 
1449   indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << endl << indent()
1450              << "args.read(input);" << endl << indent() << "input.readMessageEnd();" << endl;
1451 
1452   // Generate the function call
1453   t_struct* arg_struct = tfunction->get_arglist();
1454   const std::vector<t_field*>& fields = arg_struct->get_members();
1455   vector<t_field*>::const_iterator f_iter;
1456 
1457   // Shortcut out here for oneway functions
1458   if (tfunction->is_oneway()) {
1459     indent(f_service_) << "this._handler." << tfunction->get_name() << "(";
1460 
1461     bool first = true;
1462     for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1463       if (first) {
1464         first = false;
1465       } else {
1466         f_service_ << ", ";
1467       }
1468       f_service_ << "args." << (*f_iter)->get_name();
1469     }
1470 
1471     f_service_ << ");" << endl;
1472     indent_down();
1473 
1474     if (gen_es6_) {
1475       indent(f_service_) << "}" << endl;
1476     } else {
1477       indent(f_service_) << "};" << endl;
1478     }
1479     return;
1480   }
1481 
1482   // Promise style invocation
1483   indent(f_service_) << "if (this._handler." << tfunction->get_name()
1484              << ".length === " << fields.size() << ") {" << endl;
1485   indent_up();
1486 
1487   if (gen_es6_) {
1488     indent(f_service_) << "Promise.resolve(this._handler." << tfunction->get_name() << ".bind(this._handler)(" << endl;
1489   } else {
1490     string maybeComma = (fields.size() > 0 ? "," : "");
1491     indent(f_service_) << "Q.fcall(this._handler." << tfunction->get_name() << ".bind(this._handler)"
1492                        << maybeComma << endl;
1493   }
1494 
1495   indent_up();
1496   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1497     string maybeComma = (f_iter != fields.end() - 1 ? "," : "");
1498     indent(f_service_) << "args." << (*f_iter)->get_name() << maybeComma << endl;
1499   }
1500   indent_down();
1501 
1502   if (gen_es6_) {
1503     indent(f_service_) << ")).then(result => {" << endl;
1504   } else {
1505     indent(f_service_) << ").then(function(result) {" << endl;
1506   }
1507 
1508   indent_up();
1509   f_service_ << indent() << js_const_type_ << "result_obj = new " << resultname << "({success: result});" << endl
1510              << indent() << "output.writeMessageBegin(\"" << tfunction->get_name()
1511              << "\", Thrift.MessageType.REPLY, seqid);" << endl << indent()
1512              << "result_obj.write(output);" << endl << indent() << "output.writeMessageEnd();" << endl
1513              << indent() << "output.flush();" << endl;
1514   indent_down();
1515 
1516   if (gen_es6_) {
1517     indent(f_service_) << "}).catch(err => {" << endl;
1518   } else {
1519     indent(f_service_) << "}).catch(function (err) {" << endl;
1520   }
1521   indent_up();
1522   indent(f_service_) << js_let_type_ << "result;" << endl;
1523 
1524   bool has_exception = false;
1525   t_struct* exceptions = tfunction->get_xceptions();
1526   if (exceptions) {
1527     const vector<t_field*>& members = exceptions->get_members();
1528     for (auto member : members) {
1529       t_type* t = get_true_type(member->get_type());
1530       if (t->is_xception()) {
1531         if (!has_exception) {
1532           has_exception = true;
1533           indent(f_service_) << "if (err instanceof " << js_type_namespace(t->get_program())
1534                              << t->get_name();
1535         } else {
1536           f_service_ << " || err instanceof " << js_type_namespace(t->get_program())
1537                      << t->get_name();
1538         }
1539       }
1540     }
1541   }
1542 
1543   if (has_exception) {
1544     f_service_ << ") {" << endl;
1545     indent_up();
1546     f_service_ << indent() << "result = new " << resultname << "(err);" << endl << indent()
1547                << "output.writeMessageBegin(\"" << tfunction->get_name()
1548                << "\", Thrift.MessageType.REPLY, seqid);" << endl;
1549 
1550     indent_down();
1551     indent(f_service_) << "} else {" << endl;
1552     indent_up();
1553   }
1554 
1555   f_service_ << indent() << "result = new "
1556                             "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN,"
1557                             " err.message);" << endl << indent() << "output.writeMessageBegin(\""
1558              << tfunction->get_name() << "\", Thrift.MessageType.EXCEPTION, seqid);" << endl;
1559 
1560   if (has_exception) {
1561     indent_down();
1562     indent(f_service_) << "}" << endl;
1563   }
1564 
1565   f_service_ << indent() << "result.write(output);" << endl << indent()
1566              << "output.writeMessageEnd();" << endl << indent() << "output.flush();" << endl;
1567   indent_down();
1568   indent(f_service_) << "});" << endl;
1569   indent_down();
1570   // End promise style invocation
1571 
1572   // Callback style invocation
1573   indent(f_service_) << "} else {" << endl;
1574   indent_up();
1575   indent(f_service_) << "this._handler." << tfunction->get_name() << "(";
1576 
1577   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1578     f_service_ << "args." << (*f_iter)->get_name() << ", ";
1579   }
1580 
1581   if (gen_es6_) {
1582     f_service_ << "(err, result) => {" << endl;
1583   } else {
1584     f_service_ << "function (err, result) {" << endl;
1585   }
1586   indent_up();
1587   indent(f_service_) << js_let_type_ << "result_obj;" << endl;
1588 
1589   indent(f_service_) << "if ((err === null || typeof err === 'undefined')";
1590   if (has_exception) {
1591     const vector<t_field*>& members = exceptions->get_members();
1592     for (auto member : members) {
1593       t_type* t = get_true_type(member->get_type());
1594       if (t->is_xception()) {
1595         f_service_ << " || err instanceof " << js_type_namespace(t->get_program()) << t->get_name();
1596       }
1597     }
1598   }
1599   f_service_ << ") {" << endl;
1600   indent_up();
1601   f_service_ << indent() << "result_obj = new " << resultname
1602              << "((err !== null || typeof err === 'undefined') ? err : {success: result});" << endl << indent()
1603              << "output.writeMessageBegin(\"" << tfunction->get_name()
1604              << "\", Thrift.MessageType.REPLY, seqid);" << endl;
1605   indent_down();
1606   indent(f_service_) << "} else {" << endl;
1607   indent_up();
1608   f_service_ << indent() << "result_obj = new "
1609                             "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN,"
1610                             " err.message);" << endl << indent() << "output.writeMessageBegin(\""
1611              << tfunction->get_name() << "\", Thrift.MessageType.EXCEPTION, seqid);" << endl;
1612   indent_down();
1613   f_service_ << indent() << "}" << endl << indent() << "result_obj.write(output);" << endl << indent()
1614              << "output.writeMessageEnd();" << endl << indent() << "output.flush();" << endl;
1615 
1616   indent_down();
1617   indent(f_service_) << "});" << endl;
1618   indent_down();
1619   indent(f_service_) << "}" << endl;
1620   // End callback style invocation
1621 
1622   indent_down();
1623 
1624   if (gen_es6_) {
1625     indent(f_service_) << "}" << endl;
1626   } else {
1627     indent(f_service_) << "};" << endl;
1628   }
1629 }
1630 
1631 /**
1632  * Generates helper functions for a service.
1633  *
1634  * @param tservice The service to generate a header definition for
1635  */
generate_service_helpers(t_service * tservice)1636 void t_js_generator::generate_service_helpers(t_service* tservice) {
1637   // Do not generate TS definitions for helper functions
1638   bool gen_ts_tmp = gen_ts_;
1639   gen_ts_ = false;
1640 
1641   vector<t_function*> functions = tservice->get_functions();
1642   vector<t_function*>::iterator f_iter;
1643 
1644   f_service_ << "//HELPER FUNCTIONS AND STRUCTURES" << endl << endl;
1645 
1646   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
1647     t_struct* ts = (*f_iter)->get_arglist();
1648     string name = ts->get_name();
1649     ts->set_name(service_name_ + "_" + name);
1650     generate_js_struct_definition(f_service_, ts, false, false);
1651     generate_js_function_helpers(*f_iter);
1652     ts->set_name(name);
1653   }
1654 
1655   gen_ts_ = gen_ts_tmp;
1656 }
1657 
1658 /**
1659  * Generates a struct and helpers for a function.
1660  *
1661  * @param tfunction The function
1662  */
generate_js_function_helpers(t_function * tfunction)1663 void t_js_generator::generate_js_function_helpers(t_function* tfunction) {
1664   t_struct result(program_, service_name_ + "_" + tfunction->get_name() + "_result");
1665   t_field success(tfunction->get_returntype(), "success", 0);
1666   if (!tfunction->get_returntype()->is_void()) {
1667     result.append(&success);
1668   }
1669 
1670   t_struct* xs = tfunction->get_xceptions();
1671   const vector<t_field*>& fields = xs->get_members();
1672   vector<t_field*>::const_iterator f_iter;
1673   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1674     result.append(*f_iter);
1675   }
1676 
1677   generate_js_struct_definition(f_service_, &result, false, false);
1678 }
1679 
1680 /**
1681  * Generates a service interface definition.
1682  *
1683  * @param tservice The service to generate a header definition for
1684  */
generate_service_interface(t_service * tservice)1685 void t_js_generator::generate_service_interface(t_service* tservice) {
1686   (void)tservice;
1687 }
1688 
1689 /**
1690  * Generates a REST interface
1691  */
generate_service_rest(t_service * tservice)1692 void t_js_generator::generate_service_rest(t_service* tservice) {
1693   (void)tservice;
1694 }
1695 
1696 /**
1697  * Generates a service client definition.
1698  *
1699  * @param tservice The service to generate a server for.
1700  */
generate_service_client(t_service * tservice)1701 void t_js_generator::generate_service_client(t_service* tservice) {
1702 
1703   bool is_subclass_service = tservice->get_extends() != nullptr;
1704 
1705   if (gen_node_) {
1706     string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_;
1707     f_service_ << prefix << service_name_ << "Client = " << "exports.Client";
1708     if (gen_ts_) {
1709       f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class "
1710                     << "Client ";
1711       if (tservice->get_extends() != nullptr) {
1712         f_service_ts_ << "extends " << tservice->get_extends()->get_name() << ".Client ";
1713       }
1714       f_service_ts_ << "{" << endl;
1715     }
1716   } else {
1717     f_service_ << js_namespace(tservice->get_program()) << service_name_
1718                << "Client";
1719     if (gen_ts_) {
1720       f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class "
1721                     << service_name_ << "Client ";
1722       if (is_subclass_service) {
1723         f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Client ";
1724       }
1725       f_service_ts_ << "{" << endl;
1726     }
1727   }
1728 
1729   // ES6 Constructor
1730   if (gen_es6_) {
1731 
1732     if (is_subclass_service) {
1733       f_service_ << " = class " << service_name_ << "Client extends " << js_namespace(tservice->get_extends()->get_program())
1734                        << tservice->get_extends()->get_name() << "Client {" << endl;
1735     } else {
1736       f_service_ << " = class " << service_name_ << "Client {" << endl;
1737     }
1738     indent_up();
1739     if (gen_node_) {
1740       indent(f_service_) << "constructor(output, pClass) {" << endl;
1741     } else {
1742       indent(f_service_) << "constructor(input, output) {" << endl;
1743     }
1744   } else {
1745     if (gen_node_) {
1746       f_service_ << " = function(output, pClass) {" << endl;
1747     } else {
1748       f_service_ << " = function(input, output) {" << endl;
1749     }
1750   }
1751 
1752   indent_up();
1753 
1754   if (gen_node_) {
1755     if (gen_es6_ && is_subclass_service) {
1756       indent(f_service_) << "super(output, pClass);" << endl;
1757     }
1758     indent(f_service_) << "this.output = output;" << endl;
1759     indent(f_service_) << "this.pClass = pClass;" << endl;
1760     indent(f_service_) << "this._seqid = 0;" << endl;
1761     indent(f_service_) << "this._reqs = {};" << endl;
1762     if (gen_ts_) {
1763       if(!is_subclass_service) {
1764         f_service_ts_ << ts_indent() << "private output: thrift.TTransport;" << endl
1765                       << ts_indent() << "private pClass: thrift.TProtocol;" << endl
1766                       << ts_indent() << "private _seqid: number;" << endl
1767                       << endl;
1768       }
1769 
1770       f_service_ts_ << ts_indent() << "constructor(output: thrift.TTransport, pClass: { new(trans: thrift.TTransport): thrift.TProtocol });"
1771                     << endl;
1772     }
1773   } else {
1774     indent(f_service_) << "this.input = input;" << endl;
1775     indent(f_service_) << "this.output = (!output) ? input : output;" << endl;
1776     indent(f_service_) << "this.seqid = 0;" << endl;
1777     if (gen_ts_) {
1778       f_service_ts_ << ts_indent() << "input: Thrift.TJSONProtocol;" << endl << ts_indent()
1779                     << "output: Thrift.TJSONProtocol;" << endl << ts_indent() << "seqid: number;"
1780                     << endl << endl << ts_indent()
1781                     << "constructor(input: Thrift.TJSONProtocol, output?: Thrift.TJSONProtocol);"
1782                     << endl;
1783     }
1784   }
1785 
1786   indent_down();
1787 
1788   if (gen_es6_) {
1789     indent(f_service_) << "}" << endl;
1790   } else {
1791     indent(f_service_) << "};" << endl;
1792     if (is_subclass_service) {
1793       indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program())
1794                         << service_name_ << "Client, "
1795                         << js_namespace(tservice->get_extends()->get_program())
1796                         << tservice->get_extends()->get_name() << "Client);" << endl;
1797     } else {
1798       // init prototype
1799       indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
1800                         << "Client.prototype = {};" << endl;
1801     }
1802   }
1803 
1804   // utils for multiplexed services
1805   if (gen_node_) {
1806     if (gen_es6_) {
1807       indent(f_service_) << "seqid () { return this._seqid; }" << endl;
1808       indent(f_service_) << "new_seqid () { return this._seqid += 1; }" << endl;
1809     } else {
1810       indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
1811                         << "Client.prototype.seqid = function() { return this._seqid; };" << endl
1812                         << js_namespace(tservice->get_program()) << service_name_
1813                         << "Client.prototype.new_seqid = function() { return this._seqid += 1; };"
1814                         << endl;
1815     }
1816   }
1817 
1818   // Generate client method implementations
1819   vector<t_function*> functions = tservice->get_functions();
1820   vector<t_function*>::const_iterator f_iter;
1821   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
1822     t_struct* arg_struct = (*f_iter)->get_arglist();
1823     const vector<t_field*>& fields = arg_struct->get_members();
1824     vector<t_field*>::const_iterator fld_iter;
1825     string funname = (*f_iter)->get_name();
1826     string arglist = argument_list(arg_struct);
1827 
1828     // Open function
1829     f_service_ << endl;
1830     if (gen_es6_) {
1831       indent(f_service_) << funname << " (" << arglist << ") {" << endl;
1832     } else {
1833       indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype."
1834                 << function_signature(*f_iter, "", !gen_es6_) << " {" << endl;
1835     }
1836 
1837     indent_up();
1838 
1839     if (gen_ts_) {
1840       // function definition without callback
1841       f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, false) << endl;
1842       if (!gen_es6_) {
1843         // overload with callback
1844         f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << endl;
1845       } else {
1846         // overload with callback
1847         f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << endl;
1848       }
1849     }
1850 
1851     if (gen_es6_ && gen_node_) {
1852       indent(f_service_) << "this._seqid = this.new_seqid();" << endl;
1853       indent(f_service_) << js_const_type_ << "self = this;" << endl << indent()
1854                  << "return new Promise((resolve, reject) => {" << endl;
1855       indent_up();
1856       indent(f_service_) << "self._reqs[self.seqid()] = (error, result) => {" << endl;
1857       indent_up();
1858       indent(f_service_) << "return error ? reject(error) : resolve(result);" << endl;
1859       indent_down();
1860       indent(f_service_) << "};" << endl;
1861       indent(f_service_) << "self.send_" << funname << "(" << arglist << ");" << endl;
1862       indent_down();
1863       indent(f_service_) << "});" << endl;
1864     } else if (gen_node_) { // Node.js output      ./gen-nodejs
1865       f_service_ << indent() << "this._seqid = this.new_seqid();" << endl << indent()
1866                  << "if (callback === undefined) {" << endl;
1867       indent_up();
1868       f_service_ << indent() << js_const_type_ << "_defer = Q.defer();" << endl << indent()
1869                  << "this._reqs[this.seqid()] = function(error, result) {" << endl;
1870       indent_up();
1871       indent(f_service_) << "if (error) {" << endl;
1872       indent_up();
1873       indent(f_service_) << "_defer.reject(error);" << endl;
1874       indent_down();
1875       indent(f_service_) << "} else {" << endl;
1876       indent_up();
1877       indent(f_service_) << "_defer.resolve(result);" << endl;
1878       indent_down();
1879       indent(f_service_) << "}" << endl;
1880       indent_down();
1881       indent(f_service_) << "};" << endl;
1882       f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << endl
1883                  << indent() << "return _defer.promise;" << endl;
1884       indent_down();
1885       indent(f_service_) << "} else {" << endl;
1886       indent_up();
1887       f_service_ << indent() << "this._reqs[this.seqid()] = callback;" << endl << indent()
1888                  << "this.send_" << funname << "(" << arglist << ");" << endl;
1889       indent_down();
1890       indent(f_service_) << "}" << endl;
1891     } else if (gen_es6_) {
1892       f_service_ << indent() << js_const_type_ << "self = this;" << endl << indent()
1893                  << "return new Promise((resolve, reject) => {" << endl;
1894       indent_up();
1895       f_service_ << indent() << "self.send_" << funname << "(" << arglist
1896                  << (arglist.empty() ? "" : ", ") << "(error, result) => {" << endl;
1897       indent_up();
1898       indent(f_service_) << "return error ? reject(error) : resolve(result);" << endl;
1899       indent_down();
1900       f_service_ << indent() << "});" << endl;
1901       indent_down();
1902       f_service_ << indent() << "});" << endl;
1903 
1904     } else if (gen_jquery_) { // jQuery output       ./gen-js
1905       f_service_ << indent() << "if (callback === undefined) {" << endl;
1906       indent_up();
1907       f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << endl;
1908       if (!(*f_iter)->is_oneway()) {
1909         f_service_ << indent();
1910         if (!(*f_iter)->get_returntype()->is_void()) {
1911           f_service_ << "return ";
1912         }
1913         f_service_ << "this.recv_" << funname << "();" << endl;
1914       }
1915       indent_down();
1916       f_service_ << indent() << "} else {" << endl;
1917       indent_up();
1918       f_service_ << indent() << js_const_type_ << "postData = this.send_" << funname << "(" << arglist
1919                  << (arglist.empty() ? "" : ", ") << "true);" << endl;
1920       f_service_ << indent() << "return this.output.getTransport()" << endl;
1921       indent_up();
1922       f_service_ << indent() << ".jqRequest(this, postData, arguments, this.recv_" << funname
1923                  << ");" << endl;
1924       indent_down();
1925       indent_down();
1926       f_service_ << indent() << "}" << endl;
1927     } else { // Standard JavaScript ./gen-js
1928       f_service_ << indent() << "this.send_" << funname << "(" << arglist
1929                  << (arglist.empty() ? "" : ", ") << "callback); " << endl;
1930       if (!(*f_iter)->is_oneway()) {
1931         f_service_ << indent() << "if (!callback) {" << endl;
1932         f_service_ << indent();
1933         if (!(*f_iter)->get_returntype()->is_void()) {
1934           f_service_ << "  return ";
1935         }
1936         f_service_ << "this.recv_" << funname << "();" << endl;
1937         f_service_ << indent() << "}" << endl;
1938       }
1939     }
1940 
1941     indent_down();
1942 
1943     if (gen_es6_) {
1944       indent(f_service_) << "}" << endl << endl;
1945     } else {
1946       indent(f_service_) << "};" << endl << endl;
1947     }
1948 
1949     // Send function
1950     if (gen_es6_) {
1951       if (gen_node_) {
1952         indent(f_service_) << "send_" << funname << " (" << arglist << ") {" << endl;
1953       } else {
1954         // ES6 js still uses callbacks here. Should refactor this to promise style later..
1955         indent(f_service_) << "send_" << funname << " (" << argument_list(arg_struct, true) << ") {" << endl;
1956       }
1957     } else {
1958       indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype.send_"
1959                 << function_signature(*f_iter, "", !gen_node_) << " {" << endl;
1960     }
1961 
1962     indent_up();
1963 
1964     std::string outputVar;
1965     if (gen_node_) {
1966       f_service_ << indent() << js_const_type_ << "output = new this.pClass(this.output);" << endl;
1967       outputVar = "output";
1968     } else {
1969       outputVar = "this.output";
1970     }
1971 
1972     std::string argsname = js_namespace(program_) + service_name_ + "_" + (*f_iter)->get_name()
1973                            + "_args";
1974 
1975     std::string messageType = (*f_iter)->is_oneway() ? "Thrift.MessageType.ONEWAY"
1976                                                      : "Thrift.MessageType.CALL";
1977     // Build args
1978     if (fields.size() > 0){
1979       // It is possible that a method argument is named "params", we need to ensure the locally
1980       // generated identifier "params" is uniquely named
1981       std::string params_identifier = this->next_identifier_name(fields, "params");
1982       f_service_ << indent() << js_const_type_ << params_identifier << " = {" << endl;
1983       indent_up();
1984       for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
1985         indent(f_service_) << (*fld_iter)->get_name() << ": " << (*fld_iter)->get_name();
1986         if (fld_iter != fields.end()-1) {
1987           f_service_ << "," << endl;
1988         } else {
1989           f_service_ << endl;
1990         }
1991       }
1992       indent_down();
1993       indent(f_service_) << "};" << endl;
1994 
1995       // NOTE: "args" is a reserved keyword, so no need to generate a unique identifier
1996       indent(f_service_) << js_const_type_ << "args = new " << argsname << "(" << params_identifier << ");" << endl;
1997     } else {
1998       indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << endl;
1999     }
2000 
2001 
2002     // Serialize the request header within try/catch
2003     indent(f_service_) << "try {" << endl;
2004     indent_up();
2005 
2006     if (gen_node_) {
2007       f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name()
2008                  << "', " << messageType << ", this.seqid());" << endl;
2009     } else {
2010       f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name()
2011                  << "', " << messageType << ", this.seqid);" << endl;
2012     }
2013 
2014 
2015     // Write to the stream
2016     f_service_ << indent() << "args.write(" << outputVar << ");" << endl << indent() << outputVar
2017                << ".writeMessageEnd();" << endl;
2018 
2019     if (gen_node_) {
2020       if((*f_iter)->is_oneway()) {
2021         f_service_ << indent() << "this.output.flush();" << endl;
2022         f_service_ << indent() << js_const_type_ << "callback = this._reqs[this.seqid()] || function() {};" << endl;
2023         f_service_ << indent() << "delete this._reqs[this.seqid()];" << endl;
2024         f_service_ << indent() << "callback(null);" << endl;
2025       } else {
2026         f_service_ << indent() << "return this.output.flush();" << endl;
2027       }
2028     } else {
2029       if (gen_jquery_) {
2030         f_service_ << indent() << "return this.output.getTransport().flush(callback);" << endl;
2031       } else if (gen_es6_) {
2032         f_service_ << indent() << js_const_type_ << "self = this;" << endl;
2033         if((*f_iter)->is_oneway()) {
2034           f_service_ << indent() << "this.output.getTransport().flush(true, null);" << endl;
2035           f_service_ << indent() << "callback();" << endl;
2036         } else {
2037           f_service_ << indent() << "this.output.getTransport().flush(true, () => {" << endl;
2038           indent_up();
2039           f_service_ << indent() << js_let_type_ << "error = null, result = null;" << endl;
2040           f_service_ << indent() << "try {" << endl;
2041           f_service_ << indent() << "  result = self.recv_" << funname << "();" << endl;
2042           f_service_ << indent() << "} catch (e) {" << endl;
2043           f_service_ << indent() << "  error = e;" << endl;
2044           f_service_ << indent() << "}" << endl;
2045           f_service_ << indent() << "callback(error, result);" << endl;
2046           indent_down();
2047           f_service_ << indent() << "});" << endl;
2048         }
2049       } else {
2050         f_service_ << indent() << "if (callback) {" << endl;
2051         indent_up();
2052         if((*f_iter)->is_oneway()) {
2053           f_service_ << indent() << "this.output.getTransport().flush(true, null);" << endl;
2054           f_service_ << indent() << "callback();" << endl;
2055         } else {
2056           f_service_ << indent() << js_const_type_ << "self = this;" << endl;
2057           f_service_ << indent() << "this.output.getTransport().flush(true, function() {" << endl;
2058           indent_up();
2059           f_service_ << indent() << js_let_type_ << "result = null;" << endl;
2060           f_service_ << indent() << "try {" << endl;
2061           f_service_ << indent() << "  result = self.recv_" << funname << "();" << endl;
2062           f_service_ << indent() << "} catch (e) {" << endl;
2063           f_service_ << indent() << "  result = e;" << endl;
2064           f_service_ << indent() << "}" << endl;
2065           f_service_ << indent() << "callback(result);" << endl;
2066           indent_down();
2067           f_service_ << indent() << "});" << endl;
2068         }
2069         indent_down();
2070         f_service_ << indent() << "} else {" << endl;
2071         f_service_ << indent() << "  return this.output.getTransport().flush();" << endl;
2072         f_service_ << indent() << "}" << endl;
2073       }
2074     }
2075 
2076     indent_down();
2077     f_service_ << indent() << "}" << endl;
2078 
2079     // Reset the transport and delete registered callback if there was a serialization error
2080     f_service_ << indent() << "catch (e) {" << endl;
2081     indent_up();
2082     if (gen_node_) {
2083       f_service_ << indent() << "delete this._reqs[this.seqid()];" << endl;
2084       f_service_ << indent() << "if (typeof " << outputVar << ".reset === 'function') {" << endl;
2085       f_service_ << indent() << "  " << outputVar << ".reset();" << endl;
2086       f_service_ << indent() << "}" << endl;
2087     } else {
2088       f_service_ << indent() << "if (typeof " << outputVar << ".getTransport().reset === 'function') {" << endl;
2089       f_service_ << indent() << "  " << outputVar << ".getTransport().reset();" << endl;
2090       f_service_ << indent() << "}" << endl;
2091     }
2092     f_service_ << indent() << "throw e;" << endl;
2093     indent_down();
2094     f_service_ << indent() << "}" << endl;
2095 
2096     indent_down();
2097 
2098     // Close send function
2099     if (gen_es6_) {
2100       indent(f_service_) << "}" << endl;
2101     } else {
2102       indent(f_service_) << "};" << endl;
2103     }
2104 
2105     // Receive function
2106     if (!(*f_iter)->is_oneway()) {
2107       std::string resultname = js_namespace(tservice->get_program()) + service_name_ + "_"
2108                                + (*f_iter)->get_name() + "_result";
2109 
2110       f_service_ << endl;
2111       // Open receive function
2112       if (gen_node_) {
2113         if (gen_es6_) {
2114           indent(f_service_) << "recv_" << (*f_iter)->get_name() << " (input, mtype, rseqid) {" << endl;
2115         } else {
2116           indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
2117                       << "Client.prototype.recv_" << (*f_iter)->get_name()
2118                       << " = function(input,mtype,rseqid) {" << endl;
2119         }
2120       } else {
2121         if (gen_es6_) {
2122           indent(f_service_) << "recv_" << (*f_iter)->get_name() << " () {" << endl;
2123         } else {
2124           t_struct noargs(program_);
2125 
2126           t_function recv_function((*f_iter)->get_returntype(),
2127                                   string("recv_") + (*f_iter)->get_name(),
2128                                   &noargs);
2129           indent(f_service_) << js_namespace(tservice->get_program()) << service_name_
2130                     << "Client.prototype." << function_signature(&recv_function) << " {" << endl;
2131         }
2132       }
2133 
2134       indent_up();
2135 
2136       std::string inputVar;
2137       if (gen_node_) {
2138         inputVar = "input";
2139       } else {
2140         inputVar = "this.input";
2141       }
2142 
2143       if (gen_node_) {
2144         f_service_ << indent() << js_const_type_ << "callback = this._reqs[rseqid] || function() {};" << endl
2145                    << indent() << "delete this._reqs[rseqid];" << endl;
2146       } else {
2147         f_service_ << indent() << js_const_type_ << "ret = this.input.readMessageBegin();" << endl
2148                    << indent() << js_const_type_ << "mtype = ret.mtype;" << endl;
2149       }
2150 
2151       f_service_ << indent() << "if (mtype == Thrift.MessageType.EXCEPTION) {" << endl;
2152 
2153       indent_up();
2154       f_service_ << indent() << js_const_type_ << "x = new Thrift.TApplicationException();" << endl
2155                  << indent() << "x.read(" << inputVar << ");" << endl
2156                  << indent() << inputVar << ".readMessageEnd();" << endl
2157                  << indent() << render_recv_throw("x") << endl;
2158       scope_down(f_service_);
2159 
2160       f_service_ << indent() << js_const_type_ << "result = new " << resultname << "();" << endl << indent()
2161                  << "result.read(" << inputVar << ");" << endl;
2162 
2163       f_service_ << indent() << inputVar << ".readMessageEnd();" << endl << endl;
2164 
2165       t_struct* xs = (*f_iter)->get_xceptions();
2166       const std::vector<t_field*>& xceptions = xs->get_members();
2167       vector<t_field*>::const_iterator x_iter;
2168       for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
2169         f_service_ << indent() << "if (null !== result." << (*x_iter)->get_name() << ") {" << endl
2170                    << indent() << "  " << render_recv_throw("result." + (*x_iter)->get_name())
2171                    << endl << indent() << "}" << endl;
2172       }
2173 
2174       // Careful, only return result if not a void function
2175       if (!(*f_iter)->get_returntype()->is_void()) {
2176         f_service_ << indent() << "if (null !== result.success) {" << endl << indent() << "  "
2177                    << render_recv_return("result.success") << endl << indent() << "}" << endl;
2178         f_service_ << indent()
2179                    << render_recv_throw("'" + (*f_iter)->get_name() + " failed: unknown result'")
2180                    << endl;
2181       } else {
2182         if (gen_node_) {
2183           indent(f_service_) << "callback(null);" << endl;
2184         } else {
2185           indent(f_service_) << "return;" << endl;
2186         }
2187       }
2188 
2189       // Close receive function
2190       indent_down();
2191       if (gen_es6_) {
2192         indent(f_service_) << "}" << endl;
2193       } else {
2194         indent(f_service_) << "};" << endl;
2195       }
2196     }
2197   }
2198 
2199   // Finish class definitions
2200   if (gen_ts_) {
2201     f_service_ts_ << ts_indent() << "}" << endl;
2202   }
2203   if (gen_es6_) {
2204     indent_down();
2205     f_service_ << "};" << endl;
2206   }
2207 }
2208 
render_recv_throw(std::string var)2209 std::string t_js_generator::render_recv_throw(std::string var) {
2210   if (gen_node_) {
2211     return "return callback(" + var + ");";
2212   } else {
2213     return "throw " + var + ";";
2214   }
2215 }
2216 
render_recv_return(std::string var)2217 std::string t_js_generator::render_recv_return(std::string var) {
2218   if (gen_node_) {
2219     return "return callback(null, " + var + ");";
2220   } else {
2221     return "return " + var + ";";
2222   }
2223 }
2224 
2225 /**
2226  * Deserializes a field of any type.
2227  */
generate_deserialize_field(ostream & out,t_field * tfield,string prefix,bool inclass)2228 void t_js_generator::generate_deserialize_field(ostream& out,
2229                                                 t_field* tfield,
2230                                                 string prefix,
2231                                                 bool inclass) {
2232   (void)inclass;
2233   t_type* type = get_true_type(tfield->get_type());
2234 
2235   if (type->is_void()) {
2236     throw std::runtime_error("CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name());
2237   }
2238 
2239   string name = prefix + tfield->get_name();
2240 
2241   if (type->is_struct() || type->is_xception()) {
2242     generate_deserialize_struct(out, (t_struct*)type, name);
2243   } else if (type->is_container()) {
2244     generate_deserialize_container(out, type, name);
2245   } else if (type->is_base_type() || type->is_enum()) {
2246     indent(out) << name << " = input.";
2247 
2248     if (type->is_base_type()) {
2249       t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
2250       switch (tbase) {
2251       case t_base_type::TYPE_VOID:
2252         throw std::runtime_error("compiler error: cannot serialize void field in a struct: " + name);
2253         break;
2254       case t_base_type::TYPE_STRING:
2255         out << (type->is_binary() ? "readBinary()" : "readString()");
2256         break;
2257       case t_base_type::TYPE_BOOL:
2258         out << "readBool()";
2259         break;
2260       case t_base_type::TYPE_I8:
2261         out << "readByte()";
2262         break;
2263       case t_base_type::TYPE_I16:
2264         out << "readI16()";
2265         break;
2266       case t_base_type::TYPE_I32:
2267         out << "readI32()";
2268         break;
2269       case t_base_type::TYPE_I64:
2270         out << "readI64()";
2271         break;
2272       case t_base_type::TYPE_DOUBLE:
2273         out << "readDouble()";
2274         break;
2275       default:
2276         throw std::runtime_error("compiler error: no JS name for base type " + t_base_type::t_base_name(tbase));
2277       }
2278     } else if (type->is_enum()) {
2279       out << "readI32()";
2280     }
2281 
2282     if (!gen_node_) {
2283       out << ".value";
2284     }
2285 
2286     out << ";" << endl;
2287   } else {
2288     printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n",
2289            tfield->get_name().c_str(),
2290            type->get_name().c_str());
2291   }
2292 }
2293 
2294 /**
2295  * Generates an unserializer for a variable. This makes two key assumptions,
2296  * first that there is a const char* variable named data that points to the
2297  * buffer for deserialization, and that there is a variable protocol which
2298  * is a reference to a TProtocol serialization object.
2299  */
generate_deserialize_struct(ostream & out,t_struct * tstruct,string prefix)2300 void t_js_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) {
2301   out << indent() << prefix << " = new " << js_type_namespace(tstruct->get_program())
2302       << tstruct->get_name() << "();" << endl << indent() << prefix << ".read(input);" << endl;
2303 }
2304 
generate_deserialize_container(ostream & out,t_type * ttype,string prefix)2305 void t_js_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) {
2306   string size = tmp("_size");
2307   string rtmp3 = tmp("_rtmp3");
2308 
2309   t_field fsize(g_type_i32, size);
2310 
2311   // Declare variables, read header
2312   if (ttype->is_map()) {
2313     out << indent() << prefix << " = {};" << endl;
2314 
2315     out << indent() << js_const_type_ << rtmp3 << " = input.readMapBegin();" << endl;
2316     out << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl;
2317 
2318   } else if (ttype->is_set()) {
2319 
2320     out << indent() << prefix << " = [];" << endl
2321         << indent() << js_const_type_ << rtmp3 << " = input.readSetBegin();" << endl
2322         << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl;
2323 
2324   } else if (ttype->is_list()) {
2325 
2326     out << indent() << prefix << " = [];" << endl
2327         << indent() << js_const_type_ << rtmp3 << " = input.readListBegin();" << endl
2328         << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl;
2329   }
2330 
2331   // For loop iterates over elements
2332   string i = tmp("_i");
2333   indent(out) << "for (" << js_let_type_ << i << " = 0; " << i << " < " << size << "; ++" << i << ") {" << endl;
2334 
2335   indent_up();
2336 
2337   if (ttype->is_map()) {
2338     if (!gen_node_) {
2339       out << indent() << "if (" << i << " > 0 ) {" << endl << indent()
2340           << "  if (input.rstack.length > input.rpos[input.rpos.length -1] + 1) {" << endl
2341           << indent() << "    input.rstack.pop();" << endl << indent() << "  }" << endl << indent()
2342           << "}" << endl;
2343     }
2344 
2345     generate_deserialize_map_element(out, (t_map*)ttype, prefix);
2346   } else if (ttype->is_set()) {
2347     generate_deserialize_set_element(out, (t_set*)ttype, prefix);
2348   } else if (ttype->is_list()) {
2349     generate_deserialize_list_element(out, (t_list*)ttype, prefix);
2350   }
2351 
2352   scope_down(out);
2353 
2354   // Read container end
2355   if (ttype->is_map()) {
2356     indent(out) << "input.readMapEnd();" << endl;
2357   } else if (ttype->is_set()) {
2358     indent(out) << "input.readSetEnd();" << endl;
2359   } else if (ttype->is_list()) {
2360     indent(out) << "input.readListEnd();" << endl;
2361   }
2362 }
2363 
2364 /**
2365  * Generates code to deserialize a map
2366  */
generate_deserialize_map_element(ostream & out,t_map * tmap,string prefix)2367 void t_js_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) {
2368   string key = tmp("key");
2369   string val = tmp("val");
2370   t_field fkey(tmap->get_key_type(), key);
2371   t_field fval(tmap->get_val_type(), val);
2372 
2373   indent(out) << declare_field(&fkey, false, false) << ";" << endl;
2374   indent(out) << declare_field(&fval, false, false) << ";" << endl;
2375 
2376   generate_deserialize_field(out, &fkey);
2377   generate_deserialize_field(out, &fval);
2378 
2379   indent(out) << prefix << "[" << key << "] = " << val << ";" << endl;
2380 }
2381 
generate_deserialize_set_element(ostream & out,t_set * tset,string prefix)2382 void t_js_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) {
2383   string elem = tmp("elem");
2384   t_field felem(tset->get_elem_type(), elem);
2385 
2386   indent(out) << js_let_type_ << elem << " = null;" << endl;
2387 
2388   generate_deserialize_field(out, &felem);
2389 
2390   indent(out) << prefix << ".push(" << elem << ");" << endl;
2391 }
2392 
generate_deserialize_list_element(ostream & out,t_list * tlist,string prefix)2393 void t_js_generator::generate_deserialize_list_element(ostream& out,
2394                                                        t_list* tlist,
2395                                                        string prefix) {
2396   string elem = tmp("elem");
2397   t_field felem(tlist->get_elem_type(), elem);
2398 
2399   indent(out) << js_let_type_ << elem << " = null;" << endl;
2400 
2401   generate_deserialize_field(out, &felem);
2402 
2403   indent(out) << prefix << ".push(" << elem << ");" << endl;
2404 }
2405 
2406 /**
2407  * Serializes a field of any type.
2408  *
2409  * @param tfield The field to serialize
2410  * @param prefix Name to prepend to field name
2411  */
generate_serialize_field(ostream & out,t_field * tfield,string prefix)2412 void t_js_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) {
2413   t_type* type = get_true_type(tfield->get_type());
2414 
2415   // Do nothing for void types
2416   if (type->is_void()) {
2417     throw std::runtime_error("CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name());
2418   }
2419 
2420   if (type->is_struct() || type->is_xception()) {
2421     generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name());
2422   } else if (type->is_container()) {
2423     generate_serialize_container(out, type, prefix + tfield->get_name());
2424   } else if (type->is_base_type() || type->is_enum()) {
2425 
2426     string name = tfield->get_name();
2427 
2428     // Hack for when prefix is defined (always a hash ref)
2429     if (!prefix.empty())
2430       name = prefix + tfield->get_name();
2431 
2432     indent(out) << "output.";
2433 
2434     if (type->is_base_type()) {
2435       t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
2436       switch (tbase) {
2437       case t_base_type::TYPE_VOID:
2438         throw std::runtime_error("compiler error: cannot serialize void field in a struct: " + name);
2439         break;
2440       case t_base_type::TYPE_STRING:
2441         out << (type->is_binary() ? "writeBinary(" : "writeString(") << name << ")";
2442         break;
2443       case t_base_type::TYPE_BOOL:
2444         out << "writeBool(" << name << ")";
2445         break;
2446       case t_base_type::TYPE_I8:
2447         out << "writeByte(" << name << ")";
2448         break;
2449       case t_base_type::TYPE_I16:
2450         out << "writeI16(" << name << ")";
2451         break;
2452       case t_base_type::TYPE_I32:
2453         out << "writeI32(" << name << ")";
2454         break;
2455       case t_base_type::TYPE_I64:
2456         out << "writeI64(" << name << ")";
2457         break;
2458       case t_base_type::TYPE_DOUBLE:
2459         out << "writeDouble(" << name << ")";
2460         break;
2461       default:
2462         throw std::runtime_error("compiler error: no JS name for base type " + t_base_type::t_base_name(tbase));
2463       }
2464     } else if (type->is_enum()) {
2465       out << "writeI32(" << name << ")";
2466     }
2467     out << ";" << endl;
2468 
2469   } else {
2470     printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n",
2471            prefix.c_str(),
2472            tfield->get_name().c_str(),
2473            type->get_name().c_str());
2474   }
2475 }
2476 
2477 /**
2478  * Serializes all the members of a struct.
2479  *
2480  * @param tstruct The struct to serialize
2481  * @param prefix  String prefix to attach to all fields
2482  */
generate_serialize_struct(ostream & out,t_struct * tstruct,string prefix)2483 void t_js_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) {
2484   (void)tstruct;
2485   indent(out) << prefix << ".write(output);" << endl;
2486 }
2487 
2488 /**
2489  * Writes out a container
2490  */
generate_serialize_container(ostream & out,t_type * ttype,string prefix)2491 void t_js_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) {
2492   if (ttype->is_map()) {
2493     indent(out) << "output.writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ", "
2494                 << type_to_enum(((t_map*)ttype)->get_val_type()) << ", "
2495                 << "Thrift.objectLength(" << prefix << "));" << endl;
2496   } else if (ttype->is_set()) {
2497     indent(out) << "output.writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", "
2498                 << prefix << ".length);" << endl;
2499 
2500   } else if (ttype->is_list()) {
2501 
2502     indent(out) << "output.writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type())
2503                 << ", " << prefix << ".length);" << endl;
2504   }
2505 
2506   if (ttype->is_map()) {
2507     string kiter = tmp("kiter");
2508     string viter = tmp("viter");
2509     indent(out) << "for (" << js_let_type_ << kiter << " in " << prefix << ") {" << endl;
2510     indent_up();
2511     indent(out) << "if (" << prefix << ".hasOwnProperty(" << kiter << ")) {" << endl;
2512     indent_up();
2513     indent(out) << js_let_type_ << viter << " = " << prefix << "[" << kiter << "];" << endl;
2514     generate_serialize_map_element(out, (t_map*)ttype, kiter, viter);
2515     scope_down(out);
2516     scope_down(out);
2517 
2518   } else if (ttype->is_set()) {
2519     string iter = tmp("iter");
2520     indent(out) << "for (" << js_let_type_ << iter << " in " << prefix << ") {" << endl;
2521     indent_up();
2522     indent(out) << "if (" << prefix << ".hasOwnProperty(" << iter << ")) {" << endl;
2523     indent_up();
2524     indent(out) << iter << " = " << prefix << "[" << iter << "];" << endl;
2525     generate_serialize_set_element(out, (t_set*)ttype, iter);
2526     scope_down(out);
2527     scope_down(out);
2528 
2529   } else if (ttype->is_list()) {
2530     string iter = tmp("iter");
2531     indent(out) << "for (" << js_let_type_ << iter << " in " << prefix << ") {" << endl;
2532     indent_up();
2533     indent(out) << "if (" << prefix << ".hasOwnProperty(" << iter << ")) {" << endl;
2534     indent_up();
2535     indent(out) << iter << " = " << prefix << "[" << iter << "];" << endl;
2536     generate_serialize_list_element(out, (t_list*)ttype, iter);
2537     scope_down(out);
2538     scope_down(out);
2539   }
2540 
2541   if (ttype->is_map()) {
2542     indent(out) << "output.writeMapEnd();" << endl;
2543   } else if (ttype->is_set()) {
2544     indent(out) << "output.writeSetEnd();" << endl;
2545   } else if (ttype->is_list()) {
2546     indent(out) << "output.writeListEnd();" << endl;
2547   }
2548 }
2549 
2550 /**
2551  * Serializes the members of a map.
2552  *
2553  */
generate_serialize_map_element(ostream & out,t_map * tmap,string kiter,string viter)2554 void t_js_generator::generate_serialize_map_element(ostream& out,
2555                                                     t_map* tmap,
2556                                                     string kiter,
2557                                                     string viter) {
2558   t_field kfield(tmap->get_key_type(), kiter);
2559   generate_serialize_field(out, &kfield);
2560 
2561   t_field vfield(tmap->get_val_type(), viter);
2562   generate_serialize_field(out, &vfield);
2563 }
2564 
2565 /**
2566  * Serializes the members of a set.
2567  */
generate_serialize_set_element(ostream & out,t_set * tset,string iter)2568 void t_js_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) {
2569   t_field efield(tset->get_elem_type(), iter);
2570   generate_serialize_field(out, &efield);
2571 }
2572 
2573 /**
2574  * Serializes the members of a list.
2575  */
generate_serialize_list_element(ostream & out,t_list * tlist,string iter)2576 void t_js_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) {
2577   t_field efield(tlist->get_elem_type(), iter);
2578   generate_serialize_field(out, &efield);
2579 }
2580 
2581 /**
2582  * Declares a field, which may include initialization as necessary.
2583  *
2584  * @param ttype The type
2585  */
declare_field(t_field * tfield,bool init,bool obj)2586 string t_js_generator::declare_field(t_field* tfield, bool init, bool obj) {
2587   string result = "this." + tfield->get_name();
2588 
2589   if (!obj) {
2590     result = js_let_type_ + tfield->get_name();
2591   }
2592 
2593   if (init) {
2594     t_type* type = get_true_type(tfield->get_type());
2595     if (type->is_base_type()) {
2596       t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
2597       switch (tbase) {
2598       case t_base_type::TYPE_VOID:
2599         break;
2600       case t_base_type::TYPE_STRING:
2601       case t_base_type::TYPE_BOOL:
2602       case t_base_type::TYPE_I8:
2603       case t_base_type::TYPE_I16:
2604       case t_base_type::TYPE_I32:
2605       case t_base_type::TYPE_I64:
2606       case t_base_type::TYPE_DOUBLE:
2607         result += " = null";
2608         break;
2609       default:
2610         throw std::runtime_error("compiler error: no JS initializer for base type " + t_base_type::t_base_name(tbase));
2611       }
2612     } else if (type->is_enum()) {
2613       result += " = null";
2614     } else if (type->is_map()) {
2615       result += " = null";
2616     } else if (type->is_container()) {
2617       result += " = null";
2618     } else if (type->is_struct() || type->is_xception()) {
2619       if (obj) {
2620         result += " = new " + js_type_namespace(type->get_program()) + type->get_name() + "()";
2621       } else {
2622         result += " = null";
2623       }
2624     }
2625   } else {
2626     result += " = null";
2627   }
2628   return result;
2629 }
2630 
2631 /**
2632  * Renders a function signature of the form 'type name(args)'
2633  *
2634  * @param tfunction Function definition
2635  * @return String of rendered function definition
2636  */
function_signature(t_function * tfunction,string prefix,bool include_callback)2637 string t_js_generator::function_signature(t_function* tfunction,
2638                                           string prefix,
2639                                           bool include_callback) {
2640 
2641   string str;
2642 
2643   str = prefix + tfunction->get_name() + " = function(";
2644 
2645   str += argument_list(tfunction->get_arglist(), include_callback);
2646 
2647   str += ")";
2648   return str;
2649 }
2650 
2651 /**
2652  * Renders a field list
2653  */
argument_list(t_struct * tstruct,bool include_callback)2654 string t_js_generator::argument_list(t_struct* tstruct, bool include_callback) {
2655   string result = "";
2656 
2657   const vector<t_field*>& fields = tstruct->get_members();
2658   vector<t_field*>::const_iterator f_iter;
2659   bool first = true;
2660   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
2661     if (first) {
2662       first = false;
2663     } else {
2664       result += ", ";
2665     }
2666     result += (*f_iter)->get_name();
2667   }
2668 
2669   if (include_callback) {
2670     if (!fields.empty()) {
2671       result += ", ";
2672     }
2673     result += "callback";
2674   }
2675 
2676   return result;
2677 }
2678 
2679 /**
2680  * Converts the parse type to a C++ enum string for the given type.
2681  */
type_to_enum(t_type * type)2682 string t_js_generator::type_to_enum(t_type* type) {
2683   type = get_true_type(type);
2684 
2685   if (type->is_base_type()) {
2686     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
2687     switch (tbase) {
2688     case t_base_type::TYPE_VOID:
2689       throw std::runtime_error("NO T_VOID CONSTRUCT");
2690     case t_base_type::TYPE_STRING:
2691       return "Thrift.Type.STRING";
2692     case t_base_type::TYPE_BOOL:
2693       return "Thrift.Type.BOOL";
2694     case t_base_type::TYPE_I8:
2695       return "Thrift.Type.BYTE";
2696     case t_base_type::TYPE_I16:
2697       return "Thrift.Type.I16";
2698     case t_base_type::TYPE_I32:
2699       return "Thrift.Type.I32";
2700     case t_base_type::TYPE_I64:
2701       return "Thrift.Type.I64";
2702     case t_base_type::TYPE_DOUBLE:
2703       return "Thrift.Type.DOUBLE";
2704     default:
2705       throw "compiler error: unhandled type";
2706     }
2707   } else if (type->is_enum()) {
2708     return "Thrift.Type.I32";
2709   } else if (type->is_struct() || type->is_xception()) {
2710     return "Thrift.Type.STRUCT";
2711   } else if (type->is_map()) {
2712     return "Thrift.Type.MAP";
2713   } else if (type->is_set()) {
2714     return "Thrift.Type.SET";
2715   } else if (type->is_list()) {
2716     return "Thrift.Type.LIST";
2717   }
2718 
2719   throw std::runtime_error("INVALID TYPE IN type_to_enum: " + type->get_name());
2720 }
2721 
2722 /**
2723  * Converts a t_type to a TypeScript type (string).
2724  * @param t_type Type to convert to TypeScript
2725  * @return String TypeScript type
2726  */
ts_get_type(t_type * type)2727 string t_js_generator::ts_get_type(t_type* type) {
2728   std::string ts_type;
2729 
2730   type = get_true_type(type);
2731 
2732   if (type->is_base_type()) {
2733     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
2734     switch (tbase) {
2735     case t_base_type::TYPE_STRING:
2736       ts_type = type->is_binary() ? "Buffer" : "string";
2737       break;
2738     case t_base_type::TYPE_BOOL:
2739       ts_type = "boolean";
2740       break;
2741     case t_base_type::TYPE_I8:
2742       ts_type = "any";
2743       break;
2744     case t_base_type::TYPE_I16:
2745     case t_base_type::TYPE_I32:
2746     case t_base_type::TYPE_DOUBLE:
2747       ts_type = "number";
2748       break;
2749     case t_base_type::TYPE_I64:
2750       ts_type = "Int64";
2751       break;
2752     case t_base_type::TYPE_VOID:
2753       ts_type = "void";
2754       break;
2755     default:
2756       throw "compiler error: unhandled type";
2757     }
2758   } else if (type->is_enum() || type->is_struct() || type->is_xception()) {
2759     std::string type_name;
2760 
2761     if (type->get_program()) {
2762       type_name = js_namespace(type->get_program());
2763 
2764       // If the type is not defined within the current program, we need to prefix it with the same name as
2765       // the generated "import" statement for the types containing program
2766       if(type->get_program() != program_)  {
2767         auto prefix = include_2_import_name.find(type->get_program());
2768 
2769         if(prefix != include_2_import_name.end()) {
2770           type_name.append(prefix->second);
2771           type_name.append(".");
2772         }
2773       }
2774     }
2775 
2776     type_name.append(type->get_name());
2777     ts_type = type_name;
2778   } else if (type->is_list() || type->is_set()) {
2779     t_type* etype;
2780 
2781     if (type->is_list()) {
2782       etype = ((t_list*)type)->get_elem_type();
2783     } else {
2784       etype = ((t_set*)type)->get_elem_type();
2785     }
2786 
2787     ts_type = ts_get_type(etype) + "[]";
2788   } else if (type->is_map()) {
2789     string ktype = ts_get_type(((t_map*)type)->get_key_type());
2790     string vtype = ts_get_type(((t_map*)type)->get_val_type());
2791 
2792 
2793     if (ktype == "number" || ktype == "string" ) {
2794       ts_type = "{ [k: " + ktype + "]: " + vtype + "; }";
2795     } else if ((((t_map*)type)->get_key_type())->is_enum()) {
2796       // Not yet supported (enum map): https://github.com/Microsoft/TypeScript/pull/2652
2797       //ts_type = "{ [k: " + ktype + "]: " + vtype + "; }";
2798       ts_type = "{ [k: number /*" + ktype + "*/]: " + vtype + "; }";
2799     } else {
2800       ts_type = "any";
2801     }
2802   }
2803 
2804   return ts_type;
2805 }
2806 
2807 /**
2808  * Renders a TypeScript function signature of the form 'name(args: types): type;'
2809  *
2810  * @param t_function Function definition
2811  * @param bool in-/exclude the callback argument
2812  * @return String of rendered function definition
2813  */
ts_function_signature(t_function * tfunction,bool include_callback)2814 std::string t_js_generator::ts_function_signature(t_function* tfunction, bool include_callback) {
2815   string str;
2816   const vector<t_field*>& fields = tfunction->get_arglist()->get_members();
2817   vector<t_field*>::const_iterator f_iter;
2818 
2819   str = tfunction->get_name() + "(";
2820 
2821   bool has_written_optional = false;
2822 
2823   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
2824     // Ensure that non optional parameters do not follow optional parameters
2825     // E.g. public foo(a: string, b?: string; c: string) is invalid, c must be optional, or b non-optional
2826     string original_optional = ts_get_req(*f_iter);
2827     string optional = has_written_optional ? "?" : original_optional;
2828     has_written_optional = has_written_optional || optional.size() > 0;
2829 
2830     str += (*f_iter)->get_name() + optional + ": " + ts_get_type((*f_iter)->get_type());
2831 
2832     if (f_iter + 1 != fields.end() || (include_callback && fields.size() > 0)) {
2833       str += ", ";
2834     }
2835   }
2836 
2837   if (include_callback) {
2838     if (gen_node_) {
2839       t_struct* exceptions = tfunction->get_xceptions();
2840       string exception_types;
2841       if (exceptions) {
2842         const vector<t_field*>& members = exceptions->get_members();
2843         for (vector<t_field*>::const_iterator it = members.begin(); it != members.end(); ++it) {
2844           t_type* t = get_true_type((*it)->get_type());
2845           if (it == members.begin()) {
2846             exception_types = js_type_namespace(t->get_program()) + t->get_name();
2847           } else {
2848             exception_types += " | " + js_type_namespace(t->get_program()) + t->get_name();
2849           }
2850         }
2851       }
2852       if (exception_types == "") {
2853         str += "callback?: (error: void, response: " + ts_get_type(tfunction->get_returntype()) + ")=>void): ";
2854       } else {
2855         str += "callback?: (error: " + exception_types + ", response: " + ts_get_type(tfunction->get_returntype()) + ")=>void): ";
2856       }
2857     } else {
2858       str += "callback?: (data: " + ts_get_type(tfunction->get_returntype()) + ")=>void): ";
2859     }
2860 
2861     if (gen_jquery_) {
2862       str += "JQueryPromise<" + ts_get_type(tfunction->get_returntype()) +">;";
2863     } else {
2864       str += "void;";
2865     }
2866   } else {
2867     if (gen_es6_) {
2868       str += "): Promise<" + ts_get_type(tfunction->get_returntype()) + ">;";
2869     }
2870     else {
2871       str += "): " + ts_get_type(tfunction->get_returntype()) + ";";
2872     }
2873   }
2874 
2875   return str;
2876 }
2877 
2878 /**
2879  * Takes a name and produces a valid NodeJS identifier from it
2880  *
2881  * @param name The name which shall become a valid NodeJS identifier
2882  * @return The modified name with the updated identifier
2883  */
make_valid_nodeJs_identifier(std::string const & name)2884 std::string t_js_generator::make_valid_nodeJs_identifier(std::string const& name) {
2885   std::string str = name;
2886   if (str.empty()) {
2887     return str;
2888   }
2889 
2890   // tests rely on this
2891   assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9'));
2892 
2893   // if the first letter is a number, we add an additional underscore in front of it
2894   char c = str.at(0);
2895   if (('0' <= c) && (c <= '9')) {
2896     str = "_" + str;
2897   }
2898 
2899   // following chars: letter, number or underscore
2900   for (size_t i = 0; i < str.size(); ++i) {
2901     c = str.at(i);
2902     if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9'))
2903         && ('_' != c) && ('$' != c)) {
2904       str.replace(i, 1, "_");
2905     }
2906   }
2907 
2908   return str;
2909 }
2910 
parse_imports(t_program * program,const std::string & imports_string)2911 void t_js_generator::parse_imports(t_program* program, const std::string& imports_string) {
2912   if (program->get_recursive()) {
2913     throw std::invalid_argument("[-gen js:imports=] option is not usable in recursive code generation mode");
2914   }
2915   std::stringstream sstream(imports_string);
2916   std::string import;
2917   while (std::getline(sstream, import, ':')) {
2918     imports.emplace_back(import);
2919   }
2920   if (imports.empty()) {
2921     throw std::invalid_argument("invalid usage: [-gen js:imports=] requires at least one path "
2922           "(multiple paths are separated by ':')");
2923   }
2924   for (auto& import : imports) {
2925     // Strip trailing '/'
2926     if (!import.empty() && import[import.size() - 1] == '/') {
2927       import = import.substr(0, import.size() - 1);
2928     }
2929     if (import.empty()) {
2930       throw std::invalid_argument("empty paths are not allowed in imports");
2931     }
2932     std::ifstream episode_file;
2933     string line;
2934     const auto episode_file_path = import + "/" + episode_file_name;
2935     episode_file.open(episode_file_path);
2936     if (!episode_file) {
2937       throw std::runtime_error("failed to open the file '" + episode_file_path + "'");
2938     }
2939     while (std::getline(episode_file, line)) {
2940       const auto separator_position = line.find(':');
2941       if (separator_position == string::npos) {
2942         // could not find the separator in the line
2943         throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the line '" + line
2944           + "' does not have a key:value separator ':'");
2945       }
2946       const auto module_name = line.substr(0, separator_position);
2947       const auto import_path = line.substr(separator_position + 1);
2948       if (module_name.empty()) {
2949         throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the module name is empty");
2950       }
2951       if (import_path.empty()) {
2952         throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the import path is empty");
2953       }
2954       const auto module_import_path = import.substr(import.find_last_of('/') + 1) + "/" + import_path;
2955       const auto result = module_name_2_import_path.emplace(module_name, module_import_path);
2956       if (!result.second) {
2957         throw std::runtime_error("multiple providers of import path found for " + module_name
2958           + "\n\t" + module_import_path + "\n\t" + result.first->second);
2959       }
2960     }
2961   }
2962 }
parse_thrift_package_output_directory(const std::string & thrift_package_output_directory)2963 void t_js_generator::parse_thrift_package_output_directory(const std::string& thrift_package_output_directory) {
2964   thrift_package_output_directory_ = thrift_package_output_directory;
2965   // Strip trailing '/'
2966   if (!thrift_package_output_directory_.empty() && thrift_package_output_directory_[thrift_package_output_directory_.size() - 1] == '/') {
2967     thrift_package_output_directory_ = thrift_package_output_directory_.substr(0, thrift_package_output_directory_.size() - 1);
2968   }
2969   // Check that the thrift_package_output_directory is not empty after stripping
2970   if (thrift_package_output_directory_.empty()) {
2971     throw std::invalid_argument("the thrift_package_output_directory argument must not be empty");
2972   } else {
2973     gen_episode_file_ = true;
2974   }
2975 }
2976 
2977 /**
2978  * Checks is the specified field name is contained in the specified field vector
2979  */
find_field(const std::vector<t_field * > & fields,const std::string & name)2980 bool t_js_generator::find_field(const std::vector<t_field*>& fields, const std::string& name) {
2981     vector<t_field*>::const_iterator f_iter;
2982     for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
2983         if ((*f_iter)->get_name() == name) {
2984           return true;
2985         }
2986     }
2987 
2988     return false;
2989 }
2990 
2991 /**
2992  * Given a vector of fields, generate a valid identifier name that does not conflict with avaliable field names
2993  */
next_identifier_name(const std::vector<t_field * > & fields,const std::string & base_name)2994 std::string t_js_generator::next_identifier_name(const std::vector<t_field*>& fields, const std::string& base_name) {
2995   // Search through fields until a match is not found, if a match is found prepend "_" to the identifier name
2996   std::string current_name = this->make_valid_nodeJs_identifier(base_name);
2997   while(this->find_field(fields, current_name)) {
2998     current_name = this->make_valid_nodeJs_identifier("_" + current_name);
2999   }
3000 
3001   return current_name;
3002 }
3003 
display_name() const3004 std::string t_js_generator::display_name() const {
3005   return "Javascript";
3006 }
3007 
3008 
3009 THRIFT_REGISTER_GENERATOR(js,
3010                           "Javascript",
3011                           "    jquery:          Generate jQuery compatible code.\n"
3012                           "    node:            Generate node.js compatible code.\n"
3013                           "    ts:              Generate TypeScript definition files.\n"
3014                           "    with_ns:         Create global namespace objects when using node.js\n"
3015                           "    es6:             Create ES6 code with Promises\n"
3016                           "    thrift_package_output_directory=<path>:\n"
3017                           "                     Generate episode file and use the <path> as prefix\n"
3018                           "    imports=<paths_to_modules>:\n"
3019                           "                     ':' separated list of paths of modules that has episode files in their root\n")
3020