mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 03:49:11 +00:00
feat(ts-protoc-gen): initial commit, broken
This commit is contained in:
parent
9823670084
commit
291ec9576f
20 changed files with 1887 additions and 0 deletions
24
src/ts-protoc-gen/BUILD.bazel
Normal file
24
src/ts-protoc-gen/BUILD.bazel
Normal file
|
@ -0,0 +1,24 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "src",
|
||||
srcs = glob([
|
||||
"*.ts",
|
||||
"service/*.ts",
|
||||
"ts/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"@npm//@types/google-protobuf",
|
||||
"@npm//@types/node",
|
||||
"@npm//google-protobuf",
|
||||
],
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "protoc-gen-ts",
|
||||
data = [":src"],
|
||||
entry_point = ":index.ts",
|
||||
)
|
26
src/ts-protoc-gen/CodePrinter.ts
Normal file
26
src/ts-protoc-gen/CodePrinter.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {Printer} from "./Printer";
|
||||
import {generateIndent} from "./util";
|
||||
|
||||
export class CodePrinter {
|
||||
private indentation: string;
|
||||
constructor(private depth: number, private printer: Printer) {
|
||||
this.indentation = generateIndent(1);
|
||||
}
|
||||
indent() {
|
||||
this.depth++;
|
||||
return this;
|
||||
}
|
||||
dedent() {
|
||||
this.depth--;
|
||||
return this;
|
||||
}
|
||||
printLn(line: string) {
|
||||
this.printer.printLn(new Array(this.depth + 1).join(this.indentation) + line);
|
||||
return this;
|
||||
}
|
||||
|
||||
printEmptyLn() {
|
||||
this.printer.printEmptyLn();
|
||||
return this;
|
||||
}
|
||||
}
|
84
src/ts-protoc-gen/ExportMap.ts
Normal file
84
src/ts-protoc-gen/ExportMap.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import {
|
||||
FileDescriptorProto,
|
||||
DescriptorProto,
|
||||
MessageOptions,
|
||||
EnumOptions,
|
||||
FieldDescriptorProto
|
||||
} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
|
||||
import Type = FieldDescriptorProto.Type;
|
||||
|
||||
export type ExportMessageEntry = {
|
||||
pkg: string,
|
||||
fileName: string,
|
||||
messageOptions: MessageOptions,
|
||||
mapFieldOptions?: {
|
||||
key: [Type, string],
|
||||
value: [Type, string],
|
||||
}
|
||||
};
|
||||
|
||||
export type ExportEnumEntry = {
|
||||
pkg: string,
|
||||
fileName: string,
|
||||
enumOptions: EnumOptions,
|
||||
};
|
||||
|
||||
export class ExportMap {
|
||||
messageMap: {[key: string]: ExportMessageEntry} = {};
|
||||
enumMap: {[key: string]: ExportEnumEntry} = {};
|
||||
|
||||
exportNested(scope: string, fileDescriptor: FileDescriptorProto, message: DescriptorProto) {
|
||||
const messageEntry: ExportMessageEntry = {
|
||||
pkg: fileDescriptor.getPackage(),
|
||||
fileName: fileDescriptor.getName(),
|
||||
messageOptions: message.getOptions(),
|
||||
mapFieldOptions: message.getOptions() && message.getOptions().getMapEntry() ? {
|
||||
key: [message.getFieldList()[0].getType(), message.getFieldList()[0].getTypeName().slice(1)],
|
||||
value: [message.getFieldList()[1].getType(), message.getFieldList()[1].getTypeName().slice(1)],
|
||||
} : undefined,
|
||||
};
|
||||
|
||||
const packagePrefix = scope ? scope + "." : "";
|
||||
|
||||
const entryName = `${packagePrefix}${message.getName()}`;
|
||||
this.messageMap[entryName] = messageEntry;
|
||||
|
||||
message.getNestedTypeList().forEach(nested => {
|
||||
this.exportNested(`${packagePrefix}${message.getName()}`, fileDescriptor, nested);
|
||||
});
|
||||
|
||||
message.getEnumTypeList().forEach(enumType => {
|
||||
const identifier = `${packagePrefix}${message.getName()}.${enumType.getName()}`;
|
||||
this.enumMap[identifier] = {
|
||||
pkg: fileDescriptor.getPackage(),
|
||||
fileName: fileDescriptor.getName(),
|
||||
enumOptions: enumType.getOptions(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
addFileDescriptor(fileDescriptor: FileDescriptorProto) {
|
||||
const scope = fileDescriptor.getPackage();
|
||||
fileDescriptor.getMessageTypeList().forEach(messageType => {
|
||||
this.exportNested(scope, fileDescriptor, messageType);
|
||||
});
|
||||
|
||||
fileDescriptor.getEnumTypeList().forEach(enumType => {
|
||||
const packagePrefix = scope ? scope + "." : "";
|
||||
this.enumMap[packagePrefix + enumType.getName()] = {
|
||||
pkg: fileDescriptor.getPackage(),
|
||||
fileName: fileDescriptor.getName(),
|
||||
enumOptions: enumType.getOptions(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getMessage(str: string): ExportMessageEntry | undefined {
|
||||
return this.messageMap[str];
|
||||
}
|
||||
|
||||
getEnum(str: string): ExportEnumEntry | undefined {
|
||||
return this.enumMap[str];
|
||||
}
|
||||
}
|
30
src/ts-protoc-gen/Printer.ts
Normal file
30
src/ts-protoc-gen/Printer.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import {generateIndent} from "./util";
|
||||
|
||||
export class Printer {
|
||||
indentStr: string;
|
||||
output: string = "";
|
||||
|
||||
constructor(indentLevel: number) {
|
||||
this.indentStr = generateIndent(indentLevel);
|
||||
}
|
||||
|
||||
printLn(str: string) {
|
||||
this.output += this.indentStr + str + "\n";
|
||||
}
|
||||
|
||||
print(str: string) {
|
||||
this.output += str;
|
||||
}
|
||||
|
||||
printEmptyLn() {
|
||||
this.output += "\n";
|
||||
}
|
||||
|
||||
printIndentedLn(str: string) {
|
||||
this.output += this.indentStr + " " + str + "\n";
|
||||
}
|
||||
|
||||
getOutput(): string {
|
||||
return this.output;
|
||||
}
|
||||
}
|
14
src/ts-protoc-gen/WellKnown.ts
Normal file
14
src/ts-protoc-gen/WellKnown.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
export const WellKnownTypesMap: {[key: string]: string} = {
|
||||
"google/protobuf/compiler/plugin.proto": "google-protobuf/google/protobuf/compiler/plugin_pb",
|
||||
"google/protobuf/any.proto": "google-protobuf/google/protobuf/any_pb",
|
||||
"google/protobuf/api.proto": "google-protobuf/google/protobuf/api_pb",
|
||||
"google/protobuf/descriptor.proto": "google-protobuf/google/protobuf/descriptor_pb",
|
||||
"google/protobuf/duration.proto": "google-protobuf/google/protobuf/duration_pb",
|
||||
"google/protobuf/empty.proto": "google-protobuf/google/protobuf/empty_pb",
|
||||
"google/protobuf/field_mask.proto": "google-protobuf/google/protobuf/field_mask_pb",
|
||||
"google/protobuf/source_context.proto": "google-protobuf/google/protobuf/source_context_pb",
|
||||
"google/protobuf/struct.proto": "google-protobuf/google/protobuf/struct_pb",
|
||||
"google/protobuf/timestamp.proto": "google-protobuf/google/protobuf/timestamp_pb",
|
||||
"google/protobuf/type.proto": "google-protobuf/google/protobuf/type_pb",
|
||||
"google/protobuf/wrappers.proto": "google-protobuf/google/protobuf/wrappers_pb"
|
||||
};
|
61
src/ts-protoc-gen/index.ts
Normal file
61
src/ts-protoc-gen/index.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import {printFileDescriptorTSD} from "./ts/fileDescriptorTSD";
|
||||
import {ExportMap} from "./ExportMap";
|
||||
import {replaceProtoSuffix, withAllStdIn, getParameterEnums} from "./util";
|
||||
import {CodeGeneratorRequest, CodeGeneratorResponse} from "google-protobuf/google/protobuf/compiler/plugin_pb";
|
||||
import {FileDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {generateGrpcWebService} from "./service/grpcweb";
|
||||
import {generateGrpcNodeService} from "./service/grpcnode";
|
||||
import {ServiceParameter} from "./parameters";
|
||||
|
||||
/**
|
||||
* This is the ProtoC compiler plugin.
|
||||
*
|
||||
* The Protocol Buffers Compiler can be extended to [support new languages via plugins](https://developers.google.com/protocol-buffers/docs/reference/other).
|
||||
* A plugin is just a program which reads a CodeGeneratorRequest protocol buffer from standard input
|
||||
* and then writes a CodeGeneratorResponse protocol buffer to standard output.
|
||||
* These message types are defined in [plugin.proto](https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/plugin.proto).
|
||||
*
|
||||
*/
|
||||
withAllStdIn((inputBuff: Buffer) => {
|
||||
try {
|
||||
const typedInputBuff = new Uint8Array(inputBuff.length);
|
||||
typedInputBuff.set(inputBuff);
|
||||
|
||||
const codeGenRequest = CodeGeneratorRequest.deserializeBinary(typedInputBuff);
|
||||
const codeGenResponse = new CodeGeneratorResponse();
|
||||
const exportMap = new ExportMap();
|
||||
const fileNameToDescriptor: {[key: string]: FileDescriptorProto} = {};
|
||||
|
||||
const parameter = codeGenRequest.getParameter();
|
||||
const {service, mode} = getParameterEnums(parameter);
|
||||
|
||||
const generateGrpcWebServices = service === ServiceParameter.GrpcWeb;
|
||||
const generateGrpcNodeServices = service === ServiceParameter.GrpcNode;
|
||||
|
||||
codeGenRequest.getProtoFileList().forEach(protoFileDescriptor => {
|
||||
fileNameToDescriptor[protoFileDescriptor.getName()] = protoFileDescriptor;
|
||||
exportMap.addFileDescriptor(protoFileDescriptor);
|
||||
});
|
||||
|
||||
codeGenRequest.getFileToGenerateList().forEach(fileName => {
|
||||
const outputFileName = replaceProtoSuffix(fileName);
|
||||
const thisFile = new CodeGeneratorResponse.File();
|
||||
thisFile.setName(outputFileName + ".d.ts");
|
||||
thisFile.setContent(printFileDescriptorTSD(fileNameToDescriptor[fileName], exportMap));
|
||||
codeGenResponse.addFile(thisFile);
|
||||
|
||||
if (generateGrpcWebServices) {
|
||||
generateGrpcWebService(outputFileName, fileNameToDescriptor[fileName], exportMap)
|
||||
.forEach(file => codeGenResponse.addFile(file));
|
||||
} else if (generateGrpcNodeServices) {
|
||||
const file = generateGrpcNodeService(outputFileName, fileNameToDescriptor[fileName], exportMap, mode);
|
||||
codeGenResponse.addFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdout.write(Buffer.from(codeGenResponse.serializeBinary()));
|
||||
} catch (err) {
|
||||
console.error("protoc-gen-ts error: " + err.stack + "\n");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
10
src/ts-protoc-gen/parameters.ts
Normal file
10
src/ts-protoc-gen/parameters.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export enum ServiceParameter {
|
||||
None,
|
||||
GrpcWeb,
|
||||
GrpcNode
|
||||
}
|
||||
|
||||
export enum ModeParameter {
|
||||
None,
|
||||
GrpcJs
|
||||
}
|
23
src/ts-protoc-gen/rules/BUILD.bazel
Normal file
23
src/ts-protoc-gen/rules/BUILD.bazel
Normal file
|
@ -0,0 +1,23 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
|
||||
nodejs_binary(
|
||||
name = "change_import_style",
|
||||
data = [
|
||||
":change_import_style_lib",
|
||||
],
|
||||
entry_point = ":change_import_style.ts",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "change_import_style_lib",
|
||||
srcs = [
|
||||
"change_import_style.ts",
|
||||
],
|
||||
deps = [
|
||||
"@npm//@types/minimist",
|
||||
"@npm//@types/node",
|
||||
"@npm//minimist",
|
||||
],
|
||||
)
|
121
src/ts-protoc-gen/rules/change_import_style.ts
Normal file
121
src/ts-protoc-gen/rules/change_import_style.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Converts a list of generated protobuf-js files from commonjs modules into named AMD modules.
|
||||
*
|
||||
* Arguments:
|
||||
* --workspace_name
|
||||
* --input_base_path
|
||||
* --output_module_name
|
||||
* --input_file_path
|
||||
* --output_file_path
|
||||
*/
|
||||
import minimist = require('minimist');
|
||||
import fs = require('fs');
|
||||
|
||||
function main() {
|
||||
const args = minimist(process.argv.slice(2));
|
||||
|
||||
const initialContents = fs.readFileSync(args.input_file_path, 'utf8');
|
||||
|
||||
const umdContents = convertToUmd(args, initialContents);
|
||||
fs.writeFileSync(args.output_umd_path, umdContents, 'utf8');
|
||||
|
||||
const commonJsContents = convertToESM(args, initialContents);
|
||||
fs.writeFileSync(args.output_es6_path, commonJsContents, 'utf8');
|
||||
}
|
||||
|
||||
function replaceRecursiveFilePaths(args: any) {
|
||||
return (contents: string) => {
|
||||
return contents.replace(/(\.\.\/)+/g, `${args.workspace_name}/`);
|
||||
};
|
||||
}
|
||||
|
||||
function removeJsExtensionsFromRequires(contents: string) {
|
||||
return contents.replace(/(require\(.*).js/g, (_, captureGroup: string) => {
|
||||
return captureGroup;
|
||||
});
|
||||
}
|
||||
|
||||
function convertToUmd(args: any, initialContents: string): string {
|
||||
const wrapInAMDModule = (contents: string) => {
|
||||
return `// GENERATED CODE DO NOT EDIT
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define("${args.input_base_path}/${args.output_module_name}", factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
${contents}
|
||||
});
|
||||
`;
|
||||
};
|
||||
|
||||
const transformations: ((c: string) => string)[] = [
|
||||
wrapInAMDModule,
|
||||
replaceRecursiveFilePaths(args),
|
||||
removeJsExtensionsFromRequires,
|
||||
];
|
||||
return transformations.reduce((currentContents, transform) => {
|
||||
return transform(currentContents);
|
||||
}, initialContents);
|
||||
}
|
||||
|
||||
// Converts the CommonJS format from protoc to the ECMAScript Module format.
|
||||
// Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
|
||||
function convertToESM(args: any, initialContents: string): string {
|
||||
const replaceGoogExtendWithExports = (contents: string) => {
|
||||
return contents.replace(
|
||||
/goog\.object\.extend\(exports, ([\w\.]+)\);/g,
|
||||
(_, packageName: string) => {
|
||||
const exportSymbols = /goog\.exportSymbol\('([\w\.]+)',.*\);/g;
|
||||
const symbols = [];
|
||||
|
||||
let match: RegExpExecArray | null = exportSymbols.exec(initialContents);
|
||||
while (match) {
|
||||
// We want to ignore embedded export targets, IE: `DeliveryPerson.DataCase`.
|
||||
const exportTarget = match[1].substr(packageName.length + 1);
|
||||
if (!exportTarget.includes('.')) {
|
||||
symbols.push(exportTarget);
|
||||
}
|
||||
match = exportSymbols.exec(initialContents);
|
||||
}
|
||||
|
||||
return `export const { ${symbols.join(', ')} } = ${packageName}`;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const replaceRequiresWithImports = (contents: string) => {
|
||||
return contents.replace(
|
||||
/var ([\w\d_]+) = require\((['"][\w\d@/_-]+['"])\);/g,
|
||||
'import * as $1 from $2;'
|
||||
);
|
||||
};
|
||||
|
||||
const replaceRequiresWithSubpackageImports = (contents: string) => {
|
||||
return contents.replace(
|
||||
/var ([\w\d_]+) = require\((['"][\w\d@/_-]+['"])\)\.([\w\d_]+);/g,
|
||||
'import * as $1 from $2;'
|
||||
);
|
||||
};
|
||||
|
||||
const replaceCJSExportsWithECMAExports = (contents: string) => {
|
||||
return contents.replace(/exports\.([\w\d_]+) = .*;/g, 'export { $1 };');
|
||||
};
|
||||
|
||||
const transformations: ((c: string) => string)[] = [
|
||||
replaceRecursiveFilePaths(args),
|
||||
removeJsExtensionsFromRequires,
|
||||
replaceGoogExtendWithExports,
|
||||
replaceRequiresWithImports,
|
||||
replaceRequiresWithSubpackageImports,
|
||||
replaceCJSExportsWithECMAExports,
|
||||
];
|
||||
return transformations.reduce((currentContents, transform) => {
|
||||
return transform(currentContents);
|
||||
}, initialContents);
|
||||
}
|
||||
|
||||
main();
|
255
src/ts-protoc-gen/rules/index.bzl
Normal file
255
src/ts-protoc-gen/rules/index.bzl
Normal file
|
@ -0,0 +1,255 @@
|
|||
load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "JSEcmaScriptModuleInfo", "JSNamedModuleInfo")
|
||||
load("@rules_proto//proto:defs.bzl", "ProtoInfo")
|
||||
|
||||
TypescriptProtoLibraryAspect = provider(
|
||||
fields = {
|
||||
"es5_outputs": "The ES5 JS files produced directly from the src protos",
|
||||
"es6_outputs": "The ES6 JS files produced directly from the src protos",
|
||||
"dts_outputs": "Ths TS definition files produced directly from the src protos",
|
||||
"deps_es5": "The transitive ES5 JS dependencies",
|
||||
"deps_es6": "The transitive ES6 JS dependencies",
|
||||
"deps_dts": "The transitive dependencies' TS definitions",
|
||||
},
|
||||
)
|
||||
|
||||
def _proto_path(proto):
|
||||
"""
|
||||
The proto path is not really a file path
|
||||
It's the path to the proto that was seen when the descriptor file was generated.
|
||||
"""
|
||||
path = proto.path
|
||||
root = proto.root.path
|
||||
ws = proto.owner.workspace_root
|
||||
if path.startswith(root):
|
||||
path = path[len(root):]
|
||||
if path.startswith("/"):
|
||||
path = path[1:]
|
||||
if path.startswith(ws):
|
||||
path = path[len(ws):]
|
||||
if path.startswith("/"):
|
||||
path = path[1:]
|
||||
return path
|
||||
|
||||
def _get_protoc_inputs(target, ctx):
|
||||
inputs = []
|
||||
inputs += target[ProtoInfo].direct_sources
|
||||
inputs += target[ProtoInfo].transitive_descriptor_sets.to_list()
|
||||
return inputs
|
||||
|
||||
def _get_input_proto_names(target):
|
||||
"""
|
||||
Builds a string containing all of the input proto file names separated by spaces.
|
||||
"""
|
||||
proto_inputs = []
|
||||
for src in target[ProtoInfo].direct_sources:
|
||||
if src.extension != "proto":
|
||||
fail("Input must be a proto file")
|
||||
normalized_file = _proto_path(src)
|
||||
proto_inputs.append(normalized_file)
|
||||
return " ".join(proto_inputs)
|
||||
|
||||
def _build_protoc_command(target, ctx):
|
||||
protoc_command = "%s" % (ctx.executable._protoc.path)
|
||||
|
||||
protoc_command += " --plugin=protoc-gen-ts=%s" % (ctx.executable._ts_protoc_gen.path)
|
||||
|
||||
protoc_output_dir = ctx.var["BINDIR"]
|
||||
protoc_command += " --ts_out=service=grpc-web:%s" % (protoc_output_dir)
|
||||
protoc_command += " --js_out=import_style=commonjs,binary:%s" % (protoc_output_dir)
|
||||
|
||||
descriptor_sets_paths = [desc.path for desc in target[ProtoInfo].transitive_descriptor_sets.to_list()]
|
||||
protoc_command += " --descriptor_set_in=%s" % (":".join(descriptor_sets_paths))
|
||||
|
||||
protoc_command += " %s" % (_get_input_proto_names(target))
|
||||
|
||||
return protoc_command
|
||||
|
||||
def _create_post_process_command(target, ctx, js_outputs, js_outputs_es6):
|
||||
"""
|
||||
Builds a post-processing command that:
|
||||
- Updates the existing protoc output files to be UMD modules
|
||||
- Creates a new es6 file from the original protoc output
|
||||
"""
|
||||
convert_commands = []
|
||||
for [output, output_es6] in zip(js_outputs, js_outputs_es6):
|
||||
file_path = "/".join([p for p in [
|
||||
ctx.workspace_name,
|
||||
ctx.label.package,
|
||||
] if p])
|
||||
file_name = output.basename[:-len(output.extension) - 1]
|
||||
|
||||
convert_command = ctx.executable._change_import_style.path
|
||||
convert_command += " --workspace_name {}".format(ctx.workspace_name)
|
||||
convert_command += " --input_base_path {}".format(file_path)
|
||||
convert_command += " --output_module_name {}".format(file_name)
|
||||
convert_command += " --input_file_path {}".format(output.path)
|
||||
convert_command += " --output_umd_path {}".format(output.path)
|
||||
convert_command += " --output_es6_path {}".format(output_es6.path)
|
||||
convert_commands.append(convert_command)
|
||||
|
||||
return " && ".join(convert_commands)
|
||||
|
||||
def _get_outputs(target, ctx):
|
||||
"""
|
||||
Calculates all of the files that will be generated by the aspect.
|
||||
"""
|
||||
js_outputs = []
|
||||
js_outputs_es6 = []
|
||||
dts_outputs = []
|
||||
for src in target[ProtoInfo].direct_sources:
|
||||
file_name = src.basename[:-len(src.extension) - 1]
|
||||
for f in ["_pb", "_pb_service"]:
|
||||
full_name = file_name + f
|
||||
output = ctx.actions.declare_file(full_name + ".js")
|
||||
js_outputs.append(output)
|
||||
output_es6 = ctx.actions.declare_file(full_name + ".mjs")
|
||||
js_outputs_es6.append(output_es6)
|
||||
|
||||
for f in ["_pb.d.ts", "_pb_service.d.ts"]:
|
||||
output = ctx.actions.declare_file(file_name + f)
|
||||
dts_outputs.append(output)
|
||||
|
||||
return [js_outputs, js_outputs_es6, dts_outputs]
|
||||
|
||||
def typescript_proto_library_aspect_(target, ctx):
|
||||
"""
|
||||
A bazel aspect that is applied on every proto_library rule on the transitive set of dependencies
|
||||
of a typescript_proto_library rule.
|
||||
|
||||
Handles running protoc to produce the generated JS and TS files.
|
||||
"""
|
||||
|
||||
[js_outputs, js_outputs_es6, dts_outputs] = _get_outputs(target, ctx)
|
||||
protoc_outputs = dts_outputs + js_outputs + js_outputs_es6
|
||||
|
||||
all_commands = [
|
||||
_build_protoc_command(target, ctx),
|
||||
_create_post_process_command(target, ctx, js_outputs, js_outputs_es6),
|
||||
]
|
||||
|
||||
tools = []
|
||||
tools.extend(ctx.files._protoc)
|
||||
tools.extend(ctx.files._ts_protoc_gen)
|
||||
tools.extend(ctx.files._change_import_style)
|
||||
|
||||
ctx.actions.run_shell(
|
||||
inputs = depset(_get_protoc_inputs(target, ctx)),
|
||||
outputs = protoc_outputs,
|
||||
progress_message = "Creating Typescript pb files %s" % ctx.label,
|
||||
command = " && ".join(all_commands),
|
||||
tools = depset(tools),
|
||||
)
|
||||
|
||||
dts_outputs = depset(dts_outputs)
|
||||
es5_outputs = depset(js_outputs)
|
||||
es6_outputs = depset(js_outputs_es6)
|
||||
deps_dts = []
|
||||
deps_es5 = []
|
||||
deps_es6 = []
|
||||
|
||||
for dep in ctx.rule.attr.deps:
|
||||
aspect_data = dep[TypescriptProtoLibraryAspect]
|
||||
deps_dts.append(aspect_data.dts_outputs)
|
||||
deps_dts.append(aspect_data.deps_dts)
|
||||
deps_es5.append(aspect_data.es5_outputs)
|
||||
deps_es5.append(aspect_data.deps_es5)
|
||||
deps_es6.append(aspect_data.es6_outputs)
|
||||
deps_es6.append(aspect_data.deps_es6)
|
||||
|
||||
return [TypescriptProtoLibraryAspect(
|
||||
dts_outputs = dts_outputs,
|
||||
es5_outputs = es5_outputs,
|
||||
es6_outputs = es6_outputs,
|
||||
deps_dts = depset(transitive = deps_dts),
|
||||
deps_es5 = depset(transitive = deps_es5),
|
||||
deps_es6 = depset(transitive = deps_es6),
|
||||
)]
|
||||
|
||||
typescript_proto_library_aspect = aspect(
|
||||
implementation = typescript_proto_library_aspect_,
|
||||
attr_aspects = ["deps"],
|
||||
attrs = {
|
||||
"_ts_protoc_gen": attr.label(
|
||||
allow_files = True,
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("//src/ts-protoc-gen:protoc-gen-ts"),
|
||||
),
|
||||
"_protoc": attr.label(
|
||||
allow_single_file = True,
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("@com_google_protobuf//:protoc"),
|
||||
),
|
||||
"_change_import_style": attr.label(
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
allow_files = True,
|
||||
default = Label("//src/ts-protoc-gen/rules:change_import_style"),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
def _typescript_proto_library_impl(ctx):
|
||||
"""
|
||||
Handles converting the aspect output into a provider compatible with the rules_typescript rules.
|
||||
"""
|
||||
aspect_data = ctx.attr.proto[TypescriptProtoLibraryAspect]
|
||||
dts_outputs = aspect_data.dts_outputs
|
||||
transitive_declarations = depset(transitive = [dts_outputs, aspect_data.deps_dts])
|
||||
es5_outputs = aspect_data.es5_outputs
|
||||
es6_outputs = aspect_data.es6_outputs
|
||||
outputs = depset(transitive = [es5_outputs, es6_outputs, dts_outputs])
|
||||
|
||||
es5_srcs = depset(transitive = [es5_outputs, aspect_data.deps_es5])
|
||||
es6_srcs = depset(transitive = [es6_outputs, aspect_data.deps_es6])
|
||||
return struct(
|
||||
typescript = struct(
|
||||
declarations = dts_outputs,
|
||||
transitive_declarations = transitive_declarations,
|
||||
es5_sources = es5_srcs,
|
||||
es6_sources = es6_srcs,
|
||||
transitive_es5_sources = es5_srcs,
|
||||
transitive_es6_sources = es6_srcs,
|
||||
),
|
||||
providers = [
|
||||
DefaultInfo(files = outputs),
|
||||
DeclarationInfo(
|
||||
declarations = dts_outputs,
|
||||
transitive_declarations = transitive_declarations,
|
||||
type_blacklisted_declarations = depset([]),
|
||||
),
|
||||
JSNamedModuleInfo(
|
||||
direct_sources = es5_srcs,
|
||||
sources = es5_srcs,
|
||||
),
|
||||
JSEcmaScriptModuleInfo(
|
||||
direct_sources = es6_srcs,
|
||||
sources = es6_srcs,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
typescript_proto_library = rule(
|
||||
attrs = {
|
||||
"proto": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = True,
|
||||
providers = [ProtoInfo],
|
||||
aspects = [typescript_proto_library_aspect],
|
||||
),
|
||||
"_ts_protoc_gen": attr.label(
|
||||
allow_files = True,
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("//src/ts-protoc-gen:protoc-gen-ts"),
|
||||
),
|
||||
"_protoc": attr.label(
|
||||
allow_single_file = True,
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("@com_google_protobuf//:protoc"),
|
||||
),
|
||||
},
|
||||
implementation = _typescript_proto_library_impl,
|
||||
)
|
142
src/ts-protoc-gen/service/common.ts
Normal file
142
src/ts-protoc-gen/service/common.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
import {CodeGeneratorResponse} from "google-protobuf/google/protobuf/compiler/plugin_pb";
|
||||
import {FileDescriptorProto, MethodDescriptorProto, ServiceDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {ExportMap} from "../ExportMap";
|
||||
import {WellKnownTypesMap} from "../WellKnown";
|
||||
import {getFieldType, MESSAGE_TYPE} from "../ts/FieldTypes";
|
||||
import {filePathToPseudoNamespace, replaceProtoSuffix, getPathToRoot, normaliseFieldObjectName} from "../util";
|
||||
|
||||
export function createFile(output: string, filename: string): CodeGeneratorResponse.File {
|
||||
const file = new CodeGeneratorResponse.File();
|
||||
file.setName(filename);
|
||||
file.setContent(output);
|
||||
return file;
|
||||
}
|
||||
|
||||
type CallingTypes = {
|
||||
requestType: string
|
||||
responseType: string
|
||||
};
|
||||
|
||||
function getCallingTypes(method: MethodDescriptorProto, exportMap: ExportMap): CallingTypes {
|
||||
return {
|
||||
requestType: getFieldType(MESSAGE_TYPE, method.getInputType().slice(1), "", exportMap),
|
||||
responseType: getFieldType(MESSAGE_TYPE, method.getOutputType().slice(1), "", exportMap),
|
||||
};
|
||||
}
|
||||
|
||||
function isUsed(fileDescriptor: FileDescriptorProto, pseudoNamespace: string, exportMap: ExportMap) {
|
||||
return fileDescriptor.getServiceList().some(service => {
|
||||
return service.getMethodList().some(method => {
|
||||
const callingTypes = getCallingTypes(method, exportMap);
|
||||
const namespacePackage = pseudoNamespace + ".";
|
||||
return (
|
||||
callingTypes.requestType.indexOf(namespacePackage) === 0 ||
|
||||
callingTypes.responseType.indexOf(namespacePackage) === 0
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export type ImportDescriptor = {
|
||||
readonly namespace: string
|
||||
readonly path: string
|
||||
};
|
||||
|
||||
export type RPCMethodDescriptor = {
|
||||
readonly nameAsPascalCase: string,
|
||||
readonly nameAsCamelCase: string,
|
||||
readonly functionName: string,
|
||||
readonly serviceName: string,
|
||||
readonly requestStream: boolean
|
||||
readonly responseStream: boolean
|
||||
readonly requestType: string
|
||||
readonly responseType: string
|
||||
};
|
||||
|
||||
export class RPCDescriptor {
|
||||
private readonly grpcService: GrpcServiceDescriptor;
|
||||
private readonly protoService: ServiceDescriptorProto;
|
||||
private readonly exportMap: ExportMap;
|
||||
|
||||
constructor(grpcService: GrpcServiceDescriptor, protoService: ServiceDescriptorProto, exportMap: ExportMap) {
|
||||
this.grpcService = grpcService;
|
||||
this.protoService = protoService;
|
||||
this.exportMap = exportMap;
|
||||
}
|
||||
get name(): string {
|
||||
return this.protoService.getName();
|
||||
}
|
||||
|
||||
get qualifiedName(): string {
|
||||
return (this.grpcService.packageName ? `${this.grpcService.packageName}.` : "") + this.name;
|
||||
}
|
||||
|
||||
get methods(): RPCMethodDescriptor[] {
|
||||
return this.protoService.getMethodList()
|
||||
.map(method => {
|
||||
const callingTypes = getCallingTypes(method, this.exportMap);
|
||||
const nameAsCamelCase = method.getName()[0].toLowerCase() + method.getName().substr(1);
|
||||
return {
|
||||
nameAsPascalCase: method.getName(),
|
||||
nameAsCamelCase,
|
||||
functionName: normaliseFieldObjectName(nameAsCamelCase),
|
||||
serviceName: this.name,
|
||||
requestStream: method.getClientStreaming(),
|
||||
responseStream: method.getServerStreaming(),
|
||||
requestType: callingTypes.requestType,
|
||||
responseType: callingTypes.responseType,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class GrpcServiceDescriptor {
|
||||
private readonly fileDescriptor: FileDescriptorProto;
|
||||
private readonly exportMap: ExportMap;
|
||||
private readonly pathToRoot: string;
|
||||
|
||||
constructor(fileDescriptor: FileDescriptorProto, exportMap: ExportMap) {
|
||||
this.fileDescriptor = fileDescriptor;
|
||||
this.exportMap = exportMap;
|
||||
this.pathToRoot = getPathToRoot(fileDescriptor.getName());
|
||||
}
|
||||
|
||||
get filename(): string {
|
||||
return this.fileDescriptor.getName();
|
||||
}
|
||||
|
||||
get packageName(): string {
|
||||
return this.fileDescriptor.getPackage();
|
||||
}
|
||||
|
||||
get imports(): ImportDescriptor[] {
|
||||
const dependencies = this.fileDescriptor.getDependencyList()
|
||||
.filter(dependency => isUsed(this.fileDescriptor, filePathToPseudoNamespace(dependency), this.exportMap))
|
||||
.map(dependency => {
|
||||
const namespace = filePathToPseudoNamespace(dependency);
|
||||
if (dependency in WellKnownTypesMap) {
|
||||
return {
|
||||
namespace,
|
||||
path: WellKnownTypesMap[dependency],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
namespace,
|
||||
path: `${this.pathToRoot}${replaceProtoSuffix(replaceProtoSuffix(dependency))}`
|
||||
};
|
||||
}
|
||||
});
|
||||
const hostProto = {
|
||||
namespace: filePathToPseudoNamespace(this.filename),
|
||||
path: `${this.pathToRoot}${replaceProtoSuffix(this.filename)}`,
|
||||
};
|
||||
return [ hostProto ].concat(dependencies);
|
||||
}
|
||||
|
||||
get services(): RPCDescriptor[] {
|
||||
return this.fileDescriptor.getServiceList()
|
||||
.map(service => {
|
||||
return new RPCDescriptor(this, service, this.exportMap);
|
||||
});
|
||||
}
|
||||
}
|
127
src/ts-protoc-gen/service/grpcnode.ts
Normal file
127
src/ts-protoc-gen/service/grpcnode.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import {ExportMap} from "../ExportMap";
|
||||
import {Printer} from "../Printer";
|
||||
import {FileDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {CodeGeneratorResponse} from "google-protobuf/google/protobuf/compiler/plugin_pb";
|
||||
import {createFile, RPCDescriptor, GrpcServiceDescriptor, RPCMethodDescriptor} from "./common";
|
||||
import { ModeParameter } from "../parameters";
|
||||
|
||||
export function generateGrpcNodeService(filename: string, descriptor: FileDescriptorProto, exportMap: ExportMap, modeParameter: ModeParameter): CodeGeneratorResponse.File {
|
||||
const definitionFilename = filename.replace(/_pb$/, "_grpc_pb.d.ts");
|
||||
return createFile(generateTypeScriptDefinition(descriptor, exportMap, modeParameter), definitionFilename);
|
||||
}
|
||||
|
||||
function generateTypeScriptDefinition(fileDescriptor: FileDescriptorProto, exportMap: ExportMap, modeParameter: ModeParameter): string {
|
||||
const serviceDescriptor = new GrpcServiceDescriptor(fileDescriptor, exportMap);
|
||||
const printer = new Printer(0);
|
||||
|
||||
const hasServices = serviceDescriptor.services.length > 0;
|
||||
|
||||
// Header.
|
||||
if (hasServices) {
|
||||
printer.printLn("// GENERATED CODE -- DO NOT EDIT!");
|
||||
printer.printEmptyLn();
|
||||
} else {
|
||||
printer.printLn("// GENERATED CODE -- NO SERVICES IN PROTO");
|
||||
return printer.getOutput();
|
||||
}
|
||||
|
||||
printer.printLn(`// package: ${serviceDescriptor.packageName}`);
|
||||
printer.printLn(`// file: ${serviceDescriptor.filename}`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
// Import statements.
|
||||
serviceDescriptor.imports
|
||||
.forEach(importDescriptor => {
|
||||
printer.printLn(`import * as ${importDescriptor.namespace} from "${importDescriptor.path}";`);
|
||||
});
|
||||
const importPackage = modeParameter === ModeParameter.GrpcJs ? "@grpc/grpc-js" : "grpc";
|
||||
printer.printLn(`import * as grpc from "${importPackage}";`);
|
||||
|
||||
// Services.
|
||||
serviceDescriptor.services
|
||||
.forEach(service => {
|
||||
printer.printEmptyLn();
|
||||
printService(printer, service);
|
||||
printer.printEmptyLn();
|
||||
printClient(printer, service);
|
||||
});
|
||||
|
||||
return printer.getOutput();
|
||||
}
|
||||
|
||||
function printService(printer: Printer, service: RPCDescriptor) {
|
||||
const serviceName = `${service.name}Service`;
|
||||
printer.printLn(`interface I${serviceName} extends grpc.ServiceDefinition<grpc.UntypedServiceImplementation> {`);
|
||||
service.methods
|
||||
.forEach(method => {
|
||||
const methodType = `grpc.MethodDefinition<${method.requestType}, ${method.responseType}>`;
|
||||
printer.printIndentedLn(`${method.nameAsCamelCase}: ${methodType};`);
|
||||
});
|
||||
printer.printLn("}");
|
||||
printer.printEmptyLn();
|
||||
printer.printLn(`export const ${serviceName}: I${serviceName};`);
|
||||
}
|
||||
|
||||
function printClient(printer: Printer, service: RPCDescriptor) {
|
||||
printer.printLn(`export class ${service.name}Client extends grpc.Client {`);
|
||||
printer.printIndentedLn("constructor(address: string, credentials: grpc.ChannelCredentials, options?: object);");
|
||||
service.methods
|
||||
.forEach(method => {
|
||||
if (!method.requestStream && !method.responseStream) {
|
||||
printUnaryRequestMethod(printer, method);
|
||||
} else if (!method.requestStream) {
|
||||
printServerStreamRequestMethod(printer, method);
|
||||
} else if (!method.responseStream) {
|
||||
printClientStreamRequestMethod(printer, method);
|
||||
} else {
|
||||
printBidiStreamRequest(printer, method);
|
||||
}
|
||||
});
|
||||
printer.printLn("}");
|
||||
}
|
||||
|
||||
const metadata = "metadata: grpc.Metadata | null";
|
||||
const options = "options: grpc.CallOptions | null";
|
||||
const metadataOrOptions = "metadataOrOptions: grpc.Metadata | grpc.CallOptions | null";
|
||||
|
||||
const optionalMetadata = "metadata?: grpc.Metadata | null";
|
||||
const optionalOptions = "options?: grpc.CallOptions | null";
|
||||
const optionalMetadataOrOptions = "metadataOrOptions?: grpc.Metadata | grpc.CallOptions | null";
|
||||
|
||||
function printUnaryRequestMethod(printer: Printer, method: RPCMethodDescriptor) {
|
||||
const name = method.nameAsCamelCase;
|
||||
const argument = `argument: ${method.requestType}`;
|
||||
const callback = `callback: grpc.requestCallback<${method.responseType}>`;
|
||||
const returnType = "grpc.ClientUnaryCall";
|
||||
|
||||
printer.printIndentedLn(`${name}(${argument}, ${callback}): ${returnType};`);
|
||||
printer.printIndentedLn(`${name}(${argument}, ${metadataOrOptions}, ${callback}): ${returnType};`);
|
||||
printer.printIndentedLn(`${name}(${argument}, ${metadata}, ${options}, ${callback}): ${returnType};`);
|
||||
}
|
||||
|
||||
function printServerStreamRequestMethod(printer: Printer, method: RPCMethodDescriptor) {
|
||||
const name = method.nameAsCamelCase;
|
||||
const argument = `argument: ${method.requestType}`;
|
||||
const returnType = `grpc.ClientReadableStream<${method.responseType}>`;
|
||||
|
||||
printer.printIndentedLn(`${name}(${argument}, ${optionalMetadataOrOptions}): ${returnType};`);
|
||||
printer.printIndentedLn(`${name}(${argument}, ${optionalMetadata}, ${optionalOptions}): ${returnType};`);
|
||||
}
|
||||
|
||||
function printClientStreamRequestMethod(printer: Printer, method: RPCMethodDescriptor) {
|
||||
const name = method.nameAsCamelCase;
|
||||
const callback = `callback: grpc.requestCallback<${method.responseType}>`;
|
||||
const returnType = `grpc.ClientWritableStream<${method.requestType}>`;
|
||||
|
||||
printer.printIndentedLn(`${name}(${callback}): grpc.ClientWritableStream<${method.requestType}>;`);
|
||||
printer.printIndentedLn(`${name}(${metadataOrOptions}, ${callback}): ${returnType};`);
|
||||
printer.printIndentedLn(`${name}(${metadata}, ${options}, ${callback}): ${returnType};`);
|
||||
}
|
||||
|
||||
function printBidiStreamRequest(printer: Printer, method: RPCMethodDescriptor) {
|
||||
const name = method.nameAsCamelCase;
|
||||
const returnType = `grpc.ClientDuplexStream<${method.requestType}, ${method.responseType}>`;
|
||||
|
||||
printer.printIndentedLn(`${name}(${optionalMetadataOrOptions}): ${returnType};`);
|
||||
printer.printIndentedLn(`${name}(${optionalMetadata}, ${optionalOptions}): ${returnType};`);
|
||||
}
|
397
src/ts-protoc-gen/service/grpcweb.ts
Normal file
397
src/ts-protoc-gen/service/grpcweb.ts
Normal file
|
@ -0,0 +1,397 @@
|
|||
import {ExportMap} from "../ExportMap";
|
||||
import {Printer} from "../Printer";
|
||||
import {CodePrinter} from "../CodePrinter";
|
||||
import {FileDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {CodeGeneratorResponse} from "google-protobuf/google/protobuf/compiler/plugin_pb";
|
||||
import {createFile, RPCMethodDescriptor, RPCDescriptor, GrpcServiceDescriptor} from "./common";
|
||||
|
||||
export function generateGrpcWebService(filename: string, descriptor: FileDescriptorProto, exportMap: ExportMap): CodeGeneratorResponse.File[] {
|
||||
return [
|
||||
createFile(generateTypeScriptDefinition(descriptor, exportMap), `${filename}_service.d.ts`),
|
||||
createFile(generateJavaScript(descriptor, exportMap), `${filename}_service.js`),
|
||||
];
|
||||
}
|
||||
|
||||
function generateTypeScriptDefinition(fileDescriptor: FileDescriptorProto, exportMap: ExportMap): string {
|
||||
const serviceDescriptor = new GrpcServiceDescriptor(fileDescriptor, exportMap);
|
||||
const printer = new Printer(0);
|
||||
|
||||
// Header.
|
||||
printer.printLn(`// package: ${serviceDescriptor.packageName}`);
|
||||
printer.printLn(`// file: ${serviceDescriptor.filename}`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
if (serviceDescriptor.services.length === 0) {
|
||||
return printer.getOutput();
|
||||
}
|
||||
|
||||
// Import statements.
|
||||
serviceDescriptor.imports
|
||||
.forEach(importDescriptor => {
|
||||
printer.printLn(`import * as ${importDescriptor.namespace} from "${importDescriptor.path}";`);
|
||||
});
|
||||
printer.printLn(`import {grpc} from "@improbable-eng/grpc-web";`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
// Services.
|
||||
serviceDescriptor.services
|
||||
.forEach(service => {
|
||||
|
||||
// Method Type Definitions
|
||||
service.methods.forEach(method => {
|
||||
printer.printLn(`type ${method.serviceName}${method.nameAsPascalCase} = {`);
|
||||
printer.printIndentedLn(`readonly methodName: string;`);
|
||||
printer.printIndentedLn(`readonly service: typeof ${method.serviceName};`);
|
||||
printer.printIndentedLn(`readonly requestStream: ${method.requestStream};`);
|
||||
printer.printIndentedLn(`readonly responseStream: ${method.responseStream};`);
|
||||
printer.printIndentedLn(`readonly requestType: typeof ${method.requestType};`);
|
||||
printer.printIndentedLn(`readonly responseType: typeof ${method.responseType};`);
|
||||
printer.printLn(`};`);
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
|
||||
printer.printLn(`export class ${service.name} {`);
|
||||
printer.printIndentedLn(`static readonly serviceName: string;`);
|
||||
service.methods.forEach(method => {
|
||||
printer.printIndentedLn(`static readonly ${method.nameAsPascalCase}: ${method.serviceName}${method.nameAsPascalCase};`);
|
||||
});
|
||||
printer.printLn(`}`);
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
|
||||
|
||||
|
||||
printer.printLn(`export type ServiceError = { message: string, code: number; metadata: grpc.Metadata }`);
|
||||
printer.printLn(`export type Status = { details: string, code: number; metadata: grpc.Metadata }`);
|
||||
printer.printEmptyLn();
|
||||
printer.printLn("interface UnaryResponse {");
|
||||
printer.printIndentedLn("cancel(): void;");
|
||||
printer.printLn("}");
|
||||
printer.printLn(`interface ResponseStream<T> {`);
|
||||
printer.printIndentedLn(`cancel(): void;`);
|
||||
printer.printIndentedLn(`on(type: 'data', handler: (message: T) => void): ResponseStream<T>;`);
|
||||
printer.printIndentedLn(`on(type: 'end', handler: (status?: Status) => void): ResponseStream<T>;`);
|
||||
printer.printIndentedLn(`on(type: 'status', handler: (status: Status) => void): ResponseStream<T>;`);
|
||||
printer.printLn(`}`);
|
||||
printer.printLn(`interface RequestStream<T> {`);
|
||||
printer.printIndentedLn(`write(message: T): RequestStream<T>;`);
|
||||
printer.printIndentedLn(`end(): void;`);
|
||||
printer.printIndentedLn(`cancel(): void;`);
|
||||
printer.printIndentedLn(`on(type: 'end', handler: (status?: Status) => void): RequestStream<T>;`);
|
||||
printer.printIndentedLn(`on(type: 'status', handler: (status: Status) => void): RequestStream<T>;`);
|
||||
printer.printLn(`}`);
|
||||
printer.printLn(`interface BidirectionalStream<ReqT, ResT> {`);
|
||||
printer.printIndentedLn(`write(message: ReqT): BidirectionalStream<ReqT, ResT>;`);
|
||||
printer.printIndentedLn(`end(): void;`);
|
||||
printer.printIndentedLn(`cancel(): void;`);
|
||||
printer.printIndentedLn(`on(type: 'data', handler: (message: ResT) => void): BidirectionalStream<ReqT, ResT>;`);
|
||||
printer.printIndentedLn(`on(type: 'end', handler: (status?: Status) => void): BidirectionalStream<ReqT, ResT>;`);
|
||||
printer.printIndentedLn(`on(type: 'status', handler: (status: Status) => void): BidirectionalStream<ReqT, ResT>;`);
|
||||
printer.printLn(`}`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
// Add a client stub that talks with the @improbable-eng/grpc-web library
|
||||
serviceDescriptor.services
|
||||
.forEach(service => {
|
||||
printServiceStubTypes(printer, service);
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
|
||||
return printer.getOutput();
|
||||
}
|
||||
|
||||
function generateJavaScript(fileDescriptor: FileDescriptorProto, exportMap: ExportMap): string {
|
||||
const serviceDescriptor = new GrpcServiceDescriptor(fileDescriptor, exportMap);
|
||||
const printer = new Printer(0);
|
||||
|
||||
// Header.
|
||||
printer.printLn(`// package: ${serviceDescriptor.packageName}`);
|
||||
printer.printLn(`// file: ${serviceDescriptor.filename}`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
if (serviceDescriptor.services.length === 0) {
|
||||
return printer.getOutput();
|
||||
}
|
||||
|
||||
// Import Statements
|
||||
serviceDescriptor.imports
|
||||
.forEach(importDescriptor => {
|
||||
printer.printLn(`var ${importDescriptor.namespace} = require("${importDescriptor.path}");`);
|
||||
});
|
||||
printer.printLn(`var grpc = require("@improbable-eng/grpc-web").grpc;`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
// Services.
|
||||
serviceDescriptor.services
|
||||
.forEach(service => {
|
||||
printer.printLn(`var ${service.name} = (function () {`);
|
||||
printer.printIndentedLn(`function ${service.name}() {}`);
|
||||
printer.printIndentedLn(`${service.name}.serviceName = "${service.qualifiedName}";`);
|
||||
printer.printIndentedLn(`return ${service.name};`);
|
||||
printer.printLn(`}());`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
service.methods
|
||||
.forEach(method => {
|
||||
printer.printLn(`${method.serviceName}.${method.nameAsPascalCase} = {`);
|
||||
printer.printIndentedLn(`methodName: "${method.nameAsPascalCase}",`);
|
||||
printer.printIndentedLn(`service: ${method.serviceName},`);
|
||||
printer.printIndentedLn(`requestStream: ${method.requestStream},`);
|
||||
printer.printIndentedLn(`responseStream: ${method.responseStream},`);
|
||||
printer.printIndentedLn(`requestType: ${method.requestType},`);
|
||||
printer.printIndentedLn(`responseType: ${method.responseType}`);
|
||||
printer.printLn(`};`);
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
printer.printLn(`exports.${service.name} = ${service.name};`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
// Add a client stub that talks with the @improbable-eng/grpc-web library
|
||||
printServiceStub(printer, service);
|
||||
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
|
||||
return printer.getOutput();
|
||||
}
|
||||
|
||||
function printServiceStub(methodPrinter: Printer, service: RPCDescriptor) {
|
||||
const printer = new CodePrinter(0, methodPrinter);
|
||||
|
||||
printer
|
||||
.printLn(`function ${service.name}Client(serviceHost, options) {`)
|
||||
.indent().printLn(`this.serviceHost = serviceHost;`)
|
||||
.printLn(`this.options = options || {};`)
|
||||
.dedent().printLn(`}`)
|
||||
.printEmptyLn();
|
||||
|
||||
service.methods.forEach((method: RPCMethodDescriptor) => {
|
||||
if (method.requestStream && method.responseStream) {
|
||||
printBidirectionalStubMethod(printer, method);
|
||||
} else if (method.requestStream) {
|
||||
printClientStreamStubMethod(printer, method);
|
||||
} else if (method.responseStream) {
|
||||
printServerStreamStubMethod(printer, method);
|
||||
} else {
|
||||
printUnaryStubMethod(printer, method);
|
||||
}
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
printer.printLn(`exports.${service.name}Client = ${service.name}Client;`);
|
||||
}
|
||||
|
||||
function printUnaryStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer
|
||||
.printLn(`${method.serviceName}Client.prototype.${method.nameAsCamelCase} = function ${method.functionName}(requestMessage, metadata, callback) {`)
|
||||
.indent().printLn(`if (arguments.length === 2) {`)
|
||||
.indent().printLn(`callback = arguments[1];`)
|
||||
.dedent().printLn("}")
|
||||
.printLn(`var client = grpc.unary(${method.serviceName}.${method.nameAsPascalCase}, {`)
|
||||
.indent().printLn(`request: requestMessage,`)
|
||||
.printLn(`host: this.serviceHost,`)
|
||||
.printLn(`metadata: metadata,`)
|
||||
.printLn(`transport: this.options.transport,`)
|
||||
.printLn(`debug: this.options.debug,`)
|
||||
.printLn(`onEnd: function (response) {`)
|
||||
.indent().printLn(`if (callback) {`)
|
||||
.indent().printLn(`if (response.status !== grpc.Code.OK) {`)
|
||||
.indent().printLn(`var err = new Error(response.statusMessage);`)
|
||||
.printLn(`err.code = response.status;`)
|
||||
.printLn(`err.metadata = response.trailers;`)
|
||||
.printLn(`callback(err, null);`)
|
||||
.dedent().printLn(`} else {`)
|
||||
.indent().printLn(`callback(null, response.message);`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`return {`)
|
||||
.indent().printLn(`cancel: function () {`)
|
||||
.indent().printLn(`callback = null;`)
|
||||
.printLn(`client.close();`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`};`)
|
||||
.dedent().printLn(`};`);
|
||||
}
|
||||
|
||||
function printServerStreamStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer
|
||||
.printLn(`${method.serviceName}Client.prototype.${method.nameAsCamelCase} = function ${method.functionName}(requestMessage, metadata) {`)
|
||||
.indent().printLn(`var listeners = {`)
|
||||
.indent().printLn(`data: [],`)
|
||||
.printLn(`end: [],`)
|
||||
.printLn(`status: []`)
|
||||
.dedent().printLn(`};`)
|
||||
.printLn(`var client = grpc.invoke(${method.serviceName}.${method.nameAsPascalCase}, {`)
|
||||
.indent().printLn(`request: requestMessage,`)
|
||||
.printLn(`host: this.serviceHost,`)
|
||||
.printLn(`metadata: metadata,`)
|
||||
.printLn(`transport: this.options.transport,`)
|
||||
.printLn(`debug: this.options.debug,`)
|
||||
.printLn(`onMessage: function (responseMessage) {`)
|
||||
.indent().printLn(`listeners.data.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler(responseMessage);`)
|
||||
.dedent().printLn(`});`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`onEnd: function (status, statusMessage, trailers) {`)
|
||||
.indent().printLn(`listeners.status.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`listeners.end.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`listeners = null;`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`return {`)
|
||||
.indent().printLn(`on: function (type, handler) {`)
|
||||
.indent().printLn(`listeners[type].push(handler);`)
|
||||
.printLn(`return this;`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`cancel: function () {`)
|
||||
.indent().printLn(`listeners = null;`)
|
||||
.printLn(`client.close();`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`};`)
|
||||
.dedent().printLn(`};`);
|
||||
}
|
||||
|
||||
function printClientStreamStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer
|
||||
.printLn(`${method.serviceName}Client.prototype.${method.nameAsCamelCase} = function ${method.functionName}(metadata) {`)
|
||||
.indent().printLn(`var listeners = {`)
|
||||
.indent().printLn(`end: [],`)
|
||||
.printLn(`status: []`)
|
||||
.dedent().printLn(`};`)
|
||||
.printLn(`var client = grpc.client(${method.serviceName}.${method.nameAsPascalCase}, {`)
|
||||
.indent().printLn(`host: this.serviceHost,`)
|
||||
.printLn(`metadata: metadata,`)
|
||||
.printLn(`transport: this.options.transport`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`client.onEnd(function (status, statusMessage, trailers) {`)
|
||||
.indent().printLn(`listeners.status.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`listeners.end.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`listeners = null;`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`return {`)
|
||||
.indent().printLn(`on: function (type, handler) {`)
|
||||
.indent().printLn(`listeners[type].push(handler);`)
|
||||
.printLn(`return this;`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`write: function (requestMessage) {`)
|
||||
.indent().printLn(`if (!client.started) {`)
|
||||
.indent().printLn(`client.start(metadata);`)
|
||||
.dedent().printLn(`}`)
|
||||
.printLn(`client.send(requestMessage);`)
|
||||
.printLn(`return this;`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`end: function () {`)
|
||||
.indent().printLn(`client.finishSend();`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`cancel: function () {`)
|
||||
.indent().printLn(`listeners = null;`)
|
||||
.printLn(`client.close();`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`};`)
|
||||
.dedent().printLn(`};`);
|
||||
}
|
||||
|
||||
function printBidirectionalStubMethod(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer
|
||||
.printLn(`${method.serviceName}Client.prototype.${method.nameAsCamelCase} = function ${method.functionName}(metadata) {`)
|
||||
.indent().printLn(`var listeners = {`)
|
||||
.indent().printLn(`data: [],`)
|
||||
.printLn(`end: [],`)
|
||||
.printLn(`status: []`)
|
||||
.dedent().printLn(`};`)
|
||||
.printLn(`var client = grpc.client(${method.serviceName}.${method.nameAsPascalCase}, {`)
|
||||
.indent().printLn(`host: this.serviceHost,`)
|
||||
.printLn(`metadata: metadata,`)
|
||||
.printLn(`transport: this.options.transport`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`client.onEnd(function (status, statusMessage, trailers) {`)
|
||||
.indent().printLn(`listeners.status.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`listeners.end.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler({ code: status, details: statusMessage, metadata: trailers });`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`listeners = null;`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`client.onMessage(function (message) {`)
|
||||
.indent().printLn(`listeners.data.forEach(function (handler) {`)
|
||||
.indent().printLn(`handler(message);`)
|
||||
.dedent().printLn(`})`)
|
||||
.dedent().printLn(`});`)
|
||||
.printLn(`client.start(metadata);`)
|
||||
.printLn(`return {`)
|
||||
.indent().printLn(`on: function (type, handler) {`)
|
||||
.indent().printLn(`listeners[type].push(handler);`)
|
||||
.printLn(`return this;`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`write: function (requestMessage) {`)
|
||||
.indent().printLn(`client.send(requestMessage);`)
|
||||
.printLn(`return this;`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`end: function () {`)
|
||||
.indent().printLn(`client.finishSend();`)
|
||||
.dedent().printLn(`},`)
|
||||
.printLn(`cancel: function () {`)
|
||||
.indent().printLn(`listeners = null;`)
|
||||
.printLn(`client.close();`)
|
||||
.dedent().printLn(`}`)
|
||||
.dedent().printLn(`};`)
|
||||
.dedent().printLn(`};`);
|
||||
}
|
||||
|
||||
function printServiceStubTypes(methodPrinter: Printer, service: RPCDescriptor) {
|
||||
const printer = new CodePrinter(0, methodPrinter);
|
||||
|
||||
printer
|
||||
.printLn(`export class ${service.name}Client {`)
|
||||
.indent().printLn(`readonly serviceHost: string;`)
|
||||
.printEmptyLn()
|
||||
.printLn(`constructor(serviceHost: string, options?: grpc.RpcOptions);`);
|
||||
|
||||
service.methods.forEach((method: RPCMethodDescriptor) => {
|
||||
if (method.requestStream && method.responseStream) {
|
||||
printBidirectionalStubMethodTypes(printer, method);
|
||||
} else if (method.requestStream) {
|
||||
printClientStreamStubMethodTypes(printer, method);
|
||||
} else if (method.responseStream) {
|
||||
printServerStreamStubMethodTypes(printer, method);
|
||||
} else {
|
||||
printUnaryStubMethodTypes(printer, method);
|
||||
}
|
||||
});
|
||||
printer.dedent().printLn("}");
|
||||
}
|
||||
|
||||
function printUnaryStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer
|
||||
.printLn(`${method.nameAsCamelCase}(`)
|
||||
.indent().printLn(`requestMessage: ${method.requestType},`)
|
||||
.printLn(`metadata: grpc.Metadata,`)
|
||||
.printLn(`callback: (error: ServiceError|null, responseMessage: ${method.responseType}|null) => void`)
|
||||
.dedent().printLn(`): UnaryResponse;`)
|
||||
.printLn(`${method.nameAsCamelCase}(`)
|
||||
.indent().printLn(`requestMessage: ${method.requestType},`)
|
||||
.printLn(`callback: (error: ServiceError|null, responseMessage: ${method.responseType}|null) => void`)
|
||||
.dedent().printLn(`): UnaryResponse;`);
|
||||
}
|
||||
|
||||
function printServerStreamStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer.printLn(`${method.nameAsCamelCase}(requestMessage: ${method.requestType}, metadata?: grpc.Metadata): ResponseStream<${method.responseType}>;`);
|
||||
}
|
||||
|
||||
function printClientStreamStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer.printLn(`${method.nameAsCamelCase}(metadata?: grpc.Metadata): RequestStream<${method.requestType}>;`);
|
||||
}
|
||||
|
||||
function printBidirectionalStubMethodTypes(printer: CodePrinter, method: RPCMethodDescriptor) {
|
||||
printer.printLn(`${method.nameAsCamelCase}(metadata?: grpc.Metadata): BidirectionalStream<${method.requestType}, ${method.responseType}>;`);
|
||||
}
|
59
src/ts-protoc-gen/ts/FieldTypes.ts
Normal file
59
src/ts-protoc-gen/ts/FieldTypes.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {filePathToPseudoNamespace, withinNamespaceFromExportEntry} from "../util";
|
||||
import {ExportMap} from "../ExportMap";
|
||||
import {FieldDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
|
||||
export const MESSAGE_TYPE = 11;
|
||||
export const BYTES_TYPE = 12;
|
||||
export const ENUM_TYPE = 14;
|
||||
|
||||
const TypeNumToTypeString: {[key: number]: string} = {};
|
||||
TypeNumToTypeString[1] = "number"; // TYPE_DOUBLE
|
||||
TypeNumToTypeString[2] = "number"; // TYPE_FLOAT
|
||||
TypeNumToTypeString[3] = "number"; // TYPE_INT64
|
||||
TypeNumToTypeString[4] = "number"; // TYPE_UINT64
|
||||
TypeNumToTypeString[5] = "number"; // TYPE_INT32
|
||||
TypeNumToTypeString[6] = "number"; // TYPE_FIXED64
|
||||
TypeNumToTypeString[7] = "number"; // TYPE_FIXED32
|
||||
TypeNumToTypeString[8] = "boolean"; // TYPE_BOOL
|
||||
TypeNumToTypeString[9] = "string"; // TYPE_STRING
|
||||
TypeNumToTypeString[10] = "Object"; // TYPE_GROUP
|
||||
TypeNumToTypeString[MESSAGE_TYPE] = "Object"; // TYPE_MESSAGE - Length-delimited aggregate.
|
||||
TypeNumToTypeString[BYTES_TYPE] = "Uint8Array"; // TYPE_BYTES
|
||||
TypeNumToTypeString[13] = "number"; // TYPE_UINT32
|
||||
TypeNumToTypeString[ENUM_TYPE] = "number"; // TYPE_ENUM
|
||||
TypeNumToTypeString[15] = "number"; // TYPE_SFIXED32
|
||||
TypeNumToTypeString[16] = "number"; // TYPE_SFIXED64
|
||||
TypeNumToTypeString[17] = "number"; // TYPE_SINT32 - Uses ZigZag encoding.
|
||||
TypeNumToTypeString[18] = "number"; // TYPE_SINT64 - Uses ZigZag encoding.
|
||||
|
||||
export function getTypeName(fieldTypeNum: number): string {
|
||||
return TypeNumToTypeString[fieldTypeNum];
|
||||
}
|
||||
|
||||
export function getFieldType(type: FieldDescriptorProto.Type, typeName: string, currentFileName: string, exportMap: ExportMap): string {
|
||||
if (type === MESSAGE_TYPE) {
|
||||
const fromExport = exportMap.getMessage(typeName);
|
||||
if (!fromExport) {
|
||||
throw new Error("Could not getFieldType for message: " + typeName);
|
||||
}
|
||||
const withinNamespace = withinNamespaceFromExportEntry(typeName, fromExport);
|
||||
if (fromExport.fileName === currentFileName) {
|
||||
return withinNamespace;
|
||||
} else {
|
||||
return filePathToPseudoNamespace(fromExport.fileName) + "." + withinNamespace;
|
||||
}
|
||||
} else if (type === ENUM_TYPE) {
|
||||
const fromExport = exportMap.getEnum(typeName);
|
||||
if (!fromExport) {
|
||||
throw new Error("Could not getFieldType for enum: " + typeName);
|
||||
}
|
||||
const withinNamespace = withinNamespaceFromExportEntry(typeName, fromExport);
|
||||
if (fromExport.fileName === currentFileName) {
|
||||
return `${withinNamespace}Map`;
|
||||
} else {
|
||||
return filePathToPseudoNamespace(fromExport.fileName) + "." + withinNamespace;
|
||||
}
|
||||
} else {
|
||||
return TypeNumToTypeString[type];
|
||||
}
|
||||
}
|
16
src/ts-protoc-gen/ts/enum.ts
Normal file
16
src/ts-protoc-gen/ts/enum.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {EnumDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {Printer} from "../Printer";
|
||||
|
||||
export function printEnum(enumDescriptor: EnumDescriptorProto, indentLevel: number) {
|
||||
const printer = new Printer(indentLevel);
|
||||
const enumInterfaceName = `${enumDescriptor.getName()}Map`;
|
||||
printer.printEmptyLn();
|
||||
printer.printLn(`export interface ${enumInterfaceName} {`);
|
||||
enumDescriptor.getValueList().forEach(value => {
|
||||
printer.printIndentedLn(`${value.getName().toUpperCase()}: ${value.getNumber()};`);
|
||||
});
|
||||
printer.printLn(`}`);
|
||||
printer.printEmptyLn();
|
||||
printer.printLn(`export const ${enumDescriptor.getName()}: ${enumInterfaceName};`);
|
||||
return printer.getOutput();
|
||||
}
|
14
src/ts-protoc-gen/ts/extensions.ts
Normal file
14
src/ts-protoc-gen/ts/extensions.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {Printer} from "../Printer";
|
||||
import {ExportMap} from "../ExportMap";
|
||||
import {FieldDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {snakeToCamel} from "../util";
|
||||
import {getFieldType} from "./FieldTypes";
|
||||
|
||||
export function printExtension(fileName: string, exportMap: ExportMap, extension: FieldDescriptorProto, indentLevel: number): string {
|
||||
const printer = new Printer(indentLevel + 1);
|
||||
printer.printEmptyLn();
|
||||
const extensionName = snakeToCamel(extension.getName());
|
||||
const fieldType = getFieldType(extension.getType(), extension.getTypeName().slice(1), fileName, exportMap);
|
||||
printer.printLn(`export const ${extensionName}: jspb.ExtensionFieldInfo<${fieldType}>;`);
|
||||
return printer.output;
|
||||
}
|
49
src/ts-protoc-gen/ts/fileDescriptorTSD.ts
Normal file
49
src/ts-protoc-gen/ts/fileDescriptorTSD.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import {filePathToPseudoNamespace, replaceProtoSuffix, getPathToRoot} from "../util";
|
||||
import {ExportMap} from "../ExportMap";
|
||||
import {Printer} from "../Printer";
|
||||
import {FileDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {WellKnownTypesMap} from "../WellKnown";
|
||||
import {printMessage} from "./message";
|
||||
import {printEnum} from "./enum";
|
||||
import {printExtension} from "./extensions";
|
||||
|
||||
export function printFileDescriptorTSD(fileDescriptor: FileDescriptorProto, exportMap: ExportMap) {
|
||||
const fileName = fileDescriptor.getName();
|
||||
const packageName = fileDescriptor.getPackage();
|
||||
|
||||
const printer = new Printer(0);
|
||||
|
||||
printer.printLn(`// package: ${packageName}`);
|
||||
printer.printLn(`// file: ${fileDescriptor.getName()}`);
|
||||
|
||||
const upToRoot = getPathToRoot(fileName);
|
||||
|
||||
printer.printEmptyLn();
|
||||
printer.printLn(`import * as jspb from "google-protobuf";`);
|
||||
|
||||
fileDescriptor.getDependencyList().forEach((dependency: string) => {
|
||||
const pseudoNamespace = filePathToPseudoNamespace(dependency);
|
||||
if (dependency in WellKnownTypesMap) {
|
||||
printer.printLn(`import * as ${pseudoNamespace} from "${WellKnownTypesMap[dependency]}";`);
|
||||
} else {
|
||||
const filePath = replaceProtoSuffix(dependency);
|
||||
printer.printLn(`import * as ${pseudoNamespace} from "${upToRoot}${filePath}";`);
|
||||
}
|
||||
});
|
||||
|
||||
fileDescriptor.getMessageTypeList().forEach(enumType => {
|
||||
printer.print(printMessage(fileName, exportMap, enumType, 0, fileDescriptor));
|
||||
});
|
||||
|
||||
fileDescriptor.getExtensionList().forEach(extension => {
|
||||
printer.print(printExtension(fileName, exportMap, extension, 0));
|
||||
});
|
||||
|
||||
fileDescriptor.getEnumTypeList().forEach(enumType => {
|
||||
printer.print(printEnum(enumType, 0));
|
||||
});
|
||||
|
||||
printer.printEmptyLn();
|
||||
|
||||
return printer.getOutput();
|
||||
}
|
235
src/ts-protoc-gen/ts/message.ts
Normal file
235
src/ts-protoc-gen/ts/message.ts
Normal file
|
@ -0,0 +1,235 @@
|
|||
import {
|
||||
filePathToPseudoNamespace, snakeToCamel, uppercaseFirst, oneOfName, isProto2,
|
||||
withinNamespaceFromExportEntry, normaliseFieldObjectName, stripPrefix
|
||||
} from "../util";
|
||||
import {ExportMap} from "../ExportMap";
|
||||
import {
|
||||
FieldDescriptorProto, FileDescriptorProto, DescriptorProto,
|
||||
FieldOptions
|
||||
} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {MESSAGE_TYPE, BYTES_TYPE, ENUM_TYPE, getFieldType, getTypeName} from "./FieldTypes";
|
||||
import {Printer} from "../Printer";
|
||||
import {printEnum} from "./enum";
|
||||
import {printOneOfDecl} from "./oneof";
|
||||
import {printExtension} from "./extensions";
|
||||
import JSType = FieldOptions.JSType;
|
||||
|
||||
function hasFieldPresence(field: FieldDescriptorProto, fileDescriptor: FileDescriptorProto): boolean {
|
||||
if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (field.hasOneofIndex()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.getType() === MESSAGE_TYPE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isProto2(fileDescriptor)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function printMessage(fileName: string, exportMap: ExportMap, messageDescriptor: DescriptorProto, indentLevel: number, fileDescriptor: FileDescriptorProto) {
|
||||
const messageName = messageDescriptor.getName();
|
||||
const messageOptions = messageDescriptor.getOptions();
|
||||
if (messageOptions !== undefined && messageOptions.getMapEntry()) {
|
||||
// this message type is the entry tuple for a map - don't output it
|
||||
return "";
|
||||
}
|
||||
|
||||
const objectTypeName = `AsObject`;
|
||||
const toObjectType = new Printer(indentLevel + 1);
|
||||
toObjectType.printLn(`export type ${objectTypeName} = {`);
|
||||
|
||||
const printer = new Printer(indentLevel);
|
||||
printer.printEmptyLn();
|
||||
printer.printLn(`export class ${messageName} extends jspb.Message {`);
|
||||
|
||||
const oneOfGroups: Array<Array<FieldDescriptorProto>> = [];
|
||||
|
||||
messageDescriptor.getFieldList().forEach(field => {
|
||||
if (field.hasOneofIndex()) {
|
||||
const oneOfIndex = field.getOneofIndex();
|
||||
let existing = oneOfGroups[oneOfIndex];
|
||||
if (existing === undefined) {
|
||||
existing = [];
|
||||
oneOfGroups[oneOfIndex] = existing;
|
||||
}
|
||||
existing.push(field);
|
||||
}
|
||||
const snakeCaseName = stripPrefix(field.getName().toLowerCase(), "_");
|
||||
const camelCaseName = snakeToCamel(snakeCaseName);
|
||||
const withUppercase = uppercaseFirst(camelCaseName);
|
||||
const type = field.getType();
|
||||
|
||||
let exportType;
|
||||
const fullTypeName = field.getTypeName().slice(1);
|
||||
if (type === MESSAGE_TYPE) {
|
||||
const fieldMessageType = exportMap.getMessage(fullTypeName);
|
||||
if (fieldMessageType === undefined) {
|
||||
throw new Error("No message export for: " + fullTypeName);
|
||||
}
|
||||
if (fieldMessageType.messageOptions !== undefined && fieldMessageType.messageOptions.getMapEntry()) {
|
||||
// This field is a map
|
||||
const keyTuple = fieldMessageType.mapFieldOptions!.key;
|
||||
const keyType = keyTuple[0];
|
||||
const keyTypeName = getFieldType(keyType, keyTuple[1], fileName, exportMap);
|
||||
const valueTuple = fieldMessageType.mapFieldOptions!.value;
|
||||
const valueType = valueTuple[0];
|
||||
let valueTypeName = getFieldType(valueType, valueTuple[1], fileName, exportMap);
|
||||
if (valueType === BYTES_TYPE) {
|
||||
valueTypeName = "Uint8Array | string";
|
||||
}
|
||||
if (valueType === ENUM_TYPE) {
|
||||
valueTypeName = `${valueTypeName}[keyof ${valueTypeName}]`;
|
||||
}
|
||||
printer.printIndentedLn(`get${withUppercase}Map(): jspb.Map<${keyTypeName}, ${valueTypeName}>;`);
|
||||
printer.printIndentedLn(`clear${withUppercase}Map(): void;`);
|
||||
toObjectType.printIndentedLn(`${camelCaseName}Map: Array<[${keyTypeName}${keyType === MESSAGE_TYPE ? ".AsObject" : ""}, ${valueTypeName}${valueType === MESSAGE_TYPE ? ".AsObject" : ""}]>,`);
|
||||
return;
|
||||
}
|
||||
const withinNamespace = withinNamespaceFromExportEntry(fullTypeName, fieldMessageType);
|
||||
if (fieldMessageType.fileName === fileName) {
|
||||
exportType = withinNamespace;
|
||||
} else {
|
||||
exportType = filePathToPseudoNamespace(fieldMessageType.fileName) + "." + withinNamespace;
|
||||
}
|
||||
} else if (type === ENUM_TYPE) {
|
||||
const fieldEnumType = exportMap.getEnum(fullTypeName);
|
||||
if (fieldEnumType === undefined) {
|
||||
throw new Error("No enum export for: " + fullTypeName);
|
||||
}
|
||||
const withinNamespace = withinNamespaceFromExportEntry(fullTypeName, fieldEnumType);
|
||||
if (fieldEnumType.fileName === fileName) {
|
||||
exportType = withinNamespace;
|
||||
} else {
|
||||
exportType = filePathToPseudoNamespace(fieldEnumType.fileName) + "." + withinNamespace;
|
||||
}
|
||||
exportType = `${exportType}Map[keyof ${exportType}Map]`;
|
||||
} else {
|
||||
if (field.getOptions() && field.getOptions().hasJstype()) {
|
||||
switch (field.getOptions().getJstype()) {
|
||||
case JSType.JS_NUMBER:
|
||||
exportType = "number";
|
||||
break;
|
||||
case JSType.JS_STRING:
|
||||
exportType = "string";
|
||||
break;
|
||||
default:
|
||||
exportType = getTypeName(type);
|
||||
}
|
||||
} else {
|
||||
exportType = getTypeName(type);
|
||||
}
|
||||
}
|
||||
|
||||
let hasClearMethod = false;
|
||||
function printClearIfNotPresent() {
|
||||
if (!hasClearMethod) {
|
||||
hasClearMethod = true;
|
||||
printer.printIndentedLn(`clear${withUppercase}${field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED ? "List" : ""}(): void;`);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFieldPresence(field, fileDescriptor)) {
|
||||
printer.printIndentedLn(`has${withUppercase}(): boolean;`);
|
||||
printClearIfNotPresent();
|
||||
}
|
||||
|
||||
function printRepeatedAddMethod(valueType: string) {
|
||||
const optionalValue = field.getType() === MESSAGE_TYPE;
|
||||
printer.printIndentedLn(`add${withUppercase}(value${optionalValue ? "?" : ""}: ${valueType}, index?: number): ${valueType};`);
|
||||
}
|
||||
|
||||
if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) {// is repeated
|
||||
printClearIfNotPresent();
|
||||
if (type === BYTES_TYPE) {
|
||||
toObjectType.printIndentedLn(`${camelCaseName}List: Array<Uint8Array | string>,`);
|
||||
printer.printIndentedLn(`get${withUppercase}List(): Array<Uint8Array | string>;`);
|
||||
printer.printIndentedLn(`get${withUppercase}List_asU8(): Array<Uint8Array>;`);
|
||||
printer.printIndentedLn(`get${withUppercase}List_asB64(): Array<string>;`);
|
||||
printer.printIndentedLn(`set${withUppercase}List(value: Array<Uint8Array | string>): void;`);
|
||||
printRepeatedAddMethod("Uint8Array | string");
|
||||
} else {
|
||||
toObjectType.printIndentedLn(`${camelCaseName}List: Array<${exportType}${type === MESSAGE_TYPE ? ".AsObject" : ""}>,`);
|
||||
printer.printIndentedLn(`get${withUppercase}List(): Array<${exportType}>;`);
|
||||
printer.printIndentedLn(`set${withUppercase}List(value: Array<${exportType}>): void;`);
|
||||
printRepeatedAddMethod(exportType);
|
||||
}
|
||||
} else {
|
||||
if (type === BYTES_TYPE) {
|
||||
toObjectType.printIndentedLn(`${camelCaseName}: Uint8Array | string,`);
|
||||
printer.printIndentedLn(`get${withUppercase}(): Uint8Array | string;`);
|
||||
printer.printIndentedLn(`get${withUppercase}_asU8(): Uint8Array;`);
|
||||
printer.printIndentedLn(`get${withUppercase}_asB64(): string;`);
|
||||
printer.printIndentedLn(`set${withUppercase}(value: Uint8Array | string): void;`);
|
||||
} else {
|
||||
let fieldObjectType = exportType;
|
||||
let canBeUndefined = false;
|
||||
if (type === MESSAGE_TYPE) {
|
||||
fieldObjectType += ".AsObject";
|
||||
if (!isProto2(fileDescriptor) || (field.getLabel() === FieldDescriptorProto.Label.LABEL_OPTIONAL)) {
|
||||
canBeUndefined = true;
|
||||
}
|
||||
} else {
|
||||
if (isProto2(fileDescriptor)) {
|
||||
canBeUndefined = true;
|
||||
}
|
||||
}
|
||||
const fieldObjectName = normaliseFieldObjectName(camelCaseName);
|
||||
toObjectType.printIndentedLn(`${fieldObjectName}${canBeUndefined ? "?" : ""}: ${fieldObjectType},`);
|
||||
printer.printIndentedLn(`get${withUppercase}(): ${exportType}${canBeUndefined ? " | undefined" : ""};`);
|
||||
printer.printIndentedLn(`set${withUppercase}(value${type === MESSAGE_TYPE ? "?" : ""}: ${exportType}): void;`);
|
||||
}
|
||||
}
|
||||
printer.printEmptyLn();
|
||||
});
|
||||
|
||||
toObjectType.printLn(`}`);
|
||||
|
||||
messageDescriptor.getOneofDeclList().forEach(oneOfDecl => {
|
||||
printer.printIndentedLn(`get${oneOfName(oneOfDecl.getName())}Case(): ${messageName}.${oneOfName(oneOfDecl.getName())}Case;`);
|
||||
});
|
||||
|
||||
printer.printIndentedLn(`serializeBinary(): Uint8Array;`);
|
||||
printer.printIndentedLn(`toObject(includeInstance?: boolean): ${messageName}.${objectTypeName};`);
|
||||
printer.printIndentedLn(`static toObject(includeInstance: boolean, msg: ${messageName}): ${messageName}.${objectTypeName};`);
|
||||
printer.printIndentedLn(`static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};`);
|
||||
printer.printIndentedLn(`static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};`);
|
||||
printer.printIndentedLn(`static serializeBinaryToWriter(message: ${messageName}, writer: jspb.BinaryWriter): void;`);
|
||||
printer.printIndentedLn(`static deserializeBinary(bytes: Uint8Array): ${messageName};`);
|
||||
printer.printIndentedLn(`static deserializeBinaryFromReader(message: ${messageName}, reader: jspb.BinaryReader): ${messageName};`);
|
||||
|
||||
printer.printLn(`}`);
|
||||
printer.printEmptyLn();
|
||||
|
||||
printer.printLn(`export namespace ${messageName} {`);
|
||||
|
||||
printer.print(toObjectType.getOutput());
|
||||
|
||||
messageDescriptor.getNestedTypeList().forEach(nested => {
|
||||
const msgOutput = printMessage(fileName, exportMap, nested, indentLevel + 1, fileDescriptor);
|
||||
if (msgOutput !== "") {
|
||||
// If the message class is a Map entry then it isn't output, so don't print the namespace block
|
||||
printer.print(msgOutput);
|
||||
}
|
||||
});
|
||||
messageDescriptor.getEnumTypeList().forEach(enumType => {
|
||||
printer.print(`${printEnum(enumType, indentLevel + 1)}`);
|
||||
});
|
||||
messageDescriptor.getOneofDeclList().forEach((oneOfDecl, index) => {
|
||||
printer.print(`${printOneOfDecl(oneOfDecl, oneOfGroups[index] || [], indentLevel + 1)}`);
|
||||
});
|
||||
messageDescriptor.getExtensionList().forEach(extension => {
|
||||
printer.print(printExtension(fileName, exportMap, extension, indentLevel + 1));
|
||||
});
|
||||
|
||||
printer.printLn(`}`);
|
||||
|
||||
return printer.getOutput();
|
||||
}
|
16
src/ts-protoc-gen/ts/oneof.ts
Normal file
16
src/ts-protoc-gen/ts/oneof.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {Printer} from "../Printer";
|
||||
import {OneofDescriptorProto, FieldDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {oneOfName} from "../util";
|
||||
|
||||
export function printOneOfDecl(oneOfDecl: OneofDescriptorProto, oneOfFields: Array<FieldDescriptorProto>, indentLevel: number) {
|
||||
const printer = new Printer(indentLevel);
|
||||
printer.printEmptyLn();
|
||||
printer.printLn(`export enum ${oneOfName(oneOfDecl.getName())}Case {`);
|
||||
printer.printIndentedLn(`${oneOfDecl.getName().toUpperCase()}_NOT_SET = 0,`);
|
||||
oneOfFields.forEach(field => {
|
||||
printer.printIndentedLn(`${field.getName().toUpperCase()} = ${field.getNumber()},`);
|
||||
});
|
||||
printer.printLn("}");
|
||||
|
||||
return printer.output;
|
||||
}
|
184
src/ts-protoc-gen/util.ts
Normal file
184
src/ts-protoc-gen/util.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
import {parse} from "querystring";
|
||||
import {FileDescriptorProto} from "google-protobuf/google/protobuf/descriptor_pb";
|
||||
import {ExportEnumEntry, ExportMessageEntry} from "./ExportMap";
|
||||
import {ServiceParameter, ModeParameter} from "./parameters";
|
||||
export function filePathToPseudoNamespace(filePath: string): string {
|
||||
return filePath.replace(".proto", "").replace(/\//g, "_").replace(/\./g, "_").replace(/\-/g, "_") + "_pb";
|
||||
}
|
||||
|
||||
export function stripPrefix(str: string, prefix: string) {
|
||||
if (str.substr(0, prefix.length) === prefix) {
|
||||
return str.substr(prefix.length);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function snakeToCamel(str: string): string {
|
||||
return str.replace(/(\_\w)/g, function(m) {
|
||||
return m[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export function uppercaseFirst(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
const PROTO2_SYNTAX = "proto2";
|
||||
export function isProto2(fileDescriptor: FileDescriptorProto): boolean {
|
||||
// Empty syntax defaults to proto2
|
||||
return (fileDescriptor.getSyntax() === "" || fileDescriptor.getSyntax() === PROTO2_SYNTAX);
|
||||
}
|
||||
|
||||
export function oneOfName(name: string) {
|
||||
return uppercaseFirst(snakeToCamel(name.toLowerCase()));
|
||||
}
|
||||
|
||||
export function generateIndent(indentLevel: number): string {
|
||||
let indent = "";
|
||||
for (let i = 0; i < indentLevel; i++) {
|
||||
indent += " ";
|
||||
}
|
||||
return indent;
|
||||
}
|
||||
|
||||
export function getPathToRoot(fileName: string) {
|
||||
const depth = fileName.split("/").length;
|
||||
return depth === 1 ? "./" : new Array(depth).join("../");
|
||||
}
|
||||
|
||||
export function withinNamespaceFromExportEntry(name: string, exportEntry: ExportMessageEntry | ExportEnumEntry) {
|
||||
return exportEntry.pkg ? name.substring(exportEntry.pkg.length + 1) : name;
|
||||
}
|
||||
|
||||
export function replaceProtoSuffix(protoFilePath: string): string {
|
||||
const suffix = ".proto";
|
||||
const hasProtoSuffix = protoFilePath.slice(protoFilePath.length - suffix.length) === suffix;
|
||||
return hasProtoSuffix
|
||||
? protoFilePath.slice(0, -suffix.length) + "_pb"
|
||||
: protoFilePath;
|
||||
}
|
||||
|
||||
export function withAllStdIn(callback: (buffer: Buffer) => void): void {
|
||||
const ret: Buffer[] = [];
|
||||
let len = 0;
|
||||
|
||||
const stdin = process.stdin;
|
||||
stdin.on("readable", function () {
|
||||
let chunk;
|
||||
|
||||
while ((chunk = stdin.read())) {
|
||||
if (!(chunk instanceof Buffer)) throw new Error("Did not receive buffer");
|
||||
ret.push(chunk);
|
||||
len += chunk.length;
|
||||
}
|
||||
});
|
||||
|
||||
stdin.on("end", function () {
|
||||
callback(Buffer.concat(ret, len));
|
||||
});
|
||||
}
|
||||
|
||||
// normaliseFieldObjectName modifies the field name that appears in the `asObject` representation
|
||||
// to match the logic found in `protobuf/compiler/js/js_generator.cc`. See: https://goo.gl/tX1dPQ
|
||||
export function normaliseFieldObjectName(name: string): string {
|
||||
switch (name) {
|
||||
case "abstract":
|
||||
case "boolean":
|
||||
case "break":
|
||||
case "byte":
|
||||
case "case":
|
||||
case "catch":
|
||||
case "char":
|
||||
case "class":
|
||||
case "const":
|
||||
case "continue":
|
||||
case "debugger":
|
||||
case "default":
|
||||
case "delete":
|
||||
case "do":
|
||||
case "double":
|
||||
case "else":
|
||||
case "enum":
|
||||
case "export":
|
||||
case "extends":
|
||||
case "false":
|
||||
case "final":
|
||||
case "finally":
|
||||
case "float":
|
||||
case "for":
|
||||
case "function":
|
||||
case "goto":
|
||||
case "if":
|
||||
case "implements":
|
||||
case "import":
|
||||
case "in":
|
||||
case "instanceof":
|
||||
case "int":
|
||||
case "interface":
|
||||
case "long":
|
||||
case "native":
|
||||
case "new":
|
||||
case "null":
|
||||
case "package":
|
||||
case "private":
|
||||
case "protected":
|
||||
case "public":
|
||||
case "return":
|
||||
case "short":
|
||||
case "static":
|
||||
case "super":
|
||||
case "switch":
|
||||
case "synchronized":
|
||||
case "this":
|
||||
case "throw":
|
||||
case "throws":
|
||||
case "transient":
|
||||
case "try":
|
||||
case "typeof":
|
||||
case "var":
|
||||
case "void":
|
||||
case "volatile":
|
||||
case "while":
|
||||
case "with":
|
||||
return `pb_${name}`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function getServiceParameter(service?: string): ServiceParameter {
|
||||
switch (service) {
|
||||
case "true":
|
||||
console.warn("protoc-gen-ts warning: The service=true parameter has been deprecated. Use service=grpc-web instead.");
|
||||
return ServiceParameter.GrpcWeb;
|
||||
case "grpc-web":
|
||||
return ServiceParameter.GrpcWeb;
|
||||
case "grpc-node":
|
||||
return ServiceParameter.GrpcNode;
|
||||
case undefined:
|
||||
return ServiceParameter.None;
|
||||
default:
|
||||
throw new Error(`Unrecognised service parameter: ${service}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getModeParameter(mode?: string): ModeParameter {
|
||||
switch (mode) {
|
||||
case "grpc-js":
|
||||
return ModeParameter.GrpcJs;
|
||||
case undefined:
|
||||
return ModeParameter.None;
|
||||
default:
|
||||
throw new Error(`Unrecognised mode parameter: ${mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getParameterEnums(parameter: string): {
|
||||
service: ServiceParameter,
|
||||
mode: ModeParameter
|
||||
} {
|
||||
const {service, mode} = parse(parameter, ",");
|
||||
return {
|
||||
service: getServiceParameter(service),
|
||||
mode: getModeParameter(mode)
|
||||
};
|
||||
}
|
Loading…
Add table
Reference in a new issue