Make tsconfig default to label //:tsconfig.json

This avoids the need for users to create their own ts_library macro to follow the best-practice of having the same tsconfig.json for all libraries, and fixes the issue that it's easy to have no tsconfig applied to a ts_library if the attribute is omitted

BREAKING CHANGE:
ts_library targets with no tsconfig attribute will now
default to //:tsconfig.json.

If this breaks you, do one of these:
1) create a file at that location (in the WORKSPACE root)
2) create an alias rule in the root BUILD.bazel file like
     alias(name="tsconfig.json", actual="//path/to:tsconfig-something.json")
3) give an explicit tsconfig attribute to your ts_library rules

Closes #232

PiperOrigin-RevId: 205759471
diff --git a/BUILD.bazel b/BUILD.bazel
index 8da32ff..5941297 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -21,6 +21,14 @@
 load("@io_bazel_rules_go//go:def.bzl", "gazelle")
 load("@build_bazel_rules_nodejs//internal/js_library:js_library.bzl", "js_library")
 
+# ts_library defaults to this label in the top-level package.
+# Point to where the file actually lives.
+alias(
+    name = "tsconfig.json",
+    actual = "//examples:tsconfig.json",
+    visibility = ["//visibility:public"],
+)
+
 gazelle(
     name = "gazelle",
     prefix = "github.com/bazelbuild/rules_typescript",
diff --git a/defs.bzl b/defs.bzl
index e7caea1..aa3bafb 100644
--- a/defs.bzl
+++ b/defs.bzl
@@ -17,7 +17,7 @@
 Users should not load files under "/internal"
 """
 load("//internal:ts_repositories.bzl", _ts_setup_workspace = "ts_setup_workspace")
-load("//internal:build_defs.bzl", _ts_library = "ts_library")
+load("//internal:build_defs.bzl", _ts_library = "ts_library_macro")
 load("//internal:ts_config.bzl", _ts_config = "ts_config")
 load("//internal/devserver:ts_devserver.bzl", _ts_devserver = "ts_devserver_macro")
 load("//internal/karma:ts_web_test.bzl",
diff --git a/internal/BUILD.bazel b/internal/BUILD.bazel
index 438f315..b4a1530 100644
--- a/internal/BUILD.bazel
+++ b/internal/BUILD.bazel
@@ -17,13 +17,14 @@
 package(default_visibility = ["//visibility:public"])
 
 exports_files([
+    "tsconfig.json",
     # Exported to be consumed for generating skydoc.
     "build_defs.bzl",
     "ts_config.bzl",
     "ts_repositories.bzl",
 ])
 
-load("//internal:build_defs.bzl", "ts_library")
+load("//:defs.bzl", "ts_library")
 load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary", "jasmine_node_test")
 
 # Vanilla typescript compiler: run the tsc.js binary distributed by TypeScript
diff --git a/internal/build_defs.bzl b/internal/build_defs.bzl
index ee3b32d..db78616 100644
--- a/internal/build_defs.bzl
+++ b/internal/build_defs.bzl
@@ -162,7 +162,7 @@
     )
     return ts_providers_dict_to_struct(ts_providers)
 
-ts_library = rule(
+_ts_library = rule(
     _ts_library_impl,
     attrs = dict(COMMON_ATTRIBUTES, **{
         "srcs": attr.label_list(
@@ -217,3 +217,9 @@
 It produces declarations files (`.d.ts`) which are used for compiling downstream
 TypeScript targets and JavaScript for the browser and Closure compiler.
 """
+
+def ts_library_macro(tsconfig = None, **kwargs):
+    if not tsconfig:
+        tsconfig = "//:tsconfig.json"
+
+    _ts_library(tsconfig = tsconfig, **kwargs)
diff --git a/internal/e2e/default_tsconfig_test.js b/internal/e2e/default_tsconfig_test.js
new file mode 100644
index 0000000..298e8eb
--- /dev/null
+++ b/internal/e2e/default_tsconfig_test.js
@@ -0,0 +1,128 @@
+/**
+ * @fileoverview
+ * This tests interactions between multiple Bazel workspaces.
+ *
+ * We have learned from experience in the rules_nodejs repo that it's not
+ * practical to simply check in the nested WORKSPACE files and try to build
+ * them, because
+ * - it's hard to exclude them from the parent WORKSPACE - each nested workspace
+ *   must be registered there with a matching name
+ * - testing a child workspace requires `cd` into the directory, which doesn't
+ *   fit the CI model of `bazel test ...`
+ *
+ * The test is written in JavaScript simply to make it more portable, so we can
+ * run it on Windows for example. We don't use TypeScript here since we are
+ * running outside the build system.
+ */
+
+const fs = require('fs');
+const path = require('path');
+const child_process = require('child_process');
+const os = require('os');
+
+const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'wksp'));
+const WORKSPACE_BOILERPLATE = `
+http_archive(
+    name = "build_bazel_rules_nodejs",
+    urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.10.0.zip"],
+    strip_prefix = "rules_nodejs-0.10.0",
+)
+http_archive(
+    name = "io_bazel_rules_webtesting",
+    urls = ["https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip"],
+    strip_prefix = "rules_webtesting-0.2.0",
+)
+http_archive(
+    name = "io_bazel_rules_go",
+    urls = [
+        "http://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/0.10.3/rules_go-0.10.3.tar.gz",
+        "https://github.com/bazelbuild/rules_go/releases/download/0.10.3/rules_go-0.10.3.tar.gz"
+    ],
+)
+local_repository(
+    name = "build_bazel_rules_typescript",
+    path = "${process.cwd()}",
+)
+load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
+node_repositories(package_json=[])
+load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
+ts_setup_workspace()
+`;
+
+/**
+ * Create a file at path filename, creating parent directories as needed, under
+ * this test's temp directory. Write the content into that file.
+ */
+function write(filename, content) {
+  var parents = path.dirname(path.join(tmpdir, filename));
+  while (path.dirname(parents) !== parents) {
+    if (!fs.existsSync(path.join(parents))) {
+      fs.mkdirSync(path.join(parents));
+    }
+    parents = path.dirname(parents);
+  }
+  fs.writeFileSync(path.join(tmpdir, filename), content);
+}
+
+function bazel(workspace, args) {
+  const result = child_process.spawnSync('bazel', args, {
+    cwd: path.join(tmpdir, workspace),
+    stdio: 'inherit',
+  });
+  expect(result.status).toBe(0, 'bazel exited with non-zero exit code');
+}
+
+describe('default tsconfig', () => {
+  it(`uses the tsconfig in the workspace defining the rule,
+        not the workspace where the rule is defined (rules_typescript), nor
+        the workspace where the build is occurring`,
+     () => {
+       // Workspace 'a' can't compile with --noImplicitAny.
+       // When workspace 'b' has a dep here, we make sure not to use the
+       // tsconfig from workspace 'b'
+       write('a/WORKSPACE', `
+workspace(name = "a")
+${WORKSPACE_BOILERPLATE}`);
+       write('a/BUILD', `
+load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
+ts_library(
+    name = "a_lib",
+    srcs=["has_implicit_any.ts"],
+    node_modules = "@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules",
+    visibility = ["//visibility:public"],
+)
+        `);
+       write('a/tsconfig.json', `{}`);
+       write('a/has_implicit_any.ts', `function f(a) {
+            console.error(a);
+        }`);
+
+       // Workspace 'b' has a default tsconfig that sets --noImplicitAny.
+       write('b/WORKSPACE', `
+workspace(name="b")
+local_repository(name="a", path="../a")
+${WORKSPACE_BOILERPLATE}`);
+       write('b/BUILD', `
+load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
+exports_files(["tsconfig.json"])
+ts_library(
+    name = "b_lib",
+    srcs = ["file.ts"],
+    deps = ["@a//:a_lib"],
+    node_modules = "@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules",
+)
+        `);
+       write('b/file.ts', `
+        f('thing');
+        `);
+       write('b/tsconfig.json', `{
+            "compilerOptions": {
+                "noImplicitAny": true
+            }
+        }`);
+
+       // Now build from workspace 'b' and verify that the dep in workspace 'a'
+       // was able to compile.
+       bazel('b', ['build', ':all']);
+     });
+});
diff --git a/internal/karma/BUILD.bazel b/internal/karma/BUILD.bazel
index 8802ef9..c6fee3a 100644
--- a/internal/karma/BUILD.bazel
+++ b/internal/karma/BUILD.bazel
@@ -15,6 +15,7 @@
     srcs = glob(["*.ts"]),
     module_name = "karma-concat-js",
     node_modules = "@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules",
