Migrate run_lint_check_css to JS script

As part of the work to move more scripts to Node, not Python, over time,
picked this one as the starting point. I changed its API slightly to
allow more flags to be taken in, as we'll need that to do a stylelint
pass against TypeScript files, but I will do that in a subsequent CL.

I had to make quite a few changes to devtools_paths.js, but I think it's
now calculating paths correctly. It took a bit of messing to get the
equivalent of Python's path.abspath(__file__), as you'll see from the
large comment that tries to explain what's going on!

Bug: chromium:1166108, chromium:1166572
Change-Id: Ia0b19ff8956b2ede2447530be57876a88046887e
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2631113
Commit-Queue: Jack Franklin <[email protected]>
Reviewed-by: Tim van der Lippe <[email protected]>
diff --git a/scripts/devtools_paths.js b/scripts/devtools_paths.js
index 0f0b433..116b8b1 100644
--- a/scripts/devtools_paths.js
+++ b/scripts/devtools_paths.js
@@ -15,42 +15,114 @@
 
 /**
  * You would think we can use __filename here but we cannot because __filename
- * has any symlinks resolved. This means we can't use it to tell if the user
- * is using the external repo with a standalone build setup because the
- * symlink from chromium/src/third_party/devtools-frontend =>
- * devtools-frontend repo gets resolved by Node before it gives us __filename.
+ * has any symlinks resolved. This means we can't use it to tell if the user is
+ * using the external repo with a standalone build setup because the symlink
+ * from chromium/src/third_party/devtools-frontend => devtools-frontend repo
+ * gets resolved by Node before it gives us __filename.
  *
- * Instead we can use process.argv, whose first two arguments are the path to
- * the Node binary, and then the path to the file being executed, but without
- * symlinks being resolved. So if this script gets run in the Chromium dir
- * through a symlink, the path will still contain
- * /path/to/chromium/src/third-party/devtools-frontend/scripts/... - this is
- * NOT the case if we were to use __filename.
+ * We can use process.argv[1], which is the path to the file currently being
+ * executed without any symlinks resolution. If we assume that file is always in
+ * the devtools-frontend repository/directory, we can use that file as the
+ * starting point for figuring out if we're in Chromium or not. until we find
+ * the scripts directory, at which point we've found this file and can use it
+ * for all subsequent logic.
+ *
+ * e.g. the user executes a script: scripts/test/run_lint_check_css.js
+ *
+ * process.argv[1] =
+ * /full/path/devtools-frontend/src/scripts/test/run_lint_check_css.js
  */
-const ABS_PATH_TO_CURRENT_FILE = process.argv[1];
+const PATH_TO_EXECUTED_FILE = process.argv[1];
 
