Skip to content

Commit 1ae3682

Browse files
committed
fix: searching for config files
1 parent de4937a commit 1ae3682

File tree

4 files changed

+100
-84
lines changed

4 files changed

+100
-84
lines changed

integration-tests/snapshots/Azure/azure-rest-api-specs/report.yaml

+2-12
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Url: https://github.com/Azure/azure-rest-api-specs.git
44
Args: '["--config=cspell.json","**/*.{md,ts,js}"]'
55
Summary:
66
files: 2365
7-
filesWithIssues: 14
8-
issues: 329
7+
filesWithIssues: 12
8+
issues: 319
99
errors: 0
1010
Errors: []
1111

@@ -328,14 +328,4 @@ issues:
328328
- "README.md:23:13 azsdk U See [aka.ms/azsdk/spec-dirs](https://aka"
329329
- "SECURITY.md:13:72 MSRC U Security Response Center (MSRC) at [https://msrc.microsoft"
330330
- "SECURITY.md:17:205 msrc U found at [microsoft.com/msrc](https://aka.ms/opensource"
331-
- "specification/cognitiveservices/data-plane/FormRecognizer/readme.ruby.md:24:43 Reognizer U CognitiveServices::FormReognizer::V1_0\""
332-
- "specification/monitor/resource-manager/readme.az.md:6:7 AMCS U - AMCS: true"
333-
- "specification/monitor/resource-manager/readme.az.md:9:21 AMCS U ``` yaml $(az) && $(AMCS)"
334-
- "specification/monitor/resource-manager/readme.az.md:13:27 amcs U namespace: azure.mgmt.amcs"
335-
- "specification/monitor/resource-manager/readme.az.md:14:30 amcs U package-name: azure-mgmt-amcs"
336-
- "specification/monitor/resource-manager/readme.az.md:16:54 amcs U output-folder)/azext_amcs/vendored_sdks/amcs\""
337-
- "specification/monitor/resource-manager/readme.az.md:16:73 amcs U azext_amcs/vendored_sdks/amcs\""
338-
- "specification/monitor/resource-manager/readme.az.md:21:6 AMCS U # Az.AMCS"
339-
- "specification/monitor/resource-manager/readme.az.md:22:85 AMCS U Monitor Control Service (AMCS)."
340-
- "specification/monitor/resource-manager/readme.az.md:25:12 AMCS U ``` yaml $(AMCS)"
341331
- "specification/recoveryservicesdatareplication/resource-manager/readme.java.md:11:447 Mware U tensionModelCustomProperties:VMwareToAzStackHciRepExtnCustomProps"

integration-tests/snapshots/Azure/azure-rest-api-specs/snapshot.txt

