2 module.exports = static_target;
4 var protobuf = require("../.."),
5 UglifyJS = require("uglify-js"),
6 espree = require("espree"),
7 escodegen = require("escodegen"),
8 estraverse = require("estraverse");
10 var Type = protobuf.Type,
11 Service = protobuf.Service,
13 Namespace = protobuf.Namespace,
20 static_target.description = "Static code without reflection (non-functional on its own)";
22 function static_target(root, options, callback) {
27 aliases.push("Reader");
29 aliases.push("Writer");
33 push("// Common aliases");
34 push((config.es6 ? "const " : "var ") + aliases.map(function(name) { return "$" + name + " = $protobuf." + name; }).join(", ") + ";");
37 if (config.comments) {
39 pushComment("@fileoverview " + root.comment);
42 push("// Exported root namespace");
44 var rootProp = util.safeProp(config.root || "default");
45 push((config.es6 ? "const" : "var") + " $root = $protobuf.roots" + rootProp + " || ($protobuf.roots" + rootProp + " = {});");
46 buildNamespace(null, root);
47 return callback(null, out.join("\n"));
61 for (var i = 0; i < indent; ++i)
63 return out.push(ind + line);
66 function pushComment(lines) {
70 for (var i = 0; i < lines.length; ++i)
71 if (lines[i] != null && lines[i].substring(0, 8) !== "@exclude")
72 Array.prototype.push.apply(split, lines[i].split(/\r?\n/g));
74 split.forEach(function(line) {
77 push(" * " + line.replace(/\*\//g, "* /"));
82 function exportName(object, asInterface) {
84 if (object.__interfaceName)
85 return object.__interfaceName;
86 } else if (object.__exportName)
87 return object.__exportName;
88 var parts = object.fullName.substring(1).split("."),
90 while (i < parts.length)
91 parts[i] = escapeName(parts[i++]);
93 parts[i - 1] = "I" + parts[i - 1];
94 return object[asInterface ? "__interfaceName" : "__exportName"] = parts.join(".");
97 function escapeName(name) {
100 return util.isReserved(name) ? name + "_" : name;
103 function aOrAn(name) {
104 return ((/^[hH](?:ou|on|ei)/.test(name) || /^[aeiouAEIOU][a-z]/.test(name)) && !/^us/i.test(name)
109 function buildNamespace(ref, ns) {
112 if (ns.name !== "") {
114 if (!ref && config.es6)
115 push("export const " + escapeName(ns.name) + " = " + escapeName(ref) + "." + escapeName(ns.name) + " = (() => {");
117 push(escapeName(ref) + "." + escapeName(ns.name) + " = (function() {");
121 if (ns instanceof Type) {
122 buildType(undefined, ns);
123 } else if (ns instanceof Service)
124 buildService(undefined, ns);
125 else if (ns.name !== "") {
128 ns.comment || "Namespace " + ns.name + ".",
129 ns.parent instanceof protobuf.Root ? "@exports " + escapeName(ns.name) : "@memberof " + exportName(ns.parent),
132 push((config.es6 ? "const" : "var") + " " + escapeName(ns.name) + " = {};");
135 ns.nestedArray.forEach(function(nested) {
136 if (nested instanceof Enum)
137 buildEnum(ns.name, nested);
138 else if (nested instanceof Namespace)
139 buildNamespace(ns.name, nested);
141 if (ns.name !== "") {
143 push("return " + escapeName(ns.name) + ";");
149 var reduceableBlockStatements = {
161 "c": "end", "c2": "end2",
163 "ks": "keys", "ks2": "keys2",
172 function beautifyCode(code) {
174 code = UglifyJS.minify(code, {
177 output: { beautify: true }
180 var ast = espree.parse(code);
181 estraverse.replace(ast, {
182 enter: function(node, parent) {
184 if (node.type === "Identifier" && (parent.property !== node || parent.computed) && shortVars[node.name])
186 "type": "Identifier",
187 "name": shortVars[node.name]
189 // replace var with let if es6
190 if (config.es6 && node.type === "VariableDeclaration" && node.kind === "var") {
194 // remove braces around block statements with a single child
195 if (node.type === "BlockStatement" && reduceableBlockStatements[parent.type] && node.body.length === 1)
200 code = escodegen.generate(ast, {
206 // Add id, wireType comments
208 code = code.replace(/\.uint32\((\d+)\)/g, function($0, $1) {
211 return ".uint32(/* id " + id + ", wireType " + wireType + " =*/" + $1 + ")";
222 function buildFunction(type, functionName, gen, scope) {
223 var code = gen.toString(functionName)
224 .replace(/((?!\.)types\[\d+])(\.values)/g, "$1"); // enums: use types[N] instead of reflected types[N].values
226 var ast = espree.parse(code);
227 /* eslint-disable no-extra-parens */
228 estraverse.replace(ast, {
229 enter: function(node, parent) {
232 node.type === "Identifier" && renameVars[node.name]
234 (parent.type === "MemberExpression" && parent.object === node)
235 || (parent.type === "BinaryExpression" && parent.right === node)
239 "type": "Identifier",
240 "name": renameVars[node.name]
242 // replace this.ctor with the actual ctor
244 node.type === "MemberExpression"
245 && node.object.type === "ThisExpression"
246 && node.property.type === "Identifier" && node.property.name === "ctor"
249 "type": "Identifier",
250 "name": "$root" + type.fullName
252 // replace types[N] with the field's actual type
254 node.type === "MemberExpression"
255 && node.object.type === "Identifier" && node.object.name === "types"
256 && node.property.type === "Literal"
259 "type": "Identifier",
260 "name": "$root" + type.fieldsArray[node.property.value].resolvedType.fullName
265 /* eslint-enable no-extra-parens */
266 code = escodegen.generate(ast, {
274 code = beautifyCode(code);
276 code = code.replace(/ {4}/g, "\t");
278 var hasScope = scope && Object.keys(scope).length,
279 isCtor = functionName === type.name;
281 if (hasScope) // remove unused scope vars
282 Object.keys(scope).forEach(function(key) {
283 if (!new RegExp("\\b(" + key + ")\\b", "g").test(code))
287 var lines = code.split(/\n/g);
288 if (isCtor) // constructor
290 else if (hasScope) // enclose in an iife
291 push(escapeName(type.name) + "." + escapeName(functionName) + " = (function(" + Object.keys(scope).map(escapeName).join(", ") + ") { return " + lines[0]);
293 push(escapeName(type.name) + "." + escapeName(functionName) + " = " + lines[0]);
294 lines.slice(1, lines.length - 1).forEach(function(line) {
297 while (line.charAt(i++) === "\t")
305 push("};})(" + Object.keys(scope).map(function(key) { return scope[key]; }).join(", ") + ");");
310 function toJsType(field) {
313 switch (field.type) {
328 type = config.forceLong ? "Long" : config.forceNumber ? "number" : "number|Long";
340 if (field.resolve().resolvedType)
341 type = exportName(field.resolvedType, !(field.resolvedType instanceof protobuf.Enum || config.forceMessage));
343 type = "*"; // should not happen
347 return "Object.<string," + type + ">";
349 return "Array.<" + type + ">";
353 function buildType(ref, type) {
355 if (config.comments) {
357 "Properties of " + aOrAn(type.name) + ".",
358 type.parent instanceof protobuf.Root ? "@exports " + escapeName("I" + type.name) : "@memberof " + exportName(type.parent),
359 "@interface " + escapeName("I" + type.name)
361 type.fieldsArray.forEach(function(field) {
362 var prop = util.safeProp(field.name); // either .name or ["name"]
363 prop = prop.substring(1, prop.charAt(0) === "[" ? prop.length - 1 : prop.length);
364 var jsType = toJsType(field);
366 jsType = jsType + "|null";
367 typeDef.push("@property {" + jsType + "} " + (field.optional ? "[" + prop + "]" : prop) + " " + (field.comment || type.name + " " + field.name));
370 pushComment(typeDef);
376 "Constructs a new " + type.name + ".",
377 type.parent instanceof protobuf.Root ? "@exports " + escapeName(type.name) : "@memberof " + exportName(type.parent),
378 "@classdesc " + (type.comment || "Represents " + aOrAn(type.name) + "."),
379 config.comments ? "@implements " + escapeName("I" + type.name) : null,
381 "@param {" + exportName(type, true) + "=} [" + (config.beautify ? "properties" : "p") + "] Properties to set"
383 buildFunction(type, type.name, Type.generateConstructor(type));
386 var firstField = true;
387 type.fieldsArray.forEach(function(field) {
389 var prop = util.safeProp(field.name);
390 if (config.comments) {
392 var jsType = toJsType(field);
393 if (field.optional && !field.map && !field.repeated && field.resolvedType instanceof Type)
394 jsType = jsType + "|null|undefined";
396 field.comment || type.name + " " + field.name + ".",
397 "@member {" + jsType + "} " + field.name,
398 "@memberof " + exportName(type),
401 } else if (firstField) {
406 push(escapeName(type.name) + ".prototype" + prop + " = $util.emptyArray;"); // overwritten in constructor
408 push(escapeName(type.name) + ".prototype" + prop + " = $util.emptyObject;"); // overwritten in constructor
410 push(escapeName(type.name) + ".prototype" + prop + " = $util.Long ? $util.Long.fromBits("
411 + JSON.stringify(field.typeDefault.low) + ","
412 + JSON.stringify(field.typeDefault.high) + ","
413 + JSON.stringify(field.typeDefault.unsigned)
414 + ") : " + field.typeDefault.toNumber(field.type.charAt(0) === "u") + ";");
415 else if (field.bytes) {
416 push(escapeName(type.name) + ".prototype" + prop + " = $util.newBuffer(" + JSON.stringify(Array.prototype.slice.call(field.typeDefault)) + ");");
418 push(escapeName(type.name) + ".prototype" + prop + " = " + JSON.stringify(field.typeDefault) + ";");
421 // virtual oneof fields
422 var firstOneOf = true;
423 type.oneofsArray.forEach(function(oneof) {
428 push("// OneOf field names bound to virtual getters and setters");
429 push((config.es6 ? "let" : "var") + " $oneOfFields;");
434 oneof.comment || type.name + " " + oneof.name + ".",
435 "@member {" + oneof.oneof.map(JSON.stringify).join("|") + "|undefined} " + escapeName(oneof.name),
436 "@memberof " + exportName(type),
439 push("Object.defineProperty(" + escapeName(type.name) + ".prototype, " + JSON.stringify(oneof.name) +", {");
441 push("get: $util.oneOfGetter($oneOfFields = [" + oneof.oneof.map(JSON.stringify).join(", ") + "]),");
442 push("set: $util.oneOfSetter($oneOfFields)");
450 "Creates a new " + type.name + " instance using the specified properties.",
452 "@memberof " + exportName(type),
454 "@param {" + exportName(type, true) + "=} [properties] Properties to set",
455 "@returns {" + exportName(type) + "} " + type.name + " instance"
457 push(escapeName(type.name) + ".create = function create(properties) {");
459 push("return new " + escapeName(type.name) + "(properties);");
467 "Encodes the specified " + type.name + " message. Does not implicitly {@link " + exportName(type) + ".verify|verify} messages.",
469 "@memberof " + exportName(type),
471 "@param {" + exportName(type, !config.forceMessage) + "} " + (config.beautify ? "message" : "m") + " " + type.name + " message or plain object to encode",
472 "@param {$protobuf.Writer} [" + (config.beautify ? "writer" : "w") + "] Writer to encode to",
473 "@returns {$protobuf.Writer} Writer"
475 buildFunction(type, "encode", protobuf.encoder(type));
477 if (config.delimited) {
480 "Encodes the specified " + type.name + " message, length delimited. Does not implicitly {@link " + exportName(type) + ".verify|verify} messages.",
481 "@function encodeDelimited",
482 "@memberof " + exportName(type),
484 "@param {" + exportName(type, !config.forceMessage) + "} message " + type.name + " message or plain object to encode",
485 "@param {$protobuf.Writer} [writer] Writer to encode to",
486 "@returns {$protobuf.Writer} Writer"
488 push(escapeName(type.name) + ".encodeDelimited = function encodeDelimited(message, writer) {");
490 push("return this.encode(message, writer).ldelim();");
499 "Decodes " + aOrAn(type.name) + " message from the specified reader or buffer.",
501 "@memberof " + exportName(type),
503 "@param {$protobuf.Reader|Uint8Array} " + (config.beautify ? "reader" : "r") + " Reader or buffer to decode from",
504 "@param {number} [" + (config.beautify ? "length" : "l") + "] Message length if known beforehand",
505 "@returns {" + exportName(type) + "} " + type.name,
506 "@throws {Error} If the payload is not a reader or valid buffer",
507 "@throws {$protobuf.util.ProtocolError} If required fields are missing"
509 buildFunction(type, "decode", protobuf.decoder(type));
511 if (config.delimited) {
514 "Decodes " + aOrAn(type.name) + " message from the specified reader or buffer, length delimited.",
515 "@function decodeDelimited",
516 "@memberof " + exportName(type),
518 "@param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from",
519 "@returns {" + exportName(type) + "} " + type.name,
520 "@throws {Error} If the payload is not a reader or valid buffer",
521 "@throws {$protobuf.util.ProtocolError} If required fields are missing"
523 push(escapeName(type.name) + ".decodeDelimited = function decodeDelimited(reader) {");
525 push("if (!(reader instanceof $Reader))");
527 push("reader = new $Reader(reader);");
529 push("return this.decode(reader, reader.uint32());");
538 "Verifies " + aOrAn(type.name) + " message.",
540 "@memberof " + exportName(type),
542 "@param {Object.<string,*>} " + (config.beautify ? "message" : "m") + " Plain object to verify",
543 "@returns {string|null} `null` if valid, otherwise the reason why it is not"
545 buildFunction(type, "verify", protobuf.verifier(type));
548 if (config.convert) {
551 "Creates " + aOrAn(type.name) + " message from a plain object. Also converts values to their respective internal types.",
552 "@function fromObject",
553 "@memberof " + exportName(type),
555 "@param {Object.<string,*>} " + (config.beautify ? "object" : "d") + " Plain object",
556 "@returns {" + exportName(type) + "} " + type.name
558 buildFunction(type, "fromObject", protobuf.converter.fromObject(type));
562 "Creates a plain object from " + aOrAn(type.name) + " message. Also converts values to other types if specified.",
563 "@function toObject",
564 "@memberof " + exportName(type),
566 "@param {" + exportName(type) + "} " + (config.beautify ? "message" : "m") + " " + type.name,
567 "@param {$protobuf.IConversionOptions} [" + (config.beautify ? "options" : "o") + "] Conversion options",
568 "@returns {Object.<string,*>} Plain object"
570 buildFunction(type, "toObject", protobuf.converter.toObject(type));
574 "Converts this " + type.name + " to JSON.",
576 "@memberof " + exportName(type),
578 "@returns {Object.<string,*>} JSON object"
580 push(escapeName(type.name) + ".prototype.toJSON = function toJSON() {");
582 push("return this.constructor.toObject(this, $protobuf.util.toJSONOptions);");
588 function buildService(ref, service) {
592 "Constructs a new " + service.name + " service.",
593 service.parent instanceof protobuf.Root ? "@exports " + escapeName(service.name) : "@memberof " + exportName(service.parent),
594 "@classdesc " + (service.comment || "Represents " + aOrAn(service.name)),
595 "@extends $protobuf.rpc.Service",
597 "@param {$protobuf.RPCImpl} rpcImpl RPC implementation",
598 "@param {boolean} [requestDelimited=false] Whether requests are length-delimited",
599 "@param {boolean} [responseDelimited=false] Whether responses are length-delimited"
601 push("function " + escapeName(service.name) + "(rpcImpl, requestDelimited, responseDelimited) {");
603 push("$protobuf.rpc.Service.call(this, rpcImpl, requestDelimited, responseDelimited);");
607 push("(" + escapeName(service.name) + ".prototype = Object.create($protobuf.rpc.Service.prototype)).constructor = " + escapeName(service.name) + ";");
612 "Creates new " + service.name + " service using the specified rpc implementation.",
614 "@memberof " + exportName(service),
616 "@param {$protobuf.RPCImpl} rpcImpl RPC implementation",
617 "@param {boolean} [requestDelimited=false] Whether requests are length-delimited",
618 "@param {boolean} [responseDelimited=false] Whether responses are length-delimited",
619 "@returns {" + escapeName(service.name) + "} RPC service. Useful where requests and/or responses are streamed."
621 push(escapeName(service.name) + ".create = function create(rpcImpl, requestDelimited, responseDelimited) {");
623 push("return new this(rpcImpl, requestDelimited, responseDelimited);");
628 service.methodsArray.forEach(function(method) {
630 var lcName = protobuf.util.lcFirst(method.name),
631 cbName = escapeName(method.name + "Callback");
634 "Callback as used by {@link " + exportName(service) + "#" + escapeName(lcName) + "}.",
635 // This is a more specialized version of protobuf.rpc.ServiceCallback
636 "@memberof " + exportName(service),
637 "@typedef " + cbName,
639 "@param {Error|null} error Error, if any",
640 "@param {" + exportName(method.resolvedResponseType) + "} [response] " + method.resolvedResponseType.name
644 method.comment || "Calls " + method.name + ".",
645 "@function " + lcName,
646 "@memberof " + exportName(service),
648 "@param {" + exportName(method.resolvedRequestType, !config.forceMessage) + "} request " + method.resolvedRequestType.name + " message or plain object",
649 "@param {" + exportName(service) + "." + cbName + "} callback Node-style callback called with the error, if any, and " + method.resolvedResponseType.name,
650 "@returns {undefined}",
653 push("Object.defineProperty(" + escapeName(service.name) + ".prototype" + util.safeProp(lcName) + " = function " + escapeName(lcName) + "(request, callback) {");
655 push("return this.rpcCall(" + escapeName(lcName) + ", $root." + exportName(method.resolvedRequestType) + ", $root." + exportName(method.resolvedResponseType) + ", request, callback);");
657 push("}, \"name\", { value: " + JSON.stringify(method.name) + " });");
661 method.comment || "Calls " + method.name + ".",
662 "@function " + lcName,
663 "@memberof " + exportName(service),
665 "@param {" + exportName(method.resolvedRequestType, !config.forceMessage) + "} request " + method.resolvedRequestType.name + " message or plain object",
666 "@returns {Promise<" + exportName(method.resolvedResponseType) + ">} Promise",
672 function buildEnum(ref, enm) {
676 enm.comment || enm.name + " enum.",
677 enm.parent instanceof protobuf.Root ? "@exports " + escapeName(enm.name) : "@name " + exportName(enm),
678 config.forceEnumString ? "@enum {string}" : "@enum {number}",
680 Object.keys(enm.values).forEach(function(key) {
681 var val = config.forceEnumString ? key : enm.values[key];
682 comment.push((config.forceEnumString ? "@property {string} " : "@property {number} ") + key + "=" + val + " " + (enm.comments[key] || key + " value"));
684 pushComment(comment);
685 if (!ref && config.es6)
686 push("export const " + escapeName(enm.name) + " = " + escapeName(ref) + "." + escapeName(enm.name) + " = (() => {");
688 push(escapeName(ref) + "." + escapeName(enm.name) + " = (function() {");
690 push((config.es6 ? "const" : "var") + " valuesById = {}, values = Object.create(valuesById);");
692 Object.keys(enm.values).forEach(function(key) {
693 var valueId = enm.values[key];
694 var val = config.forceEnumString ? JSON.stringify(key) : valueId;
695 if (aliased.indexOf(valueId) > -1)
696 push("values[" + JSON.stringify(key) + "] = " + val + ";");
698 push("values[valuesById[" + valueId + "] = " + JSON.stringify(key) + "] = " + val + ";");
699 aliased.push(valueId);
702 push("return values;");