Skip to content

Commit

Permalink
[utils] [refactor] avoid hoisting
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Mar 13, 2024
1 parent 2d38b33 commit 70ca58f
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 117 deletions.
36 changes: 18 additions & 18 deletions utils/ignore.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@ const log = require('debug')('eslint-plugin-import:utils:ignore');
/** @type {Set<import('./types').Extension>} */ let cachedSet;
/** @type {import('./types').ESLintSettings} */ let lastSettings;

/** @type {(context: import('eslint').Rule.RuleContext) => Set<import('./types').Extension>} */
function validExtensions(context) {
if (cachedSet && context.settings === lastSettings) {
return cachedSet;
}

lastSettings = context.settings;
cachedSet = makeValidExtensionSet(context.settings);
return cachedSet;
}

/** @type {import('./ignore').getFileExtensions} */
function makeValidExtensionSet(settings) {
// start with explicit JS-parsed extensions
Expand All @@ -42,6 +31,24 @@ function makeValidExtensionSet(settings) {
}
exports.getFileExtensions = makeValidExtensionSet;

/** @type {(context: import('eslint').Rule.RuleContext) => Set<import('./types').Extension>} */
function validExtensions(context) {
if (cachedSet && context.settings === lastSettings) {
return cachedSet;
}

lastSettings = context.settings;
cachedSet = makeValidExtensionSet(context.settings);
return cachedSet;
}

/** @type {import('./ignore').hasValidExtension} */
function hasValidExtension(path, context) {
// eslint-disable-next-line no-extra-parens
return validExtensions(context).has(/** @type {import('./types').Extension} */ (extname(path)));
}
exports.hasValidExtension = hasValidExtension;

/** @type {import('./ignore').default} */
exports.default = function ignore(path, context) {
// check extension whitelist first (cheap)
Expand All @@ -60,10 +67,3 @@ exports.default = function ignore(path, context) {

return false;
};

/** @type {import('./ignore').hasValidExtension} */
function hasValidExtension(path, context) {
// eslint-disable-next-line no-extra-parens
return validExtensions(context).has(/** @type {import('./types').Extension} */ (extname(path)));
}
exports.hasValidExtension = hasValidExtension;
82 changes: 41 additions & 41 deletions utils/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,47 @@ function transformHashbang(text) {
return text.replace(/^#!([^\r\n]+)/u, (_, captured) => `//${captured}`);
}

/** @type {(path: string, context: import('eslint').Rule.RuleContext & { settings?: ESLintSettings }) => import('eslint').Rule.RuleContext['parserPath']} */
function getParserPath(path, context) {
const parsers = context.settings['import/parsers'];
if (parsers != null) {
// eslint-disable-next-line no-extra-parens
const extension = /** @type {Extension} */ (extname(path));
for (const parserPath in parsers) {
if (parsers[parserPath].indexOf(extension) > -1) {
// use this alternate parser
log('using alt parser:', parserPath);
return parserPath;
}
}
}
// default to use ESLint parser
return context.parserPath;
}

/** @type {(path: string, context: import('eslint').Rule.RuleContext) => string | null | (import('eslint').Linter.ParserModule)} */
function getParser(path, context) {
const parserPath = getParserPath(path, context);
if (parserPath) {
return parserPath;
}
if (
!!context.languageOptions
&& !!context.languageOptions.parser
&& typeof context.languageOptions.parser !== 'string'
&& (
// @ts-expect-error TODO: figure out a better type
typeof context.languageOptions.parser.parse === 'function'
// @ts-expect-error TODO: figure out a better type
|| typeof context.languageOptions.parser.parseForESLint === 'function'
)
) {
return context.languageOptions.parser;
}

return null;
}

/** @type {import('./parse').default} */
exports.default = function parse(path, content, context) {
if (context == null) { throw new Error('need context to parse properly'); }
Expand Down Expand Up @@ -131,44 +172,3 @@ exports.default = function parse(path, content, context) {
// @ts-expect-error TODO: FIXME
return makeParseReturn(ast, keysFromParser(parserOrPath, parser, undefined));
};

/** @type {(path: string, context: import('eslint').Rule.RuleContext) => string | null | (import('eslint').Linter.ParserModule)} */
function getParser(path, context) {
const parserPath = getParserPath(path, context);
if (parserPath) {
return parserPath;
}
if (
!!context.languageOptions
&& !!context.languageOptions.parser
&& typeof context.languageOptions.parser !== 'string'
&& (
// @ts-expect-error TODO: figure out a better type
typeof context.languageOptions.parser.parse === 'function'
// @ts-expect-error TODO: figure out a better type
|| typeof context.languageOptions.parser.parseForESLint === 'function'
)
) {
return context.languageOptions.parser;
}

return null;
}

/** @type {(path: string, context: import('eslint').Rule.RuleContext & { settings?: ESLintSettings }) => import('eslint').Rule.RuleContext['parserPath']} */
function getParserPath(path, context) {
const parsers = context.settings['import/parsers'];
if (parsers != null) {
// eslint-disable-next-line no-extra-parens
const extension = /** @type {Extension} */ (extname(path));
for (const parserPath in parsers) {
if (parsers[parserPath].indexOf(extension) > -1) {
// use this alternate parser
log('using alt parser:', parserPath);
return parserPath;
}
}
}
// default to use ESLint parser
return context.parserPath;
}
116 changes: 58 additions & 58 deletions utils/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ const createRequire = Module.createRequire
return mod.exports;
};

/** @type {(resolver: object) => resolver is import('./resolve').Resolver} */
function isResolverValid(resolver) {
if ('interfaceVersion' in resolver && resolver.interfaceVersion === 2) {
return 'resolve' in resolver && !!resolver.resolve && typeof resolver.resolve === 'function';
}
return 'resolveImport' in resolver && !!resolver.resolveImport && typeof resolver.resolveImport === 'function';
}

/** @type {<T extends string>(target: T, sourceFile?: string | null | undefined) => undefined | ReturnType<typeof require>} */
function tryRequire(target, sourceFile) {
let resolved;
Expand All @@ -57,6 +65,56 @@ function tryRequire(target, sourceFile) {
return require(resolved);
}

/** @type {<T extends Map<string, unknown>>(resolvers: string[] | string | { [k: string]: string }, map: T) => T} */
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;
}

/** @type {(sourceFile: string) => string} */
function getBaseDir(sourceFile) {
return pkgDir(sourceFile) || process.cwd();
}

/** @type {(name: string, sourceFile: string) => import('./resolve').Resolver} */
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;
}

// https://stackoverflow.com/a/27382838
/** @type {import('./resolve').fileExistsWithCaseSync} */
exports.fileExistsWithCaseSync = function fileExistsWithCaseSync(filepath, cacheSettings, strict) {
Expand Down Expand Up @@ -159,64 +217,6 @@ function relative(modulePath, sourceFile, settings) {
}
exports.relative = relative;

/** @type {<T extends Map<string, unknown>>(resolvers: string[] | string | { [k: string]: string }, map: T) => T} */
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;
}

/** @type {(sourceFile: string) => string} */
function getBaseDir(sourceFile) {
return pkgDir(sourceFile) || process.cwd();
}

/** @type {(name: string, sourceFile: string) => import('./resolve').Resolver} */
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;
}

/** @type {(resolver: object) => resolver is import('./resolve').Resolver} */
function isResolverValid(resolver) {
if ('interfaceVersion' in resolver && resolver.interfaceVersion === 2) {
return 'resolve' in resolver && !!resolver.resolve && typeof resolver.resolve === 'function';
}
return 'resolveImport' in resolver && !!resolver.resolveImport && typeof resolver.resolveImport === 'function';
}

/** @type {Set<import('eslint').Rule.RuleContext>} */
const erroredContexts = new Set();

Expand Down

0 comments on commit 70ca58f

Please sign in to comment.