eslint-plugin-import-x
Version:
Import with sanity.
1,553 lines (1,510 loc) • 268 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const node_module = __toESM(require("node:module"));
const node_path = __toESM(require("node:path"));
const unrs_resolver = __toESM(require("unrs-resolver"));
const eslint_import_context = __toESM(require("eslint-import-context"));
const node_fs = __toESM(require("node:fs"));
const debug = __toESM(require("debug"));
const eslint = __toESM(require("eslint"));
const stable_hash_x = __toESM(require("stable-hash-x"));
const __typescript_eslint_types = __toESM(require("@typescript-eslint/types"));
const node_url = __toESM(require("node:url"));
const node_vm = __toESM(require("node:vm"));
const minimatch = __toESM(require("minimatch"));
const semver = __toESM(require("semver"));
const is_glob = __toESM(require("is-glob"));
const eslint_use_at_your_own_risk = __toESM(require("eslint/use-at-your-own-risk"));
//#region src/config/electron.ts
/** Default settings for Electron applications. */
var electron_default$1 = { settings: { "import-x/core-modules": ["electron"] } };
//#endregion
//#region src/config/errors.ts
/**
* Unopinionated config. just the things that are necessarily runtime errors
* waiting to happen.
*/
var errors_default$1 = {
plugins: ["import-x"],
rules: {
"import-x/no-unresolved": 2,
"import-x/named": 2,
"import-x/namespace": 2,
"import-x/default": 2,
"import-x/export": 2
}
};
//#endregion
//#region src/config/flat/electron.ts
/** Default settings for Electron applications. */
var electron_default = { settings: { "import-x/core-modules": ["electron"] } };
//#endregion
//#region src/config/flat/errors.ts
/**
* Unopinionated config. just the things that are necessarily runtime errors
* waiting to happen.
*/
var errors_default = { rules: {
"import-x/no-unresolved": 2,
"import-x/named": 2,
"import-x/namespace": 2,
"import-x/default": 2,
"import-x/export": 2
} };
//#endregion
//#region src/config/flat/react-native.ts
/** Adds platform extensions to Node resolver */
var react_native_default$1 = { settings: { "import-x/resolver": { node: { extensions: [
".js",
".web.js",
".ios.js",
".android.js"
] } } } };
//#endregion
//#region src/config/flat/react.ts
/**
* Adds `.jsx` as an extension, and enables JSX parsing.
*
* Even if _you_ aren't using JSX (or .jsx) directly, if your dependencies
* define jsnext:main and have JSX internally, you may run into problems if you
* don't enable these settings at the top level.
*/
var react_default$1 = {
settings: { "import-x/extensions": [
".js",
".jsx",
".mjs",
".cjs"
] },
languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }
};
//#endregion
//#region src/config/flat/recommended.ts
/** The basics. */
var recommended_default$1 = { rules: {
"import-x/no-unresolved": "error",
"import-x/named": "error",
"import-x/namespace": "error",
"import-x/default": "error",
"import-x/export": "error",
"import-x/no-named-as-default": "warn",
"import-x/no-named-as-default-member": "warn",
"import-x/no-duplicates": "warn"
} };
//#endregion
//#region src/config/flat/stage-0.ts
/**
* Rules in progress.
*
* Do not expect these to adhere to semver across releases.
*/
var stage_0_default$1 = { rules: { "import-x/no-deprecated": 1 } };
//#endregion
//#region src/config/flat/typescript.ts
/**
* This config:
*
* 1. Adds `.jsx`, `.ts`, `.cts`, `.mts`, and `.tsx` as an extension
* 2. Enables JSX/TSX parsing
*/
const typeScriptExtensions$1 = [
".ts",
".tsx",
".cts",
".mts"
];
const allExtensions$1 = [
...typeScriptExtensions$1,
".js",
".jsx",
".cjs",
".mjs"
];
var typescript_default$1 = {
settings: {
"import-x/extensions": allExtensions$1,
"import-x/external-module-folders": ["node_modules", "node_modules/@types"],
"import-x/parsers": { "@typescript-eslint/parser": [...typeScriptExtensions$1] },
"import-x/resolver": { typescript: true }
},
rules: { "import-x/named": "off" }
};
//#endregion
//#region src/config/flat/warnings.ts
/** More opinionated config. */
var warnings_default$1 = { rules: {
"import-x/no-named-as-default": 1,
"import-x/no-named-as-default-member": 1,
"import-x/no-rename-default": 1,
"import-x/no-duplicates": 1
} };
//#endregion
//#region src/config/react-native.ts
/** Adds platform extensions to Node resolver */
var react_native_default = { settings: { "import-x/resolver": { node: { extensions: [
".js",
".web.js",
".ios.js",
".android.js"
] } } } };
//#endregion
//#region src/config/react.ts
/**
* Adds `.jsx` as an extension, and enables JSX parsing.
*
* Even if _you_ aren't using JSX (or .jsx) directly, if your dependencies
* define jsnext:main and have JSX internally, you may run into problems if you
* don't enable these settings at the top level.
*/
var react_default = {
settings: { "import-x/extensions": [".js", ".jsx"] },
parserOptions: { ecmaFeatures: { jsx: true } }
};
//#endregion
//#region src/config/recommended.ts
/** The basics. */
var recommended_default = {
plugins: ["import-x"],
rules: {
"import-x/no-unresolved": "error",
"import-x/named": "error",
"import-x/namespace": "error",
"import-x/default": "error",
"import-x/export": "error",
"import-x/no-named-as-default": "warn",
"import-x/no-named-as-default-member": "warn",
"import-x/no-duplicates": "warn"
},
parserOptions: {
sourceType: "module",
ecmaVersion: 2018
}
};
//#endregion
//#region src/config/stage-0.ts
/**
* Rules in progress.
*
* Do not expect these to adhere to semver across releases.
*/
var stage_0_default = {
plugins: ["import-x"],
rules: { "import-x/no-deprecated": 1 }
};
//#endregion
//#region src/config/typescript.ts
/**
* This config:
*
* 1. Adds `.jsx`, `.ts`, `.cts`, `.mts`, and `.tsx` as an extension
* 2. Enables JSX/TSX parsing
*/
const typeScriptExtensions = [
".ts",
".tsx",
".cts",
".mts"
];
const allExtensions = [
...typeScriptExtensions,
".js",
".jsx",
".cjs",
".mjs"
];
var typescript_default = {
settings: {
"import-x/extensions": allExtensions,
"import-x/external-module-folders": ["node_modules", "node_modules/@types"],
"import-x/parsers": { "@typescript-eslint/parser": [...typeScriptExtensions] },
"import-x/resolver": { typescript: true }
},
rules: { "import-x/named": "off" }
};
//#endregion
//#region src/config/warnings.ts
/** More opinionated config. */
var warnings_default = {
plugins: ["import-x"],
rules: {
"import-x/no-named-as-default": 1,
"import-x/no-named-as-default-member": 1,
"import-x/no-rename-default": 1,
"import-x/no-duplicates": 1
}
};
//#endregion
//#region src/require.ts
const importMetaUrl$1 = require("url").pathToFileURL(__filename).href;
const cjsRequire = importMetaUrl$1 ? (0, node_module.createRequire)(importMetaUrl$1) : require;
//#endregion
//#region src/meta.ts
const { name, version } = cjsRequire("../package.json");
const meta = {
name,
version
};
//#endregion
//#region src/node-resolver.ts
function createNodeResolver({ extensions = [
".mjs",
".cjs",
".js",
".json",
".node"
], conditionNames = [
"import",
"require",
"default"
], mainFields = ["module", "main"],...restOptions } = {}) {
const resolver = new unrs_resolver.ResolverFactory({
extensions,
conditionNames,
mainFields,
...restOptions
});
return {
interfaceVersion: 3,
name: "eslint-plugin-import-x:node",
resolve(modulePath, sourceFile) {
if ((0, node_module.isBuiltin)(modulePath) || modulePath.startsWith("data:")) return {
found: true,
path: null
};
try {
const resolved = resolver.sync(node_path.default.dirname(sourceFile), modulePath);
if (resolved.path) return {
found: true,
path: resolved.path
};
} catch {}
return { found: false };
}
};
}
//#endregion
//#region src/utils/deep-merge.ts
/**
* Check if the variable contains an object strictly rejecting arrays
*
* @returns `true` if obj is an object
*/
function isObjectNotArray(obj) {
return typeof obj === "object" && obj != null && !Array.isArray(obj);
}
/**
* Pure function - doesn't mutate either parameter! Merges two objects together
* deeply, overwriting the properties in first with the properties in second
*
* @param first The first object
* @param second The second object
* @returns A new object
*/
function deepMerge(first = {}, second = {}) {
const keys = new Set([...Object.keys(first), ...Object.keys(second)]);
return Object.fromEntries([...keys].map((key) => {
const firstHasKey = key in first;
const secondHasKey = key in second;
const firstValue = first[key];
const secondValue = second[key];
let value;
if (firstHasKey && secondHasKey) value = isObjectNotArray(firstValue) && isObjectNotArray(secondValue) ? deepMerge(firstValue, secondValue) : secondValue;
else if (firstHasKey) value = firstValue;
else value = secondValue;
return [key, value];
}));
}
//#endregion
//#region src/utils/apply-default.ts
/**
* Pure function - doesn't mutate either parameter! Uses the default options and
* overrides with the options provided by the user
*
* @param defaultOptions The defaults
* @param userOptions The user opts
* @returns The options with defaults
*/
function applyDefault(defaultOptions, userOptions) {
const options = structuredClone(defaultOptions);
if (userOptions == null) return options;
for (const [i, opt] of options.entries()) if (userOptions[i] !== void 0) {
const userOpt = userOptions[i];
options[i] = isObjectNotArray(userOpt) && isObjectNotArray(opt) ? deepMerge(opt, userOpt) : userOpt;
}
return options;
}
//#endregion
//#region src/utils/arraify.ts
const arraify = (value) => value ? Array.isArray(value) ? value : [value] : void 0;
//#endregion
//#region src/utils/docs-url.ts
const repoUrl = "https://github.com/un-ts/eslint-plugin-import-x";
const docsUrl = (ruleName, commitish = `v${version}`) => `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md`;
//#endregion
//#region src/utils/create-rule.ts
/**
* Creates reusable function to create rules with default options and docs URLs.
*
* @param urlCreator Creates a documentation URL for a given rule name.
* @returns Function to create a rule with the docs URL format.
*/
function RuleCreator(urlCreator) {
return function createNamedRule({ meta: meta$1, name: name$1,...rule }) {
return createRule_({
meta: {
...meta$1,
docs: {
...meta$1.docs,
url: urlCreator(name$1)
}
},
...rule
});
};
}
function createRule_({ create, defaultOptions, meta: meta$1 }) {
return {
create(context) {
const optionsWithDefault = applyDefault(defaultOptions, context.options);
return create(context, optionsWithDefault);
},
defaultOptions,
meta: meta$1
};
}
const createRule = RuleCreator(docsUrl);
//#endregion
//#region src/utils/declared-scope.ts
function declaredScope(context, node, name$1) {
const references = context.sourceCode.getScope(node).references;
const reference = references.find((x) => x.identifier.name === name$1);
return reference?.resolved?.scope.type;
}
//#endregion
//#region src/utils/get-value.ts
const getValue = (node) => {
switch (node.type) {
case __typescript_eslint_types.TSESTree.AST_NODE_TYPES.Identifier: return node.name;
case __typescript_eslint_types.TSESTree.AST_NODE_TYPES.Literal: return node.value;
default: throw new Error(`Unsupported node type: ${node.type}`);
}
};
//#endregion
//#region src/utils/ignore.ts
const log$5 = (0, debug.default)("eslint-plugin-import-x:utils:ignore");
let cachedSet;
let lastSettings;
function validExtensions(context) {
if (cachedSet && context.settings === lastSettings) return cachedSet;
lastSettings = context.settings;
cachedSet = getFileExtensions(context.settings);
return cachedSet;
}
function getFileExtensions(settings) {
const exts = new Set(settings["import-x/extensions"] || [
".js",
".mjs",
".cjs"
]);
if ("import-x/parsers" in settings) for (const parser in settings["import-x/parsers"]) {
const parserSettings = settings["import-x/parsers"][parser];
if (!Array.isArray(parserSettings)) throw new TypeError(`"settings" for ${parser} must be an array`);
for (const ext of parserSettings) exts.add(ext);
}
return exts;
}
function ignore(filepath, context, skipExtensionCheck = false) {
if (!skipExtensionCheck && !hasValidExtension(filepath, context)) return true;
const ignoreStrings = context.settings["import-x/ignore"];
if (!ignoreStrings?.length) return false;
for (let i = 0, len = ignoreStrings.length; i < len; i++) {
const ignoreString = ignoreStrings[i];
const regex = new RegExp(ignoreString);
if (regex.test(filepath)) {
log$5(`ignoring ${filepath}, matched pattern /${ignoreString}/`);
return true;
}
}
return false;
}
function hasValidExtension(filepath, context) {
return validExtensions(context).has(node_path.default.extname(filepath));
}
//#endregion
//#region src/utils/lazy-value.ts
/**
* When a value is expensive to generate, w/ this utility you can delay the
* computation until the value is needed. And once the value is computed, it
* will be cached for future calls.
*/
const lazy = (cb) => {
let isCalled = false;
let result;
return () => {
if (!isCalled) {
isCalled = true;
result = cb();
}
return result;
};
};
function defineLazyProperty(object, propertyName, valueGetter) {
const define = (value) => Object.defineProperty(object, propertyName, {
value,
enumerable: true,
writable: true
});
Object.defineProperty(object, propertyName, {
configurable: true,
enumerable: true,
get() {
const result = valueGetter();
define(result);
return result;
},
set(value) {
define(value);
}
});
return object;
}
//#endregion
//#region src/utils/module-require.ts
function createModule(filename) {
const mod = new node_module.default(filename);
mod.filename = filename;
mod.paths = node_module.default._nodeModulePaths(node_path.default.dirname(filename));
return mod;
}
function moduleRequire(p, sourceFile) {
try {
const eslintPath = cjsRequire.resolve("eslint");
const eslintModule = createModule(eslintPath);
return cjsRequire(node_module.default._resolveFilename(p, eslintModule));
} catch {}
try {
return cjsRequire.main.require(p);
} catch {}
try {
return (0, node_module.createRequire)(sourceFile)(p);
} catch {}
return cjsRequire(p);
}
//#endregion
//#region src/utils/parse.ts
function withoutProjectParserOptions(opts) {
const { EXPERIMENTAL_useProjectService, project, projectService,...rest } = opts;
return rest;
}
const log$4 = (0, debug.default)("eslint-plugin-import-x:parse");
function keysFromParser(_parserPath, parserInstance, parsedResult) {
if (parsedResult && parsedResult.visitorKeys) return parsedResult.visitorKeys;
if (parserInstance && "VisitorKeys" in parserInstance && parserInstance.VisitorKeys) return parserInstance.VisitorKeys;
return null;
}
function makeParseReturn(ast, visitorKeys) {
return {
ast,
visitorKeys
};
}
function stripUnicodeBOM(text) {
return text.codePointAt(0) === 65279 ? text.slice(1) : text;
}
function transformHashbang(text) {
return text.replace(/^#!([^\r\n]+)/u, (_, captured) => `//${captured}`);
}
function parse(path$22, content, context) {
if (context == null) throw new Error("need context to parse properly");
let parserOptions = context.languageOptions?.parserOptions || context.parserOptions;
const parserOrPath = getParser(path$22, context);
if (!parserOrPath) throw new Error("parserPath or languageOptions.parser is required!");
parserOptions = { ...parserOptions };
parserOptions.ecmaFeatures = { ...parserOptions.ecmaFeatures };
parserOptions.comment = true;
parserOptions.attachComment = true;
parserOptions.tokens = true;
parserOptions.loc = true;
parserOptions.range = true;
parserOptions.filePath = path$22;
parserOptions = withoutProjectParserOptions(parserOptions);
parserOptions.ecmaVersion ??= context.languageOptions?.ecmaVersion;
parserOptions.sourceType ??= context.languageOptions?.sourceType;
const parser = typeof parserOrPath === "string" ? moduleRequire(parserOrPath, context.physicalFilename) : parserOrPath;
content = transformHashbang(stripUnicodeBOM(String(content)));
if ("parseForESLint" in parser && typeof parser.parseForESLint === "function") {
let ast;
try {
const parserRaw = parser.parseForESLint(content, parserOptions);
ast = parserRaw.ast;
return makeParseReturn(ast, keysFromParser(parserOrPath, parser, parserRaw));
} catch (error_) {
const error = error_;
console.warn(`Error while parsing ${parserOptions.filePath}`);
console.warn(`Line ${error.lineNumber}, column ${error.column}: ${error.message}`);
}
if (!ast || typeof ast !== "object") console.warn(`\`parseForESLint\` from parser \`${typeof parserOrPath === "string" ? parserOrPath : "context.languageOptions.parser"}\` is invalid and will just be ignored`, {
content,
parserMeta: parser.meta
});
else return makeParseReturn(ast, keysFromParser(parserOrPath, parser));
}
if ("parse" in parser) {
const ast = parser.parse(content, parserOptions);
return makeParseReturn(ast, keysFromParser(parserOrPath, parser));
}
throw new Error("Parser must expose a `parse` or `parseForESLint` method");
}
function getParser(path$22, context) {
const parserPath = getParserPath(path$22, context);
if (parserPath) return parserPath;
const parser = "languageOptions" in context && context.languageOptions?.parser;
if (parser && typeof parser !== "string" && ("parse" in parser && typeof parse === "function" || "parseForESLint" in parser && typeof parser.parseForESLint === "function")) return parser;
return null;
}
function getParserPath(filepath, context) {
const parsers = context.settings["import-x/parsers"];
if (parsers != null) {
const extension = node_path.default.extname(filepath);
for (const parserPath in parsers) if (parsers[parserPath].includes(extension)) {
log$4("using alt parser:", parserPath);
return parserPath;
}
}
return context.parserPath;
}
//#endregion
//#region src/utils/pkg-up.ts
function findUp(filename, cwd) {
let dir = node_path.default.resolve(cwd || "");
const root = node_path.default.parse(dir).root;
const filenames = [filename].flat();
while (true) {
const file = filenames.find((el) => node_fs.default.existsSync(node_path.default.resolve(dir, el)));
if (file) return node_path.default.resolve(dir, file);
if (dir === root) return null;
dir = node_path.default.dirname(dir);
}
}
function pkgUp(opts) {
return findUp("package.json", opts && opts.cwd);
}
//#endregion
//#region src/utils/read-pkg-up.ts
function stripBOM(str) {
return str.replace(/^\uFEFF/, "");
}
function readPkgUp(opts) {
const fp = pkgUp(opts);
if (!fp) return {};
try {
return {
pkg: JSON.parse(stripBOM(node_fs.default.readFileSync(fp, { encoding: "utf8" }))),
path: fp
};
} catch {
return {};
}
}
//#endregion
//#region src/utils/package-path.ts
function getContextPackagePath(context) {
return getFilePackagePath(context.physicalFilename);
}
function getFilePackagePath(filename) {
return node_path.default.dirname(pkgUp({ cwd: filename }));
}
function getFilePackageName(filename) {
const { pkg, path: pkgPath } = readPkgUp({ cwd: filename });
if (pkg) return pkg.name || getFilePackageName(node_path.default.resolve(pkgPath, "../.."));
return null;
}
//#endregion
//#region src/utils/import-type.ts
/**
* Returns the base module name.
*
* @example
* '@scope/package' => '@scope/package'
* '@scope/package/subpath' => '@scope/package'
* 'package' => 'package'
* 'package/subpath' => 'package'
* 'package/subpath/index.js' => 'package'
*
* @param name The name of the module to check
* @returns The base module name
*/
function baseModule(name$1) {
if (isScoped(name$1)) {
const [scope, pkg$1] = name$1.split("/");
return `${scope}/${pkg$1}`;
}
const [pkg] = name$1.split("/");
return pkg;
}
/**
* Check if the name is an internal module.
*
* An internal module is declared by `import-x/internal-regex` via settings.
*
* @param name The name of the module to check
* @param settings The settings of the plugin
* @returns `true` if the name is an internal module, otherwise `false`
*/
function isInternalRegexMatch(name$1, settings) {
const internalScope = settings?.["import-x/internal-regex"];
return internalScope && new RegExp(internalScope).test(name$1);
}
/**
* Check if the name is an absolute path.
*
* @param name The name of the module to check
* @returns `true` if the name is an absolute path, otherwise `false`
*/
function isAbsolute(name$1) {
return typeof name$1 === "string" && node_path.default.isAbsolute(name$1);
}
/**
* Check if the name is a built-in module.
*
* A built-in module is a module that is included in Node.js by default.
*
* If `import-x/core-modules` are defined in the settings, it will also check
* against those.
*
* @example
* 'node:fs'
* 'path'
*
* @param name The name of the module to check
* @param settings The settings of the plugin
* @param modulePath The path of the module to check
* @returns `true` if the name is a built-in module, otherwise `false`
*/
function isBuiltIn(name$1, settings, modulePath) {
if (modulePath || !name$1) return false;
const base = baseModule(name$1);
const extras = settings && settings["import-x/core-modules"] || [];
return (0, node_module.isBuiltin)(base) || extras.includes(base);
}
function isExternalModule(name$1, modulePath, context) {
return (isModule(name$1) || isScoped(name$1)) && typeTest(name$1, context, modulePath) === "external";
}
const moduleRegExp = /^\w/;
/**
* Check if the name could be a module name.
*
* This is a loose check that only checks if the name contains letters, numbers,
* and underscores. It does not check if the name is a valid module name.
*
* @example
* 'package' => true
*
* '@scope/package' => false
* 'package/subpath' => false
* './package' => false
* 'package-name' => false
*
* @param name The name of the module to check
* @returns `true` if the name only contains letters, numbers, and underscores,
* otherwise `false`
*/
function isModule(name$1) {
return !!name$1 && moduleRegExp.test(name$1);
}
const scopedRegExp = /^@[^/]+\/?[^/]+/;
/**
* Check if the name could be a scoped module name.
*
* @example
* '@scope/package' => true
*
* '@/components/buttons' => false
*
* @param name The name of the module to check
* @returns `true` if the name is a scoped module name, otherwise `false`
*/
function isScoped(name$1) {
return !!name$1 && scopedRegExp.test(name$1);
}
/**
* Check if the name is a relative path to the parent module.
*
* @example
* '..' => true
* '../package' => true
*
* './package' => false
* 'package' => false
* '@scope/package' => false
*
* @param name The name of the module to check
* @returns `true` if the name is a relative path to the parent module,
* otherwise `false`
*/
function isRelativeToParent(name$1) {
return /^\.\.$|^\.\.[/\\]/.test(name$1);
}
const indexFiles = new Set([
".",
"./",
"./index",
"./index.js"
]);
/**
* Check if the name is an index file.
*
* @example
* '.' => true
* './' => true
* './index' => true
* './index.js' => true
*
* otherwise => false
*
* @param name The name of the module to check
* @returns `true` if the name is an index file, otherwise `false`
*/
function isIndex(name$1) {
return indexFiles.has(name$1);
}
/**
* Check if the name is a relative path to a sibling module.
*
* @example
* './file.js' => true
*
* '../file.js' => false
* 'file.js' => false
*
* @param name The name of the module to check
* @returns `true` if the name is a relative path to a sibling module, otherwise
* `false`
*/
function isRelativeToSibling(name$1) {
return /^\.[/\\]/.test(name$1);
}
/**
* Check if the path is an external path.
*
* An external path is a path that is outside of the package directory or the
* `import-x/external-module-folders` settings.
*
* @param filepath The path to check
* @param context The context of the rule
* @returns `true` if the path is an external path, otherwise `false`
*/
function isExternalPath(filepath, context) {
if (!filepath) return false;
const { settings } = context;
const packagePath = getContextPackagePath(context);
if (node_path.default.relative(packagePath, filepath).startsWith("..")) return true;
const folders = settings?.["import-x/external-module-folders"] || ["node_modules"];
return folders.some((folder) => {
const folderPath = node_path.default.resolve(packagePath, folder);
const relativePath = node_path.default.relative(folderPath, filepath);
return !relativePath.startsWith("..");
});
}
/**
* Check if the path is an internal path.
*
* An internal path is a path that is inside the package directory.
*
* @param filepath The path to check
* @param context The context of the rule
* @returns `true` if the path is an internal path, otherwise `false`
*/
function isInternalPath(filepath, context) {
if (!filepath) return false;
const packagePath = getContextPackagePath(context);
return !node_path.default.relative(packagePath, filepath).startsWith("../");
}
/**
* Check if the name is an external looking name.
*
* @example
* 'glob' => true
* '@scope/package' => true
*
* @param name The name of the module to check
* @returns `true` if the name is an external looking name, otherwise `false`
*/
function isExternalLookingName(name$1) {
return isModule(name$1) || isScoped(name$1);
}
/**
* Returns the type of the module.
*
* @param name The name of the module to check
* @param context The context of the rule
* @param path The path of the module to check
* @returns The type of the module
*/
function typeTest(name$1, context, path$22) {
const { settings } = context;
if (typeof name$1 === "string") {
if (isInternalRegexMatch(name$1, settings)) return "internal";
if (isAbsolute(name$1)) return "absolute";
if (isBuiltIn(name$1, settings, path$22)) return "builtin";
if (isRelativeToParent(name$1)) return "parent";
if (isIndex(name$1)) return "index";
if (isRelativeToSibling(name$1)) return "sibling";
}
if (isExternalPath(path$22, context)) return "external";
if (isInternalPath(path$22, context)) return "internal";
if (typeof name$1 === "string" && isExternalLookingName(name$1)) return "external";
return "unknown";
}
/**
* Returns the type of the module.
*
* @param name The name of the module to check
* @param context The context of the rule
* @returns The type of the module
*/
function importType(name$1, context) {
return typeTest(name$1, context, typeof name$1 === "string" ? resolve(name$1, context) : null);
}
//#endregion
//#region src/utils/pkg-dir.ts
function pkgDir(cwd) {
const fp = pkgUp({ cwd });
return fp ? node_path.default.dirname(fp) : null;
}
//#endregion
//#region src/utils/legacy-resolver-settings.ts
function resolveWithLegacyResolver(resolver, config, modulePath, sourceFile) {
if (resolver.interfaceVersion === 2) return resolver.resolve(modulePath, sourceFile, config);
try {
const resolved = resolver.resolveImport(modulePath, sourceFile, config);
if (resolved === void 0) return { found: false };
return {
found: true,
path: resolved
};
} catch {
return { found: false };
}
}
function normalizeConfigResolvers(resolvers, sourceFile) {
const resolverArray = Array.isArray(resolvers) ? resolvers : [resolvers];
const map = new Map();
for (const nameOrRecordOrObject of resolverArray) if (typeof nameOrRecordOrObject === "string") {
const name$1 = nameOrRecordOrObject;
map.set(name$1, {
name: name$1,
enable: true,
options: void 0,
resolver: requireResolver(name$1, sourceFile)
});
} else if (typeof nameOrRecordOrObject === "object") if (nameOrRecordOrObject.name && nameOrRecordOrObject.resolver) {
const object = nameOrRecordOrObject;
const { name: name$1, enable = true, options, resolver } = object;
map.set(name$1, {
name: name$1,
enable,
options,
resolver
});
} else {
const record = nameOrRecordOrObject;
for (const [name$1, enableOrOptions] of Object.entries(record)) {
const resolver = requireResolver(name$1, sourceFile);
if (typeof enableOrOptions === "boolean") map.set(name$1, {
name: name$1,
enable: enableOrOptions,
options: void 0,
resolver
});
else map.set(name$1, {
name: name$1,
enable: true,
options: enableOrOptions,
resolver
});
}
}
else {
const err = new Error("invalid resolver config");
err.name = IMPORT_RESOLVE_ERROR_NAME;
throw err;
}
return [...map.values()];
}
const LEGACY_NODE_RESOLVERS = new Set(["node", "eslint-import-resolver-node"]);
try {
LEGACY_NODE_RESOLVERS.add(cjsRequire.resolve("eslint-import-resolver-node"));
} catch {}
function requireResolver(name$1, sourceFile) {
const resolver = tryRequire(`eslint-import-resolver-${name$1}`, sourceFile) || tryRequire(name$1, sourceFile) || tryRequire(node_path.default.resolve(getBaseDir(sourceFile), name$1));
if (!resolver) {
if (LEGACY_NODE_RESOLVERS.has(name$1)) return void 0;
const err = new Error(`unable to load resolver "${name$1}".`);
err.name = IMPORT_RESOLVE_ERROR_NAME;
throw err;
}
if (!isLegacyResolverValid(resolver)) {
const err = new Error(`${name$1} with invalid interface loaded as resolver`);
err.name = IMPORT_RESOLVE_ERROR_NAME;
throw err;
}
return resolver;
}
function isLegacyResolverValid(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";
}
function tryRequire(target, sourceFile) {
let resolved;
try {
if (sourceFile == null) resolved = cjsRequire.resolve(target);
else try {
resolved = (0, node_module.createRequire)(node_path.default.resolve(sourceFile)).resolve(target);
} catch {
resolved = cjsRequire.resolve(target);
}
} catch {
return void 0;
}
return cjsRequire(resolved);
}
function getBaseDir(sourceFile) {
return pkgDir(sourceFile) || process.cwd();
}
//#endregion
//#region src/utils/module-cache.ts
const log$3 = (0, debug.default)("eslint-plugin-import-x:utils:ModuleCache");
var ModuleCache = class {
constructor(map = new Map()) {
this.map = map;
}
set(cacheKey, result) {
this.map.set(cacheKey, {
result,
lastSeen: process.hrtime()
});
log$3("setting entry for", cacheKey);
return result;
}
get(cacheKey, settings) {
const cache = this.map.get(cacheKey);
if (cache) {
if (process.hrtime(cache.lastSeen)[0] < settings.lifetime) return cache.result;
} else log$3("cache miss for", cacheKey);
}
static getSettings(settings) {
const cacheSettings = {
lifetime: 30,
...settings["import-x/cache"]
};
if (typeof cacheSettings.lifetime === "string" && ["∞", "Infinity"].includes(cacheSettings.lifetime)) cacheSettings.lifetime = Number.POSITIVE_INFINITY;
return cacheSettings;
}
};
//#endregion
//#region src/utils/resolve.ts
const importMetaUrl = require("url").pathToFileURL(__filename).href;
const _filename = importMetaUrl ? (0, node_url.fileURLToPath)(importMetaUrl) : __filename;
const _dirname = node_path.default.dirname(_filename);
const CASE_SENSITIVE_FS = !node_fs.default.existsSync(node_path.default.resolve(_dirname, node_path.default.basename(_filename).replace(/^resolve\./, "reSOLVE.")));
const IMPORT_RESOLVE_ERROR_NAME = "EslintPluginImportResolveError";
const fileExistsCache = new ModuleCache();
function fileExistsWithCaseSync(filepath, cacheSettings, strict, leaf = true) {
if (CASE_SENSITIVE_FS) return true;
if (filepath === null) return true;
if (filepath.toLowerCase() === process.cwd().toLowerCase() && !strict) return true;
const parsedPath = node_path.default.parse(filepath);
const dir = parsedPath.dir;
let result = fileExistsCache.get(filepath, cacheSettings);
if (result != null) return result;
if (dir === "" || parsedPath.root === filepath) result = true;
else {
const filenames = node_fs.default.readdirSync(dir);
result = filenames.includes(parsedPath.base) ? fileExistsWithCaseSync(dir, cacheSettings, strict, false) : !leaf && !filenames.some((p) => p.toLowerCase() === parsedPath.base.toLowerCase());
}
fileExistsCache.set(filepath, result);
return result;
}
let prevSettings = null;
let memoizedHash;
function isNamedResolver(resolver) {
return !!(typeof resolver === "object" && resolver && "name" in resolver && typeof resolver.name === "string" && resolver.name);
}
function isValidNewResolver(resolver) {
if (typeof resolver !== "object" || resolver == null) return false;
if (!("resolve" in resolver) || !("interfaceVersion" in resolver)) return false;
if (typeof resolver.interfaceVersion !== "number" || resolver.interfaceVersion !== 3) return false;
if (typeof resolver.resolve !== "function") return false;
return true;
}
function legacyNodeResolve(resolverOptions, context, modulePath, sourceFile) {
const { extensions, includeCoreModules, moduleDirectory, paths, preserveSymlinks, package: packageJson, packageFilter, pathFilter, packageIterator,...rest } = resolverOptions;
const normalizedExtensions = arraify(extensions);
const modules = arraify(moduleDirectory);
const symlinks = preserveSymlinks === false;
const resolver = createNodeResolver({
extensions: normalizedExtensions,
builtinModules: includeCoreModules !== false,
modules,
symlinks,
...rest
});
const resolved = (0, eslint_import_context.setRuleContext)(context, () => resolver.resolve(modulePath, sourceFile));
if (resolved.found) return resolved;
const normalizedPaths = arraify(paths);
if (normalizedPaths?.length) {
const paths$1 = modules?.length ? normalizedPaths.filter((p) => !modules.includes(p)) : normalizedPaths;
if (paths$1.length > 0) {
const resolver$1 = createNodeResolver({
extensions: normalizedExtensions,
builtinModules: includeCoreModules !== false,
modules: paths$1,
symlinks,
...rest
});
const resolved$1 = (0, eslint_import_context.setRuleContext)(context, () => resolver$1.resolve(modulePath, sourceFile));
if (resolved$1.found) return resolved$1;
}
}
if ([
packageJson,
packageFilter,
pathFilter,
packageIterator
].some((it) => it != null)) {
let legacyNodeResolver;
try {
legacyNodeResolver = cjsRequire("eslint-import-resolver-node");
} catch {
throw new Error([
"You're using legacy resolver options which are not supported by the new resolver.",
"Please either:",
"1. Install `eslint-import-resolver-node` as a fallback, or",
"2. Remove legacy options: `package`, `packageFilter`, `pathFilter`, `packageIterator`"
].join("\n"));
}
const resolved$1 = resolveWithLegacyResolver(legacyNodeResolver, resolverOptions, modulePath, sourceFile);
if (resolved$1.found) return resolved$1;
}
}
function fullResolve(modulePath, sourceFile, settings, context) {
const coreSet = new Set(settings["import-x/core-modules"]);
if (coreSet.has(modulePath)) return {
found: true,
path: null
};
const childContextHashKey = makeContextCacheKey(context);
const sourceDir = node_path.default.dirname(sourceFile);
if (prevSettings !== settings) {
memoizedHash = (0, stable_hash_x.stableHash)(settings);
prevSettings = settings;
}
const cacheKey = sourceDir + "\0" + childContextHashKey + "\0" + memoizedHash + "\0" + modulePath;
const cacheSettings = ModuleCache.getSettings(settings);
const cachedPath = fileExistsCache.get(cacheKey, cacheSettings);
if (cachedPath !== void 0) return {
found: true,
path: cachedPath
};
if (settings["import-x/resolver-next"]) {
let configResolvers = settings["import-x/resolver-next"];
if (!Array.isArray(configResolvers)) configResolvers = [configResolvers];
for (let i = 0, len = configResolvers.length; i < len; i++) {
const resolver = configResolvers[i];
const resolverName = isNamedResolver(resolver) ? resolver.name : `settings['import-x/resolver-next'][${i}]`;
if (!isValidNewResolver(resolver)) {
const err = new TypeError(`${resolverName} is not a valid import resolver for eslint-plugin-import-x!`);
err.name = IMPORT_RESOLVE_ERROR_NAME;
throw err;
}
const resolved = (0, eslint_import_context.setRuleContext)(context, () => resolver.resolve(modulePath, sourceFile));
if (!resolved.found) continue;
fileExistsCache.set(cacheKey, resolved.path);
return resolved;
}
} else {
const configResolvers = settings["import-x/resolver-legacy"] || settings["import-x/resolver"] || { node: settings["import-x/resolve"] };
const sourceFiles = context.physicalFilename === sourceFile || !isExternalLookingName(modulePath) ? [sourceFile] : [sourceFile, context.physicalFilename];
for (const sourceFile$1 of sourceFiles) for (const { enable, name: name$1, options, resolver } of normalizeConfigResolvers(configResolvers, sourceFile$1)) {
if (!enable) continue;
if (LEGACY_NODE_RESOLVERS.has(name$1)) {
const resolverOptions = options || {};
const resolved$1 = legacyNodeResolve(resolverOptions, context, modulePath, sourceFile$1);
if (resolved$1?.found) {
fileExistsCache.set(cacheKey, resolved$1.path);
return resolved$1;
}
if (!resolver) continue;
}
const resolved = (0, eslint_import_context.setRuleContext)(context, () => resolveWithLegacyResolver(resolver, options, modulePath, sourceFile$1));
if (!resolved?.found) continue;
fileExistsCache.set(cacheKey, resolved.path);
return resolved;
}
}
return { found: false };
}
function relative(modulePath, sourceFile, settings, context) {
return fullResolve(modulePath, sourceFile, settings, context).path;
}
const erroredContexts = new Set();
/**
* Given
*
* @param modulePath - Module path
* @param context - ESLint context
* @returns - The full module filesystem path; null if package is core;
* undefined if not found
*/
function resolve(modulePath, context) {
try {
return relative(modulePath, context.physicalFilename, context.settings, context);
} catch (error_) {
const error = error_;
if (!erroredContexts.has(context)) {
let errMessage = error.message;
if (error.name !== IMPORT_RESOLVE_ERROR_NAME && error.stack) errMessage = error.stack.replace(/^Error: /, "");
context.report({
message: `Resolve error: ${errMessage}`,
loc: {
line: 1,
column: 0
}
});
erroredContexts.add(context);
}
}
}
function importXResolverCompat(resolver, resolverOptions = {}) {
if (isValidNewResolver(resolver)) return resolver;
return {
interfaceVersion: 3,
resolve(modulePath, sourceFile) {
return resolveWithLegacyResolver(resolver, resolverOptions, modulePath, sourceFile);
}
};
}
//#endregion
//#region src/utils/unambiguous.ts
const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[*={]))|import\(/m;
/**
* Detect possible imports/exports without a full parse.
*
* A negative test means that a file is definitely _not_ a module.
*
* A positive test means it _could_ be.
*
* Not perfect, just a fast way to disqualify large non-ES6 modules and avoid a
* parse.
*/
function isMaybeUnambiguousModule(content) {
return pattern.test(content);
}
const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/;
/** Given an AST, return true if the AST unambiguously represents a module. */
function isUnambiguousModule(ast) {
return ast.body && ast.body.some((node) => unambiguousNodeType.test(node.type));
}
//#endregion
//#region src/utils/visit.ts
function visit(node, keys, visitorSpec) {
if (!node || !keys) return;
const type = node.type;
const visitor = visitorSpec[type];
if (typeof visitor === "function") visitor(node);
const childFields = keys[type];
if (!childFields) return;
for (const fieldName of childFields) for (const item of [node[fieldName]].flat()) {
if (!item || typeof item !== "object" || !("type" in item)) continue;
visit(item, keys, visitorSpec);
}
const exit = visitorSpec[`${type}:Exit`];
if (typeof exit === "function") exit(node);
}
//#endregion
//#region src/utils/export-map.ts
const log$2 = (0, debug.default)("eslint-plugin-import-x:ExportMap");
const exportCache = new Map();
const declTypes = new Set([
"VariableDeclaration",
"ClassDeclaration",
"TSDeclareFunction",
"TSEnumDeclaration",
"TSTypeAliasDeclaration",
"TSInterfaceDeclaration",
"TSAbstractClassDeclaration",
"TSModuleDeclaration"
]);
const fixup = new Set(["deprecated", "module"]);
let parseComment_;
const parseComment = (comment) => {
parseComment_ ??= cjsRequire("comment-parser").parse;
const restored = `/**${comment.split(/\r?\n/).reduce((acc, line) => {
line = line.trim();
return line && line !== "*" ? acc + "\n " + line : acc;
}, "")}
*/`;
const [doc] = parseComment_(restored);
return {
...doc,
tags: doc.tags.map((t) => t.name && fixup.has(t.tag) ? {
...t,
description: `${t.name} ${t.description}`
} : t)
};
};
var ExportMap = class ExportMap {
static for(context) {
const filepath = context.path;
const cacheKey = context.cacheKey;
let exportMap = exportCache.get(cacheKey);
const stats = lazy(() => node_fs.default.statSync(filepath));
if (exportCache.has(cacheKey)) {
const exportMap$1 = exportCache.get(cacheKey);
if (exportMap$1 === null) return null;
if (exportMap$1 != null && exportMap$1.mtime - stats().mtime.valueOf() === 0) return exportMap$1;
}
if (!hasValidExtension(filepath, context)) {
exportCache.set(cacheKey, null);
return null;
}
if (ignore(filepath, context, true)) {
log$2("ignored path due to ignore settings:", filepath);
exportCache.set(cacheKey, null);
return null;
}
const content = node_fs.default.readFileSync(filepath, { encoding: "utf8" });
if (!isMaybeUnambiguousModule(content)) {
log$2("ignored path due to unambiguous regex:", filepath);
exportCache.set(cacheKey, null);
return null;
}
log$2("cache miss", cacheKey, "for path", filepath);
exportMap = ExportMap.parse(filepath, content, context);
if (exportMap === null) {
log$2("ignored path due to ambiguous parse:", filepath);
exportCache.set(cacheKey, null);
return null;
}
exportMap.mtime = stats().mtime.valueOf();
if (exportMap.visitorKeys) exportCache.set(cacheKey, exportMap);
return exportMap;
}
static get(source, context) {
const path$22 = resolve(source, context);
if (path$22 == null) return null;
return ExportMap.for(childContext(path$22, context));
}
static parse(filepath, content, context) {
const m = new ExportMap(filepath);
const tsconfig = lazy(() => (0, eslint_import_context.getTsconfigWithContext)(context));
const isEsModuleInteropTrue = lazy(() => tsconfig()?.compilerOptions?.esModuleInterop ?? false);
let ast;
let visitorKeys;
try {
({ast, visitorKeys} = parse(filepath, content, context));
} catch (error) {
m.errors.push(error);
return m;
}
m.visitorKeys = visitorKeys;
let hasDynamicImports = false;
function processDynamicImport(source$1) {
hasDynamicImports = true;
if (source$1.type !== "Literal") return null;
const p = remotePath(source$1.value);
if (p == null) return null;
const getter = thunkFor(p, context);
m.imports.set(p, {
getter,
declarations: new Set([{
source: {
value: source$1.value,
loc: source$1.loc
},
importedSpecifiers: new Set(["ImportNamespaceSpecifier"]),
dynamic: true
}])
});
}
visit(ast, visitorKeys, {
ImportExpression(node) {
processDynamicImport(node.source);
},
CallExpression(_node) {
const node = _node;
if (node.callee.type === "Import") processDynamicImport(node.arguments[0]);
}
});
const unambiguouslyESM = lazy(() => isUnambiguousModule(ast));
if (!hasDynamicImports && !unambiguouslyESM()) return null;
const docStyles = context.settings && context.settings["import-x/docstyle"] || ["jsdoc"];
const docStyleParsers = {};
for (const style of docStyles) docStyleParsers[style] = availableDocStyleParsers[style];
const namespaces = new Map();
function remotePath(value) {
return relative(value, filepath, context.settings, context);
}
function resolveImport(value) {
const rp = remotePath(value);
if (rp == null) return null;
return ExportMap.for(childContext(rp, context));
}
function getNamespace(namespace) {
if (!namespaces.has(namespace)) return;
return function() {
return resolveImport(namespaces.get(namespace));
};
}
function addNamespace(object, identifier) {
const nsfn = getNamespace(getValue(identifier));
if (nsfn) Object.defineProperty(object, "namespace", { get: nsfn });
return object;
}
function processSpecifier(s, n, m$1) {
const nsource = "source" in n && n.source && n.source.value;
const exportMeta = {};
let local;
switch (s.type) {
case "ExportDefaultSpecifier": {
if (!nsource) return;
local = "default";
break;
}
case "ExportNamespaceSpecifier": {
m$1.exports.set(s.exported.name, n);
m$1.namespace.set(s.exported.name, Object.defineProperty(exportMeta, "namespace", { get() {
return resolveImport(nsource);
} }));
return;
}
case "ExportAllDeclaration": {
m$1.exports.set(getValue(s.exported), n);
m$1.namespace.set(getValue(s.exported), addNamespace(exportMeta, s.exported));
return;
}
case "ExportSpecifier": if (!("source" in n && n.source)) {
m$1.exports.set(getValue(s.exported), n);
m$1.namespace.set(getValue(s.exported), addNamespace(exportMeta, s.local));
return;
}
default: {
if ("local" in s) local = getValue(s.local);
else throw new Error("Unknown export specifier type");
break;
}
}
if ("exported" in s) m$1.reexports.set(getValue(s.exported), {
local,
getImport: () => resolveImport(nsource)
});
}
function captureDependencyWithSpecifiers(n) {
const declarationIsType = "importKind" in n && (n.importKind === "type" || n.importKind === "typeof");
let specifiersOnlyImportingTypes = n.specifiers.length > 0;
const importedSpecifiers = new Set();
for (const specifier of n.specifiers) {
if (specifier.type === "ImportSpecifier") importedSpecifiers.add(getValue(specifier.imported));
else if (supportedImportTypes.has(specifier.type)) importedSpecifiers.add(specifier.type);
specifiersOnlyImportingTypes = specifiersOnlyImportingTypes && "importKind" in specifier && (specifier.importKind === "type" || specifier.importKind === "typeof");
}
captureDependency(n, declarationIsType || specifiersOnlyImportingTypes, importedSpecifiers);
}
function captureDependency({ source: source$1 }, isOnlyImportingTypes, importedSpecifiers = new Set()) {
if (source$1 == null) return null;
const p = remotePath(source$1.value);
if (p == null) return null;
const declarationMetadata = {
source: {
value: source$1.value,
loc: source$1.loc
},
isOnlyImportingTypes,
importedSpecifiers
};
const existing = m.imports.get(p);
if (existing != null) {
existing.declarations.add(declarationMetadata);
return existing.getter;
}
const getter = thunkFor(p, context);
m.imports.set(p, {
getter,
declarations: new Set([declarationMetadata])
});
return getter;
}
const source = new eslint.SourceCode({
text: content,
ast
});
for (const n of ast.body) {
if (n.type === "ExportDefaultDeclaration") {
const exportMeta = captureDoc(source, docStyleParsers, n);
if (n.declaration.type === "Identifier") addNamespace(exportMeta, n.declaration);
m.exports.set("default", n);
m.namespace.set("default", exportMeta);
continue;
}
if (n.type === "ExportAllDeclaration") {
if (n.exported) {
namespaces.set(n.exported.name, n.source.value);
processSpecifier(n, n.exported, m);
} else {
const getter = captureDependency(n, n.exportKind === "type");
if (getter) m.dependencies.add(getter);
}
continue;
}
if (n.type === "ImportDeclaration") {
captureDependencyWithSpecifiers(n);
const ns = n.specifiers.find((s) => s.type === "ImportNamespaceSpecifier");
if (ns) namespaces.set(ns.local.name, n.source.value);
continue;
}
if (n.type === "ExportNamedDeclaration") {
captureDependencyWithSpecifiers(n);
if (n.declaration != null) switch (n.declaration.type) {
case "FunctionDeclaration":
case "ClassDeclaration":
case "TypeAlias":
case "InterfaceDeclaration":
case "DeclareFunction":
case "TSDeclareFunction":
case "TSEnumDeclaration":
case "TSTypeAliasDeclaration":
case "TSInterfaceDeclaration":
case "TSAbstractClassDeclaration":
case "TSModuleDeclaration": {
m.exports.set(n.declaration.id.name, n);
m.namespace.set(n.declaration.id.name, captureDoc(source, docStyleParsers, n));
break;
}
case "VariableDeclaration": {
for (const d of n.declaration.declarations) recursivePatternCapture(d.id, (id) => {
m.exports.set(id.name, n);
m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n));
});
break;