forked from jestjs/jest
/
fileWalkers.ts
133 lines (107 loc) · 3.17 KB
/
fileWalkers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {dirname, resolve} from 'path';
import * as fs from 'graceful-fs';
import type {Config} from '@jest/types';
import {tryRealpath} from 'jest-util';
export function clearFsCache(): void {
checkedPaths.clear();
checkedRealpathPaths.clear();
packageContents.clear();
}
enum IPathType {
FILE = 1,
DIRECTORY = 2,
OTHER = 3,
}
const checkedPaths = new Map<string, IPathType>();
function statSyncCached(path: string): IPathType {
const result = checkedPaths.get(path);
if (result != null) {
return result;
}
let stat;
try {
// @ts-expect-error TS2554 - throwIfNoEntry is only available in recent version of node, but inclusion of the option is a backward compatible no-op.
stat = fs.statSync(path, {throwIfNoEntry: false});
} catch (e: any) {
if (!(e && (e.code === 'ENOENT' || e.code === 'ENOTDIR'))) {
throw e;
}
}
if (stat) {
if (stat.isFile() || stat.isFIFO()) {
checkedPaths.set(path, IPathType.FILE);
return IPathType.FILE;
} else if (stat.isDirectory()) {
checkedPaths.set(path, IPathType.DIRECTORY);
return IPathType.DIRECTORY;
}
}
checkedPaths.set(path, IPathType.OTHER);
return IPathType.OTHER;
}
const checkedRealpathPaths = new Map<string, string>();
function realpathCached(path: Config.Path): Config.Path {
let result = checkedRealpathPaths.get(path);
if (result != null) {
return result;
}
result = tryRealpath(path);
checkedRealpathPaths.set(path, result);
if (path !== result) {
// also cache the result in case it's ever referenced directly - no reason to `realpath` that as well
checkedRealpathPaths.set(result, result);
}
return result;
}
export type PkgJson = Record<string, unknown>;
const packageContents = new Map<string, PkgJson>();
export function readPackageCached(path: Config.Path): PkgJson {
let result = packageContents.get(path);
if (result != null) {
return result;
}
result = JSON.parse(fs.readFileSync(path, 'utf8')) as PkgJson;
packageContents.set(path, result);
return result;
}
// adapted from
// https://github.com/lukeed/escalade/blob/2477005062cdbd8407afc90d3f48f4930354252b/src/sync.js
// to use cached `fs` calls
export function findClosestPackageJson(
start: Config.Path,
): Config.Path | undefined {
let dir = resolve('.', start);
if (!isDirectory(dir)) {
dir = dirname(dir);
}
while (true) {
const pkgJsonFile = resolve(dir, './package.json');
const hasPackageJson = isFile(pkgJsonFile);
if (hasPackageJson) {
return pkgJsonFile;
}
const prevDir = dir;
dir = dirname(dir);
if (prevDir === dir) {
return undefined;
}
}
}
/*
* helper functions
*/
export function isFile(file: Config.Path): boolean {
return statSyncCached(file) === IPathType.FILE;
}
export function isDirectory(dir: Config.Path): boolean {
return statSyncCached(dir) === IPathType.DIRECTORY;
}
export function realpathSync(file: Config.Path): Config.Path {
return realpathCached(file);
}