-/** Find the root path of the checkout.
-* In the Chromium repository, this is the src/chromium directory.
-* In the external repository, standalone build, this is the devtools-frontend directory.
-* In the external repository, integrated build, this is the src/chromium directory.
-*/
-function rootPath() {
-  const scriptsPath = path.dirname(ABS_PATH_TO_CURRENT_FILE);
-  const devtoolsFrontendPath = path.dirname(scriptsPath);
-  const devtoolsFrontendParentPath = path.dirname(devtoolsFrontendPath);
-
-  if (path.basename(devtoolsFrontendParentPath) === 'devtools-frontend') {
-    // External repository, integrated build
-    // So go up two levels to the src/chromium directory
-    return path.dirname(path.dirname(devtoolsFrontendParentPath));
-  }
-
-  // External repository, standalone build
-  return devtoolsFrontendPath;
+function pathIsMostTopLevelPath(filePath) {
+  /**
+   * On Linux/Mac, if we do path.dirname(X) as many times as possible, it will
+   * eventually equal `/`. On Windows, it will end up equalling C:\, and
+   * path.dirname('C:\') === 'C:\', so we use that to figure out if we've made
+   * it as far up the tree as we can.
+   */
+  return filePath === path.sep || path.dirname(filePath) === filePath;
 }
 
+
+const _lookUpCaches = new Map(
+    [['chromium', null]],
+);
+/**
+ * This function figures out if we're within a chromium directory, and therefore
+ * we are in the integrated workflow mode, rather than working in a standalone
+ * devtools-frontend repository.
+ */
+function isInChromiumDirectory() {
+  const cached = _lookUpCaches.get('chromium');
+  if (cached) {
+    return cached;
+  }
+
+  let potentialChromiumDir = PATH_TO_EXECUTED_FILE;
+  let isInChromium = false;
+  while (!pathIsMostTopLevelPath(potentialChromiumDir)) {
+    potentialChromiumDir = path.dirname(potentialChromiumDir);
+    if (path.basename(potentialChromiumDir) === 'chromium') {
+      isInChromium = true;
+      break;
+    }
+  }
+  const result = {isInChromium, chromiumDirectory: potentialChromiumDir};
+  _lookUpCaches.set('chromium', result);
+  return result;
+}
+/**
+ * Returns the path to the root of the devtools-frontend repository.
+ *
+ * If we're in Chromium, this will be /path/to/chromium/src/third_party/devtools-frontend/src
+ * If it's standalone, it will be /path/to/devtools-frontend
+ */
+function devtoolsRootPath() {
+  const nodeScriptFileThatIsBeingExecuted = PATH_TO_EXECUTED_FILE;
+  let devtoolsRootFolder = nodeScriptFileThatIsBeingExecuted;
+  while (path.basename(devtoolsRootFolder) !== 'devtools-frontend') {
+    devtoolsRootFolder = path.dirname(devtoolsRootFolder);
+    // We reached the end and can't find devtools-frontend.
+    if (pathIsMostTopLevelPath(devtoolsRootFolder)) {
+      throw new Error(
+          'Could not find devtools-frontend in path. If you have cloned the repository to a different directory name, it will not work.');
+    }
+  }
+  // In Chromium the path to the source code for devtools-frontend is:
+  // third_party/devtools-frontend/src
+  const {isInChromium} = isInChromiumDirectory();
+  if (isInChromium) {
+    return path.join(devtoolsRootFolder, 'src');
+  }
+
+  // But if you're in a standalone repo it's just the devtools-frontend folder.
+  return devtoolsRootFolder;
+}
+
+/**
+ * Returns the path to the root of the main repository we're in.
+ * if we're in Chromium, this is /path/to/chromium/src
+ * if we're in standalone, this is /path/to/devtools-frontend
+ *
+ * Note this is different to devtoolsRootPath(), which always returns the path
+ * to the devtools-frontend source code.
+ */
+function rootPath() {
+  const {isInChromium, chromiumDirectory} = isInChromiumDirectory();
+  if (isInChromium) {
+    return path.join(chromiumDirectory, 'src');
+  }
+  return devtoolsRootPath();
+}
+
+/**
+ * Path to the third_party directory. Used because if we're running in Chromium
+ * land we need to use e.g. the Node executable from Chromium's third_party
+ * directory, not from the devtools-frontend third_party directory.
+ */
 function thirdPartyPath() {
-  path.join(rootPath(), 'third_party');
+  return path.join(rootPath(), 'third_party');
 }
 
 function nodePath() {
@@ -59,15 +131,24 @@
     'linux': path.join('linux', 'node-linux-x64', 'bin', 'node'),
     'win32': path.join('win', 'node.exe'),
   };
-  return path.join(thirdPartyPath(), 'node', paths[os.platform]);
+  return path.join(thirdPartyPath(), 'node', paths[os.platform()]);
 }
 
-function devtoolsRootPath() {
-  return path.dirname(path.dirname(ABS_PATH_TO_CURRENT_FILE));
-}
-
+/**
+ * The path to the devtools-frontend node_modules folder.
+ */
 function nodeModulesPath() {
   return path.join(devtoolsRootPath(), 'node_modules');
 }
 
-export {thirdPartyPath, nodePath, devtoolsRootPath, nodeModulesPath};
+function stylelintExecutablePath() {
+  return path.join(nodeModulesPath(), 'stylelint', 'bin', 'stylelint.js');
+}
+
+module.exports = {
+  thirdPartyPath,
+  nodePath,
+  devtoolsRootPath,
+  nodeModulesPath,
+  stylelintExecutablePath
+};