+1-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Repository: Azure/azure-rest-api-specs
33
Url: "https://github.com/Azure/azure-rest-api-specs.git"
44
Args: ["--config=cspell.json","**/*.{md,ts,js}"]
55
Lines:
6-
CSpell: Files checked: 2365, Issues found: 329 in 14 files.
6+
CSpell: Files checked: 2365, Issues found: 319 in 12 files.
77
exit code: 1
88
CONTRIBUTING.md:24:6 - Unknown word (tocstop) -- <!-- tocstop -->
99
CONTRIBUTING.md:79:3 - Unknown word (Poli) -- - PoliCheck -
@@ -323,14 +323,4 @@ profiles/definitions/latest-2019-04-30.md:914:19 - Unknown word (iscloneable)
323323
profiles/definitions/latest-2019-04-30.md:989:25 - Unknown word (iscloneable) -- - sites/slots/iscloneable
324324
profiles/definitions/stable-2019-01.md:53:13 - Unknown word (ishostnameavailable) -- - ishostnameavailable
325325
profiles/definitions/stable-2019-01.md:54:13 - Unknown word (isusernameavailable) -- - isusernameavailable
326-
specification/cognitiveservices/data-plane/FormRecognizer/readme.ruby.md:24:43 - Unknown word (Reognizer) -- CognitiveServices::FormReognizer::V1_0"
327-
specification/monitor/resource-manager/readme.az.md:13:27 - Unknown word (amcs) -- namespace: azure.mgmt.amcs
328-
specification/monitor/resource-manager/readme.az.md:14:30 - Unknown word (amcs) -- package-name: azure-mgmt-amcs
329-
specification/monitor/resource-manager/readme.az.md:16:54 - Unknown word (amcs) -- output-folder)/azext_amcs/vendored_sdks/amcs"
330-
specification/monitor/resource-manager/readme.az.md:16:73 - Unknown word (amcs) -- azext_amcs/vendored_sdks/amcs"
331-
specification/monitor/resource-manager/readme.az.md:21:6 - Unknown word (AMCS) -- # Az.AMCS
332-
specification/monitor/resource-manager/readme.az.md:22:85 - Unknown word (AMCS) -- Monitor Control Service (AMCS).
333-
specification/monitor/resource-manager/readme.az.md:25:12 - Unknown word (AMCS) -- ``` yaml $(AMCS)
334-
specification/monitor/resource-manager/readme.az.md:6:7 - Unknown word (AMCS) -- - AMCS: true
335-
specification/monitor/resource-manager/readme.az.md:9:21 - Unknown word (AMCS) -- ``` yaml $(az) && $(AMCS)
336326
specification/recoveryservicesdatareplication/resource-manager/readme.java.md:11:447 - Unknown word (Mware) -- tensionModelCustomProperties:VMwareToAzStackHciRepExtnCustomProps

packages/cspell-lib/src/lib/Settings/Controller/configLoader/configSearch.ts

+96-60
Original file line numberDiff line numberDiff line change
@@ -9,85 +9,115 @@ import { findUpFromUrl } from '../../../util/findUpFromUrl.js';
99
type Href = string;
1010

