elearning/Frontend-Learner/node_modules/eslint-plugin-import-x/lib/index.cjs
2026-01-13 10:48:02 +07:00

7481 lines
No EOL
262 KiB
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;
}
default:
}
for (const s of n.specifiers) processSpecifier(s, n, m);
}
const exports$1 = ["TSExportAssignment"];
if (isEsModuleInteropTrue()) exports$1.push("TSNamespaceExportDeclaration");
if (exports$1.includes(n.type)) {
const exportedName = n.type === "TSNamespaceExportDeclaration" ? (n.id || n.name).name : "expression" in n && n.expression && ("name" in n.expression && n.expression.name || "id" in n.expression && n.expression.id && n.expression.id.name) || null;
const getRoot = (node) => {
if (node.left.type === "TSQualifiedName") return getRoot(node.left);
return node.left;
};
const exportedDecls = ast.body.filter((node) => {
return declTypes.has(node.type) && ("id" in node && node.id && ("name" in node.id ? node.id.name === exportedName : "left" in node.id && getRoot(node.id).name === exportedName) || "declarations" in node && node.declarations.find((d) => "name" in d.id && d.id.name === exportedName));
});
if (exportedDecls.length === 0) {
m.exports.set("default", n);
m.namespace.set("default", captureDoc(source, docStyleParsers, n));
continue;
}
if (isEsModuleInteropTrue() && !m.namespace.has("default")) {
m.exports.set("default", n);
m.namespace.set("default", {});
}
for (const decl of exportedDecls) if (decl.type === "TSModuleDeclaration") {
const type = decl.body?.type;
if (type === "TSModuleDeclaration") {
m.exports.set(decl.body.id.name, n);
m.namespace.set(decl.body.id.name, captureDoc(source, docStyleParsers, decl.body));
continue;
} else if (type === "TSModuleBlock" && decl.kind === "namespace") {
const metadata = captureDoc(source, docStyleParsers, decl.body);
if ("name" in decl.id) m.namespace.set(decl.id.name, metadata);
else m.namespace.set(decl.id.right.name, metadata);
}
if (decl.body?.body) for (const moduleBlockNode of decl.body.body) {
const namespaceDecl = moduleBlockNode.type === "ExportNamedDeclaration" ? moduleBlockNode.declaration : moduleBlockNode;
if (!namespaceDecl) {} else if (namespaceDecl.type === "VariableDeclaration") for (const d of namespaceDecl.declarations) recursivePatternCapture(d.id, (id) => {
m.exports.set(id.name, n);
m.namespace.set(id.name, captureDoc(source, docStyleParsers, decl, namespaceDecl, moduleBlockNode));
});
else if ("id" in namespaceDecl) {
m.exports.set(namespaceDecl.id.name, n);
m.namespace.set(namespaceDecl.id.name, captureDoc(source, docStyleParsers, moduleBlockNode));
}
}
} else {
m.exports.set("default", n);
m.namespace.set("default", captureDoc(source, docStyleParsers, decl));
}
}
}
defineLazyProperty(m, "doc", () => {
if (!ast.comments?.length) return;
for (const c of ast.comments) {
if (c.type !== "Block") continue;
try {
const doc = parseComment(c.value);
if (doc.tags.some((t) => t.tag === "module")) return doc;
} catch {}
}
});
if (isEsModuleInteropTrue() && m.namespace.size > 0 && !m.namespace.has("default")) {
m.exports.set("default", ast.body[0]);
m.namespace.set("default", {});
}
const prevParseGoal = m.parseGoal;
defineLazyProperty(m, "parseGoal", () => {
if (prevParseGoal !== "Module" && unambiguouslyESM()) return "Module";
return prevParseGoal;
});
return m;
}
namespace = new Map();
reexports = new Map();
/** Star-exports */
dependencies = new Set();
/** Dependencies of this module that are not explicitly re-exported */
imports = new Map();
exports = new Map();
errors = [];
parseGoal = "ambiguous";
constructor(path$22) {
this.path = path$22;
}
get hasDefault() {
return this.get("default") != null;
}
get size() {
let size = this.namespace.size + this.reexports.size;
for (const dep of this.dependencies) {
const d = dep();
if (d == null) continue;
size += d.size;
}
return size;
}
/**
* Note that this does not check explicitly re-exported names for existence in
* the base namespace, but it will expand all `export * from '...'` exports if
* not found in the explicit namespace.
*
* @returns True if `name` is exported by this module.
*/
has(name$1) {
if (this.namespace.has(name$1)) return true;
if (this.reexports.has(name$1)) return true;
if (name$1 !== "default") for (const dep of this.dependencies) {
const innerMap = dep();
if (!innerMap) continue;
if (innerMap.has(name$1)) return true;
}
return false;
}
/** Ensure that imported name fully resolves. */
hasDeep(name$1) {
if (this.namespace.has(name$1)) return {
found: true,
path: [this]
};
if (this.reexports.has(name$1)) {
const reexports = this.reexports.get(name$1);
const imported = reexports.getImport();
if (imported == null) return {
found: true,
path: [this]
};
if (imported.path === this.path && reexports.local === name$1) return {
found: false,
path: [this]
};
const deep = imported.hasDeep(reexports.local);
deep.path.unshift(this);
return deep;
}
if (name$1 !== "default") for (const dep of this.dependencies) {
const innerMap = dep();
if (innerMap == null) return {
found: true,
path: [this]
};
if (!innerMap) continue;
if (innerMap.path === this.path) continue;
const innerValue = innerMap.hasDeep(name$1);
if (innerValue.found) {
innerValue.path.unshift(this);
return innerValue;
}
}
return {
found: false,
path: [this]
};
}
get(name$1) {
if (this.namespace.has(name$1)) return this.namespace.get(name$1);
if (this.reexports.has(name$1)) {
const reexports = this.reexports.get(name$1);
const imported = reexports.getImport();
if (imported == null) return null;
if (imported.path === this.path && reexports.local === name$1) return void 0;
return imported.get(reexports.local);
}
if (name$1 !== "default") for (const dep of this.dependencies) {
const innerMap = dep();
if (!innerMap) continue;
if (innerMap.path === this.path) continue;
const innerValue = innerMap.get(name$1);
if (innerValue !== void 0) return innerValue;
}
}
$forEach(callback, thisArg) {
for (const [n, v] of this.namespace.entries()) callback.call(thisArg, v, n, this);
for (const [name$1, reexports] of this.reexports.entries()) {
const reexported = reexports.getImport();
callback.call(thisArg, reexported?.get(reexports.local), name$1, this);
}
this.dependencies.forEach((dep) => {
const d = dep();
if (d == null) return;
d.$forEach((v, n) => {
if (n !== "default") callback.call(thisArg, v, n, this);
});
});
}
reportErrors(context, declaration) {
if (!declaration.source) throw new Error("declaration.source is null");
const msg = this.errors.map((err) => `${err.message} (${err.lineNumber}:${err.column})`).join(", ");
context.report({
node: declaration.source,
message: `Parse errors in imported module '${declaration.source.value}': ${msg}`
});
}
};
/** Parse docs from the first node that has leading comments */
function captureDoc(source, docStyleParsers, ...nodes) {
const metadata = {};
defineLazyProperty(metadata, "doc", () => {
for (let i = 0, len = nodes.length; i < len; i++) {
const n = nodes[i];
if (!n) continue;
try {
let leadingComments;
if ("leadingComments" in n && Array.isArray(n.leadingComments)) leadingComments = n.leadingComments;
else if (n.range) leadingComments = source.getCommentsBefore(n);
if (!leadingComments || leadingComments.length === 0) continue;
for (const parser of Object.values(docStyleParsers)) {
const doc = parser(leadingComments);
if (doc) return doc;
}
return;
} catch {
continue;
}
}
});
return metadata;
}
const availableDocStyleParsers = {
jsdoc: captureJsDoc,
tomdoc: captureTomDoc
};
/** Parse JSDoc from leading comments */
function captureJsDoc(comments) {
for (let i = comments.length - 1; i >= 0; i--) {
const comment = comments[i];
if (comment.type !== "Block") continue;
try {
return parseComment(comment.value);
} catch {}
}
}
/** Parse TomDoc section from comments */
function captureTomDoc(comments) {
const lines = [];
for (const comment of comments) {
if (/^\s*$/.test(comment.value)) break;
lines.push(comment.value.trim());
}
const statusMatch = lines.join(" ").match(/^(Public|Internal|Deprecated):\s*(.+)/);
if (statusMatch) return {
description: statusMatch[2],
tags: [{
tag: statusMatch[1].toLowerCase(),
description: statusMatch[2]
}]
};
}
const supportedImportTypes = new Set(["ImportDefaultSpecifier", "ImportNamespaceSpecifier"]);
/**
* The creation of this closure is isolated from other scopes to avoid
* over-retention of unrelated variables, which has caused memory leaks. See
* #1266.
*/
function thunkFor(p, context) {
return () => ExportMap.for(childContext(p, context));
}
/**
* Traverse a pattern/identifier node, calling 'callback' for each leaf
* identifier.
*/
function recursivePatternCapture(pattern$1, callback) {
switch (pattern$1.type) {
case "Identifier": {
callback(pattern$1);
break;
}
case "ObjectPattern": {
for (const p of pattern$1.properties) {
if (p.type === "ExperimentalRestProperty" || p.type === "RestElement") {
callback(p.argument);
continue;
}
recursivePatternCapture(p.value, callback);
}
break;
}
case "ArrayPattern": {
for (const element of pattern$1.elements) {
if (element == null) continue;
if (element.type === "ExperimentalRestProperty" || element.type === "RestElement") {
callback(element.argument);
continue;
}
recursivePatternCapture(element, callback);
}
break;
}
case "AssignmentPattern": {
callback(pattern$1.left);
break;
}
default:
}
}
/**
* Don't hold full context object in memory, just grab what we need. also
* calculate a cacheKey, where parts of the cacheKey hash are memoized
*/
function childContext(path$22, context) {
const { settings, parserOptions, parserPath, languageOptions, cwd, filename, physicalFilename } = context;
return {
cacheKey: makeContextCacheKey(context) + "\0" + path$22,
settings,
parserOptions,
parserPath,
languageOptions,
path: path$22,
cwd,
filename,
physicalFilename
};
}
function makeContextCacheKey(context) {
const { settings, parserPath, parserOptions, languageOptions, cwd } = context;
let hash = cwd + "\0" + (0, stable_hash_x.stableHash)(settings) + "\0" + (0, stable_hash_x.stableHash)(languageOptions?.parserOptions ?? parserOptions);
if (languageOptions) hash += "\0" + String(languageOptions.ecmaVersion) + "\0" + String(languageOptions.sourceType);
hash += "\0" + (0, stable_hash_x.stableHash)(parserPath ?? languageOptions?.parser?.meta ?? languageOptions?.parser);
return hash;
}
//#endregion
//#region src/utils/import-declaration.ts
const importDeclaration = (context, node) => {
if (node.parent && node.parent.type === __typescript_eslint_types.AST_NODE_TYPES.ImportDeclaration) return node.parent;
const ancestors = context.sourceCode.getAncestors(node);
return ancestors[ancestors.length - 1];
};
//#endregion
//#region src/utils/module-visitor.ts
/**
* Returns an object of node visitors that will call 'visitor' with every
* discovered module path.
*/
function moduleVisitor(visitor, options) {
const ignore$1 = options?.ignore;
const amd = !!options?.amd;
const commonjs = !!options?.commonjs;
const esmodule = !!{
esmodule: true,
...options
}.esmodule;
const ignoreRegExps = ignore$1 == null ? [] : ignore$1.map((p) => new RegExp(p));
function checkSourceValue(source, importer) {
if (source == null) return;
if (ignoreRegExps.some((re) => re.test(String(source.value)))) return;
visitor(source, importer);
}
function checkSource(node) {
checkSourceValue(node.source, node);
}
function checkImportCall(node) {
let modulePath;
if (node.type === "ImportExpression") modulePath = node.source;
else if (node.type === "CallExpression") {
if (node.callee.type !== "Import") return;
if (node.arguments.length !== 1) return;
modulePath = node.arguments[0];
} else throw new TypeError("this should be unreachable");
if (modulePath.type !== "Literal") return;
if (typeof modulePath.value !== "string") return;
checkSourceValue(modulePath, node);
}
function checkCommon(call) {
if (call.callee.type !== "Identifier") return;
if (call.callee.name !== "require") return;
if (call.arguments.length !== 1) return;
const modulePath = call.arguments[0];
if (modulePath.type !== "Literal") return;
if (typeof modulePath.value !== "string") return;
checkSourceValue(modulePath, call);
}
function checkAMD(call) {
if (call.callee.type !== "Identifier") return;
if (call.callee.name !== "require" && call.callee.name !== "define") return;
if (call.arguments.length !== 2) return;
const modules = call.arguments[0];
if (modules.type !== "ArrayExpression") return;
for (const element of modules.elements) {
if (!element) continue;
if (element.type !== "Literal") continue;
if (typeof element.value !== "string") continue;
if (element.value === "require" || element.value === "exports") continue;
checkSourceValue(element, element);
}
}
const visitors = {};
if (esmodule) Object.assign(visitors, {
ImportDeclaration: checkSource,
ExportNamedDeclaration: checkSource,
ExportAllDeclaration: checkSource,
CallExpression: checkImportCall,
ImportExpression: checkImportCall
});
if (commonjs || amd) {
const currentCallExpression = visitors.CallExpression;
visitors.CallExpression = function(call) {
if (currentCallExpression) currentCallExpression(call);
if (commonjs) checkCommon(call);
if (amd) checkAMD(call);
};
}
return visitors;
}
/**
* Make an options schema for the module visitor, optionally adding extra
* fields.
*/
function makeOptionsSchema(additionalProperties) {
const base = {
type: "object",
properties: {
commonjs: { type: "boolean" },
amd: { type: "boolean" },
esmodule: { type: "boolean" },
ignore: {
type: "array",
minItems: 1,
items: { type: "string" },
uniqueItems: true
}
},
additionalProperties: false
};
if (additionalProperties) for (const key in additionalProperties) base.properties[key] = additionalProperties[key];
return base;
}
/**
* Json schema object for options parameter. can be used to build rule options
* schema object.
*/
const optionsSchema = makeOptionsSchema();
//#endregion
//#region src/utils/npm-client.ts
const NPM = "npm";
const NPM_CLIENTS = new Set([
NPM,
"yarn",
"pnpm",
"bun",
"deno"
]);
let npmClient;
const getNpmClient = () => {
if (npmClient) return npmClient;
const client = process.env.npm_config_user_agent?.split("/")[0];
npmClient = client && NPM_CLIENTS.has(client) ? client : NPM;
return npmClient;
};
const getNpmInstallCommand = (packageName) => `${getNpmClient()} ${npmClient === NPM ? "i" : "add"} ${npmClient === "deno" ? `${NPM}:` : ""}${packageName}`;
//#endregion
//#region src/utils/parse-path.ts
const parsePath = (path$22) => {
const hashIndex = path$22.indexOf("#");
const queryIndex = path$22.indexOf("?");
const hasHash = hashIndex !== -1;
const hash = hasHash ? path$22.slice(hashIndex) : "";
const hasQuery = queryIndex !== -1 && (!hasHash || queryIndex < hashIndex);
const query = hasQuery ? path$22.slice(queryIndex, hasHash ? hashIndex : void 0) : "";
const pathname = hasQuery ? path$22.slice(0, queryIndex) : hasHash ? path$22.slice(0, hashIndex) : path$22;
return {
pathname,
query,
hash
};
};
const stringifyPath = ({ pathname, query, hash }) => pathname + query + hash;
//#endregion
//#region src/utils/source-type.ts
function sourceType(context) {
if ("sourceType" in context.parserOptions) return context.parserOptions.sourceType;
if ("languageOptions" in context && context.languageOptions) return context.languageOptions.sourceType;
}
//#endregion
//#region src/utils/static-require.ts
function isStaticRequire(node) {
return node && node.callee && node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length === 1 && node.arguments[0].type === "Literal" && typeof node.arguments[0].value === "string";
}
//#endregion
//#region src/rules/consistent-type-specifier-style.ts
function isComma(token) {
return token.type === "Punctuator" && token.value === ",";
}
function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
for (const specifier of specifiers) {
const token = sourceCode.getTokenAfter(specifier);
if (token && isComma(token)) fixes.push(fixer.remove(token));
fixes.push(fixer.remove(specifier));
}
}
function getImportText(node, sourceCode, specifiers, kind) {
const sourceString = sourceCode.getText(node.source);
if (specifiers.length === 0) return "";
const names = specifiers.map((s) => {
const importedName = getValue(s.imported);
if (importedName === s.local.name) return importedName;
return `${importedName} as ${s.local.name}`;
});
return `import ${kind} {${names.join(", ")}} from ${sourceString};`;
}
var consistent_type_specifier_style_default = createRule({
name: "consistent-type-specifier-style",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Enforce or ban the use of inline type-only markers for named imports."
},
fixable: "code",
schema: [{
type: "string",
enum: ["prefer-top-level", "prefer-inline"],
default: "prefer-top-level"
}],
messages: {
inline: "Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.",
topLevel: "Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers."
}
},
defaultOptions: [],
create(context) {
const { sourceCode } = context;
if (context.options[0] === "prefer-inline") return { ImportDeclaration(node) {
if (node.importKind === "value" || node.importKind == null) return;
if (node.specifiers.length === 0 || node.specifiers.length === 1 && (node.specifiers[0].type === "ImportDefaultSpecifier" || node.specifiers[0].type === "ImportNamespaceSpecifier")) return;
context.report({
node,
messageId: "inline",
data: { kind: node.importKind },
fix(fixer) {
const kindToken = sourceCode.getFirstToken(node, { skip: 1 });
return [kindToken ? fixer.remove(kindToken) : [], node.specifiers.map((specifier) => fixer.insertTextBefore(specifier, `${node.importKind} `))].flat();
}
});
} };
return { ImportDeclaration(node) {
if (node.importKind === "type" || node.importKind === "typeof" || node.specifiers.length === 0 || node.specifiers.length === 1 && (node.specifiers[0].type === "ImportDefaultSpecifier" || node.specifiers[0].type === "ImportNamespaceSpecifier")) return;
const typeSpecifiers = [];
const typeofSpecifiers = [];
const valueSpecifiers = [];
let defaultSpecifier = null;
for (const specifier of node.specifiers) {
if (specifier.type === "ImportDefaultSpecifier") {
defaultSpecifier = specifier;
continue;
}
if (!("importKind" in specifier)) continue;
if (specifier.importKind === "type") typeSpecifiers.push(specifier);
else if (specifier.importKind === "typeof") typeofSpecifiers.push(specifier);
else if (specifier.importKind === "value" || specifier.importKind == null) valueSpecifiers.push(specifier);
}
const typeImport = getImportText(node, sourceCode, typeSpecifiers, "type");
const typeofImport = getImportText(node, sourceCode, typeofSpecifiers, "typeof");
const newImports = `${typeImport}\n${typeofImport}`.trim();
if (typeSpecifiers.length + typeofSpecifiers.length === node.specifiers.length) {
const kind = [typeSpecifiers.length > 0 ? "type" : [], typeofSpecifiers.length > 0 ? "typeof" : []].flat();
context.report({
node,
messageId: "topLevel",
data: { kind: kind.join("/") },
fix(fixer) {
return fixer.replaceText(node, newImports);
}
});
} else for (const specifier of [...typeSpecifiers, ...typeofSpecifiers]) context.report({
node: specifier,
messageId: "topLevel",
data: { kind: specifier.importKind },
fix(fixer) {
const fixes = [];
if (valueSpecifiers.length > 0) {
removeSpecifiers(fixes, fixer, sourceCode, typeSpecifiers);
removeSpecifiers(fixes, fixer, sourceCode, typeofSpecifiers);
const maybeComma = sourceCode.getTokenAfter(valueSpecifiers[valueSpecifiers.length - 1]);
if (isComma(maybeComma)) fixes.push(fixer.remove(maybeComma));
} else if (defaultSpecifier) {
const comma = sourceCode.getTokenAfter(defaultSpecifier, isComma);
const closingBrace = sourceCode.getTokenAfter(node.specifiers[node.specifiers.length - 1], (token) => token.type === "Punctuator" && token.value === "}");
fixes.push(fixer.removeRange([comma.range[0], closingBrace.range[1]]));
}
return [...fixes, fixer.insertTextAfter(node, `\n${newImports}`)];
}
});
} };
}
});
//#endregion
//#region src/rules/default.ts
var default_default = createRule({
name: "default",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Ensure a default export is present, given a default import."
},
schema: [],
messages: { noDefaultExport: "No default export found in imported module \"{{module}}\"." }
},
defaultOptions: [],
create(context) {
function checkDefault(specifierType, node) {
const defaultSpecifier = node.specifiers.find((specifier) => specifier.type === specifierType);
if (!defaultSpecifier) return;
const imports = ExportMap.get(node.source.value, context);
if (imports == null) return;
if (imports.errors.length > 0) imports.reportErrors(context, node);
else if (imports.get("default") === void 0) context.report({
node: defaultSpecifier,
messageId: "noDefaultExport",
data: { module: node.source.value }
});
}
return {
ImportDeclaration: checkDefault.bind(null, "ImportDefaultSpecifier"),
ExportNamedDeclaration: checkDefault.bind(null, "ExportDefaultSpecifier")
};
}
});
//#endregion
//#region src/rules/dynamic-import-chunkname.ts
var dynamic_import_chunkname_default = createRule({
name: "dynamic-import-chunkname",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Enforce a leading comment with the webpackChunkName for dynamic imports."
},
hasSuggestions: true,
schema: [{
type: "object",
properties: {
importFunctions: {
type: "array",
uniqueItems: true,
items: { type: "string" }
},
allowEmpty: { type: "boolean" },
webpackChunknameFormat: { type: "string" }
}
}],
messages: {
leadingComment: "dynamic imports require a leading comment with the webpack chunkname",
blockComment: "dynamic imports require a /* foo */ style comment, not a // foo comment",
paddedSpaces: "dynamic imports require a block comment padded with spaces - /* foo */",
webpackComment: "dynamic imports require a \"webpack\" comment with valid syntax",
chunknameFormat: "dynamic imports require a leading comment in the form /* {{format}} */",
webpackEagerModeNoChunkName: "dynamic imports using eager mode do not need a webpackChunkName",
webpackRemoveEagerMode: "Remove webpackMode",
webpackRemoveChunkName: "Remove webpackChunkName"
}
},
defaultOptions: [],
create(context) {
const { importFunctions = [], allowEmpty = false, webpackChunknameFormat = String.raw`([0-9a-zA-Z-_/.]|\[(request|index)\])+` } = context.options[0] || {};
const paddedCommentRegex = /^ (\S[\S\s]+\S) $/;
const commentStyleRegex = /^( (((webpackChunkName|webpackFetchPriority): .+)|((webpackPrefetch|webpackPreload): (true|false|-?\d+))|(webpackIgnore: (true|false))|((webpackInclude|webpackExclude): \/.+\/)|(webpackMode: ["'](lazy|lazy-once|eager|weak)["'])|(webpackExports: (["']\w+["']|\[(["']\w+["'], *)+(["']\w+["']*)]))),?)+ $/;
const chunkSubstrFormat = `webpackChunkName: ["']${webpackChunknameFormat}["'],?`;
const chunkSubstrRegex = new RegExp(chunkSubstrFormat);
const eagerModeFormat = `webpackMode: ["']eager["'],?`;
const eagerModeRegex = new RegExp(eagerModeFormat);
function run(node, arg) {
const { sourceCode } = context;
const leadingComments = sourceCode.getCommentsBefore(arg);
if ((!leadingComments || leadingComments.length === 0) && !allowEmpty) {
context.report({
node,
messageId: "leadingComment"
});
return;
}
let isChunknamePresent = false;
let isEagerModePresent = false;
for (const comment of leadingComments) {
if (comment.type !== "Block") {
context.report({
node,
messageId: "blockComment"
});
return;
}
if (!paddedCommentRegex.test(comment.value)) {
context.report({
node,
messageId: "paddedSpaces"
});
return;
}
try {
node_vm.default.runInNewContext(`(function() {return {${comment.value}}})()`);
} catch {
context.report({
node,
messageId: "webpackComment"
});
return;
}
if (!commentStyleRegex.test(comment.value)) {
context.report({
node,
messageId: "webpackComment"
});
return;
}
if (eagerModeRegex.test(comment.value)) isEagerModePresent = true;
if (chunkSubstrRegex.test(comment.value)) isChunknamePresent = true;
}
const removeCommentsAndLeadingSpaces = (fixer, comment) => {
const leftToken = sourceCode.getTokenBefore(comment);
const leftComments = sourceCode.getCommentsBefore(comment);
if (leftToken) {
if (leftComments.length > 0) return fixer.removeRange([Math.max(leftToken.range[1], leftComments[leftComments.length - 1].range[1]), comment.range[1]]);
return fixer.removeRange([leftToken.range[1], comment.range[1]]);
}
return fixer.remove(comment);
};
if (isChunknamePresent && isEagerModePresent) context.report({
node,
messageId: "webpackEagerModeNoChunkName",
suggest: [{
messageId: "webpackRemoveChunkName",
fix(fixer) {
for (const comment of leadingComments) if (chunkSubstrRegex.test(comment.value)) {
const replacement = comment.value.replace(chunkSubstrRegex, "").trim().replace(/,$/, "");
return replacement === "" ? removeCommentsAndLeadingSpaces(fixer, comment) : fixer.replaceText(comment, `/* ${replacement} */`);
}
return null;
}
}, {
messageId: "webpackRemoveEagerMode",
fix(fixer) {
for (const comment of leadingComments) if (eagerModeRegex.test(comment.value)) {
const replacement = comment.value.replace(eagerModeRegex, "").trim().replace(/,$/, "");
return replacement === "" ? removeCommentsAndLeadingSpaces(fixer, comment) : fixer.replaceText(comment, `/* ${replacement} */`);
}
return null;
}
}]
});
if (!isChunknamePresent && !allowEmpty && !isEagerModePresent) context.report({
node,
messageId: "chunknameFormat",
data: { format: chunkSubstrFormat }
});
}
return {
ImportExpression(node) {
run(node, node.source);
},
CallExpression(node) {
if (node.callee.type !== "Import" && (!("name" in node.callee) || !importFunctions.includes(node.callee.name))) return;
run(node, node.arguments[0]);
}
};
}
});
//#endregion
//#region src/rules/export.ts
const rootProgram = "root";
const tsTypePrefix = "type:";
/**
* Remove function overloads like:
*
* ```ts
* export function foo(a: number)
* export function foo(a: string)
* export function foo(a: number | string) {
* return a
* }
* ```
*/
function removeTypescriptFunctionOverloads(nodes) {
for (const node of nodes) {
const declType = node.type === __typescript_eslint_types.AST_NODE_TYPES.ExportDefaultDeclaration ? node.declaration.type : node.parent?.type;
if (declType === __typescript_eslint_types.AST_NODE_TYPES.TSDeclareFunction) nodes.delete(node);
}
}
/**
* Detect merging Namespaces with Classes, Functions, or Enums like:
*
* ```ts
* export class Foo {}
* export namespace Foo {}
* ```
*/
function isTypescriptNamespaceMerging(nodes) {
const types$1 = new Set(Array.from(nodes, (node) => `${node.parent.type}`));
const noNamespaceNodes = [...nodes].filter((node) => node.parent.type !== "TSModuleDeclaration");
return types$1.has("TSModuleDeclaration") && (types$1.size === 1 || types$1.size === 2 && (types$1.has("FunctionDeclaration") || types$1.has("TSDeclareFunction")) || types$1.size === 3 && types$1.has("FunctionDeclaration") && types$1.has("TSDeclareFunction") || types$1.size === 2 && (types$1.has("ClassDeclaration") || types$1.has("TSEnumDeclaration")) && noNamespaceNodes.length === 1);
}
/**
* Detect if a typescript namespace node should be reported as multiple export:
*
* ```ts
* export class Foo {}
* export function Foo()
* export namespace Foo {}
* ```
*/
function shouldSkipTypescriptNamespace(node, nodes) {
const types$1 = new Set(Array.from(nodes, (node$1) => `${node$1.parent.type}`));
return !isTypescriptNamespaceMerging(nodes) && node.parent.type === "TSModuleDeclaration" && (types$1.has("TSEnumDeclaration") || types$1.has("ClassDeclaration") || types$1.has("FunctionDeclaration") || types$1.has("TSDeclareFunction"));
}
var export_default = createRule({
name: "export",
meta: {
type: "problem",
docs: {
category: "Helpful warnings",
description: "Forbid any invalid exports, i.e. re-export of the same name."
},
schema: [],
messages: {
noNamed: "No named exports found in module '{{module}}'.",
multiDefault: "Multiple default exports.",
multiNamed: "Multiple exports of name '{{name}}'."
}
},
defaultOptions: [],
create(context) {
const namespace = new Map([[rootProgram, new Map()]]);
function addNamed(name$1, node, parent, isType) {
if (!namespace.has(parent)) namespace.set(parent, new Map());
const named = namespace.get(parent);
const key = isType ? `${tsTypePrefix}${name$1}` : name$1;
let nodes = named.get(key);
if (nodes == null) {
nodes = new Set();
named.set(key, nodes);
}
nodes.add(node);
}
function getParent(node) {
if (node.parent?.type === "TSModuleBlock") return node.parent.parent;
return rootProgram;
}
return {
ExportDefaultDeclaration(node) {
addNamed("default", node, getParent(node));
},
ExportSpecifier(node) {
addNamed(getValue(node.exported), node.exported, getParent(node.parent));
},
ExportNamedDeclaration(node) {
if (node.declaration == null) return;
const parent = getParent(node);
const isTypeVariableDecl = "kind" in node.declaration && node.declaration.kind === "type";
if ("id" in node.declaration && node.declaration.id != null) {
const id = node.declaration.id;
addNamed(id.name, id, parent, ["TSTypeAliasDeclaration", "TSInterfaceDeclaration"].includes(node.declaration.type) || isTypeVariableDecl);
}
if ("declarations" in node.declaration && node.declaration.declarations != null) for (const declaration of node.declaration.declarations) recursivePatternCapture(declaration.id, (v) => {
addNamed(v.name, v, parent, isTypeVariableDecl);
});
},
ExportAllDeclaration(node) {
if (node.source == null) return;
if (node.exported && node.exported.name) return;
const remoteExports = ExportMap.get(node.source.value, context);
if (remoteExports == null) return;
if (remoteExports.errors.length > 0) {
remoteExports.reportErrors(context, node);
return;
}
const parent = getParent(node);
let any = false;
remoteExports.$forEach((_, name$1) => {
if (name$1 !== "default") {
any = true;
addNamed(name$1, node, parent);
}
});
if (!any) context.report({
node: node.source,
messageId: "noNamed",
data: { module: node.source.value }
});
},
"Program:exit"() {
for (const [, named] of namespace) for (const [name$1, nodes] of named) {
if (nodes.size === 0) continue;
removeTypescriptFunctionOverloads(nodes);
if (nodes.size <= 1) continue;
if (isTypescriptNamespaceMerging(nodes)) continue;
for (const node of nodes) {
if (shouldSkipTypescriptNamespace(node, nodes)) continue;
if (name$1 === "default") context.report({
node,
messageId: "multiDefault"
});
else context.report({
node,
messageId: "multiNamed",
data: { name: name$1.replace(tsTypePrefix, "") }
});
}
}
}
};
}
});
//#endregion
//#region src/rules/exports-last.ts
const findLastIndex = (array, predicate) => {
let i = array.length - 1;
while (i >= 0) {
if (predicate(array[i])) return i;
i--;
}
return -1;
};
function isNonExportStatement({ type }) {
return type !== "ExportDefaultDeclaration" && type !== "ExportNamedDeclaration" && type !== "ExportAllDeclaration";
}
var exports_last_default = createRule({
name: "exports-last",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Ensure all exports appear after other statements."
},
schema: [],
messages: { end: "Export statements should appear at the end of the file" }
},
defaultOptions: [],
create(context) {
return { Program({ body }) {
const lastNonExportStatementIndex = findLastIndex(body, isNonExportStatement);
if (lastNonExportStatementIndex !== -1) {
for (const node of body.slice(0, lastNonExportStatementIndex)) if (!isNonExportStatement(node)) context.report({
node,
messageId: "end"
});
}
} };
}
});
//#endregion
//#region src/rules/extensions.ts
const modifierValues = [
"always",
"ignorePackages",
"never"
];
const modifierSchema = {
type: "string",
enum: [...modifierValues]
};
const modifierByFileExtensionSchema = {
type: "object",
patternProperties: { ".*": modifierSchema }
};
const properties = {
type: "object",
properties: {
pattern: modifierByFileExtensionSchema,
ignorePackages: { type: "boolean" },
checkTypeImports: { type: "boolean" },
pathGroupOverrides: {
type: "array",
items: {
type: "object",
properties: {
pattern: { type: "string" },
patternOptions: { type: "object" },
action: {
type: "string",
enum: ["enforce", "ignore"]
}
},
additionalProperties: false,
required: ["pattern", "action"]
}
},
fix: { type: "boolean" }
}
};
function buildProperties(context) {
const result = {
defaultConfig: "never",
pattern: {},
ignorePackages: false,
checkTypeImports: false,
pathGroupOverrides: [],
fix: false
};
for (const obj of context.options) {
if (typeof obj === "string") {
result.defaultConfig = obj;
continue;
}
if (typeof obj !== "object" || !obj) continue;
if (obj.fix != null) result.fix = Boolean(obj.fix);
if ((!("pattern" in obj) || obj.pattern == null) && obj.ignorePackages == null && obj.checkTypeImports == null) {
Object.assign(result.pattern, obj);
continue;
}
if ("pattern" in obj && obj.pattern != null) Object.assign(result.pattern, obj.pattern);
if (typeof obj.ignorePackages === "boolean") result.ignorePackages = obj.ignorePackages;
if (typeof obj.checkTypeImports === "boolean") result.checkTypeImports = obj.checkTypeImports;
if (Array.isArray(obj.pathGroupOverrides)) result.pathGroupOverrides = obj.pathGroupOverrides;
}
if (result.defaultConfig === "ignorePackages") {
result.defaultConfig = "always";
result.ignorePackages = true;
}
return result;
}
function isExternalRootModule(file) {
if (file === "." || file === "..") return false;
const slashCount = file.split("/").length - 1;
return slashCount === 0 || isScoped(file) && slashCount <= 1;
}
function computeOverrideAction(pathGroupOverrides, path$22) {
for (const { pattern: pattern$1, patternOptions, action } of pathGroupOverrides) if ((0, minimatch.minimatch)(path$22, pattern$1, patternOptions || { nocomment: true })) return action;
}
/**
* Replaces the import path in a source string with a new import path.
*
* @param source - The original source string containing the import statement.
* @param importPath - The new import path to replace the existing one.
* @returns The updated source string with the replaced import path.
*/
function replaceImportPath(source, importPath) {
return source.replace(/^(['"])(.+)\1$/, (_, quote) => `${quote}${importPath}${quote}`);
}
var extensions_default = createRule({
name: "extensions",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Ensure consistent use of file extension within the import path."
},
fixable: "code",
hasSuggestions: true,
schema: { anyOf: [
{
type: "array",
items: [modifierSchema],
additionalItems: false
},
{
type: "array",
items: [modifierSchema, properties],
additionalItems: false
},
{
type: "array",
items: [properties],
additionalItems: false
},
{
type: "array",
items: [modifierSchema, modifierByFileExtensionSchema],
additionalItems: false
},
{
type: "array",
items: [modifierByFileExtensionSchema],
additionalItems: false
}
] },
messages: {
missing: "Missing file extension for \"{{importPath}}\"",
missingKnown: "Missing file extension \"{{extension}}\" for \"{{importPath}}\"",
unexpected: "Unexpected use of file extension \"{{extension}}\" for \"{{importPath}}\"",
addMissing: "Add \"{{extension}}\" file extension from \"{{importPath}}\" into \"{{fixedImportPath}}\"",
removeUnexpected: "Remove unexpected \"{{extension}}\" file extension from \"{{importPath}}\" into \"{{fixedImportPath}}\""
}
},
defaultOptions: [],
create(context) {
const props = buildProperties(context);
function getModifier(extension) {
return props.pattern[extension] || props.defaultConfig;
}
function isUseOfExtensionRequired(extension, isPackage) {
return getModifier(extension) === "always" && (!props.ignorePackages || !isPackage);
}
function isUseOfExtensionForbidden(extension) {
return getModifier(extension) === "never";
}
function isResolvableWithoutExtension(file) {
const extension = node_path.default.extname(file);
const fileWithoutExtension = file.slice(0, -extension.length);
const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context);
return resolvedFileWithoutExtension === resolve(file, context);
}
return moduleVisitor((source, node) => {
if (!source || !source.value) return;
const importPathWithQueryString = source.value;
const overrideAction = computeOverrideAction(props.pathGroupOverrides || [], importPathWithQueryString);
if (overrideAction === "ignore") return;
if (!overrideAction && isBuiltIn(importPathWithQueryString, context.settings)) return;
const { pathname: importPath, query, hash } = parsePath(importPathWithQueryString);
if (!overrideAction && isExternalRootModule(importPath)) return;
const resolvedPath = resolve(importPath, context);
const extension = node_path.default.extname(resolvedPath || importPath).slice(1);
const isPackage = isExternalModule(importPath, resolve(importPath, context), context) || isScoped(importPath);
if (!extension || !importPath.endsWith(`.${extension}`)) {
if (!props.checkTypeImports && ("importKind" in node && node.importKind === "type" || "exportKind" in node && node.exportKind === "type")) return;
const extensionRequired = isUseOfExtensionRequired(extension, !overrideAction && isPackage);
const extensionForbidden = isUseOfExtensionForbidden(extension);
if (extensionRequired && !extensionForbidden) {
const fixedImportPath = stringifyPath({
pathname: `${/([\\/]|[\\/]?\.?\.)$/.test(importPath) ? `${importPath.endsWith("/") ? importPath.slice(0, -1) : importPath}/index.${extension}` : `${importPath}.${extension}`}`,
query,
hash
});
const fixOrSuggest = { fix(fixer) {
return fixer.replaceText(source, replaceImportPath(source.raw, fixedImportPath));
} };
context.report({
node: source,
messageId: extension ? "missingKnown" : "missing",
data: {
extension,
importPath: importPathWithQueryString
},
...extension && (props.fix ? fixOrSuggest : { suggest: [{
...fixOrSuggest,
messageId: "addMissing",
data: {
extension,
importPath: importPathWithQueryString,
fixedImportPath
}
}] })
});
}
} else if (extension && isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) {
const fixedPathname = importPath.slice(0, -(extension.length + 1));
const isIndex$1 = fixedPathname.endsWith("/index");
const fixedImportPath = stringifyPath({
pathname: isIndex$1 ? fixedPathname.slice(0, -6) : fixedPathname,
query,
hash
});
const fixOrSuggest = { fix(fixer) {
return fixer.replaceText(source, replaceImportPath(source.raw, fixedImportPath));
} };
const commonSuggestion = {
...fixOrSuggest,
messageId: "removeUnexpected",
data: {
extension,
importPath: importPathWithQueryString,
fixedImportPath
}
};
context.report({
node: source,
messageId: "unexpected",
data: {
extension,
importPath: importPathWithQueryString
},
...props.fix ? fixOrSuggest : { suggest: [commonSuggestion, isIndex$1 && {
...commonSuggestion,
fix(fixer) {
return fixer.replaceText(source, replaceImportPath(source.raw, stringifyPath({
pathname: fixedPathname,
query,
hash
})));
},
data: {
...commonSuggestion.data,
fixedImportPath: stringifyPath({
pathname: fixedPathname,
query,
hash
})
}
}].filter(Boolean) }
});
}
}, { commonjs: true });
}
});
//#endregion
//#region src/rules/first.ts
function getImportValue(node) {
return node.type === "ImportDeclaration" ? node.source.value : "moduleReference" in node && "expression" in node.moduleReference && "value" in node.moduleReference.expression && node.moduleReference.expression.value;
}
function isPossibleDirective(node) {
return node.type === "ExpressionStatement" && node.expression.type === "Literal" && typeof node.expression.value === "string";
}
var first_default = createRule({
name: "first",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Ensure all imports appear before other statements."
},
fixable: "code",
schema: [{
type: "string",
enum: ["absolute-first", "disable-absolute-first"]
}],
messages: {
absolute: "Absolute imports should come before relative imports.",
order: "Import in body of module; reorder to top."
}
},
defaultOptions: [],
create(context) {
return { Program(n) {
const body = n.body;
if (!body?.length) return;
const absoluteFirst = context.options[0] === "absolute-first";
const { sourceCode } = context;
const originSourceCode = sourceCode.getText();
let nonImportCount = 0;
let anyExpressions = false;
let anyRelative = false;
let lastLegalImp = null;
const errorInfos = [];
let shouldSort = true;
let lastSortNodesIndex = 0;
for (const [index, node] of body.entries()) {
if (!anyExpressions && isPossibleDirective(node)) continue;
anyExpressions = true;
if (node.type === "ImportDeclaration" || node.type === "TSImportEqualsDeclaration") {
if (absoluteFirst) {
const importValue = getImportValue(node);
if (typeof importValue === "string" && /^\./.test(importValue)) anyRelative = true;
else if (anyRelative) context.report({
node: node.type === "ImportDeclaration" ? node.source : node.moduleReference,
messageId: "absolute"
});
}
if (nonImportCount > 0) {
/** @see https://eslint.org/docs/next/use/migrate-to-9.0.0#-removed-multiple-context-methods */
for (const variable of sourceCode.getDeclaredVariables(node)) {
if (!shouldSort) break;
for (const reference of variable.references) if (reference.identifier.range[0] < node.range[1]) {
shouldSort = false;
break;
}
}
if (shouldSort) lastSortNodesIndex = errorInfos.length;
errorInfos.push({
node,
range: [body[index - 1].range[1], node.range[1]]
});
} else lastLegalImp = node;
} else nonImportCount++;
}
if (errorInfos.length === 0) return;
for (const [index, { node }] of errorInfos.entries()) {
let fix;
if (index < lastSortNodesIndex) fix = (fixer) => fixer.insertTextAfter(node, "");
else if (index === lastSortNodesIndex) {
const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1);
fix = (fixer) => {
const removeFixers = sortNodes.map(({ range: range$1 }) => fixer.removeRange(range$1));
const range = [0, removeFixers[removeFixers.length - 1].range[1]];
let insertSourceCode = sortNodes.map(({ range: range$1 }) => {
const nodeSourceCode = originSourceCode.slice(...range$1);
if (/\S/.test(nodeSourceCode[0])) return `\n${nodeSourceCode}`;
return nodeSourceCode;
}).join("");
let replaceSourceCode = "";
if (!lastLegalImp) insertSourceCode = insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0];
const insertFixer = lastLegalImp ? fixer.insertTextAfter(lastLegalImp, insertSourceCode) : fixer.insertTextBefore(body[0], insertSourceCode);
const fixers = [insertFixer, ...removeFixers];
for (const [i, computedFixer] of fixers.entries()) replaceSourceCode += originSourceCode.slice(fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0]) + computedFixer.text;
return fixer.replaceTextRange(range, replaceSourceCode);
};
}
context.report({
node,
messageId: "order",
fix
});
}
} };
}
});
//#endregion
//#region src/rules/group-exports.ts
/**
* Returns an array with names of the properties in the accessor chain for
* MemberExpression nodes
*
* Example:
*
* `module.exports = {}` => ['module', 'exports']
*
* `module.exports.property = true` => ['module', 'exports', 'property']
*/
function accessorChain(node) {
const chain = [];
let exp = node;
do {
if ("name" in exp.property) chain.unshift(exp.property.name);
else if ("value" in exp.property) chain.unshift(exp.property.value);
if (exp.object.type === "Identifier") {
chain.unshift(exp.object.name);
break;
}
exp = exp.object;
} while (exp.type === "MemberExpression");
return chain;
}
var group_exports_default = createRule({
name: "group-exports",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Prefer named exports to be grouped together in a single export declaration."
},
schema: [],
messages: {
ExportNamedDeclaration: "Multiple named export declarations; consolidate all named exports into a single export declaration",
AssignmentExpression: "Multiple CommonJS exports; consolidate all exports into a single assignment to `module.exports`"
}
},
defaultOptions: [],
create(context) {
const nodes = {
modules: {
set: new Set(),
sources: {}
},
types: {
set: new Set(),
sources: {}
},
commonjs: { set: new Set() }
};
return {
ExportNamedDeclaration(node) {
const target = node.exportKind === "type" ? nodes.types : nodes.modules;
if (!node.source) target.set.add(node);
else if (Array.isArray(target.sources[node.source.value])) target.sources[node.source.value].push(node);
else target.sources[node.source.value] = [node];
},
AssignmentExpression(node) {
if (node.left.type !== "MemberExpression") return;
const chain = accessorChain(node.left);
if (chain[0] === "module" && chain[1] === "exports" && chain.length <= 3) {
nodes.commonjs.set.add(node);
return;
}
if (chain[0] === "exports" && chain.length === 2) {
nodes.commonjs.set.add(node);
return;
}
},
"Program:exit"() {
if (nodes.modules.set.size > 1) for (const node of nodes.modules.set) context.report({
node,
messageId: node.type
});
for (const node of Object.values(nodes.modules.sources).filter((nodesWithSource) => Array.isArray(nodesWithSource) && nodesWithSource.length > 1).flat()) context.report({
node,
messageId: node.type
});
if (nodes.types.set.size > 1) for (const node of nodes.types.set) context.report({
node,
messageId: node.type
});
for (const node of Object.values(nodes.types.sources).filter((nodesWithSource) => Array.isArray(nodesWithSource) && nodesWithSource.length > 1).flat()) context.report({
node,
messageId: node.type
});
if (nodes.commonjs.set.size > 1) for (const node of nodes.commonjs.set) context.report({
node,
messageId: node.type
});
}
};
}
});
//#endregion
//#region src/rules/imports-first.ts
var imports_first_default = createRule({
...first_default,
name: "imports-first",
meta: {
...first_default.meta,
deprecated: {
message: "Replaced by `import-x/first`.",
url: "https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md#changed-24",
deprecatedSince: "2.0.0",
replacedBy: [{
message: "Replaced by `import-x/first`.",
rule: {
name: "first",
url: docsUrl("first")
}
}]
},
docs: {
category: "Style guide",
description: "Replaced by `import-x/first`."
}
}
});
//#endregion
//#region src/rules/max-dependencies.ts
var max_dependencies_default = createRule({
name: "max-dependencies",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Enforce the maximum number of dependencies a module can have."
},
schema: [{
type: "object",
properties: {
max: { type: "number" },
ignoreTypeImports: { type: "boolean" }
},
additionalProperties: false
}],
messages: { max: "Maximum number of dependencies ({{max}}) exceeded." }
},
defaultOptions: [],
create(context) {
const { ignoreTypeImports } = context.options[0] || {};
const dependencies = new Set();
let lastNode;
return {
"Program:exit"() {
const { max = 10 } = context.options[0] || {};
if (dependencies.size <= max) return;
context.report({
node: lastNode,
messageId: "max",
data: { max }
});
},
...moduleVisitor((source, node) => {
if ("importKind" in node && node.importKind !== "type" || !ignoreTypeImports) dependencies.add(source.value);
lastNode = source;
}, { commonjs: true })
};
}
});
//#endregion
//#region src/rules/named.ts
var named_default = createRule({
name: "named",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Ensure named imports correspond to a named export in the remote file."
},
schema: [{
type: "object",
properties: { commonjs: { type: "boolean" } },
additionalProperties: false
}],
messages: {
notFound: "{{name}} not found in '{{path}}'",
notFoundDeep: "{{name}} not found via {{deepPath}}"
}
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
function checkSpecifiers(key, type, node) {
if (node.source == null || "importKind" in node && (node.importKind === "type" || node.importKind === "typeof") || "exportKind" in node && node.exportKind === "type") return;
if (!node.specifiers.some((im) => im.type === type)) return;
const imports = ExportMap.get(node.source.value, context);
if (imports == null || imports.parseGoal === "ambiguous") return;
if (imports.errors.length > 0) {
imports.reportErrors(context, node);
return;
}
for (const im of node.specifiers) {
if (im.type !== type || "importKind" in im && (im.importKind === "type" || im.importKind === "typeof")) continue;
/** @see im is @see TSESTree.ExportSpecifier or @see TSESTree.ImportSpecifier */
const imNode = im[key];
const name$1 = imNode.name || imNode.value;
const deepLookup = imports.hasDeep(name$1);
if (!deepLookup.found) if (deepLookup.path.length > 1) {
const deepPath = deepLookup.path.map((i) => node_path.default.relative(node_path.default.dirname(context.physicalFilename), i.path)).join(" -> ");
context.report({
node: imNode,
messageId: "notFoundDeep",
data: {
name: name$1,
deepPath
}
});
} else context.report({
node: imNode,
messageId: "notFound",
data: {
name: name$1,
path: node.source.value
}
});
}
}
return {
ImportDeclaration: checkSpecifiers.bind(null, "imported", "ImportSpecifier"),
ExportNamedDeclaration: checkSpecifiers.bind(null, "local", "ExportSpecifier"),
VariableDeclarator(node) {
if (!options.commonjs || node.type !== "VariableDeclarator" || !node.id || node.id.type !== "ObjectPattern" || node.id.properties.length === 0 || !node.init || node.init.type !== "CallExpression") return;
const call = node.init;
const source = call.arguments[0];
const variableImports = node.id.properties;
const variableExports = ExportMap.get(source.value, context);
if (call.callee.type !== "Identifier" || call.callee.name !== "require" || call.arguments.length !== 1 || source.type !== "Literal" || variableExports == null || variableExports.parseGoal === "ambiguous") return;
if (variableExports.errors.length > 0) {
variableExports.reportErrors(context, node);
return;
}
for (const im of variableImports) {
if (im.type !== "Property" || !im.key || im.key.type !== "Identifier") continue;
const deepLookup = variableExports.hasDeep(im.key.name);
if (!deepLookup.found) if (deepLookup.path.length > 1) {
const deepPath = deepLookup.path.map((i) => node_path.default.relative(node_path.default.dirname(context.filename), i.path)).join(" -> ");
context.report({
node: im.key,
messageId: "notFoundDeep",
data: {
name: im.key.name,
deepPath
}
});
} else context.report({
node: im.key,
messageId: "notFound",
data: {
name: im.key.name,
path: source.value
}
});
}
}
};
}
});
//#endregion
//#region src/rules/namespace.ts
function processBodyStatement(context, namespaces, declaration) {
if (declaration.type !== "ImportDeclaration") return;
if (declaration.specifiers.length === 0) return;
const imports = ExportMap.get(declaration.source.value, context);
if (imports == null) return;
if (imports.errors.length > 0) {
imports.reportErrors(context, declaration);
return;
}
for (const specifier of declaration.specifiers) switch (specifier.type) {
case "ImportNamespaceSpecifier": {
if (imports.size === 0) context.report({
node: specifier,
messageId: "noNamesFound",
data: { module: declaration.source.value }
});
namespaces.set(specifier.local.name, imports);
break;
}
case "ImportDefaultSpecifier":
case "ImportSpecifier": {
const meta$1 = imports.get("imported" in specifier ? getValue(specifier.imported) : "default");
if (!meta$1 || !meta$1.namespace) break;
namespaces.set(specifier.local.name, meta$1.namespace);
break;
}
default:
}
}
function makeMessage(last, namepath, node = last) {
const messageId = namepath.length > 1 ? "notFoundInNamespaceDeep" : "notFoundInNamespace";
return {
node,
messageId,
data: {
name: last.name,
namepath: namepath.join(".")
}
};
}
var namespace_default = createRule({
name: "namespace",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Ensure imported namespaces contain dereferenced properties as they are dereferenced."
},
schema: [{
type: "object",
properties: { allowComputed: {
description: "If `false`, will report computed (and thus, un-lintable) references to namespace members.",
type: "boolean",
default: false
} },
additionalProperties: false
}],
messages: {
noNamesFound: "No exported names found in module '{{module}}'.",
computedReference: "Unable to validate computed reference to imported namespace '{{namespace}}'.",
namespaceMember: "Assignment to member of namespace '{{namespace}}'.",
topLevelNames: "Only destructure top-level names.",
notFoundInNamespace: "'{{name}}' not found in imported namespace '{{namepath}}'.",
notFoundInNamespaceDeep: "'{{name}}' not found in deeply imported namespace '{{namepath}}'."
}
},
defaultOptions: [{ allowComputed: false }],
create(context) {
const { allowComputed } = context.options[0] || {};
const namespaces = new Map();
return {
Program({ body }) {
for (const x of body) processBodyStatement(context, namespaces, x);
},
ExportNamespaceSpecifier(namespace) {
const declaration = importDeclaration(context, namespace);
const imports = ExportMap.get(declaration.source.value, context);
if (imports == null) return null;
if (imports.errors.length > 0) {
imports.reportErrors(context, declaration);
return;
}
if (imports.size === 0) context.report({
node: namespace,
messageId: "noNamesFound",
data: { module: declaration.source.value }
});
},
MemberExpression(dereference) {
if (dereference.object.type !== "Identifier") return;
if (!namespaces.has(dereference.object.name)) return;
if (declaredScope(context, dereference, dereference.object.name) !== "module") return;
const parent = dereference.parent;
if (parent?.type === "AssignmentExpression" && parent.left === dereference) context.report({
node: parent,
messageId: "namespaceMember",
data: { namespace: dereference.object.name }
});
let namespace = namespaces.get(dereference.object.name);
const namepath = [dereference.object.name];
let deref = dereference;
while (namespace instanceof ExportMap && deref?.type === "MemberExpression") {
if (deref.computed) {
if (!allowComputed) context.report({
node: deref.property,
messageId: "computedReference",
data: { namespace: "name" in deref.object && deref.object.name }
});
return;
}
if (!namespace.has(deref.property.name)) {
context.report(makeMessage(deref.property, namepath));
break;
}
const exported = namespace.get(deref.property.name);
if (exported == null) return;
namepath.push(deref.property.name);
namespace = exported.namespace;
deref = deref.parent;
}
},
VariableDeclarator(node) {
const { id, init } = node;
if (init == null) return;
if (init.type !== "Identifier") return;
if (!namespaces.has(init.name)) return;
if (declaredScope(context, node, init.name) !== "module") return;
const initName = init.name;
function testKey(pattern$1, namespace, path$22 = [initName]) {
if (!(namespace instanceof ExportMap)) return;
if (pattern$1.type !== "ObjectPattern") return;
for (const property of pattern$1.properties) {
if (property.type === "ExperimentalRestProperty" || property.type === "RestElement" || !property.key) continue;
if (property.key.type !== "Identifier") {
context.report({
node: property,
messageId: "topLevelNames"
});
continue;
}
if (!namespace.has(property.key.name)) {
context.report(makeMessage(property.key, path$22, property));
continue;
}
path$22.push(property.key.name);
const dependencyExportMap = namespace.get(property.key.name);
if (dependencyExportMap != null) testKey(property.value, dependencyExportMap.namespace, path$22);
path$22.pop();
}
}
testKey(id, namespaces.get(init.name));
},
JSXMemberExpression({ object, property }) {
if (!("name" in object) || typeof object.name !== "string" || !namespaces.has(object.name)) return;
const namespace = namespaces.get(object.name);
if (!namespace.has(property.name)) context.report(makeMessage(property, [object.name]));
}
};
}
});
//#endregion
//#region src/rules/newline-after-import.ts
const log$1 = (0, debug.default)("eslint-plugin-import-x:rules:newline-after-import");
function containsNodeOrEqual(outerNode, innerNode) {
return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1];
}
function getScopeBody(scope) {
if (scope.block.type === "SwitchStatement") {
log$1("SwitchStatement scopes not supported");
return [];
}
const body = "body" in scope.block ? scope.block.body : null;
if (body && "type" in body && body.type === "BlockStatement") return body.body;
return Array.isArray(body) ? body : [];
}
function findNodeIndexInScopeBody(body, nodeToFind) {
return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind));
}
function getLineDifference(node, nextNode) {
return nextNode.loc.start.line - node.loc.end.line;
}
function isClassWithDecorator(node) {
return node.type === "ClassDeclaration" && !!node.decorators?.length;
}
function isExportDefaultClass(node) {
return node.type === "ExportDefaultDeclaration" && node.declaration.type === "ClassDeclaration";
}
function isExportNameClass(node) {
return node.type === "ExportNamedDeclaration" && node.declaration?.type === "ClassDeclaration";
}
var newline_after_import_default = createRule({
name: "newline-after-import",
meta: {
type: "layout",
docs: {
category: "Style guide",
description: "Enforce a newline after import statements."
},
fixable: "whitespace",
schema: [{
type: "object",
properties: {
count: {
type: "integer",
minimum: 1
},
exactCount: { type: "boolean" },
considerComments: { type: "boolean" }
},
additionalProperties: false
}],
messages: { newline: "Expected {{count}} empty line{{lineSuffix}} after {{type}} statement not followed by another {{type}}." }
},
defaultOptions: [],
create(context) {
let level = 0;
const requireCalls = [];
const options = {
count: 1,
exactCount: false,
considerComments: false,
...context.options[0]
};
function checkForNewLine(node, nextNode, type) {
if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) {
const classNode = nextNode.declaration;
if (isClassWithDecorator(classNode)) nextNode = classNode.decorators[0];
} else if (isClassWithDecorator(nextNode)) nextNode = nextNode.decorators[0];
const lineDifference = getLineDifference(node, nextNode);
const EXPECTED_LINE_DIFFERENCE = options.count + 1;
if (lineDifference < EXPECTED_LINE_DIFFERENCE || options.exactCount && lineDifference !== EXPECTED_LINE_DIFFERENCE) {
let column = node.loc.start.column;
if (node.loc.start.line !== node.loc.end.line) column = 0;
context.report({
loc: {
line: node.loc.end.line,
column
},
messageId: "newline",
data: {
count: options.count,
lineSuffix: options.count > 1 ? "s" : "",
type
},
fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? void 0 : (fixer) => fixer.insertTextAfter(node, "\n".repeat(EXPECTED_LINE_DIFFERENCE - lineDifference))
});
}
}
function commentAfterImport(node, nextComment, type) {
const lineDifference = getLineDifference(node, nextComment);
const EXPECTED_LINE_DIFFERENCE = options.count + 1;
if (lineDifference < EXPECTED_LINE_DIFFERENCE) {
let column = node.loc.start.column;
if (node.loc.start.line !== node.loc.end.line) column = 0;
context.report({
loc: {
line: node.loc.end.line,
column
},
messageId: "newline",
data: {
count: options.count,
lineSuffix: options.count > 1 ? "s" : "",
type
},
fix: options.exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? void 0 : (fixer) => fixer.insertTextAfter(node, "\n".repeat(EXPECTED_LINE_DIFFERENCE - lineDifference))
});
}
}
function incrementLevel() {
level++;
}
function decrementLevel() {
level--;
}
function checkImport(node) {
const { parent } = node;
if (!parent || !("body" in parent) || !parent.body) return;
const root = parent;
const nodePosition = root.body.indexOf(node);
const nextNode = root.body[nodePosition + 1];
const endLine = node.loc.end.line;
let nextComment;
if (root.comments !== void 0 && options.considerComments) nextComment = root.comments.find((o) => o.loc.start.line >= endLine && o.loc.start.line <= endLine + options.count + 1);
if (node.type === "TSImportEqualsDeclaration" && node.isExport) return;
if (nextComment) commentAfterImport(node, nextComment, "import");
else if (nextNode && nextNode.type !== "ImportDeclaration" && (nextNode.type !== "TSImportEqualsDeclaration" || nextNode.isExport)) checkForNewLine(node, nextNode, "import");
}
return {
ImportDeclaration: checkImport,
TSImportEqualsDeclaration: checkImport,
CallExpression(node) {
if (isStaticRequire(node) && level === 0) requireCalls.push(node);
},
"Program:exit"(node) {
log$1("exit processing for", context.physicalFilename);
const scopeBody = getScopeBody(context.sourceCode.getScope(node));
log$1("got scope:", scopeBody);
for (const [index, node$1] of requireCalls.entries()) {
const nodePosition = findNodeIndexInScopeBody(scopeBody, node$1);
log$1("node position in scope:", nodePosition);
const statementWithRequireCall = scopeBody[nodePosition];
const nextStatement = scopeBody[nodePosition + 1];
const nextRequireCall = requireCalls[index + 1];
if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) continue;
if (nextStatement && (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) {
let nextComment;
if ("comments" in statementWithRequireCall.parent && statementWithRequireCall.parent.comments !== void 0 && options.considerComments) {
const endLine = node$1.loc.end.line;
nextComment = statementWithRequireCall.parent.comments.find((o) => o.loc.start.line >= endLine && o.loc.start.line <= endLine + options.count + 1);
}
if (nextComment && nextComment !== void 0) commentAfterImport(statementWithRequireCall, nextComment, "require");
else checkForNewLine(statementWithRequireCall, nextStatement, "require");
}
}
},
FunctionDeclaration: incrementLevel,
FunctionExpression: incrementLevel,
ArrowFunctionExpression: incrementLevel,
BlockStatement: incrementLevel,
ObjectExpression: incrementLevel,
Decorator: incrementLevel,
"FunctionDeclaration:exit": decrementLevel,
"FunctionExpression:exit": decrementLevel,
"ArrowFunctionExpression:exit": decrementLevel,
"BlockStatement:exit": decrementLevel,
"ObjectExpression:exit": decrementLevel,
"Decorator:exit": decrementLevel
};
}
});
//#endregion
//#region src/rules/no-absolute-path.ts
var no_absolute_path_default = createRule({
name: "no-absolute-path",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid import of modules using absolute paths."
},
fixable: "code",
schema: [makeOptionsSchema()],
messages: { absolute: "Do not import modules using an absolute path" }
},
defaultOptions: [],
create(context) {
const options = {
esmodule: true,
commonjs: true,
...context.options[0]
};
return moduleVisitor((source) => {
if (!isAbsolute(source.value)) return;
context.report({
node: source,
messageId: "absolute",
fix(fixer) {
let relativePath = node_path.default.posix.relative(node_path.default.dirname(context.physicalFilename), source.value);
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
return fixer.replaceText(source, JSON.stringify(relativePath));
}
});
}, options);
}
});
//#endregion
//#region src/rules/no-amd.ts
var no_amd_default = createRule({
name: "no-amd",
meta: {
type: "suggestion",
docs: {
category: "Module systems",
description: "Forbid AMD `require` and `define` calls."
},
schema: [],
messages: { amd: "Expected imports instead of AMD {{type}}()." }
},
defaultOptions: [],
create(context) {
return { CallExpression(node) {
if (context.sourceCode.getScope(node).type !== "module") return;
if (node.callee.type !== "Identifier") return;
if (node.callee.name !== "require" && node.callee.name !== "define") return;
if (node.arguments.length !== 2) return;
const modules = node.arguments[0];
if (modules.type !== "ArrayExpression") return;
context.report({
node,
messageId: "amd",
data: { type: node.callee.name }
});
} };
}
});
//#endregion
//#region src/rules/no-anonymous-default-export.ts
const { hasOwnProperty } = Object.prototype;
const hasOwn = (object, key) => hasOwnProperty.call(object, key);
const defs = {
ArrayExpression: {
option: "allowArray",
description: "If `false`, will report default export of an array",
messageId: "assign",
data: { type: "array" }
},
ArrowFunctionExpression: {
option: "allowArrowFunction",
description: "If `false`, will report default export of an arrow function",
messageId: "assign",
data: { type: "arrow function" }
},
CallExpression: {
option: "allowCallExpression",
description: "If `false`, will report default export of a function call",
messageId: "assign",
data: { type: "call result" },
default: true
},
ClassDeclaration: {
option: "allowAnonymousClass",
description: "If `false`, will report default export of an anonymous class",
messageId: "anonymous",
data: { type: "class" },
forbid: (node) => !("id" in node.declaration) || !node.declaration.id
},
FunctionDeclaration: {
option: "allowAnonymousFunction",
description: "If `false`, will report default export of an anonymous function",
messageId: "anonymous",
data: { type: "function" },
forbid: (node) => !("id" in node.declaration) || !node.declaration.id
},
Literal: {
option: "allowLiteral",
description: "If `false`, will report default export of a literal",
messageId: "assign",
data: { type: "literal" }
},
ObjectExpression: {
option: "allowObject",
description: "If `false`, will report default export of an object expression",
messageId: "assign",
data: { type: "object" }
},
TemplateLiteral: {
option: "allowLiteral",
description: "If `false`, will report default export of a literal",
messageId: "assign",
data: { type: "literal" }
},
NewExpression: {
option: "allowNew",
description: "If `false`, will report default export of a class instantiation",
messageId: "assign",
data: { type: "instance" }
}
};
const schemaProperties = Object.fromEntries(Object.values(defs).map((def) => [def.option, {
description: def.description,
type: "boolean"
}]));
const defaults = Object.fromEntries(Object.values(defs).map((def) => [def.option, hasOwn(def, "default") ? def.default : false]));
var no_anonymous_default_export_default = createRule({
name: "no-anonymous-default-export",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Forbid anonymous values as default exports."
},
schema: [{
type: "object",
properties: schemaProperties,
additionalProperties: false
}],
messages: {
assign: "Assign {{type}} to a variable before exporting as module default",
anonymous: "Unexpected default export of anonymous {{type}}"
}
},
defaultOptions: [],
create(context) {
const options = {
...defaults,
...context.options[0]
};
return { ExportDefaultDeclaration(node) {
const type = node.declaration.type;
if (!(type in defs)) return;
const def = defs[type];
if (!options[def.option] && (!("forbid" in def) || def.forbid(node))) context.report({
node,
messageId: def.messageId,
data: def.data
});
} };
}
});
//#endregion
//#region src/rules/no-commonjs.ts
function normalizeLegacyOptions(options) {
if (options.includes("allow-primitive-modules")) return { allowPrimitiveModules: true };
return options[0] || {};
}
function allowPrimitive(node, options) {
if (!options.allowPrimitiveModules) return false;
if (node.parent.type !== "AssignmentExpression") return false;
return node.parent.right.type !== "ObjectExpression";
}
function validateScope(scope) {
return scope.variableScope.type === "module";
}
function isConditional(node) {
if (node.type === "IfStatement" || node.type === "TryStatement" || node.type === "LogicalExpression" || node.type === "ConditionalExpression") return true;
if (node.parent) return isConditional(node.parent);
return false;
}
function isLiteralString(node) {
return node.type === "Literal" && typeof node.value === "string" || node.type === "TemplateLiteral" && node.expressions.length === 0;
}
var no_commonjs_default = createRule({
name: "no-commonjs",
meta: {
type: "suggestion",
docs: {
category: "Module systems",
description: "Forbid CommonJS `require` calls and `module.exports` or `exports.*`."
},
schema: { anyOf: [{
type: "array",
items: [{
type: "string",
enum: ["allow-primitive-modules"]
}],
additionalItems: false
}, {
type: "array",
items: [{
type: "object",
properties: {
allowPrimitiveModules: { type: "boolean" },
allowRequire: { type: "boolean" },
allowConditionalRequire: { type: "boolean" }
},
additionalProperties: false
}],
additionalItems: false
}] },
messages: {
export: "Expected \"export\" or \"export default\"",
import: "Expected \"import\" instead of \"require()\""
}
},
defaultOptions: [],
create(context) {
const options = normalizeLegacyOptions(context.options);
return {
MemberExpression(node) {
if ("name" in node.object && node.object.name === "module" && "name" in node.property && node.property.name === "exports") {
if (allowPrimitive(node, options)) return;
context.report({
node,
messageId: "export"
});
}
if ("name" in node.object && node.object.name === "exports") {
const isInScope = context.sourceCode.getScope(node).variables.some((variable) => variable.name === "exports");
if (!isInScope) context.report({
node,
messageId: "export"
});
}
},
CallExpression(call) {
if (!validateScope(context.sourceCode.getScope(call))) return;
if (call.callee.type !== "Identifier") return;
if (call.callee.name !== "require") return;
if (call.arguments.length !== 1) return;
if (!isLiteralString(call.arguments[0])) return;
if (options.allowRequire) return;
if (options.allowConditionalRequire !== false && isConditional(call.parent)) return;
context.report({
node: call.callee,
messageId: "import"
});
}
};
}
});
//#endregion
//#region src/rules/no-cycle.ts
const traversed = new Set();
var no_cycle_default = createRule({
name: "no-cycle",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid a module from importing a module with a dependency path back to itself."
},
schema: [makeOptionsSchema({
maxDepth: { anyOf: [{
description: "maximum dependency depth to traverse",
type: "integer",
minimum: 1
}, {
enum: ["∞"],
type: "string"
}] },
ignoreExternal: {
description: "ignore external modules",
type: "boolean",
default: false
},
allowUnsafeDynamicCyclicDependency: {
description: "Allow cyclic dependency if there is at least one dynamic import in the chain",
type: "boolean",
default: false
}
})],
messages: {
cycle: "Dependency cycle detected",
cycleSource: "Dependency cycle via \"{{source}}\""
}
},
defaultOptions: [],
create(context) {
const filename = context.physicalFilename;
if (filename === "<text>") return {};
const options = context.options[0] || {};
const maxDepth = typeof options.maxDepth === "number" ? options.maxDepth : Number.POSITIVE_INFINITY;
const ignoreModule = options.ignoreExternal ? (name$1) => isExternalModule(name$1, resolve(name$1, context), context) : () => false;
return {
...moduleVisitor(function checkSourceValue(sourceNode, importer) {
if (ignoreModule(sourceNode.value)) return;
if (options.allowUnsafeDynamicCyclicDependency && (importer.type === "ImportExpression" || importer.type === "CallExpression" && "name" in importer.callee && importer.callee.name !== "require")) return;
if (importer.type === "ImportDeclaration" && (importer.importKind === "type" || importer.specifiers.every((s) => "importKind" in s && s.importKind === "type"))) return;
const imported = ExportMap.get(sourceNode.value, context);
if (imported == null) return;
if (imported.path === filename) return;
const untraversed = [{
mget: () => imported,
route: []
}];
function detectCycle({ mget, route }) {
const m = mget();
if (m == null) return;
if (traversed.has(m.path)) return;
traversed.add(m.path);
for (const [path$22, { getter, declarations }] of m.imports) {
if (traversed.has(path$22)) continue;
const toTraverse = [...declarations].filter(({ source, isOnlyImportingTypes }) => !ignoreModule(source.value) && !isOnlyImportingTypes);
/**
* If cyclic dependency is allowed via dynamic import, skip checking
* if any module is imported dynamically
*/
if (options.allowUnsafeDynamicCyclicDependency && toTraverse.some((d) => d.dynamic)) return;
/**
* Only report as a cycle if there are any import declarations that
* are considered by the rule. For example:
*
* A.ts:
*
* `import { foo } from './b'` // should not be reported as a cycle
*
* B.ts: `import type { Bar } from './a'`
*/
if (path$22 === filename && toTraverse.length > 0) return true;
if (route.length + 1 < maxDepth) for (const { source } of toTraverse) untraversed.push({
mget: getter,
route: [...route, source]
});
}
}
while (untraversed.length > 0) {
const next = untraversed.shift();
if (detectCycle(next)) {
if (next.route.length > 0) context.report({
node: importer,
messageId: "cycleSource",
data: { source: routeString(next.route) }
});
else context.report({
node: importer,
messageId: "cycle"
});
return;
}
}
}, context.options[0]),
"Program:exit"() {
traversed.clear();
}
};
}
});
function routeString(route) {
return route.map((s) => `${s.value}:${s.loc.start.line}`).join("=>");
}
//#endregion
//#region src/rules/no-default-export.ts
var no_default_export_default = createRule({
name: "no-default-export",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Forbid default exports."
},
schema: [],
messages: {
preferNamed: "Prefer named exports.",
noAliasDefault: "Do not alias `{{local}}` as `default`. Just export `{{local}}` itself instead."
}
},
defaultOptions: [],
create(context) {
if (sourceType(context) !== "module") return {};
const { sourceCode } = context;
return {
ExportDefaultDeclaration(node) {
const { loc } = sourceCode.getFirstTokens(node)[1] || {};
context.report({
node,
messageId: "preferNamed",
loc
});
},
ExportNamedDeclaration(node) {
for (const specifier of node.specifiers.filter((specifier$1) => getValue(specifier$1.exported) === "default")) {
const { loc } = sourceCode.getFirstTokens(node)[1] || {};
if (specifier.type === "ExportDefaultSpecifier") context.report({
node,
messageId: "preferNamed",
loc
});
else if (specifier.type === "ExportSpecifier") context.report({
node,
messageId: "noAliasDefault",
data: { local: getValue(specifier.local) },
loc
});
}
}
};
}
});
//#endregion
//#region src/rules/no-deprecated.ts
function message(deprecation) {
if (deprecation.description) return {
messageId: "deprecatedDesc",
data: { description: deprecation.description }
};
return { messageId: "deprecated" };
}
function getDeprecation(metadata) {
if (!metadata || !metadata.doc) return;
return metadata.doc.tags.find((t) => t.tag === "deprecated");
}
var no_deprecated_default = createRule({
name: "no-deprecated",
meta: {
type: "suggestion",
docs: {
category: "Helpful warnings",
description: "Forbid imported names marked with `@deprecated` documentation tag."
},
schema: [],
messages: {
deprecatedDesc: "Deprecated: {{description}}",
deprecated: "Deprecated: consider to find an alternative."
}
},
defaultOptions: [],
create(context) {
const deprecated = new Map();
const namespaces = new Map();
return {
Program({ body }) {
for (const node of body) {
if (node.type !== "ImportDeclaration") continue;
if (node.source == null) continue;
const imports = ExportMap.get(node.source.value, context);
if (imports == null) continue;
const moduleDeprecation = imports.doc?.tags.find((t) => t.tag === "deprecated");
if (moduleDeprecation) context.report({
node,
...message(moduleDeprecation)
});
if (imports.errors.length > 0) {
imports.reportErrors(context, node);
continue;
}
for (const im of node.specifiers) {
let imported;
let local;
switch (im.type) {
case "ImportNamespaceSpecifier": {
if (imports.size === 0) continue;
namespaces.set(im.local.name, imports);
continue;
}
case "ImportDefaultSpecifier": {
imported = "default";
local = im.local.name;
break;
}
case "ImportSpecifier": {
imported = getValue(im.imported);
local = im.local.name;
break;
}
default: continue;
}
const exported = imports.get(imported);
if (exported == null) continue;
if (exported.namespace) namespaces.set(local, exported.namespace);
const deprecation = getDeprecation(imports.get(imported));
if (!deprecation) continue;
context.report({
node: im,
...message(deprecation)
});
deprecated.set(local, deprecation);
}
}
},
Identifier(node) {
if (!node.parent || node.parent.type === "MemberExpression" && node.parent.property === node) return;
if (node.parent.type.slice(0, 6) === "Import") return;
if (!deprecated.has(node.name)) return;
if (declaredScope(context, node, node.name) !== "module") return;
context.report({
node,
...message(deprecated.get(node.name))
});
},
MemberExpression(dereference) {
if (dereference.object.type !== "Identifier") return;
if (!namespaces.has(dereference.object.name)) return;
if (declaredScope(context, dereference, dereference.object.name) !== "module") return;
let namespace = namespaces.get(dereference.object.name);
const namepath = [dereference.object.name];
let node = dereference;
while (namespace instanceof ExportMap && node?.type === "MemberExpression") {
if (node.computed) return;
const metadata = namespace.get(node.property.name);
if (!metadata) break;
const deprecation = getDeprecation(metadata);
if (deprecation) context.report({
node: node.property,
...message(deprecation)
});
namepath.push(node.property.name);
namespace = metadata.namespace;
node = node.parent;
}
}
};
}
});
//#endregion
//#region src/rules/no-duplicates.ts
const isTypeScriptVersionSupportPreferInline = lazy(() => {
let typescriptPkg;
try {
typescriptPkg = cjsRequire("typescript/package.json");
} catch {}
return !typescriptPkg || !semver.satisfies(typescriptPkg.version, ">= 4.5");
});
function checkImports(imported, context) {
imported.forEach((nodes, module$1) => {
if (nodes.length <= 1) return;
for (let i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
context.report({
node: node.source,
messageId: "duplicate",
data: { module: module$1 },
fix: i === 0 ? getFix(nodes, context.sourceCode, context) : null
});
}
});
}
function getFix(nodes, sourceCode, context) {
const first = nodes[0];
if (hasProblematicComments(first, sourceCode) || hasNamespace(first)) return null;
const defaultImportNames = new Set(nodes.flatMap((x) => getDefaultImportName(x) || []));
if (defaultImportNames.size > 1) return null;
const rest = nodes.slice(1);
const restWithoutCommentsAndNamespaces = rest.filter((node) => !hasProblematicComments(node, sourceCode) && !hasNamespace(node));
const restWithoutCommentsAndNamespacesHasSpecifiers = restWithoutCommentsAndNamespaces.map(hasSpecifiers);
const specifiers = restWithoutCommentsAndNamespaces.reduce((acc, node, nodeIndex) => {
const tokens = sourceCode.getTokens(node);
const openBrace = tokens.find((token) => isPunctuator(token, "{"));
const closeBrace = tokens.find((token) => isPunctuator(token, "}"));
if (openBrace == null || closeBrace == null) return acc;
acc.push({
importNode: node,
identifiers: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]).split(","),
isEmpty: !restWithoutCommentsAndNamespacesHasSpecifiers[nodeIndex]
});
return acc;
}, []);
const unnecessaryImports = restWithoutCommentsAndNamespaces.filter((node, nodeIndex) => !restWithoutCommentsAndNamespacesHasSpecifiers[nodeIndex] && !specifiers.some((specifier) => specifier.importNode === node));
const shouldAddSpecifiers = specifiers.length > 0;
const shouldRemoveUnnecessary = unnecessaryImports.length > 0;
const shouldAddDefault = lazy(() => getDefaultImportName(first) == null && defaultImportNames.size === 1);
if (!shouldAddSpecifiers && !shouldRemoveUnnecessary && !shouldAddDefault()) return null;
const preferInline = context.options[0] && context.options[0]["prefer-inline"];
return (fixer) => {
const tokens = sourceCode.getTokens(first);
const openBrace = tokens.find((token) => isPunctuator(token, "{"));
const closeBrace = tokens.find((token) => isPunctuator(token, "}"));
const firstToken = sourceCode.getFirstToken(first);
const [defaultImportName] = defaultImportNames;
const firstHasTrailingComma = closeBrace != null && isPunctuator(sourceCode.getTokenBefore(closeBrace), ",");
const firstIsEmpty = !hasSpecifiers(first);
const firstExistingIdentifiers = firstIsEmpty ? new Set() : new Set(sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]).split(",").map((x) => x.split(" as ")[0].trim()));
const [specifiersText] = specifiers.reduce(([result, needsComma, existingIdentifiers], specifier) => {
const isTypeSpecifier = "importNode" in specifier && specifier.importNode.importKind === "type";
if (preferInline && isTypeScriptVersionSupportPreferInline()) throw new Error("Your version of TypeScript does not support inline type imports.");
const [specifierText, updatedExistingIdentifiers] = specifier.identifiers.reduce(([text, set], cur) => {
const trimmed = cur.trim();
if (trimmed.length === 0 || existingIdentifiers.has(trimmed)) return [text, set];
const curWithType = preferInline && isTypeSpecifier ? cur.replace(/^(\s*)/, "$1type ") : cur;
return [text.length > 0 ? `${text},${curWithType}` : curWithType, set.add(trimmed)];
}, ["", existingIdentifiers]);
return [
needsComma && !specifier.isEmpty && specifierText.length > 0 ? `${result},${specifierText}` : `${result}${specifierText}`,
specifier.isEmpty ? needsComma : true,
updatedExistingIdentifiers
];
}, [
"",
!firstHasTrailingComma && !firstIsEmpty,
firstExistingIdentifiers
]);
const fixes = [];
if (shouldAddSpecifiers && preferInline && first.importKind === "type") {
const typeIdentifierToken = tokens.find((token) => token.type === "Identifier" && token.value === "type");
if (typeIdentifierToken) fixes.push(fixer.removeRange([typeIdentifierToken.range[0], typeIdentifierToken.range[1] + 1]));
for (const identifier of tokens.filter((token) => firstExistingIdentifiers.has(token.value))) fixes.push(fixer.replaceTextRange([identifier.range[0], identifier.range[1]], `type ${identifier.value}`));
}
if (openBrace == null && shouldAddSpecifiers && shouldAddDefault()) fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`));
else if (openBrace == null && !shouldAddSpecifiers && shouldAddDefault()) fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`));
else if (openBrace != null && closeBrace != null && shouldAddDefault()) {
fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`));
if (shouldAddSpecifiers) fixes.push(fixer.insertTextBefore(closeBrace, specifiersText));
} else if (openBrace == null && shouldAddSpecifiers && !shouldAddDefault()) if (first.specifiers.length === 0) fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`));
else fixes.push(fixer.insertTextAfter(first.specifiers[0], `, {${specifiersText}}`));
else if (openBrace != null && closeBrace != null && !shouldAddDefault()) {
const tokenBefore = sourceCode.getTokenBefore(closeBrace);
fixes.push(fixer.insertTextAfter(tokenBefore, specifiersText));
}
for (const specifier of specifiers) {
const importNode = specifier.importNode;
fixes.push(fixer.remove(importNode));
const charAfterImportRange = [importNode.range[1], importNode.range[1] + 1];
const charAfterImport = sourceCode.text.slice(charAfterImportRange[0], charAfterImportRange[1]);
if (charAfterImport === "\n") fixes.push(fixer.removeRange(charAfterImportRange));
}
for (const node of unnecessaryImports) {
fixes.push(fixer.remove(node));
const charAfterImportRange = [node.range[1], node.range[1] + 1];
const charAfterImport = sourceCode.text.slice(charAfterImportRange[0], charAfterImportRange[1]);
if (charAfterImport === "\n") fixes.push(fixer.removeRange(charAfterImportRange));
}
return fixes;
};
}
function isPunctuator(node, value) {
return node.type === "Punctuator" && node.value === value;
}
function getDefaultImportName(node) {
const defaultSpecifier = node.specifiers.find((specifier) => specifier.type === "ImportDefaultSpecifier");
return defaultSpecifier?.local.name;
}
function hasNamespace(node) {
return node.specifiers.some((specifier) => specifier.type === "ImportNamespaceSpecifier");
}
function hasSpecifiers(node) {
return node.specifiers.some((specifier) => specifier.type === "ImportSpecifier");
}
function hasProblematicComments(node, sourceCode) {
return hasCommentBefore(node, sourceCode) || hasCommentAfter(node, sourceCode) || hasCommentInsideNonSpecifiers(node, sourceCode);
}
function hasCommentBefore(node, sourceCode) {
return sourceCode.getCommentsBefore(node).some((comment) => comment.loc.end.line >= node.loc.start.line - 1);
}
function hasCommentAfter(node, sourceCode) {
return sourceCode.getCommentsAfter(node).some((comment) => comment.loc.start.line === node.loc.end.line);
}
function hasCommentInsideNonSpecifiers(node, sourceCode) {
const tokens = sourceCode.getTokens(node);
const openBraceIndex = tokens.findIndex((token) => isPunctuator(token, "{"));
const closeBraceIndex = tokens.findIndex((token) => isPunctuator(token, "}"));
const someTokens = openBraceIndex !== -1 && closeBraceIndex !== -1 ? [...tokens.slice(1, openBraceIndex + 1), ...tokens.slice(closeBraceIndex + 1)] : tokens.slice(1);
return someTokens.some((token) => sourceCode.getCommentsBefore(token).length > 0);
}
var no_duplicates_default = createRule({
name: "no-duplicates",
meta: {
type: "problem",
docs: {
category: "Style guide",
description: "Forbid repeated import of the same module in multiple places."
},
fixable: "code",
schema: [{
type: "object",
properties: {
considerQueryString: { type: "boolean" },
"prefer-inline": { type: "boolean" }
},
additionalProperties: false
}],
messages: { duplicate: "'{{module}}' imported multiple times." }
},
defaultOptions: [],
create(context) {
const preferInline = context.options[0]?.["prefer-inline"];
const considerQueryStringOption = context.options[0]?.considerQueryString;
const defaultResolver = (sourcePath) => resolve(sourcePath, context) || sourcePath;
const resolver = considerQueryStringOption ? (sourcePath) => {
const parts = sourcePath.match(/^([^?]*)\?(.*)$/);
if (!parts) return defaultResolver(sourcePath);
return `${defaultResolver(parts[1])}?${parts[2]}`;
} : defaultResolver;
const moduleMaps = new Map();
function getImportMap(n) {
const parent = n.parent;
let map;
if (moduleMaps.has(parent)) map = moduleMaps.get(parent);
else {
map = {
imported: new Map(),
nsImported: new Map(),
defaultTypesImported: new Map(),
namespaceTypesImported: new Map(),
namedTypesImported: new Map()
};
moduleMaps.set(parent, map);
}
if (n.importKind === "type") {
if (n.specifiers.length > 0 && n.specifiers[0].type === "ImportDefaultSpecifier") return map.defaultTypesImported;
if (n.specifiers.length > 0 && n.specifiers[0].type === "ImportNamespaceSpecifier") return map.namespaceTypesImported;
if (!preferInline) return map.namedTypesImported;
}
if (!preferInline && n.specifiers.some((spec) => "importKind" in spec && spec.importKind === "type")) return map.namedTypesImported;
return hasNamespace(n) ? map.nsImported : map.imported;
}
return {
ImportDeclaration(n) {
const resolvedPath = resolver(n.source.value);
const importMap = getImportMap(n);
if (importMap.has(resolvedPath)) importMap.get(resolvedPath).push(n);
else importMap.set(resolvedPath, [n]);
},
"Program:exit"() {
for (const map of moduleMaps.values()) {
checkImports(map.imported, context);
checkImports(map.nsImported, context);
checkImports(map.defaultTypesImported, context);
checkImports(map.namedTypesImported, context);
}
}
};
}
});
//#endregion
//#region src/rules/no-dynamic-require.ts
function isRequire(node) {
return node.callee?.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0;
}
function isDynamicImport(node) {
return node?.callee && node.callee.type === "Import";
}
function isStaticValue(node) {
return node.type === "Literal" || node.type === "TemplateLiteral" && node.expressions.length === 0;
}
var no_dynamic_require_default = createRule({
name: "no-dynamic-require",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid `require()` calls with expressions."
},
schema: [{
type: "object",
properties: { esmodule: { type: "boolean" } },
additionalProperties: false
}],
messages: {
import: "Calls to import() should use string literals",
require: "Calls to require() should use string literals"
}
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
return {
CallExpression(node) {
if (!node.arguments[0] || isStaticValue(node.arguments[0])) return;
if (isRequire(node)) return context.report({
node,
messageId: "require"
});
if (options.esmodule && isDynamicImport(node)) return context.report({
node,
messageId: "import"
});
},
ImportExpression(node) {
if (!options.esmodule || isStaticValue(node.source)) return;
return context.report({
node,
messageId: "import"
});
}
};
}
});
//#endregion
//#region src/rules/no-empty-named-blocks.ts
function getEmptyBlockRange(tokens, index) {
const token = tokens[index];
const nextToken = tokens[index + 1];
const prevToken = tokens[index - 1];
let start = token.range[0];
const end = nextToken.range[1];
if (prevToken.value === "," || prevToken.value === "type" || prevToken.value === "typeof") start = prevToken.range[0];
return [start, end];
}
var no_empty_named_blocks_default = createRule({
name: "no-empty-named-blocks",
meta: {
type: "suggestion",
docs: {
category: "Helpful warnings",
description: "Forbid empty named import blocks."
},
fixable: "code",
hasSuggestions: true,
schema: [],
messages: {
emptyNamed: "Unexpected empty named import block",
unused: "Remove unused import",
emptyImport: "Remove empty import block"
}
},
defaultOptions: [],
create(context) {
const importsWithoutNameds = [];
return {
ImportDeclaration(node) {
if (!node.specifiers.some((x) => x.type === "ImportSpecifier")) importsWithoutNameds.push(node);
},
"Program:exit"(program) {
const importsTokens = importsWithoutNameds.map((node) => [node, program.tokens.filter((x) => x.range[0] >= node.range[0] && x.range[1] <= node.range[1])]);
const pTokens = program.tokens || [];
for (const [node, tokens] of importsTokens) for (const token of tokens) {
const idx = pTokens.indexOf(token);
const nextToken = pTokens[idx + 1];
if (nextToken && token.value === "{" && nextToken.value === "}") {
const hasOtherIdentifiers = tokens.some((token$1) => token$1.type === "Identifier" && token$1.value !== "from" && token$1.value !== "type" && token$1.value !== "typeof");
if (hasOtherIdentifiers) context.report({
node,
messageId: "emptyNamed",
fix(fixer) {
return fixer.removeRange(getEmptyBlockRange(pTokens, idx));
}
});
else context.report({
node,
messageId: "emptyNamed",
suggest: [{
messageId: "unused",
fix(fixer) {
return fixer.remove(node);
}
}, {
messageId: "emptyImport",
fix(fixer) {
const { sourceCode } = context;
const fromToken = pTokens.find((t) => t.value === "from");
const importToken = pTokens.find((t) => t.value === "import");
const hasSpaceAfterFrom = sourceCode.isSpaceBetween(fromToken, sourceCode.getTokenAfter(fromToken));
const hasSpaceAfterImport = sourceCode.isSpaceBetween(importToken, sourceCode.getTokenAfter(fromToken));
const [start] = getEmptyBlockRange(pTokens, idx);
const [, end] = fromToken.range;
const range = [start, hasSpaceAfterFrom ? end + 1 : end];
return fixer.replaceTextRange(range, hasSpaceAfterImport ? "" : " ");
}
}]
});
}
}
}
};
}
});
//#endregion
//#region src/rules/no-extraneous-dependencies.ts
const depFieldCache = new Map();
function hasKeys(obj = {}) {
return Object.keys(obj).length > 0;
}
function arrayOrKeys(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : Object.keys(arrayOrObject);
}
function readJSON(jsonPath, throwException) {
try {
return JSON.parse(node_fs.default.readFileSync(jsonPath, "utf8"));
} catch (error) {
if (throwException) throw error;
}
}
function extractDepFields(pkg) {
return {
dependencies: pkg.dependencies || {},
devDependencies: pkg.devDependencies || {},
optionalDependencies: pkg.optionalDependencies || {},
peerDependencies: pkg.peerDependencies || {},
bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || [])
};
}
function getPackageDepFields(packageJsonPath, throwAtRead) {
if (!depFieldCache.has(packageJsonPath)) {
const packageJson = readJSON(packageJsonPath, throwAtRead);
if (packageJson) {
const depFields = extractDepFields(packageJson);
depFieldCache.set(packageJsonPath, depFields);
}
}
return depFieldCache.get(packageJsonPath);
}
function getDependencies(context, packageDir) {
let paths = [];
try {
let packageContent = {
dependencies: {},
devDependencies: {},
optionalDependencies: {},
peerDependencies: {},
bundledDependencies: []
};
if (packageDir && packageDir.length > 0) paths = Array.isArray(packageDir) ? packageDir.map((dir) => node_path.default.resolve(dir)) : [node_path.default.resolve(packageDir)];
if (paths.length > 0) for (const dir of paths) {
const packageJsonPath = node_path.default.resolve(dir, "package.json");
const packageContent_ = getPackageDepFields(packageJsonPath, paths.length === 1);
if (packageContent_) for (const depsKey of Object.keys(packageContent)) {
const key = depsKey;
Object.assign(packageContent[key], packageContent_[key]);
}
}
else {
const packageJsonPath = pkgUp({ cwd: context.physicalFilename });
const packageContent_ = getPackageDepFields(packageJsonPath, false);
if (packageContent_) packageContent = packageContent_;
}
if (![
packageContent.dependencies,
packageContent.devDependencies,
packageContent.optionalDependencies,
packageContent.peerDependencies,
packageContent.bundledDependencies
].some(hasKeys)) return;
return packageContent;
} catch (error_) {
const error = error_;
if (paths.length > 0 && error.code === "ENOENT") context.report({
messageId: "pkgNotFound",
loc: {
line: 0,
column: 0
}
});
if (error.name === "JSONError" || error instanceof SyntaxError) context.report({
messageId: "pkgUnparsable",
data: { error: error.message },
loc: {
line: 0,
column: 0
}
});
}
}
function getModuleOriginalName(name$1) {
const [first, second] = name$1.split("/");
return first.startsWith("@") ? `${first}/${second}` : first;
}
function checkDependencyDeclaration(deps, packageName, declarationStatus) {
const newDeclarationStatus = declarationStatus || {
isInDeps: false,
isInDevDeps: false,
isInOptDeps: false,
isInPeerDeps: false,
isInBundledDeps: false
};
const packageHierarchy = [];
const packageNameParts = packageName ? packageName.split("/") : [];
for (const [index, namePart] of packageNameParts.entries()) if (!namePart.startsWith("@")) {
const ancestor = packageNameParts.slice(0, index + 1).join("/");
packageHierarchy.push(ancestor);
}
return packageHierarchy.reduce((result, ancestorName) => ({
isInDeps: result.isInDeps || deps.dependencies[ancestorName] !== void 0,
isInDevDeps: result.isInDevDeps || deps.devDependencies[ancestorName] !== void 0,
isInOptDeps: result.isInOptDeps || deps.optionalDependencies[ancestorName] !== void 0,
isInPeerDeps: result.isInPeerDeps || deps.peerDependencies[ancestorName] !== void 0,
isInBundledDeps: result.isInBundledDeps || deps.bundledDependencies.includes(ancestorName)
}), newDeclarationStatus);
}
function reportIfMissing(context, deps, depsOptions, node, name$1, whitelist) {
if (!depsOptions.verifyTypeImports && ("importKind" in node && (node.importKind === "type" || node.importKind === "typeof") || "exportKind" in node && node.exportKind === "type" || "specifiers" in node && Array.isArray(node.specifiers) && node.specifiers.length > 0 && node.specifiers.every((specifier) => "importKind" in specifier && (specifier.importKind === "type" || specifier.importKind === "typeof")))) return;
const typeOfImport = importType(name$1, context);
if (typeOfImport !== "external" && (typeOfImport !== "internal" || !depsOptions.verifyInternalDeps)) return;
const resolved = resolve(name$1, context);
if (!resolved) return;
const importPackageName = getModuleOriginalName(name$1);
let declarationStatus = checkDependencyDeclaration(deps, importPackageName);
if (declarationStatus.isInDeps || depsOptions.allowDevDeps && declarationStatus.isInDevDeps || depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps || depsOptions.allowOptDeps && declarationStatus.isInOptDeps || depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps) return;
const realPackageName = getFilePackageName(resolved);
if (realPackageName && realPackageName !== importPackageName) {
declarationStatus = checkDependencyDeclaration(deps, realPackageName, declarationStatus);
if (declarationStatus.isInDeps || depsOptions.allowDevDeps && declarationStatus.isInDevDeps || depsOptions.allowPeerDeps && declarationStatus.isInPeerDeps || depsOptions.allowOptDeps && declarationStatus.isInOptDeps || depsOptions.allowBundledDeps && declarationStatus.isInBundledDeps) return;
}
const packageName = realPackageName || importPackageName;
if (whitelist?.has(packageName)) return;
if (declarationStatus.isInDevDeps && !depsOptions.allowDevDeps) {
context.report({
node,
messageId: "devDep",
data: { packageName }
});
return;
}
if (declarationStatus.isInOptDeps && !depsOptions.allowOptDeps) {
context.report({
node,
messageId: "optDep",
data: { packageName }
});
return;
}
context.report({
node,
messageId: "missing",
data: { packageName }
});
}
function testConfig(config, context) {
if (typeof config === "boolean" || config === void 0) return config;
const filename = context.physicalFilename;
return config.some((c) => (0, minimatch.minimatch)(filename, c) || (0, minimatch.minimatch)(filename, node_path.default.resolve(context.cwd, c), { windowsPathsNoEscape: true }) || (0, minimatch.minimatch)(filename, node_path.default.resolve(c), { windowsPathsNoEscape: true }));
}
var no_extraneous_dependencies_default = createRule({
name: "no-extraneous-dependencies",
meta: {
type: "problem",
docs: {
category: "Helpful warnings",
description: "Forbid the use of extraneous packages."
},
schema: [{
type: "object",
properties: {
devDependencies: { type: ["boolean", "array"] },
optionalDependencies: { type: ["boolean", "array"] },
peerDependencies: { type: ["boolean", "array"] },
bundledDependencies: { type: ["boolean", "array"] },
packageDir: { type: ["string", "array"] },
includeInternal: { type: ["boolean"] },
includeTypes: { type: ["boolean"] },
whitelist: { type: ["array"] }
},
additionalProperties: false
}],
messages: {
pkgNotFound: "The package.json file could not be found.",
pkgUnparsable: "The package.json file could not be parsed: {{error}}",
devDep: "'{{packageName}}' should be listed in the project's dependencies, not devDependencies.",
optDep: "'{{packageName}}' should be listed in the project's dependencies, not optionalDependencies.",
missing: `'{{packageName}}' should be listed in the project's dependencies. Run '${getNpmInstallCommand("{{packageName}}")}' to add it`
}
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
const deps = getDependencies(context, options.packageDir) || extractDepFields({});
const depsOptions = {
allowDevDeps: testConfig(options.devDependencies, context) !== false,
allowOptDeps: testConfig(options.optionalDependencies, context) !== false,
allowPeerDeps: testConfig(options.peerDependencies, context) !== false,
allowBundledDeps: testConfig(options.bundledDependencies, context) !== false,
verifyInternalDeps: !!options.includeInternal,
verifyTypeImports: !!options.includeTypes
};
return {
...moduleVisitor((source, node) => {
reportIfMissing(context, deps, depsOptions, node, source.value, options.whitelist ? new Set(options.whitelist) : void 0);
}, { commonjs: true }),
"Program:exit"() {
depFieldCache.clear();
}
};
}
});
//#endregion
//#region src/rules/no-import-module-exports.ts
function getEntryPoint(context) {
const pkgPath = pkgUp({ cwd: context.physicalFilename });
try {
return cjsRequire.resolve(node_path.default.dirname(pkgPath));
} catch {
return null;
}
}
function findScope(context, identifier) {
const { scopeManager } = context.sourceCode;
return scopeManager?.scopes.slice().reverse().find((scope) => scope.variables.some((variable) => variable.identifiers.some((node) => node.name === identifier)));
}
function findDefinition(objectScope, identifier) {
const variable = objectScope.variables.find((variable$1) => variable$1.name === identifier);
return variable.defs.find((def) => "name" in def.name && def.name.name === identifier);
}
var no_import_module_exports_default = createRule({
name: "no-import-module-exports",
meta: {
type: "problem",
docs: {
category: "Module systems",
description: "Forbid import statements with CommonJS module.exports.",
recommended: true
},
fixable: "code",
schema: [{
type: "object",
properties: { exceptions: { type: "array" } },
additionalProperties: false
}],
messages: { notAllowed: "Cannot use import declarations in modules that export using CommonJS (module.exports = 'foo' or exports.bar = 'hi')" }
},
defaultOptions: [],
create(context) {
const importDeclarations = [];
const entryPoint = getEntryPoint(context);
const options = context.options[0] || {};
let alreadyReported = false;
return {
ImportDeclaration(node) {
importDeclarations.push(node);
},
MemberExpression(node) {
if (alreadyReported) return;
const filename = context.physicalFilename;
const isEntryPoint = entryPoint === filename;
const isIdentifier = node.object.type === "Identifier";
if (!("name" in node.object)) return;
const hasKeywords = /^(module|exports)$/.test(node.object.name);
const objectScope = hasKeywords ? findScope(context, node.object.name) : void 0;
const variableDefinition = objectScope && findDefinition(objectScope, node.object.name);
const isImportBinding = variableDefinition?.type === "ImportBinding";
const hasCJSExportReference = hasKeywords && (!objectScope || objectScope.type === "module");
if (isIdentifier && hasCJSExportReference && !isEntryPoint && !options.exceptions?.some((glob) => (0, minimatch.minimatch)(filename, glob, { nocomment: true })) && !isImportBinding) {
for (const importDeclaration$1 of importDeclarations) context.report({
node: importDeclaration$1,
messageId: "notAllowed"
});
alreadyReported = true;
}
}
};
}
});
//#endregion
//#region src/rules/no-internal-modules.ts
function normalizeSep(somePath) {
return somePath.split("\\").join("/");
}
function toSteps(somePath) {
return normalizeSep(somePath).split("/").filter((step) => step && step !== ".").reduce((acc, step) => {
if (step === "..") return acc.slice(0, -1);
return [...acc, step];
}, []);
}
const potentialViolationTypes$1 = new Set([
"parent",
"index",
"sibling",
"external",
"internal"
]);
var no_internal_modules_default = createRule({
name: "no-internal-modules",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid importing the submodules of other modules."
},
schema: [{ anyOf: [{
type: "object",
properties: { allow: {
type: "array",
items: { type: "string" }
} },
additionalProperties: false
}, {
type: "object",
properties: { forbid: {
type: "array",
items: { type: "string" }
} },
additionalProperties: false
}] }],
messages: { noAllowed: `Reaching to "{{importPath}}" is not allowed.` }
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
const allowRegexps = (options.allow || []).map((p) => (0, minimatch.makeRe)(p)).filter(Boolean);
const forbidRegexps = (options.forbid || []).map((p) => (0, minimatch.makeRe)(p)).filter(Boolean);
function reachingAllowed(importPath) {
return allowRegexps.some((re) => re.test(importPath));
}
function reachingForbidden(importPath) {
return forbidRegexps.some((re) => re.test(importPath));
}
function isAllowViolation(importPath) {
const steps = toSteps(importPath);
const nonScopeSteps = steps.filter((step) => step.indexOf("@") !== 0);
if (nonScopeSteps.length <= 1) return false;
const justSteps = steps.join("/");
if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) return false;
const resolved = resolve(importPath, context);
if (!resolved || reachingAllowed(normalizeSep(resolved))) return false;
return true;
}
function isForbidViolation(importPath) {
const steps = toSteps(importPath);
const justSteps = steps.join("/");
if (reachingForbidden(justSteps) || reachingForbidden(`/${justSteps}`)) return true;
const resolved = resolve(importPath, context);
if (resolved && reachingForbidden(normalizeSep(resolved))) return true;
return false;
}
const isReachViolation = options.forbid ? isForbidViolation : isAllowViolation;
return moduleVisitor((source) => {
const importPath = source.value;
if (potentialViolationTypes$1.has(importType(importPath, context)) && isReachViolation(importPath)) context.report({
node: source,
messageId: "noAllowed",
data: { importPath }
});
}, { commonjs: true });
}
});
//#endregion
//#region src/rules/no-mutable-exports.ts
var no_mutable_exports_default = createRule({
name: "no-mutable-exports",
meta: {
type: "suggestion",
docs: {
category: "Helpful warnings",
description: "Forbid the use of mutable exports with `var` or `let`."
},
schema: [],
messages: { noMutable: "Exporting mutable '{{kind}}' binding, use 'const' instead." }
},
defaultOptions: [],
create(context) {
function checkDeclaration(node) {
if ("kind" in node && (node.kind === "var" || node.kind === "let")) context.report({
node,
messageId: "noMutable",
data: { kind: node.kind }
});
}
function checkDeclarationsInScope({ variables }, name$1) {
for (const variable of variables) if (variable.name === name$1) {
for (const def of variable.defs) if (def.type === "Variable" && def.parent) checkDeclaration(def.parent);
}
}
return {
ExportDefaultDeclaration(node) {
const scope = context.sourceCode.getScope(node);
if ("name" in node.declaration) checkDeclarationsInScope(scope, node.declaration.name);
},
ExportNamedDeclaration(node) {
const scope = context.sourceCode.getScope(node);
if (node.declaration) checkDeclaration(node.declaration);
else if (!node.source) for (const specifier of node.specifiers) checkDeclarationsInScope(scope, specifier.local.name);
}
};
}
});
//#endregion
//#region src/rules/no-named-as-default-member.ts
var no_named_as_default_member_default = createRule({
name: "no-named-as-default-member",
meta: {
type: "suggestion",
docs: {
category: "Helpful warnings",
description: "Forbid use of exported name as property of default export."
},
schema: [],
messages: { member: "Caution: `{{objectName}}` also has a named export `{{propName}}`. Check if you meant to write `import {{{propName}}} from '{{sourcePath}}'` instead." }
},
defaultOptions: [],
create(context) {
const fileImports = new Map();
const allPropertyLookups = new Map();
function storePropertyLookup(objectName, propName, node) {
const lookups = allPropertyLookups.get(objectName) || [];
lookups.push({
node,
propName
});
allPropertyLookups.set(objectName, lookups);
}
return {
ImportDefaultSpecifier(node) {
const declaration = importDeclaration(context, node);
const exportMap = ExportMap.get(declaration.source.value, context);
if (exportMap == null) return;
if (exportMap.errors.length > 0) {
exportMap.reportErrors(context, declaration);
return;
}
fileImports.set(node.local.name, {
exportMap,
sourcePath: declaration.source.value
});
},
MemberExpression(node) {
if ("name" in node.object && "name" in node.property) storePropertyLookup(node.object.name, node.property.name, node);
},
VariableDeclarator(node) {
const isDestructure = node.id.type === "ObjectPattern" && node.init?.type === "Identifier";
if (!isDestructure || !node.init || !("name" in node.init) || !("properties" in node.id)) return;
const objectName = node.init.name;
for (const prop of node.id.properties) {
if (!("key" in prop) || !("name" in prop.key)) continue;
storePropertyLookup(objectName, prop.key.name, prop.key);
}
},
"Program:exit"() {
for (const [objectName, lookups] of allPropertyLookups.entries()) {
const fileImport = fileImports.get(objectName);
if (fileImport == null) continue;
for (const { propName, node } of lookups) {
if (propName === "default") continue;
if (!fileImport.exportMap.namespace.has(propName)) continue;
context.report({
node,
messageId: "member",
data: {
objectName,
propName,
sourcePath: fileImport.sourcePath
}
});
}
}
}
};
}
});
//#endregion
//#region src/rules/no-named-as-default.ts
var no_named_as_default_default = createRule({
name: "no-named-as-default",
meta: {
type: "problem",
docs: {
category: "Helpful warnings",
description: "Forbid use of exported name as identifier of default export."
},
schema: [],
messages: { default: "Using exported name '{{name}}' as identifier for default export." }
},
defaultOptions: [],
create(context) {
function createCheckDefault(nameKey) {
return function checkDefault(defaultSpecifier) {
const nameValue = defaultSpecifier[nameKey].name;
if (nameValue === "default") return;
const declaration = importDeclaration(context, defaultSpecifier);
const exportMapOfImported = ExportMap.get(declaration.source.value, context);
if (exportMapOfImported == null) return;
if (exportMapOfImported.errors.length > 0) {
exportMapOfImported.reportErrors(context, declaration);
return;
}
if (!exportMapOfImported.hasDefault) return;
if (!exportMapOfImported.has(nameValue)) return;
if (exportMapOfImported.exports.has("default") && exportMapOfImported.exports.has(nameValue)) context.report({
node: defaultSpecifier,
messageId: "default",
data: { name: nameValue }
});
};
}
return {
ImportDefaultSpecifier: createCheckDefault("local"),
ExportDefaultSpecifier: createCheckDefault("exported")
};
}
});
//#endregion
//#region src/rules/no-named-default.ts
var no_named_default_default = createRule({
name: "no-named-default",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Forbid named default exports."
},
schema: [],
messages: { default: `Use default import syntax to import '{{importName}}'.` }
},
defaultOptions: [],
create(context) {
return { ImportDeclaration(node) {
for (const im of node.specifiers) {
if ("importKind" in im && (im.importKind === "type" || im.importKind === "typeof")) continue;
if (im.type === "ImportSpecifier" && getValue(im.imported) === "default") context.report({
node: im.local,
messageId: "default",
data: { importName: im.local.name }
});
}
} };
}
});
//#endregion
//#region src/rules/no-named-export.ts
var no_named_export_default = createRule({
name: "no-named-export",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Forbid named exports."
},
schema: [],
messages: { noAllowed: "Named exports are not allowed." }
},
defaultOptions: [],
create(context) {
if (sourceType(context) !== "module") return {};
return {
ExportAllDeclaration(node) {
context.report({
node,
messageId: "noAllowed"
});
},
ExportNamedDeclaration(node) {
if (node.specifiers.length === 0) return context.report({
node,
messageId: "noAllowed"
});
const someNamed = node.specifiers.some((specifier) => getValue(specifier.exported) !== "default");
if (someNamed) context.report({
node,
messageId: "noAllowed"
});
}
};
}
});
//#endregion
//#region src/rules/no-namespace.ts
var no_namespace_default = createRule({
name: "no-namespace",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Forbid namespace (a.k.a. \"wildcard\" `*`) imports."
},
fixable: "code",
schema: [{
type: "object",
properties: { ignore: {
type: "array",
items: { type: "string" },
uniqueItems: true
} }
}],
messages: { noNamespace: "Unexpected namespace import." }
},
defaultOptions: [],
create(context) {
const firstOption = context.options[0] || {};
const ignoreGlobs = firstOption.ignore;
return { ImportNamespaceSpecifier(node) {
if (ignoreGlobs?.find((glob) => (0, minimatch.minimatch)(node.parent.source.value, glob, {
matchBase: true,
nocomment: true
}))) return;
const scopeVariables = context.sourceCode.getScope(node).variables;
const namespaceVariable = scopeVariables.find((variable) => variable.defs[0].node === node);
const namespaceReferences = namespaceVariable.references;
const namespaceIdentifiers = namespaceReferences.map((reference) => reference.identifier);
const canFix = namespaceIdentifiers.length > 0 && !usesNamespaceAsObject(namespaceIdentifiers);
context.report({
node,
messageId: `noNamespace`,
fix: canFix ? (fixer) => {
const scopeManager = context.sourceCode.scopeManager;
const fixes = [];
const importNameConflicts = {};
for (const identifier of namespaceIdentifiers) {
const parent = identifier.parent;
if (parent && parent.type === "MemberExpression") {
const importName = getMemberPropertyName(parent);
const localConflicts = getVariableNamesInScope(scopeManager, parent);
if (importNameConflicts[importName]) for (const c of localConflicts) importNameConflicts[importName].add(c);
else importNameConflicts[importName] = localConflicts;
}
}
const importNames = Object.keys(importNameConflicts);
const importLocalNames = generateLocalNames(importNames, importNameConflicts, namespaceVariable.name);
const namedImportSpecifiers = importNames.map((importName) => importName === importLocalNames[importName] ? importName : `${importName} as ${importLocalNames[importName]}`);
fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(", ")} }`));
for (const identifier of namespaceIdentifiers) {
const parent = identifier.parent;
if (parent && parent.type === "MemberExpression") {
const importName = getMemberPropertyName(parent);
fixes.push(fixer.replaceText(parent, importLocalNames[importName]));
}
}
return fixes;
} : null
});
} };
}
});
function usesNamespaceAsObject(namespaceIdentifiers) {
return !namespaceIdentifiers.every((identifier) => {
const parent = identifier.parent;
return parent && parent.type === "MemberExpression" && (parent.property.type === "Identifier" || parent.property.type === "Literal");
});
}
function getMemberPropertyName(memberExpression) {
return memberExpression.property.type === "Identifier" ? memberExpression.property.name : memberExpression.property.value;
}
function getVariableNamesInScope(scopeManager, node) {
let currentNode = node;
let scope = scopeManager.acquire(currentNode);
while (scope == null) {
currentNode = currentNode.parent;
scope = scopeManager.acquire(currentNode, true);
}
return new Set([...scope.variables, ...scope.upper.variables].map((variable) => variable.name));
}
function generateLocalNames(names, nameConflicts, namespaceName) {
const localNames = {};
for (const name$1 of names) {
let localName;
if (!nameConflicts[name$1].has(name$1)) localName = name$1;
else if (nameConflicts[name$1].has(`${namespaceName}_${name$1}`)) {
for (let i = 1; i < Number.POSITIVE_INFINITY; i++) if (!nameConflicts[name$1].has(`${namespaceName}_${name$1}_${i}`)) {
localName = `${namespaceName}_${name$1}_${i}`;
break;
}
} else localName = `${namespaceName}_${name$1}`;
localNames[name$1] = localName;
}
return localNames;
}
//#endregion
//#region src/rules/no-nodejs-modules.ts
var no_nodejs_modules_default = createRule({
name: "no-nodejs-modules",
meta: {
type: "suggestion",
docs: {
category: "Module systems",
description: "Forbid Node.js builtin modules."
},
schema: [{
type: "object",
properties: { allow: {
type: "array",
uniqueItems: true,
items: { type: "string" }
} },
additionalProperties: false
}],
messages: { builtin: "Do not import Node.js builtin module \"{{moduleName}}\"" }
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
const allowed = options.allow || [];
return moduleVisitor((source, node) => {
const moduleName = source.value;
if (!allowed.includes(moduleName) && importType(moduleName, context) === "builtin") context.report({
node,
messageId: "builtin",
data: { moduleName }
});
}, { commonjs: true });
}
});
//#endregion
//#region src/rules/no-relative-packages.ts
function toPosixPath(filePath) {
return filePath.replaceAll("\\", "/");
}
function findNamedPackage(filePath) {
const found = readPkgUp({ cwd: filePath });
if (found.pkg && !found.pkg.name) return findNamedPackage(node_path.default.resolve(found.path, "../.."));
return found;
}
const potentialViolationTypes = new Set([
"parent",
"index",
"sibling"
]);
function checkImportForRelativePackage(context, importPath, node) {
if (!potentialViolationTypes.has(importType(importPath, context))) return;
const resolvedImport = resolve(importPath, context);
const resolvedContext = context.physicalFilename;
if (!resolvedImport || !resolvedContext) return;
const importPkg = findNamedPackage(resolvedImport);
const contextPkg = findNamedPackage(resolvedContext);
if (importPkg.pkg && contextPkg.pkg && importPkg.pkg.name !== contextPkg.pkg.name) {
const importBaseName = node_path.default.basename(importPath);
const importRoot = node_path.default.dirname(importPkg.path);
const properPath = node_path.default.relative(importRoot, resolvedImport);
const properImport = node_path.default.join(importPkg.pkg.name, node_path.default.dirname(properPath), importBaseName === node_path.default.basename(importRoot) ? "" : importBaseName);
context.report({
node,
messageId: "noAllowed",
data: {
properImport,
importPath
},
fix: (fixer) => fixer.replaceText(node, JSON.stringify(toPosixPath(properImport)))
});
}
}
var no_relative_packages_default = createRule({
name: "no-relative-packages",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid importing packages through relative paths."
},
fixable: "code",
schema: [makeOptionsSchema()],
messages: { noAllowed: "Relative import from another package is not allowed. Use `{{properImport}}` instead of `{{importPath}}`" }
},
defaultOptions: [],
create(context) {
return moduleVisitor((source) => checkImportForRelativePackage(context, source.value, source), context.options[0]);
}
});
//#endregion
//#region src/rules/no-relative-parent-imports.ts
var no_relative_parent_imports_default = createRule({
name: "no-relative-parent-imports",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid importing modules from parent directories."
},
schema: [makeOptionsSchema()],
messages: { noAllowed: "Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move `{{filename}}` to same directory as `{{depPath}}` or consider making `{{depPath}}` a package." }
},
defaultOptions: [],
create(context) {
const filename = context.physicalFilename;
if (filename === "<text>") return {};
return moduleVisitor((sourceNode) => {
const depPath = sourceNode.value;
if (importType(depPath, context) === "external") return;
const absDepPath = resolve(depPath, context);
if (!absDepPath) return;
const relDepPath = node_path.default.relative(node_path.default.dirname(filename), absDepPath);
if (importType(relDepPath, context) === "parent") context.report({
node: sourceNode,
messageId: "noAllowed",
data: {
filename: node_path.default.basename(filename),
depPath
}
});
}, context.options[0]);
}
});
//#endregion
//#region src/rules/no-rename-default.ts
var no_rename_default_default = createRule({
name: "no-rename-default",
meta: {
type: "suggestion",
docs: {
category: "Helpful warnings",
description: "Forbid importing a default export by a different name."
},
schema: [{
type: "object",
properties: {
commonjs: {
default: false,
type: "boolean"
},
preventRenamingBindings: {
default: true,
type: "boolean"
}
},
additionalProperties: false
}],
messages: { renameDefault: "Caution: `{{importBasename}}` has a default export `{{defaultExportName}}`. This {{requiresOrImports}} `{{defaultExportName}}` as `{{importName}}`. Check if you meant to write `{{suggestion}}` instead." }
},
defaultOptions: [],
create(context) {
const { commonjs = false, preventRenamingBindings = true } = context.options[0] || {};
function getDefaultExportName(targetNode) {
if (targetNode == null) return;
switch (targetNode.type) {
case "AssignmentExpression": {
if (!preventRenamingBindings) return;
if (targetNode.left.type !== "Identifier") return;
return targetNode.left.name;
}
case "CallExpression": {
const [argumentNode] = targetNode.arguments;
return getDefaultExportName(argumentNode);
}
case "ClassDeclaration": {
if (targetNode.id && typeof targetNode.id.name === "string") return targetNode.id.name;
return;
}
case "ExportSpecifier": return getValue(targetNode.local);
case "FunctionDeclaration": return targetNode.id?.name;
case "Identifier": {
if (!preventRenamingBindings) return;
return targetNode.name;
}
default:
}
}
function getExportMap(source) {
if (!source) return;
const exportMap = ExportMap.get(source.value, context);
if (exportMap == null) return;
if (exportMap.errors.length > 0) {
exportMap.reportErrors(context, { source });
return;
}
return exportMap;
}
function handleImport(node) {
const exportMap = getExportMap(node.parent.source);
if (exportMap == null) return;
const defaultExportNode = getDefaultExportNode(exportMap);
if (defaultExportNode == null) return;
const defaultExportName = getDefaultExportName(defaultExportNode);
if (defaultExportName === void 0) return;
const importTarget = node.parent.source?.value;
const importBasename = node_path.default.basename(exportMap.path);
if (node.type === "ImportDefaultSpecifier") {
const importName = node.local.name;
if (importName === defaultExportName) return;
context.report({
node,
messageId: "renameDefault",
data: {
importBasename,
defaultExportName,
importName,
requiresOrImports: "imports",
suggestion: `import ${defaultExportName} from '${importTarget}'`
}
});
return;
}
if (node.type !== "ImportSpecifier") return;
if (getValue(node.imported) !== "default") return;
const actualImportedName = node.local.name;
if (actualImportedName === defaultExportName) return;
context.report({
node,
messageId: "renameDefault",
data: {
importBasename,
defaultExportName,
importName: actualImportedName,
requiresOrImports: "imports",
suggestion: `import { default as ${defaultExportName} } from '${importTarget}'`
}
});
}
function handleRequire(node) {
if (!commonjs || node.type !== "VariableDeclarator" || !node.id || !(node.id.type === "Identifier" || node.id.type === "ObjectPattern") || !node.init || node.init.type !== "CallExpression") return;
let defaultDestructure;
if (node.id.type === "ObjectPattern") {
defaultDestructure = findDefaultDestructure(node.id.properties);
if (defaultDestructure === void 0) return;
}
const call = node.init;
const [source] = call.arguments;
if (call.callee.type !== "Identifier" || call.callee.name !== "require" || call.arguments.length !== 1 || source.type !== "Literal" || typeof source.value !== "string") return;
const exportMap = getExportMap(source);
if (exportMap == null) return;
const defaultExportNode = getDefaultExportNode(exportMap);
if (defaultExportNode == null) return;
const defaultExportName = getDefaultExportName(defaultExportNode);
const requireTarget = source.value;
const requireBasename = node_path.default.basename(exportMap.path);
let requireName;
if (node.id.type === "Identifier") requireName = node.id.name;
else if (defaultDestructure?.value?.type === "Identifier") requireName = defaultDestructure.value.name;
else requireName = "";
if (defaultExportName === void 0) return;
if (requireName === defaultExportName) return;
if (node.id.type === "Identifier") {
context.report({
node,
messageId: "renameDefault",
data: {
importBasename: requireBasename,
defaultExportName,
importName: requireName,
requiresOrImports: "requires",
suggestion: `const ${defaultExportName} = require('${requireTarget}')`
}
});
return;
}
context.report({
node,
messageId: "renameDefault",
data: {
importBasename: requireBasename,
defaultExportName,
importName: requireName,
requiresOrImports: "requires",
suggestion: `const { default: ${defaultExportName} } = require('${requireTarget}')`
}
});
}
return {
ImportDefaultSpecifier: handleImport,
ImportSpecifier: handleImport,
VariableDeclarator: handleRequire
};
}
});
function findDefaultDestructure(properties$1) {
const found = properties$1.find((property) => {
if ("key" in property && "name" in property.key && property.key.name === "default") return property;
});
return found;
}
function getDefaultExportNode(exportMap) {
const defaultExportNode = exportMap.exports.get("default");
if (defaultExportNode == null) return;
switch (defaultExportNode.type) {
case "ExportDefaultDeclaration": return defaultExportNode.declaration;
case "ExportNamedDeclaration": return defaultExportNode.specifiers.find((specifier) => getValue(specifier.exported) === "default");
default: return;
}
}
//#endregion
//#region src/rules/no-restricted-paths.ts
const containsPath = (filepath, target) => {
const relative$1 = node_path.default.relative(target, filepath);
return relative$1 === "" || !relative$1.startsWith("..");
};
function isMatchingTargetPath(filename, targetPath) {
if ((0, is_glob.default)(targetPath)) {
const mm = new minimatch.Minimatch(targetPath, { windowsPathsNoEscape: true });
return mm.match(filename);
}
return containsPath(filename, targetPath);
}
function areBothGlobPatternAndAbsolutePath(areGlobPatterns) {
return areGlobPatterns.some(Boolean) && areGlobPatterns.some((isGlob$1) => !isGlob$1);
}
var no_restricted_paths_default = createRule({
name: "no-restricted-paths",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Enforce which files can be imported in a given folder."
},
schema: [{
type: "object",
properties: {
zones: {
type: "array",
minItems: 1,
items: {
type: "object",
properties: {
target: { anyOf: [{ type: "string" }, {
type: "array",
items: { type: "string" },
uniqueItems: true,
minItems: 1
}] },
from: { anyOf: [{ type: "string" }, {
type: "array",
items: { type: "string" },
uniqueItems: true,
minItems: 1
}] },
except: {
type: "array",
items: { type: "string" },
uniqueItems: true
},
message: { type: "string" }
},
additionalProperties: false
}
},
basePath: { type: "string" }
},
additionalProperties: false
}],
messages: {
path: "Restricted path exceptions must be descendants of the configured `from` path for that zone.",
mixedGlob: "Restricted path `from` must contain either only glob patterns or none",
glob: "Restricted path exceptions must be glob patterns when `from` contains glob patterns",
zone: "Unexpected path \"{{importPath}}\" imported in restricted zone.{{extra}}"
}
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
const restrictedPaths = options.zones || [];
const basePath = options.basePath || process.cwd();
const filename = context.physicalFilename;
const matchingZones = restrictedPaths.filter((zone) => [zone.target].flat().map((target) => node_path.default.resolve(basePath, target)).some((targetPath) => isMatchingTargetPath(filename, targetPath)));
function isValidExceptionPath(absoluteFromPath, absoluteExceptionPath) {
const relativeExceptionPath = node_path.default.relative(absoluteFromPath, absoluteExceptionPath);
return importType(relativeExceptionPath, context) !== "parent";
}
function reportInvalidExceptionPath(node) {
context.report({
node,
messageId: "path"
});
}
function reportInvalidExceptionMixedGlobAndNonGlob(node) {
context.report({
node,
messageId: "mixedGlob"
});
}
function reportInvalidExceptionGlob(node) {
context.report({
node,
messageId: "glob"
});
}
function computeMixedGlobAndAbsolutePathValidator() {
return {
isPathRestricted: () => true,
hasValidExceptions: false,
reportInvalidException: reportInvalidExceptionMixedGlobAndNonGlob
};
}
function computeGlobPatternPathValidator(absoluteFrom, zoneExcept) {
let isPathException;
const mm = new minimatch.Minimatch(absoluteFrom, { windowsPathsNoEscape: true });
const isPathRestricted = (absoluteImportPath) => mm.match(absoluteImportPath);
const hasValidExceptions = zoneExcept.every((it) => (0, is_glob.default)(it));
if (hasValidExceptions) {
const exceptionsMm = zoneExcept.map((except) => new minimatch.Minimatch(except, { windowsPathsNoEscape: true }));
isPathException = (absoluteImportPath) => exceptionsMm.some((mm$1) => mm$1.match(absoluteImportPath));
}
const reportInvalidException = reportInvalidExceptionGlob;
return {
isPathRestricted,
hasValidExceptions,
isPathException,
reportInvalidException
};
}
function computeAbsolutePathValidator(absoluteFrom, zoneExcept) {
let isPathException;
const isPathRestricted = (absoluteImportPath) => containsPath(absoluteImportPath, absoluteFrom);
const absoluteExceptionPaths = zoneExcept.map((exceptionPath) => node_path.default.resolve(absoluteFrom, exceptionPath));
const hasValidExceptions = absoluteExceptionPaths.every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath));
if (hasValidExceptions) isPathException = (absoluteImportPath) => absoluteExceptionPaths.some((absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath));
const reportInvalidException = reportInvalidExceptionPath;
return {
isPathRestricted,
hasValidExceptions,
isPathException,
reportInvalidException
};
}
function reportInvalidExceptions(validators$1, node) {
for (const validator of validators$1) validator.reportInvalidException(node);
}
function reportImportsInRestrictedZone(validators$1, node, importPath, customMessage) {
for (const _ of validators$1) context.report({
node,
messageId: "zone",
data: {
importPath,
extra: customMessage ? ` ${customMessage}` : ""
}
});
}
const makePathValidators = (zoneFrom, zoneExcept = []) => {
const allZoneFrom = [zoneFrom].flat();
const areGlobPatterns = allZoneFrom.map((it) => (0, is_glob.default)(it));
if (areBothGlobPatternAndAbsolutePath(areGlobPatterns)) return [computeMixedGlobAndAbsolutePathValidator()];
const isGlobPattern = areGlobPatterns.every(Boolean);
return allZoneFrom.map((singleZoneFrom) => {
const absoluteFrom = node_path.default.resolve(basePath, singleZoneFrom);
if (isGlobPattern) return computeGlobPatternPathValidator(absoluteFrom, zoneExcept);
return computeAbsolutePathValidator(absoluteFrom, zoneExcept);
});
};
const validators = [];
return moduleVisitor((source) => {
const importPath = source.value;
const absoluteImportPath = resolve(importPath, context);
if (!absoluteImportPath) return;
for (const [index, zone] of matchingZones.entries()) {
if (!validators[index]) validators[index] = makePathValidators(zone.from, zone.except);
const applicableValidatorsForImportPath = validators[index].filter((validator) => validator.isPathRestricted(absoluteImportPath));
const validatorsWithInvalidExceptions = applicableValidatorsForImportPath.filter((validator) => !validator.hasValidExceptions);
reportInvalidExceptions(validatorsWithInvalidExceptions, source);
const applicableValidatorsForImportPathExcludingExceptions = applicableValidatorsForImportPath.filter((validator) => validator.hasValidExceptions && !validator.isPathException(absoluteImportPath));
reportImportsInRestrictedZone(applicableValidatorsForImportPathExcludingExceptions, source, importPath, zone.message);
}
}, { commonjs: true });
}
});
//#endregion
//#region src/rules/no-self-import.ts
function isImportingSelf(context, node, requireName) {
const filename = context.physicalFilename;
if (filename !== "<text>" && filename === resolve(requireName, context)) context.report({
node,
messageId: "self"
});
}
var no_self_import_default = createRule({
name: "no-self-import",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Forbid a module from importing itself.",
recommended: true
},
schema: [],
messages: { self: "Module imports itself." }
},
defaultOptions: [],
create(context) {
return moduleVisitor((source, node) => {
isImportingSelf(context, node, source.value);
}, { commonjs: true });
}
});
//#endregion
//#region src/rules/no-unassigned-import.ts
function testIsAllow(globs, context, source) {
if (!Array.isArray(globs)) return false;
const filename = context.physicalFilename;
const filePath = source[0] !== "." && source[0] !== "/" ? source : node_path.default.resolve(filename, "..", source);
return globs.some((glob) => (0, minimatch.minimatch)(filePath, glob, { nocomment: true }) || (0, minimatch.minimatch)(filePath, node_path.default.resolve(context.cwd, glob), {
nocomment: true,
windowsPathsNoEscape: true
}) || (0, minimatch.minimatch)(filePath, node_path.default.resolve(glob), {
nocomment: true,
windowsPathsNoEscape: true
}));
}
var no_unassigned_import_default = createRule({
name: "no-unassigned-import",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Forbid unassigned imports."
},
schema: [{
type: "object",
properties: {
devDependencies: { type: ["boolean", "array"] },
optionalDependencies: { type: ["boolean", "array"] },
peerDependencies: { type: ["boolean", "array"] },
allow: {
type: "array",
items: { type: "string" }
}
},
additionalProperties: false
}],
messages: { unassigned: "Imported module should be assigned" }
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
const isAllow = (source) => testIsAllow(options.allow, context, source);
return {
ImportDeclaration(node) {
if (node.specifiers.length === 0 && !isAllow(node.source.value)) context.report({
node,
messageId: "unassigned"
});
},
ExpressionStatement(node) {
if (node.expression.type === "CallExpression" && isStaticRequire(node.expression) && "value" in node.expression.arguments[0] && typeof node.expression.arguments[0].value === "string" && !isAllow(node.expression.arguments[0].value)) context.report({
node: node.expression,
messageId: "unassigned"
});
}
};
}
});
//#endregion
//#region src/rules/no-unresolved.ts
var no_unresolved_default = createRule({
name: "no-unresolved",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Ensure imports point to a file/module that can be resolved."
},
schema: [makeOptionsSchema({
caseSensitive: {
type: "boolean",
default: true
},
caseSensitiveStrict: { type: "boolean" }
})],
messages: {
unresolved: "Unable to resolve path to module '{{module}}'.",
casingMismatch: "Casing of {{module}} does not match the underlying filesystem."
}
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
return moduleVisitor(function checkSourceValue(source, node) {
if ("importKind" in node && node.importKind === "type" || "exportKind" in node && node.exportKind === "type") return;
const caseSensitive = !CASE_SENSITIVE_FS && options.caseSensitive !== false;
const caseSensitiveStrict = !CASE_SENSITIVE_FS && options.caseSensitiveStrict;
const resolvedPath = resolve(source.value, context);
if (resolvedPath === void 0) context.report({
node: source,
messageId: "unresolved",
data: { module: source.value }
});
else if (caseSensitive || caseSensitiveStrict) {
const cacheSettings = ModuleCache.getSettings(context.settings);
if (!fileExistsWithCaseSync(resolvedPath, cacheSettings, caseSensitiveStrict)) context.report({
node: source,
messageId: "casingMismatch",
data: { module: source.value }
});
}
}, options);
}
});
//#endregion
//#region src/rules/no-unused-modules.ts
const { FileEnumerator, shouldUseFlatConfig } = eslint_use_at_your_own_risk.default;
function listFilesUsingFileEnumerator(src, extensions) {
const { ESLINT_USE_FLAT_CONFIG } = process.env;
let isUsingFlatConfig;
try {
isUsingFlatConfig = shouldUseFlatConfig && ESLINT_USE_FLAT_CONFIG !== "false";
} catch {
isUsingFlatConfig = !!ESLINT_USE_FLAT_CONFIG && ESLINT_USE_FLAT_CONFIG !== "false";
}
const enumerator = new FileEnumerator({ extensions });
try {
return Array.from(enumerator.iterateFiles(src), ({ filePath, ignored }) => ({
filename: filePath,
ignored
}));
} catch (error) {
if (isUsingFlatConfig && error.message.includes("No ESLint configuration found")) throw new Error(`
Due to the exclusion of certain internal ESLint APIs when using flat config,
the import-x/no-unused-modules rule requires an .eslintrc file (even empty) to know which
files to ignore (even when using flat config).
The .eslintrc file only needs to contain "ignorePatterns", or can be empty if
you do not want to ignore any files.
See https://github.com/import-js/eslint-plugin-import/issues/3079
for additional context.
`);
throw error;
}
}
const DEFAULT = "default";
const { AST_NODE_TYPES } = __typescript_eslint_types.TSESTree;
function forEachDeclarationIdentifier(declaration, cb) {
if (declaration) {
const isTypeDeclaration = declaration.type === AST_NODE_TYPES.TSInterfaceDeclaration || declaration.type === AST_NODE_TYPES.TSTypeAliasDeclaration || declaration.type === AST_NODE_TYPES.TSEnumDeclaration;
if (declaration.type === AST_NODE_TYPES.FunctionDeclaration || declaration.type === AST_NODE_TYPES.ClassDeclaration || isTypeDeclaration) cb(declaration.id.name, isTypeDeclaration);
else if (declaration.type === AST_NODE_TYPES.VariableDeclaration) for (const { id } of declaration.declarations) if (id.type === AST_NODE_TYPES.ObjectPattern) recursivePatternCapture(id, (pattern$1) => {
if (pattern$1.type === AST_NODE_TYPES.Identifier) cb(pattern$1.name, false);
});
else if (id.type === AST_NODE_TYPES.ArrayPattern) {
for (const el of id.elements) if (el?.type === AST_NODE_TYPES.Identifier) cb(el.name, false);
} else cb(id.name, false);
}
}
/**
* List of imports per file.
*
* Represented by a two-level Map to a Set of identifiers. The upper-level Map
* keys are the paths to the modules containing the imports, while the
* lower-level Map keys are the paths to the files which are being imported
* from. Lastly, the Set of identifiers contains either names being imported or
* a special AST node name listed above (e.g ImportDefaultSpecifier).
*
* For example, if we have a file named foo.js containing:
*
* `import { o2 } from './bar.js';`
*
* Then we will have a structure that looks like:
*
* `Map { 'foo.js' => Map { 'bar.js' => Set { 'o2' } } }`
*/
const importList = new Map();
/**
* List of exports per file.
*
* Represented by a two-level Map to an object of metadata. The upper-level Map
* keys are the paths to the modules containing the exports, while the
* lower-level Map keys are the specific identifiers or special AST node names
* being exported. The leaf-level metadata object at the moment only contains a
* `whereUsed` property, which contains a Set of paths to modules that import
* the name.
*
* For example, if we have a file named bar.js containing the following exports:
*
* `const o2 = 'bar'; export { o2 };`
*
* And a file named foo.js containing the following import:
*
* `import { o2 } from './bar.js';`
*
* Then we will have a structure that looks like:
*
* `Map { 'bar.js' => Map { 'o2' => { whereUsed: Set { 'foo.js' } } } }`
*/
const exportList = new Map();
const visitorKeyMap = new Map();
const ignoredFiles = new Set();
const filesOutsideSrc = new Set();
const isNodeModule = (path$22) => /([/\\])(node_modules)\1/.test(path$22);
/**
* Read all files matching the patterns in src and ignoreExports
*
* Return all files matching src pattern, which are not matching the
* ignoreExports pattern
*/
const resolveFiles = (src, ignoreExports, context) => {
const extensions = [...getFileExtensions(context.settings)];
const srcFileList = listFilesUsingFileEnumerator(src, extensions);
const ignoredFilesList = listFilesUsingFileEnumerator(ignoreExports, extensions);
for (const { filename } of ignoredFilesList) ignoredFiles.add(filename);
return new Set(srcFileList.flatMap(({ filename }) => isNodeModule(filename) ? [] : filename));
};
/**
* Parse all source files and build up 2 maps containing the existing imports
* and exports
*/
const prepareImportsAndExports = (srcFiles$1, context) => {
const exportAll = new Map();
for (const file of srcFiles$1) {
const exports$1 = new Map();
const imports = new Map();
const currentExports = ExportMap.get(file, context);
if (currentExports) {
const { dependencies, reexports, imports: localImportList, namespace, visitorKeys } = currentExports;
visitorKeyMap.set(file, visitorKeys);
const currentExportAll = new Set();
for (const getDependency of dependencies) {
const dependency = getDependency();
if (dependency === null) continue;
currentExportAll.add(dependency.path);
}
exportAll.set(file, currentExportAll);
for (const [key, value] of reexports.entries()) {
if (key === DEFAULT) exports$1.set(AST_NODE_TYPES.ImportDefaultSpecifier, { whereUsed: new Set() });
else exports$1.set(key, { whereUsed: new Set() });
const reexport = value.getImport();
if (!reexport) continue;
let localImport = imports.get(reexport.path);
const currentValue = value.local === DEFAULT ? AST_NODE_TYPES.ImportDefaultSpecifier : value.local;
localImport = localImport === void 0 ? new Set([currentValue]) : new Set([...localImport, currentValue]);
imports.set(reexport.path, localImport);
}
for (const [key, value] of localImportList.entries()) {
if (isNodeModule(key)) continue;
const localImport = imports.get(key) || new Set();
for (const { importedSpecifiers } of value.declarations) for (const specifier of importedSpecifiers) localImport.add(specifier);
imports.set(key, localImport);
}
importList.set(file, imports);
if (ignoredFiles.has(file)) continue;
for (const [key, _value] of namespace.entries()) if (key === DEFAULT) exports$1.set(AST_NODE_TYPES.ImportDefaultSpecifier, { whereUsed: new Set() });
else exports$1.set(key, { whereUsed: new Set() });
}
exports$1.set(AST_NODE_TYPES.ExportAllDeclaration, { whereUsed: new Set() });
exports$1.set(AST_NODE_TYPES.ImportNamespaceSpecifier, { whereUsed: new Set() });
exportList.set(file, exports$1);
}
for (const [key, value] of exportAll.entries()) for (const val of value) {
const currentExports = exportList.get(val);
if (currentExports) {
const currentExport = currentExports.get(AST_NODE_TYPES.ExportAllDeclaration);
currentExport.whereUsed.add(key);
}
}
};
/**
* Traverse through all imports and add the respective path to the
* whereUsed-list of the corresponding export
*/
const determineUsage = () => {
for (const [listKey, listValue] of importList.entries()) for (const [key, value] of listValue.entries()) {
const exports$1 = exportList.get(key);
if (exports$1 !== void 0) for (const currentImport of value) {
let specifier;
if (currentImport === AST_NODE_TYPES.ImportNamespaceSpecifier) specifier = AST_NODE_TYPES.ImportNamespaceSpecifier;
else if (currentImport === AST_NODE_TYPES.ImportDefaultSpecifier) specifier = AST_NODE_TYPES.ImportDefaultSpecifier;
else specifier = currentImport;
if (specifier !== void 0) {
const exportStatement = exports$1.get(specifier);
if (exportStatement !== void 0) {
const { whereUsed } = exportStatement;
whereUsed.add(listKey);
exports$1.set(specifier, { whereUsed });
}
}
}
}
};
/**
* Prepare the lists of existing imports and exports - should only be executed
* once at the start of a new eslint run
*/
let srcFiles;
let lastPrepareKey;
const doPreparation = (src, ignoreExports, context) => {
const prepareKey = JSON.stringify({
src: src.sort(),
ignoreExports: (ignoreExports || []).sort(),
extensions: [...getFileExtensions(context.settings)].sort()
});
if (prepareKey === lastPrepareKey) return;
importList.clear();
exportList.clear();
ignoredFiles.clear();
filesOutsideSrc.clear();
srcFiles = resolveFiles(src, ignoreExports, context);
prepareImportsAndExports(srcFiles, context);
determineUsage();
lastPrepareKey = prepareKey;
};
const newNamespaceImportExists = (specifiers) => specifiers.some(({ type }) => type === AST_NODE_TYPES.ImportNamespaceSpecifier);
const newDefaultImportExists = (specifiers) => specifiers.some(({ type }) => type === AST_NODE_TYPES.ImportDefaultSpecifier);
const fileIsInPkg = (file) => {
const { pkg, path: pkgPath } = readPkgUp({ cwd: file });
const basePath = node_path.default.dirname(pkgPath);
const checkPkgFieldString = (pkgField) => {
if (node_path.default.join(basePath, pkgField) === file) return true;
};
const checkPkgFieldObject = (pkgField) => {
const pkgFieldFiles = Object.values(pkgField).flatMap((value) => typeof value === "boolean" ? [] : node_path.default.join(basePath, value));
if (pkgFieldFiles.includes(file)) return true;
};
const checkPkgField = (pkgField) => {
if (typeof pkgField === "string") return checkPkgFieldString(pkgField);
if (typeof pkgField === "object") return checkPkgFieldObject(pkgField);
};
if (!pkg) return false;
if (pkg.private === true) return false;
if (pkg.bin && checkPkgField(pkg.bin)) return true;
if (pkg.browser && checkPkgField(pkg.browser)) return true;
if (pkg.main && checkPkgFieldString(pkg.main)) return true;
return false;
};
var no_unused_modules_default = createRule({
name: "no-unused-modules",
meta: {
type: "suggestion",
docs: {
category: "Helpful warnings",
description: "Forbid modules without exports, or exports without matching import in another module."
},
schema: [{
type: "object",
properties: {
src: {
description: "files/paths to be analyzed (only for unused exports)",
type: "array",
uniqueItems: true,
items: {
type: "string",
minLength: 1
}
},
ignoreExports: {
description: "files/paths for which unused exports will not be reported (e.g module entry points)",
type: "array",
uniqueItems: true,
items: {
type: "string",
minLength: 1
}
},
missingExports: {
description: "report modules without any exports",
type: "boolean"
},
unusedExports: {
description: "report exports without any usage",
type: "boolean"
},
ignoreUnusedTypeExports: {
description: "ignore type exports without any usage",
type: "boolean"
}
},
anyOf: [{
type: "object",
properties: {
unusedExports: {
type: "boolean",
enum: [true]
},
src: {
type: "array",
minItems: 1
}
},
required: ["unusedExports"]
}, {
type: "object",
properties: { missingExports: {
type: "boolean",
enum: [true]
} },
required: ["missingExports"]
}]
}],
messages: {
notFound: "No exports found",
unused: "exported declaration '{{value}}' not used within other modules"
}
},
defaultOptions: [],
create(context) {
const { src = [process.cwd()], ignoreExports = [], missingExports, unusedExports, ignoreUnusedTypeExports } = context.options[0] || {};
if (unusedExports) doPreparation(src, ignoreExports, context);
const filename = context.physicalFilename;
const checkExportPresence = (node) => {
if (!missingExports) return;
if (ignoreUnusedTypeExports) return;
if (ignoredFiles.has(filename)) return;
const exportCount = exportList.get(filename);
const exportAll = exportCount.get(AST_NODE_TYPES.ExportAllDeclaration);
const namespaceImports = exportCount.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
exportCount.delete(AST_NODE_TYPES.ExportAllDeclaration);
exportCount.delete(AST_NODE_TYPES.ImportNamespaceSpecifier);
if (exportCount.size === 0) context.report({
node: node.body[0] ?? node,
messageId: "notFound"
});
exportCount.set(AST_NODE_TYPES.ExportAllDeclaration, exportAll);
exportCount.set(AST_NODE_TYPES.ImportNamespaceSpecifier, namespaceImports);
};
const checkUsage = (node, exportedValue, isTypeExport) => {
if (!unusedExports) return;
if (isTypeExport && ignoreUnusedTypeExports) return;
if (ignoredFiles.has(filename)) return;
if (fileIsInPkg(filename)) return;
if (filesOutsideSrc.has(filename)) return;
if (!srcFiles.has(filename)) {
srcFiles = resolveFiles(src, ignoreExports, context);
if (!srcFiles.has(filename)) {
filesOutsideSrc.add(filename);
return;
}
}
const exports$1 = exportList.get(filename);
if (!exports$1) {
console.error(`file \`${filename}\` has no exports. Please update to the latest, and if it still happens, report this on https://github.com/import-js/eslint-plugin-import/issues/2866!`);
return;
}
const exportAll = exports$1.get(AST_NODE_TYPES.ExportAllDeclaration);
if (exportAll !== void 0 && exportedValue !== AST_NODE_TYPES.ImportDefaultSpecifier && exportAll.whereUsed.size > 0) return;
const namespaceImports = exports$1.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
if (namespaceImports !== void 0 && namespaceImports.whereUsed.size > 0) return;
const exportsKey = exportedValue === DEFAULT ? AST_NODE_TYPES.ImportDefaultSpecifier : exportedValue;
const exportStatement = exports$1.get(exportsKey);
const value = exportsKey === AST_NODE_TYPES.ImportDefaultSpecifier ? DEFAULT : exportsKey;
if (exportStatement === void 0) context.report({
node,
messageId: "unused",
data: { value }
});
else if (exportStatement.whereUsed.size === 0) context.report({
node,
messageId: "unused",
data: { value }
});
};
/**
* Only useful for tools like vscode-eslint
*
* Update lists of existing exports during runtime
*/
const updateExportUsage = (node) => {
if (ignoredFiles.has(filename)) return;
const exports$1 = exportList.get(filename) ?? new Map();
const newExports = new Map();
const newExportIdentifiers = new Set();
for (const s of node.body) {
if (s.type === AST_NODE_TYPES.ExportDefaultDeclaration) newExportIdentifiers.add(AST_NODE_TYPES.ImportDefaultSpecifier);
if (s.type === AST_NODE_TYPES.ExportNamedDeclaration) {
if (s.specifiers.length > 0) {
for (const specifier of s.specifiers) if (specifier.exported) newExportIdentifiers.add(getValue(specifier.exported));
}
forEachDeclarationIdentifier(s.declaration, (name$1) => {
newExportIdentifiers.add(name$1);
});
}
}
for (const [key, value] of exports$1.entries()) if (newExportIdentifiers.has(key)) newExports.set(key, value);
for (const key of newExportIdentifiers) if (!exports$1.has(key)) newExports.set(key, { whereUsed: new Set() });
const exportAll = exports$1.get(AST_NODE_TYPES.ExportAllDeclaration);
const namespaceImports = exports$1.get(AST_NODE_TYPES.ImportNamespaceSpecifier) ?? { whereUsed: new Set() };
newExports.set(AST_NODE_TYPES.ExportAllDeclaration, exportAll);
newExports.set(AST_NODE_TYPES.ImportNamespaceSpecifier, namespaceImports);
exportList.set(filename, newExports);
};
/**
* Only useful for tools like vscode-eslint
*
* Update lists of existing imports during runtime
*/
const updateImportUsage = (node) => {
if (!unusedExports) return;
const oldImportPaths = importList.get(filename) ?? new Map();
const oldNamespaceImports = new Set();
const newNamespaceImports = new Set();
const oldExportAll = new Set();
const newExportAll = new Set();
const oldDefaultImports = new Set();
const newDefaultImports = new Set();
const oldImports = new Map();
const newImports = new Map();
for (const [key, value] of oldImportPaths.entries()) {
if (value.has(AST_NODE_TYPES.ExportAllDeclaration)) oldExportAll.add(key);
if (value.has(AST_NODE_TYPES.ImportNamespaceSpecifier)) oldNamespaceImports.add(key);
if (value.has(AST_NODE_TYPES.ImportDefaultSpecifier)) oldDefaultImports.add(key);
for (const val of value) if (val !== AST_NODE_TYPES.ImportNamespaceSpecifier && val !== AST_NODE_TYPES.ImportDefaultSpecifier) oldImports.set(val, key);
}
function processDynamicImport(source) {
if (source.type !== "Literal" || typeof source.value !== "string") return null;
const p = resolve(source.value, context);
if (p == null) return null;
newNamespaceImports.add(p);
}
visit(node, visitorKeyMap.get(filename), {
ImportExpression(child) {
processDynamicImport(child.source);
},
CallExpression(child_) {
const child = child_;
if (child.callee.type === "Import") processDynamicImport(child.arguments[0]);
}
});
for (const astNode of node.body) {
let resolvedPath;
if (astNode.type === AST_NODE_TYPES.ExportNamedDeclaration && astNode.source) {
resolvedPath = resolve(astNode.source.raw.replaceAll(/('|")/g, ""), context);
for (const specifier of astNode.specifiers) {
const name$1 = getValue(specifier.local);
if (name$1 === DEFAULT) newDefaultImports.add(resolvedPath);
else newImports.set(name$1, resolvedPath);
}
}
if (astNode.type === AST_NODE_TYPES.ExportAllDeclaration) {
resolvedPath = resolve(astNode.source.raw.replaceAll(/('|")/g, ""), context);
newExportAll.add(resolvedPath);
}
if (astNode.type === AST_NODE_TYPES.ImportDeclaration) {
resolvedPath = resolve(astNode.source.raw.replaceAll(/('|")/g, ""), context);
if (!resolvedPath) continue;
if (isNodeModule(resolvedPath)) continue;
if (newNamespaceImportExists(astNode.specifiers)) newNamespaceImports.add(resolvedPath);
if (newDefaultImportExists(astNode.specifiers)) newDefaultImports.add(resolvedPath);
for (const specifier of astNode.specifiers.filter((specifier$1) => specifier$1.type !== AST_NODE_TYPES.ImportDefaultSpecifier && specifier$1.type !== AST_NODE_TYPES.ImportNamespaceSpecifier)) if ("imported" in specifier) newImports.set(getValue(specifier.imported), resolvedPath);
}
}
for (const value of newExportAll) if (!oldExportAll.has(value)) {
const imports = oldImportPaths.get(value) ?? new Set();
imports.add(AST_NODE_TYPES.ExportAllDeclaration);
oldImportPaths.set(value, imports);
let exports$1 = exportList.get(value);
let currentExport;
if (exports$1 === void 0) {
exports$1 = new Map();
exportList.set(value, exports$1);
} else currentExport = exports$1.get(AST_NODE_TYPES.ExportAllDeclaration);
if (currentExport === void 0) {
const whereUsed = new Set();
whereUsed.add(filename);
exports$1.set(AST_NODE_TYPES.ExportAllDeclaration, { whereUsed });
} else currentExport.whereUsed.add(filename);
}
for (const value of oldExportAll) if (!newExportAll.has(value)) {
const imports = oldImportPaths.get(value);
imports.delete(AST_NODE_TYPES.ExportAllDeclaration);
const exports$1 = exportList.get(value);
if (exports$1 !== void 0) {
const currentExport = exports$1.get(AST_NODE_TYPES.ExportAllDeclaration);
if (currentExport !== void 0) currentExport.whereUsed.delete(filename);
}
}
for (const value of newDefaultImports) if (!oldDefaultImports.has(value)) {
let imports = oldImportPaths.get(value);
if (imports === void 0) imports = new Set();
imports.add(AST_NODE_TYPES.ImportDefaultSpecifier);
oldImportPaths.set(value, imports);
let exports$1 = exportList.get(value);
let currentExport;
if (exports$1 === void 0) {
exports$1 = new Map();
exportList.set(value, exports$1);
} else currentExport = exports$1.get(AST_NODE_TYPES.ImportDefaultSpecifier);
if (currentExport === void 0) {
const whereUsed = new Set();
whereUsed.add(filename);
exports$1.set(AST_NODE_TYPES.ImportDefaultSpecifier, { whereUsed });
} else currentExport.whereUsed.add(filename);
}
for (const value of oldDefaultImports) if (!newDefaultImports.has(value)) {
const imports = oldImportPaths.get(value);
imports.delete(AST_NODE_TYPES.ImportDefaultSpecifier);
const exports$1 = exportList.get(value);
if (exports$1 !== void 0) {
const currentExport = exports$1.get(AST_NODE_TYPES.ImportDefaultSpecifier);
if (currentExport !== void 0) currentExport.whereUsed.delete(filename);
}
}
for (const value of newNamespaceImports) if (!oldNamespaceImports.has(value)) {
let imports = oldImportPaths.get(value);
if (imports === void 0) imports = new Set();
imports.add(AST_NODE_TYPES.ImportNamespaceSpecifier);
oldImportPaths.set(value, imports);
let exports$1 = exportList.get(value);
let currentExport;
if (exports$1 === void 0) {
exports$1 = new Map();
exportList.set(value, exports$1);
} else currentExport = exports$1.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
if (currentExport === void 0) {
const whereUsed = new Set();
whereUsed.add(filename);
exports$1.set(AST_NODE_TYPES.ImportNamespaceSpecifier, { whereUsed });
} else currentExport.whereUsed.add(filename);
}
for (const value of oldNamespaceImports) if (!newNamespaceImports.has(value)) {
const imports = oldImportPaths.get(value);
imports.delete(AST_NODE_TYPES.ImportNamespaceSpecifier);
const exports$1 = exportList.get(value);
if (exports$1 !== void 0) {
const currentExport = exports$1.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
if (currentExport !== void 0) currentExport.whereUsed.delete(filename);
}
}
for (const [key, value] of newImports.entries()) if (!oldImports.has(key)) {
let imports = oldImportPaths.get(value);
if (imports === void 0) imports = new Set();
imports.add(key);
oldImportPaths.set(value, imports);
let exports$1 = exportList.get(value);
let currentExport;
if (exports$1 === void 0) {
exports$1 = new Map();
exportList.set(value, exports$1);
} else currentExport = exports$1.get(key);
if (currentExport === void 0) {
const whereUsed = new Set();
whereUsed.add(filename);
exports$1.set(key, { whereUsed });
} else currentExport.whereUsed.add(filename);
}
for (const [key, value] of oldImports.entries()) if (!newImports.has(key)) {
const imports = oldImportPaths.get(value);
imports.delete(key);
const exports$1 = exportList.get(value);
if (exports$1 !== void 0) {
const currentExport = exports$1.get(key);
if (currentExport !== void 0) currentExport.whereUsed.delete(filename);
}
}
};
return {
"Program:exit"(node) {
updateExportUsage(node);
updateImportUsage(node);
checkExportPresence(node);
},
ExportDefaultDeclaration(node) {
checkUsage(node, AST_NODE_TYPES.ImportDefaultSpecifier, false);
},
ExportNamedDeclaration(node) {
for (const specifier of node.specifiers) checkUsage(specifier, getValue(specifier.exported), false);
forEachDeclarationIdentifier(node.declaration, (name$1, isTypeExport) => {
checkUsage(node, name$1, isTypeExport);
});
}
};
}
});
//#endregion
//#region src/rules/no-useless-path-segments.ts
/**
* Convert a potentially relative path from node utils into a true relative
* path.
*
* `../ -> ..`
*
* `./ -> .`
*
* `.foo/bar -> ./.foo/bar`
*
* `..foo/bar -> ./..foo/bar`
*
* `foo/bar -> ./foo/bar`
*
* @param relativePath Relative posix path potentially missing leading './'
* @returns Relative posix path that always starts with a ./
*/
function toRelativePath(relativePath) {
const stripped = relativePath.replaceAll(/\/$/g, "");
return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}`;
}
function normalize(filepath) {
return toRelativePath(node_path.default.posix.normalize(filepath));
}
function countRelativeParents(pathSegments) {
return pathSegments.filter((x) => x === "..").length;
}
var no_useless_path_segments_default = createRule({
name: "no-useless-path-segments",
meta: {
type: "suggestion",
docs: {
category: "Static analysis",
description: "Forbid unnecessary path segments in import and require statements."
},
fixable: "code",
schema: [{
type: "object",
properties: {
commonjs: { type: "boolean" },
noUselessIndex: { type: "boolean" }
},
additionalProperties: false
}],
messages: { useless: "Useless path segments for \"{{importPath}}\", should be \"{{proposedPath}}\"" }
},
defaultOptions: [],
create(context) {
const currentDir = node_path.default.dirname(context.physicalFilename);
const options = context.options[0] || {};
return moduleVisitor((source) => {
const { value: importPath } = source;
function reportWithProposedPath(proposedPath) {
context.report({
node: source,
messageId: "useless",
data: {
importPath,
proposedPath
},
fix: (fixer) => proposedPath ? fixer.replaceText(source, JSON.stringify(proposedPath)) : null
});
}
if (!importPath.startsWith(".")) return;
const resolvedPath = resolve(importPath, context);
const normedPath = normalize(importPath);
const resolvedNormedPath = resolve(normedPath, context);
if (normedPath !== importPath && resolvedPath === resolvedNormedPath) return reportWithProposedPath(normedPath);
const fileExtensions = getFileExtensions(context.settings);
const regexUnnecessaryIndex = new RegExp(`.*\\/index(\\${[...fileExtensions].join("|\\")})?$`);
if (options.noUselessIndex && regexUnnecessaryIndex.test(importPath)) {
const parentDirectory = node_path.default.dirname(importPath);
if (parentDirectory !== "." && parentDirectory !== "..") {
for (const fileExtension of fileExtensions) if (resolve(`${parentDirectory}${fileExtension}`, context)) return reportWithProposedPath(`${parentDirectory}/`);
}
return reportWithProposedPath(parentDirectory);
}
if (importPath.startsWith("./")) return;
if (resolvedPath === void 0) return;
const expected = node_path.default.relative(currentDir, resolvedPath);
const expectedSplit = expected.split(node_path.default.sep);
const importPathSplit = importPath.replace(/^\.\//, "").split("/");
const countImportPathRelativeParents = countRelativeParents(importPathSplit);
const countExpectedRelativeParents = countRelativeParents(expectedSplit);
const diff = countImportPathRelativeParents - countExpectedRelativeParents;
if (diff <= 0) return;
return reportWithProposedPath(toRelativePath([...importPathSplit.slice(0, countExpectedRelativeParents), ...importPathSplit.slice(countImportPathRelativeParents + diff)].join("/")));
}, options);
}
});
//#endregion
//#region src/rules/no-webpack-loader-syntax.ts
var no_webpack_loader_syntax_default = createRule({
name: "no-webpack-loader-syntax",
meta: {
type: "problem",
docs: {
category: "Static analysis",
description: "Forbid webpack loader syntax in imports."
},
schema: [],
messages: { unexpected: "Unexpected '!' in '{{name}}'. Do not use import syntax to configure webpack loaders." }
},
defaultOptions: [],
create(context) {
return moduleVisitor((source, node) => {
if (source.value?.includes("!")) context.report({
node,
messageId: "unexpected",
data: { name: source.value }
});
}, { commonjs: true });
}
});
//#endregion
//#region src/rules/order.ts
const log = (0, debug.default)("eslint-plugin-import-x:rules:order");
const groupBy = (array, grouper) => array.reduce((acc, curr, index) => {
const key = grouper(curr, index);
(acc[key] ||= []).push(curr);
return acc;
}, {});
const categories = {
named: "named",
import: "import",
exports: "exports"
};
const defaultGroups = [
"builtin",
"external",
"parent",
"sibling",
"index"
];
function reverse(array) {
return array.map((v) => ({
...v,
rank: -v.rank
})).reverse();
}
function getTokensOrCommentsAfter(sourceCode, node, count) {
let currentNodeOrToken = node;
const result = [];
for (let i = 0; i < count; i++) {
currentNodeOrToken = sourceCode.getTokenAfter(currentNodeOrToken, { includeComments: true });
if (currentNodeOrToken == null) break;
result.push(currentNodeOrToken);
}
return result;
}
function getTokensOrCommentsBefore(sourceCode, node, count) {
let currentNodeOrToken = node;
const result = [];
for (let i = 0; i < count; i++) {
currentNodeOrToken = sourceCode.getTokenBefore(currentNodeOrToken, { includeComments: true });
if (currentNodeOrToken == null) break;
result.push(currentNodeOrToken);
}
return result.reverse();
}
function takeTokensAfterWhile(sourceCode, node, condition) {
const tokens = getTokensOrCommentsAfter(sourceCode, node, 100);
const result = [];
for (const token of tokens) if (condition(token)) result.push(token);
else break;
return result;
}
function takeTokensBeforeWhile(sourceCode, node, condition) {
const tokens = getTokensOrCommentsBefore(sourceCode, node, 100);
const result = [];
for (let i = tokens.length - 1; i >= 0; i--) if (condition(tokens[i])) result.push(tokens[i]);
else break;
return result.reverse();
}
function findOutOfOrder(imported) {
if (imported.length === 0) return [];
let maxSeenRankNode = imported[0];
return imported.filter(function(importedModule) {
const res = importedModule.rank < maxSeenRankNode.rank;
if (maxSeenRankNode.rank < importedModule.rank) maxSeenRankNode = importedModule;
return res;
});
}
function findRootNode(node) {
let parent = node;
while (parent.parent != null && (!("body" in parent.parent) || parent.parent.body == null)) parent = parent.parent;
return parent;
}
function findEndOfLineWithComments(sourceCode, node) {
const tokensToEndOfLine = takeTokensAfterWhile(sourceCode, node, commentOnSameLineAs(node));
const endOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1] : node.range[1];
let result = endOfTokens;
for (let i = endOfTokens; i < sourceCode.text.length; i++) {
if (sourceCode.text[i] === "\n") {
result = i + 1;
break;
}
if (sourceCode.text[i] !== " " && sourceCode.text[i] !== " " && sourceCode.text[i] !== "\r") break;
result = i + 1;
}
return result;
}
function commentOnSameLineAs(node) {
return (token) => (token.type === "Block" || token.type === "Line") && token.loc.start.line === token.loc.end.line && token.loc.end.line === node.loc.end.line;
}
function findStartOfLineWithComments(sourceCode, node) {
const tokensToEndOfLine = takeTokensBeforeWhile(sourceCode, node, commentOnSameLineAs(node));
const startOfTokens = tokensToEndOfLine.length > 0 ? tokensToEndOfLine[0].range[0] : node.range[0];
let result = startOfTokens;
for (let i = startOfTokens - 1; i > 0; i--) {
if (sourceCode.text[i] !== " " && sourceCode.text[i] !== " ") break;
result = i;
}
return result;
}
function findSpecifierStart(sourceCode, node) {
let token;
do
token = sourceCode.getTokenBefore(node);
while (token.value !== "," && token.value !== "{");
return token.range[1];
}
function findSpecifierEnd(sourceCode, node) {
let token;
do
token = sourceCode.getTokenAfter(node);
while (token.value !== "," && token.value !== "}");
return token.range[0];
}
function isRequireExpression(expr) {
return expr != null && expr.type === "CallExpression" && expr.callee != null && "name" in expr.callee && expr.callee.name === "require" && expr.arguments != null && expr.arguments.length === 1 && expr.arguments[0].type === "Literal";
}
function isSupportedRequireModule(node) {
if (node.type !== "VariableDeclaration") return false;
if (node.declarations.length !== 1) return false;
const decl = node.declarations[0];
const isPlainRequire = decl.id && (decl.id.type === "Identifier" || decl.id.type === "ObjectPattern") && isRequireExpression(decl.init);
const isRequireWithMemberExpression = decl.id && (decl.id.type === "Identifier" || decl.id.type === "ObjectPattern") && decl.init != null && decl.init.type === "CallExpression" && decl.init.callee != null && decl.init.callee.type === "MemberExpression" && isRequireExpression(decl.init.callee.object);
return isPlainRequire || isRequireWithMemberExpression;
}
function isPlainImportModule(node) {
return node.type === "ImportDeclaration" && node.specifiers != null && node.specifiers.length > 0;
}
function isPlainImportEquals(node) {
return node.type === "TSImportEqualsDeclaration" && "expression" in node.moduleReference && !!node.moduleReference.expression;
}
function isCJSExports(context, node) {
if (node.type === "MemberExpression" && node.object.type === "Identifier" && node.property.type === "Identifier" && node.object.name === "module" && node.property.name === "exports") return !context.sourceCode.getScope(node).variables.some((variable) => variable.name === "module");
if (node.type === "Identifier" && node.name === "exports") return !context.sourceCode.getScope(node).variables.some((variable) => variable.name === "exports");
}
function getNamedCJSExports(context, node) {
if (node.type !== "MemberExpression") return;
const result = [];
let root = node;
let parent;
while (root.type === "MemberExpression") {
if (root.property.type !== "Identifier") return;
result.unshift(root.property.name);
parent = root;
root = root.object;
}
if (isCJSExports(context, root)) return result;
if (isCJSExports(context, parent)) return result.slice(1);
}
function canCrossNodeWhileReorder(node) {
return isSupportedRequireModule(node) || isPlainImportModule(node) || isPlainImportEquals(node);
}
function canReorderItems(firstNode, secondNode) {
const parent = firstNode.parent;
if (!parent || !("body" in parent) || !Array.isArray(parent.body)) return false;
const body = parent.body;
const [firstIndex, secondIndex] = [body.indexOf(firstNode), body.indexOf(secondNode)].sort();
const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1);
for (const nodeBetween of nodesBetween) if (!canCrossNodeWhileReorder(nodeBetween)) return false;
return true;
}
function makeImportDescription(node) {
if (node.type === "export") {
if (node.node.exportKind === "type") return "type export";
return "export";
}
if (node.node.importKind === "type") return "type import";
if (node.node.importKind === "typeof") return "typeof import";
return "import";
}
function fixOutOfOrder(context, firstNode, secondNode, order, category) {
const isNamed = category === categories.named;
const isExports = category === categories.exports;
const { sourceCode } = context;
const { firstRoot, secondRoot } = isNamed ? {
firstRoot: firstNode.node,
secondRoot: secondNode.node
} : {
firstRoot: findRootNode(firstNode.node),
secondRoot: findRootNode(secondNode.node)
};
const { firstRootStart, firstRootEnd, secondRootStart, secondRootEnd } = isNamed ? {
firstRootStart: findSpecifierStart(sourceCode, firstRoot),
firstRootEnd: findSpecifierEnd(sourceCode, firstRoot),
secondRootStart: findSpecifierStart(sourceCode, secondRoot),
secondRootEnd: findSpecifierEnd(sourceCode, secondRoot)
} : {
firstRootStart: findStartOfLineWithComments(sourceCode, firstRoot),
firstRootEnd: findEndOfLineWithComments(sourceCode, firstRoot),
secondRootStart: findStartOfLineWithComments(sourceCode, secondRoot),
secondRootEnd: findEndOfLineWithComments(sourceCode, secondRoot)
};
if (firstNode.displayName === secondNode.displayName) {
if (firstNode.alias) firstNode.displayName = `${firstNode.displayName} as ${firstNode.alias}`;
if (secondNode.alias) secondNode.displayName = `${secondNode.displayName} as ${secondNode.alias}`;
}
const firstDesc = makeImportDescription(firstNode);
const secondDesc = makeImportDescription(secondNode);
if (firstNode.displayName === secondNode.displayName && firstDesc === secondDesc) {
log(firstNode.displayName, firstNode.node.loc, secondNode.displayName, secondNode.node.loc);
return;
}
const firstImport = `${firstDesc} of \`${firstNode.displayName}\``;
const secondImport = `\`${secondNode.displayName}\` ${secondDesc}`;
const messageOptions = {
messageId: "order",
data: {
firstImport,
secondImport,
order
}
};
if (isNamed) {
const firstCode = sourceCode.text.slice(firstRootStart, firstRoot.range[1]);
const firstTrivia = sourceCode.text.slice(firstRoot.range[1], firstRootEnd);
const secondCode = sourceCode.text.slice(secondRootStart, secondRoot.range[1]);
const secondTrivia = sourceCode.text.slice(secondRoot.range[1], secondRootEnd);
if (order === "before") {
const trimmedTrivia = secondTrivia.trimEnd();
const gapCode = sourceCode.text.slice(firstRootEnd, secondRootStart - 1);
const whitespaces = secondTrivia.slice(trimmedTrivia.length);
context.report({
node: secondNode.node,
...messageOptions,
fix: (fixer) => fixer.replaceTextRange([firstRootStart, secondRootEnd], `${secondCode},${trimmedTrivia}${firstCode}${firstTrivia}${gapCode}${whitespaces}`)
});
} else if (order === "after") {
const trimmedTrivia = firstTrivia.trimEnd();
const gapCode = sourceCode.text.slice(secondRootEnd + 1, firstRootStart);
const whitespaces = firstTrivia.slice(trimmedTrivia.length);
context.report({
node: secondNode.node,
...messageOptions,
fix: (fixes) => fixes.replaceTextRange([secondRootStart, firstRootEnd], `${gapCode}${firstCode},${trimmedTrivia}${secondCode}${whitespaces}`)
});
}
} else {
const canFix = isExports || canReorderItems(firstRoot, secondRoot);
let newCode = sourceCode.text.slice(secondRootStart, secondRootEnd);
if (newCode[newCode.length - 1] !== "\n") newCode = `${newCode}\n`;
if (order === "before") context.report({
node: secondNode.node,
...messageOptions,
fix: canFix ? (fixer) => fixer.replaceTextRange([firstRootStart, secondRootEnd], newCode + sourceCode.text.slice(firstRootStart, secondRootStart)) : null
});
else if (order === "after") context.report({
node: secondNode.node,
...messageOptions,
fix: canFix ? (fixer) => fixer.replaceTextRange([secondRootStart, firstRootEnd], sourceCode.text.slice(secondRootEnd, firstRootEnd) + newCode) : null
});
}
}
function reportOutOfOrder(context, imported, outOfOrder, order, category) {
for (const imp of outOfOrder) fixOutOfOrder(context, imported.find((importedItem) => importedItem.rank > imp.rank), imp, order, category);
}
function makeOutOfOrderReport(context, imported, category) {
const outOfOrder = findOutOfOrder(imported);
if (outOfOrder.length === 0) return;
const reversedImported = reverse(imported);
const reversedOrder = findOutOfOrder(reversedImported);
if (reversedOrder.length < outOfOrder.length) {
reportOutOfOrder(context, reversedImported, reversedOrder, "after", category);
return;
}
reportOutOfOrder(context, imported, outOfOrder, "before", category);
}
const compareString = (a, b) => {
if (a < b) return -1;
if (a > b) return 1;
return 0;
};
/** Some parsers (languages without types) don't provide ImportKind */
const DEFAULT_IMPORT_KIND = "value";
const getNormalizedValue = (node, toLowerCase) => {
const value = String(node.value);
return toLowerCase ? value.toLowerCase() : value;
};
const RELATIVE_DOTS = new Set([".", ".."]);
function getSorter(alphabetizeOptions) {
const multiplier = alphabetizeOptions.order === "asc" ? 1 : -1;
const orderImportKind = alphabetizeOptions.orderImportKind;
const multiplierImportKind = orderImportKind !== "ignore" && (alphabetizeOptions.orderImportKind === "asc" ? 1 : -1);
return function importsSorter(nodeA, nodeB) {
const importA = getNormalizedValue(nodeA, alphabetizeOptions.caseInsensitive);
const importB = getNormalizedValue(nodeB, alphabetizeOptions.caseInsensitive);
let result = 0;
if (!importA.includes("/") && !importB.includes("/")) result = compareString(importA, importB);
else {
const A = importA.split("/");
const B = importB.split("/");
const a = A.length;
const b = B.length;
for (let i = 0; i < Math.min(a, b); i++) {
const x = A[i];
const y = B[i];
if (i === 0 && RELATIVE_DOTS.has(x) && RELATIVE_DOTS.has(y)) {
if (x !== y) break;
continue;
}
result = compareString(x, y);
if (result) break;
}
if (!result && a !== b) result = a < b ? -1 : 1;
}
result = result * multiplier;
if (!result && multiplierImportKind) result = multiplierImportKind * compareString(nodeA.node.importKind || DEFAULT_IMPORT_KIND, nodeB.node.importKind || DEFAULT_IMPORT_KIND);
return result;
};
}
function mutateRanksToAlphabetize(imported, alphabetizeOptions) {
const groupedByRanks = groupBy(imported, (item) => item.rank);
const sorterFn = getSorter(alphabetizeOptions);
const groupRanks = Object.keys(groupedByRanks).sort((a, b) => +a - +b);
for (const groupRank of groupRanks) groupedByRanks[groupRank].sort(sorterFn);
let newRank = 0;
const alphabetizedRanks = groupRanks.reduce((acc, groupRank) => {
for (const importedItem of groupedByRanks[groupRank]) {
acc[`${importedItem.value}|${importedItem.node.importKind}`] = Number.parseInt(groupRank, 10) + newRank;
newRank += 1;
}
return acc;
}, {});
for (const importedItem of imported) importedItem.rank = alphabetizedRanks[`${importedItem.value}|${importedItem.node.importKind}`];
}
function computePathRank(ranks, pathGroups, path$22, maxPosition) {
for (const { pattern: pattern$1, patternOptions, group, position = 1 } of pathGroups) if ((0, minimatch.minimatch)(path$22, pattern$1, patternOptions || { nocomment: true })) return ranks[group] + position / maxPosition;
}
function computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesGroup) {
let impType;
let rank;
const isTypeGroupInGroups = !ranks.omittedTypes.includes("type");
const isTypeOnlyImport = importEntry.node.importKind === "type";
const isExcludedFromPathRank = isTypeOnlyImport && isTypeGroupInGroups && excludedImportTypes.has("type");
if (importEntry.type === "import:object") impType = "object";
else if (isTypeOnlyImport && isTypeGroupInGroups && !isSortingTypesGroup) impType = "type";
else impType = importType(importEntry.value, context);
if (!excludedImportTypes.has(impType) && !isExcludedFromPathRank) rank = typeof importEntry.value === "string" ? computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition) : void 0;
if (rank === void 0) {
rank = ranks.groups[impType];
if (rank === void 0) return -1;
}
if (isTypeOnlyImport && isSortingTypesGroup) rank = ranks.groups.type + rank / 10;
if (importEntry.type !== "import" && !importEntry.type.startsWith("import:")) rank += 100;
return rank;
}
function registerNode(context, importEntry, ranks, imported, excludedImportTypes, isSortingTypesGroup) {
const rank = computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesGroup);
if (rank !== -1) {
let importNode = importEntry.node;
if (importEntry.type === "require" && importNode.parent?.parent?.type === "VariableDeclaration") importNode = importNode.parent.parent;
imported.push({
...importEntry,
rank,
isMultiline: importNode.loc.end.line !== importNode.loc.start.line
});
}
}
function getRequireBlock(node) {
let n = node;
while (n.parent?.type === "MemberExpression" && n.parent.object === n || n.parent?.type === "CallExpression" && n.parent.callee === n) n = n.parent;
if (n.parent?.type === "VariableDeclarator" && n.parent.parent.type === "VariableDeclaration" && n.parent.parent.parent.type === "Program") return n.parent.parent.parent;
}
const types = [
"builtin",
"external",
"internal",
"unknown",
"parent",
"sibling",
"index",
"object",
"type"
];
function convertGroupsToRanks(groups) {
const rankObject = groups.reduce((res, group, index) => {
for (const groupItem of [group].flat()) {
if (!types.includes(groupItem)) throw new Error(`Incorrect configuration of the rule: Unknown type \`${JSON.stringify(groupItem)}\``);
if (res[groupItem] !== void 0) throw new Error(`Incorrect configuration of the rule: \`${groupItem}\` is duplicated`);
res[groupItem] = index * 2;
}
return res;
}, {});
const omittedTypes = types.filter((type) => rankObject[type] === void 0);
const ranks = omittedTypes.reduce(function(res, type) {
res[type] = groups.length * 2;
return res;
}, rankObject);
return {
groups: ranks,
omittedTypes
};
}
function convertPathGroupsForRanks(pathGroups) {
const after = {};
const before = {};
const transformed = pathGroups.map((pathGroup, index) => {
const { group, position: positionString } = pathGroup;
let position = 0;
if (positionString === "after") {
if (!after[group]) after[group] = 1;
position = after[group]++;
} else if (positionString === "before") {
if (!before[group]) before[group] = [];
before[group].push(index);
}
return {
...pathGroup,
position
};
});
let maxPosition = 1;
for (const group of Object.keys(before)) {
const groupLength = before[group].length;
for (const [index, groupIndex] of before[group].entries()) transformed[groupIndex].position = -1 * (groupLength - index);
maxPosition = Math.max(maxPosition, groupLength);
}
for (const key of Object.keys(after)) {
const groupNextPosition = after[key];
maxPosition = Math.max(maxPosition, groupNextPosition - 1);
}
return {
pathGroups: transformed,
maxPosition: maxPosition > 10 ? Math.pow(10, Math.ceil(Math.log10(maxPosition))) : 10
};
}
function fixNewLineAfterImport(context, previousImport) {
const prevRoot = findRootNode(previousImport.node);
const tokensToEndOfLine = takeTokensAfterWhile(context.sourceCode, prevRoot, commentOnSameLineAs(prevRoot));
let endOfLine = prevRoot.range[1];
if (tokensToEndOfLine.length > 0) endOfLine = tokensToEndOfLine[tokensToEndOfLine.length - 1].range[1];
return (fixer) => fixer.insertTextAfterRange([prevRoot.range[0], endOfLine], "\n");
}
function removeNewLineAfterImport(context, currentImport, previousImport) {
const { sourceCode } = context;
const prevRoot = findRootNode(previousImport.node);
const currRoot = findRootNode(currentImport.node);
const rangeToRemove = [findEndOfLineWithComments(sourceCode, prevRoot), findStartOfLineWithComments(sourceCode, currRoot)];
if (/^\s*$/.test(sourceCode.text.slice(rangeToRemove[0], rangeToRemove[1]))) return (fixer) => fixer.removeRange(rangeToRemove);
return;
}
function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports_, newlinesBetweenTypeOnlyImports_, distinctGroup, isSortingTypesGroup, isConsolidatingSpaceBetweenImports) {
const getNumberOfEmptyLinesBetween = (currentImport, previousImport$1) => {
return context.sourceCode.lines.slice(previousImport$1.node.loc.end.line, currentImport.node.loc.start.line - 1).filter((line) => line.trim().length === 0).length;
};
const getIsStartOfDistinctGroup = (currentImport, previousImport$1) => currentImport.rank - 1 >= previousImport$1.rank;
let previousImport = imported[0];
for (const currentImport of imported.slice(1)) {
const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport);
const isStartOfDistinctGroup = getIsStartOfDistinctGroup(currentImport, previousImport);
const isTypeOnlyImport = currentImport.node.importKind === "type";
const isPreviousImportTypeOnlyImport = previousImport.node.importKind === "type";
const isNormalImportNextToTypeOnlyImportAndRelevant = isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesGroup;
const isTypeOnlyImportAndRelevant = isTypeOnlyImport && isSortingTypesGroup;
const newlinesBetweenImports = isSortingTypesGroup && isConsolidatingSpaceBetweenImports && (previousImport.isMultiline || currentImport.isMultiline) && newlinesBetweenImports_ === "never" ? "always-and-inside-groups" : newlinesBetweenImports_;
const newlinesBetweenTypeOnlyImports = isSortingTypesGroup && isConsolidatingSpaceBetweenImports && (isNormalImportNextToTypeOnlyImportAndRelevant || previousImport.isMultiline || currentImport.isMultiline) && newlinesBetweenTypeOnlyImports_ === "never" ? "always-and-inside-groups" : newlinesBetweenTypeOnlyImports_;
const isNotIgnored = isTypeOnlyImportAndRelevant && newlinesBetweenTypeOnlyImports !== "ignore" || !isTypeOnlyImportAndRelevant && newlinesBetweenImports !== "ignore";
if (isNotIgnored) {
const shouldAssertNewlineBetweenGroups = (isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant) && (newlinesBetweenTypeOnlyImports === "always" || newlinesBetweenTypeOnlyImports === "always-and-inside-groups") || !isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant && (newlinesBetweenImports === "always" || newlinesBetweenImports === "always-and-inside-groups");
const shouldAssertNoNewlineWithinGroup = (isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant) && newlinesBetweenTypeOnlyImports !== "always-and-inside-groups" || !isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant && newlinesBetweenImports !== "always-and-inside-groups";
const shouldAssertNoNewlineBetweenGroup = !isSortingTypesGroup || !isNormalImportNextToTypeOnlyImportAndRelevant || newlinesBetweenTypeOnlyImports === "never";
const isTheNewlineBetweenImportsInTheSameGroup = distinctGroup && currentImport.rank === previousImport.rank || !distinctGroup && !isStartOfDistinctGroup;
let alreadyReported = false;
if (shouldAssertNewlineBetweenGroups) {
if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
if (distinctGroup || isStartOfDistinctGroup) {
alreadyReported = true;
context.report({
node: previousImport.node,
messageId: "oneLineBetweenGroups",
fix: fixNewLineAfterImport(context, previousImport)
});
}
} else if (emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup && isTheNewlineBetweenImportsInTheSameGroup) {
alreadyReported = true;
context.report({
node: previousImport.node,
messageId: "noLineWithinGroup",
fix: removeNewLineAfterImport(context, currentImport, previousImport)
});
}
} else if (emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup) {
alreadyReported = true;
context.report({
node: previousImport.node,
messageId: "noLineBetweenGroups",
fix: removeNewLineAfterImport(context, currentImport, previousImport)
});
}
if (!alreadyReported && isConsolidatingSpaceBetweenImports) {
if (emptyLinesBetween === 0 && currentImport.isMultiline) context.report({
node: previousImport.node,
messageId: "oneLineBetweenTheMultiLineImport",
fix: fixNewLineAfterImport(context, previousImport)
});
else if (emptyLinesBetween === 0 && previousImport.isMultiline) context.report({
node: previousImport.node,
messageId: "oneLineBetweenThisMultiLineImport",
fix: fixNewLineAfterImport(context, previousImport)
});
else if (emptyLinesBetween > 0 && !previousImport.isMultiline && !currentImport.isMultiline && isTheNewlineBetweenImportsInTheSameGroup) context.report({
node: previousImport.node,
messageId: "noLineBetweenSingleLineImport",
fix: removeNewLineAfterImport(context, currentImport, previousImport)
});
}
}
previousImport = currentImport;
}
}
function getAlphabetizeConfig(options) {
const alphabetize = options.alphabetize || {};
const order = alphabetize.order || "ignore";
const orderImportKind = alphabetize.orderImportKind || "ignore";
const caseInsensitive = alphabetize.caseInsensitive || false;
return {
order,
orderImportKind,
caseInsensitive
};
}
const defaultDistinctGroup = true;
var order_default = createRule({
name: "order",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Enforce a convention in module import order."
},
fixable: "code",
schema: [{
type: "object",
properties: {
groups: { type: "array" },
pathGroupsExcludedImportTypes: { type: "array" },
distinctGroup: {
type: "boolean",
default: defaultDistinctGroup
},
pathGroups: {
type: "array",
items: {
type: "object",
properties: {
pattern: { type: "string" },
patternOptions: { type: "object" },
group: {
type: "string",
enum: types
},
position: {
type: "string",
enum: ["after", "before"]
}
},
additionalProperties: false,
required: ["pattern", "group"]
}
},
"newlines-between": {
type: "string",
enum: [
"ignore",
"always",
"always-and-inside-groups",
"never"
]
},
"newlines-between-types": {
type: "string",
enum: [
"ignore",
"always",
"always-and-inside-groups",
"never"
]
},
consolidateIslands: {
type: "string",
enum: ["inside-groups", "never"]
},
sortTypesGroup: {
type: "boolean",
default: false
},
named: {
default: false,
oneOf: [{ type: "boolean" }, {
type: "object",
properties: {
enabled: { type: "boolean" },
import: { type: "boolean" },
export: { type: "boolean" },
require: { type: "boolean" },
cjsExports: { type: "boolean" },
types: {
type: "string",
enum: [
"mixed",
"types-first",
"types-last"
]
}
},
additionalProperties: false
}]
},
alphabetize: {
type: "object",
properties: {
caseInsensitive: {
type: "boolean",
default: false
},
order: {
type: "string",
enum: [
"ignore",
"asc",
"desc"
],
default: "ignore"
},
orderImportKind: {
type: "string",
enum: [
"ignore",
"asc",
"desc"
],
default: "ignore"
}
},
additionalProperties: false
},
warnOnUnassignedImports: {
type: "boolean",
default: false
}
},
additionalProperties: false,
dependencies: {
"newlines-between-types": {
type: "object",
properties: { sortTypesGroup: {
type: "boolean",
enum: [true]
} },
required: ["sortTypesGroup"]
},
consolidateIslands: { anyOf: [{
type: "object",
properties: { "newlines-between": {
type: "string",
enum: ["always-and-inside-groups"]
} },
required: ["newlines-between"]
}, {
type: "object",
properties: { "newlines-between-types": {
type: "string",
enum: ["always-and-inside-groups"]
} },
required: ["newlines-between-types"]
}] }
}
}],
messages: {
error: "{{error}}",
noLineWithinGroup: "There should be no empty line within import group",
noLineBetweenGroups: "There should be no empty line between import groups",
oneLineBetweenGroups: "There should be at least one empty line between import groups",
order: "{{secondImport}} should occur {{order}} {{firstImport}}",
oneLineBetweenTheMultiLineImport: "There should be at least one empty line between this import and the multi-line import that follows it",
oneLineBetweenThisMultiLineImport: "There should be at least one empty line between this multi-line import and the import that follows it",
noLineBetweenSingleLineImport: "There should be no empty lines between this single-line import and the single-line import that follows it"
}
},
defaultOptions: [],
create(context) {
const options = context.options[0] || {};
const newlinesBetweenImports = options["newlines-between"] || "ignore";
const newlinesBetweenTypeOnlyImports = options["newlines-between-types"] || newlinesBetweenImports;
const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || [
"builtin",
"external",
"object"
]);
const sortTypesGroup = options.sortTypesGroup;
const consolidateIslands = options.consolidateIslands || "never";
const named = {
types: "mixed",
...typeof options.named === "object" ? {
...options.named,
import: "import" in options.named ? options.named.import : options.named.enabled,
export: "export" in options.named ? options.named.export : options.named.enabled,
require: "require" in options.named ? options.named.require : options.named.enabled,
cjsExports: "cjsExports" in options.named ? options.named.cjsExports : options.named.enabled
} : {
import: options.named,
export: options.named,
require: options.named,
cjsExports: options.named
}
};
const namedGroups = named.types === "mixed" ? [] : named.types === "types-last" ? ["value"] : ["type"];
const alphabetize = getAlphabetizeConfig(options);
const distinctGroup = options.distinctGroup == null ? defaultDistinctGroup : !!options.distinctGroup;
let ranks;
try {
const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups || []);
const { groups, omittedTypes } = convertGroupsToRanks(options.groups || defaultGroups);
ranks = {
groups,
omittedTypes,
pathGroups,
maxPosition
};
} catch (error) {
return { Program(node) {
context.report({
node,
messageId: "error",
data: { error: error.message }
});
} };
}
const importMap = new Map();
const exportMap = new Map();
const isTypeGroupInGroups = !ranks.omittedTypes.includes("type");
const isSortingTypesGroup = isTypeGroupInGroups && sortTypesGroup;
function getBlockImports(node) {
let blockImports = importMap.get(node);
if (!blockImports) importMap.set(node, blockImports = []);
return blockImports;
}
function getBlockExports(node) {
let blockExports = exportMap.get(node);
if (!blockExports) exportMap.set(node, blockExports = []);
return blockExports;
}
function makeNamedOrderReport(context$1, namedImports) {
if (namedImports.length > 1) {
const imports = namedImports.map((namedImport) => {
const kind = namedImport.kind || "value";
const rank = namedGroups.indexOf(kind);
return {
displayName: namedImport.value,
rank: rank === -1 ? namedGroups.length : rank,
...namedImport,
value: `${namedImport.value}:${namedImport.alias || ""}`
};
});
if (alphabetize.order !== "ignore") mutateRanksToAlphabetize(imports, alphabetize);
makeOutOfOrderReport(context$1, imports, categories.named);
}
}
return {
ImportDeclaration(node) {
if (node.specifiers.length > 0 || options.warnOnUnassignedImports) {
const name$1 = node.source.value;
registerNode(context, {
node,
value: name$1,
displayName: name$1,
type: "import"
}, ranks, getBlockImports(node.parent), pathGroupsExcludedImportTypes, isSortingTypesGroup);
if (named.import) makeNamedOrderReport(context, node.specifiers.filter((specifier) => specifier.type === "ImportSpecifier").map((specifier) => ({
node: specifier,
value: getValue(specifier.imported),
type: "import",
kind: specifier.importKind,
...specifier.local.range[0] !== specifier.imported.range[0] && { alias: specifier.local.name }
})));
}
},
TSImportEqualsDeclaration(node) {
if (node.isExport) return;
let displayName;
let value;
let type;
if (node.moduleReference.type === "TSExternalModuleReference") {
value = node.moduleReference.expression.value;
displayName = value;
type = "import";
} else {
value = "";
displayName = context.sourceCode.getText(node.moduleReference);
type = "import:object";
}
registerNode(context, {
node,
value,
displayName,
type
}, ranks, getBlockImports(node.parent), pathGroupsExcludedImportTypes, isSortingTypesGroup);
},
CallExpression(node) {
if (!isStaticRequire(node)) return;
const block = getRequireBlock(node);
const firstArg = node.arguments[0];
if (!block || !("value" in firstArg)) return;
const { value } = firstArg;
registerNode(context, {
node,
value,
displayName: value,
type: "require"
}, ranks, getBlockImports(block), pathGroupsExcludedImportTypes, isSortingTypesGroup);
},
...named.require && { VariableDeclarator(node) {
if (node.id.type === "ObjectPattern" && isRequireExpression(node.init)) {
const { properties: properties$1 } = node.id;
for (const p of properties$1) if (!("key" in p) || p.key.type !== "Identifier" || p.value.type !== "Identifier") return;
makeNamedOrderReport(context, node.id.properties.map((prop_) => {
const prop = prop_;
const key = prop.key;
const value = prop.value;
return {
node: prop,
value: key.name,
type: "require",
...key.range[0] !== value.range[0] && { alias: value.name }
};
}));
}
} },
...named.export && { ExportNamedDeclaration(node) {
makeNamedOrderReport(context, node.specifiers.map((specifier) => ({
node: specifier,
value: getValue(specifier.local),
type: "export",
kind: specifier.exportKind,
...specifier.local.range[0] !== specifier.exported.range[0] && { alias: getValue(specifier.exported) }
})));
} },
...named.cjsExports && { AssignmentExpression(node) {
if (node.parent.type === "ExpressionStatement") if (isCJSExports(context, node.left)) {
if (node.right.type === "ObjectExpression") {
const { properties: properties$1 } = node.right;
for (const p of properties$1) if (!("key" in p) || p.key.type !== "Identifier" || p.value.type !== "Identifier") return;
makeNamedOrderReport(context, properties$1.map((prop_) => {
const prop = prop_;
const key = prop.key;
const value = prop.value;
return {
node: prop,
value: key.name,
type: "export",
...key.range[0] !== value.range[0] && { alias: value.name }
};
}));
}
} else {
const nameParts = getNamedCJSExports(context, node.left);
if (nameParts && nameParts.length > 0) {
const name$1 = nameParts.join(".");
getBlockExports(node.parent.parent).push({
node,
value: name$1,
displayName: name$1,
type: "export",
rank: 0
});
}
}
} },
"Program:exit"() {
for (const imported of importMap.values()) {
if (newlinesBetweenImports !== "ignore" || newlinesBetweenTypeOnlyImports !== "ignore") makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, newlinesBetweenTypeOnlyImports, distinctGroup, isSortingTypesGroup, consolidateIslands === "inside-groups" && (newlinesBetweenImports === "always-and-inside-groups" || newlinesBetweenTypeOnlyImports === "always-and-inside-groups"));
if (alphabetize.order !== "ignore") mutateRanksToAlphabetize(imported, alphabetize);
makeOutOfOrderReport(context, imported, categories.import);
}
for (const exported of exportMap.values()) if (alphabetize.order !== "ignore") {
mutateRanksToAlphabetize(exported, alphabetize);
makeOutOfOrderReport(context, exported, categories.exports);
}
importMap.clear();
exportMap.clear();
}
};
}
});
//#endregion
//#region src/rules/prefer-default-export.ts
var prefer_default_export_default = createRule({
name: "prefer-default-export",
meta: {
type: "suggestion",
docs: {
category: "Style guide",
description: "Prefer a default export if module exports a single name or multiple names."
},
schema: [{
type: "object",
properties: { target: {
type: "string",
enum: ["single", "any"],
default: "single"
} },
additionalProperties: false
}],
messages: {
single: "Prefer default export on a file with single export.",
any: "Prefer default export to be present on every file that has export."
}
},
defaultOptions: [],
create(context) {
let specifierExportCount = 0;
let hasDefaultExport = false;
let hasStarExport = false;
let hasTypeExport = false;
let namedExportNode;
const { target = "single" } = context.options[0] || {};
function captureDeclaration(identifierOrPattern) {
if (identifierOrPattern?.type === "ObjectPattern") for (const property of identifierOrPattern.properties) captureDeclaration(property.value);
else if (identifierOrPattern?.type === "ArrayPattern") for (const el of identifierOrPattern.elements) captureDeclaration(el);
else specifierExportCount++;
}
return {
ExportDefaultSpecifier() {
hasDefaultExport = true;
},
ExportSpecifier(node) {
if (getValue(node.exported) === "default") hasDefaultExport = true;
else {
specifierExportCount++;
namedExportNode = node;
}
},
ExportNamedDeclaration(node) {
if (!node.declaration) return;
const { type } = node.declaration;
if (type === "TSTypeAliasDeclaration" || type === "TSInterfaceDeclaration" || type === "TypeAlias" || type === "InterfaceDeclaration") {
specifierExportCount++;
hasTypeExport = true;
return;
}
if ("declarations" in node.declaration && node.declaration.declarations) for (const declaration of node.declaration.declarations) captureDeclaration(declaration.id);
else specifierExportCount++;
namedExportNode = node;
},
ExportDefaultDeclaration() {
hasDefaultExport = true;
},
ExportAllDeclaration() {
hasStarExport = true;
},
"Program:exit"() {
if (hasDefaultExport || hasStarExport || hasTypeExport) return;
if (target === "single" && specifierExportCount === 1) context.report({
node: namedExportNode,
messageId: "single"
});
else if (target === "any" && specifierExportCount > 0) context.report({
node: namedExportNode,
messageId: "any"
});
}
};
}
});
//#endregion
//#region src/rules/prefer-namespace-import.ts
var prefer_namespace_import_default = createRule({
name: "prefer-namespace-import",
meta: {
type: "problem",
docs: {
category: "Style guide",
description: "Enforce using namespace imports for specific modules, like `react`/`react-dom`, etc."
},
fixable: "code",
schema: [{
type: "object",
additionalProperties: false,
properties: { patterns: {
type: "array",
items: { type: "string" },
uniqueItems: true
} }
}],
messages: { preferNamespaceImport: "Prefer importing {{specifier}} as 'import * as {{specifier}} from \"{{source}}\"';" }
},
defaultOptions: [],
create(context) {
const { patterns } = context.options[0] ?? {};
if (!patterns?.length) return {};
const regexps = patterns.map(toRegExp);
return { ImportDefaultSpecifier(node) {
const importSource = node.parent.source.value;
if (!regexps.some((exp) => exp.test(importSource))) return;
const defaultSpecifier = node.local.name;
const hasOtherSpecifiers = node.parent.specifiers.length > 1;
context.report({
messageId: "preferNamespaceImport",
node: hasOtherSpecifiers ? node : node.parent,
data: {
source: importSource,
specifier: defaultSpecifier
},
fix(fixer) {
const importDeclarationText = context.sourceCode.getText(node.parent);
const localName = node.local.name;
if (!hasOtherSpecifiers) return fixer.replaceText(node, `* as ${localName}`);
const isTypeImport = node.parent.importKind === "type";
const importStringPrefix = `import${isTypeImport ? " type" : ""}`;
const rightBraceIndex = importDeclarationText.indexOf("}") + 1;
const specifiers = importDeclarationText.slice(importDeclarationText.indexOf("{"), rightBraceIndex);
const remainingText = importDeclarationText.slice(rightBraceIndex);
return fixer.replaceText(node.parent, [`${importStringPrefix} * as ${localName} ${remainingText.trimStart()}`, `${importStringPrefix} ${specifiers}${remainingText}`].join("\n"));
}
});
} };
}
});
/** Regular expression for matching a RegExp string. */
const REGEXP_STR = /^\/(.+)\/([A-Za-z]*)$/u;
/**
* Convert a string to the `RegExp`. Normal strings (e.g. `"foo"`) is converted
* to `/^foo$/` of `RegExp`. Strings like `"/^foo/i"` are converted to `/^foo/i`
* of `RegExp`.
*
* @param string The string to convert.
* @returns Returns the `RegExp`.
* @see https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/utils/regexp.ts
*/
function toRegExp(string) {
const [, pattern$1, flags = "u"] = REGEXP_STR.exec(string) ?? [];
if (pattern$1 != null) return new RegExp(pattern$1, flags);
return { test: (s) => s === string };
}
//#endregion
//#region src/rules/unambiguous.ts
var unambiguous_default = createRule({
name: "unambiguous",
meta: {
type: "suggestion",
docs: {
category: "Module systems",
description: "Forbid potentially ambiguous parse goal (`script` vs. `module`)."
},
schema: [],
messages: { module: "This module could be parsed as a valid script." }
},
defaultOptions: [],
create(context) {
if (sourceType(context) !== "module") return {};
return { Program(ast) {
if (!isUnambiguousModule(ast)) context.report({
node: ast,
messageId: "module"
});
} };
}
});
//#endregion
//#region src/index.ts
const rules = {
"no-unresolved": no_unresolved_default,
named: named_default,
default: default_default,
namespace: namespace_default,
"no-namespace": no_namespace_default,
export: export_default,
"no-mutable-exports": no_mutable_exports_default,
extensions: extensions_default,
"no-restricted-paths": no_restricted_paths_default,
"no-internal-modules": no_internal_modules_default,
"group-exports": group_exports_default,
"no-relative-packages": no_relative_packages_default,
"no-relative-parent-imports": no_relative_parent_imports_default,
"consistent-type-specifier-style": consistent_type_specifier_style_default,
"no-self-import": no_self_import_default,
"no-cycle": no_cycle_default,
"no-named-default": no_named_default_default,
"no-named-as-default": no_named_as_default_default,
"no-named-as-default-member": no_named_as_default_member_default,
"no-anonymous-default-export": no_anonymous_default_export_default,
"no-rename-default": no_rename_default_default,
"no-unused-modules": no_unused_modules_default,
"no-commonjs": no_commonjs_default,
"no-amd": no_amd_default,
"no-duplicates": no_duplicates_default,
first: first_default,
"max-dependencies": max_dependencies_default,
"no-extraneous-dependencies": no_extraneous_dependencies_default,
"no-absolute-path": no_absolute_path_default,
"no-nodejs-modules": no_nodejs_modules_default,
"no-webpack-loader-syntax": no_webpack_loader_syntax_default,
order: order_default,
"newline-after-import": newline_after_import_default,
"prefer-default-export": prefer_default_export_default,
"prefer-namespace-import": prefer_namespace_import_default,
"no-default-export": no_default_export_default,
"no-named-export": no_named_export_default,
"no-dynamic-require": no_dynamic_require_default,
unambiguous: unambiguous_default,
"no-unassigned-import": no_unassigned_import_default,
"no-useless-path-segments": no_useless_path_segments_default,
"dynamic-import-chunkname": dynamic_import_chunkname_default,
"no-import-module-exports": no_import_module_exports_default,
"no-empty-named-blocks": no_empty_named_blocks_default,
"exports-last": exports_last_default,
"no-deprecated": no_deprecated_default,
"imports-first": imports_first_default
};
const plugin_ = {
meta,
rules,
cjsRequire,
importXResolverCompat,
createNodeResolver
};
const createFlatConfig = (baseConfig, configName) => ({
...baseConfig,
name: `import-x/${configName}`,
plugins: { "import-x": plugin_ }
});
const flatConfigs = {
recommended: createFlatConfig(recommended_default$1, "recommended"),
errors: createFlatConfig(errors_default, "errors"),
warnings: createFlatConfig(warnings_default$1, "warnings"),
"stage-0": createFlatConfig(stage_0_default$1, "stage-0"),
react: createFlatConfig(react_default$1, "react"),
"react-native": createFlatConfig(react_native_default$1, "react-native"),
electron: createFlatConfig(electron_default, "electron"),
typescript: createFlatConfig(typescript_default$1, "typescript")
};
const configs = {
recommended: recommended_default,
errors: errors_default$1,
warnings: warnings_default,
"stage-0": stage_0_default,
react: react_default,
"react-native": react_native_default,
electron: electron_default$1,
typescript: typescript_default,
"flat/recommended": flatConfigs.recommended,
"flat/errors": flatConfigs.errors,
"flat/warnings": flatConfigs.warnings,
"flat/stage-0": flatConfigs["stage-0"],
"flat/react": flatConfigs.react,
"flat/react-native": flatConfigs["react-native"],
"flat/electron": flatConfigs.electron,
"flat/typescript": flatConfigs.typescript
};
const plugin = plugin_;
plugin.flatConfigs = flatConfigs;
plugin.configs = configs;
var src_default = plugin;
//#endregion
exports.cjsRequire = cjsRequire;
exports.configs = configs;
exports.createNodeResolver = createNodeResolver;
exports.default = src_default;
exports.flatConfigs = flatConfigs;
exports.importX = plugin;
exports.importXResolverCompat = importXResolverCompat;
exports.meta = meta;
exports.rules = rules;