resolve.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. 'use strict';
  2. exports.__esModule = true;
  3. const fs = require('fs');
  4. const Module = require('module');
  5. const path = require('path');
  6. const hashObject = require('./hash').hashObject;
  7. const ModuleCache = require('./ModuleCache').default;
  8. const pkgDir = require('./pkgDir').default;
  9. const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js'));
  10. exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS;
  11. const ERROR_NAME = 'EslintPluginImportResolveError';
  12. const fileExistsCache = new ModuleCache();
  13. // Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
  14. // Use `Module.createRequire` if available (added in Node v12.2.0)
  15. const createRequire = Module.createRequire || Module.createRequireFromPath || function (filename) {
  16. const mod = new Module(filename, null);
  17. mod.filename = filename;
  18. mod.paths = Module._nodeModulePaths(path.dirname(filename));
  19. mod._compile(`module.exports = require;`, filename);
  20. return mod.exports;
  21. };
  22. function tryRequire(target, sourceFile) {
  23. let resolved;
  24. try {
  25. // Check if the target exists
  26. if (sourceFile != null) {
  27. try {
  28. resolved = createRequire(path.resolve(sourceFile)).resolve(target);
  29. } catch (e) {
  30. resolved = require.resolve(target);
  31. }
  32. } else {
  33. resolved = require.resolve(target);
  34. }
  35. } catch (e) {
  36. // If the target does not exist then just return undefined
  37. return undefined;
  38. }
  39. // If the target exists then return the loaded module
  40. return require(resolved);
  41. }
  42. // https://stackoverflow.com/a/27382838
  43. exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) {
  44. // don't care if the FS is case-sensitive
  45. if (CASE_SENSITIVE_FS) { return true; }
  46. // null means it resolved to a builtin
  47. if (filepath === null) { return true; }
  48. if (filepath.toLowerCase() === process.cwd().toLowerCase() && !strict) { return true; }
  49. const parsedPath = path.parse(filepath);
  50. const dir = parsedPath.dir;
  51. let result = fileExistsCache.get(filepath, cacheSettings);
  52. if (result != null) { return result; }
  53. // base case
  54. if (dir === '' || parsedPath.root === filepath) {
  55. result = true;
  56. } else {
  57. const filenames = fs.readdirSync(dir);
  58. if (filenames.indexOf(parsedPath.base) === -1) {
  59. result = false;
  60. } else {
  61. result = fileExistsWithCaseSync(dir, cacheSettings, strict);
  62. }
  63. }
  64. fileExistsCache.set(filepath, result);
  65. return result;
  66. };
  67. function relative(modulePath, sourceFile, settings) {
  68. return fullResolve(modulePath, sourceFile, settings).path;
  69. }
  70. let prevSettings = null;
  71. let memoizedHash = '';
  72. function fullResolve(modulePath, sourceFile, settings) {
  73. // check if this is a bonus core module
  74. const coreSet = new Set(settings['import/core-modules']);
  75. if (coreSet.has(modulePath)) { return { found: true, path: null }; }
  76. const sourceDir = path.dirname(sourceFile);
  77. if (prevSettings !== settings) {
  78. memoizedHash = hashObject(settings).digest('hex');
  79. prevSettings = settings;
  80. }
  81. const cacheKey = sourceDir + memoizedHash + modulePath;
  82. const cacheSettings = ModuleCache.getSettings(settings);
  83. const cachedPath = fileExistsCache.get(cacheKey, cacheSettings);
  84. if (cachedPath !== undefined) { return { found: true, path: cachedPath }; }
  85. function cache(resolvedPath) {
  86. fileExistsCache.set(cacheKey, resolvedPath);
  87. }
  88. function withResolver(resolver, config) {
  89. if (resolver.interfaceVersion === 2) {
  90. return resolver.resolve(modulePath, sourceFile, config);
  91. }
  92. try {
  93. const resolved = resolver.resolveImport(modulePath, sourceFile, config);
  94. if (resolved === undefined) { return { found: false }; }
  95. return { found: true, path: resolved };
  96. } catch (err) {
  97. return { found: false };
  98. }
  99. }
  100. const configResolvers = settings['import/resolver']
  101. || { node: settings['import/resolve'] }; // backward compatibility
  102. const resolvers = resolverReducer(configResolvers, new Map());
  103. for (const pair of resolvers) {
  104. const name = pair[0];
  105. const config = pair[1];
  106. const resolver = requireResolver(name, sourceFile);
  107. const resolved = withResolver(resolver, config);
  108. if (!resolved.found) { continue; }
  109. // else, counts
  110. cache(resolved.path);
  111. return resolved;
  112. }
  113. // failed
  114. // cache(undefined)
  115. return { found: false };
  116. }
  117. exports.relative = relative;
  118. function resolverReducer(resolvers, map) {
  119. if (Array.isArray(resolvers)) {
  120. resolvers.forEach((r) => resolverReducer(r, map));
  121. return map;
  122. }
  123. if (typeof resolvers === 'string') {
  124. map.set(resolvers, null);
  125. return map;
  126. }
  127. if (typeof resolvers === 'object') {
  128. for (const key in resolvers) {
  129. map.set(key, resolvers[key]);
  130. }
  131. return map;
  132. }
  133. const err = new Error('invalid resolver config');
  134. err.name = ERROR_NAME;
  135. throw err;
  136. }
  137. function getBaseDir(sourceFile) {
  138. return pkgDir(sourceFile) || process.cwd();
  139. }
  140. function requireResolver(name, sourceFile) {
  141. // Try to resolve package with conventional name
  142. const resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile)
  143. || tryRequire(name, sourceFile)
  144. || tryRequire(path.resolve(getBaseDir(sourceFile), name));
  145. if (!resolver) {
  146. const err = new Error(`unable to load resolver "${name}".`);
  147. err.name = ERROR_NAME;
  148. throw err;
  149. }
  150. if (!isResolverValid(resolver)) {
  151. const err = new Error(`${name} with invalid interface loaded as resolver`);
  152. err.name = ERROR_NAME;
  153. throw err;
  154. }
  155. return resolver;
  156. }
  157. function isResolverValid(resolver) {
  158. if (resolver.interfaceVersion === 2) {
  159. return resolver.resolve && typeof resolver.resolve === 'function';
  160. } else {
  161. return resolver.resolveImport && typeof resolver.resolveImport === 'function';
  162. }
  163. }
  164. const erroredContexts = new Set();
  165. /**
  166. * Given
  167. * @param {string} p - module path
  168. * @param {object} context - ESLint context
  169. * @return {string} - the full module filesystem path;
  170. * null if package is core;
  171. * undefined if not found
  172. */
  173. function resolve(p, context) {
  174. try {
  175. return relative(p, context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), context.settings);
  176. } catch (err) {
  177. if (!erroredContexts.has(context)) {
  178. // The `err.stack` string starts with `err.name` followed by colon and `err.message`.
  179. // We're filtering out the default `err.name` because it adds little value to the message.
  180. let errMessage = err.message;
  181. if (err.name !== ERROR_NAME && err.stack) {
  182. errMessage = err.stack.replace(/^Error: /, '');
  183. }
  184. context.report({
  185. message: `Resolve error: ${errMessage}`,
  186. loc: { line: 1, column: 0 },
  187. });
  188. erroredContexts.add(context);
  189. }
  190. }
  191. }
  192. resolve.relative = relative;
  193. exports.default = resolve;