+    tsconfig = "//internal:tsconfig.json",
 )
 
 load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
diff --git a/internal/tsconfig.json b/internal/tsconfig.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/internal/tsconfig.json
diff --git a/package.json b/package.json
index 664ad04..6d9788a 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,8 @@
     },
     "scripts": {
         "pree2e": "webdriver-manager update $CHROMEDRIVER_VERSION_ARG && bazel build examples/app:e2e && bazel build examples/protocol_buffers:e2e",
-        "e2e": "yarn e2e-examples-app-devserver && yarn e2e-examples-app-prodserver && yarn e2e-examples-protobuf-devserver && yarn e2e-examples-protobuf-prodserver",
+        "e2e": "yarn e2e-bazel-external && yarn e2e-examples-app-devserver && yarn e2e-examples-app-prodserver && yarn e2e-examples-protobuf-devserver && yarn e2e-examples-protobuf-prodserver",
+        "e2e-bazel-external": "jasmine internal/e2e/default_tsconfig_test.js",
         "e2e-examples-app-devserver": "concurrently \"bazel run examples/app:devserver\" \"while ! nc -z 127.0.0.1 8080; do sleep 1; done && protractor --suite app\" --kill-others --success first",
         "e2e-examples-app-prodserver": "concurrently \"bazel run examples/app:prodserver\" \"while ! nc -z 127.0.0.1 8080; do sleep 1; done && protractor --suite app\" --kill-others --success first",
         "e2e-examples-protobuf-devserver": "concurrently \"bazel run examples/protocol_buffers:devserver\" \"while ! nc -z 127.0.0.1 8080; do sleep 1; done && protractor --suite protocol_buffers\" --kill-others --success first",