Skip to content

Commit

Permalink
feat(typescript): moved file functions to tslb.bzl
Browse files Browse the repository at this point in the history
  • Loading branch information
twheys authored and alexeagle committed Feb 2, 2022
1 parent 96d37b6 commit 20c5c58
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 102 deletions.
5 changes: 4 additions & 1 deletion packages/typescript/index.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Users should not load files under "/internal"

load("@build_bazel_rules_nodejs//internal/node:node.bzl", "nodejs_binary")
load("//packages/typescript/internal:ts_config.bzl", "write_tsconfig", _ts_config = "ts_config")
load("//packages/typescript/internal:ts_project.bzl", _lib = "lib", _ts_project = "ts_project")
load("//packages/typescript/internal:ts_project.bzl", _ts_project = "ts_project")
load("//packages/typescript/internal:tslib.bzl", _lib = "lib")
load("//packages/typescript/internal:validate_options.bzl", "validate_options")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("@bazel_skylib//lib:partial.bzl", "partial")
Expand Down Expand Up @@ -405,6 +406,8 @@ def ts_project(
files = srcs,
extends = Label("%s//%s:%s" % (native.repository_name(), native.package_name(), name)).relative(extends) if extends else None,
out = "tsconfig_%s.json" % name,
allow_js = allow_js,
resolve_json_module = resolve_json_module,
)

# From here, tsconfig becomes a file, the same as if the
Expand Down
21 changes: 18 additions & 3 deletions packages/typescript/internal/ts_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

"tsconfig.json files using extends"

load(":tslib.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
Expand Down Expand Up @@ -77,6 +79,13 @@ def _relative_path(tsconfig, dest):
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)
Expand All @@ -85,10 +94,12 @@ def _write_tsconfig_rule(ctx):
"__extends__",
_relative_path(ctx.outputs.out, ctx.file.extends),
)
if ctx.attr.files:

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 ctx.files.files]),
str([_relative_path(ctx.outputs.out, f) for f in filtered_files]),
)
ctx.actions.write(
output = ctx.outputs.out,
Expand All @@ -103,11 +114,13 @@ write_tsconfig_rule = rule(
"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):
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:
Expand All @@ -133,4 +146,6 @@ def write_tsconfig(name, config, files, out, extends = None):
extends = extends,
content = [json.encode(amended_config)],
out = out,
allow_js = allow_js,
resolve_json_module = resolve_json_module,
)
109 changes: 11 additions & 98 deletions packages/typescript/internal/ts_project.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
load("@rules_nodejs//nodejs:providers.bzl", "DeclarationInfo", "declaration_info", "js_module_info")
load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "run_node")
load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "module_mappings_aspect")
load(":tslib.bzl", _lib = "lib")
load(":ts_config.bzl", "TsConfigInfo")
load(":validate_options.bzl", "ValidOptionsInfo", _validate_lib = "lib")

Expand Down Expand Up @@ -50,19 +51,6 @@ _OUTPUTS = {
"typings_outs": attr.output_list(),
}

def _join(*elements):
segments = [f for f in elements if f]
if len(segments):
return "/".join(segments)
return "."

def _relative_to_package(path, ctx):
for prefix in (ctx.bin_dir.path, ctx.label.package):
prefix += "/"
if path.startswith(prefix):
path = path[len(prefix):]
return path

def _declare_outputs(ctx, paths):
return [
ctx.actions.declare_file(path)
Expand All @@ -82,7 +70,7 @@ def _calculate_root_dir(ctx):
# 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 _lib.is_ts_src(src.path, allow_js):
if src.is_source:
some_source_path = src.path
else:
Expand All @@ -95,26 +83,26 @@ def _calculate_root_dir(ctx):
" found generated file %s and source file %s" %
(some_generated_path, some_source_path))

return _join(
return _lib.join(
root_path,
ctx.label.workspace_root,
ctx.label.package,
ctx.attr.root_dir,
)

def _ts_project_impl(ctx):
srcs = [_relative_to_package(src.path, ctx) for src in ctx.files.srcs]
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 = _declare_outputs(ctx, [] if not ctx.attr.transpile else _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 = _declare_outputs(ctx, [] if not ctx.attr.transpile else _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 = _declare_outputs(ctx, _calculate_typings_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration, ctx.attr.composite, ctx.attr.allow_js))
typing_maps_outs = _declare_outputs(ctx, _calculate_typing_maps_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration_map, ctx.attr.allow_js))
js_outs = _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 = _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 = _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 = _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 = {}
Expand All @@ -135,15 +123,15 @@ def _ts_project_impl(ctx):
"--project",
ctx.file.tsconfig.path,
"--outDir",
_join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, ctx.attr.out_dir),
_lib.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, ctx.attr.out_dir),
"--rootDir",
_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",
_join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, declaration_dir),
_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
Expand Down Expand Up @@ -190,7 +178,7 @@ def _ts_project_impl(ctx):
if len(js_outs):
pkg_len = len(ctx.label.package) + 1 if len(ctx.label.package) else 0
json_outs = [
ctx.actions.declare_file(_join(ctx.attr.out_dir, src.short_path[pkg_len:]))
ctx.actions.declare_file(_lib.join(ctx.attr.out_dir, src.short_path[pkg_len:]))
for src in ctx.files.srcs
if src.basename.endswith(".json") and src.is_source
]
Expand Down Expand Up @@ -295,78 +283,3 @@ ts_project = rule(
implementation = _ts_project_impl,
attrs = dict(_ATTRS, **_OUTPUTS),
)

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)

lib = struct(
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,
)
92 changes: 92 additions & 0 deletions packages/typescript/internal/tslib.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"Utilities functions for selecting and filtering ts and other files"

def _join(*elements):
segments = [f for f in elements if f]
if len(segments):
return "/".join(segments)
return "."

def _relative_to_package(path, ctx):
for prefix in (ctx.bin_dir.path, ctx.label.package):
prefix += "/"
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)

lib = struct(
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,
)

0 comments on commit 20c5c58

Please sign in to comment.