blob: 4a35c6a472642d4e9b6d63d727a2d6164095b87c [file] [log] [blame]
Tim van der Lippe2c891972021-07-29 16:22:50 +01001'use strict';
2exports.__esModule = true;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +01003
Tim van der Lippe2c891972021-07-29 16:22:50 +01004const fs = require('fs');
5const Module = require('module');
6const path = require('path');
Tim van der Lippefdbd42e2020-04-07 15:14:36 +01007
Tim van der Lippe2c891972021-07-29 16:22:50 +01008const hashObject = require('./hash').hashObject;
9const ModuleCache = require('./ModuleCache').default;
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010010const pkgDir = require('./pkgDir').default;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010011
Tim van der Lippe2c891972021-07-29 16:22:50 +010012const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js'));
13exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010014
Tim van der Lippe2c891972021-07-29 16:22:50 +010015const ERROR_NAME = 'EslintPluginImportResolveError';
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010016
Tim van der Lippe2c891972021-07-29 16:22:50 +010017const fileExistsCache = new ModuleCache();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010018
19// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
20// Use `Module.createRequire` if available (added in Node v12.2.0)
21const createRequire = Module.createRequire || Module.createRequireFromPath || function (filename) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010022 const mod = new Module(filename, null);
23 mod.filename = filename;
24 mod.paths = Module._nodeModulePaths(path.dirname(filename));
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010025
Tim van der Lippe2c891972021-07-29 16:22:50 +010026 mod._compile(`module.exports = require;`, filename);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010027
Tim van der Lippe2c891972021-07-29 16:22:50 +010028 return mod.exports;
29};
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010030
31function tryRequire(target, sourceFile) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010032 let resolved;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010033 try {
34 // Check if the target exists
35 if (sourceFile != null) {
36 try {
Tim van der Lippe2c891972021-07-29 16:22:50 +010037 resolved = createRequire(path.resolve(sourceFile)).resolve(target);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010038 } catch (e) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010039 resolved = require.resolve(target);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010040 }
41 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +010042 resolved = require.resolve(target);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010043 }
Tim van der Lippebc3a0b72021-11-08 15:22:37 +000044 } catch (e) {
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010045 // If the target does not exist then just return undefined
Tim van der Lippe2c891972021-07-29 16:22:50 +010046 return undefined;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010047 }
48
49 // If the target exists then return the loaded module
Tim van der Lippe2c891972021-07-29 16:22:50 +010050 return require(resolved);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010051}
52
Tim van der Lippe0ceb4652022-01-06 14:23:36 +010053// https://stackoverflow.com/a/27382838
Tim van der Lippebc3a0b72021-11-08 15:22:37 +000054exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) {
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010055 // don't care if the FS is case-sensitive
Tim van der Lippe2c891972021-07-29 16:22:50 +010056 if (CASE_SENSITIVE_FS) return true;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010057
58 // null means it resolved to a builtin
Tim van der Lippe2c891972021-07-29 16:22:50 +010059 if (filepath === null) return true;
Tim van der Lippebc3a0b72021-11-08 15:22:37 +000060 if (filepath.toLowerCase() === process.cwd().toLowerCase() && !strict) return true;
Tim van der Lippe2c891972021-07-29 16:22:50 +010061 const parsedPath = path.parse(filepath);
62 const dir = parsedPath.dir;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010063
Tim van der Lippe2c891972021-07-29 16:22:50 +010064 let result = fileExistsCache.get(filepath, cacheSettings);
65 if (result != null) return result;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010066
67 // base case
68 if (dir === '' || parsedPath.root === filepath) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010069 result = true;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010070 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +010071 const filenames = fs.readdirSync(dir);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010072 if (filenames.indexOf(parsedPath.base) === -1) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010073 result = false;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010074 } else {
Tim van der Lippebc3a0b72021-11-08 15:22:37 +000075 result = fileExistsWithCaseSync(dir, cacheSettings, strict);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010076 }
77 }
Tim van der Lippe2c891972021-07-29 16:22:50 +010078 fileExistsCache.set(filepath, result);
79 return result;
80};
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010081
82function relative(modulePath, sourceFile, settings) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010083 return fullResolve(modulePath, sourceFile, settings).path;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010084}
85
86function fullResolve(modulePath, sourceFile, settings) {
87 // check if this is a bonus core module
Tim van der Lippe2c891972021-07-29 16:22:50 +010088 const coreSet = new Set(settings['import/core-modules']);
89 if (coreSet.has(modulePath)) return { found: true, path: null };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010090
Tim van der Lippe2c891972021-07-29 16:22:50 +010091 const sourceDir = path.dirname(sourceFile);
92 const cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010093
Tim van der Lippe2c891972021-07-29 16:22:50 +010094 const cacheSettings = ModuleCache.getSettings(settings);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010095
Tim van der Lippe2c891972021-07-29 16:22:50 +010096 const cachedPath = fileExistsCache.get(cacheKey, cacheSettings);
97 if (cachedPath !== undefined) return { found: true, path: cachedPath };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010098
99 function cache(resolvedPath) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100100 fileExistsCache.set(cacheKey, resolvedPath);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100101 }
102
103 function withResolver(resolver, config) {
104
105 function v1() {
106 try {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100107 const resolved = resolver.resolveImport(modulePath, sourceFile, config);
108 if (resolved === undefined) return { found: false };
109 return { found: true, path: resolved };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100110 } catch (err) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100111 return { found: false };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100112 }
113 }
114
115 function v2() {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100116 return resolver.resolve(modulePath, sourceFile, config);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100117 }
118
119 switch (resolver.interfaceVersion) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100120 case 2:
121 return v2();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100122
Tim van der Lippe2c891972021-07-29 16:22:50 +0100123 default:
124 case 1:
125 return v1();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100126 }
127 }
128
129 const configResolvers = (settings['import/resolver']
Tim van der Lippe2c891972021-07-29 16:22:50 +0100130 || { 'node': settings['import/resolve'] }); // backward compatibility
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100131
Tim van der Lippe2c891972021-07-29 16:22:50 +0100132 const resolvers = resolverReducer(configResolvers, new Map());
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100133
Tim van der Lippe2c891972021-07-29 16:22:50 +0100134 for (const pair of resolvers) {
135 const name = pair[0];
136 const config = pair[1];
137 const resolver = requireResolver(name, sourceFile);
138 const resolved = withResolver(resolver, config);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100139
Tim van der Lippe2c891972021-07-29 16:22:50 +0100140 if (!resolved.found) continue;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100141
142 // else, counts
Tim van der Lippe2c891972021-07-29 16:22:50 +0100143 cache(resolved.path);
144 return resolved;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100145 }
146
147 // failed
148 // cache(undefined)
Tim van der Lippe2c891972021-07-29 16:22:50 +0100149 return { found: false };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100150}
Tim van der Lippe2c891972021-07-29 16:22:50 +0100151exports.relative = relative;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100152
153function resolverReducer(resolvers, map) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100154 if (Array.isArray(resolvers)) {
155 resolvers.forEach(r => resolverReducer(r, map));
156 return map;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100157 }
158
159 if (typeof resolvers === 'string') {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100160 map.set(resolvers, null);
161 return map;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100162 }
163
164 if (typeof resolvers === 'object') {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100165 for (const key in resolvers) {
166 map.set(key, resolvers[key]);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100167 }
Tim van der Lippe2c891972021-07-29 16:22:50 +0100168 return map;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100169 }
170
Tim van der Lippe2c891972021-07-29 16:22:50 +0100171 const err = new Error('invalid resolver config');
172 err.name = ERROR_NAME;
173 throw err;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100174}
175
176function getBaseDir(sourceFile) {
Tim van der Lippe0ceb4652022-01-06 14:23:36 +0100177 return pkgDir(sourceFile) || process.cwd();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100178}
179function requireResolver(name, sourceFile) {
180 // Try to resolve package with conventional name
Tim van der Lippe2c891972021-07-29 16:22:50 +0100181 const resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile) ||
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100182 tryRequire(name, sourceFile) ||
Tim van der Lippe2c891972021-07-29 16:22:50 +0100183 tryRequire(path.resolve(getBaseDir(sourceFile), name));
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100184
185 if (!resolver) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100186 const err = new Error(`unable to load resolver "${name}".`);
187 err.name = ERROR_NAME;
188 throw err;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100189 }
190 if (!isResolverValid(resolver)) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100191 const err = new Error(`${name} with invalid interface loaded as resolver`);
192 err.name = ERROR_NAME;
193 throw err;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100194 }
195
Tim van der Lippe2c891972021-07-29 16:22:50 +0100196 return resolver;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100197}
198
199function isResolverValid(resolver) {
200 if (resolver.interfaceVersion === 2) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100201 return resolver.resolve && typeof resolver.resolve === 'function';
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100202 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100203 return resolver.resolveImport && typeof resolver.resolveImport === 'function';
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100204 }
205}
206
Tim van der Lippe2c891972021-07-29 16:22:50 +0100207const erroredContexts = new Set();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100208
209/**
210 * Given
211 * @param {string} p - module path
212 * @param {object} context - ESLint context
213 * @return {string} - the full module filesystem path;
214 * null if package is core;
215 * undefined if not found
216 */
217function resolve(p, context) {
218 try {
Tim van der Lippea6619412021-09-13 14:28:55 +0200219 return relative(p, context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), context.settings);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100220 } catch (err) {
221 if (!erroredContexts.has(context)) {
222 // The `err.stack` string starts with `err.name` followed by colon and `err.message`.
223 // We're filtering out the default `err.name` because it adds little value to the message.
Tim van der Lippe2c891972021-07-29 16:22:50 +0100224 let errMessage = err.message;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100225 if (err.name !== ERROR_NAME && err.stack) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100226 errMessage = err.stack.replace(/^Error: /, '');
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100227 }
228 context.report({
229 message: `Resolve error: ${errMessage}`,
230 loc: { line: 1, column: 0 },
Tim van der Lippe2c891972021-07-29 16:22:50 +0100231 });
232 erroredContexts.add(context);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100233 }
234 }
235}
Tim van der Lippe2c891972021-07-29 16:22:50 +0100236resolve.relative = relative;
237exports.default = resolve;