@rebeccastevens/eslint-config
Version:
My ESLint shareable config.
1,302 lines (1,289 loc) • 170 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var path = require('node:path');
var localPkg = require('local-pkg');
var fs = require('node:fs/promises');
var globals = require('globals');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
async function interopDefault(value) {
const resolved = await value;
// eslint-disable-next-line ts/no-unsafe-return, ts/no-explicit-any, ts/no-unsafe-member-access
return resolved.default ?? resolved;
}
const parserPlain = {
meta: {
name: "parser-plain",
},
parseForESLint: (code) => ({
ast: {
body: [],
comments: [],
loc: { end: code.length, start: 0 },
range: [0, code.length],
tokens: [],
type: "Program",
},
scopeManager: null,
services: { isPlain: true },
visitorKeys: {
Program: [],
},
}),
};
async function loadPackages(packageIds) {
const missing = packageIds.filter((id) => !localPkg.isPackageExists(id));
if (missing.length > 0) {
await installPackages(missing);
}
// eslint-disable-next-line ts/no-explicit-any, ts/no-unsafe-return
return Promise.all(packageIds.map((id) => interopDefault(import(id))));
}
const mut_installPackagesToLoad = new Set();
let mut_installPackagesAction = null;
let mut_installPackagesActionResolver = null;
let mut_installPackagesTimeout = null;
/* eslint-disable functional/no-loop-statements */
async function installPackages(packages) {
for (const p of packages) {
mut_installPackagesToLoad.add(p);
}
if (mut_installPackagesTimeout !== null) {
clearTimeout(mut_installPackagesTimeout);
}
mut_installPackagesTimeout = setTimeout(() => {
const allPackages = [...mut_installPackagesToLoad.values()];
mut_installPackagesTimeout = null;
mut_installPackagesToLoad.clear();
mut_installPackagesAction = null;
mut_installPackagesActionResolver(allPackages);
mut_installPackagesActionResolver = null;
}, 100);
mut_installPackagesAction ??= new Promise((resolve) => {
mut_installPackagesActionResolver = resolve;
}).then(async (allPackages) => {
const allPackagesString = allPackages.join(", ");
if (Boolean(process.env["CI"]) || !process.stdout.isTTY) {
throw new Error(`Missing packages: ${allPackagesString}`);
}
const prompt = await import('@clack/prompts');
const result = await prompt.confirm({
message: allPackages.length === 1
? `${allPackages[0]} is required for this config. Do you want to install it?`
: `Packages are required for this config: ${allPackagesString}.\nDo you want to install them?`,
});
if (result !== false) {
await import('@antfu/install-pkg').then(({ installPackage }) => installPackage(allPackages, { dev: true }));
}
});
return mut_installPackagesAction;
}
/* eslint-enable functional/no-loop-statements */
async function comments() {
const [pluginComments] = (await loadPackages(["@eslint-community/eslint-plugin-eslint-comments"]));
return [
{
name: "rs:comments",
plugins: {
"eslint-comments": pluginComments,
},
rules: {
"eslint-comments/no-aggregating-enable": "error",
"eslint-comments/no-duplicate-disable": "error",
"eslint-comments/no-unlimited-disable": "error",
"eslint-comments/no-unused-enable": "error",
"eslint-comments/disable-enable-pair": ["error", { allowWholeFile: true }],
},
},
];
}
const GLOB_SRC_EXT = "?([cm])[jt]s?(x)";
const GLOB_SRC = "**/?(.)*.?([cm])[jt]s?(x)";
const GLOB_JS = "**/?(.)*.?([cm])js";
const GLOB_JSX = "**/?(.)*.?([cm])jsx";
const GLOB_TS = "**/?(.)*.?([cm])ts";
const GLOB_TSX = "**/?(.)*.?([cm])tsx";
const GLOB_DTS = "**/?(.)*.d.?([cm])ts";
const GLOB_ROOT_JS = "./?(.)*.?([cm])js";
const GLOB_ROOT_JSX = "./?(.)*.?([cm])jsx";
const GLOB_ROOT_TS = "./?(.)*.?([cm])ts";
const GLOB_ROOT_TSX = "./?(.)*.?([cm])tsx";
const GLOB_ROOT_DTS = "./?(.)*.d.?([cm])ts";
const GLOB_TYPINGS = "typings/**/?(.)*.?([cm])ts";
const GLOB_MJS = "**/?(.)*.mjs";
const GLOB_MTS = "**/?(.)*.mts";
const GLOB_CJS = "**/?(.)*.cjs";
const GLOB_CTS = "**/?(.)*.cts";
// cspell:disable-next-line
const GLOB_STYLE = "**/?(.)*.{c,le,sc,pc,postc}ss";
const GLOB_CSS = "**/?(.)*.css";
const GLOB_POSTCSS = "**/?(.)*.{p,post}css";
const GLOB_LESS = "**/?(.)*.less";
const GLOB_SCSS = "**/?(.)*.scss";
const GLOB_JSON = "**/?(.)*.json";
const GLOB_JSON5 = "**/?(.)*.json5";
const GLOB_JSONC = "**/?(.)*.jsonc";
const GLOB_MARKDOWN = "**/?(.)*.md";
const GLOB_MARKDOWN_IN_MARKDOWN = "**/?(.)*.md/?(.)*.md";
const GLOB_VUE = "**/?(.)*.vue";
const GLOB_YAML = "**/?(.)*.y?(a)ml";
const GLOB_TOML = "**/?(.)*.toml";
const GLOB_HTML = "**/?(.)*.htm?(l)";
const GLOB_GRAPHQL = "**/?(.)*.{g,graph}ql";
const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}`;
const GLOB_TESTS = [
`**/__tests__/**/?(.)*.${GLOB_SRC_EXT}`,
`**/?(.)*.spec.${GLOB_SRC_EXT}`,
`**/?(.)*.test.${GLOB_SRC_EXT}`,
`**/?(.)*.bench.${GLOB_SRC_EXT}`,
`**/?(.)*.benchmark.${GLOB_SRC_EXT}`,
];
const GLOB_ALL_SRC = [
GLOB_SRC,
GLOB_STYLE,
GLOB_JSON,
GLOB_JSON5,
GLOB_JSONC,
GLOB_MARKDOWN,
GLOB_VUE,
GLOB_YAML,
GLOB_HTML,
GLOB_TOML,
GLOB_GRAPHQL,
];
const GLOB_EXCLUDE = [
"**/node_modules",
"**/dist",
"**/lib",
"**/package-lock.json",
"**/yarn.lock",
"**/pnpm-lock.yaml",
"**/bun.lockb",
"**/output",
"**/coverage",
"**/temp",
"**/.temp",
"**/tmp",
"**/.tmp",
"**/.history",
"**/.vitepress/cache",
"**/.nuxt",
"**/.next",
"**/.vercel",
"**/.changeset",
"**/.idea",
"**/.cache",
"**/.output",
"**/.vite-inspect",
"**/.yarn",
"**/CHANGELOG*.md",
"**/?(.)*.min.*",
"**/LICENSE*",
"**/__snapshots__",
"**/auto-import.ts",
"**/auto-import.d.ts",
"**/auto-imports.ts",
"**/auto-imports.d.ts",
"**/components.ts",
"**/components.d.ts",
"**/typegen.ts",
"**/typegen.d.ts",
];
const StylisticConfigDefaults = {
indent: 2,
jsx: true,
quotes: "double",
semi: true,
printWidth: 120,
};
async function stylistic(options) {
const { stylistic: { indent, jsx, quotes, semi }, overrides, typescript, } = options;
const [pluginStylistic] = (await loadPackages(["@stylistic/eslint-plugin"]));
const config = pluginStylistic.configs.customize({
flat: true,
indent,
jsx,
pluginName: "@stylistic",
quotes,
semi,
});
return [
{
name: "rs:stylistic",
plugins: {
"@stylistic": pluginStylistic,
},
rules: {
...config.rules,
"@stylistic/array-bracket-spacing": ["error", "never"],
"@stylistic/arrow-parens": ["error", "always"],
"@stylistic/arrow-spacing": ["error", { before: true, after: true }],
"@stylistic/block-spacing": ["error", "always"],
"@stylistic/brace-style": "error",
"@stylistic/comma-dangle": [
"error",
{
arrays: "only-multiline",
exports: "only-multiline",
functions: "ignore",
imports: "only-multiline",
objects: "only-multiline",
...(typescript
? {
enums: "only-multiline",
generics: "only-multiline",
tuples: "only-multiline",
}
: {}),
},
],
"@stylistic/comma-spacing": ["error", { before: false, after: true }],
"@stylistic/comma-style": ["error", "last"],
"@stylistic/computed-property-spacing": "error",
"@stylistic/dot-location": ["error", "property"],
"@stylistic/eol-last": "error",
"@stylistic/function-call-spacing": ["error", "never"],
"@stylistic/generator-star-spacing": ["error", "after"],
"@stylistic/indent": typescript
? "off"
: [
"error",
indent,
{
SwitchCase: 1,
VariableDeclarator: 1,
outerIIFEBody: 1,
MemberExpression: 1,
FunctionDeclaration: { parameters: 1, body: 1 },
FunctionExpression: { parameters: 1, body: 1 },
CallExpression: { arguments: 1 },
ArrayExpression: 1,
ObjectExpression: 1,
ImportDeclaration: 1,
flatTernaryExpressions: false,
ignoreComments: false,
},
],
"@stylistic/indent-binary-ops": "error",
"@stylistic/key-spacing": ["error", { beforeColon: false, afterColon: true }],
"@stylistic/keyword-spacing": ["error", { before: true, after: true }],
"@stylistic/linebreak-style": ["error", "unix"],
"@stylistic/lines-around-comment": [
"warn",
{
beforeBlockComment: true,
beforeLineComment: false,
afterBlockComment: false,
afterLineComment: false,
afterHashbangComment: true,
allowBlockStart: true,
allowBlockEnd: true,
allowObjectStart: true,
allowObjectEnd: true,
allowArrayStart: true,
allowArrayEnd: true,
allowClassStart: true,
allowClassEnd: true,
...(typescript
? {
allowEnumEnd: true,
allowEnumStart: true,
allowInterfaceEnd: true,
allowInterfaceStart: true,
allowModuleEnd: true,
allowModuleStart: true,
allowTypeEnd: true,
allowTypeStart: true,
}
: {}),
},
],
"@stylistic/lines-between-class-members": [
"error",
"always",
{
exceptAfterSingleLine: true,
...(typescript
? {
exceptAfterOverload: true,
}
: {}),
},
],
"@stylistic/max-statements-per-line": ["error", { max: 1 }],
"@stylistic/multiline-ternary": ["error", "always-multiline"],
"@stylistic/new-parens": "error",
"@stylistic/newline-per-chained-call": ["error", { ignoreChainWithDepth: 2 }],
"@stylistic/no-extra-parens": ["error", "all", { nestedBinaryExpressions: false }],
"@stylistic/no-extra-semi": "error",
"@stylistic/no-floating-decimal": "error",
"@stylistic/no-mixed-operators": [
"error",
{
groups: [
["+", "-", "*", "/", "%", "**"],
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"],
],
allowSamePrecedence: true,
},
],
"@stylistic/no-mixed-spaces-and-tabs": "error",
"@stylistic/no-multi-spaces": ["error", { ignoreEOLComments: true }],
"@stylistic/no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }],
"@stylistic/no-tabs": "error",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/no-whitespace-before-property": "error",
"@stylistic/nonblock-statement-body-position": ["error", "beside", { overrides: {} }],
"@stylistic/object-curly-newline": [
"error",
{
ObjectExpression: {
minProperties: 3,
multiline: true,
consistent: true,
},
ObjectPattern: {
minProperties: 3,
multiline: true,
consistent: true,
},
},
],
"@stylistic/object-curly-spacing": ["error", "always"],
"@stylistic/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
"@stylistic/one-var-declaration-per-line": ["error", "always"],
"@stylistic/operator-linebreak": [
"error",
"after",
{
overrides: {
// "=": "none",
"==": "none",
"===": "none",
"?": "before",
":": "before",
"|": "before",
},
},
],
"@stylistic/padded-blocks": [
"error",
{
blocks: "never",
switches: "never",
classes: "never",
},
],
"@stylistic/quote-props": ["error", "consistent-as-needed"],
"@stylistic/quotes": ["error", quotes, { avoidEscape: true, allowTemplateLiterals: "never" }],
"@stylistic/rest-spread-spacing": ["error", "never"],
"@stylistic/semi-spacing": ["error", { before: false, after: true }],
"@stylistic/semi-style": ["error", "last"],
"@stylistic/semi": ["error", semi ? "always" : "never"],
"@stylistic/space-before-blocks": ["error", "always"],
"@stylistic/space-before-function-paren": [
"error",
{
asyncArrow: "always",
anonymous: "never",
named: "never",
},
],
"@stylistic/space-in-parens": ["error", "never"],
"@stylistic/space-infix-ops": "error",
"@stylistic/space-unary-ops": ["error", { words: true, nonwords: false }],
"@stylistic/spaced-comment": [
"error",
"always",
{
line: {
exceptions: ["-", "+", "*"],
markers: ["*package", "!", "/", ",", "="],
},
block: {
balanced: true,
exceptions: ["-", "+", "*"],
markers: ["*package", "!", "*", ",", ":", "::", "flow-include"],
},
},
],
"@stylistic/switch-colon-spacing": ["error", { after: true, before: false }],
"@stylistic/template-curly-spacing": ["error", "never"],
"@stylistic/template-tag-spacing": ["error", "never"],
"@stylistic/wrap-iife": ["error", "inside", { functionPrototypeMethods: true }],
"@stylistic/yield-star-spacing": ["error", "after"],
...(typescript
? {
"@stylistic/member-delimiter-style": "error",
"@stylistic/type-annotation-spacing": "error",
}
: {}),
...overrides,
},
},
];
}
async function formatters(opts, stylistic) {
const options = opts === true
? {
js: true,
ts: true,
dts: false,
json: true,
yaml: true,
css: true,
graphql: true,
html: true,
markdown: true,
slidev: localPkg.isPackageExists("@slidev/cli"),
tailwind: localPkg.isPackageExists("tailwindcss"),
}
: opts;
if (options.slidev !== false && options.slidev !== undefined && options.markdown !== true) {
throw new Error("`slidev` option only works when `markdown` is enabled with `prettier`");
}
const { indent, printWidth, quotes, semi } = stylistic;
const prettierOptions = Object.assign({
endOfLine: "lf",
printWidth: printWidth ?? 120,
semi: semi ?? true,
singleQuote: quotes === "single",
tabWidth: typeof indent === "number"
? indent
: typeof StylisticConfigDefaults.indent === "number"
? StylisticConfigDefaults.indent
: 2,
trailingComma: "all",
useTabs: indent === "tab",
}, options.prettierOptions ?? {});
const packages = loadPackages([
"eslint-plugin-format",
"eslint-config-prettier",
"sort-package-json",
"eslint-formatting-reporter",
"prettier",
]);
const [pluginFormat, configPrettier, sortPackageJson, formattingReporter] = await packages;
const turnOffRulesForPrettier = {
...Object.fromEntries(Object.entries(configPrettier.rules ?? {}).filter(([, value]) => value === "off")),
// curly: "off",
"no-unexpected-multiline": "off",
// "@stylistic/lines-around-comment": "off",
"@stylistic/max-len": "off",
"@stylistic/no-confusing-arrow": "off",
"@stylistic/no-mixed-operators": "off",
"@stylistic/no-tabs": "off",
"@stylistic/quotes": "off",
// "@stylistic/js/lines-around-comment": "off",
// "@stylistic/js/max-len": "off",
"@stylistic/js/no-confusing-arrow": "off",
"@stylistic/js/no-mixed-operators": "off",
"@stylistic/js/no-tabs": "off",
"@stylistic/js/quotes": "off",
// "@stylistic/ts/lines-around-comment": "off",
"@stylistic/ts/quotes": "off",
"@typescript-eslint/lines-around-comment": "off",
"@typescript-eslint/quotes": "off",
// "babel/quotes": "off",
// "unicorn/template-indent": "off",
"vue/html-self-closing": "off",
"vue/max-len": "off",
// other
"no-irregular-whitespace": "off",
"yml/block-sequence-hyphen-indicator-newline": "off",
};
const mut_configs = [
{
name: "rs:formatters:setup",
plugins: {
format: pluginFormat,
},
},
];
if (options.js !== undefined && options.js) {
mut_configs.push({
name: "rs:formatter:javascript",
files: [GLOB_JS, GLOB_JSX],
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "babel",
...(options.tailwind !== undefined && options.tailwind
? {
plugins: prettierOptions.plugins ?? [],
}
: {}),
},
],
},
});
}
if (options.ts !== undefined && options.ts) {
mut_configs.push({
name: "rs:formatter:typescript",
files: [GLOB_TS, GLOB_TSX],
ignores: options.dts === true ? [] : [GLOB_DTS],
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "typescript",
...(options.tailwind !== undefined && options.tailwind
? {
plugins: prettierOptions.plugins ?? [],
}
: {}),
},
],
},
});
}
if (options.yaml !== undefined && options.yaml) {
mut_configs.push({
name: "rs:formatter:yaml",
files: [GLOB_YAML],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "yaml",
},
],
},
});
}
if (options.json !== undefined && options.json) {
mut_configs.push({
name: "rs:formatter:json",
files: [GLOB_JSON],
ignores: ["**/package.json"],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "json",
},
],
},
}, {
name: "rs:formatter:jsonc",
files: [GLOB_JSONC],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "jsonc",
},
],
},
}, {
name: "rs:formatter:json5",
files: [GLOB_JSON5],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "json5",
},
],
},
}, {
name: "rs:formatter:packagejson",
files: ["**/package.json"],
languageOptions: {
parser: parserPlain,
},
plugins: {
"package-json": {
meta: {
name: "rs:formatter:packagejson",
},
rules: {
sort: {
meta: {
type: "layout",
fixable: "whitespace",
messages: formattingReporter.messages,
schema: [
{
type: "object",
properties: {
parser: {
type: "string",
required: true,
},
},
additionalProperties: true,
},
],
},
create(context) {
return {
Program() {
if (!("text" in context.sourceCode)) {
return;
}
const sourceCode = context.sourceCode.text;
try {
const formatted = sortPackageJson(sourceCode);
formattingReporter.reportDifferences(context, sourceCode, formatted);
}
catch (error) {
console.error(error);
context.report({
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 0 },
},
message: "Failed to format package.json",
});
}
},
};
},
},
},
},
},
rules: {
"package-json/sort": "error",
},
});
}
if (options.css !== undefined && options.css) {
mut_configs.push({
name: "rs:formatter:css",
files: [GLOB_CSS, GLOB_POSTCSS],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "css",
},
],
},
}, {
name: "rs:formatter:scss",
files: [GLOB_SCSS],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "scss",
},
],
},
}, {
name: "rs:formatter:less",
files: [GLOB_LESS],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "less",
},
],
},
});
}
if (options.html !== undefined && options.html) {
mut_configs.push({
name: "rs:formatter:html",
files: ["**/*.html"],
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "html",
},
],
},
});
}
if (options.markdown !== undefined && options.markdown) {
const GLOB_SLIDEV = options.slidev === undefined || options.slidev === false
? []
: options.slidev === true
? ["**/slides.md"]
: (options.slidev.files ?? []);
mut_configs.push({
name: "rs:formatter:markdown",
files: [GLOB_MARKDOWN],
ignores: GLOB_SLIDEV,
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
embeddedLanguageFormatting: "off",
parser: "markdown",
},
],
},
});
if (options.slidev !== undefined && options.slidev !== false) {
mut_configs.push({
name: "rs:formatter:slidev",
files: GLOB_SLIDEV,
languageOptions: {
parser: parserPlain,
},
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
embeddedLanguageFormatting: "off",
parser: "slidev",
plugins: ["prettier-plugin-slidev"],
},
],
},
});
}
}
if (options.graphql !== undefined && options.graphql) {
mut_configs.push({
files: [GLOB_GRAPHQL],
languageOptions: {
parser: parserPlain,
},
name: "rs:formatter:graphql",
rules: {
...turnOffRulesForPrettier,
"format/prettier": [
"error",
{
...prettierOptions,
parser: "graphql",
},
],
},
});
}
return mut_configs;
}
async function functional(options) {
const { mode, overrides, stylistic, filesTypeAware, functionalEnforcement, ignoreNamePattern,
// ignoreTypePattern,
} = options;
const [pluginFunctional] = (await loadPackages(["eslint-plugin-functional"]));
const strictRules = {
"functional/functional-parameters": "error",
"functional/no-throw-statements": "error",
"functional/no-try-statements": "error",
"functional/no-let": "error",
"functional/no-class-inheritance": "error",
"functional/no-this-expressions": "error",
"functional/no-loop-statements": "error",
"functional/immutable-data": "error",
"functional/prefer-immutable-types": "error",
"functional/type-declaration-immutability": "error",
"functional/no-mixed-types": "error",
"functional/no-conditional-statements": [
"error",
{
ignoreCodePattern: ["import.meta.vitest"],
},
],
"functional/no-expression-statements": "error",
"functional/no-return-void": "error",
"functional/prefer-property-signatures": stylistic === false ? "off" : "error",
"functional/prefer-tacit": stylistic === false ? "off" : "warn",
"functional/readonly-type": stylistic === false ? "off" : "error",
};
const recommendedRules = {
"functional/functional-parameters": [
"error",
{
allowRestParameter: true,
enforceParameterCount: false,
},
],
"functional/no-let": [
"error",
{
allowInForLoopInit: true,
ignoreIdentifierPattern: ignoreNamePattern,
},
],
"functional/no-loop-statements": "error",
"functional/no-throw-statements": [
"error",
{
allowToRejectPromises: true,
},
],
"functional/immutable-data": [
"error",
{
ignoreAccessorPattern: ["**.mut_*.**"],
ignoreClasses: "fieldsOnly",
ignoreImmediateMutation: true,
ignoreNonConstDeclarations: true,
},
],
"functional/no-conditional-statements": [
"error",
{
allowReturningBranches: true,
ignoreCodePattern: ["import.meta.vitest"],
},
],
"functional/no-expression-statements": [
"error",
{
ignoreCodePattern: "^assert",
ignoreSelfReturning: true,
ignoreVoid: true,
},
],
"functional/no-return-void": mode === "library" ? "error" : "off",
"@typescript-eslint/prefer-readonly-parameter-types": "off",
"functional/prefer-immutable-types": [
mode === "library" ? "warn" : "off",
{
enforcement: "None",
overrides: [
{
specifiers: [
{
from: "file",
},
{
from: "lib",
},
],
options: {
ignoreInferredTypes: true,
ignoreNamePattern,
parameters: { enforcement: "ReadonlyShallow" },
},
},
],
suggestions: {
ReadonlyShallow: [
[
{
pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$",
replace: "readonly $1",
message: "Prepend with readonly.",
},
{
pattern: "^(Array|Map|Set)<(.+)>$",
replace: "Readonly$1<$2>",
message: "Use Readonly$1 instead of $1.",
},
],
[
{
pattern: "^(.+)$",
replace: "Readonly<$1>",
message: "Surround with Readonly.",
},
],
],
ReadonlyDeep: [
[
{
pattern: "^(?:Readonly<(.+)>|(.+))$",
replace: "ReadonlyDeep<$1$2>",
message: "Surround with ReadonlyDeep.",
},
],
],
Immutable: [
[
{
pattern: "^(?:Readonly(?:Deep)?<(.+)>|(.+))$",
replace: "Immutable<$1$2>",
message: "Surround with Immutable.",
},
],
],
},
},
],
"functional/type-declaration-immutability": [
"error",
{
rules: [
{
identifiers: "I?Immutable.+",
immutability: "Immutable",
comparator: "AtLeast",
},
{
identifiers: "I?ReadonlyDeep.+",
immutability: "ReadonlyDeep",
comparator: "AtLeast",
},
{
identifiers: "I?Readonly.+",
immutability: "ReadonlyShallow",
comparator: "AtLeast",
fixer: [
{
pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$",
replace: "readonly $1",
},
{
pattern: "^(Array|Map|Set)<(.+)>$",
replace: "Readonly$1<$2>",
},
{
pattern: "^(.+)$",
replace: "Readonly<$1>",
},
],
},
{
identifiers: "I?Mutable.+",
immutability: "Mutable",
comparator: "AtMost",
fixer: [
{
pattern: "^Readonly(Array|Map|Set)<(.+)>$",
replace: "$1<$2>",
},
{
pattern: "^Readonly<(.+)>$",
replace: "$1",
},
],
},
],
},
],
"functional/prefer-property-signatures": stylistic === false ? "off" : "error",
"functional/prefer-tacit": stylistic === false ? "off" : "warn",
"functional/readonly-type": stylistic === false ? "off" : "error",
};
const liteRules = {
...recommendedRules,
"functional/no-conditional-statements": "off",
"functional/no-expression-statements": "off",
"functional/no-return-void": "off",
"functional/prefer-immutable-types": [
mode === "library" ? "warn" : "off",
{
...recommendedRules["functional/prefer-immutable-types"][1],
overrides: [
{
...recommendedRules["functional/prefer-immutable-types"][1].overrides[0],
specifiers: [
{
from: "file",
},
],
},
],
},
],
};
const noneLibraryRules = {
"functional/prefer-immutable-types": liteRules["functional/prefer-immutable-types"],
"functional/type-declaration-immutability": ["warn", liteRules["functional/type-declaration-immutability"][1]],
};
return [
{
name: "rs:functional",
plugins: {
functional: pluginFunctional,
},
settings: {
immutability: {
overrides: [
{ type: "^Readonly<.+>$", to: "ReadonlyShallow" },
{ type: "^ReadonlyDeep<.+>$", to: "ReadonlyDeep" },
{ type: "^Immutable<.+>$", to: "Immutable" },
],
},
},
rules: {
...pluginFunctional.configs.off.rules,
...(functionalEnforcement === "none"
? mode === "library"
? noneLibraryRules
: {}
: functionalEnforcement === "lite"
? liteRules
: functionalEnforcement === "strict"
? strictRules
: recommendedRules),
...overrides,
},
},
{
name: "rs:functional:disable-type-aware",
ignores: filesTypeAware,
rules: pluginFunctional.configs.disableTypeChecked.rules,
},
];
}
async function ignores(options) {
const { ignoreFiles, ignores: ignoresOptions, projectRoot } = options;
const includeIgnoreFile = ignoreFiles.length === 0
? undefined
: await loadPackages(["@eslint/compat"]).then(([p]) => p.includeIgnoreFile);
const [extend, files] = Array.isArray(ignoresOptions)
? [true, ignoresOptions]
: [ignoresOptions.extend, ignoresOptions.files];
const ignoreConfig = {
ignores: extend ? [...GLOB_EXCLUDE, ...files] : [...files],
};
const ignoreFileConfigs = await Promise.all(ignoreFiles.map((file) => {
const filePath = path.resolve(projectRoot, file);
return fs
.access(filePath)
.then(() => includeIgnoreFile(filePath))
.catch(() => {
console.warn(`Ignore file "${filePath}" not found.`);
return null;
});
}));
return [ignoreConfig, ...ignoreFileConfigs.filter((v) => v !== null)];
}
async function imports(options) {
const { stylistic, parserOptions, typescript } = options;
const [pluginImport] = (await loadPackages(typescript
? [
"eslint-plugin-import-x",
"eslint-import-resolver-typescript", // make sure it exists - we only implicitly use it
]
: ["eslint-plugin-import-x"]));
return [
{
name: "rs:imports",
plugins: {
"import-x": pluginImport,
},
settings: {
"import-x/external-module-folders": ["node_modules", "node_modules/@types"],
"import-x/internal-regex": "^(?:#|(?:@|~)\\/).*",
"import-x/extensions": [".ts", ".tsx", ".js", ".jsx"],
"import-x/parsers": {
...(typescript
? {
"@typescript-eslint/parser": [".ts", ".tsx", ".cts", ".mts"],
}
: undefined),
},
"import-x/resolver": {
node: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
...(typescript
? {
typescript: {
alwaysTryTypes: true,
projectService: parserOptions.projectService,
},
}
: undefined),
},
},
rules: {
// "import-x/consistent-type-specifier-style": ["error", "prefer-inline"], -- using ts/consistent-type-imports instead
"import-x/default": "error",
// "import-x/dynamic-import-chunkname": "off",
"import-x/export": "error",
// "import-x/exports-last": "off",
// "import-x/extensions": "off",
"import-x/first": "error",
// "import-x/group-exports": "off",
// "import-x/max-dependencies": [
// "off",
// {
// max: 10,
// },
// ],
"import-x/named": "error",
"import-x/namespace": [
"error",
{
allowComputed: true,
},
],
"import-x/no-absolute-path": "error",
"import-x/no-amd": "error",
// "import-x/no-anonymous-default-export": "off",
// "import-x/no-commonjs": "off",
// "import-x/no-cycle": "off",
// "import-x/no-default-export": "off",
// "import-x/no-deprecated": "warn",
"import-x/no-duplicates": ["error", { "prefer-inline": true }],
// "import-x/no-dynamic-require": "off",
"import-x/no-empty-named-blocks": "error",
// "import-x/no-internal-modules": "off",
"import-x/no-mutable-exports": "error",
// "import-x/no-named-as-default": "off",
// "import-x/no-named-as-default-member": "off",
"import-x/no-named-default": "error",
// "import-x/no-named-export": "off",
// "import-x/no-namespace": "off",
// "import-x/no-nodejs-modules": "off",
// "import-x/no-relative-parent-imports": "off",
// "import-x/no-restricted-paths": "off",
"import-x/no-self-import": "error",
"import-x/no-unassigned-import": "error",
// "import-x/no-unused-modules": "off",
// "import-x/no-unresolved": "off",
"import-x/no-useless-path-segments": [
"error",
{
commonjs: true,
noUselessIndex: true,
},
],
"import-x/no-webpack-loader-syntax": "error",
// "import-x/prefer-default-export": "off",
// "import-x/unambiguous": "off",
"import-x/newline-after-import": [stylistic === false ? "off" : "error", { count: 1 }],
"import-x/order": [
stylistic === false ? "off" : "error",
{
alphabetize: {
caseInsensitive: false,
order: "asc",
},
groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
"newlines-between": "always",
},
],
},
},
{
files: [GLOB_MJS, GLOB_MTS],
rules: {
"import-x/no-commonjs": "error",
"import-x/no-dynamic-require": "error",
},
},
...(typescript
? [
{
files: [GLOB_TS, GLOB_TSX, GLOB_DTS],
rules: {
"import-x/no-unresolved": "off",
"import-x/named": "off",
"import-x/default": "off",
"import-x/namespace": "off",
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/consistent-type-imports": [
stylistic === false ? "off" : "error",
{
prefer: "type-imports",
fixStyle: "inline-type-imports",
disallowTypeAnnotations: false,
},
],
},
},
]
: []),
...(options.mode === "library"
? [
{
files: [GLOB_SRC],
rules: {
// Use non-sloppy imports. See: https://jsr.io/docs/publishing-packages#relative-imports
"import-x/extensions": [
"error",
"always",
{
checkTypeImports: true,
ignorePackages: true,
},
],
"import-x/no-useless-path-segments": ["error", { noUselessIndex: false }],
"import-x/no-extraneous-dependencies": [
"error",
{
bundledDependencies: true,
devDependencies: [
`?(.)*.${GLOB_SRC_EXT}`,
`?(.)config?(s)/**`,
"**/?(.)script?(s)/**",
"**/spec/**",
"**/test?(s)/**",
"**/__mocks__/**",
"**/__tests__/**",
`**/*{.,_}{test,spec}.${GLOB_SRC_EXT}`,
`**/Gruntfile{,.${GLOB_SRC_EXT}`,
`**/gulpfile.${GLOB_SRC_EXT}`,
`**/gulpfile.*.${GLOB_SRC_EXT}`,
`**/jest.config.${GLOB_SRC_EXT}`,
`**/jest.setup.${GLOB_SRC_EXT}`,
`**/karma.conf.${GLOB_SRC_EXT}`,
`**/postcss.config.${GLOB_SRC_EXT}`,
`**/protractor.conf.${GLOB_SRC_EXT}`,
`**/protractor.conf.*.${GLOB_SRC_EXT}`,