Internal change.

PiperOrigin-RevId: 242208600
diff --git a/internal/tsc_wrapped/compiler_host.ts b/internal/tsc_wrapped/compiler_host.ts
index e0b2d03..9008a08 100644
--- a/internal/tsc_wrapped/compiler_host.ts
+++ b/internal/tsc_wrapped/compiler_host.ts
@@ -42,13 +42,23 @@
 
   if (bazelOpts.compilationTargetSrc &&
       bazelOpts.compilationTargetSrc.length > 1) {
-    throw new Error("In JS transpilation mode, only one file can appear in " +
-                    "bazelOptions.compilationTargetSrc.");
+    throw new Error(
+        'In JS transpilation mode, only one file can appear in ' +
+        'bazelOptions.compilationTargetSrc.');
   }
 
-  if (!bazelOpts.transpiledJsOutputFileName) {
-    throw new Error("In JS transpilation mode, transpiledJsOutputFileName " +
-                    "must be specified in tsconfig.");
+  if (!bazelOpts.transpiledJsOutputFileName &&
+      !bazelOpts.transpiledJsOutputDirectory) {
+    throw new Error(
+        'In JS transpilation mode, either transpiledJsOutputFileName or ' +
+        'transpiledJsOutputDirectory must be specified in tsconfig.');
+  }
+
+  if (bazelOpts.transpiledJsOutputFileName &&
+      bazelOpts.transpiledJsOutputDirectory) {
+    throw new Error(
+        'In JS transpilation mode, cannot set both ' +
+        'transpiledJsOutputFileName and transpiledJsOutputDirectory.');
   }
 }
 
