UNPKG

@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
"use strict"; 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;