From 962e5167f5a3f013d453d66dcdfa64dd43b740da Mon Sep 17 00:00:00 2001 From: Joe Lencioni Date: Wed, 6 Jan 2021 11:10:34 -0600 Subject: [PATCH] feat(typescript): add resolve_json_module support to ts_project As of TypeScript v2.9, `tsc` can read and extract types from `.json` files by using the `resolveJsonModule` option. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---resolvejsonmodule The new behavior allows people to pass `resolve_json_module` to `ts_project` in order to expect `.json` files from `tsc`. This change was modeled after the previous `allow_js` work in #2260. Fixes #2365 --- docs/TypeScript.md | 9 ++- packages/typescript/internal/ts_project.bzl | 66 +++++++++++++------ .../internal/ts_project_options_validator.ts | 2 + .../test/ts_project/json/BUILD.bazel | 3 + .../resolve_json_module/BUILD.bazel | 37 +++++++++++ .../resolve_json_module/config.json | 3 + .../ts_project/resolve_json_module/index.ts | 3 + .../resolve_json_module/tsconfig.json | 10 +++ .../ts_project/resolve_json_module/verify.js | 6 ++ 9 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 packages/typescript/test/ts_project/resolve_json_module/BUILD.bazel create mode 100644 packages/typescript/test/ts_project/resolve_json_module/config.json create mode 100644 packages/typescript/test/ts_project/resolve_json_module/index.ts create mode 100644 packages/typescript/test/ts_project/resolve_json_module/tsconfig.json create mode 100644 packages/typescript/test/ts_project/resolve_json_module/verify.js diff --git a/docs/TypeScript.md b/docs/TypeScript.md index 5adc71042b..df1d1805ca 100755 --- a/docs/TypeScript.md +++ b/docs/TypeScript.md @@ -556,7 +556,7 @@ Defaults to `False` **USAGE**
-ts_project(name, tsconfig, srcs, args, deps, extends, allow_js, declaration, source_map,
+ts_project(name, tsconfig, srcs, args, deps, extends, allow_js, resolve_json_module, declaration, source_map,
            declaration_map, composite, incremental, emit_declaration_only, ts_build_info_file, tsc,
            worker_tsc_bin, worker_typescript_module, validate, supports_workers, declaration_dir,
            out_dir, root_dir, link_workspace_root, kwargs)
@@ -751,6 +751,13 @@ TypeScript will generate .d.ts files from .js files.
 
 Defaults to `False`
 
+

resolve_json_module

+ +boolean; Specifies whether TypeScript will import and extract types from +.json files. + +Defaults to `False` +

declaration

if the `declaration` bit is set in the tsconfig. diff --git a/packages/typescript/internal/ts_project.bzl b/packages/typescript/internal/ts_project.bzl index 3911e4466f..fcc4003bc3 100644 --- a/packages/typescript/internal/ts_project.bzl +++ b/packages/typescript/internal/ts_project.bzl @@ -75,15 +75,16 @@ def _calculate_root_dir(ctx): 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. + # Note we don't have access to the ts_project macro allow_js and + # resolve_json_module params here. For error-handling purposes, we can + # assume that any .js/.jsx/.json 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 + resolve_json_module = True for src in ctx.files.srcs: - if _is_ts_src(src.path, allow_js): + if _is_ts_src(src.path, allow_js, resolve_json_module): if src.is_source: some_source_path = src.path else: @@ -268,6 +269,7 @@ def _validate_options_impl(ctx): declaration_map = ctx.attr.declaration_map, 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, @@ -300,6 +302,7 @@ validate_options = rule( "emit_declaration_only": attr.bool(), "extends": attr.label(allow_files = [".json"]), "incremental": attr.bool(), + "resolve_json_module": attr.bool(), "source_map": attr.bool(), "target": attr.string(), "ts_build_info_file": attr.string(), @@ -308,19 +311,33 @@ validate_options = rule( }, ) -def _is_ts_src(src, allow_js): +def _is_ts_src(src, allow_js, resolve_json_module): if not src.endswith(".d.ts") and (src.endswith(".ts") or src.endswith(".tsx")): return True + + if resolve_json_module and src.endswith(".json"): + return True + return allow_js and (src.endswith(".js") or src.endswith(".jsx")) -def _out_paths(srcs, outdir, rootdir, allow_js, ext): +def _out_paths(srcs, outdir, rootdir, allow_js, resolve_json_module, ext): rootdir_replace_pattern = rootdir + "/" if rootdir else "" - return [ + + out_paths = [ _join(outdir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + ext) for f in srcs - if _is_ts_src(f, allow_js) + if _is_ts_src(f, allow_js, False) ] + if resolve_json_module == True: + out_paths = out_paths + [ + _join(outdir, f.replace(rootdir_replace_pattern, "")) + for f in srcs + if f.endswith(".json") + ] + + return out_paths + def ts_project_macro( name = "tsconfig", tsconfig = None, @@ -329,6 +346,7 @@ def ts_project_macro( deps = [], extends = None, allow_js = False, + resolve_json_module = False, declaration = False, source_map = False, declaration_map = False, @@ -541,6 +559,8 @@ def ts_project_macro( allow_js: boolean; Specifies whether TypeScript will read .js and .jsx files. When used with declaration, TypeScript will generate .d.ts files from .js files. + resolve_json_module: boolean; Specifies whether TypeScript will read import and extract types from + .json files. TypeScript will generate .d.ts files from .json files. declaration_dir: a string specifying a subdirectory under the bazel-out folder where generated declaration outputs are written. Equivalent to the TypeScript --declarationDir option. @@ -568,10 +588,16 @@ def ts_project_macro( """ if srcs == None: + globs = ["**/*.ts", "**/*.tsx"] + if allow_js == True: - srcs = native.glob(["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]) - else: - srcs = native.glob(["**/*.ts", "**/*.tsx"]) + globs = globs + ["**/*.js", "**/*.jsx"] + + if resolve_json_module == True: + globs = globs + ["**/*.json"] + + srcs = native.glob(globs) + extra_deps = [] if type(extends) == type([]): @@ -587,6 +613,7 @@ def ts_project_macro( declaration_map = compiler_options.setdefault("declarationMap", declaration_map) emit_declaration_only = compiler_options.setdefault("emitDeclarationOnly", emit_declaration_only) allow_js = compiler_options.setdefault("allowJs", allow_js) + resolve_json_module = compiler_options.setdefault("resolveJsonModule", resolve_json_module) # These options are always passed on the tsc command line so don't include them # in the tsconfig. At best they're redundant, but at worst we'll have a conflict @@ -601,7 +628,7 @@ def ts_project_macro( write_tsconfig( name = "_gen_tsconfig_%s" % name, config = tsconfig, - files = [s for s in srcs if _is_ts_src(s, allow_js)], + files = [s for s in srcs if _is_ts_src(s, allow_js, resolve_json_module)], extends = Label("//%s:%s" % (native.package_name(), name)).relative(extends) if extends else None, out = "tsconfig_%s.json" % name, ) @@ -626,6 +653,7 @@ def ts_project_macro( ts_build_info_file = ts_build_info_file, emit_declaration_only = emit_declaration_only, allow_js = allow_js, + resolve_json_module = resolve_json_module, tsconfig = tsconfig, extends = extends, ) @@ -668,13 +696,13 @@ def ts_project_macro( typing_maps_outs = [] if not emit_declaration_only: - js_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js")) + js_outs.extend(_out_paths(srcs, out_dir, root_dir, False, resolve_json_module, ".js")) if source_map and not emit_declaration_only: - map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js.map")) + map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, False, ".js.map")) if declaration or composite: - typings_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts")) + typings_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, False, ".d.ts")) if declaration_map: - typing_maps_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, ".d.ts.map")) + typing_maps_outs.extend(_out_paths(srcs, typings_out_dir, root_dir, allow_js, False, ".d.ts.map")) if not len(js_outs) and not len(typings_outs): fail("""ts_project target "//{}:{}" is configured to produce no outputs. diff --git a/packages/typescript/internal/ts_project_options_validator.ts b/packages/typescript/internal/ts_project_options_validator.ts index a98b4df74d..b28573f227 100644 --- a/packages/typescript/internal/ts_project_options_validator.ts +++ b/packages/typescript/internal/ts_project_options_validator.ts @@ -65,6 +65,7 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 { } check('allowJs', 'allow_js'); + check('resolveJsonModule', 'resolve_json_module'); check('declarationMap', 'declaration_map'); check('emitDeclarationOnly', 'emit_declaration_only'); check('sourceMap', 'source_map'); @@ -91,6 +92,7 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 { output, ` // ${process.argv[1]} checked attributes for ${target} // allow_js: ${attrs.allow_js} +// resolve_json_module: ${attrs.resolve_json_module} // composite: ${attrs.composite} // declaration: ${attrs.declaration} // declaration_map: ${attrs.declaration_map} diff --git a/packages/typescript/test/ts_project/json/BUILD.bazel b/packages/typescript/test/ts_project/json/BUILD.bazel index be14cdba0e..3386967bc2 100644 --- a/packages/typescript/test/ts_project/json/BUILD.bazel +++ b/packages/typescript/test/ts_project/json/BUILD.bazel @@ -13,18 +13,21 @@ ts_project( name = "tsconfig", srcs = SRCS, out_dir = "foobar", + validate = False, # resolveJsonModule is in tsconfig.json but not ts_project ) ts_project( name = "tsconfig-no-outdir", srcs = SRCS, tsconfig = "tsconfig.json", + validate = False, # resolveJsonModule is in tsconfig.json but not ts_project ) # Test that we don't try to declare .json outputs when tsc isn't producing any JS ts_project( name = "tsconfig-decl-only", srcs = SRCS, + resolve_json_module = True, tsconfig = { "compilerOptions": { "declaration": True, diff --git a/packages/typescript/test/ts_project/resolve_json_module/BUILD.bazel b/packages/typescript/test/ts_project/resolve_json_module/BUILD.bazel new file mode 100644 index 0000000000..686e0efde2 --- /dev/null +++ b/packages/typescript/test/ts_project/resolve_json_module/BUILD.bazel @@ -0,0 +1,37 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") +load("//packages/typescript:index.bzl", "ts_project") + +# Ensure that config.json produces outDir/config.json +SRCS = [ + "index.ts", + "config.json", +] + +ts_project( + name = "tsconfig", + srcs = SRCS, + declaration = True, + declaration_map = True, + out_dir = "out", + resolve_json_module = True, + source_map = True, +) + +filegroup( + name = "types", + srcs = [":tsconfig"], + output_group = "types", +) + +nodejs_test( + name = "test", + data = [ + ":tsconfig", + ":types", + ], + entry_point = "verify.js", + templated_args = [ + "$(locations :types)", + "$(locations :tsconfig)", + ], +) diff --git a/packages/typescript/test/ts_project/resolve_json_module/config.json b/packages/typescript/test/ts_project/resolve_json_module/config.json new file mode 100644 index 0000000000..7042cb431a --- /dev/null +++ b/packages/typescript/test/ts_project/resolve_json_module/config.json @@ -0,0 +1,3 @@ +{ + "pizza": "yum" +} diff --git a/packages/typescript/test/ts_project/resolve_json_module/index.ts b/packages/typescript/test/ts_project/resolve_json_module/index.ts new file mode 100644 index 0000000000..4ede71a7f6 --- /dev/null +++ b/packages/typescript/test/ts_project/resolve_json_module/index.ts @@ -0,0 +1,3 @@ +import config from './config.json'; + +export const pizza = config.pizza; diff --git a/packages/typescript/test/ts_project/resolve_json_module/tsconfig.json b/packages/typescript/test/ts_project/resolve_json_module/tsconfig.json new file mode 100644 index 0000000000..6a8b0b99f0 --- /dev/null +++ b/packages/typescript/test/ts_project/resolve_json_module/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "types": [] + } +} diff --git a/packages/typescript/test/ts_project/resolve_json_module/verify.js b/packages/typescript/test/ts_project/resolve_json_module/verify.js new file mode 100644 index 0000000000..8dd8c16a7c --- /dev/null +++ b/packages/typescript/test/ts_project/resolve_json_module/verify.js @@ -0,0 +1,6 @@ +const assert = require('assert'); + +const types_files = process.argv.slice(2, 4); +const code_files = process.argv.slice(4, 6); +assert.ok( + code_files.some(f => f.endsWith('out/config.json')), `Missing config.json in ${code_files}`);