@@ -483,7 +493,17 @@
     fileName = this.flattenOutDir(fileName);
 
     if (this.bazelOpts.isJsTranspilation) {
-      fileName = this.bazelOpts.transpiledJsOutputFileName!;
+      if (this.bazelOpts.transpiledJsOutputFileName) {
+        fileName = this.bazelOpts.transpiledJsOutputFileName!;
+      } else {
+        // Strip the input directory path off of fileName to get the logical
+        // path within the input directory.
+        fileName =
+            path.relative(this.bazelOpts.transpiledJsInputDirectory!, fileName);
+        // Then prepend the output directory name.
+        fileName =
+            path.join(this.bazelOpts.transpiledJsOutputDirectory!, fileName);
+      }
     } else if (!this.bazelOpts.es5Mode) {
       // Write ES6 transpiled files to *.closure.js.
       if (this.bazelOpts.locale) {
diff --git a/internal/tsc_wrapped/tsc_wrapped.ts b/internal/tsc_wrapped/tsc_wrapped.ts
index 2062b73..9113ede 100644
--- a/internal/tsc_wrapped/tsc_wrapped.ts
+++ b/internal/tsc_wrapped/tsc_wrapped.ts
@@ -47,6 +47,12 @@
 
 function isCompilationTarget(
     bazelOpts: BazelOptions, sf: ts.SourceFile): boolean {
+  if (bazelOpts.isJsTranspilation && bazelOpts.transpiledJsInputDirectory) {
+    // transpiledJsInputDirectory is a relative logical path, so we cannot
+    // compare it to the resolved, absolute path of sf here.
+    // compilationTargetSrc is resolved, so use that for the comparison.
+    return sf.fileName.startsWith(bazelOpts.compilationTargetSrc[0]);
+  }
   return (bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
 }
 
@@ -115,6 +121,24 @@
 }
 
 /**
+ * expandSourcesFromDirectories finds any directories under filePath and expands
+ * them to their .js or .ts contents.
+ */
+function expandSourcesFromDirectories(fileList: string[], filePath: string) {
+  if (!fs.statSync(filePath).isDirectory()) {
+    if (filePath.endsWith('.ts') || filePath.endsWith('.tsx') ||
+        filePath.endsWith('.js')) {
+      fileList.push(filePath);
+    }
+    return;
+  }
+  const entries = fs.readdirSync(filePath);
+  for (const entry of entries) {
+    expandSourcesFromDirectories(fileList, path.join(filePath, entry));
+  }
+}
+
+/**
  * Runs a single build, returning false on failure.  This is potentially called
  * multiple times (once per bazel request) when running as a bazel worker.
  * Any encountered errors are written to stderr.
@@ -147,6 +171,12 @@
     angularCompilerOptions
   } = parsed;
 
+  const sourceFiles: string[] = [];
+  for (let i = 0; i < files.length; i++) {
+    const filePath = files[i];
+    expandSourcesFromDirectories(sourceFiles, filePath);
+  }
+
   if (bazelOpts.maxCacheSizeMb !== undefined) {
     const maxCacheSizeBytes = bazelOpts.maxCacheSizeMb * (1 << 20);
     cache.setMaxCacheSize(maxCacheSizeBytes);
@@ -170,7 +200,7 @@
   const perfTracePath = bazelOpts.perfTracePath;
   if (!perfTracePath) {
     return runFromOptions(
-        fileLoader, options, bazelOpts, files, disabledTsetseRules,
+        fileLoader, options, bazelOpts, sourceFiles, disabledTsetseRules,
         angularCompilerOptions);
   }
 
@@ -178,7 +208,7 @@
   const success = perfTrace.wrap(
       'runOneBuild',
       () => runFromOptions(
-          fileLoader, options, bazelOpts, files, disabledTsetseRules,
+          fileLoader, options, bazelOpts, sourceFiles, disabledTsetseRules,
           angularCompilerOptions));
   if (!success) return false;
   // Force a garbage collection pass.  This keeps our memory usage
@@ -212,7 +242,7 @@
   const moduleResolver = bazelOpts.isJsTranspilation ?
       makeJsModuleResolver(bazelOpts.workspaceName) :
       ts.resolveModuleName;
-  const tsickleCompilerHost: CompilerHost = new CompilerHost(
+  const tsickleCompilerHost = new CompilerHost(
       files, options, bazelOpts, compilerHostDelegate, fileLoader,
       moduleResolver);
   let compilerHost: PluginCompilerHost = tsickleCompilerHost;
diff --git a/internal/tsc_wrapped/tsc_wrapped_test.ts b/internal/tsc_wrapped/tsc_wrapped_test.ts
index cc5af0a..3f77c07 100644
--- a/internal/tsc_wrapped/tsc_wrapped_test.ts
+++ b/internal/tsc_wrapped/tsc_wrapped_test.ts
@@ -149,12 +149,16 @@
       moduleRoots = {} as {[moduleName: string]: string},
       isJsTranspilation = false,
       transpiledJsOutputFileName = undefined as string | undefined,
+      transpiledJsInputDirectory = undefined as string | undefined,
+      transpiledJsOutputDirectory = undefined as string | undefined,
     } = {}) {
       const bazelOpts = {
         ...defaultBazelOpts,
         es5Mode: es5,
         isJsTranspilation,
         transpiledJsOutputFileName,
+        transpiledJsInputDirectory,
+        transpiledJsOutputDirectory,
       } as BazelOptions;
       return new CompilerHost(
           [], COMPILER_OPTIONS, bazelOpts, delegateHost, testFileLoader,
@@ -272,16 +276,32 @@
         ]);
       });
 
-      it('writes to closureOptions.transpiledJsOutputFileName in JS transpilation mode',
-         () => {
-           createFakeGoogle3Host({
-             isJsTranspilation: true,
-             transpiledJsOutputFileName: 'foo/bar/a/b.dev_es5.js',
-           }).writeFile('a/b.js', 'some.code();', false, undefined, []);
-           expect(Object.keys(writtenFiles)).toEqual([
-             '/root/google3/blaze-out/k8-fastbuild/bin/foo/bar/a/b.dev_es5.js'
-           ]);
-         });
+      describe('transpiled JS', () => {
+        it('writes to transpiledJsOutputFileName', () => {
+          const host = createFakeGoogle3Host({
+            isJsTranspilation: true,
+            transpiledJsOutputFileName: 'foo/bar/a/b.dev_es5.js',
+          });
+          host.writeFile('a/b.js', 'some.code();', false, undefined, []);
+          expect(Object.keys(writtenFiles)).toEqual([
+            '/root/google3/blaze-out/k8-fastbuild/bin/foo/bar/a/b.dev_es5.js'
+          ]);
+        });
+
+        it('writes to transpiledJsOutputDirectory', () => {
+          const host = createFakeGoogle3Host({
+            isJsTranspilation: true,
+            transpiledJsInputDirectory: 'foo/bar/jsinputdir',
+            transpiledJsOutputDirectory: 'foo/bar/jsoutputdir',
+          });
+          host.writeFile(
+              'foo/bar/jsinputdir/a/b.js', 'some.code();', false, undefined,
+              []);
+          expect(Object.keys(writtenFiles)).toEqual([
+            '/root/google3/blaze-out/k8-fastbuild/bin/foo/bar/jsoutputdir/a/b.js'
+          ]);
+        });
+      });
     });
   });
 });
diff --git a/internal/tsc_wrapped/tsconfig.ts b/internal/tsc_wrapped/tsconfig.ts
index f8460c3..c147d3b 100644
--- a/internal/tsc_wrapped/tsconfig.ts
+++ b/internal/tsc_wrapped/tsconfig.ts
@@ -143,18 +143,34 @@
 
   /**
    * If true, indicates that this job is transpiling JS sources. If true, only
-   * one file can appear in compilationTargetSrc, and transpiledJsOutputFileName
-   * must be set.
+   * one file can appear in compilationTargetSrc, and either
+   * transpiledJsOutputFileName or the transpiledJs*Directory options must be
+   * set.
    */
   isJsTranspilation?: boolean;
 
   /**
-   * The path where the file containing the JS transpiled output should
-   * be written. Ignored if isJsTranspilation is false.
+   * The path where the file containing the JS transpiled output should be
+   * written. Ignored if isJsTranspilation is false. transpiledJsOutputFileName
+   *
    */
   transpiledJsOutputFileName?: string;
 
   /**
+   * The path where transpiled JS output should be written. Ignored if
+   * isJsTranspilation is false. Must not be set together with
+   * transpiledJsOutputFileName.
+   */
+  transpiledJsInputDirectory?: string;
+
+  /**
+   * The path where transpiled JS output should be written. Ignored if
+   * isJsTranspilation is false. Must not be set together with
+   * transpiledJsOutputFileName.
+   */
+  transpiledJsOutputDirectory?: string;
+
+  /**
    * Whether the user provided an implementation shim for .d.ts files in the
    * compilation unit.
    */
diff --git a/internal/tsc_wrapped/worker.ts b/internal/tsc_wrapped/worker.ts
index 2998318..2f9828e 100644
--- a/internal/tsc_wrapped/worker.ts
+++ b/internal/tsc_wrapped/worker.ts
@@ -26,7 +26,7 @@
  * Write a message to stderr, which appears in the bazel log and is visible to
  * the end user.
  */
-export function log(...args: Array<{}>) {
+export function log(...args: Array<unknown>) {
   console.error.apply(console, args);
 }