UNPKG

eslint-plugin-import-x

Version:
1,553 lines (1,510 loc) 268 kB
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;