@codacy/tools-eslint-9 0.8.0 → 0.9.0

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 +235 -98
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -39,6 +39,9 @@ var import_tooling2 = require("@codacy/tooling");
39
39
  var import_tooling = require("@codacy/tooling");
40
40
  var import_path3 = __toESM(require("path"));
41
41
  var import_fs3 = __toESM(require("fs"));
42
+ var import_module = require("module");
43
+ var import_async_hooks = require("async_hooks");
44
+ var import_promises = require("timers/promises");
42
45
 
43
46
  // src/patterns.json
44
47
  var patterns_default = [
@@ -30662,7 +30665,27 @@ var descriptor = {
30662
30665
  };
30663
30666
 
30664
30667
  // src/adapter.ts
30668
+ var yieldToEventLoop = () => (0, import_promises.setTimeout)(0);
30665
30669
  var TOOL_ID2 = "ESLint9";
30670
+ var ESLINT_MAX_FILE_SIZE_BYTES = 512 * 1024;
30671
+ var LINT_BATCH_SIZE = 50;
30672
+ var ESLINT_SKIP_DIRS = /* @__PURE__ */ new Set([".yarn", "vendor", ".next", ".nuxt"]);
30673
+ function shouldSkipFile(filePath, repositoryRoot) {
30674
+ const relPath = import_path3.default.relative(repositoryRoot, filePath).replace(/\\/g, "/");
30675
+ const segments = relPath.split("/");
30676
+ for (const seg of segments) {
30677
+ if (ESLINT_SKIP_DIRS.has(seg)) return `path contains skip directory "${seg}"`;
30678
+ }
30679
+ if (/\.min\.[cm]?[jt]sx?$/.test(import_path3.default.basename(filePath))) return "minified file";
30680
+ try {
30681
+ const stat = import_fs3.default.statSync(filePath);
30682
+ if (stat.size > ESLINT_MAX_FILE_SIZE_BYTES) {
30683
+ return `file size ${Math.round(stat.size / 1024)} KB exceeds ESLint limit`;
30684
+ }
30685
+ } catch {
30686
+ }
30687
+ return void 0;
30688
+ }
30666
30689
  var LOCAL_CONFIG_FILES = [
30667
30690
  "eslint.config.js",
30668
30691
  "eslint.config.mjs",
@@ -30754,6 +30777,62 @@ function removeRulesByPluginFromFlatConfig(config, pluginPrefix) {
30754
30777
  }
30755
30778
  return removed;
30756
30779
  }
30780
+ async function resolveProjectESLint(repositoryRoot) {
30781
+ try {
30782
+ const projectRequire = (0, import_module.createRequire)(import_path3.default.join(repositoryRoot, "__placeholder__.js"));
30783
+ const mod = projectRequire("eslint");
30784
+ const version = mod.ESLint?.version ?? "";
30785
+ const major = parseInt(version.split(".")[0], 10);
30786
+ if (major !== 9) return void 0;
30787
+ return { ESLintClass: mod.ESLint, version };
30788
+ } catch {
30789
+ return void 0;
30790
+ }
30791
+ }
30792
+ var consoleCaptureStorage = new import_async_hooks.AsyncLocalStorage();
30793
+ var consoleGlobalsPatched = false;
30794
+ function patchConsoleGlobals() {
30795
+ if (consoleGlobalsPatched) return;
30796
+ consoleGlobalsPatched = true;
30797
+ const origLog = console.log.bind(console);
30798
+ const origInfo = console.info.bind(console);
30799
+ const origWarn = console.warn.bind(console);
30800
+ const origError = console.error.bind(console);
30801
+ const origEmitWarning = process.emitWarning.bind(process);
30802
+ const intercept = (orig) => (...args) => {
30803
+ const sink = consoleCaptureStorage.getStore();
30804
+ if (!sink) {
30805
+ orig(...args);
30806
+ return;
30807
+ }
30808
+ const msg = args.map(String).join(" ").trim();
30809
+ if (msg) sink(msg);
30810
+ };
30811
+ console.log = intercept(origLog);
30812
+ console.info = intercept(origInfo);
30813
+ console.warn = intercept(origWarn);
30814
+ console.error = intercept(origError);
30815
+ process.emitWarning = ((warning, ...rest) => {
30816
+ const sink = consoleCaptureStorage.getStore();
30817
+ if (!sink) {
30818
+ origEmitWarning(warning, ...rest);
30819
+ return;
30820
+ }
30821
+ let msg = typeof warning === "string" ? warning : warning.message;
30822
+ let type = typeof rest[0] === "string" ? rest[0] : void 0;
30823
+ if (rest[0] && typeof rest[0] === "object") {
30824
+ const opts = rest[0];
30825
+ if (opts.type) type = opts.type;
30826
+ if (opts.detail) msg += ` - ${opts.detail}`;
30827
+ }
30828
+ if (!type && warning instanceof Error) type = warning.name;
30829
+ sink(type ? `${type}: ${msg}` : msg);
30830
+ });
30831
+ }
30832
+ async function withCapturedConsole(fn, onMessage) {
30833
+ patchConsoleGlobals();
30834
+ return consoleCaptureStorage.run(onMessage, fn);
30835
+ }
30757
30836
  var adapter = {
30758
30837
  ...descriptor,
30759
30838
  patternsMetadata: patterns_default,
@@ -30818,7 +30897,25 @@ var adapter = {
30818
30897
  const startedAt = /* @__PURE__ */ new Date();
30819
30898
  const issues = [];
30820
30899
  const errors = [];
30821
- const absoluteFiles = ctx.targetFiles.map((f) => import_path3.default.resolve(ctx.repositoryRoot, f));
30900
+ const allAbsoluteFiles = ctx.targetFiles.map((f) => import_path3.default.resolve(ctx.repositoryRoot, f));
30901
+ const absoluteFiles = [];
30902
+ for (const f of allAbsoluteFiles) {
30903
+ const skipReason = shouldSkipFile(f, ctx.repositoryRoot);
30904
+ if (skipReason) {
30905
+ const relPath = import_path3.default.relative(ctx.repositoryRoot, f);
30906
+ ctx.logger.debug("Skipping file", { toolId: TOOL_ID2, file: relPath, reason: skipReason });
30907
+ } else {
30908
+ absoluteFiles.push(f);
30909
+ }
30910
+ }
30911
+ if (absoluteFiles.length < allAbsoluteFiles.length) {
30912
+ const skipped = allAbsoluteFiles.length - absoluteFiles.length;
30913
+ ctx.logger.info("Skipped oversized/generated files", {
30914
+ toolId: TOOL_ID2,
30915
+ skipped,
30916
+ remaining: absoluteFiles.length
30917
+ });
30918
+ }
30822
30919
  if (absoluteFiles.length === 0) {
30823
30920
  const completedAt2 = /* @__PURE__ */ new Date();
30824
30921
  return (0, import_tooling.buildResult)(TOOL_ID2, startedAt, completedAt2, issues, errors, ctx.targetFiles.length);
@@ -30840,7 +30937,16 @@ var adapter = {
30840
30937
  }
30841
30938
  const eslintOptions = { cwd: ctx.repositoryRoot };
30842
30939
  if (codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile) {
30843
- eslintOptions.overrideConfigFile = codacyConfig.localConfigurationFile;
30940
+ const projectESLint = await resolveProjectESLint(ctx.repositoryRoot);
30941
+ if (projectESLint) {
30942
+ ESLintClass = projectESLint.ESLintClass;
30943
+ ctx.logger.info("Using project ESLint", {
30944
+ toolId: TOOL_ID2,
30945
+ version: projectESLint.version
30946
+ });
30947
+ } else {
30948
+ eslintOptions.overrideConfigFile = codacyConfig.localConfigurationFile;
30949
+ }
30844
30950
  } else if (codacyConfig.patterns.length > 0) {
30845
30951
  const buildStartMs = Date.now();
30846
30952
  try {
@@ -30876,117 +30982,148 @@ var adapter = {
30876
30982
  let attempt = 0;
30877
30983
  let results = [];
30878
30984
  try {
30879
- const probeStartMs = Date.now();
30880
- let lastProbeFailure;
30881
- while (attempt <= MAX_CONFIG_RETRIES) {
30882
- const probeEslint = new ESLintClass(eslintOptions);
30883
- const failure = await probeConfig(probeEslint, ctx.repositoryRoot);
30884
- if (!failure) {
30885
- lastProbeFailure = void 0;
30886
- break;
30887
- }
30888
- lastProbeFailure = failure;
30889
- const rule = extractInvalidRule(failure.message);
30890
- if (!rule) throw failure;
30891
- if (isParserServicesError(failure.message)) {
30892
- const prefix = rulePluginPrefix(rule);
30893
- if (prefix) {
30894
- const removedRules = removeRulesByPluginFromFlatConfig(
30895
- eslintOptions.overrideConfig,
30896
- prefix
30897
- );
30898
- if (removedRules.length > 0) {
30899
- ctx.logger.warn("Skipping plugin requiring parserServices", {
30900
- toolId: TOOL_ID2,
30901
- plugin: prefix,
30902
- count: removedRules.length
30903
- });
30904
- errors.push({
30905
- toolId: TOOL_ID2,
30906
- phase: "toolConfig",
30907
- kind: "InvalidRuleConfig",
30908
- message: `Plugin "${prefix}/*" (${removedRules.length} rule${removedRules.length === 1 ? "" : "s"}) skipped: requires typescript-eslint parserServices (no tsconfig.json in the analysed repo).`,
30909
- level: "warning"
30910
- });
30911
- attempt++;
30912
- continue;
30985
+ const capturedMessages = [];
30986
+ results = await withCapturedConsole(
30987
+ async () => {
30988
+ let lintResults = [];
30989
+ const probeStartMs = Date.now();
30990
+ let lastProbeFailure;
30991
+ ctx.onProgress?.("Validating config");
30992
+ while (attempt <= MAX_CONFIG_RETRIES) {
30993
+ const probeEslint = new ESLintClass(eslintOptions);
30994
+ const failure = await probeConfig(probeEslint, ctx.repositoryRoot);
30995
+ if (!failure) {
30996
+ lastProbeFailure = void 0;
30997
+ break;
30998
+ }
30999
+ lastProbeFailure = failure;
31000
+ const rule = extractInvalidRule(failure.message);
31001
+ if (!rule) throw failure;
31002
+ if (isParserServicesError(failure.message)) {
31003
+ const prefix = rulePluginPrefix(rule);
31004
+ if (prefix) {
31005
+ const removedRules = removeRulesByPluginFromFlatConfig(
31006
+ eslintOptions.overrideConfig,
31007
+ prefix
31008
+ );
31009
+ if (removedRules.length > 0) {
31010
+ ctx.logger.warn("Skipping plugin requiring parserServices", {
31011
+ toolId: TOOL_ID2,
31012
+ plugin: prefix,
31013
+ count: removedRules.length
31014
+ });
31015
+ errors.push({
31016
+ toolId: TOOL_ID2,
31017
+ phase: "toolConfig",
31018
+ kind: "InvalidRuleConfig",
31019
+ message: `Plugin "${prefix}/*" (${removedRules.length} rule${removedRules.length === 1 ? "" : "s"}) skipped: requires typescript-eslint parserServices (no tsconfig.json in the analysed repo).`,
31020
+ level: "warning"
31021
+ });
31022
+ attempt++;
31023
+ continue;
31024
+ }
31025
+ }
30913
31026
  }
31027
+ const removed = removeRuleFromFlatConfig(eslintOptions.overrideConfig, rule);
31028
+ if (!removed) throw failure;
31029
+ ctx.logger.warn("Skipping rule rejected during probe", { toolId: TOOL_ID2, rule });
31030
+ errors.push({
31031
+ toolId: TOOL_ID2,
31032
+ phase: "toolConfig",
31033
+ kind: "InvalidRuleConfig",
31034
+ message: `Rule "${rule}" was skipped: ${truncateForError(failure.message)}`,
31035
+ level: "warning"
31036
+ });
31037
+ attempt++;
30914
31038
  }
30915
- }
30916
- const removed = removeRuleFromFlatConfig(eslintOptions.overrideConfig, rule);
30917
- if (!removed) throw failure;
30918
- ctx.logger.warn("Skipping rule rejected during probe", { toolId: TOOL_ID2, rule });
30919
- errors.push({
30920
- toolId: TOOL_ID2,
30921
- phase: "toolConfig",
30922
- kind: "InvalidRuleConfig",
30923
- message: `Rule "${rule}" was skipped: ${truncateForError(failure.message)}`,
30924
- level: "warning"
30925
- });
30926
- attempt++;
30927
- }
30928
- ctx.logger.debug("Config probe complete", {
30929
- toolId: TOOL_ID2,
30930
- durationMs: Date.now() - probeStartMs,
30931
- rulesSkipped: attempt
30932
- });
30933
- if (lastProbeFailure) throw lastProbeFailure;
30934
- while (true) {
30935
- try {
30936
- const instantiateStartMs = Date.now();
30937
- const eslint = new ESLintClass(eslintOptions);
30938
- ctx.logger.debug("ESLint instance created", {
31039
+ ctx.logger.debug("Config probe complete", {
30939
31040
  toolId: TOOL_ID2,
30940
- durationMs: Date.now() - instantiateStartMs
31041
+ durationMs: Date.now() - probeStartMs,
31042
+ rulesSkipped: attempt
30941
31043
  });
30942
- const lintStartMs = Date.now();
30943
- results = await eslint.lintFiles(absoluteFiles);
30944
- ctx.logger.debug("lintFiles() complete", {
30945
- toolId: TOOL_ID2,
30946
- durationMs: Date.now() - lintStartMs,
30947
- fileCount: results.length
30948
- });
30949
- break;
30950
- } catch (err) {
30951
- const rule = err instanceof Error ? extractInvalidRule(err.message) : void 0;
30952
- if (!rule || attempt >= MAX_CONFIG_RETRIES) throw err;
30953
- const parserServicesCause = err instanceof Error && isParserServicesError(err.message);
30954
- const prefix = rulePluginPrefix(rule);
30955
- if (prefix) {
30956
- const removedRules = removeRulesByPluginFromFlatConfig(
30957
- eslintOptions.overrideConfig,
30958
- prefix
30959
- );
30960
- if (removedRules.length > 0) {
30961
- 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))}`;
30962
- ctx.logger.warn(
30963
- parserServicesCause ? "Skipping plugin requiring parserServices" : "Skipping plugin that failed during lintFiles",
30964
- { toolId: TOOL_ID2, plugin: prefix, count: removedRules.length, rule }
30965
- );
31044
+ if (lastProbeFailure) throw lastProbeFailure;
31045
+ ctx.onProgress?.(`Preparing to lint ${absoluteFiles.length} files`);
31046
+ while (true) {
31047
+ try {
31048
+ const instantiateStartMs = Date.now();
31049
+ const eslint = new ESLintClass(eslintOptions);
31050
+ ctx.logger.debug("ESLint instance created", {
31051
+ toolId: TOOL_ID2,
31052
+ durationMs: Date.now() - instantiateStartMs
31053
+ });
31054
+ const lintStartMs = Date.now();
31055
+ if (absoluteFiles.length <= LINT_BATCH_SIZE) {
31056
+ lintResults = await eslint.lintFiles(absoluteFiles);
31057
+ } else {
31058
+ lintResults = [];
31059
+ for (let i = 0; i < absoluteFiles.length; i += LINT_BATCH_SIZE) {
31060
+ const batch = absoluteFiles.slice(i, i + LINT_BATCH_SIZE);
31061
+ const batchResults = await eslint.lintFiles(batch);
31062
+ lintResults.push(...batchResults);
31063
+ const processed = Math.min(i + LINT_BATCH_SIZE, absoluteFiles.length);
31064
+ ctx.onProgress?.(`Linting files ${processed}/${absoluteFiles.length}`);
31065
+ await yieldToEventLoop();
31066
+ }
31067
+ }
31068
+ ctx.logger.debug("lintFiles() complete", {
31069
+ toolId: TOOL_ID2,
31070
+ durationMs: Date.now() - lintStartMs,
31071
+ fileCount: lintResults.length
31072
+ });
31073
+ break;
31074
+ } catch (err) {
31075
+ const rule = err instanceof Error ? extractInvalidRule(err.message) : void 0;
31076
+ if (!rule || attempt >= MAX_CONFIG_RETRIES) throw err;
31077
+ const parserServicesCause = err instanceof Error && isParserServicesError(err.message);
31078
+ const prefix = rulePluginPrefix(rule);
31079
+ if (prefix) {
31080
+ const removedRules = removeRulesByPluginFromFlatConfig(
31081
+ eslintOptions.overrideConfig,
31082
+ prefix
31083
+ );
31084
+ if (removedRules.length > 0) {
31085
+ 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))}`;
31086
+ ctx.logger.warn(
31087
+ parserServicesCause ? "Skipping plugin requiring parserServices" : "Skipping plugin that failed during lintFiles",
31088
+ { toolId: TOOL_ID2, plugin: prefix, count: removedRules.length, rule }
31089
+ );
31090
+ errors.push({
31091
+ toolId: TOOL_ID2,
31092
+ phase: "toolConfig",
31093
+ kind: "InvalidRuleConfig",
31094
+ message,
31095
+ level: "warning"
31096
+ });
31097
+ attempt++;
31098
+ continue;
31099
+ }
31100
+ }
31101
+ const removed = removeRuleFromFlatConfig(eslintOptions.overrideConfig, rule);
31102
+ if (!removed) throw err;
31103
+ ctx.logger.warn("Skipping rule that failed during lintFiles", {
31104
+ toolId: TOOL_ID2,
31105
+ rule
31106
+ });
30966
31107
  errors.push({
30967
31108
  toolId: TOOL_ID2,
30968
31109
  phase: "toolConfig",
30969
31110
  kind: "InvalidRuleConfig",
30970
- message,
31111
+ message: `Rule "${rule}" was skipped after lintFiles failure: ${truncateForError(err instanceof Error ? err.message : String(err))}`,
30971
31112
  level: "warning"
30972
31113
  });
30973
31114
  attempt++;
30974
- continue;
30975
31115
  }
30976
31116
  }
30977
- const removed = removeRuleFromFlatConfig(eslintOptions.overrideConfig, rule);
30978
- if (!removed) throw err;
30979
- ctx.logger.warn("Skipping rule that failed during lintFiles", { toolId: TOOL_ID2, rule });
30980
- errors.push({
30981
- toolId: TOOL_ID2,
30982
- phase: "toolConfig",
30983
- kind: "InvalidRuleConfig",
30984
- message: `Rule "${rule}" was skipped after lintFiles failure: ${truncateForError(err instanceof Error ? err.message : String(err))}`,
30985
- level: "warning"
30986
- });
30987
- attempt++;
31117
+ return lintResults;
31118
+ },
31119
+ (msg) => {
31120
+ capturedMessages.push(msg);
30988
31121
  }
31122
+ );
31123
+ for (const msg of capturedMessages) {
31124
+ ctx.logger.warn("ESLint plugin output", { toolId: TOOL_ID2, detail: msg });
30989
31125
  }
31126
+ ctx.onProgress?.(`Processing results from ${results.length} files`);
30990
31127
  for (const fileResult of results) {
30991
31128
  if (fileResult.messages.length === 0) continue;
30992
31129
  const relPath = import_path3.default.relative(ctx.repositoryRoot, fileResult.filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/tools-eslint-9",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "ESLint 9 adapter — Library-mode JavaScript/TypeScript linter",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -79,7 +79,7 @@
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.8.0"
82
+ "@codacy/tooling": "0.9.0"
83
83
  },
84
84
  "license": "MIT",
85
85
  "author": "Codacy <support@codacy.com> (https://www.codacy.com)",