eslint-config-setup 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ Most ESLint configs compose rules at runtime from dozens of plugins. That means
16
16
 
17
17
  - **AI guardrails** — a dedicated `ai` mode that enforces what code review can't: explicit types, strict naming, no magic values, complexity limits. Rules that humans find tedious are trivial for an AI to follow. The AI doesn't push back. It just fixes the code.
18
18
  - **OxLint-ready** — a single `oxlint` flag disables every ESLint rule that OxLint already covers, and `getOxlintConfig()` generates a matching OxLint config. No manual migration, no rule conflicts, no coverage gaps. Run both linters and keep the full rule surface.
19
- - **Curated plugin stack, one import** — TypeScript (`strictTypeChecked`), React 19, import cycles, security, browser compat, spell checking, and more. Every rule pre-resolved at build time. No plugin conflicts, no version mismatches.
19
+ - **30 lint integrations, one import** — TypeScript (`strictTypeChecked`), React 19, import cycles, security, browser compat, spell checking, formatter-conflict support, and more. Every rule pre-resolved at build time. No plugin conflicts, no version mismatches.
20
20
 
21
21
  ```typescript
22
22
  // eslint.config.ts
@@ -683,7 +683,6 @@ export default [
683
683
  "minimumCases": 3
684
684
  }
685
685
  ],
686
- "unicorn/prevent-abbreviations": "error",
687
686
  "unused-imports/no-unused-imports": "error",
688
687
  "yoda": "error"
689
688
  },
@@ -864,7 +864,6 @@ export default [
864
864
  "minimumCases": 3
865
865
  }
866
866
  ],
867
- "unicorn/prevent-abbreviations": "error",
868
867
  "unused-imports/no-unused-imports": "error",
869
868
  "yoda": "error"
870
869
  },
@@ -716,7 +716,6 @@ export default [
716
716
  "minimumCases": 3
717
717
  }
718
718
  ],
719
- "unicorn/prevent-abbreviations": "error",
720
719
  "unused-imports/no-unused-imports": "error",
721
720
  "yoda": "error"
722
721
  },
@@ -992,7 +992,6 @@ export default [
992
992
  ],
993
993
  "unicorn/prefer-top-level-await": "error",
994
994
  "unicorn/prefer-type-error": "error",
995
- "unicorn/prevent-abbreviations": "error",
996
995
  "unicorn/relative-url-style": "error",
997
996
  "unicorn/switch-case-braces": "error",
998
997
  "unicorn/text-encoding-identifier-case": "error",
@@ -1250,7 +1250,6 @@ export default [
1250
1250
  ],
1251
1251
  "unicorn/prefer-top-level-await": "error",
1252
1252
  "unicorn/prefer-type-error": "error",
1253
- "unicorn/prevent-abbreviations": "error",
1254
1253
  "unicorn/relative-url-style": "error",
1255
1254
  "unicorn/switch-case-braces": "error",
1256
1255
  "unicorn/text-encoding-identifier-case": "error",
@@ -1025,7 +1025,6 @@ export default [
1025
1025
  ],
1026
1026
  "unicorn/prefer-top-level-await": "error",
1027
1027
  "unicorn/prefer-type-error": "error",
1028
- "unicorn/prevent-abbreviations": "error",
1029
1028
  "unicorn/relative-url-style": "error",
1030
1029
  "unicorn/switch-case-braces": "error",
1031
1030
  "unicorn/text-encoding-identifier-case": "error",
@@ -1280,7 +1280,6 @@ export default [
1280
1280
  ],
1281
1281
  "unicorn/prefer-top-level-await": "error",
1282
1282
  "unicorn/prefer-type-error": "error",
1283
- "unicorn/prevent-abbreviations": "error",
1284
1283
  "unicorn/relative-url-style": "error",
1285
1284
  "unicorn/switch-case-braces": "error",
1286
1285
  "unicorn/text-encoding-identifier-case": "error",
@@ -894,7 +894,6 @@ export default [
894
894
  "minimumCases": 3
895
895
  }
896
896
  ],
897
- "unicorn/prevent-abbreviations": "error",
898
897
  "unused-imports/no-unused-imports": "error",
899
898
  "yoda": "error"
900
899
  },
package/dist/index.js CHANGED
@@ -112,21 +112,31 @@ function oxlintOptionsToFilename(opts) {
112
112
  }
113
113
 
114
114
  // src/loader.ts
115
- import { readFileSync } from "fs";
115
+ import { existsSync, readFileSync } from "fs";
116
116
  import path from "path";
117
+ import { pathToFileURL } from "url";
117
118
  async function getEslintConfig(opts = {}) {
118
119
  const filename = optionsToFilename(opts);
119
120
  const dirname = import.meta.dirname;
120
121
  const configPath = path.join(dirname, "configs", filename);
122
+ if (!existsSync(configPath)) {
123
+ throwMissingConfigError(opts, filename);
124
+ }
125
+ const configUrl = pathToFileURL(configPath);
126
+ configUrl.search = String(Date.now());
121
127
  try {
122
128
  const module = await import(
123
129
  /* webpackIgnore: true */
124
- `${configPath}?${Date.now()}`
130
+ configUrl.href
125
131
  );
126
132
  return module.default;
127
- } catch {
133
+ } catch (error) {
134
+ if (!existsSync(configPath)) {
135
+ throwMissingConfigError(opts, filename, error);
136
+ }
128
137
  throw new Error(
129
- `eslint-config-setup: No pre-generated config found for options ${JSON.stringify(opts)}. Expected file: configs/${filename}. Run "npm run generate" in the package to build configs.`
138
+ `eslint-config-setup: Pre-generated config failed to load for options ${JSON.stringify(opts)}. Expected file: configs/${filename}. Original error: ${getErrorMessage(error)}`,
139
+ { cause: error }
130
140
  );
131
141
  }
132
142
  }
@@ -143,6 +153,16 @@ function getOxlintConfig(opts = {}) {
143
153
  );
144
154
  }
145
155
  }
156
+ function getErrorMessage(error) {
157
+ return error instanceof Error ? error.message : String(error);
158
+ }
159
+ function throwMissingConfigError(opts, filename, cause) {
160
+ const message = `eslint-config-setup: No pre-generated config found for options ${JSON.stringify(opts)}. Expected file: configs/${filename}. Run "npm run generate" in the package to build configs.`;
161
+ if (cause === void 0) {
162
+ throw new Error(message);
163
+ }
164
+ throw new Error(message, { cause });
165
+ }
146
166
  export {
147
167
  addRule,
148
168
  bitmaskToHash,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api/rule-helpers.ts","../src/hash.ts","../src/loader.ts"],"sourcesContent":["/* eslint-disable max-params, complexity -- Public API: config + ruleName + severity + options is the natural signature. Options objects would hurt ergonomics for one-liner calls. */\nimport type { Linter } from \"eslint\"\n\nimport type {\n FlatConfigArray,\n RuleOptions,\n RuleSeverity,\n} from \"../types\"\n\nconst CONFIG_PREFIX = \"eslint-config-setup/\"\n\n/** Maps user-facing scope names to config block name segments. */\nconst SCOPE_TO_BLOCK: Record<string, string> = {\n configs: \"config-files\",\n}\n\n/**\n * Check if a config block matches a given scope.\n * A block matches if its name is `eslint-config-setup/{segment}` or starts with `eslint-config-setup/{segment}-`.\n */\nfunction blockMatchesScope(\n block: Linter.Config,\n scope: string,\n): boolean {\n const name = block.name\n if (name == null) return false\n const segment = SCOPE_TO_BLOCK[scope] ?? scope\n const target = `${CONFIG_PREFIX}${segment}`\n return name === target || name.startsWith(`${target}-`)\n}\n\n/**\n * Change the severity of a rule across all config blocks, preserving options.\n */\nexport function setRuleSeverity(\n config: FlatConfigArray,\n ruleName: string,\n severity: RuleSeverity,\n options?: RuleOptions,\n): void {\n for (const block of config) {\n if (options?.scope !== undefined && !blockMatchesScope(block, options.scope)) continue\n if (block.rules?.[ruleName] == null) continue\n\n const current = block.rules[ruleName]\n if (Array.isArray(current)) {\n block.rules[ruleName] = [severity, ...current.slice(1)] as Linter.RuleEntry\n } else {\n block.rules[ruleName] = severity\n }\n }\n}\n\n/**\n * Update the options of a rule across all config blocks, preserving severity.\n */\nexport function configureRule(\n config: FlatConfigArray,\n ruleName: string,\n options: unknown[],\n ruleOptions?: RuleOptions,\n): void {\n for (const block of config) {\n if (ruleOptions?.scope !== undefined && !blockMatchesScope(block, ruleOptions.scope))\n continue\n if (block.rules?.[ruleName] == null) continue\n\n const current = block.rules[ruleName]\n const severity = Array.isArray(current) ? current[0] : current\n block.rules[ruleName] = [severity, ...options] as Linter.RuleEntry\n }\n}\n\n/**\n * Completely disable a rule across all config blocks.\n * With scope: disables in the first matching block (creates entry if needed).\n */\nexport function disableRule(\n config: FlatConfigArray,\n ruleName: string,\n options?: RuleOptions,\n): void {\n if (options?.scope != null) {\n const { scope } = options\n const block = config.find((b) => blockMatchesScope(b, scope))\n if (!block) return\n block.rules ??= {}\n block.rules[ruleName] = \"off\"\n return\n }\n\n for (const block of config) {\n if (block.rules?.[ruleName] == null) continue\n block.rules[ruleName] = \"off\"\n }\n}\n\n/**\n * Add a new rule to the first (base) config block.\n * With scope: adds to the first matching block instead.\n */\nexport function addRule(\n config: FlatConfigArray,\n ruleName: string,\n severity: RuleSeverity,\n options?: RuleOptions | unknown[],\n ruleOptions?: RuleOptions,\n): void {\n // Handle overloaded signatures: addRule(config, rule, severity, options?, ruleOptions?)\n // When options is a plain object with scope, it's actually ruleOptions\n let ruleOpts: undefined | unknown[]\n let scopeOpts: RuleOptions | undefined\n\n if (Array.isArray(options)) {\n ruleOpts = options\n scopeOpts = ruleOptions\n } else if (options && typeof options === \"object\" && \"scope\" in options) {\n scopeOpts = options\n } else if (options === undefined) {\n scopeOpts = ruleOptions\n }\n\n let target: Linter.Config | undefined\n if (scopeOpts?.scope != null) {\n const { scope } = scopeOpts\n target = config.find((b) => blockMatchesScope(b, scope))\n } else {\n target = config[0]\n }\n\n if (!target) return\n\n target.rules ??= {}\n target.rules[ruleName] = ruleOpts\n ? ([severity, ...ruleOpts] as Linter.RuleEntry)\n : severity\n}\n\n/**\n * Disable all rules except the specified one — useful for debugging.\n */\nexport function disableAllRulesBut(\n config: FlatConfigArray,\n keepRuleName: string,\n): void {\n for (const block of config) {\n if (!block.rules) continue\n for (const ruleName of Object.keys(block.rules)) {\n if (ruleName !== keepRuleName) {\n block.rules[ruleName] = \"off\"\n }\n }\n }\n}\n","import { createHash } from \"node:crypto\"\n\nimport type { ConfigOptions, OxlintConfigOptions } from \"./types\"\n\n/**\n * Converts config options to a deterministic bitmask.\n * Bit order is fixed and must never change (would break published configs).\n */\nexport function optionsToBitmask(opts: ConfigOptions): number {\n let mask = 0\n if (opts.react) mask |= 1 << 0\n if (opts.node) mask |= 1 << 1\n if (opts.ai) mask |= 1 << 2\n if (opts.oxlint) mask |= 1 << 3\n return mask\n}\n\n/**\n * Converts a bitmask to a short deterministic hash (8 hex chars).\n * The same hash is produced at build-time (generate) and run-time (getConfig).\n */\nexport function bitmaskToHash(mask: number): string {\n const input = `effective-eslint-config:${mask}`\n return createHash(\"sha1\").update(input).digest(\"hex\").slice(0, 8)\n}\n\n/** Convenience: options → filename (without path prefix). */\nexport function optionsToFilename(opts: ConfigOptions): string {\n return `${bitmaskToHash(optionsToBitmask(opts))}.js`\n}\n\n/** Total number of permutations (2^4 = 16). */\nexport const TOTAL_PERMUTATIONS = 16\n\n/** Iterate all possible option combinations. */\nexport function* allPermutations(): Generator<ConfigOptions> {\n for (let mask = 0; mask < TOTAL_PERMUTATIONS; mask++) {\n yield {\n react: Boolean(mask & (1 << 0)),\n node: Boolean(mask & (1 << 1)),\n ai: Boolean(mask & (1 << 2)),\n oxlint: Boolean(mask & (1 << 3)),\n }\n }\n}\n\n// --- OxLint config permutations (3 flags → 8 combos) ---\n\n/** Converts OxLint-relevant options to a 3-bit bitmask (react, node, ai). */\nexport function oxlintOptionsToBitmask(opts: OxlintConfigOptions): number {\n let mask = 0\n if (opts.react) mask |= 1 << 0\n if (opts.node) mask |= 1 << 1\n if (opts.ai) mask |= 1 << 2\n return mask\n}\n\n/** Deterministic hash for OxLint configs (different salt to avoid collisions). */\nexport function oxlintBitmaskToHash(mask: number): string {\n const input = `eslint-config-setup-oxlint:${mask}`\n return createHash(\"sha1\").update(input).digest(\"hex\").slice(0, 8)\n}\n\n/** OxLint options → filename (without path prefix). */\nexport function oxlintOptionsToFilename(opts: OxlintConfigOptions): string {\n return `${oxlintBitmaskToHash(oxlintOptionsToBitmask(opts))}.json`\n}\n\n/** Total number of OxLint permutations (2^3 = 8). */\nexport const TOTAL_OXLINT_PERMUTATIONS = 8\n\n/** Iterate all OxLint option combinations. */\nexport function* allOxlintPermutations(): Generator<OxlintConfigOptions> {\n for (let mask = 0; mask < TOTAL_OXLINT_PERMUTATIONS; mask++) {\n yield {\n react: Boolean(mask & (1 << 0)),\n node: Boolean(mask & (1 << 1)),\n ai: Boolean(mask & (1 << 2)),\n }\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename, @typescript-eslint/no-unsafe-type-assertion -- Paths are computed from deterministic hashes in dist/, not from user input. */\nimport { readFileSync } from \"node:fs\"\nimport path from \"node:path\"\n\nimport type { ConfigOptions, FlatConfigArray, OxlintConfigOptions, OxlintConfigResult } from \"./types\"\n\nimport { optionsToFilename, oxlintOptionsToFilename } from \"./hash\"\n\n/**\n * Loads a pre-generated ESLint config by dynamically importing the hashed file.\n * The hash is computed from the options using the same algorithm as the build.\n */\nexport async function getEslintConfig(\n opts: ConfigOptions = {},\n): Promise<FlatConfigArray> {\n const filename = optionsToFilename(opts)\n const dirname = import.meta.dirname\n const configPath = path.join(dirname, \"configs\", filename)\n\n try {\n const module = (await import(\n /* webpackIgnore: true */\n `${configPath}?${Date.now()}`\n )) as { default: FlatConfigArray }\n return module.default\n } catch {\n throw new Error(\n `eslint-config-setup: No pre-generated config found for options ${JSON.stringify(opts)}. ` +\n `Expected file: configs/${filename}. Run \"npm run generate\" in the package to build configs.`,\n )\n }\n}\n\n/**\n * Loads a pre-generated OxLint config from the hashed JSON file.\n * Only `react`, `node`, and `ai` flags are relevant — `oxlint` is ignored.\n */\nexport function getOxlintConfig(\n opts: OxlintConfigOptions = {},\n): OxlintConfigResult {\n const filename = oxlintOptionsToFilename(opts)\n const dirname = import.meta.dirname\n const configPath = path.join(dirname, \"oxlint-configs\", filename)\n\n try {\n const content = readFileSync(configPath, \"utf8\")\n return JSON.parse(content) as OxlintConfigResult\n } catch {\n throw new Error(\n `eslint-config-setup: No pre-generated OxLint config found for options ${JSON.stringify(opts)}. ` +\n `Expected file: oxlint-configs/${filename}. Run \"npm run generate\" in the package to build configs.`,\n )\n }\n}\n"],"mappings":";AASA,IAAM,gBAAgB;AAGtB,IAAM,iBAAyC;AAAA,EAC7C,SAAS;AACX;AAMA,SAAS,kBACP,OACA,OACS;AACT,QAAM,OAAO,MAAM;AACnB,MAAI,QAAQ,KAAM,QAAO;AACzB,QAAM,UAAU,eAAe,KAAK,KAAK;AACzC,QAAM,SAAS,GAAG,aAAa,GAAG,OAAO;AACzC,SAAO,SAAS,UAAU,KAAK,WAAW,GAAG,MAAM,GAAG;AACxD;AAKO,SAAS,gBACd,QACA,UACA,UACA,SACM;AACN,aAAW,SAAS,QAAQ;AAC1B,QAAI,SAAS,UAAU,UAAa,CAAC,kBAAkB,OAAO,QAAQ,KAAK,EAAG;AAC9E,QAAI,MAAM,QAAQ,QAAQ,KAAK,KAAM;AAErC,UAAM,UAAU,MAAM,MAAM,QAAQ;AACpC,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,YAAM,MAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IACxD,OAAO;AACL,YAAM,MAAM,QAAQ,IAAI;AAAA,IAC1B;AAAA,EACF;AACF;AAKO,SAAS,cACd,QACA,UACA,SACA,aACM;AACN,aAAW,SAAS,QAAQ;AAC1B,QAAI,aAAa,UAAU,UAAa,CAAC,kBAAkB,OAAO,YAAY,KAAK;AACjF;AACF,QAAI,MAAM,QAAQ,QAAQ,KAAK,KAAM;AAErC,UAAM,UAAU,MAAM,MAAM,QAAQ;AACpC,UAAM,WAAW,MAAM,QAAQ,OAAO,IAAI,QAAQ,CAAC,IAAI;AACvD,UAAM,MAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,OAAO;AAAA,EAC/C;AACF;AAMO,SAAS,YACd,QACA,UACA,SACM;AACN,MAAI,SAAS,SAAS,MAAM;AAC1B,UAAM,EAAE,MAAM,IAAI;AAClB,UAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAC5D,QAAI,CAAC,MAAO;AACZ,UAAM,UAAU,CAAC;AACjB,UAAM,MAAM,QAAQ,IAAI;AACxB;AAAA,EACF;AAEA,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,QAAQ,QAAQ,KAAK,KAAM;AACrC,UAAM,MAAM,QAAQ,IAAI;AAAA,EAC1B;AACF;AAMO,SAAS,QACd,QACA,UACA,UACA,SACA,aACM;AAGN,MAAI;AACJ,MAAI;AAEJ,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW;AACX,gBAAY;AAAA,EACd,WAAW,WAAW,OAAO,YAAY,YAAY,WAAW,SAAS;AACvE,gBAAY;AAAA,EACd,WAAW,YAAY,QAAW;AAChC,gBAAY;AAAA,EACd;AAEA,MAAI;AACJ,MAAI,WAAW,SAAS,MAAM;AAC5B,UAAM,EAAE,MAAM,IAAI;AAClB,aAAS,OAAO,KAAK,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAAA,EACzD,OAAO;AACL,aAAS,OAAO,CAAC;AAAA,EACnB;AAEA,MAAI,CAAC,OAAQ;AAEb,SAAO,UAAU,CAAC;AAClB,SAAO,MAAM,QAAQ,IAAI,WACpB,CAAC,UAAU,GAAG,QAAQ,IACvB;AACN;AAKO,SAAS,mBACd,QACA,cACM;AACN,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,MAAM,MAAO;AAClB,eAAW,YAAY,OAAO,KAAK,MAAM,KAAK,GAAG;AAC/C,UAAI,aAAa,cAAc;AAC7B,cAAM,MAAM,QAAQ,IAAI;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACF;;;ACzJA,SAAS,kBAAkB;AAQpB,SAAS,iBAAiB,MAA6B;AAC5D,MAAI,OAAO;AACX,MAAI,KAAK,MAAO,SAAQ,KAAK;AAC7B,MAAI,KAAK,KAAM,SAAQ,KAAK;AAC5B,MAAI,KAAK,GAAI,SAAQ,KAAK;AAC1B,MAAI,KAAK,OAAQ,SAAQ,KAAK;AAC9B,SAAO;AACT;AAMO,SAAS,cAAc,MAAsB;AAClD,QAAM,QAAQ,2BAA2B,IAAI;AAC7C,SAAO,WAAW,MAAM,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AAClE;AAGO,SAAS,kBAAkB,MAA6B;AAC7D,SAAO,GAAG,cAAc,iBAAiB,IAAI,CAAC,CAAC;AACjD;AAoBO,SAAS,uBAAuB,MAAmC;AACxE,MAAI,OAAO;AACX,MAAI,KAAK,MAAO,SAAQ,KAAK;AAC7B,MAAI,KAAK,KAAM,SAAQ,KAAK;AAC5B,MAAI,KAAK,GAAI,SAAQ,KAAK;AAC1B,SAAO;AACT;AAGO,SAAS,oBAAoB,MAAsB;AACxD,QAAM,QAAQ,8BAA8B,IAAI;AAChD,SAAO,WAAW,MAAM,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AAClE;AAGO,SAAS,wBAAwB,MAAmC;AACzE,SAAO,GAAG,oBAAoB,uBAAuB,IAAI,CAAC,CAAC;AAC7D;;;ACjEA,SAAS,oBAAoB;AAC7B,OAAO,UAAU;AAUjB,eAAsB,gBACpB,OAAsB,CAAC,GACG;AAC1B,QAAM,WAAW,kBAAkB,IAAI;AACvC,QAAM,UAAU,YAAY;AAC5B,QAAM,aAAa,KAAK,KAAK,SAAS,WAAW,QAAQ;AAEzD,MAAI;AACF,UAAM,SAAU,MAAM;AAAA;AAAA,MAEpB,GAAG,UAAU,IAAI,KAAK,IAAI,CAAC;AAAA;AAE7B,WAAO,OAAO;AAAA,EAChB,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,kEAAkE,KAAK,UAAU,IAAI,CAAC,4BAC1D,QAAQ;AAAA,IACtC;AAAA,EACF;AACF;AAMO,SAAS,gBACd,OAA4B,CAAC,GACT;AACpB,QAAM,WAAW,wBAAwB,IAAI;AAC7C,QAAM,UAAU,YAAY;AAC5B,QAAM,aAAa,KAAK,KAAK,SAAS,kBAAkB,QAAQ;AAEhE,MAAI;AACF,UAAM,UAAU,aAAa,YAAY,MAAM;AAC/C,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,yEAAyE,KAAK,UAAU,IAAI,CAAC,mCAC1D,QAAQ;AAAA,IAC7C;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/api/rule-helpers.ts","../src/hash.ts","../src/loader.ts"],"sourcesContent":["/* eslint-disable max-params, complexity -- Public API: config + ruleName + severity + options is the natural signature. Options objects would hurt ergonomics for one-liner calls. */\nimport type { Linter } from \"eslint\"\n\nimport type {\n FlatConfigArray,\n RuleOptions,\n RuleSeverity,\n} from \"../types\"\n\nconst CONFIG_PREFIX = \"eslint-config-setup/\"\n\n/** Maps user-facing scope names to config block name segments. */\nconst SCOPE_TO_BLOCK: Record<string, string> = {\n configs: \"config-files\",\n}\n\n/**\n * Check if a config block matches a given scope.\n * A block matches if its name is `eslint-config-setup/{segment}` or starts with `eslint-config-setup/{segment}-`.\n */\nfunction blockMatchesScope(\n block: Linter.Config,\n scope: string,\n): boolean {\n const name = block.name\n if (name == null) return false\n const segment = SCOPE_TO_BLOCK[scope] ?? scope\n const target = `${CONFIG_PREFIX}${segment}`\n return name === target || name.startsWith(`${target}-`)\n}\n\n/**\n * Change the severity of a rule across all config blocks, preserving options.\n */\nexport function setRuleSeverity(\n config: FlatConfigArray,\n ruleName: string,\n severity: RuleSeverity,\n options?: RuleOptions,\n): void {\n for (const block of config) {\n if (options?.scope !== undefined && !blockMatchesScope(block, options.scope)) continue\n if (block.rules?.[ruleName] == null) continue\n\n const current = block.rules[ruleName]\n if (Array.isArray(current)) {\n block.rules[ruleName] = [severity, ...current.slice(1)] as Linter.RuleEntry\n } else {\n block.rules[ruleName] = severity\n }\n }\n}\n\n/**\n * Update the options of a rule across all config blocks, preserving severity.\n */\nexport function configureRule(\n config: FlatConfigArray,\n ruleName: string,\n options: unknown[],\n ruleOptions?: RuleOptions,\n): void {\n for (const block of config) {\n if (ruleOptions?.scope !== undefined && !blockMatchesScope(block, ruleOptions.scope))\n continue\n if (block.rules?.[ruleName] == null) continue\n\n const current = block.rules[ruleName]\n const severity = Array.isArray(current) ? current[0] : current\n block.rules[ruleName] = [severity, ...options] as Linter.RuleEntry\n }\n}\n\n/**\n * Completely disable a rule across all config blocks.\n * With scope: disables in the first matching block (creates entry if needed).\n */\nexport function disableRule(\n config: FlatConfigArray,\n ruleName: string,\n options?: RuleOptions,\n): void {\n if (options?.scope != null) {\n const { scope } = options\n const block = config.find((b) => blockMatchesScope(b, scope))\n if (!block) return\n block.rules ??= {}\n block.rules[ruleName] = \"off\"\n return\n }\n\n for (const block of config) {\n if (block.rules?.[ruleName] == null) continue\n block.rules[ruleName] = \"off\"\n }\n}\n\n/**\n * Add a new rule to the first (base) config block.\n * With scope: adds to the first matching block instead.\n */\nexport function addRule(\n config: FlatConfigArray,\n ruleName: string,\n severity: RuleSeverity,\n options?: RuleOptions | unknown[],\n ruleOptions?: RuleOptions,\n): void {\n // Handle overloaded signatures: addRule(config, rule, severity, options?, ruleOptions?)\n // When options is a plain object with scope, it's actually ruleOptions\n let ruleOpts: undefined | unknown[]\n let scopeOpts: RuleOptions | undefined\n\n if (Array.isArray(options)) {\n ruleOpts = options\n scopeOpts = ruleOptions\n } else if (options && typeof options === \"object\" && \"scope\" in options) {\n scopeOpts = options\n } else if (options === undefined) {\n scopeOpts = ruleOptions\n }\n\n let target: Linter.Config | undefined\n if (scopeOpts?.scope != null) {\n const { scope } = scopeOpts\n target = config.find((b) => blockMatchesScope(b, scope))\n } else {\n target = config[0]\n }\n\n if (!target) return\n\n target.rules ??= {}\n target.rules[ruleName] = ruleOpts\n ? ([severity, ...ruleOpts] as Linter.RuleEntry)\n : severity\n}\n\n/**\n * Disable all rules except the specified one — useful for debugging.\n */\nexport function disableAllRulesBut(\n config: FlatConfigArray,\n keepRuleName: string,\n): void {\n for (const block of config) {\n if (!block.rules) continue\n for (const ruleName of Object.keys(block.rules)) {\n if (ruleName !== keepRuleName) {\n block.rules[ruleName] = \"off\"\n }\n }\n }\n}\n","import { createHash } from \"node:crypto\"\n\nimport type { ConfigOptions, OxlintConfigOptions } from \"./types\"\n\n/**\n * Converts config options to a deterministic bitmask.\n * Bit order is fixed and must never change (would break published configs).\n */\nexport function optionsToBitmask(opts: ConfigOptions): number {\n let mask = 0\n if (opts.react) mask |= 1 << 0\n if (opts.node) mask |= 1 << 1\n if (opts.ai) mask |= 1 << 2\n if (opts.oxlint) mask |= 1 << 3\n return mask\n}\n\n/**\n * Converts a bitmask to a short deterministic hash (8 hex chars).\n * The same hash is produced at build-time (generate) and run-time (getConfig).\n */\nexport function bitmaskToHash(mask: number): string {\n const input = `effective-eslint-config:${mask}`\n return createHash(\"sha1\").update(input).digest(\"hex\").slice(0, 8)\n}\n\n/** Convenience: options → filename (without path prefix). */\nexport function optionsToFilename(opts: ConfigOptions): string {\n return `${bitmaskToHash(optionsToBitmask(opts))}.js`\n}\n\n/** Total number of permutations (2^4 = 16). */\nexport const TOTAL_PERMUTATIONS = 16\n\n/** Iterate all possible option combinations. */\nexport function* allPermutations(): Generator<ConfigOptions> {\n for (let mask = 0; mask < TOTAL_PERMUTATIONS; mask++) {\n yield {\n react: Boolean(mask & (1 << 0)),\n node: Boolean(mask & (1 << 1)),\n ai: Boolean(mask & (1 << 2)),\n oxlint: Boolean(mask & (1 << 3)),\n }\n }\n}\n\n// --- OxLint config permutations (3 flags → 8 combos) ---\n\n/** Converts OxLint-relevant options to a 3-bit bitmask (react, node, ai). */\nexport function oxlintOptionsToBitmask(opts: OxlintConfigOptions): number {\n let mask = 0\n if (opts.react) mask |= 1 << 0\n if (opts.node) mask |= 1 << 1\n if (opts.ai) mask |= 1 << 2\n return mask\n}\n\n/** Deterministic hash for OxLint configs (different salt to avoid collisions). */\nexport function oxlintBitmaskToHash(mask: number): string {\n const input = `eslint-config-setup-oxlint:${mask}`\n return createHash(\"sha1\").update(input).digest(\"hex\").slice(0, 8)\n}\n\n/** OxLint options → filename (without path prefix). */\nexport function oxlintOptionsToFilename(opts: OxlintConfigOptions): string {\n return `${oxlintBitmaskToHash(oxlintOptionsToBitmask(opts))}.json`\n}\n\n/** Total number of OxLint permutations (2^3 = 8). */\nexport const TOTAL_OXLINT_PERMUTATIONS = 8\n\n/** Iterate all OxLint option combinations. */\nexport function* allOxlintPermutations(): Generator<OxlintConfigOptions> {\n for (let mask = 0; mask < TOTAL_OXLINT_PERMUTATIONS; mask++) {\n yield {\n react: Boolean(mask & (1 << 0)),\n node: Boolean(mask & (1 << 1)),\n ai: Boolean(mask & (1 << 2)),\n }\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename, @typescript-eslint/no-unsafe-type-assertion -- Paths are computed from deterministic hashes in dist/, not from user input. */\nimport { existsSync, readFileSync } from \"node:fs\"\nimport path from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\n\nimport type { ConfigOptions, FlatConfigArray, OxlintConfigOptions, OxlintConfigResult } from \"./types\"\n\nimport { optionsToFilename, oxlintOptionsToFilename } from \"./hash\"\n\n/**\n * Loads a pre-generated ESLint config by dynamically importing the hashed file.\n * The hash is computed from the options using the same algorithm as the build.\n */\nexport async function getEslintConfig(\n opts: ConfigOptions = {},\n): Promise<FlatConfigArray> {\n const filename = optionsToFilename(opts)\n const dirname = import.meta.dirname\n const configPath = path.join(dirname, \"configs\", filename)\n\n if (!existsSync(configPath)) {\n throwMissingConfigError(opts, filename)\n }\n\n const configUrl = pathToFileURL(configPath)\n configUrl.search = String(Date.now())\n\n try {\n const module = (await import(\n /* webpackIgnore: true */\n configUrl.href\n )) as { default: FlatConfigArray }\n return module.default\n } catch (error) {\n if (!existsSync(configPath)) {\n throwMissingConfigError(opts, filename, error)\n }\n\n throw new Error(\n `eslint-config-setup: Pre-generated config failed to load for options ${JSON.stringify(opts)}. ` +\n `Expected file: configs/${filename}. Original error: ${getErrorMessage(error)}`,\n { cause: error },\n )\n }\n}\n\n/**\n * Loads a pre-generated OxLint config from the hashed JSON file.\n * Only `react`, `node`, and `ai` flags are relevant — `oxlint` is ignored.\n */\nexport function getOxlintConfig(\n opts: OxlintConfigOptions = {},\n): OxlintConfigResult {\n const filename = oxlintOptionsToFilename(opts)\n const dirname = import.meta.dirname\n const configPath = path.join(dirname, \"oxlint-configs\", filename)\n\n try {\n const content = readFileSync(configPath, \"utf8\")\n return JSON.parse(content) as OxlintConfigResult\n } catch {\n throw new Error(\n `eslint-config-setup: No pre-generated OxLint config found for options ${JSON.stringify(opts)}. ` +\n `Expected file: oxlint-configs/${filename}. Run \"npm run generate\" in the package to build configs.`,\n )\n }\n}\n\nfunction getErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction throwMissingConfigError(\n opts: ConfigOptions,\n filename: string,\n cause?: unknown,\n): never {\n const message =\n `eslint-config-setup: No pre-generated config found for options ${JSON.stringify(opts)}. ` +\n `Expected file: configs/${filename}. Run \"npm run generate\" in the package to build configs.`\n\n if (cause === undefined) {\n throw new Error(message)\n }\n\n throw new Error(message, { cause })\n}\n"],"mappings":"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","names":[]}
package/dist/modules.js CHANGED
@@ -1923,7 +1923,6 @@ function unicornConfig(opts) {
1923
1923
  builder.addRule("unicorn/no-for-each", "error");
1924
1924
  builder.addRule("unicorn/prefer-ternary", ["error", "only-single-line"]);
1925
1925
  builder.addRule("unicorn/prefer-switch", ["error", { minimumCases: 3 }]);
1926
- builder.addRule("unicorn/prevent-abbreviations", "error");
1927
1926
  builder.addRule("unicorn/no-useless-switch-case", "error");
1928
1927
  builder.addRule("unicorn/custom-error-definition", "error");
1929
1928
  builder.addRule("unicorn/prefer-default-parameters", "error");