Move rules_typescript to protobufjs 6.8.

protobufjs 6 is a full, API incompatible rewrite. This change updates any proto use,
and also rewrite the partial message reading logic.

PiperOrigin-RevId: 240776103
diff --git a/internal/BUILD.bazel b/internal/BUILD.bazel
index 8b42aa8..375a5ce 100644
--- a/internal/BUILD.bazel
+++ b/internal/BUILD.bazel
@@ -67,6 +67,7 @@
     # Workaround for https://github.com/Microsoft/TypeScript/issues/22208
     deps = [
         "@npm//@types/node",
+        "@npm//protobufjs",
         "@npm//tsickle",
         "@npm//tsutils",
         "@npm//typescript",
diff --git a/internal/tsc_wrapped/worker.ts b/internal/tsc_wrapped/worker.ts
index 7e52ba8..2998318 100644
--- a/internal/tsc_wrapped/worker.ts
+++ b/internal/tsc_wrapped/worker.ts
@@ -1,20 +1,24 @@
 import * as path from 'path';
-/* tslint:disable:no-require-imports */
-const protobufjs = require('protobufjs');
-// tslint:disable-next-line:variable-name: ByteBuffer is instantiatable.
-const ByteBuffer = require('bytebuffer');
+import * as protobufjs from 'protobufjs';
 
 // Equivalent of running node with --expose-gc
 // but easier to write tooling since we don't need to inject that arg to
 // nodejs_binary
 if (typeof global.gc !== 'function') {
+  // tslint:disable-next-line:no-require-imports
   require('v8').setFlagsFromString('--expose_gc');
+  // tslint:disable-next-line:no-require-imports
   global.gc = require('vm').runInNewContext('gc');
 }
 
+/**
+ * Whether to print debug messages (to console.error) from the debug function
+ * below.
+ */
 export const DEBUG = false;
 
-export function debug(...args: Array<{}>) {
+/** Maybe print a debug message (depending on a flag defaulting to false). */
+export function debug(...args: Array<unknown>) {
   if (DEBUG) console.error.apply(console, args);
 }
 
@@ -26,14 +30,52 @@
   console.error.apply(console, args);
 }
 
+/**
+ * runAsWorker returns true if the given arguments indicate the process should
+ * run as a persistent worker.
+ */
 export function runAsWorker(args: string[]) {
   return args.indexOf('--persistent_worker') !== -1;
 }
 
-const workerpb = (function loadWorkerPb() {
-  const protoPath = '../worker_protocol.proto';
+/**
+ * workerProto declares the static type of the object constructed at runtime by
+ * protobufjs, based on reading the protocol buffer definition.
+ */
+declare namespace workerProto {
+  /** Input represents the blaze.worker.Input message. */
+  interface Input extends protobufjs.Message<Input> {
+    path: string;
+    /**
+     * In Node, digest is a Buffer. In the browser, it's a replacement
+     * implementation. We only care about its toString(encoding) method.
+     */
+    digest: {toString(encoding: string): string};
+  }
 
-  // Use node module resolution so we can find the .proto file in any of the root dirs
+  /** WorkRequest repesents the blaze.worker.WorkRequest message. */
+  interface WorkRequest extends protobufjs.Message<WorkRequest> {
+    arguments: string[];
+    inputs: Input[];
+  }
+
+  // tslint:disable:variable-name reflected, constructable types.
+  const WorkRequest: protobufjs.Type;
+  const WorkResponse: protobufjs.Type;
+  // tslint:enable:variable-name
+}
+
+/**
+ * loadWorkerPb finds and loads the protocol buffer definition for bazel's
+ * worker protocol using protobufjs. In protobufjs, this means it's a reflection
+ * object that also contains properties for the individual messages.
+ */
+function loadWorkerPb() {
+  const protoPath =
+      '../worker_protocol.proto';
+
+  // Use node module resolution so we can find the .proto file in any of the
+  // root dirs
   let protofile;
   try {
     // Look for the .proto file relative in its @bazel/typescript npm package
@@ -50,35 +92,32 @@
         '../../third_party/github.com/bazelbuild/bazel/src/main/protobuf/worker_protocol.proto');
   }
 
-  // Under Bazel, we use the version of TypeScript installed in the user's
-  // workspace This means we also use their version of protobuf.js. Handle both.
-  // v5 and v6 by checking which one is present.
-  if (protobufjs.loadProtoFile) {
-    // Protobuf.js v5
-    const protoNamespace = protobufjs.loadProtoFile(protofile);
-    if (!protoNamespace) {
-      throw new Error('Cannot find ' + path.resolve(protoPath));
-    }
-    return protoNamespace.build('blaze.worker');
-  } else {
-    // Protobuf.js v6
-    const protoNamespace = protobufjs.loadSync(protofile);
-    if (!protoNamespace) {
-      throw new Error('Cannot find ' + path.resolve(protoPath));
-    }
-    return protoNamespace.lookup('blaze.worker');
+  const protoNamespace = protobufjs.loadSync(protofile);
+  if (!protoNamespace) {
+    throw new Error('Cannot find ' + path.resolve(protoPath));
   }
-})();
-
-interface Input {
-  getPath(): string;
-  getDigest(): {toString(encoding: string): string};  // npm:ByteBuffer
-}
-interface WorkRequest {
-  getArguments(): string[];
-  getInputs(): Input[];
+  const workerpb = protoNamespace.lookup('blaze.worker');
+  if (!workerpb) {
+    throw new Error(`Cannot find namespace blaze.worker`);
+  }
+  return workerpb as protobufjs.ReflectionObject & typeof workerProto;
 }
 
+/**
+ * workerpb contains the runtime representation of the worker protocol buffer,
+ * including accessor for the defined messages.
+ */
+const workerpb = loadWorkerPb();
+
+/**
+ * runWorkerLoop handles the interacton between bazel workers and the
+ * TypeScript compiler. It reads compilation requests from stdin, unmarshals the
+ * data, and dispatches into `runOneBuild` for the actual compilation to happen.
+ *
+ * The compilation handler is parameterized so that this code can be used by
+ * different compiler entry points (currently TypeScript compilation and Angular
+ * compilation).
+ */
 export function runWorkerLoop(
     runOneBuild: (args: string[], inputs?: {[path: string]: string}) =>
         boolean) {
@@ -88,39 +127,54 @@
   // user as expected.
   let consoleOutput = '';
   process.stderr.write =
-      (chunk: string | Buffer, ...otherArgs: any[]): boolean => {
+      (chunk: string|Buffer, ...otherArgs: Array<unknown>): boolean => {
         consoleOutput += chunk.toString();
         return true;
       };
 
   // Accumulator for asynchronously read input.
-  // tslint:disable-next-line:no-any protobufjs is untyped
-  let buf: any;
+  // protobufjs uses node's Buffer, but has its own reader abstraction on top of
+  // it (for browser compatiblity). It ignores Buffer's builtin start and
+  // offset, which means the handling code below cannot use Buffer in a
+  // meaningful way (such as cycling data through it). The handler below reads
+  // any data available on stdin, concatenating it into this buffer. It then
+  // attempts to read a delimited Message from it. If a message is incomplete,
+  // it exits and waits for more input. If a message has been read, it strips
+  // its data of this buffer.
+  let buf: Buffer = Buffer.alloc(0);
   process.stdin.on('readable', () => {
-    const chunk = process.stdin.read();
+    const chunk = process.stdin.read() as Buffer;
     if (!chunk) return;
-
-    const wrapped = ByteBuffer.wrap(chunk);
-    buf = buf ? ByteBuffer.concat([buf, wrapped]) : wrapped;
+    buf = Buffer.concat([buf, chunk]);
     try {
-      let req: WorkRequest;
+      const reader = new protobufjs.Reader(buf);
       // Read all requests that have accumulated in the buffer.
-      while ((req = workerpb.WorkRequest.decodeDelimited(buf)) != null) {
+      while (reader.len - reader.pos > 0) {
+        const messageStart = reader.len;
+        const msgLength: number = reader.uint32();
+        // chunk might be an incomplete read from stdin. If there are not enough
+        // bytes for the next full message, wait for more input.
+        if ((reader.len - reader.pos) < msgLength) return;
+
+        const req = workerpb.WorkRequest.decode(reader, msgLength) as
+            workerProto.WorkRequest;
+        // Once a message has been read, remove it from buf so that if we pause
+        // to read more input, this message will not be processed again.
+        buf = buf.slice(messageStart);
         debug('=== Handling new build request');
         // Reset accumulated log output.
         consoleOutput = '';
-        const args = req.getArguments();
+        const args = req.arguments;
         const inputs: {[path: string]: string} = {};
-        for (const input of req.getInputs()) {
-          inputs[input.getPath()] = input.getDigest().toString('hex');
+        for (const input of req.inputs) {
+          inputs[input.path] = input.digest.toString('hex');
         }
         debug('Compiling with:\n\t' + args.join('\n\t'));
         const exitCode = runOneBuild(args, inputs) ? 0 : 1;
-        process.stdout.write(new workerpb.WorkResponse()
-                                 .setExitCode(exitCode)
-                                 .setOutput(consoleOutput)
-                                 .encodeDelimited()
-                                 .toBuffer());
+        process.stdout.write((workerpb.WorkResponse.encodeDelimited({
+                               exitCode,
+                               output: consoleOutput,
+                             })).finish() as Buffer);
         // Force a garbage collection pass.  This keeps our memory usage
         // consistent across multiple compilations, and allows the file
         // cache to use the current memory usage as a guideline for expiring
@@ -128,17 +182,19 @@
         // we want to gc only after all its locals have gone out of scope.
         global.gc();
       }
-      // Avoid growing the buffer indefinitely.
-      buf.compact();
+      // All messages have been handled, make sure the invariant holds and
+      // Buffer is empty once all messages have been read.
+      if (buf.length > 0) {
+        throw new Error('buffer not empty after reading all messages');
+      }
     } catch (e) {
       log('Compilation failed', e.stack);
-      process.stdout.write(new workerpb.WorkResponse()
-                               .setExitCode(1)
-                               .setOutput(consoleOutput)
-                               .encodeDelimited()
-                               .toBuffer());
+      process.stdout.write(
+          workerpb.WorkResponse
+              .encodeDelimited({exitCode: 1, output: consoleOutput})
+              .finish() as Buffer);
       // Clear buffer so the next build won't read an incomplete request.
-      buf = null;
+      buf = Buffer.alloc(0);
     }
   });
 }
diff --git a/package.json b/package.json
index dd1a029..b0579be 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
     "homepage": "https://github.com/bazelbuild/rules_typescript",
     "license": "Apache-2.0",
     "devDependencies": {
-        "protobufjs": "5.0.3",
+        "protobufjs": "6.8.8",
         "semver": "5.6.0",
         "source-map-support": "0.5.9",
         "tsutils": "2.27.2",