resolve.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. 'use strict'
  2. exports.__esModule = true
  3. const pkgDir = require('pkg-dir')
  4. const fs = require('fs')
  5. const Module = require('module')
  6. const path = require('path')
  7. const hashObject = require('./hash').hashObject
  8. , ModuleCache = require('./ModuleCache').default
  9. const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname, '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. // http://stackoverflow.com/a/27382838
  43. exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings) {
  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()) return true
  49. const parsedPath = path.parse(filepath)
  50. , 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)
  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. function fullResolve(modulePath, sourceFile, settings) {
  71. // check if this is a bonus core module
  72. const coreSet = new Set(settings['import/core-modules'])
  73. if (coreSet.has(modulePath)) return { found: true, path: null }
  74. const sourceDir = path.dirname(sourceFile)
  75. , cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath
  76. const cacheSettings = ModuleCache.getSettings(settings)
  77. const cachedPath = fileExistsCache.get(cacheKey, cacheSettings)
  78. if (cachedPath !== undefined) return { found: true, path: cachedPath }
  79. function cache(resolvedPath) {
  80. fileExistsCache.set(cacheKey, resolvedPath)
  81. }
  82. function withResolver(resolver, config) {
  83. function v1() {
  84. try {
  85. const resolved = resolver.resolveImport(modulePath, sourceFile, config)
  86. if (resolved === undefined) return { found: false }
  87. return { found: true, path: resolved }
  88. } catch (err) {
  89. return { found: false }
  90. }
  91. }
  92. function v2() {
  93. return resolver.resolve(modulePath, sourceFile, config)
  94. }
  95. switch (resolver.interfaceVersion) {
  96. case 2:
  97. return v2()
  98. default:
  99. case 1:
  100. return v1()
  101. }
  102. }
  103. const configResolvers = (settings['import/resolver']
  104. || { 'node': settings['import/resolve'] }) // backward compatibility
  105. const resolvers = resolverReducer(configResolvers, new Map())
  106. for (let pair of resolvers) {
  107. let name = pair[0]
  108. , config = pair[1]
  109. const resolver = requireResolver(name, sourceFile)
  110. , resolved = withResolver(resolver, config)
  111. if (!resolved.found) continue
  112. // else, counts
  113. cache(resolved.path)
  114. return resolved
  115. }
  116. // failed
  117. // cache(undefined)
  118. return { found: false }
  119. }
  120. exports.relative = relative
  121. function resolverReducer(resolvers, map) {
  122. if (resolvers instanceof Array) {
  123. resolvers.forEach(r => resolverReducer(r, map))
  124. return map
  125. }
  126. if (typeof resolvers === 'string') {
  127. map.set(resolvers, null)
  128. return map
  129. }
  130. if (typeof resolvers === 'object') {
  131. for (let key in resolvers) {
  132. map.set(key, resolvers[key])
  133. }
  134. return map
  135. }
  136. const err = new Error('invalid resolver config')
  137. err.name = ERROR_NAME
  138. throw err
  139. }
  140. function getBaseDir(sourceFile) {
  141. return pkgDir.sync(sourceFile) || process.cwd()
  142. }
  143. function requireResolver(name, sourceFile) {
  144. // Try to resolve package with conventional name
  145. let resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile) ||
  146. tryRequire(name, sourceFile) ||
  147. tryRequire(path.resolve(getBaseDir(sourceFile), name))
  148. if (!resolver) {
  149. const err = new Error(`unable to load resolver "${name}".`)
  150. err.name = ERROR_NAME
  151. throw err
  152. }
  153. if (!isResolverValid(resolver)) {
  154. const err = new Error(`${name} with invalid interface loaded as resolver`)
  155. err.name = ERROR_NAME
  156. throw err
  157. }
  158. return resolver
  159. }
  160. function isResolverValid(resolver) {
  161. if (resolver.interfaceVersion === 2) {
  162. return resolver.resolve && typeof resolver.resolve === 'function'
  163. } else {
  164. return resolver.resolveImport && typeof resolver.resolveImport === 'function'
  165. }
  166. }
  167. const erroredContexts = new Set()
  168. /**
  169. * Given
  170. * @param {string} p - module path
  171. * @param {object} context - ESLint context
  172. * @return {string} - the full module filesystem path;
  173. * null if package is core;
  174. * undefined if not found
  175. */
  176. function resolve(p, context) {
  177. try {
  178. return relative( p
  179. , context.getFilename()
  180. , context.settings
  181. )
  182. } catch (err) {
  183. if (!erroredContexts.has(context)) {
  184. // The `err.stack` string starts with `err.name` followed by colon and `err.message`.
  185. // We're filtering out the default `err.name` because it adds little value to the message.
  186. let errMessage = err.message
  187. if (err.name !== ERROR_NAME && err.stack) {
  188. errMessage = err.stack.replace(/^Error: /, '')
  189. }
  190. context.report({
  191. message: `Resolve error: ${errMessage}`,
  192. loc: { line: 1, column: 0 },
  193. })
  194. erroredContexts.add(context)
  195. }
  196. }
  197. }
  198. resolve.relative = relative
  199. exports.default = resolve