2 * @fileoverview Define classes what use the in-memory file system.
4 * This provides utilities to test `ConfigArrayFactory`,
5 * `CascadingConfigArrayFactory`, `FileEnumerator`, and `CLIEngine`.
7 * - `defineConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })`
8 * - `defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })`
9 * - `defineFileEnumeratorWithInMemoryFileSystem({ cwd, files })`
10 * - `defineCLIEngineWithInMemoryFileSystem({ cwd, files })`
12 * Those functions define correspond classes with the in-memory file system.
13 * Those search config files, parsers, and plugins in the `files` option via the
14 * in-memory file system.
16 * For each test case, it makes more readable if we define minimal files the
22 * const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
24 * "node_modules/eslint-config-foo/index.js": `
32 * "node_modules/eslint-config-foo/parser.js": `
37 * ".eslintrc.json": JSON.stringify({ root: true, extends: "foo" })
40 * const factory = new ConfigArrayFactory();
41 * const config = factory.loadFile(".eslintrc.json");
43 * assert(config[0].name === ".eslintrc.json ยป eslint-config-foo");
44 * assert(config[0].filePath === path.resolve("node_modules/eslint-config-foo/index.js"));
45 * assert(config[0].parser.filePath === path.resolve("node_modules/eslint-config-foo/parser.js"));
47 * assert(config[1].name === ".eslintrc.json");
48 * assert(config[1].filePath === path.resolve(".eslintrc.json"));
49 * assert(config[1].root === true);
52 * @author Toru Nagashima <https://github.com/mysticatea>
56 const path
= require("path");
57 const vm
= require("vm");
58 const Proxyquire
= require("proxyquire/lib/proxyquire");
59 const { defineInMemoryFs
} = require("../_utils");
61 const CascadingConfigArrayFactoryPath
=
62 require
.resolve("../../../lib/cli-engine/cascading-config-array-factory");
64 require
.resolve("../../../lib/cli-engine/cli-engine");
65 const ConfigArrayFactoryPath
=
66 require
.resolve("../../../lib/cli-engine/config-array-factory");
67 const FileEnumeratorPath
=
68 require
.resolve("../../../lib/cli-engine/file-enumerator");
70 require
.resolve("../../../lib/cli-engine/load-rules");
72 require
.resolve("../../../conf/eslint-all");
73 const ESLintRecommendedPath
=
74 require
.resolve("../../../conf/eslint-recommended");
76 // Ensure the needed files has been loaded and cached.
77 require(CascadingConfigArrayFactoryPath
);
78 require(CLIEnginePath
);
79 require(ConfigArrayFactoryPath
);
80 require(FileEnumeratorPath
);
81 require(LoadRulesPath
);
85 // Override `_require` in order to throw runtime errors in stubs.
86 const ERRORED
= Symbol("errored");
87 const proxyquire
= new class extends Proxyquire
{
89 const retv
= super._require(...args
); // eslint-disable-line no-underscore-dangle
96 }(module
).noCallThru().noPreserveCache();
98 // Separated (sandbox) context to compile fixture files.
99 const context
= vm
.createContext();
102 * Check if a given path is an existing file.
103 * @param {import("fs")} fs The file system.
104 * @param {string} filePath Tha path to a file to check.
105 * @returns {boolean} `true` if the file existed.
107 function isExistingFile(fs
, filePath
) {
109 return fs
.statSync(filePath
).isFile();
116 * Get some paths to test.
117 * @param {string} prefix The prefix to try.
118 * @returns {string[]} The paths to test.
120 function getTestPaths(prefix
) {
123 path
.join(`${prefix}.js`),
124 path
.join(prefix
, "index.js")
129 * Iterate the candidate paths of a given request to resolve.
130 * @param {string} request Tha package name or file path to resolve.
131 * @param {string} relativeTo Tha path to the file what called this resolving.
132 * @returns {IterableIterator<string>} The candidate paths.
134 function *iterateCandidatePaths(request
, relativeTo
) {
135 if (path
.isAbsolute(request
)) {
136 yield* getTestPaths(request
);
139 if (/^\.{1,2}[/\\]/u
.test(request
)) {
140 yield* getTestPaths(path
.resolve(path
.dirname(relativeTo
), request
));
144 let prevPath
= path
.resolve(relativeTo
);
145 let dirPath
= path
.dirname(prevPath
);
147 while (dirPath
&& dirPath
!== prevPath
) {
148 yield* getTestPaths(path
.join(dirPath
, "node_modules", request
));
150 dirPath
= path
.dirname(dirPath
);
155 * Resolve a given module name or file path relatively in the given file system.
156 * @param {import("fs")} fs The file system.
157 * @param {string} request Tha package name or file path to resolve.
158 * @param {string} relativeTo Tha path to the file what called this resolving.
161 function fsResolve(fs
, request
, relativeTo
) {
162 for (const filePath
of iterateCandidatePaths(request
, relativeTo
)) {
163 if (isExistingFile(fs
, filePath
)) {
169 new Error(`Cannot find module '${request}'`),
170 { code
: "MODULE_NOT_FOUND" }
175 * Compile a JavaScript file.
176 * This is used to compile only fixture files, so this is minimam.
177 * @param {import("fs")} fs The file system.
178 * @param {Object} stubs The stubs.
179 * @param {string} filePath The path to a JavaScript file to compile.
180 * @param {string} content The source code to compile.
181 * @returns {any} The exported value.
183 function compile(fs
, stubs
, filePath
, content
) {
184 const code
= `(function(exports, require, module, __filename, __dirname) { ${content} })`;
185 const f
= vm
.runInContext(code
, context
);
187 const module
= { exports
};
193 const modulePath
= fsResolve(fs
, request
, filePath
);
194 const stub
= stubs
[modulePath
];
203 path
.dirname(filePath
)
206 return module
.exports
;
210 * Import a given file path in the given file system.
211 * @param {import("fs")} fs The file system.
212 * @param {Object} stubs The stubs.
213 * @param {string} absolutePath Tha file path to import.
216 function fsImportFresh(fs
, stubs
, absolutePath
) {
217 if (absolutePath
=== ESLintAllPath
) {
218 return require(ESLintAllPath
);
220 if (absolutePath
=== ESLintRecommendedPath
) {
221 return require(ESLintRecommendedPath
);
224 if (fs
.existsSync(absolutePath
)) {
229 fs
.readFileSync(absolutePath
, "utf8")
234 new Error(`Cannot find module '${absolutePath}'`),
235 { code
: "MODULE_NOT_FOUND" }
240 * Define stubbed `ConfigArrayFactory` class what uses the in-memory file system.
241 * @param {Object} options The options.
242 * @param {() => string} [options.cwd] The current working directory.
243 * @param {Object} [options.files] The initial files definition in the in-memory file system.
244 * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"] }} The stubbed `ConfigArrayFactory` class.
246 function defineConfigArrayFactoryWithInMemoryFileSystem({
250 const fs
= defineInMemoryFs({ cwd
, files
});
251 const RelativeModuleResolver
= { resolve
: fsResolve
.bind(null, fs
) };
254 * Stubs for proxyquire.
255 * This contains the JavaScript files in `options.files`.
260 stubs
["import-fresh"] = fsImportFresh
.bind(null, fs
, stubs
);
261 stubs
["../shared/relative-module-resolver"] = RelativeModuleResolver
;
264 * Write all files to the in-memory file system and compile all JavaScript
265 * files then set to `stubs`.
267 (function initFiles(directoryPath
, definition
) {
268 for (const [filename
, content
] of Object
.entries(definition
)) {
269 const filePath
= path
.resolve(directoryPath
, filename
);
271 if (typeof content
=== "object") {
272 initFiles(filePath
, content
);
277 * Compile then stub if this file is a JavaScript file.
278 * For parsers and plugins that `require()` will import.
280 if (path
.extname(filePath
) === ".js") {
281 Object
.defineProperty(stubs
, filePath
, {
288 stub
= compile(fs
, stubs
, filePath
, content
);
290 stub
= { [ERRORED
]: error
};
292 Object
.defineProperty(stubs
, filePath
, {
305 // Load the stubbed one.
306 const { ConfigArrayFactory
} = proxyquire(ConfigArrayFactoryPath
, stubs
);
308 // Override the default cwd.
312 RelativeModuleResolver
,
313 ConfigArrayFactory
: cwd
=== process
.cwd
315 : class extends ConfigArrayFactory
{
316 constructor(options
) {
317 super({ cwd
: cwd(), ...options
});
324 * Define stubbed `CascadingConfigArrayFactory` class what uses the in-memory file system.
325 * @param {Object} options The options.
326 * @param {() => string} [options.cwd] The current working directory.
327 * @param {Object} [options.files] The initial files definition in the in-memory file system.
328 * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"] }} The stubbed `CascadingConfigArrayFactory` class.
330 function defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
334 const { fs
, stubs
, RelativeModuleResolver
, ConfigArrayFactory
} =
335 defineConfigArrayFactoryWithInMemoryFileSystem({ cwd
, files
});
336 const loadRules
= proxyquire(LoadRulesPath
, stubs
);
337 const { CascadingConfigArrayFactory
} =
338 proxyquire(CascadingConfigArrayFactoryPath
, {
339 "./config-array-factory": { ConfigArrayFactory
},
340 "./load-rules": loadRules
343 // Override the default cwd.
346 RelativeModuleResolver
,
348 CascadingConfigArrayFactory
: cwd
=== process
.cwd
349 ? CascadingConfigArrayFactory
350 : class extends CascadingConfigArrayFactory
{
351 constructor(options
) {
352 super({ cwd
: cwd(), ...options
});
359 * Define stubbed `FileEnumerator` class what uses the in-memory file system.
360 * @param {Object} options The options.
361 * @param {() => string} [options.cwd] The current working directory.
362 * @param {Object} [options.files] The initial files definition in the in-memory file system.
363 * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../../lib/cli-engine/file-enumerator")["FileEnumerator"] }} The stubbed `FileEnumerator` class.
365 function defineFileEnumeratorWithInMemoryFileSystem({
371 RelativeModuleResolver
,
373 CascadingConfigArrayFactory
375 defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd
, files
});
376 const { FileEnumerator
} = proxyquire(FileEnumeratorPath
, {
378 "./cascading-config-array-factory": { CascadingConfigArrayFactory
}
381 // Override the default cwd.
384 RelativeModuleResolver
,
386 CascadingConfigArrayFactory
,
387 FileEnumerator
: cwd
=== process
.cwd
389 : class extends FileEnumerator
{
390 constructor(options
) {
391 super({ cwd
: cwd(), ...options
});
398 * Define stubbed `CLIEngine` class what uses the in-memory file system.
399 * @param {Object} options The options.
400 * @param {() => string} [options.cwd] The current working directory.
401 * @param {Object} [options.files] The initial files definition in the in-memory file system.
402 * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../../lib/cli-engine/file-enumerator")["FileEnumerator"], CLIEngine: import("../../../lib/cli-engine/cli-engine")["CLIEngine"], getCLIEngineInternalSlots: import("../../../lib/cli-engine/cli-engine")["getCLIEngineInternalSlots"] }} The stubbed `CLIEngine` class.
404 function defineCLIEngineWithInMemoryFileSystem({
410 RelativeModuleResolver
,
412 CascadingConfigArrayFactory
,
415 defineFileEnumeratorWithInMemoryFileSystem({ cwd
, files
});
416 const { CLIEngine
, getCLIEngineInternalSlots
} = proxyquire(CLIEnginePath
, {
418 "./cascading-config-array-factory": { CascadingConfigArrayFactory
},
419 "./file-enumerator": { FileEnumerator
},
420 "../shared/relative-module-resolver": RelativeModuleResolver
423 // Override the default cwd.
426 RelativeModuleResolver
,
428 CascadingConfigArrayFactory
,
430 CLIEngine
: cwd
=== process
.cwd
432 : class extends CLIEngine
{
433 constructor(options
) {
434 super({ cwd
: cwd(), ...options
});
437 getCLIEngineInternalSlots
442 defineConfigArrayFactoryWithInMemoryFileSystem
,
443 defineCascadingConfigArrayFactoryWithInMemoryFileSystem
,
444 defineFileEnumeratorWithInMemoryFileSystem
,
445 defineCLIEngineWithInMemoryFileSystem