From e473d52730aab2f4e81729ff8502b3bd3e541e34 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Mon, 3 Apr 2023 11:49:50 -0700 Subject: [PATCH] chore(6.0): remove typescript internals --- .github/BUILD.bazel | 4 - .github/CODEOWNERS | 4 - docs/Built-ins.md | 11 +- docs/Providers.md | 1 + .../checked_in_tsc.bzl | 16 +- internal/coverage/BUILD.bazel | 4 +- internal/linker/BUILD.bazel | 4 +- internal/linker/test/BUILD.bazel | 4 +- internal/linker/test/multi_linker/BUILD.bazel | 4 +- internal/npm_install/BUILD.bazel | 4 +- internal/npm_install/npm_install.bzl | 12 +- internal/pkg_npm/test/pkg_npm.spec.js | 2 +- .../typescript/index.bzl => internal/tsc.bzl | 8 +- nodejs/private/BUILD.bazel | 2 - nodejs/private/ts_config.bzl | 152 ---------- nodejs/private/ts_lib.bzl | 264 ------------------ nodejs/private/ts_project.bzl | 224 --------------- .../private/ts_project_options_validator.js | 144 ---------- nodejs/private/ts_validate_options.bzl | 79 ------ packages/runfiles/BUILD.bazel | 4 +- packages/typescript/BUILD.bazel | 0 21 files changed, 26 insertions(+), 921 deletions(-) rename packages/typescript/checked_in_ts_project.bzl => internal/checked_in_tsc.bzl (62%) rename packages/typescript/index.bzl => internal/tsc.bzl (66%) delete mode 100644 nodejs/private/ts_config.bzl delete mode 100644 nodejs/private/ts_lib.bzl delete mode 100644 nodejs/private/ts_project.bzl delete mode 100755 nodejs/private/ts_project_options_validator.js delete mode 100644 nodejs/private/ts_validate_options.bzl delete mode 100644 packages/typescript/BUILD.bazel diff --git a/.github/BUILD.bazel b/.github/BUILD.bazel index f782e519bf..7960a3adf3 100644 --- a/.github/BUILD.bazel +++ b/.github/BUILD.bazel @@ -8,10 +8,6 @@ generate_codeowners( # do not sort owners = [ "//:OWNERS", - "//examples:OWNERS.examples_jest", - "//examples:OWNERS.examples_nestjs", - "//examples:OWNERS.examples_angular", - "//examples:OWNERS.examples_angular_bazel_architect", ], ) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2a25941e66..c830d0e391 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,7 +3,3 @@ # yarn update-codeowners * @mattem @gregmagolan @alexeagle -/examples/jest/** @mrmeku @alexeagle @gregmagolan @mattem -/examples/nestjs/** @zachgrayio @zMotivat0r @rayman1104 @siberex @alexeagle @gregmagolan @mattem -/examples/angular/** @alan-agius4 @jbedard @alexeagle @gregmagolan @mattem -/examples/angular_bazel_architect/** @alan-agius4 @jbedard @alexeagle @gregmagolan @mattem diff --git a/docs/Built-ins.md b/docs/Built-ins.md index 0a5da6fd44..9371b7dd73 100755 --- a/docs/Built-ins.md +++ b/docs/Built-ins.md @@ -100,7 +100,6 @@ nodejs_binary( ```python nodejs_binary( name = "bin", - data = [":main"] entry_point = ":main.js", ) ``` @@ -566,8 +565,6 @@ NB: This feature requires runfiles be enabled due to an issue in Bazel which we On Windows runfiles are off by default and must be enabled with the `--enable_runfiles` flag when using this feature. -NB: `ts_project` does not support directory npm deps due to internal dependency on having all input sources files explicitly specified. - For the `nodejs_binary` & `nodejs_test` `entry_point` attribute (which often needs to reference a file within an npm package) you can set the entry_point to a dict with a single entry, where the key corresponds to the directory label and the value corresponds to the path within that directory to the entry point, e.g. @@ -1014,7 +1011,7 @@ my_rule(

deps

-(*List of labels*): Other targets which produce files that should be included in the package +(*List of labels*): Other targets which produce files that should be included in the package` Defaults to `[]` @@ -1242,8 +1239,6 @@ NB: This feature requires runfiles be enabled due to an issue in Bazel which we On Windows runfiles are off by default and must be enabled with the `--enable_runfiles` flag when using this feature. -NB: `ts_project` does not support directory npm deps due to internal dependency on having all input sources files explicitly specified. - For the `nodejs_binary` & `nodejs_test` `entry_point` attribute (which often needs to reference a file within an npm package) you can set the entry_point to a dict with a single entry, where the key corresponds to the directory label and the value corresponds to the path within that directory to the entry point, e.g. @@ -1700,13 +1695,11 @@ js_library( package_name = "@myco/mypkg", # Consumers might need fields like "main" or "typings" srcs = ["package.json"], + # The .js and .d.ts outputs from above will be part of the package data = glob(["*.js"]), ) ``` -> `js_library` has some undocumented advanced features you can find in the source code or in our examples. -> These should not be considered a public API and aren't subject to our usual support and semver guarantees. - ### Outputs Like all Bazel rules it produces a default output by providing [DefaultInfo]. diff --git a/docs/Providers.md b/docs/Providers.md index a083bd6e52..160798b126 100755 --- a/docs/Providers.md +++ b/docs/Providers.md @@ -73,6 +73,7 @@ JSNamedModuleInfo(direct_sources JavaScript files whose module name is self-contained. +For example named AMD/UMD or goog.module format. These outputs should be named "foo.umd.js" (note that renaming it from "foo.js" doesn't affect the module id) diff --git a/packages/typescript/checked_in_ts_project.bzl b/internal/checked_in_tsc.bzl similarity index 62% rename from packages/typescript/checked_in_ts_project.bzl rename to internal/checked_in_tsc.bzl index 889f86a9d0..1fa7e08ae0 100644 --- a/packages/typescript/checked_in_ts_project.bzl +++ b/internal/checked_in_tsc.bzl @@ -1,23 +1,17 @@ -"checked_in_ts_project rule" +"checked_in_tsc rule" load("@build_bazel_rules_nodejs//:index.bzl", "generated_file_test") -load("//packages/typescript:index.bzl", "ts_project") +load("//internal:tsc.bzl", "tsc") -def checked_in_ts_project(name, src, checked_in_js = None, tsconfig = None, **kwargs): - """ - In rules_nodejs "builtin" package, we are creating the toolchain for building - tsc-wrapped and executing ts_project, so we cannot depend on them. - However, we still want to be able to write our tooling in TypeScript. - This macro lets us check in the resulting .js files, and still ensure that they are - compiled from the .ts by using a golden file test. - """ +def checked_in_tsc(name, src, checked_in_js = None, tsconfig = None, **kwargs): + """A tsc rule that also asserts that the generated JS is up-to-date.""" if not checked_in_js: checked_in_js = src[:-3] + ".js" if tsconfig == None: tsconfig = "//:tsconfig.json" - ts_project( + tsc( name = name, srcs = [src], tsconfig = tsconfig, diff --git a/internal/coverage/BUILD.bazel b/internal/coverage/BUILD.bazel index 9da8434439..8fe71e8d69 100644 --- a/internal/coverage/BUILD.bazel +++ b/internal/coverage/BUILD.bazel @@ -1,9 +1,9 @@ load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") # BEGIN-INTERNAL -load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project") +load("//internal:checked_in_tsc.bzl", "checked_in_tsc") -checked_in_ts_project( +checked_in_tsc( name = "lcov_merger_js_lib", src = "lcov_merger.ts", checked_in_js = "lcov_merger-js.js", diff --git a/internal/linker/BUILD.bazel b/internal/linker/BUILD.bazel index 80580b8a46..ba03158e18 100644 --- a/internal/linker/BUILD.bazel +++ b/internal/linker/BUILD.bazel @@ -2,14 +2,14 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") # BEGIN-INTERNAL load("//:index.bzl", "js_library") -load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project") +load("//internal:checked_in_tsc.bzl", "checked_in_tsc") # We can't bootstrap the ts_library rule using the linker itself, # because the implementation of ts_library depends on the linker so that would be a cycle. # So we compile it to JS and check in the result as index.js. # To update index.js run: # bazel run //internal/linker:linker_lib_check_compiled.update -checked_in_ts_project( +checked_in_tsc( name = "linker_lib", src = "link_node_modules.ts", checked_in_js = "index.js", diff --git a/internal/linker/test/BUILD.bazel b/internal/linker/test/BUILD.bazel index d46225869f..af700e4ab4 100644 --- a/internal/linker/test/BUILD.bazel +++ b/internal/linker/test/BUILD.bazel @@ -1,6 +1,6 @@ -load("//packages/typescript:index.bzl", "ts_project") +load("//internal:tsc.bzl", "tsc") -ts_project( +tsc( name = "test_lib", srcs = glob(["*.ts"]), deps = [ diff --git a/internal/linker/test/multi_linker/BUILD.bazel b/internal/linker/test/multi_linker/BUILD.bazel index a34b8750c2..bba4692546 100644 --- a/internal/linker/test/multi_linker/BUILD.bazel +++ b/internal/linker/test/multi_linker/BUILD.bazel @@ -1,6 +1,6 @@ -load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project") +load("//internal:checked_in_tsc.bzl", "checked_in_tsc") -checked_in_ts_project( +checked_in_tsc( name = "checked_in_test", src = "test.ts", checked_in_js = "checked_in_test.js", diff --git a/internal/npm_install/BUILD.bazel b/internal/npm_install/BUILD.bazel index 54c7dcd691..07d5a2f233 100644 --- a/internal/npm_install/BUILD.bazel +++ b/internal/npm_install/BUILD.bazel @@ -3,12 +3,12 @@ load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") # BEGIN-INTERNAL # avoid leaking a ts dependency -load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project") +load("//internal:checked_in_tsc.bzl", "checked_in_tsc") # Using checked in ts library like the linker # To update index.js run: # bazel run //internal/npm_install:compile_generate_build_file_check_compiled.update -checked_in_ts_project( +checked_in_tsc( name = "compile_generate_build_file", src = "generate_build_file.ts", checked_in_js = "index.js", diff --git a/internal/npm_install/npm_install.bzl b/internal/npm_install/npm_install.bzl index cad4ab2a97..beeadc4563 100644 --- a/internal/npm_install/npm_install.bzl +++ b/internal/npm_install/npm_install.bzl @@ -155,7 +155,7 @@ The above links will create the targets, @npm//target ``` -that can be referenced as `data` or `deps` by other rules such as `nodejs_binary` and `ts_project` +that can be referenced as `data` or `deps` by other rules such as `nodejs_binary` and can be required as `@scope/target` and `target` with standard node_modules resolution at runtime, ``` @@ -168,16 +168,6 @@ nodejs_binary( "@npm//other/dep" ], ) - -ts_project( - name = "test", - srcs = [...], - deps = [ - "@npm//@scope/target", - "@npm//target" - "@npm//other/dep" - ], -) ``` """, ), diff --git a/internal/pkg_npm/test/pkg_npm.spec.js b/internal/pkg_npm/test/pkg_npm.spec.js index a44e64e709..28c6554456 100644 --- a/internal/pkg_npm/test/pkg_npm.spec.js +++ b/internal/pkg_npm/test/pkg_npm.spec.js @@ -33,7 +33,7 @@ describe('pkg_npm', () => { it('copies files from other packages', () => { expect(readFromPkg('dependent_file')).toEqual('dependent_file content'); }); - it('copies js files from ts_project', () => { + it('copies js files from tsc', () => { expect(readFromPkg('foo.js')).toContain('exports.a = \'\';'); }); it('copies data dependencies', () => { diff --git a/packages/typescript/index.bzl b/internal/tsc.bzl similarity index 66% rename from packages/typescript/index.bzl rename to internal/tsc.bzl index 84f21938cf..ab8165edba 100644 --- a/packages/typescript/index.bzl +++ b/internal/tsc.bzl @@ -1,10 +1,10 @@ -load("@npm//typescript:index.bzl", "tsc") +load("@npm//typescript:index.bzl", _tsc = "tsc") -# Basic wrapper around tsc to replace ts_project() -def ts_project(name, srcs, deps = [], data = [], tsconfig = "//:tsconfig.json", **kwargs): +# Basic wrapper around tsc to replace tsc() +def tsc(name, srcs, deps = [], data = [], tsconfig = "//:tsconfig.json", **kwargs): outs = [s.replace(".ts", ".js") for s in srcs] + [s.replace(".ts", ".d.ts") for s in srcs] - tsc( + _tsc( name = name, args = [ "-p", diff --git a/nodejs/private/BUILD.bazel b/nodejs/private/BUILD.bazel index 28bf7fd0d4..a839aa9264 100644 --- a/nodejs/private/BUILD.bazel +++ b/nodejs/private/BUILD.bazel @@ -1,7 +1,5 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -exports_files(["ts_project_options_validator.js"]) - filegroup( name = "package_contents", srcs = glob(["*"]) + [ diff --git a/nodejs/private/ts_config.bzl b/nodejs/private/ts_config.bzl deleted file mode 100644 index c16ad74950..0000000000 --- a/nodejs/private/ts_config.bzl +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright 2017 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"tsconfig.json files using extends" - -load(":ts_lib.bzl", _lib = "lib") - -TsConfigInfo = provider( - doc = """Provides TypeScript configuration, in the form of a tsconfig.json file - along with any transitively referenced tsconfig.json files chained by the - "extends" feature""", - fields = { - "deps": "all tsconfig.json files needed to configure TypeScript", - }, -) - -def _ts_config_impl(ctx): - files = depset([ctx.file.src]) - transitive_deps = [] - for dep in ctx.attr.deps: - if TsConfigInfo in dep: - transitive_deps.append(dep[TsConfigInfo].deps) - transitive_deps.append(depset(ctx.files.deps)) - return [ - DefaultInfo(files = files), - TsConfigInfo(deps = depset([ctx.file.src], transitive = transitive_deps)), - ] - -ts_config = rule( - implementation = _ts_config_impl, - attrs = { - "deps": attr.label_list( - doc = """Additional tsconfig.json files referenced via extends""", - allow_files = True, - ), - "src": attr.label( - doc = """The tsconfig.json file passed to the TypeScript compiler""", - allow_single_file = True, - mandatory = True, - ), - }, - doc = """Allows a tsconfig.json file to extend another file. - -Normally, you just give a single `tsconfig.json` file as the tsconfig attribute -of a `ts_project` rule. However, if your `tsconfig.json` uses the `extends` -feature from TypeScript, then the Bazel implementation needs to know about that -extended configuration file as well, to pass them both to the TypeScript compiler. -""", -) - -def _join(*elements): - return "/".join([f for f in elements if f]) - -def _relative_path(tsconfig, dest): - relative_to = tsconfig.dirname - if dest.is_source: - # Calculate a relative path from the directory where we're writing the tsconfig - # back to the sources root - workspace_root = "/".join([".."] * len(relative_to.split("/"))) - return _join(workspace_root, dest.path) - - # Bazel guarantees that srcs are beneath the package directory, and we disallow - # tsconfig.json being generated with a "/" in the name. - # So we can calculate a relative path from e.g. - # bazel-out/darwin-fastbuild/bin/packages/typescript/test/ts_project/generated_tsconfig/gen_src - # to - result = dest.path[len(relative_to) + 1:] - if not result.startswith("."): - result = "./" + result - return result - -def _filter_input_files(files, allow_js, resolve_json_module): - return [ - f - for f in files - if _lib.is_ts_src(f.basename, allow_js) or _lib.is_json_src(f.basename, resolve_json_module) - ] - -def _write_tsconfig_rule(ctx): - # TODO: is it useful to expand Make variables in the content? - content = "\n".join(ctx.attr.content) - if ctx.attr.extends: - content = content.replace( - "__extends__", - _relative_path(ctx.outputs.out, ctx.file.extends), - ) - - filtered_files = _filter_input_files(ctx.files.files, ctx.attr.allow_js, ctx.attr.resolve_json_module) - if filtered_files: - content = content.replace( - "\"__files__\"", - str([_relative_path(ctx.outputs.out, f) for f in filtered_files]), - ) - ctx.actions.write( - output = ctx.outputs.out, - content = content, - ) - return [DefaultInfo(files = depset([ctx.outputs.out]))] - -write_tsconfig_rule = rule( - implementation = _write_tsconfig_rule, - attrs = { - "content": attr.string_list(), - "extends": attr.label(allow_single_file = True), - "files": attr.label_list(allow_files = True), - "out": attr.output(), - "allow_js": attr.bool(), - "resolve_json_module": attr.bool(), - }, -) - -# Syntax sugar around skylib's write_file -def write_tsconfig(name, config, files, out, extends = None, allow_js = None, resolve_json_module = None): - """Wrapper around bazel_skylib's write_file which understands tsconfig paths - - Args: - name: name of the resulting write_file rule - config: tsconfig dictionary - files: list of input .ts files to put in the files[] array - out: the file to write - extends: a label for a tsconfig.json file to extend from, if any - """ - if out.find("/") >= 0: - fail("tsconfig should be generated in the package directory, to make relative pathing simple") - - if extends: - config["extends"] = "__extends__" - - amended_config = struct( - files = "__files__", - **config - ) - write_tsconfig_rule( - name = name, - files = files, - extends = extends, - content = [json.encode(amended_config)], - out = out, - allow_js = allow_js, - resolve_json_module = resolve_json_module, - ) diff --git a/nodejs/private/ts_lib.bzl b/nodejs/private/ts_lib.bzl deleted file mode 100644 index 32817b19cf..0000000000 --- a/nodejs/private/ts_lib.bzl +++ /dev/null @@ -1,264 +0,0 @@ -"Utilities functions for selecting and filtering ts and other files" - -load("@rules_nodejs//nodejs/private/providers:declaration_info.bzl", "DeclarationInfo") - -ValidOptionsInfo = provider( - doc = "Internal: whether the validator ran successfully", - fields = { - "marker": """useless file that must be depended on to cause the validation action to run. - TODO: replace with https://docs.bazel.build/versions/main/skylark/rules.html#validation-actions""", - }, -) - -# Targets in deps must provide one or the other of these -DEPS_PROVIDERS = [ - [DeclarationInfo], - [ValidOptionsInfo], -] - -# Attributes common to all TypeScript rules -STD_ATTRS = { - "args": attr.string_list( - doc = "https://www.typescriptlang.org/docs/handbook/compiler-options.html", - ), - "data": attr.label_list( - doc = "Runtime dependencies to include in binaries/tests that depend on this TS code", - default = [], - allow_files = True, - ), - "declaration_dir": attr.string( - doc = "Always controlled by Bazel. https://www.typescriptlang.org/tsconfig#declarationDir", - ), - # Note, this is overridden by the @bazel/typescript implementation in build_bazel_rules_nodejs - # to add an aspect on the deps attribute. - "deps": attr.label_list( - doc = "Other targets which produce TypeScript typings", - providers = DEPS_PROVIDERS, - ), - "out_dir": attr.string( - doc = "Always controlled by Bazel. https://www.typescriptlang.org/tsconfig#outDir", - ), - "root_dir": attr.string( - doc = "Always controlled by Bazel. https://www.typescriptlang.org/tsconfig#rootDir", - ), - # NB: no restriction on extensions here, because tsc sometimes adds type-check support - # for more file kinds (like require('some.json')) and also - # if you swap out the `compiler` attribute (like with ngtsc) - # that compiler might allow more sources than tsc does. - "srcs": attr.label_list( - doc = "TypeScript source files", - allow_files = True, - mandatory = True, - ), - "supports_workers": attr.bool( - doc = "Whether the tsc compiler understands Bazel's persistent worker protocol", - default = False, - ), - "transpile": attr.bool( - doc = "whether tsc should be used to produce .js outputs", - default = True, - ), - "tsc": attr.label( - doc = "TypeScript compiler binary", - mandatory = True, - executable = True, - cfg = "exec", - ), - "tsconfig": attr.label( - doc = "tsconfig.json file, see https://www.typescriptlang.org/tsconfig", - mandatory = True, - allow_single_file = [".json"], - ), -} - -# These attrs are shared between the validate and the ts_project rules -# They simply mirror data from the compilerOptions block in tsconfig.json -# so that Bazel can predict all of tsc's outputs. -COMPILER_OPTION_ATTRS = { - "allow_js": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#allowJs", - ), - "composite": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#composite", - ), - "declaration": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#declaration", - ), - "declaration_map": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#declarationMap", - ), - "emit_declaration_only": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#emitDeclarationOnly", - ), - "extends": attr.label( - allow_files = [".json"], - doc = "https://www.typescriptlang.org/tsconfig#extends", - ), - "incremental": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#incremental", - ), - "preserve_jsx": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#jsx", - ), - "resolve_json_module": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#resolveJsonModule", - ), - "source_map": attr.bool( - doc = "https://www.typescriptlang.org/tsconfig#sourceMap", - ), -} - -# tsc knows how to produce the following kinds of output files. -# NB: the macro `ts_project_macro` will set these outputs based on user -# telling us which settings are enabled in the tsconfig for this project. -OUTPUT_ATTRS = { - "buildinfo_out": attr.output( - doc = "Location in bazel-out where tsc will write a `.tsbuildinfo` file", - ), - "js_outs": attr.output_list( - doc = "Locations in bazel-out where tsc will write `.js` files", - ), - "map_outs": attr.output_list( - doc = "Locations in bazel-out where tsc will write `.js.map` files", - ), - "typing_maps_outs": attr.output_list( - doc = "Locations in bazel-out where tsc will write `.d.ts.map` files", - ), - "typings_outs": attr.output_list( - doc = "Locations in bazel-out where tsc will write `.d.ts` files", - ), -} - -def _join(*elements): - segments = [f for f in elements if f] - if len(segments): - return "/".join(segments) - return "." - -def _strip_external(path): - return path[len("external/"):] if path.startswith("external/") else path - -def _relative_to_package(path, ctx): - for prefix in [ctx.bin_dir.path, ctx.label.workspace_name, ctx.label.package]: - prefix += "/" - path = _strip_external(path) - if path.startswith(prefix): - path = path[len(prefix):] - return path - -def _is_ts_src(src, allow_js): - if not src.endswith(".d.ts") and (src.endswith(".ts") or src.endswith(".tsx")): - return True - return allow_js and (src.endswith(".js") or src.endswith(".jsx")) - -def _is_json_src(src, resolve_json_module): - return resolve_json_module and src.endswith(".json") - -def _replace_ext(f, ext_map): - cur_ext = f[f.rindex("."):] - new_ext = ext_map.get(cur_ext) - if new_ext != None: - return new_ext - new_ext = ext_map.get("*") - if new_ext != None: - return new_ext - return None - -def _out_paths(srcs, out_dir, root_dir, allow_js, ext_map): - rootdir_replace_pattern = root_dir + "/" if root_dir else "" - outs = [] - for f in srcs: - if _is_ts_src(f, allow_js): - out = _join(out_dir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "", 1) + _replace_ext(f, ext_map)) - - # Don't declare outputs that collide with inputs - # for example, a.js -> a.js - if out != f: - outs.append(out) - return outs - -def _calculate_js_outs(srcs, out_dir, root_dir, allow_js, preserve_jsx, emit_declaration_only): - if emit_declaration_only: - return [] - - exts = { - "*": ".js", - ".jsx": ".jsx", - ".tsx": ".jsx", - } if preserve_jsx else {"*": ".js"} - return _out_paths(srcs, out_dir, root_dir, allow_js, exts) - -def _calculate_map_outs(srcs, out_dir, root_dir, source_map, preserve_jsx, emit_declaration_only): - if not source_map or emit_declaration_only: - return [] - - exts = { - "*": ".js.map", - ".tsx": ".jsx.map", - } if preserve_jsx else {"*": ".js.map"} - return _out_paths(srcs, out_dir, root_dir, False, exts) - -def _calculate_typings_outs(srcs, typings_out_dir, root_dir, declaration, composite, allow_js, include_srcs = True): - if not (declaration or composite): - return [] - return _out_paths(srcs, typings_out_dir, root_dir, allow_js, {"*": ".d.ts"}) - -def _calculate_typing_maps_outs(srcs, typings_out_dir, root_dir, declaration_map, allow_js): - if not declaration_map: - return [] - - exts = {"*": ".d.ts.map"} - return _out_paths(srcs, typings_out_dir, root_dir, allow_js, exts) - -def _calculate_root_dir(ctx): - some_generated_path = None - some_source_path = None - root_path = None - - # Note we don't have access to the ts_project macro allow_js param here. - # For error-handling purposes, we can assume that any .js/.jsx - # input is meant to be in the rootDir alongside .ts/.tsx sources, - # whether the user meant for them to be sources or not. - # It's a non-breaking change to relax this constraint later, but would be - # a breaking change to restrict it further. - allow_js = True - for src in ctx.files.srcs: - if _is_ts_src(src.path, allow_js): - if src.is_source: - some_source_path = src.path - else: - some_generated_path = src.path - root_path = ctx.bin_dir.path - - if some_source_path and some_generated_path: - fail("ERROR: %s srcs cannot be a mix of generated files and source files " % ctx.label + - "since this would prevent giving a single rootDir to the TypeScript compiler\n" + - " found generated file %s and source file %s" % - (some_generated_path, some_source_path)) - - return _join( - root_path, - ctx.label.workspace_root, - ctx.label.package, - ctx.attr.root_dir, - ) - -def _declare_outputs(ctx, paths): - return [ - ctx.actions.declare_file(path) - for path in paths - ] - -lib = struct( - declare_outputs = _declare_outputs, - join = _join, - relative_to_package = _relative_to_package, - is_ts_src = _is_ts_src, - is_json_src = _is_json_src, - out_paths = _out_paths, - calculate_js_outs = _calculate_js_outs, - calculate_map_outs = _calculate_map_outs, - calculate_typings_outs = _calculate_typings_outs, - calculate_typing_maps_outs = _calculate_typing_maps_outs, - calculate_root_dir = _calculate_root_dir, -) diff --git a/nodejs/private/ts_project.bzl b/nodejs/private/ts_project.bzl deleted file mode 100644 index a4b67670d2..0000000000 --- a/nodejs/private/ts_project.bzl +++ /dev/null @@ -1,224 +0,0 @@ -"ts_project rule" - -load("@bazel_skylib//lib:dicts.bzl", "dicts") -load("@rules_nodejs//nodejs/private/providers:declaration_info.bzl", "DeclarationInfo", "declaration_info") -load("@rules_nodejs//nodejs/private/providers:js_providers.bzl", "js_module_info") -load(":ts_lib.bzl", "COMPILER_OPTION_ATTRS", "OUTPUT_ATTRS", "STD_ATTRS", "ValidOptionsInfo", _lib = "lib") -load(":ts_config.bzl", "TsConfigInfo") -load(":ts_validate_options.bzl", _validate_lib = "lib") - -def _ts_project_impl(ctx, run_action = None, ExternalNpmPackageInfo = None): - """Creates the action which spawns `tsc`. - - This function has two extra arguments that are particular to how it's called - within build_bazel_rules_nodejs and @bazel/typescript npm package. - Other TS rule implementations wouldn't need to pass these: - - Args: - ctx: starlark rule execution context - run_action: used with the build_bazel_rules_nodejs linker, by default we use ctx.actions.run - ExternalNpmPackageInfo: a provider symbol specific to the build_bazel_rules_nodejs linker - - Returns: - list of providers - """ - srcs = [_lib.relative_to_package(src.path, ctx) for src in ctx.files.srcs] - - # Recalculate outputs inside the rule implementation. - # The outs are first calculated in the macro in order to try to predetermine outputs so they can be declared as - # outputs on the rule. This provides the benefit of being able to reference an output file with a label. - # However, it is not possible to evaluate files in outputs of other rules such as filegroup, therefore the outs are - # recalculated here. - typings_out_dir = ctx.attr.declaration_dir or ctx.attr.out_dir - js_outs = _lib.declare_outputs(ctx, [] if not ctx.attr.transpile else _lib.calculate_js_outs(srcs, ctx.attr.out_dir, ctx.attr.root_dir, ctx.attr.allow_js, ctx.attr.preserve_jsx, ctx.attr.emit_declaration_only)) - map_outs = _lib.declare_outputs(ctx, [] if not ctx.attr.transpile else _lib.calculate_map_outs(srcs, ctx.attr.out_dir, ctx.attr.root_dir, ctx.attr.source_map, ctx.attr.preserve_jsx, ctx.attr.emit_declaration_only)) - typings_outs = _lib.declare_outputs(ctx, _lib.calculate_typings_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration, ctx.attr.composite, ctx.attr.allow_js)) - typing_maps_outs = _lib.declare_outputs(ctx, _lib.calculate_typing_maps_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration_map, ctx.attr.allow_js)) - - arguments = ctx.actions.args() - execution_requirements = {} - progress_prefix = "Compiling TypeScript project" - - if ctx.attr.supports_workers: - # Set to use a multiline param-file for worker mode - arguments.use_param_file("@%s", use_always = True) - arguments.set_param_file_format("multiline") - execution_requirements["supports-workers"] = "1" - execution_requirements["worker-key-mnemonic"] = "TsProject" - progress_prefix = "Compiling TypeScript project (worker mode)" - - # Add user specified arguments *before* rule supplied arguments - arguments.add_all(ctx.attr.args) - - arguments.add_all([ - "--project", - ctx.file.tsconfig.path, - "--outDir", - _lib.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, ctx.attr.out_dir), - "--rootDir", - _lib.calculate_root_dir(ctx), - ]) - if len(typings_outs) > 0: - declaration_dir = ctx.attr.declaration_dir if ctx.attr.declaration_dir else ctx.attr.out_dir - arguments.add_all([ - "--declarationDir", - _lib.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, declaration_dir), - ]) - - # When users report problems, we can ask them to re-build with - # --define=VERBOSE_LOGS=1 - # so anything that's useful to diagnose rule failures belongs here - if "VERBOSE_LOGS" in ctx.var.keys(): - arguments.add_all([ - # What files were in the ts.Program - "--listFiles", - # Did tsc write all outputs to the place we expect to find them? - "--listEmittedFiles", - # Why did module resolution fail? - "--traceResolution", - # Why was the build slow? - "--diagnostics", - "--extendedDiagnostics", - ]) - - transitive_inputs = [] - inputs = ctx.files.srcs[:] - for dep in ctx.attr.deps: - if TsConfigInfo in dep: - transitive_inputs.append(dep[TsConfigInfo].deps) - if ExternalNpmPackageInfo != None and ExternalNpmPackageInfo in dep: - # TODO: we could maybe filter these to be tsconfig.json or *.d.ts only - # we don't expect tsc wants to read any other files from npm packages. - transitive_inputs.append(dep[ExternalNpmPackageInfo].sources) - if DeclarationInfo in dep: - transitive_inputs.append(dep[DeclarationInfo].transitive_declarations) - if ValidOptionsInfo in dep: - transitive_inputs.append(depset([dep[ValidOptionsInfo].marker])) - - # Gather TsConfig info from both the direct (tsconfig) and indirect (extends) attribute - tsconfig_inputs = _validate_lib.tsconfig_inputs(ctx) - transitive_inputs.append(tsconfig_inputs) - - # We do not try to predeclare json_outs, because their output locations generally conflict with their path in the source tree. - # (The exception is when out_dir is used, then the .json output is a different path than the input.) - # However tsc will copy .json srcs to the output tree so we want to declare these outputs to include along with .js Default outs - # NB: We don't have emit_declaration_only setting here, so use presence of any JS outputs as an equivalent. - # tsc will only produce .json if it also produces .js - if len(js_outs): - pkg_len = len(ctx.label.package) + 1 if len(ctx.label.package) else 0 - rootdir_replace_pattern = ctx.attr.root_dir + "/" if ctx.attr.root_dir else "" - json_outs = _lib.declare_outputs(ctx, [ - _lib.join(ctx.attr.out_dir, src.short_path[pkg_len:].replace(rootdir_replace_pattern, "")) - for src in ctx.files.srcs - if src.basename.endswith(".json") and src.is_source - ]) - else: - json_outs = [] - - outputs = json_outs + js_outs + map_outs + typings_outs + typing_maps_outs - if ctx.outputs.buildinfo_out: - arguments.add_all([ - "--tsBuildInfoFile", - ctx.outputs.buildinfo_out.path, - ]) - outputs.append(ctx.outputs.buildinfo_out) - runtime_outputs = json_outs + js_outs + map_outs - typings_outputs = typings_outs + typing_maps_outs + [s for s in ctx.files.srcs if s.path.endswith(".d.ts")] - - if not js_outs and not typings_outputs and not ctx.attr.deps: - label = "//{}:{}".format(ctx.label.package, ctx.label.name) - if ctx.attr.transpile: - no_outs_msg = """ts_project target %s is configured to produce no outputs. - -This might be because -- you configured it with `noEmit` -- the `srcs` are empty -""" % label - else: - no_outs_msg = "ts_project target %s with custom transpiler needs `declaration = True`." % label - fail(no_outs_msg + """ -This is an error because Bazel does not run actions unless their outputs are needed for the requested targets to build. -""") - - if ctx.attr.transpile: - default_outputs_depset = depset(runtime_outputs) if len(runtime_outputs) else depset(typings_outputs) - else: - # We must avoid tsc writing any JS files in this case, as tsc was only run for typings, and some other - # action will try to write the JS files. We must avoid collisions where two actions write the same file. - arguments.add("--emitDeclarationOnly") - - # We don't produce any DefaultInfo outputs in this case, because we avoid running the tsc action - # unless the DeclarationInfo is requested. - default_outputs_depset = depset([]) - - if len(outputs) > 0: - run_action_kwargs = { - "inputs": depset(inputs, transitive = transitive_inputs), - "arguments": [arguments], - "outputs": outputs, - "mnemonic": "TsProject", - "execution_requirements": execution_requirements, - "progress_message": "%s %s [tsc -p %s]" % ( - progress_prefix, - ctx.label, - ctx.file.tsconfig.short_path, - ), - } - if run_action != None: - run_action( - ctx, - link_workspace_root = ctx.attr.link_workspace_root, - executable = "tsc", - **run_action_kwargs - ) - else: - ctx.actions.run( - executable = ctx.executable.tsc, - **run_action_kwargs - ) - - providers = [ - # DefaultInfo is what you see on the command-line for a built library, - # and determines what files are used by a simple non-provider-aware - # downstream library. - # Only the JavaScript outputs are intended for use in non-TS-aware - # dependents. - DefaultInfo( - files = default_outputs_depset, - runfiles = ctx.runfiles( - transitive_files = depset(ctx.files.data, transitive = [ - default_outputs_depset, - ]), - collect_default = True, - ), - ), - js_module_info( - sources = depset(runtime_outputs), - deps = ctx.attr.deps, - ), - TsConfigInfo(deps = depset(transitive = [tsconfig_inputs] + [ - dep[TsConfigInfo].deps - for dep in ctx.attr.deps - if TsConfigInfo in dep - ])), - coverage_common.instrumented_files_info( - ctx, - source_attributes = ["srcs"], - dependency_attributes = ["deps"], - extensions = ["ts", "tsx"], - ), - ] - - # Only provide DeclarationInfo if there are some typings. - # Improves error messaging if a ts_project is missing declaration = True - typings_in_deps = [d for d in ctx.attr.deps if DeclarationInfo in d] - if len(typings_outputs) or len(typings_in_deps): - providers.append(declaration_info(depset(typings_outputs), typings_in_deps)) - providers.append(OutputGroupInfo(types = depset(typings_outputs))) - - return providers - -ts_project = struct( - implementation = _ts_project_impl, - attrs = dicts.add(COMPILER_OPTION_ATTRS, STD_ATTRS, OUTPUT_ATTRS), -) diff --git a/nodejs/private/ts_project_options_validator.js b/nodejs/private/ts_project_options_validator.js deleted file mode 100755 index 119b1b9872..0000000000 --- a/nodejs/private/ts_project_options_validator.js +++ /dev/null @@ -1,144 +0,0 @@ -"use strict"; -exports.__esModule = true; -var path_1 = require("path"); -var ts = require("typescript"); -var diagnosticsHost = { - getCurrentDirectory: function () { return ts.sys.getCurrentDirectory(); }, - getNewLine: function () { return ts.sys.newLine; }, - // Print filenames including their relativeRoot, so they can be located on - // disk - getCanonicalFileName: function (f) { return f; } -}; -function main(_a) { - var _b; - var tsconfigPath = _a[0], output = _a[1], target = _a[2], attrsStr = _a[3]; - // The Bazel ts_project attributes were json-encoded - // (on Windows the quotes seem to be quoted wrong, so replace backslash with quotes :shrug:) - var attrs = JSON.parse(attrsStr.replace(/\\/g, '"')); - // Parse your typescript settings from the tsconfig - // This will understand the "extends" semantics. - var _c = ts.readConfigFile(tsconfigPath, ts.sys.readFile), config = _c.config, error = _c.error; - if (error) - throw new Error(tsconfigPath + ':' + ts.formatDiagnostic(error, diagnosticsHost)); - var _d = ts.parseJsonConfigFileContent(config, ts.sys, path_1.dirname(tsconfigPath)), errors = _d.errors, options = _d.options; - // We don't pass the srcs to this action, so it can't know if the program has the right sources. - // Diagnostics look like - // error TS18002: The 'files' list in config file 'tsconfig.json' is empty. - // error TS18003: No inputs were found in config file 'tsconfig.json'. Specified 'include'... - var fatalErrors = errors.filter(function (e) { return e.code !== 18002 && e.code !== 18003; }); - if (fatalErrors.length > 0) - throw new Error(tsconfigPath + ':' + ts.formatDiagnostics(fatalErrors, diagnosticsHost)); - var failures = []; - var buildozerCmds = []; - function getTsOption(option) { - if (typeof (options[option]) === 'string') { - // Currently the only string-typed options are filepaths. - // TypeScript will resolve these to a project path - // so when echoing that back to the user, we need to reverse that resolution. - // First turn //path/to/pkg:tsconfig into path/to/pkg - var packageDir = target.substr(2, target.indexOf(':') - 2); - return path_1.relative(packageDir, options[option]); - } - return options[option]; - } - function check(option, attr) { - attr = attr || option; - // treat compilerOptions undefined as false - var optionVal = getTsOption(option); - var match = optionVal === attrs[attr] || - (optionVal === undefined && (attrs[attr] === false || attrs[attr] === '')); - if (!match) { - failures.push("attribute " + attr + "=" + attrs[attr] + " does not match compilerOptions." + option + "=" + optionVal); - if (typeof (optionVal) === 'boolean') { - buildozerCmds.push("set " + attr + " " + (optionVal ? 'True' : 'False')); - } - else if (typeof (optionVal) === 'string') { - buildozerCmds.push("set " + attr + " \"" + optionVal + "\""); - } - else if (optionVal === undefined) { - // nothing to sync - } - else { - throw new Error("cannot check option " + option + " of type " + typeof (option)); - } - } - } - var jsxEmit = (_b = {}, - _b[ts.JsxEmit.None] = 'none', - _b[ts.JsxEmit.Preserve] = 'preserve', - _b[ts.JsxEmit.React] = 'react', - _b[ts.JsxEmit.ReactNative] = 'react-native', - _b[ts.JsxEmit.ReactJSX] = 'react-jsx', - _b[ts.JsxEmit.ReactJSXDev] = 'react-jsx-dev', - _b); - function check_preserve_jsx() { - var attr = 'preserve_jsx'; - var jsxVal = options['jsx']; - if ((jsxVal === ts.JsxEmit.Preserve) !== Boolean(attrs[attr])) { - failures.push("attribute " + attr + "=" + attrs[attr] + " does not match compilerOptions.jsx=" + jsxEmit[jsxVal]); - buildozerCmds.push("set " + attr + " " + (jsxVal === ts.JsxEmit.Preserve ? 'True' : 'False')); - } - } - if (options.noEmit) { - console.error("ERROR: ts_project rule " + target + " cannot be built because the 'noEmit' option is specified in the tsconfig."); - console.error('This is not compatible with ts_project, which always produces outputs.'); - console.error('- If you mean to only typecheck the code, use the tsc_test rule instead.'); - console.error(' (See the Alternatives section in the documentation.)'); - console.error('- Otherwise, remove the noEmit option from tsconfig and try again.'); - return 1; - } - // When there are dependencies on other ts_project targets, the tsconfig must be configured - // to help TypeScript resolve them. - if (attrs.has_local_deps) { - var rootDirsValid = true; - if (!options.rootDirs) { - console.error("ERROR: ts_project rule " + target + " is configured without rootDirs."); - rootDirsValid = false; - } - else if (!options.rootDirs.some(function (d) { return d.startsWith(process.env['BINDIR']); })) { - console.error("ERROR: ts_project rule " + target + " is missing a needed rootDir under " + process.env['BINDIR'] + "."); - console.error('Found only: ', options.rootDirs); - rootDirsValid = false; - } - if (!rootDirsValid) { - console.error('This makes it likely that TypeScript will be unable to resolve dependencies using relative import paths'); - console.error("For example, if you 'import {} from ./foo', this expects to resolve foo.d.ts from Bazel's output tree"); - console.error('and TypeScript only knows how to locate this when the rootDirs attribute includes that path.'); - console.error('See the ts_project documentation: https://bazelbuild.github.io/rules_nodejs/TypeScript.html#ts_project'); - return 1; - } - } - check('allowJs', 'allow_js'); - check('declarationMap', 'declaration_map'); - check('emitDeclarationOnly', 'emit_declaration_only'); - check('resolveJsonModule', 'resolve_json_module'); - check('sourceMap', 'source_map'); - check('composite'); - check('declaration'); - check('incremental'); - check('tsBuildInfoFile', 'ts_build_info_file'); - check_preserve_jsx(); - if (failures.length > 0) { - console.error("ERROR: ts_project rule " + target + " was configured with attributes that don't match the tsconfig"); - failures.forEach(function (f) { return console.error(' - ' + f); }); - console.error('You can automatically fix this by running:'); - console.error(" npx @bazel/buildozer " + buildozerCmds.map(function (c) { return "'" + c + "'"; }).join(' ') + " " + target); - return 1; - } - // We have to write an output so that Bazel needs to execute this action. - // Make the output change whenever the attributes changed. - require('fs').writeFileSync(output, "\n// checked attributes for " + target + "\n// allow_js: " + attrs.allow_js + "\n// composite: " + attrs.composite + "\n// declaration: " + attrs.declaration + "\n// declaration_map: " + attrs.declaration_map + "\n// incremental: " + attrs.incremental + "\n// source_map: " + attrs.source_map + "\n// emit_declaration_only: " + attrs.emit_declaration_only + "\n// ts_build_info_file: " + attrs.ts_build_info_file + "\n// preserve_jsx: " + attrs.preserve_jsx + "\n", 'utf-8'); - return 0; -} -if (require.main === module) { - try { - process.exitCode = main(process.argv.slice(2)); - if (process.exitCode != 0) { - console.error('Or to suppress this error, run:'); - console.error(" npx @bazel/buildozer 'set validate False' " + process.argv[4]); - } - } - catch (e) { - console.error(process.argv[1], e); - } -} diff --git a/nodejs/private/ts_validate_options.bzl b/nodejs/private/ts_validate_options.bzl deleted file mode 100644 index 119662dfab..0000000000 --- a/nodejs/private/ts_validate_options.bzl +++ /dev/null @@ -1,79 +0,0 @@ -"Helper rule to check that ts_project attributes match tsconfig.json properties" - -load(":ts_config.bzl", "TsConfigInfo") -load(":ts_lib.bzl", "COMPILER_OPTION_ATTRS", "ValidOptionsInfo") - -def _tsconfig_inputs(ctx): - """Returns all transitively referenced tsconfig files from "tsconfig" and "extends" attributes.""" - inputs = [] - if TsConfigInfo in ctx.attr.tsconfig: - inputs.append(ctx.attr.tsconfig[TsConfigInfo].deps) - else: - inputs.append(depset([ctx.file.tsconfig])) - if hasattr(ctx.attr, "extends") and ctx.attr.extends: - if TsConfigInfo in ctx.attr.extends: - inputs.append(ctx.attr.extends[TsConfigInfo].deps) - else: - inputs.append(ctx.attr.extends.files) - return depset(transitive = inputs) - -def _validate_options_impl(ctx, run_action = None): - # Bazel won't run our action unless its output is needed, so make a marker file - # We make it a .d.ts file so we can plumb it to the deps of the ts_project compile. - marker = ctx.actions.declare_file("%s.optionsvalid.d.ts" % ctx.label.name) - - arguments = ctx.actions.args() - config = struct( - allow_js = ctx.attr.allow_js, - declaration = ctx.attr.declaration, - declaration_map = ctx.attr.declaration_map, - has_local_deps = ctx.attr.has_local_deps, - preserve_jsx = ctx.attr.preserve_jsx, - composite = ctx.attr.composite, - emit_declaration_only = ctx.attr.emit_declaration_only, - resolve_json_module = ctx.attr.resolve_json_module, - source_map = ctx.attr.source_map, - incremental = ctx.attr.incremental, - ts_build_info_file = ctx.attr.ts_build_info_file, - ) - arguments.add_all([ctx.file.tsconfig.path, marker.path, ctx.attr.target, json.encode(config)]) - - inputs = _tsconfig_inputs(ctx) - - run_action_kwargs = { - "inputs": inputs, - "outputs": [marker], - "arguments": [arguments], - "env": { - "BINDIR": ctx.var["BINDIR"], - }, - } - if run_action != None: - run_action( - ctx, - executable = "validator", - **run_action_kwargs - ) - else: - ctx.actions.run( - executable = ctx.executable.validator, - **run_action_kwargs - ) - - return [ - ValidOptionsInfo(marker = marker), - ] - -_ATTRS = dict(COMPILER_OPTION_ATTRS, **{ - "has_local_deps": attr.bool(doc = "Whether any of the deps are in the local workspace"), - "target": attr.string(), - "ts_build_info_file": attr.string(), - "tsconfig": attr.label(mandatory = True, allow_single_file = [".json"]), - "validator": attr.label(mandatory = True, executable = True, cfg = "exec"), -}) - -lib = struct( - attrs = _ATTRS, - implementation = _validate_options_impl, - tsconfig_inputs = _tsconfig_inputs, -) diff --git a/packages/runfiles/BUILD.bazel b/packages/runfiles/BUILD.bazel index 2997128a32..477c181ff9 100644 --- a/packages/runfiles/BUILD.bazel +++ b/packages/runfiles/BUILD.bazel @@ -1,8 +1,8 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("@build_bazel_rules_nodejs//:tools/defaults.bzl", "pkg_npm") -load("//packages/typescript:index.bzl", "ts_project") +load("//internal:tsc.bzl", "tsc") -ts_project( +tsc( name = "runfiles_lib", srcs = glob(["*.ts"]), tsconfig = "tsconfig.json", diff --git a/packages/typescript/BUILD.bazel b/packages/typescript/BUILD.bazel deleted file mode 100644 index e69de29bb2..0000000000