1111
export class ConfigSearch {
12-
private searchCache = new Map<Href, Promise<URL | undefined>>();
13-
private searchDirCache = new Map<Href, Promise<URL | undefined>>();
14-
private searchPlacesByProtocol: Map<string, string[]>;
12+
/**
13+
* Cache of search results.
14+
*/
15+
#searchCache = new Map<Href, Promise<URL | undefined>>();
16+
/**
17+
* The scanner to use to search for config files.
18+
*/
19+
#scanner: DirConfigScanner;
1520

1621
/**
1722
* @param searchPlaces - The list of file names to search for.
1823
* @param allowedExtensionsByProtocol - Map of allowed extensions by protocol, '*' is used to match all protocols.
1924
* @param fs - The file system to use.
2025
*/
2126
constructor(
22-
readonly searchPlaces: readonly string[],
23-
readonly allowedExtensionsByProtocol: Map<string, readonly string[]>,
24-
private fs: VFileSystem,
27+
searchPlaces: readonly string[],
28+
allowedExtensionsByProtocol: Map<string, readonly string[]>,
29+
fs: VFileSystem,
2530
) {
26-
this.searchPlacesByProtocol = setupSearchPlacesByProtocol(searchPlaces, allowedExtensionsByProtocol);
27-
this.searchPlaces = this.searchPlacesByProtocol.get('*') || searchPlaces;
31+
this.#scanner = new DirConfigScanner(searchPlaces, allowedExtensionsByProtocol, fs);
2832
}
2933

3034
searchForConfig(searchFromURL: URL): Promise<URL | undefined> {
31-
const dirUrl = new URL('.', searchFromURL);
32-
const searchHref = dirUrl.href;
33-
const searchCache = this.searchCache;
34-
const cached = searchCache.get(searchHref);
35+
const dirUrl = searchFromURL.pathname.endsWith('/') ? searchFromURL : new URL('.', searchFromURL);
36+
return this.#findUp(dirUrl);
37+
}
38+
39+
clearCache() {
40+
this.#searchCache.clear();
41+
this.#scanner.clearCache();
42+
}
43+
44+
#findUp(fromDir: URL): Promise<URL | undefined> {
45+
const searchDirCache = this.#searchCache;
46+
const cached = searchDirCache.get(fromDir.href);
3547
if (cached) {
3648
return cached;
3749
}
38-
39-
const toPatchCache: URL[] = [];
40-
const pFoundUrl = this.findUpConfigPath(dirUrl, storeVisit);
41-
this.searchCache.set(searchHref, pFoundUrl);
42-
const searchDirCache = this.searchDirCache;
43-
44-
const patch = async () => {
45-
try {
46-
await pFoundUrl;
47-
for (const dir of toPatchCache) {
48-
searchDirCache.set(dir.href, searchDirCache.get(dir.href) || pFoundUrl);
49-
searchCache.set(dir.href, searchCache.get(dir.href) || pFoundUrl);
50-
}
51-
52-
const result = searchCache.get(searchHref) || pFoundUrl;
53-
searchCache.set(searchHref, result);
54-
} catch {
55-
// ignore
56-
}
50+
const visited: URL[] = [];
51+
let result: Promise<URL | undefined> | undefined = undefined;
52+
const predicate = (dir: URL) => {
53+
visit(dir);
54+
return this.#scanner.scanDirForConfigFile(dir);
5755
};
56+
result = findUpFromUrl(predicate, fromDir, { type: 'file' });
57+
searchDirCache.set(fromDir.href, result);
58+
visited.forEach((dir) => searchDirCache.set(dir.href, result));
59+
return result;
5860

59-
patch();
60-
return pFoundUrl;
61-
62-
function storeVisit(dir: URL) {
63-
toPatchCache.push(dir);
61+
/**
62+
* Record directories that are visited while walking up the directory tree.
63+
* This will help speed up future searches.
64+
* @param dir - the directory that was visited.
65+
*/
66+
function visit(dir: URL) {
67+
if (!result) {
68+
visited.push(dir);
69+
return;
70+
}
71+
searchDirCache.set(dir.href, searchDirCache.get(dir.href) || result);
6472
}
6573
}
74+
}
6675

67-
clearCache() {
68-
this.searchCache.clear();
69-
this.searchDirCache.clear();
70-
}
71-
72-
private findUpConfigPath(cwd: URL, visit: (dir: URL) => void): Promise<URL | undefined> {
73-
const searchDirCache = this.searchDirCache;
74-
const cached = searchDirCache.get(cwd.href);
75-
if (cached) return cached;
76+
/**
77+
* A Scanner that searches for a config file in a directory. It caches the results to speed up future requests.
78+
*/
79+
export class DirConfigScanner {
80+
#searchDirCache = new Map<Href, Promise<URL | undefined>>();
81+
#searchPlacesByProtocol: Map<string, string[]>;
82+
#searchPlaces: readonly string[];
7683

77-
return findUpFromUrl((dir) => this.hasConfig(dir, visit), cwd, { type: 'file' });
84+
/**
85+
* @param searchPlaces - The list of file names to search for.
86+
* @param allowedExtensionsByProtocol - Map of allowed extensions by protocol, '*' is used to match all protocols.
87+
* @param fs - The file system to use.
88+
*/
89+
constructor(
90+
searchPlaces: readonly string[],
91+
readonly allowedExtensionsByProtocol: Map<string, readonly string[]>,
92+
private fs: VFileSystem,
93+
) {
94+
this.#searchPlacesByProtocol = setupSearchPlacesByProtocol(searchPlaces, allowedExtensionsByProtocol);
95+
this.#searchPlaces = this.#searchPlacesByProtocol.get('*') || searchPlaces;
7896
}
7997

80-
private hasConfig(dir: URL, visited: (dir: URL) => void): Promise<URL | undefined> {
81-
const cached = this.searchDirCache.get(dir.href);
82-
if (cached) return cached;
83-
visited(dir);
98+
clearCache() {
99+
this.#searchDirCache.clear();
100+
}
84101

85-
const result = this.hasConfigDir(dir);
86-
this.searchDirCache.set(dir.href, result);
102+
/**
103+
*
104+
* @param dir - the directory to search for a config file.
105+
* @param visited - a callback to be called for each directory visited.
106+
* @returns A promise that resolves to the url of the config file or `undefined`.
107+
*/
108+
scanDirForConfigFile(dir: URL): Promise<URL | undefined> {
109+
const searchDirCache = this.#searchDirCache;
110+
const href = dir.href;
111+
const cached = searchDirCache.get(href);
112+
if (cached) {
113+
return cached;
114+
}
115+
const result = this.#scanDirForConfig(dir);
116+
searchDirCache.set(href, result);
87117
return result;
88118
}
89119

90-
private createHasFileDirSearch(): (file: URL) => Promise<boolean> {
120+
#createHasFileDirSearch(): (file: URL) => Promise<boolean> {
91121
const dirInfoCache = createAutoResolveCache<Href, Promise<Map<string, VfsDirEntry>>>();
92122

93123
const hasFile = async (filename: URL): Promise<boolean> => {
@@ -102,7 +132,7 @@ export class ConfigSearch {
102132
if (!found?.isDirectory() && !found?.isSymbolicLink()) return false;
103133
}
104134
const dirUrlHref = dir.href;
105-
const dirInfo = await dirInfoCache.get(dirUrlHref, async () => await this.readDir(dir));
135+
const dirInfo = await dirInfoCache.get(dirUrlHref, async () => await this.#readDir(dir));
106136

107137
const name = urlBasename(filename);
108138
const found = dirInfo.get(name);
@@ -112,7 +142,7 @@ export class ConfigSearch {
112142
return hasFile;
113143
}
114144

115-
private async readDir(dir: URL): Promise<Map<string, VfsDirEntry>> {
145+
async #readDir(dir: URL): Promise<Map<string, VfsDirEntry>> {
116146
try {
117147
const dirInfo = await this.fs.readDirectory(dir);
118148
return new Map(dirInfo.map((ent) => [ent.name, ent]));
@@ -121,7 +151,7 @@ export class ConfigSearch {
121151
}
122152
}
123153

124-
private createHasFileStatCheck(): (file: URL) => Promise<boolean> {
154+
#createHasFileStatCheck(): (file: URL) => Promise<boolean> {
125155
const hasFile = async (filename: URL): Promise<boolean> => {
126156
const stat = await this.fs.stat(filename).catch(() => undefined);
127157
return !!stat?.isFile();
@@ -130,12 +160,17 @@ export class ConfigSearch {
130160
return hasFile;
131161
}
132162

133-
private async hasConfigDir(dir: URL): Promise<URL | undefined> {
163+
/**
164+
* Scan the directory for the first matching config file.
165+
* @param dir - url of the directory to scan.
166+
* @returns A promise that resolves to the url of the config file or `undefined`.
167+
*/
168+
async #scanDirForConfig(dir: URL): Promise<URL | undefined> {
134169
const hasFile = this.fs.getCapabilities(dir).readDirectory
135-
? this.createHasFileDirSearch()
136-
: this.createHasFileStatCheck();
170+
? this.#createHasFileDirSearch()
171+
: this.#createHasFileStatCheck();
137172

138-
const searchPlaces = this.searchPlacesByProtocol.get(dir.protocol) || this.searchPlaces;
173+
const searchPlaces = this.#searchPlacesByProtocol.get(dir.protocol) || this.#searchPlaces;
139174

140175
for (const searchPlace of searchPlaces) {
141176
const file = new URL(searchPlace, dir);
@@ -145,6 +180,7 @@ export class ConfigSearch {
145180
if (await checkPackageJson(this.fs, file)) return file;
146181
}
147182
}
183+
148184
return undefined;
149185
}
150186
}

packages/cspell-lib/src/lib/util/findUpFromUrl.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface FindUpURLOptions {
1111
fs?: FindUpFileSystem;
1212
}
1313

14-
type FindUpPredicate = (dir: URL) => URL | undefined | Promise<URL | undefined>;
14+
export type FindUpPredicate = (dir: URL) => URL | undefined | Promise<URL | undefined>;
1515

1616
export async function findUpFromUrl(
1717
name: string | string[] | FindUpPredicate,

0 commit comments

Comments
 (0)