# Copyright 2024 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Copyright 2019 The Chromium Authors.  All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/toolchain/rbe.gni")
import("//build/toolchain/siso.gni")
import("../ninja/vars.gni")

declare_args() {
  # Set to true to enable remote compilation of TypeScript using reclient.
  # This flag is temporarily until DevTools reclient support has stabilized.
  # At that point, this flag will be folded together with "use_remoteexec".
  # TODO(crbug.com/1139220): Remove the flag once we are confident.
  devtools_use_remoteexec = false

  devtools_skip_typecheck = build_with_chromium && !is_official_build
}

assert(!devtools_skip_typecheck || !is_official_build,
       "Official build should not skip typecheck")

# Defines a target that compiles .ts files using TypeScript.
# A temporary tsconfig.json is generated which uses the
# tsconfig.json in this folder as basis.
#
# Either `sources` or `deps` must be listed (or both).
#
# For tests, you must specify the `testonly = true` argument:
#
# Variables:
#   sources (optional):
#     List of TypeScript files to typecheck and generate
#   deps (optional):
#     List of dependencies that are ts_libraries or export a tsconfig.json
# Example:
#   ts_library("main") {
#     sources = ["bar.js", "foo.ts"]
#     deps = ["../common"]
#   }
#
#   ts_library("unittest") {
#     testonly = true
#     sources = [ "bar_test.ts" ]
#     deps = [ "../../../../front_end/common" ]
#   }
#
template("ts_library") {
  action(target_name) {
    script =
        devtools_location_prepend + "scripts/build/typescript/ts_library.py"

    forward_variables_from(invoker,
                           [
                             "sources",
                             "visibility",
                             "deps",
                             "public_deps",
                             "inputs",
                           ])

    inputs += [
      "//third_party/node/node.py",
      "//third_party/node/node_modules.py",
      devtools_location_prepend + "scripts/devtools_paths.py",
      devtools_location_prepend + "config/typescript/tsconfig.base.json",
      devtools_location_prepend + "node_modules/typescript/lib/tsc.js",
      devtools_location_prepend + "front_end/legacy/legacy-defs.d.ts",
      devtools_location_prepend + "front_end/global_typings/global_defs.d.ts",
      devtools_location_prepend + "node_modules/@types/filesystem/index.d.ts",
      devtools_location_prepend +
          "node_modules/@types/wicg-task-scheduling/index.d.ts",
    ]

    # When use_remoteexec=true, node actions run on remote Linux worker.
    # So it should include linux node binary in inputs.
    if (use_remoteexec) {
      inputs += [ "//third_party/node/linux/node-linux-x64/bin/node" ]
    }

    _typescript_config_name = target_name

    if (defined(invoker.typescript_config_name)) {
      _typescript_config_name = invoker.typescript_config_name
    }

    args = [
      "--tsconfig_output_location",
      rebase_path(target_gen_dir, root_build_dir) +
          "/$_typescript_config_name-tsconfig.json",
    ]

    assert(
        defined(sources) || defined(deps) || defined(invoker.sourceslist),
        "You must either specify an array of 'sources' or 'deps' or a 'sourceslist' for $target_name")

    _test_only = defined(invoker.testonly) && invoker.testonly
    _no_emit = defined(invoker.no_emit) && invoker.no_emit
    _tsconfig_only = defined(invoker.tsconfigonly) && invoker.tsconfigonly
    _verify_lib_check =
        defined(invoker.verify_lib_check) && invoker.verify_lib_check
    _rootdir = "."

    if (defined(invoker.rootdir)) {
      _rootdir = invoker.rootdir
    }

    if (!defined(public_deps)) {
      public_deps = []
    }

    if (defined(deps)) {
      args += [ "--deps" ]
      foreach(dep, deps) {
        args += [ rebase_path(get_label_info(dep, "dir"), ".") + "/" +
                  get_label_info(dep, "name") + "-tsconfig.json" ]
      }

      # We need to expose our deps in the Ninja chain, to allow `generate_devtools_grd`
      # to eventually roll up all files. If these are not public, Ninja complains that
      # the files are not reachable.
      public_deps += deps
    }

    # Here we assume that all sources are in this folder listed,
    # as there is no way of retrieving the folder the template
    # is in with Ninja.
    args += [
      "--front_end_directory",
      rebase_path(_rootdir, root_build_dir),
    ]

    if (_test_only) {
      args += [ "--test-only" ]
      inputs += [
        devtools_location_prepend + "node_modules/@types/chai/index.d.ts",
        devtools_location_prepend + "node_modules/@types/mocha/index.d.ts",
        devtools_location_prepend + "node_modules/@types/sinon/index.d.ts",
        devtools_location_prepend +
            "node_modules/@types/karma-chai-sinon/index.d.ts",
      ]
    }

    if (_tsconfig_only) {
      args += [ "--tsconfig-only" ]
    }

    if (defined(invoker.sourceslist)) {
      args += [
        "--sources-list",
        rebase_path(invoker.sourceslist, root_build_dir),
      ]
    }

    if (_no_emit) {
      args += [ "--no-emit" ]
    }

    if (_verify_lib_check) {
      args += [ "--verify-lib-check" ]
    }

    if (defined(invoker.module)) {
      args += [ "--module=" + invoker.module ]
    }

    if (defined(invoker.reset_timestamps) && invoker.reset_timestamps) {
      args += [ "--reset_timestamps" ]
    }

    if (defined(invoker.is_web_worker) && invoker.is_web_worker) {
      args += [ "--is_web_worker" ]
    }

    if (defined(use_remoteexec) && use_remoteexec &&
        defined(devtools_use_remoteexec) && devtools_use_remoteexec) {
      # Allow ts_library template invocations to override user provided flags.
      if (!defined(invoker.use_remoteexec) || invoker.use_remoteexec) {
        args += [
          "--use-remoteexec",
          "--rewrapper-binary=${rbe_bin_dir}/rewrapper",
          "--rewrapper-cfg=${rbe_cc_cfg_file}",
          "--rewrapper-exec-root=${rbe_exec_root}",
        ]
      }
    }

    if (devtools_skip_typecheck) {
      args += [ "--use-esbuild" ]
      _esbuild = devtools_location_prepend + "third_party/esbuild/esbuild"
      if (host_os == "win") {
        inputs += [ _esbuild + ".exe" ]
      } else {
        inputs += [ _esbuild ]
      }
    }

    output_files = [ "$target_gen_dir/$_typescript_config_name-tsconfig.json" ]

    if (defined(sources)) {
      args += [ "--sources" ] + rebase_path(sources, root_build_dir)

      foreach(src, sources) {
        _extension = get_path_info(src, "extension")
        _relative_file_name = rebase_path(src, _rootdir)
        _fileName = get_path_info(_relative_file_name, "dir") + "/" +
                    get_path_info(_relative_file_name, "name")

        # Any .d.ts file does not generate a corresponding a .js file, but we
        # copy it over into the generated build folder instead, as tsc doesn't
        # do that on its own
        if (_extension == "ts" &&
            get_path_info(get_path_info(src, "name"), "extension") == "d") {
          output_files += [ "$target_gen_dir/$src" ]
          # Any file checked by TypeScript that is jsdoc-typed will be also
          # generating a js file in the gen folder
        } else if (_extension == "js" || _extension == "ts") {
          if (!_no_emit) {
            output_files += [
              "$target_gen_dir/$_fileName.js",
              "$target_gen_dir/$_fileName.js.map",
            ]
          }
        } else {
          assert(false,
                 "Incorrect extension on '$src' with extension '$_extension'")
        }

        if (!devtools_skip_typecheck) {
          output_files += [ "$target_gen_dir/$_fileName.d.ts" ]
        }
      }
    }

    outputs = output_files

    _data = outputs
    if (defined(invoker.data)) {
      _data += invoker.data
    }
    _javascript_implementation_files = filter_include(_data, [ "*.js" ])
    _javascript_map_files = filter_include(_data, [ "*.map" ])

    data = _javascript_implementation_files + _javascript_map_files

    ts_files = []
    if (defined(sources)) {
      ts_files = rebase_path(sources, root_build_dir)
    }

    metadata = {
      typescript_files = ts_files
      tests = filter_include(output_files,
                             [
                               "*.test.js",
                               "*_test.js",
                             ])
    }

    if (is_debug) {
      metadata.grd_files = _javascript_implementation_files
    }
  }
}

set_defaults("ts_library") {
  # Build output should be private and directories should export all relevant
  # components in a group
  visibility = [ "*" ]
  inputs = []
}

template("node_ts_library") {
  ts_library(target_name) {
    module = "commonjs"
    testonly = true
    reset_timestamps = true

    inputs =
        [ devtools_location_prepend + "node_modules/@types/node/index.d.ts" ]

    forward_variables_from(invoker,
                           [
                             "sources",
                             "visibility",
                             "deps",
                           ])
  }
}
