From f6e5fbe38c0ffc63fdc90c91222ab73d789d0111 Mon Sep 17 00:00:00 2001 From: Duarte Nunes Date: Tue, 30 Mar 2021 14:03:03 -0300 Subject: [PATCH] feat(typescript): add support for "jsx: preserve" compiler option When tsc is instructed to preserve jsx, the emitted files have a .jsx extension for .tsx and .jsx inputs, which means ts_project needs to be aware of the option to properly define outputs. --- packages/typescript/internal/ts_project.bzl | 37 +++++++++++++++--- .../internal/ts_project_options_validator.ts | 21 ++++++++++ .../test/ts_project/jsx/BUILD.bazel | 38 +++++++++++++++++++ packages/typescript/test/ts_project/jsx/a.tsx | 1 + packages/typescript/test/ts_project/jsx/b.jsx | 1 + .../test/ts_project/jsx/tsconfig.json | 10 +++++ .../test/ts_project/jsx/verify-preserve.js | 8 ++++ 7 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 packages/typescript/test/ts_project/jsx/BUILD.bazel create mode 100644 packages/typescript/test/ts_project/jsx/a.tsx create mode 100644 packages/typescript/test/ts_project/jsx/b.jsx create mode 100644 packages/typescript/test/ts_project/jsx/tsconfig.json create mode 100644 packages/typescript/test/ts_project/jsx/verify-preserve.js diff --git a/packages/typescript/internal/ts_project.bzl b/packages/typescript/internal/ts_project.bzl index a216aa6d95..468753116d 100644 --- a/packages/typescript/internal/ts_project.bzl +++ b/packages/typescript/internal/ts_project.bzl @@ -269,6 +269,7 @@ def _validate_options_impl(ctx): allow_js = ctx.attr.allow_js, declaration = ctx.attr.declaration, declaration_map = ctx.attr.declaration_map, + preserve_jsx = ctx.attr.preserve_jsx, composite = ctx.attr.composite, emit_declaration_only = ctx.attr.emit_declaration_only, source_map = ctx.attr.source_map, @@ -296,6 +297,7 @@ validate_options = rule( "composite": attr.bool(), "declaration": attr.bool(), "declaration_map": attr.bool(), + "preserve_jsx": attr.bool(), "emit_declaration_only": attr.bool(), "extends": attr.label(allow_files = [".json"]), "incremental": attr.bool(), @@ -312,10 +314,20 @@ def _is_ts_src(src, allow_js): return True return allow_js and (src.endswith(".js") or src.endswith(".jsx")) -def _out_paths(srcs, outdir, rootdir, allow_js, ext): +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, outdir, rootdir, allow_js, ext_map): rootdir_replace_pattern = rootdir + "/" if rootdir else "" return [ - _join(outdir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + ext) + _join(outdir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + _replace_ext(f, ext_map)) for f in srcs if _is_ts_src(f, allow_js) ] @@ -331,6 +343,7 @@ def ts_project_macro( declaration = False, source_map = False, declaration_map = False, + preserve_jsx = False, composite = False, incremental = False, emit_declaration_only = False, @@ -551,6 +564,8 @@ def ts_project_macro( Instructs Bazel to expect a `.js.map` output for each `.ts` source. declaration_map: if the `declarationMap` bit is set in the tsconfig. Instructs Bazel to expect a `.d.ts.map` output for each `.ts` source. + preserve_jsx: if the `jsx` value is set to "preserve" in the tsconfig. + Instructs Bazel to expect a `.jsx` or `.jsx.map` output for each `.tsx` source. composite: if the `composite` bit is set in the tsconfig. Instructs Bazel to expect a `.tsbuildinfo` output and a `.d.ts` output for each `.ts` source. incremental: if the `incremental` bit is set in the tsconfig. @@ -620,6 +635,7 @@ def ts_project_macro( declaration = declaration, source_map = source_map, declaration_map = declaration_map, + preserve_jsx = preserve_jsx, composite = composite, incremental = incremental, ts_build_info_file = ts_build_info_file, @@ -660,13 +676,22 @@ def ts_project_macro( typing_maps_outs = [] if not emit_declaration_only: - js_outs.extend(_out_paths(srcs, out_dir, root_dir, allow_js, ".js")) + exts = { + ".tsx": ".jsx", + ".jsx": ".jsx", + "*": ".js", + } if preserve_jsx else {"*": ".js"} + js_outs.extend(_out_paths(srcs, out_dir, root_dir, allow_js, exts)) if source_map and not emit_declaration_only: - map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, ".js.map")) + exts = { + ".tsx": ".jsx.map", + "*": ".js.map", + } if preserve_jsx else {"*": ".js.map"} + map_outs.extend(_out_paths(srcs, out_dir, root_dir, False, exts)) 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, {"*": ".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, {"*": ".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..439822ec1a 100644 --- a/packages/typescript/internal/ts_project_options_validator.ts +++ b/packages/typescript/internal/ts_project_options_validator.ts @@ -64,6 +64,25 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 { } } + const jsxEmit: Record = + { + [ts.JsxEmit.None]: 'none', + [ts.JsxEmit.Preserve]: 'preserve', + [ts.JsxEmit.React]: 'react', + [ts.JsxEmit.ReactNative]: 'react-native', + } + + function + check_preserve_jsx() { + const attr = 'preserve_jsx' + const jsxVal = options['jsx'] as ts.JsxEmit + 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'}`); + } + } + check('allowJs', 'allow_js'); check('declarationMap', 'declaration_map'); check('emitDeclarationOnly', 'emit_declaration_only'); @@ -72,6 +91,7 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 { check('declaration'); check('incremental'); check('tsBuildInfoFile', 'ts_build_info_file'); + check_preserve_jsx(); if (failures.length > 0) { console.error(`ERROR: ts_project rule ${ @@ -98,6 +118,7 @@ function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 { // source_map: ${attrs.source_map} // emit_declaration_only: ${attrs.emit_declaration_only} // ts_build_info_file: ${attrs.ts_build_info_file} +// preserve_jsx: ${attrs.preserve_jsx} `, 'utf-8'); return 0; diff --git a/packages/typescript/test/ts_project/jsx/BUILD.bazel b/packages/typescript/test/ts_project/jsx/BUILD.bazel new file mode 100644 index 0000000000..10b42295a9 --- /dev/null +++ b/packages/typescript/test/ts_project/jsx/BUILD.bazel @@ -0,0 +1,38 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") +load("//packages/typescript:index.bzl", "ts_project") + +# Ensure that a.js produces outDir/a.js, outDir/a.d.ts, and outDir/a.d.ts.map +SRCS = [ + "a.tsx", + "b.jsx", +] + +ts_project( + name = "tsconfig", + srcs = SRCS, + allow_js = True, + declaration = True, + declaration_map = True, + out_dir = "out", + preserve_jsx = True, + source_map = True, +) + +filegroup( + name = "types", + srcs = [":tsconfig"], + output_group = "types", +) + +nodejs_test( + name = "test", + data = [ + ":tsconfig", + ":types", + ], + entry_point = "verify-preserve.js", + templated_args = [ + "$(locations :types)", + "$(locations :tsconfig)", + ], +) diff --git a/packages/typescript/test/ts_project/jsx/a.tsx b/packages/typescript/test/ts_project/jsx/a.tsx new file mode 100644 index 0000000000..0e5fccf6da --- /dev/null +++ b/packages/typescript/test/ts_project/jsx/a.tsx @@ -0,0 +1 @@ +export const A =
a
\ No newline at end of file diff --git a/packages/typescript/test/ts_project/jsx/b.jsx b/packages/typescript/test/ts_project/jsx/b.jsx new file mode 100644 index 0000000000..d5a0def4d0 --- /dev/null +++ b/packages/typescript/test/ts_project/jsx/b.jsx @@ -0,0 +1 @@ +export const B =
b
\ No newline at end of file diff --git a/packages/typescript/test/ts_project/jsx/tsconfig.json b/packages/typescript/test/ts_project/jsx/tsconfig.json new file mode 100644 index 0000000000..762accd9fb --- /dev/null +++ b/packages/typescript/test/ts_project/jsx/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowJs": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "jsx": "preserve", + "types": [] + } +} diff --git a/packages/typescript/test/ts_project/jsx/verify-preserve.js b/packages/typescript/test/ts_project/jsx/verify-preserve.js new file mode 100644 index 0000000000..159763e8cf --- /dev/null +++ b/packages/typescript/test/ts_project/jsx/verify-preserve.js @@ -0,0 +1,8 @@ +const assert = require('assert'); + +const files = process.argv.slice(2); +assert.ok(files.some(f => f.endsWith('out/a.d.ts')), 'Missing a.d.ts'); +assert.ok(files.some(f => f.endsWith('out/a.d.ts.map')), 'Missing a.d.ts.map'); +assert.ok(files.some(f => f.endsWith('out/a.jsx.map'), 'Missing a.jsx.map')); +assert.ok(files.some(f => f.endsWith('out/a.jsx'), 'Missing a.jsx')); +assert.ok(files.some(f => f.endsWith('out/b.jsx'), 'Missing b.jsx'));