You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
6.7 KiB
237 lines
6.7 KiB
'use strict'; |
|
exports.__esModule = true; |
|
|
|
const fs = require('fs'); |
|
const Module = require('module'); |
|
const path = require('path'); |
|
|
|
const hashObject = require('./hash').hashObject; |
|
const ModuleCache = require('./ModuleCache').default; |
|
const pkgDir = require('./pkgDir').default; |
|
|
|
const CASE_SENSITIVE_FS = !fs.existsSync(path.join(__dirname.toUpperCase(), 'reSOLVE.js')); |
|
exports.CASE_SENSITIVE_FS = CASE_SENSITIVE_FS; |
|
|
|
const ERROR_NAME = 'EslintPluginImportResolveError'; |
|
|
|
const fileExistsCache = new ModuleCache(); |
|
|
|
// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0) |
|
// Use `Module.createRequire` if available (added in Node v12.2.0) |
|
const createRequire = Module.createRequire || Module.createRequireFromPath || function (filename) { |
|
const mod = new Module(filename, null); |
|
mod.filename = filename; |
|
mod.paths = Module._nodeModulePaths(path.dirname(filename)); |
|
|
|
mod._compile(`module.exports = require;`, filename); |
|
|
|
return mod.exports; |
|
}; |
|
|
|
function tryRequire(target, sourceFile) { |
|
let resolved; |
|
try { |
|
// Check if the target exists |
|
if (sourceFile != null) { |
|
try { |
|
resolved = createRequire(path.resolve(sourceFile)).resolve(target); |
|
} catch (e) { |
|
resolved = require.resolve(target); |
|
} |
|
} else { |
|
resolved = require.resolve(target); |
|
} |
|
} catch (e) { |
|
// If the target does not exist then just return undefined |
|
return undefined; |
|
} |
|
|
|
// If the target exists then return the loaded module |
|
return require(resolved); |
|
} |
|
|
|
// https://stackoverflow.com/a/27382838 |
|
exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) { |
|
// don't care if the FS is case-sensitive |
|
if (CASE_SENSITIVE_FS) return true; |
|
|
|
// null means it resolved to a builtin |
|
if (filepath === null) return true; |
|
if (filepath.toLowerCase() === process.cwd().toLowerCase() && !strict) return true; |
|
const parsedPath = path.parse(filepath); |
|
const dir = parsedPath.dir; |
|
|
|
let result = fileExistsCache.get(filepath, cacheSettings); |
|
if (result != null) return result; |
|
|
|
// base case |
|
if (dir === '' || parsedPath.root === filepath) { |
|
result = true; |
|
} else { |
|
const filenames = fs.readdirSync(dir); |
|
if (filenames.indexOf(parsedPath.base) === -1) { |
|
result = false; |
|
} else { |
|
result = fileExistsWithCaseSync(dir, cacheSettings, strict); |
|
} |
|
} |
|
fileExistsCache.set(filepath, result); |
|
return result; |
|
}; |
|
|
|
function relative(modulePath, sourceFile, settings) { |
|
return fullResolve(modulePath, sourceFile, settings).path; |
|
} |
|
|
|
function fullResolve(modulePath, sourceFile, settings) { |
|
// check if this is a bonus core module |
|
const coreSet = new Set(settings['import/core-modules']); |
|
if (coreSet.has(modulePath)) return { found: true, path: null }; |
|
|
|
const sourceDir = path.dirname(sourceFile); |
|
const cacheKey = sourceDir + hashObject(settings).digest('hex') + modulePath; |
|
|
|
const cacheSettings = ModuleCache.getSettings(settings); |
|
|
|
const cachedPath = fileExistsCache.get(cacheKey, cacheSettings); |
|
if (cachedPath !== undefined) return { found: true, path: cachedPath }; |
|
|
|
function cache(resolvedPath) { |
|
fileExistsCache.set(cacheKey, resolvedPath); |
|
} |
|
|
|
function withResolver(resolver, config) { |
|
|
|
function v1() { |
|
try { |
|
const resolved = resolver.resolveImport(modulePath, sourceFile, config); |
|
if (resolved === undefined) return { found: false }; |
|
return { found: true, path: resolved }; |
|
} catch (err) { |
|
return { found: false }; |
|
} |
|
} |
|
|
|
function v2() { |
|
return resolver.resolve(modulePath, sourceFile, config); |
|
} |
|
|
|
switch (resolver.interfaceVersion) { |
|
case 2: |
|
return v2(); |
|
|
|
default: |
|
case 1: |
|
return v1(); |
|
} |
|
} |
|
|
|
const configResolvers = (settings['import/resolver'] |
|
|| { 'node': settings['import/resolve'] }); // backward compatibility |
|
|
|
const resolvers = resolverReducer(configResolvers, new Map()); |
|
|
|
for (const pair of resolvers) { |
|
const name = pair[0]; |
|
const config = pair[1]; |
|
const resolver = requireResolver(name, sourceFile); |
|
const resolved = withResolver(resolver, config); |
|
|
|
if (!resolved.found) continue; |
|
|
|
// else, counts |
|
cache(resolved.path); |
|
return resolved; |
|
} |
|
|
|
// failed |
|
// cache(undefined) |
|
return { found: false }; |
|
} |
|
exports.relative = relative; |
|
|
|
function resolverReducer(resolvers, map) { |
|
if (Array.isArray(resolvers)) { |
|
resolvers.forEach(r => resolverReducer(r, map)); |
|
return map; |
|
} |
|
|
|
if (typeof resolvers === 'string') { |
|
map.set(resolvers, null); |
|
return map; |
|
} |
|
|
|
if (typeof resolvers === 'object') { |
|
for (const key in resolvers) { |
|
map.set(key, resolvers[key]); |
|
} |
|
return map; |
|
} |
|
|
|
const err = new Error('invalid resolver config'); |
|
err.name = ERROR_NAME; |
|
throw err; |
|
} |
|
|
|
function getBaseDir(sourceFile) { |
|
return pkgDir(sourceFile) || process.cwd(); |
|
} |
|
function requireResolver(name, sourceFile) { |
|
// Try to resolve package with conventional name |
|
const resolver = tryRequire(`eslint-import-resolver-${name}`, sourceFile) || |
|
tryRequire(name, sourceFile) || |
|
tryRequire(path.resolve(getBaseDir(sourceFile), name)); |
|
|
|
if (!resolver) { |
|
const err = new Error(`unable to load resolver "${name}".`); |
|
err.name = ERROR_NAME; |
|
throw err; |
|
} |
|
if (!isResolverValid(resolver)) { |
|
const err = new Error(`${name} with invalid interface loaded as resolver`); |
|
err.name = ERROR_NAME; |
|
throw err; |
|
} |
|
|
|
return resolver; |
|
} |
|
|
|
function isResolverValid(resolver) { |
|
if (resolver.interfaceVersion === 2) { |
|
return resolver.resolve && typeof resolver.resolve === 'function'; |
|
} else { |
|
return resolver.resolveImport && typeof resolver.resolveImport === 'function'; |
|
} |
|
} |
|
|
|
const erroredContexts = new Set(); |
|
|
|
/** |
|
* Given |
|
* @param {string} p - module path |
|
* @param {object} context - ESLint context |
|
* @return {string} - the full module filesystem path; |
|
* null if package is core; |
|
* undefined if not found |
|
*/ |
|
function resolve(p, context) { |
|
try { |
|
return relative(p, context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(), context.settings); |
|
} catch (err) { |
|
if (!erroredContexts.has(context)) { |
|
// The `err.stack` string starts with `err.name` followed by colon and `err.message`. |
|
// We're filtering out the default `err.name` because it adds little value to the message. |
|
let errMessage = err.message; |
|
if (err.name !== ERROR_NAME && err.stack) { |
|
errMessage = err.stack.replace(/^Error: /, ''); |
|
} |
|
context.report({ |
|
message: `Resolve error: ${errMessage}`, |
|
loc: { line: 1, column: 0 }, |
|
}); |
|
erroredContexts.add(context); |
|
} |
|
} |
|
} |
|
resolve.relative = relative; |
|
exports.default = resolve;
|
|
|