diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 069597bf1..c456ed446 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -53,6 +53,10 @@ groupName: "Regex related dependencies", matchPackageNames: ["regexp-util", "unicode-regex", "cjk-regex"], }, + { + groupName: "Config search related dependencies", + matchPackageNames: ["url-or-path", "search-closest"], + }, { branchPrefix: "renovate/website/", matchFileNames: ["website/package.json"], diff --git a/package.json b/package.json index 86eb2eac0..bffc58a0a 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "ignore": "7.0.3", "import-meta-resolve": "4.1.0", "is-es5-identifier-name": "1.0.0", - "iterate-directory-up": "1.1.1", "jest-docblock": "30.0.0-alpha.7", "js-yaml": "4.1.0", "json5": "2.2.3", @@ -94,6 +93,7 @@ "remark-math": "3.0.1", "remark-parse": "8.0.3", "sdbm": "2.0.0", + "search-closest": "0.1.1", "smol-toml": "1.3.4", "strip-ansi": "7.1.0", "to-fast-properties": "4.0.0", @@ -101,7 +101,7 @@ "typescript": "5.8.3", "unicode-regex": "4.1.2", "unified": "9.2.2", - "url-or-path": "2.3.2", + "url-or-path": "2.6.1", "vnopts": "2.0.2", "wcwidth.js": "2.0.0", "yaml-unist-parser": "2.0.5" diff --git a/src/config/find-project-root.js b/src/config/find-project-root.js index feefaf0fc..200edad10 100644 --- a/src/config/find-project-root.js +++ b/src/config/find-project-root.js @@ -2,16 +2,12 @@ // https://github.com/kirstein/find-project-root/blob/master/index.js import * as path from "node:path"; -import isDirectory from "../utils/is-directory.js"; -import Searcher from "./searcher.js"; +import { DirectorySearcher } from "search-closest"; -const MARKERS = [".git", ".hg"]; +const DIRECTORIES = [".git", ".hg"]; + +/** @type {DirectorySearcher} */ let searcher; -const searchOptions = { - names: MARKERS, - filter: ({ path: directory }) => - isDirectory(directory, { allowSymlinks: false }), -}; /** * Find the directory contains a version control system directory @@ -20,10 +16,12 @@ const searchOptions = { * @returns {Promise} */ async function findProjectRoot(startDirectory, options) { - searcher ??= new Searcher(searchOptions); - const mark = await searcher.search(startDirectory, options); + searcher ??= new DirectorySearcher(DIRECTORIES, { allowSymlinks: false }); + const directory = await searcher.search(startDirectory, { + cache: options.shouldCache, + }); - return mark ? path.dirname(mark) : undefined; + return directory ? path.dirname(directory) : undefined; } function clearFindProjectRootCache() { diff --git a/src/config/prettier-config/config-searcher.js b/src/config/prettier-config/config-searcher.js index 49f0a560d..474509757 100644 --- a/src/config/prettier-config/config-searcher.js +++ b/src/config/prettier-config/config-searcher.js @@ -1,10 +1,13 @@ -import isFile from "../../utils/is-file.js"; -import Searcher from "../searcher.js"; +import { FileSearcher } from "search-closest"; import { loadConfigFromPackageJson, loadConfigFromPackageYaml, } from "./loaders.js"; +/** +@import {SearcherOptions} from 'search-closest' +*/ + const CONFIG_FILE_NAMES = [ "package.json", "package.yaml", @@ -28,11 +31,8 @@ const CONFIG_FILE_NAMES = [ ".prettierrc.toml", ]; +/** @type {SearcherOptions["filter"]} */ async function filter({ name, path: file }) { - if (!(await isFile(file))) { - return false; - } - if (name === "package.json") { try { return Boolean(await loadConfigFromPackageJson(file)); @@ -53,7 +53,7 @@ async function filter({ name, path: file }) { } function getSearcher(stopDirectory) { - return new Searcher({ names: CONFIG_FILE_NAMES, filter, stopDirectory }); + return new FileSearcher(CONFIG_FILE_NAMES, { filter, stopDirectory }); } export default getSearcher; diff --git a/src/config/prettier-config/index.js b/src/config/prettier-config/index.js index 0e44a5b94..d86fe2868 100644 --- a/src/config/prettier-config/index.js +++ b/src/config/prettier-config/index.js @@ -3,6 +3,10 @@ import mockable from "../../common/mockable.js"; import getConfigSearcher from "./config-searcher.js"; import loadConfig from "./load-config.js"; +/** +@import {FileSearcher} from 'search-closest' +*/ + const loadCache = new Map(); const searchCache = new Map(); function clearPrettierConfigCache() { @@ -27,8 +31,9 @@ function loadPrettierConfig(configFile, { shouldCache }) { } /** - * @param {string} stopDirectory - */ +@param {string} stopDirectory +@returns {FileSearcher["search"]} +*/ function getSearchFunction(stopDirectory) { stopDirectory = stopDirectory ? path.resolve(stopDirectory) : undefined; @@ -44,7 +49,7 @@ function getSearchFunction(stopDirectory) { /** * @param {string} startDirectory * @param {{shouldCache?: boolean}} options - * @returns {Promise} + * @returns {Promise} */ function searchPrettierConfig(startDirectory, options = {}) { startDirectory = startDirectory @@ -55,7 +60,7 @@ function searchPrettierConfig(startDirectory, options = {}) { const search = getSearchFunction(stopDirectory); - return search(startDirectory, { shouldCache: options.shouldCache }); + return search(startDirectory, { cache: options.shouldCache }); } export { clearPrettierConfigCache, loadPrettierConfig, searchPrettierConfig }; diff --git a/src/config/searcher.js b/src/config/searcher.js deleted file mode 100644 index 5300267bf..000000000 --- a/src/config/searcher.js +++ /dev/null @@ -1,71 +0,0 @@ -import path from "node:path"; -import iterateDirectoryUp from "iterate-directory-up"; - -class Searcher { - #names; - #filter; - #stopDirectory; - #cache = new Map(); - - /** - * @param {{ - * names: string[], - * filter: (fileOrDirectory: {name: string, path: string}) => Promise, - * stopDirectory?: string, - * }} param0 - */ - constructor({ names, filter, stopDirectory }) { - this.#names = names; - this.#filter = filter; - this.#stopDirectory = stopDirectory; - } - - async #searchInDirectory(directory, shouldCache) { - const cache = this.#cache; - if (shouldCache && cache.has(directory)) { - return cache.get(directory); - } - - for (const name of this.#names) { - const fileOrDirectory = path.join(directory, name); - - if (await this.#filter({ name, path: fileOrDirectory })) { - return fileOrDirectory; - } - } - } - - async search(startDirectory, { shouldCache }) { - const cache = this.#cache; - if (shouldCache && cache.has(startDirectory)) { - return cache.get(startDirectory); - } - - const searchedDirectories = []; - let result; - for (const directory of iterateDirectoryUp( - startDirectory, - this.#stopDirectory, - )) { - searchedDirectories.push(directory); - result = await this.#searchInDirectory(directory, shouldCache); - - if (result) { - break; - } - } - - // Always cache the result, so we can use it when `useCache` is set to true - for (const directory of searchedDirectories) { - cache.set(directory, result); - } - - return result; - } - - clearCache() { - this.#cache.clear(); - } -} - -export default Searcher; diff --git a/src/utils/is-directory.js b/src/utils/is-directory.js deleted file mode 100644 index d9bec460c..000000000 --- a/src/utils/is-directory.js +++ /dev/null @@ -1,22 +0,0 @@ -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/src/utils/is-file.js b/src/utils/is-file.js deleted file mode 100644 index d9526826a..000000000 --- a/src/utils/is-file.js +++ /dev/null @@ -1,22 +0,0 @@ -import fs from "node:fs/promises"; -import { toPath } from "url-or-path"; - -/** - * @param {string | URL} file - * @param {{allowSymlinks: boolean}} [options] - * @returns {Promise} - */ -async function isFile(file, options) { - const allowSymlinks = options?.allowSymlinks ?? true; - - let stats; - try { - stats = await (allowSymlinks ? fs.stat : fs.lstat)(toPath(file)); - } catch { - return false; - } - - return stats.isFile(); -} - -export default isFile; diff --git a/yarn.lock b/yarn.lock index 167654511..b78b3f85f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4365,6 +4365,15 @@ __metadata: languageName: node linkType: hard +"find-in-directory@npm:^2.0.1": + version: 2.0.1 + resolution: "find-in-directory@npm:2.0.1" + dependencies: + url-or-path: "npm:2.6.1" + checksum: 10/1a02fdec539ff4c4d63d0e296d853bf88a633a882bc82fba8addc24fb08de799eaa2f05c4721e3954a23dddeb8301cc994dd509b58b5cac34d90a95a43e6a5ea + languageName: node + linkType: hard + "find-up-json@npm:^2.0.4, find-up-json@npm:^2.0.5": version: 2.0.5 resolution: "find-up-json@npm:2.0.5" @@ -5213,12 +5222,12 @@ __metadata: languageName: node linkType: hard -"iterate-directory-up@npm:1.1.1": - version: 1.1.1 - resolution: "iterate-directory-up@npm:1.1.1" +"iterate-directory-up@npm:^1.2.1": + version: 1.2.1 + resolution: "iterate-directory-up@npm:1.2.1" dependencies: - url-or-path: "npm:^2.3.0" - checksum: 10/b724f63d1035a453b974d15dd9f809a0862131aa38cfe2960b0f388ee8100e42404f8fb8b52c46c82dc2c263c3ca9b30ba8716216ab9ae78cb9142d85110d706 + url-or-path: "npm:^2.6.1" + checksum: 10/35846f8df1d31b6e37eef19cd7c4a7b3c4cbacbbdf80e1b7b1eb2f75ec7a611039da895592328a55885ace3f06def73015402d55409d0aec9e959fd4cb0d6b7e languageName: node linkType: hard @@ -7035,7 +7044,6 @@ __metadata: import-meta-resolve: "npm:4.1.0" index-to-position: "npm:1.1.0" is-es5-identifier-name: "npm:1.0.0" - iterate-directory-up: "npm:1.1.1" jest: "npm:30.0.0-alpha.7" jest-docblock: "npm:30.0.0-alpha.7" jest-light-runner: "npm:0.7.8" @@ -7073,6 +7081,7 @@ __metadata: remark-parse: "npm:8.0.3" rollup-plugin-license: "npm:3.6.0" sdbm: "npm:2.0.0" + search-closest: "npm:0.1.1" semver: "npm:7.7.1" serialize-javascript: "npm:6.0.2" smol-toml: "npm:1.3.4" @@ -7086,7 +7095,7 @@ __metadata: typescript: "npm:5.8.3" unicode-regex: "npm:4.1.2" unified: "npm:9.2.2" - url-or-path: "npm:2.3.2" + url-or-path: "npm:2.6.1" vnopts: "npm:2.0.2" wcwidth.js: "npm:2.0.0" yaml-unist-parser: "npm:2.0.5" @@ -7475,6 +7484,16 @@ __metadata: languageName: node linkType: hard +"search-closest@npm:0.1.1": + version: 0.1.1 + resolution: "search-closest@npm:0.1.1" + dependencies: + find-in-directory: "npm:^2.0.1" + iterate-directory-up: "npm:^1.2.1" + checksum: 10/5285c2d5e7e5d7ffbb2aa0bf2fd37acd19dff58c3975889125f2a92e9a0c17adea10484c43f1ae67ccf03896f891e7721b54211fea777164ac90b26dde7c93ff + languageName: node + linkType: hard + "semver-compare@npm:^1.0.0": version: 1.0.0 resolution: "semver-compare@npm:1.0.0" @@ -8468,10 +8487,10 @@ __metadata: languageName: node linkType: hard -"url-or-path@npm:2.3.2, url-or-path@npm:^2.3.0": - version: 2.3.2 - resolution: "url-or-path@npm:2.3.2" - checksum: 10/9078f06d5754293464332da3f98eddd9878dc6a2b4ece5303f72e6a5fa72c3d25a3c33f01bd097bf54cccd29e61283fec38432c928894c032630a04867ceaf81 +"url-or-path@npm:2.6.1, url-or-path@npm:^2.3.0, url-or-path@npm:^2.6.1": + version: 2.6.1 + resolution: "url-or-path@npm:2.6.1" + checksum: 10/707d340bc72dbaccfaa0a371f8e70816518141ecbfdebddc4d6faf8e5e5afa6f33cb01e4e5701c0e76dc661bd699e00041c3b00f1a3e2fb7a0c4e144c9dd6ace languageName: node linkType: hard