Make ts_compile_actions callable from aspects.

This adds fallbacks for cases where a certain attribute isn't present on ctx:
- ctx.attr.compiler falls back to ctx.attr._compiler
- ctx.attr.generate_externs defaults to True
- ctx.attr.runtime defaults to "browser"
- ctx.attr.module_name defaults to None

It also defines an attribute dict for aspects that define a lot of the necessary attributes for compilation (similar to TS_LIB_DECL_ATTRIBUTES).

Finally, it allows callers of compile_ts or ts_compile_actions to define their own  tsconfig output instead o getting it from ctx.outputs (since aspects cannot have outputs).

PiperOrigin-RevId: 256070225
diff --git a/internal/common/compilation.bzl b/internal/common/compilation.bzl
index d173ce7..511fcc0 100644
--- a/internal/common/compilation.bzl
+++ b/internal/common/compilation.bzl
@@ -24,6 +24,10 @@
     module_mappings_aspect,
 ]
 
+_ADDITIONAL_D_TS = attr.label_list(
+    allow_files = True,
+)
+
 # Attributes shared by any typescript-compatible rule (ts_library, ng_module)
 COMMON_ATTRIBUTES = {
     "data": attr.label_list(
@@ -48,9 +52,12 @@
         providers = ["js"],
     ),
     "deps": attr.label_list(aspects = DEPS_ASPECTS),
-    "_additional_d_ts": attr.label_list(
-        allow_files = True,
-    ),
+    "_additional_d_ts": _ADDITIONAL_D_TS,
+}
+
+# Attributes shared by any typescript-compatible aspect.
+ASPECT_ATTRIBUTES = {
+    "_additional_d_ts": _ADDITIONAL_D_TS,
 }
 
 COMMON_OUTPUTS = {
@@ -145,6 +152,19 @@
         type_blacklisted = depset(transitive = type_blacklisted_declarations),
     )
 
+def _should_generate_externs(ctx):
+    """Whether externs should be generated.
+
+    If ctx has a generate_externs attribute, the value of that is returned.
+    Otherwise, this is true."""
+    return getattr(ctx.attr, "generate_externs", True)
+
+def _get_runtime(ctx):
+    """Gets the runtime for the rule.
+
+    Defaults to "browser" if the runtime attr isn't present."""
+    return getattr(ctx.attr, "runtime", "browser")
+
 def _outputs(ctx, label, srcs_files = []):
     """Returns closure js, devmode js, and .d.ts output files.
 
@@ -205,6 +225,7 @@
         devmode_compile_action = None,
         jsx_factory = None,
         tsc_wrapped_tsconfig = None,
+        tsconfig = None,
         outputs = _outputs):
     """Creates actions to compile TypeScript code.
 
@@ -220,6 +241,7 @@
         for devmode.
       jsx_factory: optional string. Enables overriding jsx pragma.
       tsc_wrapped_tsconfig: function that produces a tsconfig object.
+      tsconfig: The tsconfig file to output, if other than ctx.outputs.tsconfig.
       outputs: function from a ctx to the expected compilation outputs.
 
     Returns:
@@ -229,6 +251,7 @@
     ### Collect srcs and outputs.
     srcs = srcs if srcs != None else ctx.attr.srcs
     deps = deps if deps != None else ctx.attr.deps
+    tsconfig = tsconfig if tsconfig != None else ctx.outputs.tsconfig
     srcs_files = [f for t in srcs for f in t.files.to_list()]
     src_declarations = []  # d.ts found in inputs.
     tsickle_externs = []  # externs.js generated by tsickle, if any.
@@ -267,14 +290,14 @@
     transpiled_devmode_js = outs.devmode_js
     gen_declarations = outs.declarations
 
-    if has_sources and ctx.attr.runtime != "nodejs":
+    if has_sources and _get_runtime(ctx) != "nodejs":
         # Note: setting this variable controls whether tsickle is run at all.
         tsickle_externs = [ctx.actions.declare_file(ctx.label.name + ".externs.js")]
 
     dep_declarations = _collect_dep_declarations(ctx, deps)
     input_declarations = depset(src_declarations, transitive = [dep_declarations.transitive])
     type_blacklisted_declarations = dep_declarations.type_blacklisted
-    if not is_library and not ctx.attr.generate_externs:
+    if not is_library and not _should_generate_externs(ctx):
         type_blacklisted_declarations += srcs_files
 
     # The depsets of output files. These are the files that are always built
@@ -339,7 +362,7 @@
         files_depsets.append(depset([perf_trace_file, profile_file]))
 
     ctx.actions.write(
-        output = ctx.outputs.tsconfig,
+        output = tsconfig,
         content = json_marshal(tsconfig_es6),
     )
 
@@ -348,12 +371,12 @@
     replay_params = None
 
     if has_sources:
-        inputs = compilation_inputs + [ctx.outputs.tsconfig]
+        inputs = compilation_inputs + [tsconfig]
         replay_params = compile_action(
             ctx,
             inputs,
             outputs,
-            ctx.outputs.tsconfig,
+            tsconfig,
             node_profile_args,
         )
 
@@ -460,7 +483,7 @@
         # Expose the module_name so that packaging rules can access it.
         # e.g. rollup_bundle under Bazel needs to convert this into a UMD global
         # name in the Rollup configuration.
-        "module_name": ctx.attr.module_name,
+        "module_name": getattr(ctx.attr, "module_name", None),
         "output_groups": {
             "es5_sources": es5_sources,
             "es6_sources": es6_sources,
@@ -473,7 +496,7 @@
             collect_data = True,
         ),
         # Expose the tags so that a Skylark aspect can access them.
-        "tags": ctx.attr.tags,
+        "tags": ctx.attr.tags if hasattr(ctx.attr, "tags") else ctx.rule.attr.tags,
         # TODO(martinprobst): Prune transitive deps, only re-export what's needed.
         "typescript": {
             "declarations": depset(transitive = declarations_depsets),