@codacy/tools-eslint-9 0.6.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +458 -38
  2. package/package.json +17 -17
package/dist/index.js CHANGED
@@ -37,8 +37,8 @@ var import_tooling2 = require("@codacy/tooling");
37
37
 
38
38
  // src/adapter.ts
39
39
  var import_tooling = require("@codacy/tooling");
40
- var import_path = __toESM(require("path"));
41
- var import_fs = __toESM(require("fs"));
40
+ var import_path3 = __toESM(require("path"));
41
+ var import_fs3 = __toESM(require("fs"));
42
42
 
43
43
  // src/patterns.json
44
44
  var patterns_default = [
@@ -28427,15 +28427,55 @@ var patterns_default = [
28427
28427
  }
28428
28428
  ];
28429
28429
 
28430
+ // src/config-builder.ts
28431
+ var import_fs2 = __toESM(require("fs"));
28432
+ var import_path2 = __toESM(require("path"));
28433
+
28430
28434
  // src/blacklist.ts
28435
+ var import_fs = __toESM(require("fs"));
28436
+ var import_path = __toESM(require("path"));
28437
+ var BLACKLISTED_RULE_PATTERNS = [
28438
+ // `eslint-plugin-unused-imports` ships duplicate `-ts` variants (e.g.
28439
+ // `no-unused-imports-ts`) that are meant for codebases NOT using the
28440
+ // `@typescript-eslint` plugin. In our config `@typescript-eslint/
28441
+ // no-unused-vars` is always available for TS files, so the `-ts`
28442
+ // variants are redundant and conflict with it.
28443
+ /^unused-imports\/.*-ts$/,
28444
+ // `eslint-plugin-angular` targets AngularJS 1.x (not Angular 2+) and
28445
+ // also calls removed ESLint 9 APIs in several rules
28446
+ // (`angular/component-limit`, `angular/di*`, `angular/dumb-inject`,
28447
+ // `angular/no-directive-replace`, `angular/no-run-logic`,
28448
+ // `angular/prefer-component`, `angular/service-name`, …). The modern
28449
+ // `@angular-eslint/*` plugin (Angular 2+) is unaffected and stays
28450
+ // enabled.
28451
+ /^angular\//,
28452
+ // `eslint-plugin-eslint-plugin` targets ESLint plugin authors. Several
28453
+ // of its rules (`no-property-in-node`, …) also call removed AST
28454
+ // utilities and fail on ESLint 9. Not useful for end-user code
28455
+ // analysis either.
28456
+ /^eslint-plugin\//
28457
+ ];
28431
28458
  var BLACKLISTED_RULES = /* @__PURE__ */ new Set([
28432
- // --- Plugin rules that crash or misbehave ---
28433
- "@lwc/lwc/no-unexpected-wire-adapter-usages",
28434
- "@lwc/lwc/no-unknown-wire-adapters",
28459
+ // --- Plugin rules that crash on ESLint 9 (but aren't covered by a
28460
+ // blanket namespace above) ---------------------------------------
28461
+ // `@typescript-eslint/consistent-type-imports` is incompatible with
28462
+ // the `verbatimModuleSyntax` tsconfig flag (now the recommended TS
28463
+ // setting) and emits conflicting auto-fixes when both are enabled.
28435
28464
  "@typescript-eslint/consistent-type-imports",
28436
- "angular/service-name",
28437
- "ember/no-restricted-property-modifications",
28465
+ // `ember/template-indent` reformats Handlebars templates in a way that
28466
+ // fights both the ember-template-lint formatter and Prettier. Source
28467
+ // of persistent whitespace false positives; preserved from the
28468
+ // codacy-eslint blacklist history.
28438
28469
  "ember/template-indent",
28470
+ // --- eslint-plugin-functional family ------------------------------
28471
+ //
28472
+ // The `functional` plugin enforces an extremely strict pure-FP style
28473
+ // (no mutation, no `let`, no throw, no void return, …). On general
28474
+ // JavaScript/TypeScript codebases every one of these rules produces
28475
+ // a flood of false positives that drowns out every other finding.
28476
+ // They are only appropriate for codebases that have opted into the
28477
+ // plugin explicitly — which users can do via their own local
28478
+ // `eslint.config.js` + `useLocalConfigurationFile`.
28439
28479
  "functional/functional-parameters",
28440
28480
  "functional/immutable-data",
28441
28481
  "functional/no-conditional-statements",
@@ -28447,8 +28487,19 @@ var BLACKLISTED_RULES = /* @__PURE__ */ new Set([
28447
28487
  "functional/no-try-statements",
28448
28488
  "functional/prefer-immutable-types",
28449
28489
  "functional/type-declaration-immutability",
28490
+ // `yml/sort-sequence-values` is the YAML counterpart of `jsonc/sort-
28491
+ // array-values` — it requires a non-empty options array (pathPattern
28492
+ // + order) and crashes without one. Listed as unconditional because
28493
+ // patterns.json does not surface a parameter schema that lets us
28494
+ // tell "user supplied" from "default" for this rule; revisit if a
28495
+ // user complains.
28450
28496
  "yml/sort-sequence-values",
28451
- // --- Core rules that crash or conflict with @stylistic equivalents ---
28497
+ // --- Core formatting rules conflicting with @stylistic ------------
28498
+ //
28499
+ // When both a core layout rule and its `@stylistic/*` equivalent are
28500
+ // enabled, they fight each other (same AST, different fixers). We
28501
+ // prefer the `@stylistic` versions because they track ESLint's
28502
+ // modern layout semantics and handle TS syntax natively.
28452
28503
  "arrow-parens",
28453
28504
  "indent",
28454
28505
  "computed-property-spacing",
@@ -28458,15 +28509,125 @@ var BLACKLISTED_RULES = /* @__PURE__ */ new Set([
28458
28509
  "semi",
28459
28510
  "comma-dangle",
28460
28511
  "no-mixed-spaces-and-tabs",
28461
- // --- @typescript-eslint formatting rules that conflict with @stylistic ---
28512
+ // `@typescript-eslint/brace-style` and `@typescript-eslint/indent` are
28513
+ // also deprecated in favour of the `@stylistic` equivalents for the
28514
+ // same reason.
28462
28515
  "@typescript-eslint/brace-style",
28463
28516
  "@typescript-eslint/indent"
28464
28517
  ]);
28465
- var BLACKLISTED_RULE_PATTERNS = [/^unused-imports\/.*-ts$/];
28518
+ var requireAnyUserParam = (pattern) => !pattern.parameters || Object.keys(pattern.parameters).length === 0;
28519
+ var CONDITIONAL_BLACKLIST = /* @__PURE__ */ new Map([
28520
+ // `ember/no-restricted-service-injections` requires at least one of
28521
+ // `paths` or `services` to have a non-empty entry — without a real
28522
+ // value the plugin's own JSON schema rejects the options and aborts
28523
+ // the whole run. Codacy's declared default is `[""]` (an array with
28524
+ // a single empty string) which is nonsensical as a match list. Only
28525
+ // keep the rule when the user has actually configured something.
28526
+ [
28527
+ "ember/no-restricted-service-injections",
28528
+ (pattern) => !hasUserSuppliedAny(pattern, "services", "paths")
28529
+ ],
28530
+ // `ember/no-restricted-property-modifications` has the same pattern:
28531
+ // it takes a list of property names to watch, and is unusable without
28532
+ // one.
28533
+ [
28534
+ "ember/no-restricted-property-modifications",
28535
+ (pattern) => !hasUserSuppliedAny(pattern, "properties")
28536
+ ],
28537
+ // `jsonc/sort-array-values` / `jsonc/sort-keys` need a non-empty
28538
+ // `pathPattern` or `order` — the default placeholder (`^$` / `{}`)
28539
+ // matches nothing meaningful and makes the rule schema-invalid.
28540
+ ["jsonc/sort-array-values", (pattern) => !hasUserSuppliedAny(pattern, "pathPattern", "order")],
28541
+ ["jsonc/sort-keys", (pattern) => !hasUserSuppliedAny(pattern, "pathPattern", "order")],
28542
+ // `jest/no-deprecated-functions` inspects the analysed project's
28543
+ // installed jest version at rule-load time. In a repo without jest
28544
+ // in `package.json` the rule throws `Unable to detect Jest version`
28545
+ // after ESLint has already walked every file, aborting the whole
28546
+ // run. Skip the rule when the target repo has no jest dependency.
28547
+ ["jest/no-deprecated-functions", (_p, ctx) => !hasTargetDependency(ctx.repositoryRoot, "jest")],
28548
+ // `eslint-plugin-check-file` rules require the user to supply a
28549
+ // non-empty naming-pattern object. Without it, each rule fires on
28550
+ // *every* JS/TS file with
28551
+ // `The naming pattern object "undefined" does not appear to be an
28552
+ // Object type, please double-check it and try again`
28553
+ // Codacy's `patterns.json` exposes no parameters for these rules, so
28554
+ // they are never useful without a user-provided override. If a user
28555
+ // supplies a `rules` / `patterns` parameter, trust them and run.
28556
+ ["check-file/filename-blocklist", requireAnyUserParam],
28557
+ ["check-file/filename-naming-convention", requireAnyUserParam],
28558
+ ["check-file/folder-match-with-fex", requireAnyUserParam],
28559
+ ["check-file/folder-naming-convention", requireAnyUserParam],
28560
+ // `jsdoc/no-missing-syntax` requires a non-empty `contexts` option.
28561
+ // The Codacy-declared default is `[]`, which the rule itself rejects
28562
+ // at runtime with
28563
+ // `Rule no-missing-syntax is missing a 'contexts' option`
28564
+ // Skip unless the user has supplied a real value.
28565
+ ["jsdoc/no-missing-syntax", (pattern) => !hasUserSuppliedAny(pattern, "contexts")],
28566
+ // `jsdoc/imports-as-dependencies` synchronously reads the analysed
28567
+ // repo's `package.json` to verify every `@import` tag references a
28568
+ // listed dependency. If the file is missing the rule crashes with
28569
+ // `ENOENT: no such file or directory, open '<root>/package.json'`
28570
+ // and that sync throw propagates through the rule visitor, aborting
28571
+ // the run mid-way. Skip the rule unless the target repo actually
28572
+ // has a `package.json`.
28573
+ ["jsdoc/imports-as-dependencies", (_p, ctx) => !hasTargetPackageJson(ctx.repositoryRoot)]
28574
+ ]);
28575
+ function hasUserSuppliedAny(pattern, ...paramNames) {
28576
+ const params = pattern.parameters;
28577
+ if (!params) return false;
28578
+ for (const name of paramNames) {
28579
+ const raw = params[name];
28580
+ if (raw === void 0 || raw === "") continue;
28581
+ const parsed = tryParseCollection(raw);
28582
+ if (Array.isArray(parsed)) {
28583
+ if (parsed.length > 0 && parsed.some((v) => v !== "" && v != null)) return true;
28584
+ } else if (parsed && typeof parsed === "object") {
28585
+ if (Object.keys(parsed).length > 0) return true;
28586
+ } else if (typeof parsed === "string") {
28587
+ if (parsed.length > 0 && parsed !== "^$") return true;
28588
+ } else if (parsed !== void 0 && parsed !== null) {
28589
+ return true;
28590
+ }
28591
+ }
28592
+ return false;
28593
+ }
28594
+ function tryParseCollection(raw) {
28595
+ const trimmed = raw.trim();
28596
+ if (trimmed.startsWith("[") && trimmed.endsWith("]") || trimmed.startsWith("{") && trimmed.endsWith("}")) {
28597
+ try {
28598
+ return JSON.parse(trimmed);
28599
+ } catch {
28600
+ }
28601
+ }
28602
+ return trimmed;
28603
+ }
28604
+ function hasTargetPackageJson(repositoryRoot) {
28605
+ try {
28606
+ import_fs.default.accessSync(import_path.default.join(repositoryRoot, "package.json"), import_fs.default.constants.R_OK);
28607
+ return true;
28608
+ } catch {
28609
+ return false;
28610
+ }
28611
+ }
28612
+ function hasTargetDependency(repositoryRoot, dependencyName) {
28613
+ try {
28614
+ const pkgPath = import_path.default.join(repositoryRoot, "package.json");
28615
+ const raw = import_fs.default.readFileSync(pkgPath, "utf-8");
28616
+ const pkg = JSON.parse(raw);
28617
+ return !!pkg.dependencies?.[dependencyName] || !!pkg.devDependencies?.[dependencyName] || !!pkg.peerDependencies?.[dependencyName] || !!pkg.optionalDependencies?.[dependencyName];
28618
+ } catch {
28619
+ return false;
28620
+ }
28621
+ }
28466
28622
  function isBlacklisted(ruleId) {
28467
28623
  if (BLACKLISTED_RULES.has(ruleId)) return true;
28468
28624
  return BLACKLISTED_RULE_PATTERNS.some((re) => re.test(ruleId));
28469
28625
  }
28626
+ function shouldSkipRule(ruleId, pattern, ctx) {
28627
+ if (isBlacklisted(ruleId)) return true;
28628
+ const predicate = CONDITIONAL_BLACKLIST.get(ruleId);
28629
+ return predicate ? predicate(pattern, ctx) : false;
28630
+ }
28470
28631
 
28471
28632
  // src/plugin-registry.ts
28472
28633
  async function loadPlugin(packageName) {
@@ -28609,6 +28770,30 @@ function extractPluginPrefix(ruleId) {
28609
28770
  }
28610
28771
 
28611
28772
  // src/config-builder.ts
28773
+ var TSCONFIG_CANDIDATES = [
28774
+ "tsconfig.json",
28775
+ "tsconfig.base.json",
28776
+ "tsconfig.eslint.json",
28777
+ "jsconfig.json"
28778
+ ];
28779
+ function hasTypeScriptProjectConfig(repositoryRoot) {
28780
+ for (const name of TSCONFIG_CANDIDATES) {
28781
+ try {
28782
+ import_fs2.default.accessSync(import_path2.default.join(repositoryRoot, name));
28783
+ return true;
28784
+ } catch {
28785
+ }
28786
+ }
28787
+ return false;
28788
+ }
28789
+ var PATTERN_DEFAULTS = new Map(
28790
+ patterns_default.filter((p) => Array.isArray(p.parameters) && p.parameters.length > 0).map((p) => [
28791
+ p.id,
28792
+ Object.fromEntries(
28793
+ (p.parameters ?? []).filter((param) => param.default !== void 0).map((param) => [param.name, param.default ?? ""])
28794
+ )
28795
+ ])
28796
+ );
28612
28797
  var TOOL_ID = "ESLint9";
28613
28798
  var PREFIX = `${TOOL_ID}_`;
28614
28799
  var CORE_TO_TS_EXTENSION = /* @__PURE__ */ new Map([
@@ -28645,11 +28830,11 @@ function decodePatternId(patternId) {
28645
28830
  function encodePatternId(ruleId) {
28646
28831
  return `${PREFIX}${ruleId.replace(/_/g, "__").replace(/\//g, "_")}`;
28647
28832
  }
28648
- async function buildESLintConfig(patterns) {
28833
+ async function buildESLintConfig(patterns, ctx = { repositoryRoot: process.cwd() }) {
28649
28834
  const ruleEntries = [];
28650
28835
  for (const p of patterns) {
28651
28836
  const ruleId = decodePatternId(p.patternId);
28652
- if (isBlacklisted(ruleId)) continue;
28837
+ if (shouldSkipRule(ruleId, p, ctx)) continue;
28653
28838
  ruleEntries.push({ ruleId, pattern: p });
28654
28839
  }
28655
28840
  const allRuleIds = ruleEntries.map((e) => e.ruleId);
@@ -28662,6 +28847,7 @@ async function buildESLintConfig(patterns) {
28662
28847
  if (!prefix) {
28663
28848
  coreRules[ruleId] = ruleValue;
28664
28849
  } else if (plugins.has(prefix)) {
28850
+ if (!pluginExposesRule(plugins.get(prefix), prefix, ruleId)) continue;
28665
28851
  if (!pluginRuleGroups.has(prefix)) {
28666
28852
  pluginRuleGroups.set(prefix, {});
28667
28853
  }
@@ -28669,6 +28855,14 @@ async function buildESLintConfig(patterns) {
28669
28855
  }
28670
28856
  }
28671
28857
  const configs = [];
28858
+ configs.push({
28859
+ files: ["**/*.jsx", "**/*.tsx"],
28860
+ languageOptions: {
28861
+ parserOptions: {
28862
+ ecmaFeatures: { jsx: true }
28863
+ }
28864
+ }
28865
+ });
28672
28866
  if (Object.keys(coreRules).length > 0) {
28673
28867
  configs.push({
28674
28868
  files: ["**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs", "**/*.ts", "**/*.tsx"],
@@ -28678,17 +28872,16 @@ async function buildESLintConfig(patterns) {
28678
28872
  const tsPlugin = plugins.get("@typescript-eslint");
28679
28873
  const tsRules = pluginRuleGroups.get("@typescript-eslint");
28680
28874
  if (tsRules && tsPlugin) {
28875
+ const tsLanguageOptions = {
28876
+ parser: await loadTsParser()
28877
+ };
28878
+ if (hasTypeScriptProjectConfig(ctx.repositoryRoot)) {
28879
+ tsLanguageOptions.parserOptions = { projectService: true };
28880
+ }
28681
28881
  configs.push({
28682
28882
  files: ["**/*.ts", "**/*.tsx"],
28683
28883
  plugins: { "@typescript-eslint": tsPlugin },
28684
- languageOptions: {
28685
- parser: await loadTsParser(),
28686
- parserOptions: {
28687
- projectService: {
28688
- allowDefaultProject: ["*.ts", "*.tsx"]
28689
- }
28690
- }
28691
- },
28884
+ languageOptions: tsLanguageOptions,
28692
28885
  rules: tsRules
28693
28886
  });
28694
28887
  pluginRuleGroups.delete("@typescript-eslint");
@@ -28713,6 +28906,26 @@ async function buildESLintConfig(patterns) {
28713
28906
  if (prefix === "yml") {
28714
28907
  configItem.files = ["**/*.yml", "**/*.yaml"];
28715
28908
  }
28909
+ if (prefix === "storybook") {
28910
+ configItem.files = [
28911
+ "**/*.stories.js",
28912
+ "**/*.stories.jsx",
28913
+ "**/*.stories.mjs",
28914
+ "**/*.stories.cjs",
28915
+ "**/*.stories.ts",
28916
+ "**/*.stories.tsx",
28917
+ "**/*.story.js",
28918
+ "**/*.story.jsx",
28919
+ "**/*.story.mjs",
28920
+ "**/*.story.cjs",
28921
+ "**/*.story.ts",
28922
+ "**/*.story.tsx"
28923
+ ];
28924
+ }
28925
+ if (prefix === "react") {
28926
+ const version = hasTargetDependency(ctx.repositoryRoot, "react") ? "detect" : "999.999.999";
28927
+ configItem.settings = { react: { version } };
28928
+ }
28716
28929
  configs.push(configItem);
28717
28930
  }
28718
28931
  const tsOverrideRules = {};
@@ -28734,16 +28947,18 @@ async function buildESLintConfig(patterns) {
28734
28947
  return configs;
28735
28948
  }
28736
28949
  function buildRuleValue(pattern) {
28737
- if (!pattern.parameters || Object.keys(pattern.parameters).length === 0) {
28950
+ const defaults = PATTERN_DEFAULTS.get(pattern.patternId) ?? {};
28951
+ const merged = { ...defaults, ...pattern.parameters ?? {} };
28952
+ if (Object.keys(merged).length === 0) {
28738
28953
  return "error";
28739
28954
  }
28740
28955
  const result = ["error"];
28741
28956
  const optionsObj = {};
28742
- for (const [name, rawValue] of Object.entries(pattern.parameters)) {
28957
+ for (const [name, rawValue] of Object.entries(merged)) {
28743
28958
  const value = parseParamValue(rawValue);
28744
28959
  if (name === "unnamedParam") {
28745
28960
  result.splice(1, 0, value);
28746
- } else {
28961
+ } else if (!isEmptyCollection(value)) {
28747
28962
  optionsObj[name] = value;
28748
28963
  }
28749
28964
  }
@@ -28752,6 +28967,13 @@ function buildRuleValue(pattern) {
28752
28967
  }
28753
28968
  return result.length === 1 ? "error" : result;
28754
28969
  }
28970
+ function isEmptyCollection(value) {
28971
+ if (Array.isArray(value)) return value.length === 0;
28972
+ if (value !== null && typeof value === "object") {
28973
+ return Object.keys(value).length === 0;
28974
+ }
28975
+ return false;
28976
+ }
28755
28977
  function parseParamValue(value) {
28756
28978
  const trimmed = value.trim();
28757
28979
  if (trimmed === "true") return true;
@@ -28770,6 +28992,15 @@ async function loadTsParser() {
28770
28992
  const mod = await import("typescript-eslint");
28771
28993
  return mod.default?.parser ?? mod.parser;
28772
28994
  }
28995
+ function pluginExposesRule(plugin, prefix, fullRuleId) {
28996
+ if (!plugin || typeof plugin !== "object") return false;
28997
+ const rules = plugin.rules;
28998
+ if (!rules || typeof rules !== "object") {
28999
+ return true;
29000
+ }
29001
+ const shortName = fullRuleId.startsWith(prefix + "/") ? fullRuleId.slice(prefix.length + 1) : fullRuleId;
29002
+ return Object.prototype.hasOwnProperty.call(rules, shortName);
29003
+ }
28773
29004
 
28774
29005
  // src/metadata.ts
28775
29006
  var descriptor = {
@@ -28806,14 +29037,75 @@ function ruleMetadata(ruleId) {
28806
29037
  const patternId = encodePatternId(ruleId);
28807
29038
  return PATTERN_META.get(patternId) ?? { category: "BestPractice", severity: "Info" };
28808
29039
  }
28809
- function readLine(filePath, lineNumber) {
28810
- try {
28811
- const content = import_fs.default.readFileSync(filePath, "utf-8");
28812
- const lines = content.split("\n");
28813
- return lines[lineNumber - 1] ?? "";
28814
- } catch {
28815
- return "";
29040
+ var PROBE_SNIPPETS = [
29041
+ { filePath: "__codacy_probe__.js", source: "var x=0;\n" },
29042
+ { filePath: "__codacy_probe__.jsx", source: "const x=<div/>;\n" },
29043
+ { filePath: "__codacy_probe__.ts", source: "const x: number = 0;\n" },
29044
+ { filePath: "__codacy_probe__.tsx", source: "const x:unknown=<div/>;\n" },
29045
+ { filePath: "__codacy_probe__.vue", source: "<template><div/></template>\n" },
29046
+ { filePath: "__codacy_probe__.svelte", source: "<div></div>\n" },
29047
+ { filePath: "__codacy_probe__.json", source: "{}\n" },
29048
+ { filePath: "__codacy_probe__.yml", source: "a: 1\n" },
29049
+ { filePath: "__codacy_probe__.md", source: "# probe\n" }
29050
+ ];
29051
+ async function probeConfig(eslint, repositoryRoot) {
29052
+ for (const { filePath, source } of PROBE_SNIPPETS) {
29053
+ try {
29054
+ await eslint.lintText(source, { filePath: import_path3.default.join(repositoryRoot, filePath) });
29055
+ } catch (err) {
29056
+ return err instanceof Error ? err : new Error(String(err));
29057
+ }
28816
29058
  }
29059
+ return void 0;
29060
+ }
29061
+ function truncateForError(message) {
29062
+ const oneLine = message.replace(/\s+/g, " ").trim();
29063
+ return oneLine.length > 240 ? oneLine.slice(0, 237) + "\u2026" : oneLine;
29064
+ }
29065
+ function extractInvalidRule(message) {
29066
+ const flatMatch = message.match(/Key "rules":\s*Key "([^"]+)"/);
29067
+ if (flatMatch) return flatMatch[1];
29068
+ const legacyMatch = message.match(/Configuration for rule "([^"]+)" is invalid/);
29069
+ if (legacyMatch) return legacyMatch[1];
29070
+ const loadMatch = message.match(/Error while loading rule ['"]([^'"]+)['"]/);
29071
+ if (loadMatch) return loadMatch[1];
29072
+ const trailingRule = message.match(/Rule: ['"]([^'"]+)['"]/);
29073
+ if (trailingRule) return trailingRule[1];
29074
+ return void 0;
29075
+ }
29076
+ function removeRuleFromFlatConfig(config, ruleId) {
29077
+ if (!Array.isArray(config)) return false;
29078
+ let removed = false;
29079
+ for (const entry of config) {
29080
+ if (entry?.rules && Object.prototype.hasOwnProperty.call(entry.rules, ruleId)) {
29081
+ delete entry.rules[ruleId];
29082
+ removed = true;
29083
+ }
29084
+ }
29085
+ return removed;
29086
+ }
29087
+ function isParserServicesError(message) {
29088
+ return /requires parserServices|parserOptions\.project|requires type information/.test(message);
29089
+ }
29090
+ function rulePluginPrefix(ruleId) {
29091
+ const slashIdx = ruleId.indexOf("/");
29092
+ if (slashIdx <= 0) return void 0;
29093
+ return ruleId.slice(0, slashIdx);
29094
+ }
29095
+ function removeRulesByPluginFromFlatConfig(config, pluginPrefix) {
29096
+ if (!Array.isArray(config)) return [];
29097
+ const removed = [];
29098
+ const prefix = pluginPrefix + "/";
29099
+ for (const entry of config) {
29100
+ if (!entry?.rules) continue;
29101
+ for (const key of Object.keys(entry.rules)) {
29102
+ if (key.startsWith(prefix)) {
29103
+ delete entry.rules[key];
29104
+ if (!removed.includes(key)) removed.push(key);
29105
+ }
29106
+ }
29107
+ }
29108
+ return removed;
28817
29109
  }
28818
29110
  var adapter = {
28819
29111
  ...descriptor,
@@ -28854,9 +29146,9 @@ var adapter = {
28854
29146
  async checkLocalConfigurationFile(ctx) {
28855
29147
  ctx.logger.debug("Checking for local config", { toolId: TOOL_ID2 });
28856
29148
  for (const name of LOCAL_CONFIG_FILES) {
28857
- const candidate = import_path.default.join(ctx.repositoryRoot, name);
29149
+ const candidate = import_path3.default.join(ctx.repositoryRoot, name);
28858
29150
  try {
28859
- import_fs.default.accessSync(candidate);
29151
+ import_fs3.default.accessSync(candidate);
28860
29152
  ctx.logger.debug("Local config found", { toolId: TOOL_ID2, path: candidate });
28861
29153
  return { found: true, path: candidate };
28862
29154
  } catch {
@@ -28878,7 +29170,7 @@ var adapter = {
28878
29170
  const startedAt = /* @__PURE__ */ new Date();
28879
29171
  const issues = [];
28880
29172
  const errors = [];
28881
- const absoluteFiles = ctx.targetFiles.map((f) => import_path.default.resolve(ctx.repositoryRoot, f));
29173
+ const absoluteFiles = ctx.targetFiles.map((f) => import_path3.default.resolve(ctx.repositoryRoot, f));
28882
29174
  if (absoluteFiles.length === 0) {
28883
29175
  const completedAt2 = /* @__PURE__ */ new Date();
28884
29176
  return (0, import_tooling.buildResult)(TOOL_ID2, startedAt, completedAt2, issues, errors, ctx.targetFiles.length);
@@ -28902,10 +29194,19 @@ var adapter = {
28902
29194
  if (codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile) {
28903
29195
  eslintOptions.overrideConfigFile = codacyConfig.localConfigurationFile;
28904
29196
  } else if (codacyConfig.patterns.length > 0) {
29197
+ const buildStartMs = Date.now();
28905
29198
  try {
28906
- const config = await buildESLintConfig(codacyConfig.patterns);
29199
+ const config = await buildESLintConfig(codacyConfig.patterns, {
29200
+ repositoryRoot: ctx.repositoryRoot
29201
+ });
28907
29202
  eslintOptions.overrideConfig = config;
28908
29203
  eslintOptions.overrideConfigFile = true;
29204
+ ctx.logger.debug("Flat config built", {
29205
+ toolId: TOOL_ID2,
29206
+ durationMs: Date.now() - buildStartMs,
29207
+ configEntries: Array.isArray(config) ? config.length : 0,
29208
+ patternCount: codacyConfig.patterns.length
29209
+ });
28909
29210
  } catch (err) {
28910
29211
  errors.push({
28911
29212
  toolId: TOOL_ID2,
@@ -28923,14 +29224,130 @@ var adapter = {
28923
29224
  return (0, import_tooling.buildResult)(TOOL_ID2, startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
28924
29225
  }
28925
29226
  const enabledPatterns = new Set(codacyConfig.patterns.map((p) => p.patternId));
29227
+ const MAX_CONFIG_RETRIES = 30;
29228
+ let attempt = 0;
29229
+ let results = [];
28926
29230
  try {
28927
- const eslint = new ESLintClass(eslintOptions);
28928
- const results = await eslint.lintFiles(absoluteFiles);
29231
+ const probeStartMs = Date.now();
29232
+ let lastProbeFailure;
29233
+ while (attempt <= MAX_CONFIG_RETRIES) {
29234
+ const probeEslint = new ESLintClass(eslintOptions);
29235
+ const failure = await probeConfig(probeEslint, ctx.repositoryRoot);
29236
+ if (!failure) {
29237
+ lastProbeFailure = void 0;
29238
+ break;
29239
+ }
29240
+ lastProbeFailure = failure;
29241
+ const rule = extractInvalidRule(failure.message);
29242
+ if (!rule) throw failure;
29243
+ if (isParserServicesError(failure.message)) {
29244
+ const prefix = rulePluginPrefix(rule);
29245
+ if (prefix) {
29246
+ const removedRules = removeRulesByPluginFromFlatConfig(
29247
+ eslintOptions.overrideConfig,
29248
+ prefix
29249
+ );
29250
+ if (removedRules.length > 0) {
29251
+ ctx.logger.warn("Skipping plugin requiring parserServices", {
29252
+ toolId: TOOL_ID2,
29253
+ plugin: prefix,
29254
+ count: removedRules.length
29255
+ });
29256
+ errors.push({
29257
+ toolId: TOOL_ID2,
29258
+ phase: "toolConfig",
29259
+ kind: "InvalidRuleConfig",
29260
+ message: `Plugin "${prefix}/*" (${removedRules.length} rule${removedRules.length === 1 ? "" : "s"}) skipped: requires typescript-eslint parserServices (no tsconfig.json in the analysed repo).`,
29261
+ level: "warning"
29262
+ });
29263
+ attempt++;
29264
+ continue;
29265
+ }
29266
+ }
29267
+ }
29268
+ const removed = removeRuleFromFlatConfig(eslintOptions.overrideConfig, rule);
29269
+ if (!removed) throw failure;
29270
+ ctx.logger.warn("Skipping rule rejected during probe", { toolId: TOOL_ID2, rule });
29271
+ errors.push({
29272
+ toolId: TOOL_ID2,
29273
+ phase: "toolConfig",
29274
+ kind: "InvalidRuleConfig",
29275
+ message: `Rule "${rule}" was skipped: ${truncateForError(failure.message)}`,
29276
+ level: "warning"
29277
+ });
29278
+ attempt++;
29279
+ }
29280
+ ctx.logger.debug("Config probe complete", {
29281
+ toolId: TOOL_ID2,
29282
+ durationMs: Date.now() - probeStartMs,
29283
+ rulesSkipped: attempt
29284
+ });
29285
+ if (lastProbeFailure) throw lastProbeFailure;
29286
+ while (true) {
29287
+ try {
29288
+ const instantiateStartMs = Date.now();
29289
+ const eslint = new ESLintClass(eslintOptions);
29290
+ ctx.logger.debug("ESLint instance created", {
29291
+ toolId: TOOL_ID2,
29292
+ durationMs: Date.now() - instantiateStartMs
29293
+ });
29294
+ const lintStartMs = Date.now();
29295
+ results = await eslint.lintFiles(absoluteFiles);
29296
+ ctx.logger.debug("lintFiles() complete", {
29297
+ toolId: TOOL_ID2,
29298
+ durationMs: Date.now() - lintStartMs,
29299
+ fileCount: results.length
29300
+ });
29301
+ break;
29302
+ } catch (err) {
29303
+ const rule = err instanceof Error ? extractInvalidRule(err.message) : void 0;
29304
+ if (!rule || attempt >= MAX_CONFIG_RETRIES) throw err;
29305
+ const parserServicesCause = err instanceof Error && isParserServicesError(err.message);
29306
+ const prefix = rulePluginPrefix(rule);
29307
+ if (prefix) {
29308
+ const removedRules = removeRulesByPluginFromFlatConfig(
29309
+ eslintOptions.overrideConfig,
29310
+ prefix
29311
+ );
29312
+ if (removedRules.length > 0) {
29313
+ const message = parserServicesCause ? `Plugin "${prefix}/*" (${removedRules.length} rule${removedRules.length === 1 ? "" : "s"}) skipped: requires typescript-eslint parserServices (no tsconfig.json / tsconfig.base.json / tsconfig.eslint.json / jsconfig.json found at the repository root).` : `Plugin "${prefix}/*" (${removedRules.length} rule${removedRules.length === 1 ? "" : "s"}) skipped after lintFiles failure on "${rule}": ${truncateForError(err instanceof Error ? err.message : String(err))}`;
29314
+ ctx.logger.warn(
29315
+ parserServicesCause ? "Skipping plugin requiring parserServices" : "Skipping plugin that failed during lintFiles",
29316
+ { toolId: TOOL_ID2, plugin: prefix, count: removedRules.length, rule }
29317
+ );
29318
+ errors.push({
29319
+ toolId: TOOL_ID2,
29320
+ phase: "toolConfig",
29321
+ kind: "InvalidRuleConfig",
29322
+ message,
29323
+ level: "warning"
29324
+ });
29325
+ attempt++;
29326
+ continue;
29327
+ }
29328
+ }
29329
+ const removed = removeRuleFromFlatConfig(eslintOptions.overrideConfig, rule);
29330
+ if (!removed) throw err;
29331
+ ctx.logger.warn("Skipping rule that failed during lintFiles", { toolId: TOOL_ID2, rule });
29332
+ errors.push({
29333
+ toolId: TOOL_ID2,
29334
+ phase: "toolConfig",
29335
+ kind: "InvalidRuleConfig",
29336
+ message: `Rule "${rule}" was skipped after lintFiles failure: ${truncateForError(err instanceof Error ? err.message : String(err))}`,
29337
+ level: "warning"
29338
+ });
29339
+ attempt++;
29340
+ }
29341
+ }
28929
29342
  for (const fileResult of results) {
28930
- const relPath = import_path.default.relative(ctx.repositoryRoot, fileResult.filePath);
29343
+ if (fileResult.messages.length === 0) continue;
29344
+ const relPath = import_path3.default.relative(ctx.repositoryRoot, fileResult.filePath);
29345
+ let lines;
28931
29346
  for (const msg of fileResult.messages) {
28932
29347
  if (!msg.ruleId) continue;
28933
29348
  if (isBlacklisted(msg.ruleId)) continue;
29349
+ if (msg.message.startsWith("Definition for rule ")) continue;
29350
+ if (msg.line === 0) continue;
28934
29351
  const patternId = encodePatternId(msg.ruleId);
28935
29352
  if (enabledPatterns.size > 0 && !enabledPatterns.has(patternId)) continue;
28936
29353
  const meta = ruleMetadata(msg.ruleId);
@@ -28938,6 +29355,9 @@ var adapter = {
28938
29355
  if (msg.suggestions && msg.suggestions.length > 0) {
28939
29356
  suggestion = msg.suggestions[0].desc;
28940
29357
  }
29358
+ if (lines === void 0) {
29359
+ lines = await (0, import_tooling.readFileLines)(fileResult.filePath);
29360
+ }
28941
29361
  issues.push({
28942
29362
  filePath: relPath,
28943
29363
  patternId,
@@ -28947,7 +29367,7 @@ var adapter = {
28947
29367
  endLine: msg.endLine,
28948
29368
  endColumn: msg.endColumn,
28949
29369
  message: msg.message,
28950
- lineContent: readLine(fileResult.filePath, msg.line),
29370
+ lineContent: (0, import_tooling.getLineContent)(lines, msg.line),
28951
29371
  category: meta.category,
28952
29372
  severity: meta.severity,
28953
29373
  suggestion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/tools-eslint-9",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "ESLint 9 adapter — Library-mode JavaScript/TypeScript linter",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,21 +8,21 @@
8
8
  "dist"
9
9
  ],
10
10
  "dependencies": {
11
- "eslint": "^9.0.0",
12
- "@eslint/js": "^9.0.0",
11
+ "eslint": "^9.38.0",
12
+ "@eslint/js": "^9.38.0",
13
13
  "typescript-eslint": "^8.0.0",
14
14
  "globals": "^16.5.0",
15
15
  "@angular-eslint/eslint-plugin": "^19.0.0",
16
- "@lwc/eslint-plugin-lwc": "^1.0.0",
17
- "@salesforce/eslint-plugin-lightning": "^1.0.0",
18
- "@shopify/eslint-plugin": "^46.0.0",
16
+ "@lwc/eslint-plugin-lwc": "^3.0.0",
17
+ "@salesforce/eslint-plugin-lightning": "^2.0.0",
18
+ "@shopify/eslint-plugin": "^48.0.2",
19
19
  "@stylistic/eslint-plugin": "^4.0.0",
20
20
  "@tanstack/eslint-plugin-query": "^5.0.0",
21
21
  "@vitest/eslint-plugin": "^1.0.0",
22
22
  "eslint-plugin-angular": "^4.0.0",
23
- "eslint-plugin-ava": "^14.0.0",
23
+ "eslint-plugin-ava": "^15.0.1",
24
24
  "eslint-plugin-backbone": "^2.0.0",
25
- "eslint-plugin-check-file": "^2.0.0",
25
+ "eslint-plugin-check-file": "^3.2.0",
26
26
  "eslint-plugin-compat": "^6.0.0",
27
27
  "eslint-plugin-cypress": "^4.0.0",
28
28
  "eslint-plugin-ember": "^12.0.0",
@@ -30,22 +30,22 @@
30
30
  "eslint-plugin-es-x": "^8.0.0",
31
31
  "eslint-plugin-eslint-plugin": "^6.0.0",
32
32
  "eslint-plugin-fp": "^2.0.0",
33
- "eslint-plugin-functional": "^7.0.0",
33
+ "eslint-plugin-functional": "^9.0.1",
34
34
  "eslint-plugin-i18next": "^6.0.0",
35
35
  "eslint-plugin-import": "^2.0.0",
36
36
  "eslint-plugin-jasmine": "^4.0.0",
37
37
  "eslint-plugin-jest": "^28.0.0",
38
38
  "eslint-plugin-jest-dom": "^5.0.0",
39
- "eslint-plugin-jest-extended": "^2.0.0",
39
+ "eslint-plugin-jest-extended": "^3.0.0",
40
40
  "eslint-plugin-jest-formatting": "^3.0.0",
41
41
  "eslint-plugin-jsdoc": "^50.0.0",
42
42
  "eslint-plugin-jsonc": "^2.0.0",
43
43
  "eslint-plugin-jsx-a11y": "^6.0.0",
44
- "eslint-plugin-lit": "^1.0.0",
44
+ "eslint-plugin-lit": "^2.1.1",
45
45
  "eslint-plugin-lodash": "^8.0.0",
46
46
  "eslint-plugin-lodash-fp": "^2.0.0",
47
47
  "eslint-plugin-meteor": "^7.0.0",
48
- "eslint-plugin-mocha": "^10.0.0",
48
+ "eslint-plugin-mocha": "^11.0.0",
49
49
  "eslint-plugin-n": "^17.0.0",
50
50
  "eslint-plugin-nuxt": "^4.0.0",
51
51
  "eslint-plugin-perfectionist": "^4.0.0",
@@ -53,7 +53,7 @@
53
53
  "eslint-plugin-prettier": "^5.0.0",
54
54
  "eslint-plugin-promise": "^7.0.0",
55
55
  "eslint-plugin-react": "^7.0.0",
56
- "eslint-plugin-react-hooks": "^5.0.0",
56
+ "eslint-plugin-react-hooks": "^7.0.1",
57
57
  "eslint-plugin-react-perf": "^3.0.0",
58
58
  "eslint-plugin-react-redux": "^4.0.0",
59
59
  "eslint-plugin-redux-saga": "^1.0.0",
@@ -61,7 +61,7 @@
61
61
  "eslint-plugin-security": "^3.0.0",
62
62
  "eslint-plugin-security-node": "^1.0.0",
63
63
  "eslint-plugin-sonarjs": "^3.0.0",
64
- "eslint-plugin-sort": "^3.0.0",
64
+ "eslint-plugin-sort": "^4.0.0",
65
65
  "eslint-plugin-sort-destructure-keys": "^2.0.0",
66
66
  "eslint-plugin-storybook": "^0.11.0",
67
67
  "eslint-plugin-suitescript": "^1.0.0",
@@ -70,16 +70,16 @@
70
70
  "eslint-plugin-test-selectors": "^2.0.0",
71
71
  "eslint-plugin-testing-library": "^7.0.0",
72
72
  "eslint-plugin-typescript-sort-keys": "^3.0.0",
73
- "eslint-plugin-unicorn": "^57.0.0",
73
+ "eslint-plugin-unicorn": "^62.0.0",
74
74
  "eslint-plugin-unused-imports": "^4.0.0",
75
- "eslint-plugin-vue": "^9.0.0",
75
+ "eslint-plugin-vue": "^10.1.0",
76
76
  "eslint-plugin-vue-scoped-css": "^2.0.0",
77
77
  "eslint-plugin-vuejs-accessibility": "^2.0.0",
78
78
  "eslint-plugin-vuetify": "^2.0.0",
79
79
  "eslint-plugin-wdio": "^9.0.0",
80
80
  "eslint-plugin-xss": "^0.1.0",
81
81
  "eslint-plugin-yml": "^1.0.0",
82
- "@codacy/tooling": "0.6.0"
82
+ "@codacy/tooling": "0.6.1"
83
83
  },
84
84
  "license": "MIT",
85
85
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",