blob: 329acff22e1c709cad17ce9a1fe834a59e48f2e2 [file]
"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, "") + _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,
)