@protobuf-ts/plugin
Version:
The protocol buffer compiler plugin "protobuf-ts" generates TypeScript, gRPC-web, Twirp, and more.
238 lines (237 loc) • 13.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFileRegistryFromRequest = exports.ProtobuftsPlugin = void 0;
const protobuf_1 = require("@bufbuild/protobuf");
const wkt_1 = require("@bufbuild/protobuf/wkt");
const reflect_1 = require("@bufbuild/protobuf/reflect");
const typescript_compile_1 = require("./framework/typescript-compile");
const options_1 = require("./options");
const service_server_generator_grpc_1 = require("./code-gen/service-server-generator-grpc");
const comment_generator_1 = require("./code-gen/comment-generator");
const message_interface_generator_1 = require("./code-gen/message-interface-generator");
const message_type_generator_1 = require("./code-gen/message-type-generator");
const enum_generator_1 = require("./code-gen/enum-generator");
const service_type_generator_1 = require("./code-gen/service-type-generator");
const service_client_generator_generic_1 = require("./code-gen/service-client-generator-generic");
const file_table_1 = require("./file-table");
const service_server_generator_generic_1 = require("./code-gen/service-server-generator-generic");
const service_client_generator_grpc_1 = require("./code-gen/service-client-generator-grpc");
const ts = require("typescript");
const well_known_types_1 = require("./message-type-extensions/well-known-types");
const interpreter_1 = require("./interpreter");
const symbol_table_1 = require("./framework/symbol-table");
const typescript_imports_1 = require("./framework/typescript-imports");
const plugin_base_1 = require("./framework/plugin-base");
const protobuf_ts_pb_1 = require("./gen/protobuf-ts_pb");
class ProtobuftsPlugin extends plugin_base_1.PluginBaseProtobufES {
constructor(version) {
super();
this.version = version;
// we support proto3-optionals, so we let protoc know
this.getSupportedFeatures = () => [wkt_1.CodeGeneratorResponse_Feature.PROTO3_OPTIONAL];
this.version = version;
}
generate(request) {
const options = options_1.parseOptions(request.parameter, `by protobuf-ts ${this.version}` + (request.parameter ? ` with parameter ${request.parameter}` : '')), registry = createFileRegistryFromRequest(request), symbols = new symbol_table_1.SymbolTable(), fileTable = new file_table_1.FileTable(options), imports = new typescript_imports_1.TypeScriptImports(symbols, registry), comments = new comment_generator_1.CommentGenerator(), interpreter = new interpreter_1.Interpreter(registry, options), genMessageInterface = new message_interface_generator_1.MessageInterfaceGenerator(symbols, imports, comments, interpreter, options), genEnum = new enum_generator_1.EnumGenerator(symbols, imports, comments, interpreter), genMessageType = new message_type_generator_1.MessageTypeGenerator(registry, imports, comments, interpreter, options), genServiceType = new service_type_generator_1.ServiceTypeGenerator(symbols, imports, comments, interpreter, options), genServerGeneric = new service_server_generator_generic_1.ServiceServerGeneratorGeneric(symbols, imports, comments, interpreter, options), genServerGrpc = new service_server_generator_grpc_1.ServiceServerGeneratorGrpc(symbols, imports, comments, interpreter), genClientGeneric = new service_client_generator_generic_1.ServiceClientGeneratorGeneric(symbols, registry, imports, comments, interpreter, options), genClientGrpc = new service_client_generator_grpc_1.ServiceClientGeneratorGrpc(symbols, registry, imports, comments, interpreter, options);
// in first pass, register standard file names
for (let fileDescriptor of registry.files) {
const base = fileDescriptor.name + (options.addPbSuffix ? "_pb" : "");
fileTable.register(base + '.ts', fileDescriptor);
}
// in second pass, register client and server file names
for (let descFile of registry.files) {
const base = descFile.name + (options.addPbSuffix ? "_pb" : "");
fileTable.register(base + '.server.ts', descFile, 'generic-server');
fileTable.register(base + '.grpc-server.ts', descFile, 'grpc1-server');
fileTable.register(base + '.client.ts', descFile, 'client');
fileTable.register(base + '.promise-client.ts', descFile, 'promise-client');
fileTable.register(base + '.rx-client.ts', descFile, 'rx-client');
fileTable.register(base + '.grpc-client.ts', descFile, 'grpc1-client');
}
// in third pass, generate files
for (let descFile of registry.files) {
const outMain = fileTable.create(descFile), outServerGeneric = fileTable.create(descFile, 'generic-server'), outServerGrpc = fileTable.create(descFile, 'grpc1-server'), outClientCall = fileTable.create(descFile, 'client'),
// TODO
//outClientPromise = fileTable.create(descFile, 'promise-client'),
//outClientRx = fileTable.create(descFile, 'rx-client'),
outClientGrpc = fileTable.create(descFile, 'grpc1-client');
// in first pass over types, register all symbols, regardless whether they are going to be used
for (const desc of reflect_1.nestedTypes(descFile)) {
switch (desc.kind) {
case "enum":
genEnum.registerSymbols(outMain, desc);
break;
case "message":
genMessageInterface.registerSymbols(outMain, desc);
break;
case "service":
genServiceType.registerSymbols(outMain, desc);
genClientGeneric.registerSymbols(outClientCall, desc);
genClientGrpc.registerSymbols(outClientGrpc, desc);
genServerGeneric.registerSymbols(outServerGeneric, desc);
genServerGrpc.registerSymbols(outServerGrpc, desc);
break;
}
}
// in second pass over types, generate message interfaces and enums
for (const desc of reflect_1.nestedTypes(descFile)) {
switch (desc.kind) {
case "message":
genMessageInterface.generateMessageInterface(outMain, desc);
break;
case "enum":
genEnum.generateEnum(outMain, desc);
break;
}
}
// in third pass over types, generate message and service types
for (const desc of reflect_1.nestedTypes(descFile)) {
switch (desc.kind) {
case "message":
genMessageType.generateMessageType(outMain, desc, options.getOptimizeMode(descFile));
break;
case "service":
if (options.forceDisableServices) {
break;
}
// service type
genServiceType.generateServiceType(outMain, desc);
// clients
const clientStyles = options.getClientStyles(desc);
if (clientStyles.includes(protobuf_ts_pb_1.ClientStyle.GENERIC_CLIENT)) {
genClientGeneric.generateInterface(outClientCall, desc);
genClientGeneric.generateImplementationClass(outClientCall, desc);
}
if (clientStyles.includes(protobuf_ts_pb_1.ClientStyle.GRPC1_CLIENT)) {
genClientGrpc.generateInterface(outClientGrpc, desc);
genClientGrpc.generateImplementationClass(outClientGrpc, desc);
}
// servers
const serverStyles = options.getServerStyles(desc);
if (serverStyles.includes(protobuf_ts_pb_1.ServerStyle.GENERIC_SERVER)) {
genServerGeneric.generateInterface(outServerGeneric, desc);
}
if (serverStyles.includes(protobuf_ts_pb_1.ServerStyle.GRPC1_SERVER)) {
genServerGrpc.generateInterface(outServerGrpc, desc);
genServerGrpc.generateDefinition(outServerGrpc, desc);
}
break;
}
}
}
// plugins should only return files requested to generate
// unless our option "generate_dependencies" is set.
// We always return well-known types, because we do not
// maintain them in a package - they are always generated
// on demand.
let tsFiles = fileTable.outFiles.concat();
if (!options.generateDependencies) {
tsFiles = tsFiles.filter(file => {
const protoFilename = file.descFile.proto.name;
if (request.fileToGenerate.includes(protoFilename)) {
return true;
}
if (well_known_types_1.WellKnownTypes.protoFilenames.includes(protoFilename)) {
return true;
}
return false;
});
}
// if a proto file is imported to use custom options, or if a proto file declares custom options,
// we do not to emit it. unless it was explicitly requested.
// TODO why does the fallback condition include "used" files? isn't that what generateDependencies should do?
tsFiles = tsFiles.filter(of => request.fileToGenerate.includes(of.descFile.proto.name)
|| this.isFileUsed(of.descFile, tsFiles.map(x => x.descFile)));
return this.transpile(tsFiles, options);
}
transpile(tsFiles, options) {
if (options.transpileTarget === undefined) {
return tsFiles;
}
const opt = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
skipLibCheck: true,
declaration: true,
module: options.transpileModule,
target: options.transpileTarget,
};
const [program,] = typescript_compile_1.setupCompiler(opt, tsFiles, tsFiles.map(f => f.getFilename()));
const results = [];
let err;
program.emit(undefined, (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
// We have to go through some hoops here because the header we add to each file
// is not part of the AST. So we find the TypeScript file we generated for each
// emitted file and add the header to each output ourselves.
if (!sourceFiles) {
err = new Error(`unable to map emitted file "${fileName}" to a source file: missing source files`);
return;
}
if (sourceFiles.length !== 1) {
err = new Error(`unable to map emitted file "${fileName}" to a source file: expected 1 source file, got ${sourceFiles.length}`);
return;
}
const tsFile = tsFiles.find(x => sourceFiles[0].fileName === x.getFilename());
if (!tsFile) {
err = new Error(`unable to map emitted file "${fileName}" to a source file: not found`);
return;
}
const content = tsFile.getHeader() + data;
results.push({
getFilename() {
return fileName;
},
getContent() {
return content;
}
});
});
if (err) {
throw err;
}
return results;
}
isFileUsed(descFile, files) {
for (const type of reflect_1.nestedTypes(descFile)) {
// TODO do not consider referencing a type that a file defines itself used - filter the second argument to exclude the current file
if (this.isTypeUsed(type, files)) {
return true;
}
}
return false;
}
isTypeUsed(type, files) {
for (const otherFile of files) {
for (const otherType of reflect_1.nestedTypes(otherFile)) {
switch (otherType.kind) {
case "service":
if (type.kind == "message") {
if (otherType.methods.some(descMethod => descMethod.input.typeName == type.typeName)) {
return true;
}
if (otherType.methods.some(descMethod => descMethod.output.typeName == type.typeName)) {
return true;
}
}
break;
case "message":
if (otherType.fields.some(descField => { var _a; return ((_a = descField.message) === null || _a === void 0 ? void 0 : _a.typeName) === type.typeName; })) {
return true;
}
if (otherType.fields.some(descField => { var _a; return ((_a = descField.enum) === null || _a === void 0 ? void 0 : _a.typeName) === type.typeName; })) {
return true;
}
break;
}
}
}
return false;
}
}
exports.ProtobuftsPlugin = ProtobuftsPlugin;
function createFileRegistryFromRequest(request) {
const set = protobuf_1.create(wkt_1.FileDescriptorSetSchema, {
file: request.protoFile,
});
return protobuf_1.createFileRegistry(set);
}
exports.createFileRegistryFromRequest = createFileRegistryFromRequest;