source: trip-planner-front/node_modules/enhanced-resolve/lib/Resolver.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 12.8 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable");
9const createInnerContext = require("./createInnerContext");
10const { parseIdentifier } = require("./util/identifier");
11const {
12 normalize,
13 cachedJoin: join,
14 getType,
15 PathType
16} = require("./util/path");
17
18/** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
19
20/**
21 * @typedef {Object} FileSystemStats
22 * @property {function(): boolean} isDirectory
23 * @property {function(): boolean} isFile
24 */
25
26/**
27 * @typedef {Object} FileSystemDirent
28 * @property {Buffer | string} name
29 * @property {function(): boolean} isDirectory
30 * @property {function(): boolean} isFile
31 */
32
33/**
34 * @typedef {Object} PossibleFileSystemError
35 * @property {string=} code
36 * @property {number=} errno
37 * @property {string=} path
38 * @property {string=} syscall
39 */
40
41/**
42 * @template T
43 * @callback FileSystemCallback
44 * @param {PossibleFileSystemError & Error | null | undefined} err
45 * @param {T=} result
46 */
47
48/**
49 * @typedef {Object} FileSystem
50 * @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readFile
51 * @property {(function(string, FileSystemCallback<(Buffer | string)[] | FileSystemDirent[]>): void) & function(string, object, FileSystemCallback<(Buffer | string)[] | FileSystemDirent[]>): void} readdir
52 * @property {((function(string, FileSystemCallback<object>): void) & function(string, object, FileSystemCallback<object>): void)=} readJson
53 * @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readlink
54 * @property {(function(string, FileSystemCallback<FileSystemStats>): void) & function(string, object, FileSystemCallback<Buffer | string>): void=} lstat
55 * @property {(function(string, FileSystemCallback<FileSystemStats>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} stat
56 */
57
58/**
59 * @typedef {Object} SyncFileSystem
60 * @property {function(string, object=): Buffer | string} readFileSync
61 * @property {function(string, object=): (Buffer | string)[] | FileSystemDirent[]} readdirSync
62 * @property {(function(string, object=): object)=} readJsonSync
63 * @property {function(string, object=): Buffer | string} readlinkSync
64 * @property {function(string, object=): FileSystemStats=} lstatSync
65 * @property {function(string, object=): FileSystemStats} statSync
66 */
67
68/**
69 * @typedef {Object} ParsedIdentifier
70 * @property {string} request
71 * @property {string} query
72 * @property {string} fragment
73 * @property {boolean} directory
74 * @property {boolean} module
75 * @property {boolean} file
76 * @property {boolean} internal
77 */
78
79/**
80 * @typedef {Object} BaseResolveRequest
81 * @property {string | false} path
82 * @property {string=} descriptionFilePath
83 * @property {string=} descriptionFileRoot
84 * @property {object=} descriptionFileData
85 * @property {string=} relativePath
86 * @property {boolean=} ignoreSymlinks
87 * @property {boolean=} fullySpecified
88 */
89
90/** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest */
91
92/**
93 * String with special formatting
94 * @typedef {string} StackEntry
95 */
96
97/** @template T @typedef {{ add: (T) => void }} WriteOnlySet */
98
99/**
100 * Resolve context
101 * @typedef {Object} ResolveContext
102 * @property {WriteOnlySet<string>=} contextDependencies
103 * @property {WriteOnlySet<string>=} fileDependencies files that was found on file system
104 * @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
105 * @property {Set<StackEntry>=} stack set of hooks' calls. For instance, `resolve → parsedResolve → describedResolve`,
106 * @property {(function(string): void)=} log log function
107 */
108
109/** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
110
111/**
112 * @param {string} str input string
113 * @returns {string} in camel case
114 */
115function toCamelCase(str) {
116 return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase());
117}
118
119class Resolver {
120 /**
121 * @param {ResolveStepHook} hook hook
122 * @param {ResolveRequest} request request
123 * @returns {StackEntry} stack entry
124 */
125 static createStackEntry(hook, request) {
126 return (
127 hook.name +
128 ": (" +
129 request.path +
130 ") " +
131 (request.request || "") +
132 (request.query || "") +
133 (request.fragment || "") +
134 (request.directory ? " directory" : "") +
135 (request.module ? " module" : "")
136 );
137 }
138
139 /**
140 * @param {FileSystem} fileSystem a filesystem
141 * @param {ResolveOptions} options options
142 */
143 constructor(fileSystem, options) {
144 this.fileSystem = fileSystem;
145 this.options = options;
146 this.hooks = {
147 /** @type {SyncHook<[ResolveStepHook, ResolveRequest], void>} */
148 resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
149 /** @type {SyncHook<[ResolveRequest, Error]>} */
150 noResolve: new SyncHook(["request", "error"], "noResolve"),
151 /** @type {ResolveStepHook} */
152 resolve: new AsyncSeriesBailHook(
153 ["request", "resolveContext"],
154 "resolve"
155 ),
156 /** @type {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} */
157 result: new AsyncSeriesHook(["result", "resolveContext"], "result")
158 };
159 }
160
161 /**
162 * @param {string | ResolveStepHook} name hook name or hook itself
163 * @returns {ResolveStepHook} the hook
164 */
165 ensureHook(name) {
166 if (typeof name !== "string") {
167 return name;
168 }
169 name = toCamelCase(name);
170 if (/^before/.test(name)) {
171 return /** @type {ResolveStepHook} */ (this.ensureHook(
172 name[6].toLowerCase() + name.substr(7)
173 ).withOptions({
174 stage: -10
175 }));
176 }
177 if (/^after/.test(name)) {
178 return /** @type {ResolveStepHook} */ (this.ensureHook(
179 name[5].toLowerCase() + name.substr(6)
180 ).withOptions({
181 stage: 10
182 }));
183 }
184 const hook = this.hooks[name];
185 if (!hook) {
186 return (this.hooks[name] = new AsyncSeriesBailHook(
187 ["request", "resolveContext"],
188 name
189 ));
190 }
191 return hook;
192 }
193
194 /**
195 * @param {string | ResolveStepHook} name hook name or hook itself
196 * @returns {ResolveStepHook} the hook
197 */
198 getHook(name) {
199 if (typeof name !== "string") {
200 return name;
201 }
202 name = toCamelCase(name);
203 if (/^before/.test(name)) {
204 return /** @type {ResolveStepHook} */ (this.getHook(
205 name[6].toLowerCase() + name.substr(7)
206 ).withOptions({
207 stage: -10
208 }));
209 }
210 if (/^after/.test(name)) {
211 return /** @type {ResolveStepHook} */ (this.getHook(
212 name[5].toLowerCase() + name.substr(6)
213 ).withOptions({
214 stage: 10
215 }));
216 }
217 const hook = this.hooks[name];
218 if (!hook) {
219 throw new Error(`Hook ${name} doesn't exist`);
220 }
221 return hook;
222 }
223
224 /**
225 * @param {object} context context information object
226 * @param {string} path context path
227 * @param {string} request request string
228 * @returns {string | false} result
229 */
230 resolveSync(context, path, request) {
231 /** @type {Error | null | undefined} */
232 let err = undefined;
233 /** @type {string | false | undefined} */
234 let result = undefined;
235 let sync = false;
236 this.resolve(context, path, request, {}, (e, r) => {
237 err = e;
238 result = r;
239 sync = true;
240 });
241 if (!sync) {
242 throw new Error(
243 "Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!"
244 );
245 }
246 if (err) throw err;
247 if (result === undefined) throw new Error("No result");
248 return result;
249 }
250
251 /**
252 * @param {object} context context information object
253 * @param {string} path context path
254 * @param {string} request request string
255 * @param {ResolveContext} resolveContext resolve context
256 * @param {function(Error | null, (string|false)=, ResolveRequest=): void} callback callback function
257 * @returns {void}
258 */
259 resolve(context, path, request, resolveContext, callback) {
260 if (!context || typeof context !== "object")
261 return callback(new Error("context argument is not an object"));
262 if (typeof path !== "string")
263 return callback(new Error("path argument is not a string"));
264 if (typeof request !== "string")
265 return callback(new Error("path argument is not a string"));
266 if (!resolveContext)
267 return callback(new Error("resolveContext argument is not set"));
268
269 const obj = {
270 context: context,
271 path: path,
272 request: request
273 };
274
275 const message = `resolve '${request}' in '${path}'`;
276
277 const finishResolved = result => {
278 return callback(
279 null,
280 result.path === false
281 ? false
282 : `${result.path.replace(/#/g, "\0#")}${
283 result.query ? result.query.replace(/#/g, "\0#") : ""
284 }${result.fragment || ""}`,
285 result
286 );
287 };
288
289 const finishWithoutResolve = log => {
290 /**
291 * @type {Error & {details?: string}}
292 */
293 const error = new Error("Can't " + message);
294 error.details = log.join("\n");
295 this.hooks.noResolve.call(obj, error);
296 return callback(error);
297 };
298
299 if (resolveContext.log) {
300 // We need log anyway to capture it in case of an error
301 const parentLog = resolveContext.log;
302 const log = [];
303 return this.doResolve(
304 this.hooks.resolve,
305 obj,
306 message,
307 {
308 log: msg => {
309 parentLog(msg);
310 log.push(msg);
311 },
312 fileDependencies: resolveContext.fileDependencies,
313 contextDependencies: resolveContext.contextDependencies,
314 missingDependencies: resolveContext.missingDependencies,
315 stack: resolveContext.stack
316 },
317 (err, result) => {
318 if (err) return callback(err);
319
320 if (result) return finishResolved(result);
321
322 return finishWithoutResolve(log);
323 }
324 );
325 } else {
326 // Try to resolve assuming there is no error
327 // We don't log stuff in this case
328 return this.doResolve(
329 this.hooks.resolve,
330 obj,
331 message,
332 {
333 log: undefined,
334 fileDependencies: resolveContext.fileDependencies,
335 contextDependencies: resolveContext.contextDependencies,
336 missingDependencies: resolveContext.missingDependencies,
337 stack: resolveContext.stack
338 },
339 (err, result) => {
340 if (err) return callback(err);
341
342 if (result) return finishResolved(result);
343
344 // log is missing for the error details
345 // so we redo the resolving for the log info
346 // this is more expensive to the success case
347 // is assumed by default
348
349 const log = [];
350
351 return this.doResolve(
352 this.hooks.resolve,
353 obj,
354 message,
355 {
356 log: msg => log.push(msg),
357 stack: resolveContext.stack
358 },
359 (err, result) => {
360 if (err) return callback(err);
361
362 return finishWithoutResolve(log);
363 }
364 );
365 }
366 );
367 }
368 }
369
370 doResolve(hook, request, message, resolveContext, callback) {
371 const stackEntry = Resolver.createStackEntry(hook, request);
372
373 let newStack;
374 if (resolveContext.stack) {
375 newStack = new Set(resolveContext.stack);
376 if (resolveContext.stack.has(stackEntry)) {
377 /**
378 * Prevent recursion
379 * @type {Error & {recursion?: boolean}}
380 */
381 const recursionError = new Error(
382 "Recursion in resolving\nStack:\n " +
383 Array.from(newStack).join("\n ")
384 );
385 recursionError.recursion = true;
386 if (resolveContext.log)
387 resolveContext.log("abort resolving because of recursion");
388 return callback(recursionError);
389 }
390 newStack.add(stackEntry);
391 } else {
392 newStack = new Set([stackEntry]);
393 }
394 this.hooks.resolveStep.call(hook, request);
395
396 if (hook.isUsed()) {
397 const innerContext = createInnerContext(
398 {
399 log: resolveContext.log,
400 fileDependencies: resolveContext.fileDependencies,
401 contextDependencies: resolveContext.contextDependencies,
402 missingDependencies: resolveContext.missingDependencies,
403 stack: newStack
404 },
405 message
406 );
407 return hook.callAsync(request, innerContext, (err, result) => {
408 if (err) return callback(err);
409 if (result) return callback(null, result);
410 callback();
411 });
412 } else {
413 callback();
414 }
415 }
416
417 /**
418 * @param {string} identifier identifier
419 * @returns {ParsedIdentifier} parsed identifier
420 */
421 parse(identifier) {
422 const part = {
423 request: "",
424 query: "",
425 fragment: "",
426 module: false,
427 directory: false,
428 file: false,
429 internal: false
430 };
431
432 const parsedIdentifier = parseIdentifier(identifier);
433
434 if (!parsedIdentifier) return part;
435
436 [part.request, part.query, part.fragment] = parsedIdentifier;
437
438 if (part.request.length > 0) {
439 part.internal = this.isPrivate(identifier);
440 part.module = this.isModule(part.request);
441 part.directory = this.isDirectory(part.request);
442 if (part.directory) {
443 part.request = part.request.substr(0, part.request.length - 1);
444 }
445 }
446
447 return part;
448 }
449
450 isModule(path) {
451 return getType(path) === PathType.Normal;
452 }
453
454 isPrivate(path) {
455 return getType(path) === PathType.Internal;
456 }
457
458 /**
459 * @param {string} path a path
460 * @returns {boolean} true, if the path is a directory path
461 */
462 isDirectory(path) {
463 return path.endsWith("/");
464 }
465
466 join(path, request) {
467 return join(path, request);
468 }
469
470 normalize(path) {
471 return normalize(path);
472 }
473}
474
475module.exports = Resolver;
Note: See TracBrowser for help on using the repository browser.