From 1b21096744a6f43e7361cacaa38e333029e500c3 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 17 Nov 2023 21:44:39 +0800 Subject: [PATCH] Make `findProjectRoot` async (#15672) --- package.json | 3 ++- src/config/find-project-root.js | 25 +++++++++++-------- src/config/resolve-editorconfig.js | 2 +- src/utils/is-directory.js | 22 ++++++++++++++++ .../repo-root/{.hg => .hg/.gitkeep} | 0 yarn.lock | 20 +++++++++++---- 6 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 src/utils/is-directory.js rename tests/integration/cli/config/editorconfig/repo-root/{.hg => .hg/.gitkeep} (100%) diff --git a/package.json b/package.json index 4f233060a..8fa7fbc90 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "html-ua-styles": "0.0.2", "ignore": "5.2.4", "import-meta-resolve": "4.0.0", + "iterate-directory-up": "1.0.1", "jest-docblock": "29.7.0", "js-yaml": "4.1.0", "json5": "2.2.3", @@ -101,7 +102,7 @@ "typescript": "5.2.2", "unicode-regex": "4.0.0", "unified": "9.2.2", - "url-or-path": "2.1.0", + "url-or-path": "2.2.0", "vnopts": "2.0.0", "wcwidth": "1.0.1", "yaml-unist-parser": "2.0.1" diff --git a/src/config/find-project-root.js b/src/config/find-project-root.js index 26350318c..320826b3a 100644 --- a/src/config/find-project-root.js +++ b/src/config/find-project-root.js @@ -1,24 +1,27 @@ // Simple version of `find-project-root` // https://github.com/kirstein/find-project-root/blob/master/index.js -import fs from "node:fs"; import path from "node:path"; +import iterateDirectoryUp from "iterate-directory-up"; +import isDirectory from "../utils/is-directory.js"; const MARKERS = [".git", ".hg"]; -const markerExists = (directory) => - MARKERS.some((mark) => fs.existsSync(path.join(directory, mark))); +/** + * Find the directory contains a version control system directory + * @param {string} startDirectory + * @returns {Promise} + */ +async function findProjectRoot(startDirectory) { + for (const directory of iterateDirectoryUp(startDirectory)) { + for (const name of MARKERS) { + const directoryPath = path.join(directory, name); -function findProjectRoot(directory) { - while (!markerExists(directory)) { - const parentDirectory = path.resolve(directory, ".."); - if (parentDirectory === directory) { - break; + if (await isDirectory(directoryPath, { allowSymlinks: false })) { + return directory; + } } - directory = parentDirectory; } - - return directory; } export default findProjectRoot; diff --git a/src/config/resolve-editorconfig.js b/src/config/resolve-editorconfig.js index 36d81d607..c28d922c9 100644 --- a/src/config/resolve-editorconfig.js +++ b/src/config/resolve-editorconfig.js @@ -5,7 +5,7 @@ import findProjectRoot from "./find-project-root.js"; async function loadEditorConfig(filePath) { const editorConfig = await editorconfig.parse(filePath, { - root: findProjectRoot(path.dirname(path.resolve(filePath))), + root: await findProjectRoot(path.dirname(path.resolve(filePath))), }); const config = editorConfigToPrettier(editorConfig); diff --git a/src/utils/is-directory.js b/src/utils/is-directory.js new file mode 100644 index 000000000..d9bec460c --- /dev/null +++ b/src/utils/is-directory.js @@ -0,0 +1,22 @@ +import fs from "node:fs/promises"; +import { toPath } from "url-or-path"; + +/** + * @param {string | URL} directory + * @param {{allowSymlinks: boolean}} [options] + * @returns {Promise} + */ +async function isDirectory(directory, options) { + const allowSymlinks = options?.allowSymlinks ?? true; + + let stats; + try { + stats = await (allowSymlinks ? fs.stat : fs.lstat)(toPath(directory)); + } catch { + return false; + } + + return stats.isDirectory(); +} + +export default isDirectory; diff --git a/tests/integration/cli/config/editorconfig/repo-root/.hg b/tests/integration/cli/config/editorconfig/repo-root/.hg/.gitkeep similarity index 100% rename from tests/integration/cli/config/editorconfig/repo-root/.hg rename to tests/integration/cli/config/editorconfig/repo-root/.hg/.gitkeep diff --git a/yarn.lock b/yarn.lock index 863b1cdb3..fd9c01e32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5310,6 +5310,15 @@ __metadata: languageName: node linkType: hard +"iterate-directory-up@npm:1.0.1": + version: 1.0.1 + resolution: "iterate-directory-up@npm:1.0.1" + dependencies: + url-or-path: ^2.2.0 + checksum: 82a81601c98a5c669bab95e2c928dba28758993f93a132c486755b4299683be07a7de95d2a46257ebc552c84704aa39b34f9853c119078ab0f63a6a2aeaa5150 + languageName: node + linkType: hard + "iterator.prototype@npm:^1.1.0": version: 1.1.0 resolution: "iterator.prototype@npm:1.1.0" @@ -7204,6 +7213,7 @@ __metadata: html-ua-styles: 0.0.2 ignore: 5.2.4 import-meta-resolve: 4.0.0 + iterate-directory-up: 1.0.1 jest: 29.7.0 jest-docblock: 29.7.0 jest-light-runner: 0.5.1 @@ -7250,7 +7260,7 @@ __metadata: typescript: 5.2.2 unicode-regex: 4.0.0 unified: 9.2.2 - url-or-path: 2.1.0 + url-or-path: 2.2.0 vnopts: 2.0.0 wcwidth: 1.0.1 yaml-unist-parser: 2.0.1 @@ -8694,10 +8704,10 @@ __metadata: languageName: node linkType: hard -"url-or-path@npm:2.1.0, url-or-path@npm:^2.1.0": - version: 2.1.0 - resolution: "url-or-path@npm:2.1.0" - checksum: 8bbc00439fe7bc4ce8c3cb92d80a5b8b4925d4709a210116c6996cf17135e361b97233f7abbd7dd09ea00684fc0043c8ae9abfd62fc20b648419bc2cd01bc219 +"url-or-path@npm:2.2.0, url-or-path@npm:^2.1.0, url-or-path@npm:^2.2.0": + version: 2.2.0 + resolution: "url-or-path@npm:2.2.0" + checksum: 20fc2c78dd695f751075b8dab7e199890d3dc1aa2c3ee57e8e40aa20e89943dcdba8e9a69427b5edf9b5ea9677df8e4bb3887ee7b2c5ed41c3bebe9204b6e6c0 languageName: node linkType: hard