Component bridges presubmit check

This CL introduces a PRESUBMIT that will:

1. run `npm run regenerate-all-component-bridges`
2. Check that the git diff is clean.

If the git diff is not clean, it will fail. If the bridge has to be
manually edited (e.g. to work around a bug in the generator), it must
have a comment that matches the pattern `MANUALLY_EDITED_BRIDGE=...`.
The regeneration script will ignore bridges that contain these.

Change-Id: I372a3cd17a8e94bcdcea69ef64db189d2be6bde1
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2364597
Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 1dcbd61..1fcffd5 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -340,9 +340,10 @@
                                          stderr=input_api.subprocess.STDOUT)
     out, _ = process.communicate()
     if process.returncode != 0:
-        files_changed_process = input_api.subprocess.Popen(['git', 'diff', '--name-only'],
-                                                           stdout=input_api.subprocess.PIPE,
-                                                           stderr=input_api.subprocess.STDOUT)
+        files_changed_process = input_api.subprocess.Popen(
+            ['git', 'diff', '--name-only'],
+            stdout=input_api.subprocess.PIPE,
+            stderr=input_api.subprocess.STDOUT)
         files_changed, _ = files_changed_process.communicate()
 
         return [
@@ -380,6 +381,21 @@
         return []
 
 
+def _CheckComponentBridgesUpToDate(input_api, output_api):
+    # Regenerate all bridge files - if any are out of date it will cause the git diff check to fail.
+    results = []
+
+    script_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
+                                         'scripts', 'component_bridges', 'gen',
+                                         'regenerate-all-bridges.js')
+
+    tsc_arguments = ['-p', 'scripts/component_bridges/tsconfig.json']
+    results.extend(
+        _checkWithTypeScript(input_api, output_api, tsc_arguments, script_path,
+                             ['--silent']))
+    return results
+
+
 def _CommonChecks(input_api, output_api):
     """Checks common to both upload and commit."""
     results = []
@@ -400,6 +416,7 @@
     results.extend(_CheckFormat(input_api, output_api))
     results.extend(_CheckOptimizeSVGHashes(input_api, output_api))
     results.extend(_CheckChangesAreExclusiveToDirectory(input_api, output_api))
+    results.extend(_CheckComponentBridgesUpToDate(input_api, output_api))
     results.extend(_CheckNoUncheckedFiles(input_api, output_api))
     results.extend(_CheckForTooLargeFiles(input_api, output_api))
     return results
@@ -445,6 +462,40 @@
     return _ExecuteSubProcess(input_api, output_api, [devtools_paths.node_path(), script_path], script_arguments, [])
 
 
+def _checkWithTypeScript(input_api,
+                         output_api,
+                         tsc_arguments,
+                         script_path,
+                         script_arguments=[]):  # pylint: disable=invalid-name
+    original_sys_path = sys.path
+    try:
+        sys.path = sys.path + [
+            input_api.os_path.join(input_api.PresubmitLocalPath(), 'scripts')
+        ]
+        import devtools_paths
+    finally:
+        sys.path = original_sys_path
+
+    # First run tsc to compile the TS script that we then run in the _ExecuteSubProcess call
+    tsc_compiler_process = input_api.subprocess.Popen(
+        [
+            devtools_paths.node_path(),
+            devtools_paths.typescript_compiler_path()
+        ] + tsc_arguments,
+        stdout=input_api.subprocess.PIPE,
+        stderr=input_api.subprocess.STDOUT)
+
+    out, _ = tsc_compiler_process.communicate()
+    if tsc_compiler_process.returncode != 0:
+        return [
+            output_api.PresubmitError('Error compiling briges regenerator:\n' +
+                                      str(out))
+        ]
+
+    return _checkWithNodeScript(input_api, output_api, script_path,
+                                script_arguments)
+
+
 def _getFilesToLint(input_api, output_api, lint_config_files,
                     default_linted_directories, accepted_endings, results):
     run_full_check = False