blob: ea5bf5a15015afed711cf5bd72e1fc943af70c91 [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 pkgDir = require('pkg-dir');
Tim van der Lippefdbd42e2020-04-07 15:14:36 +01005
Tim van der Lippe2c891972021-07-29 16:22:50 +01006const fs = require('fs');
7const Module = require('module');
8const path = require('path');
Tim van der Lippefdbd42e2020-04-07 15:14:36 +01009
Tim van der Lippe2c891972021-07-29 16:22:50 +010010const hashObject = require('./hash').hashObject;
11const ModuleCache = require('./ModuleCache').default;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010012
Tim van der Lippe2c891972021-07-29 16:22:50 +010013const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js'));
14exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010015
Tim van der Lippe2c891972021-07-29 16:22:50 +010016const ERROR_NAME = 'EslintPluginImportResolveError';
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010017
Tim van der Lippe2c891972021-07-29 16:22:50 +010018const fileExistsCache = new ModuleCache();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010019
20// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
21// Use `Module.createRequire` if available (added in Node v12.2.0)
22const createRequire = Module.createRequire || Module.createRequireFromPath || function (filename) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010023 const mod = new Module(filename, null);
24 mod.filename = filename;
25 mod.paths = Module._nodeModulePaths(path.dirname(filename));
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010026
Tim van der Lippe2c891972021-07-29 16:22:50 +010027 mod._compile(`module.exports = require;`, filename);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010028
Tim van der Lippe2c891972021-07-29 16:22:50 +010029 return mod.exports;
30};
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010031
32function tryRequire(target, sourceFile) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010033 let resolved;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010034 try {
35 // Check if the target exists
36 if (sourceFile != null) {
37 try {
Tim van der Lippe2c891972021-07-29 16:22:50 +010038 resolved = createRequire(path.resolve(sourceFile)).resolve(target);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010039 } catch (e) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010040 resolved = require.resolve(target);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010041 }
42 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +010043 resolved = require.resolve(target);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010044 }
45 } catch(e) {
46 // If the target does not exist then just return undefined
Tim van der Lippe2c891972021-07-29 16:22:50 +010047 return undefined;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010048 }
49
50 // If the target exists then return the loaded module
Tim van der Lippe2c891972021-07-29 16:22:50 +010051 return require(resolved);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010052}
53
54// http://stackoverflow.com/a/27382838
55exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings) {
56 // don't care if the FS is case-sensitive
Tim van der Lippe2c891972021-07-29 16:22:50 +010057 if (CASE_SENSITIVE_FS) return true;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010058
59 // null means it resolved to a builtin
Tim van der Lippe2c891972021-07-29 16:22:50 +010060 if (filepath === null) return true;
61 if (filepath.toLowerCase() === process.cwd().toLowerCase()) return true;
62 const parsedPath = path.parse(filepath);
63 const dir = parsedPath.dir;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010064
Tim van der Lippe2c891972021-07-29 16:22:50 +010065 let result = fileExistsCache.get(filepath, cacheSettings);
66 if (result != null) return result;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010067
68 // base case
69 if (dir === '' || parsedPath.root === filepath) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010070 result = true;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010071 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +010072 const filenames = fs.readdirSync(dir);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010073 if (filenames.indexOf(parsedPath.base) === -1) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010074 result = false;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010075 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +010076 result = fileExistsWithCaseSync(dir, cacheSettings);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010077 }
78 }
Tim van der Lippe2c891972021-07-29 16:22:50 +010079 fileExistsCache.set(filepath, result);
80 return result;
81};
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010082
83function relative(modulePath, sourceFile, settings) {
Tim van der Lippe2c891972021-07-29 16:22:50 +010084 return fullResolve(modulePath, sourceFile, settings).path;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010085}
86
87function fullResolve(modulePath, sourceFile, settings) {
88 // check if this is a bonus core module
Tim van der Lippe2c891972021-07-29 16:22:50 +010089 const coreSet = new Set(settings['import/core-modules']);
90 if (coreSet.has(modulePath)) return { found: true, path: null };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010091
Tim van der Lippe2c891972021-07-29 16:22:50 +010092 const sourceDir = path.dirname(sourceFile);
93 const cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010094
Tim van der Lippe2c891972021-07-29 16:22:50 +010095 const cacheSettings = ModuleCache.getSettings(settings);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010096
Tim van der Lippe2c891972021-07-29 16:22:50 +010097 const cachedPath = fileExistsCache.get(cacheKey, cacheSettings);
98 if (cachedPath !== undefined) return { found: true, path: cachedPath };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +010099
100 function cache(resolvedPath) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100101 fileExistsCache.set(cacheKey, resolvedPath);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100102 }
103
104 function withResolver(resolver, config) {
105
106 function v1() {
107 try {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100108 const resolved = resolver.resolveImport(modulePath, sourceFile, config);
109 if (resolved === undefined) return { found: false };
110 return { found: true, path: resolved };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100111 } catch (err) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100112 return { found: false };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100113 }
114 }
115
116 function v2() {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100117 return resolver.resolve(modulePath, sourceFile, config);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100118 }
119
120 switch (resolver.interfaceVersion) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100121 case 2:
122 return v2();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100123
Tim van der Lippe2c891972021-07-29 16:22:50 +0100124 default:
125 case 1:
126 return v1();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100127 }
128 }
129
130 const configResolvers = (settings['import/resolver']
Tim van der Lippe2c891972021-07-29 16:22:50 +0100131 || { 'node': settings['import/resolve'] }); // backward compatibility
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100132
Tim van der Lippe2c891972021-07-29 16:22:50 +0100133 const resolvers = resolverReducer(configResolvers, new Map());
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100134
Tim van der Lippe2c891972021-07-29 16:22:50 +0100135 for (const pair of resolvers) {
136 const name = pair[0];
137 const config = pair[1];
138 const resolver = requireResolver(name, sourceFile);
139 const resolved = withResolver(resolver, config);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100140
Tim van der Lippe2c891972021-07-29 16:22:50 +0100141 if (!resolved.found) continue;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100142
143 // else, counts
Tim van der Lippe2c891972021-07-29 16:22:50 +0100144 cache(resolved.path);
145 return resolved;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100146 }
147
148 // failed
149 // cache(undefined)
Tim van der Lippe2c891972021-07-29 16:22:50 +0100150 return { found: false };
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100151}
Tim van der Lippe2c891972021-07-29 16:22:50 +0100152exports.relative = relative;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100153
154function resolverReducer(resolvers, map) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100155 if (Array.isArray(resolvers)) {
156 resolvers.forEach(r => resolverReducer(r, map));
157 return map;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100158 }
159
160 if (typeof resolvers === 'string') {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100161 map.set(resolvers, null);
162 return map;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100163 }
164
165 if (typeof resolvers === 'object') {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100166 for (const key in resolvers) {
167 map.set(key, resolvers[key]);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100168 }
Tim van der Lippe2c891972021-07-29 16:22:50 +0100169 return map;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100170 }
171
Tim van der Lippe2c891972021-07-29 16:22:50 +0100172 const err = new Error('invalid resolver config');
173 err.name = ERROR_NAME;
174 throw err;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100175}
176
177function getBaseDir(sourceFile) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100178 return pkgDir.sync(sourceFile) || process.cwd();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100179}
180function requireResolver(name, sourceFile) {
181 // Try to resolve package with conventional name
Tim van der Lippe2c891972021-07-29 16:22:50 +0100182 const resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile) ||
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100183 tryRequire(name, sourceFile) ||
Tim van der Lippe2c891972021-07-29 16:22:50 +0100184 tryRequire(path.resolve(getBaseDir(sourceFile), name));
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100185
186 if (!resolver) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100187 const err = new Error(`unable to load resolver "${name}".`);
188 err.name = ERROR_NAME;
189 throw err;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100190 }
191 if (!isResolverValid(resolver)) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100192 const err = new Error(`${name} with invalid interface loaded as resolver`);
193 err.name = ERROR_NAME;
194 throw err;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100195 }
196
Tim van der Lippe2c891972021-07-29 16:22:50 +0100197 return resolver;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100198}
199
200function isResolverValid(resolver) {
201 if (resolver.interfaceVersion === 2) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100202 return resolver.resolve && typeof resolver.resolve === 'function';
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100203 } else {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100204 return resolver.resolveImport && typeof resolver.resolveImport === 'function';
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100205 }
206}
207
Tim van der Lippe2c891972021-07-29 16:22:50 +0100208const erroredContexts = new Set();
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100209
210/**
211 * Given
212 * @param {string} p - module path
213 * @param {object} context - ESLint context
214 * @return {string} - the full module filesystem path;
215 * null if package is core;
216 * undefined if not found
217 */
218function resolve(p, context) {
219 try {
220 return relative( p
Tim van der Lippe2c891972021-07-29 16:22:50 +0100221 , context.getFilename()
222 , context.settings
223 );
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100224 } catch (err) {
225 if (!erroredContexts.has(context)) {
226 // The `err.stack` string starts with `err.name` followed by colon and `err.message`.
227 // 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 +0100228 let errMessage = err.message;
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100229 if (err.name !== ERROR_NAME && err.stack) {
Tim van der Lippe2c891972021-07-29 16:22:50 +0100230 errMessage = err.stack.replace(/^Error: /, '');
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100231 }
232 context.report({
233 message: `Resolve error: ${errMessage}`,
234 loc: { line: 1, column: 0 },
Tim van der Lippe2c891972021-07-29 16:22:50 +0100235 });
236 erroredContexts.add(context);
Tim van der Lippefdbd42e2020-04-07 15:14:36 +0100237 }
238 }
239}
Tim van der Lippe2c891972021-07-29 16:22:50 +0100240resolve.relative = relative;
241exports.default = resolve;