@codacy/analysis-runner 0.10.1 → 0.12.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.
- package/dist/index.d.ts +85 -59
- package/dist/index.js +653 -227
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -63,6 +63,7 @@ __export(index_exports, {
|
|
|
63
63
|
readCodacyYaml: () => readCodacyYaml,
|
|
64
64
|
registerAdapter: () => registerAdapter,
|
|
65
65
|
registerLazyAdapter: () => registerLazyAdapter,
|
|
66
|
+
registerUnsupportedTool: () => registerUnsupportedTool,
|
|
66
67
|
resolveConfigPath: () => resolveConfigPath,
|
|
67
68
|
resolveEngineToAdapter: () => resolveEngineToAdapter,
|
|
68
69
|
rotateLogFiles: () => rotateLogFiles,
|
|
@@ -75,7 +76,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
75
76
|
// src/analyze.ts
|
|
76
77
|
var import_os = __toESM(require("os"));
|
|
77
78
|
var import_path3 = __toESM(require("path"));
|
|
78
|
-
var
|
|
79
|
+
var import_child_process3 = require("child_process");
|
|
79
80
|
var import_util = require("util");
|
|
80
81
|
var import_tooling3 = require("@codacy/tooling");
|
|
81
82
|
|
|
@@ -92,6 +93,9 @@ function registerAdapter(adapter) {
|
|
|
92
93
|
function registerLazyAdapter(descriptor, factory) {
|
|
93
94
|
entries.set(descriptor.id, { descriptor, factory });
|
|
94
95
|
}
|
|
96
|
+
function registerUnsupportedTool(descriptor) {
|
|
97
|
+
entries.set(descriptor.id, { descriptor });
|
|
98
|
+
}
|
|
95
99
|
function resolveEntry(input) {
|
|
96
100
|
const exact = entries.get(input);
|
|
97
101
|
if (exact) return exact;
|
|
@@ -103,15 +107,23 @@ function resolveEntry(input) {
|
|
|
103
107
|
}
|
|
104
108
|
async function loadAdapter(entry) {
|
|
105
109
|
if (entry.resolved) return entry.resolved;
|
|
110
|
+
if (!entry.factory) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Tool "${entry.descriptor.id}" is unsupported and has no local adapter to load`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
106
115
|
entry.resolved = await entry.factory();
|
|
107
116
|
return entry.resolved;
|
|
108
117
|
}
|
|
118
|
+
function isLoadable(entry) {
|
|
119
|
+
return entry.factory !== void 0;
|
|
120
|
+
}
|
|
109
121
|
async function getRegisteredAdapters(toolIds) {
|
|
110
122
|
if (toolIds && toolIds.length > 0) {
|
|
111
|
-
const matched = toolIds.map((id) => resolveEntry(id)).filter((e) => e !== void 0);
|
|
123
|
+
const matched = toolIds.map((id) => resolveEntry(id)).filter((e) => e !== void 0).filter(isLoadable);
|
|
112
124
|
return Promise.all(matched.map(loadAdapter));
|
|
113
125
|
}
|
|
114
|
-
return Promise.all(Array.from(entries.values()).map(loadAdapter));
|
|
126
|
+
return Promise.all(Array.from(entries.values()).filter(isLoadable).map(loadAdapter));
|
|
115
127
|
}
|
|
116
128
|
function getRegisteredDescriptors(toolIds) {
|
|
117
129
|
if (toolIds && toolIds.length > 0) {
|
|
@@ -121,7 +133,7 @@ function getRegisteredDescriptors(toolIds) {
|
|
|
121
133
|
}
|
|
122
134
|
async function getAdapter(toolId) {
|
|
123
135
|
const entry = entries.get(toolId);
|
|
124
|
-
if (!entry) return void 0;
|
|
136
|
+
if (!entry || !isLoadable(entry)) return void 0;
|
|
125
137
|
return loadAdapter(entry);
|
|
126
138
|
}
|
|
127
139
|
function clearAdapters() {
|
|
@@ -226,84 +238,98 @@ function createCodacyConfig(params = {}) {
|
|
|
226
238
|
}
|
|
227
239
|
|
|
228
240
|
// src/files.ts
|
|
229
|
-
var
|
|
241
|
+
var import_child_process2 = require("child_process");
|
|
230
242
|
var import_fs = __toESM(require("fs"));
|
|
231
243
|
var import_path2 = __toESM(require("path"));
|
|
244
|
+
var import_minimatch2 = require("minimatch");
|
|
245
|
+
|
|
246
|
+
// src/glob-matcher.ts
|
|
232
247
|
var import_minimatch = require("minimatch");
|
|
233
|
-
var
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
cwd: repositoryRoot,
|
|
238
|
-
encoding: "utf-8",
|
|
239
|
-
maxBuffer: 50 * 1024 * 1024
|
|
240
|
-
}).trim();
|
|
241
|
-
const untracked = (0, import_child_process.execSync)("git ls-files --others --exclude-standard", {
|
|
242
|
-
cwd: repositoryRoot,
|
|
243
|
-
encoding: "utf-8",
|
|
244
|
-
maxBuffer: 50 * 1024 * 1024
|
|
245
|
-
}).trim();
|
|
246
|
-
const files = /* @__PURE__ */ new Set();
|
|
247
|
-
for (const f of tracked.split("\n")) {
|
|
248
|
-
if (f) files.add(f);
|
|
249
|
-
}
|
|
250
|
-
for (const f of untracked.split("\n")) {
|
|
251
|
-
if (f) files.add(f);
|
|
252
|
-
}
|
|
253
|
-
return Array.from(files).sort();
|
|
254
|
-
} catch {
|
|
255
|
-
return [];
|
|
256
|
-
}
|
|
248
|
+
var PURE_EXTENSION_PATTERN = /^\*\*\/\*(\.[^*/.]+)$/;
|
|
249
|
+
function pureExtensionOf(pattern) {
|
|
250
|
+
const m = PURE_EXTENSION_PATTERN.exec(pattern);
|
|
251
|
+
return m ? m[1] : null;
|
|
257
252
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
253
|
+
var CompiledMatcher = class {
|
|
254
|
+
/** True if any pattern is the bare `"*"` wildcard (matches every file). */
|
|
255
|
+
matchAll;
|
|
256
|
+
literalPaths;
|
|
257
|
+
literalNames;
|
|
258
|
+
exts;
|
|
259
|
+
// Residual globs are split by whether they contain a path separator so the
|
|
260
|
+
// hot loop never runs a full-path match for a slash-less pattern (which can
|
|
261
|
+
// only ever match via the basename) — that call is always wasted on nested
|
|
262
|
+
// files. Slash patterns match against the full path; slash-less ones against
|
|
263
|
+
// the basename, which together reproduce the old dual check exactly.
|
|
264
|
+
residualPath;
|
|
265
|
+
residualName;
|
|
266
|
+
constructor(patterns) {
|
|
267
|
+
let matchAll = false;
|
|
268
|
+
const literalPaths = /* @__PURE__ */ new Set();
|
|
269
|
+
const literalNames = /* @__PURE__ */ new Set();
|
|
270
|
+
const exts = /* @__PURE__ */ new Set();
|
|
271
|
+
const residualPath = [];
|
|
272
|
+
const residualName = [];
|
|
273
|
+
for (const pattern of patterns) {
|
|
274
|
+
if (pattern === "*") {
|
|
275
|
+
matchAll = true;
|
|
271
276
|
continue;
|
|
272
277
|
}
|
|
273
|
-
|
|
278
|
+
const ext = pureExtensionOf(pattern);
|
|
279
|
+
if (ext !== null) {
|
|
280
|
+
exts.add(ext);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const mm = new import_minimatch.Minimatch(pattern, { dot: true });
|
|
284
|
+
if (mm.hasMagic()) {
|
|
285
|
+
(pattern.includes("/") ? residualPath : residualName).push(mm);
|
|
286
|
+
} else if (pattern.includes("/")) {
|
|
287
|
+
literalPaths.add(pattern);
|
|
288
|
+
} else {
|
|
289
|
+
literalNames.add(pattern);
|
|
290
|
+
}
|
|
274
291
|
}
|
|
275
|
-
|
|
292
|
+
this.matchAll = matchAll;
|
|
293
|
+
this.literalPaths = literalPaths;
|
|
294
|
+
this.literalNames = literalNames;
|
|
295
|
+
this.exts = exts;
|
|
296
|
+
this.residualPath = residualPath;
|
|
297
|
+
this.residualName = residualName;
|
|
276
298
|
}
|
|
277
|
-
|
|
278
|
-
|
|
299
|
+
/** True when this matcher holds no patterns at all (matches nothing). */
|
|
300
|
+
get isEmpty() {
|
|
301
|
+
return !this.matchAll && this.literalPaths.size === 0 && this.literalNames.size === 0 && this.exts.size === 0 && this.residualPath.length === 0 && this.residualName.length === 0;
|
|
279
302
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Returns true if `file` matches any pattern. `basename` must be
|
|
305
|
+
* `path.basename(file)` — passed in so callers compute it once per file.
|
|
306
|
+
*
|
|
307
|
+
* Mirrors the historical dual check: a pattern matches if it matches the full
|
|
308
|
+
* relative path, or (for patterns with no path separator) the basename. For
|
|
309
|
+
* globstar/slash patterns the full-path check already subsumes the basename
|
|
310
|
+
* check, so gating the basename check on `!includes("/")` is equivalent to
|
|
311
|
+
* the old unconditional basename check used by `routeFilesToTool`.
|
|
312
|
+
*/
|
|
313
|
+
matches(file, basename) {
|
|
314
|
+
if (this.matchAll) return true;
|
|
315
|
+
if (this.literalPaths.size > 0 && this.literalPaths.has(file)) return true;
|
|
316
|
+
if (this.literalNames.size > 0 && this.literalNames.has(basename)) return true;
|
|
317
|
+
if (this.exts.size > 0) {
|
|
318
|
+
const dot = basename.lastIndexOf(".");
|
|
319
|
+
if (dot >= 0 && this.exts.has(basename.slice(dot))) return true;
|
|
320
|
+
}
|
|
321
|
+
for (const m of this.residualPath) {
|
|
322
|
+
if (m.match(file)) return true;
|
|
323
|
+
}
|
|
324
|
+
for (const m of this.residualName) {
|
|
325
|
+
if (m.match(basename)) return true;
|
|
326
|
+
}
|
|
327
|
+
return false;
|
|
298
328
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
(pattern) => (0, import_minimatch.minimatch)(basename, pattern, { dot: true }) || (0, import_minimatch.minimatch)(file, pattern, { dot: true })
|
|
304
|
-
);
|
|
305
|
-
});
|
|
306
|
-
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/git-scope.ts
|
|
332
|
+
var import_child_process = require("child_process");
|
|
307
333
|
function listStagedFiles(repositoryRoot) {
|
|
308
334
|
try {
|
|
309
335
|
const output = (0, import_child_process.execSync)("git diff --cached --name-only --diff-filter=d", {
|
|
@@ -333,7 +359,7 @@ function listDiffFiles(repositoryRoot, base) {
|
|
|
333
359
|
}
|
|
334
360
|
function listPrFiles(repositoryRoot) {
|
|
335
361
|
try {
|
|
336
|
-
const output = (0, import_child_process.execSync)(
|
|
362
|
+
const output = (0, import_child_process.execSync)('gh pr view --json files --jq ".files[].path"', {
|
|
337
363
|
cwd: repositoryRoot,
|
|
338
364
|
encoding: "utf-8",
|
|
339
365
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -389,23 +415,95 @@ function detectDefaultBranch(repositoryRoot) {
|
|
|
389
415
|
"Cannot auto-detect default branch: no origin/HEAD, origin/main, or origin/master found. Specify a base explicitly with --diff <base>."
|
|
390
416
|
);
|
|
391
417
|
}
|
|
418
|
+
|
|
419
|
+
// src/files.ts
|
|
420
|
+
var MAX_FILE_SIZE_BYTES = 150 * 1024;
|
|
421
|
+
function listRepoFiles(repositoryRoot) {
|
|
422
|
+
try {
|
|
423
|
+
const tracked = (0, import_child_process2.execSync)("git ls-files", {
|
|
424
|
+
cwd: repositoryRoot,
|
|
425
|
+
encoding: "utf-8",
|
|
426
|
+
maxBuffer: 50 * 1024 * 1024
|
|
427
|
+
}).trim();
|
|
428
|
+
const untracked = (0, import_child_process2.execSync)("git ls-files --others --exclude-standard", {
|
|
429
|
+
cwd: repositoryRoot,
|
|
430
|
+
encoding: "utf-8",
|
|
431
|
+
maxBuffer: 50 * 1024 * 1024
|
|
432
|
+
}).trim();
|
|
433
|
+
const files = /* @__PURE__ */ new Set();
|
|
434
|
+
for (const f of tracked.split("\n")) {
|
|
435
|
+
if (f) files.add(f);
|
|
436
|
+
}
|
|
437
|
+
for (const f of untracked.split("\n")) {
|
|
438
|
+
if (f) files.add(f);
|
|
439
|
+
}
|
|
440
|
+
return Array.from(files).sort();
|
|
441
|
+
} catch {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function filterOversizedFiles(files, repositoryRoot, logger) {
|
|
446
|
+
const result = [];
|
|
447
|
+
const skipped = [];
|
|
448
|
+
for (const f of files) {
|
|
449
|
+
try {
|
|
450
|
+
const stat = import_fs.default.statSync(import_path2.default.join(repositoryRoot, f));
|
|
451
|
+
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
452
|
+
skipped.push({ filePath: f, sizeBytes: stat.size });
|
|
453
|
+
logger?.debug("Skipping oversized file", {
|
|
454
|
+
file: f,
|
|
455
|
+
sizeKB: Math.round(stat.size / 1024),
|
|
456
|
+
limitKB: MAX_FILE_SIZE_BYTES / 1024
|
|
457
|
+
});
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
result.push(f);
|
|
463
|
+
}
|
|
464
|
+
if (skipped.length > 0) {
|
|
465
|
+
logger?.info("Filtered oversized files", {
|
|
466
|
+
skipped: skipped.length,
|
|
467
|
+
remaining: result.length,
|
|
468
|
+
limitKB: MAX_FILE_SIZE_BYTES / 1024
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return { files: result, skipped };
|
|
472
|
+
}
|
|
473
|
+
function applyExclusions(files, globalExclude, toolExclude) {
|
|
474
|
+
if (globalExclude.length === 0 && toolExclude.length === 0) return files;
|
|
475
|
+
const matcher = new CompiledMatcher([...globalExclude, ...toolExclude]);
|
|
476
|
+
if (matcher.isEmpty) return files;
|
|
477
|
+
return files.filter((file) => !matcher.matches(file, import_path2.default.basename(file)));
|
|
478
|
+
}
|
|
479
|
+
function routeFilesToTool(files, adapter, toolConfig) {
|
|
480
|
+
const patterns = [...adapter.filePatterns];
|
|
481
|
+
if (toolConfig?.extraFilePatterns) {
|
|
482
|
+
patterns.push(...toolConfig.extraFilePatterns);
|
|
483
|
+
}
|
|
484
|
+
const matcher = new CompiledMatcher(patterns);
|
|
485
|
+
if (matcher.matchAll) return files;
|
|
486
|
+
return files.filter((file) => matcher.matches(file, import_path2.default.basename(file)));
|
|
487
|
+
}
|
|
392
488
|
function filterByTargetFiles(allFiles, targetPatterns) {
|
|
393
489
|
if (targetPatterns.length === 0) return allFiles;
|
|
394
490
|
const normalized = targetPatterns.map((p) => p.replace(/^\.\//, ""));
|
|
491
|
+
const exact = new Set(normalized);
|
|
492
|
+
const globs = normalized.map((p) => new import_minimatch2.Minimatch(p, { dot: true, matchBase: false })).filter((m) => m.hasMagic());
|
|
493
|
+
const dirPrefixes = normalized.filter((p) => p.endsWith("/"));
|
|
395
494
|
return allFiles.filter(
|
|
396
|
-
(f) =>
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
pattern.endsWith("/") && f.startsWith(pattern)
|
|
402
|
-
)
|
|
495
|
+
(f) => (
|
|
496
|
+
// Exact match (fast path)
|
|
497
|
+
exact.has(f) || // Glob match
|
|
498
|
+
globs.some((m) => m.match(f)) || // Directory prefix (e.g. "src/subdir/" matches all files under it)
|
|
499
|
+
dirPrefixes.some((p) => f.startsWith(p))
|
|
403
500
|
)
|
|
404
501
|
);
|
|
405
502
|
}
|
|
406
503
|
|
|
407
504
|
// src/executor.ts
|
|
408
505
|
var import_tooling2 = require("@codacy/tooling");
|
|
506
|
+
var HARD_TIMEOUT_GRACE_MS = 2e3;
|
|
409
507
|
async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
|
|
410
508
|
const startTime = Date.now();
|
|
411
509
|
ctx.logger.debug("runTool started", {
|
|
@@ -413,12 +511,18 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
|
|
|
413
511
|
fileCount: ctx.targetFiles.length,
|
|
414
512
|
timeoutMs
|
|
415
513
|
});
|
|
514
|
+
const abortController = new AbortController();
|
|
515
|
+
ctx.signal = abortController.signal;
|
|
516
|
+
const abortTimer = setTimeout(() => abortController.abort(), timeoutMs);
|
|
517
|
+
abortTimer.unref?.();
|
|
518
|
+
let hardTimer;
|
|
416
519
|
try {
|
|
417
520
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
418
|
-
setTimeout(
|
|
521
|
+
hardTimer = setTimeout(
|
|
419
522
|
() => reject(new Error(`TIMEOUT: Tool ${adapter.id} exceeded ${(0, import_tooling2.formatDuration)(timeoutMs)}`)),
|
|
420
|
-
timeoutMs
|
|
523
|
+
timeoutMs + HARD_TIMEOUT_GRACE_MS
|
|
421
524
|
);
|
|
525
|
+
hardTimer.unref?.();
|
|
422
526
|
});
|
|
423
527
|
const result = await Promise.race([adapter.analyze(ctx, codacyToolConfig), timeoutPromise]);
|
|
424
528
|
ctx.logger.debug("runTool completed", {
|
|
@@ -465,6 +569,9 @@ async function runTool(adapter, ctx, codacyToolConfig, timeoutMs) {
|
|
|
465
569
|
filesAnalyzed: ctx.targetFiles.length
|
|
466
570
|
}
|
|
467
571
|
};
|
|
572
|
+
} finally {
|
|
573
|
+
clearTimeout(abortTimer);
|
|
574
|
+
if (hardTimer) clearTimeout(hardTimer);
|
|
468
575
|
}
|
|
469
576
|
}
|
|
470
577
|
async function runToolsSequential(tools, timeoutMs, callbacks) {
|
|
@@ -623,7 +730,7 @@ function computePatternSummary(issues, lookup) {
|
|
|
623
730
|
}
|
|
624
731
|
|
|
625
732
|
// src/analyze.ts
|
|
626
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
733
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
627
734
|
var nullLogger = {
|
|
628
735
|
debug: () => {
|
|
629
736
|
},
|
|
@@ -634,10 +741,24 @@ var nullLogger = {
|
|
|
634
741
|
error: () => {
|
|
635
742
|
}
|
|
636
743
|
};
|
|
744
|
+
var CHECK_CONCURRENCY = 12;
|
|
745
|
+
async function mapWithConcurrency(items, concurrency, task) {
|
|
746
|
+
const results = new Array(items.length);
|
|
747
|
+
let next = 0;
|
|
748
|
+
async function worker() {
|
|
749
|
+
while (next < items.length) {
|
|
750
|
+
const index = next++;
|
|
751
|
+
results[index] = await task(items[index], index);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
|
|
755
|
+
await Promise.all(workers);
|
|
756
|
+
return results;
|
|
757
|
+
}
|
|
637
758
|
async function analyze(options) {
|
|
638
759
|
const startedAt = /* @__PURE__ */ new Date();
|
|
639
760
|
const mode = options.mode || "standard";
|
|
640
|
-
const toolTimeout = options.toolTimeout ||
|
|
761
|
+
const toolTimeout = options.toolTimeout || 9e5;
|
|
641
762
|
const parallelTools = options.parallelTools || 1;
|
|
642
763
|
const logger = options.logger ?? nullLogger;
|
|
643
764
|
const phaseTimings = {
|
|
@@ -727,23 +848,25 @@ async function analyze(options) {
|
|
|
727
848
|
const unavailable = [];
|
|
728
849
|
const availabilityTimeout = 6e4;
|
|
729
850
|
const availabilityStartMs = Date.now();
|
|
730
|
-
|
|
851
|
+
const checkAvailabilityOf = async (adapter) => {
|
|
731
852
|
const ctx = makeToolContext(baseCtx, adapter, []);
|
|
732
853
|
const checkStart = Date.now();
|
|
733
854
|
logger.debug("Checking availability", { toolId: adapter.id });
|
|
855
|
+
let timer;
|
|
734
856
|
try {
|
|
735
857
|
const availability = await Promise.race([
|
|
736
858
|
adapter.checkAvailability(ctx),
|
|
737
|
-
new Promise(
|
|
738
|
-
|
|
859
|
+
new Promise((_, reject) => {
|
|
860
|
+
timer = setTimeout(
|
|
739
861
|
() => reject(
|
|
740
862
|
new Error(
|
|
741
863
|
`checkAvailability timed out after ${(0, import_tooling3.formatDuration)(availabilityTimeout)}`
|
|
742
864
|
)
|
|
743
865
|
),
|
|
744
866
|
availabilityTimeout
|
|
745
|
-
)
|
|
746
|
-
|
|
867
|
+
);
|
|
868
|
+
timer.unref?.();
|
|
869
|
+
})
|
|
747
870
|
]);
|
|
748
871
|
if (availability.available) {
|
|
749
872
|
logger.debug("Tool available", {
|
|
@@ -752,38 +875,57 @@ async function analyze(options) {
|
|
|
752
875
|
installation: availability.installation,
|
|
753
876
|
durationMs: Date.now() - checkStart
|
|
754
877
|
});
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
878
|
+
return {
|
|
879
|
+
kind: "ready",
|
|
880
|
+
status: {
|
|
881
|
+
toolId: adapter.id,
|
|
882
|
+
displayName: adapter.displayName,
|
|
883
|
+
version: availability.version ?? adapter.preferredVersion,
|
|
884
|
+
installation: availability.installation ?? "global",
|
|
885
|
+
runner: "local"
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
logger.warn("Tool unavailable", {
|
|
890
|
+
toolId: adapter.id,
|
|
891
|
+
reason: availability.reason,
|
|
892
|
+
durationMs: Date.now() - checkStart
|
|
893
|
+
});
|
|
894
|
+
return {
|
|
895
|
+
kind: "unavailable",
|
|
896
|
+
status: {
|
|
769
897
|
toolId: adapter.id,
|
|
770
898
|
displayName: adapter.displayName,
|
|
771
899
|
reason: availability.reason || "Tool is not available",
|
|
772
900
|
...availability.installHint ? { remediation: availability.installHint } : {}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
901
|
+
}
|
|
902
|
+
};
|
|
775
903
|
} catch (err) {
|
|
776
904
|
logger.warn("Availability check failed", {
|
|
777
905
|
toolId: adapter.id,
|
|
778
906
|
error: err instanceof Error ? err.message : String(err),
|
|
779
907
|
durationMs: Date.now() - checkStart
|
|
780
908
|
});
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
909
|
+
return {
|
|
910
|
+
kind: "unavailable",
|
|
911
|
+
status: {
|
|
912
|
+
toolId: adapter.id,
|
|
913
|
+
displayName: adapter.displayName,
|
|
914
|
+
reason: `Availability check failed: ${err instanceof Error ? err.message : String(err)}`
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
} finally {
|
|
918
|
+
if (timer) clearTimeout(timer);
|
|
786
919
|
}
|
|
920
|
+
};
|
|
921
|
+
const availabilityOutcomes = await mapWithConcurrency(
|
|
922
|
+
adapters,
|
|
923
|
+
CHECK_CONCURRENCY,
|
|
924
|
+
checkAvailabilityOf
|
|
925
|
+
);
|
|
926
|
+
for (const outcome of availabilityOutcomes) {
|
|
927
|
+
if (outcome.kind === "ready") ready.push(outcome.status);
|
|
928
|
+
else unavailable.push(outcome.status);
|
|
787
929
|
}
|
|
788
930
|
phaseTimings.availabilityMs = Date.now() - availabilityStartMs;
|
|
789
931
|
const capability = {
|
|
@@ -800,6 +942,31 @@ async function analyze(options) {
|
|
|
800
942
|
readyAfter: capability.ready.length,
|
|
801
943
|
configuredToolCount: configuredIds.size
|
|
802
944
|
});
|
|
945
|
+
const representedIds = /* @__PURE__ */ new Set([
|
|
946
|
+
...capability.ready.map((t) => t.toolId),
|
|
947
|
+
...capability.unavailable.map((t) => t.toolId)
|
|
948
|
+
]);
|
|
949
|
+
const requestedLower = options.tools?.map((t) => t.toLowerCase());
|
|
950
|
+
const inScope = (toolId) => {
|
|
951
|
+
if (!requestedLower) return true;
|
|
952
|
+
const id = toolId.toLowerCase();
|
|
953
|
+
return requestedLower.some((r) => id.includes(r) || r.includes(id));
|
|
954
|
+
};
|
|
955
|
+
const descriptorsById = new Map(getRegisteredDescriptors().map((d) => [d.id, d]));
|
|
956
|
+
for (const toolConfig of codacyConfig.tools) {
|
|
957
|
+
if (!toolConfig?.toolId) continue;
|
|
958
|
+
if (representedIds.has(toolConfig.toolId)) continue;
|
|
959
|
+
if (!inScope(toolConfig.toolId)) continue;
|
|
960
|
+
const descriptor = descriptorsById.get(toolConfig.toolId);
|
|
961
|
+
if (!descriptor?.unsupported) continue;
|
|
962
|
+
capability.unavailable.push({
|
|
963
|
+
toolId: toolConfig.toolId,
|
|
964
|
+
displayName: descriptor.displayName ?? toolConfig.toolId,
|
|
965
|
+
reason: "No local analyzer (configured on Codacy Cloud)",
|
|
966
|
+
availableVia: "cloud"
|
|
967
|
+
});
|
|
968
|
+
representedIds.add(toolConfig.toolId);
|
|
969
|
+
}
|
|
803
970
|
if (mode === "auto-install" && capability.unavailable.length > 0) {
|
|
804
971
|
const installStartMs = Date.now();
|
|
805
972
|
const installErrors = [];
|
|
@@ -908,8 +1075,9 @@ async function analyze(options) {
|
|
|
908
1075
|
}
|
|
909
1076
|
return buildResult(startedAt, options.repositoryRoot, mode, capability, [], [], []);
|
|
910
1077
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1078
|
+
const strictMissing = capability.unavailable.filter((u) => u.availableVia !== "cloud");
|
|
1079
|
+
if (mode === "strict" && strictMissing.length > 0) {
|
|
1080
|
+
const missingTools = strictMissing.map((u) => u.toolId).join(", ");
|
|
913
1081
|
return buildResult(
|
|
914
1082
|
startedAt,
|
|
915
1083
|
options.repositoryRoot,
|
|
@@ -950,25 +1118,52 @@ async function analyze(options) {
|
|
|
950
1118
|
allFiles = filterByTargetFiles(allFiles, options.files);
|
|
951
1119
|
logger.debug("Filtered by target files", { afterFilter: allFiles.length });
|
|
952
1120
|
}
|
|
953
|
-
|
|
1121
|
+
const oversize = filterOversizedFiles(allFiles, options.repositoryRoot, logger);
|
|
1122
|
+
allFiles = oversize.files;
|
|
1123
|
+
const skippedFiles = oversize.skipped;
|
|
954
1124
|
phaseTimings.fileDiscoveryMs = Date.now() - fileDiscoveryStartMs;
|
|
955
1125
|
const fileRoutingStartMs = Date.now();
|
|
956
|
-
const
|
|
1126
|
+
const readyById = new Map(capability.ready.map((r) => [r.toolId, r]));
|
|
1127
|
+
const readyAdapters = adapters.filter((a) => readyById.has(a.id));
|
|
1128
|
+
const filesAfterGlobalExclude = applyExclusions(allFiles, globalExclude, []);
|
|
957
1129
|
const toolRuns = [];
|
|
1130
|
+
const candidates = [];
|
|
958
1131
|
for (const adapter of readyAdapters) {
|
|
959
1132
|
const toolConfig = getToolConfig(codacyConfig, adapter.id);
|
|
960
1133
|
if (!toolConfig) continue;
|
|
961
|
-
const toolExclude = toolConfig
|
|
962
|
-
const
|
|
963
|
-
const toolFiles = routeFilesToTool(
|
|
964
|
-
const
|
|
965
|
-
if (
|
|
966
|
-
|
|
1134
|
+
const toolExclude = toolConfig.exclude || [];
|
|
1135
|
+
const filesForTool = toolExclude.length > 0 ? applyExclusions(filesAfterGlobalExclude, toolExclude, []) : filesAfterGlobalExclude;
|
|
1136
|
+
const toolFiles = routeFilesToTool(filesForTool, adapter, toolConfig);
|
|
1137
|
+
const readyEntry = readyById.get(adapter.id);
|
|
1138
|
+
if (readyEntry) {
|
|
1139
|
+
readyEntry.filesRouted = toolFiles.length;
|
|
1140
|
+
}
|
|
1141
|
+
candidates.push({
|
|
1142
|
+
adapter,
|
|
1143
|
+
toolFiles,
|
|
1144
|
+
configCtx: makeToolContext(baseCtx, adapter, toolFiles),
|
|
1145
|
+
readyEntry
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
const configChecks = await mapWithConcurrency(
|
|
1149
|
+
candidates,
|
|
1150
|
+
CHECK_CONCURRENCY,
|
|
1151
|
+
async (c) => {
|
|
1152
|
+
try {
|
|
1153
|
+
return await c.adapter.checkLocalConfigurationFile(c.configCtx);
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
logger.debug("Local config check failed, assuming none", {
|
|
1156
|
+
toolId: c.adapter.id,
|
|
1157
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1158
|
+
});
|
|
1159
|
+
return { found: false };
|
|
1160
|
+
}
|
|
967
1161
|
}
|
|
968
|
-
|
|
969
|
-
|
|
1162
|
+
);
|
|
1163
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
1164
|
+
const { adapter, toolFiles, configCtx, readyEntry } = candidates[i];
|
|
1165
|
+
const configCheck = configChecks[i];
|
|
970
1166
|
const codacyToolConfig = buildToolConfig(codacyConfig, adapter, configCheck);
|
|
971
|
-
const readyEntry = capability.ready.find((r) => r.toolId === adapter.id);
|
|
972
1167
|
if (readyEntry) {
|
|
973
1168
|
if (configCheck.found && codacyToolConfig.useLocalConfigurationFile) {
|
|
974
1169
|
readyEntry.configSource = "local";
|
|
@@ -1042,7 +1237,8 @@ async function analyze(options) {
|
|
|
1042
1237
|
allErrors,
|
|
1043
1238
|
allSummaries,
|
|
1044
1239
|
patternSummary,
|
|
1045
|
-
phaseTimings
|
|
1240
|
+
phaseTimings,
|
|
1241
|
+
skippedFiles
|
|
1046
1242
|
);
|
|
1047
1243
|
}
|
|
1048
1244
|
function makeToolContext(base, adapter, targetFiles) {
|
|
@@ -1068,7 +1264,7 @@ function resolveGitScope(repositoryRoot, scope) {
|
|
|
1068
1264
|
const base = scope.diff === true ? void 0 : scope.diff;
|
|
1069
1265
|
return listDiffFiles(repositoryRoot, base);
|
|
1070
1266
|
}
|
|
1071
|
-
function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings) {
|
|
1267
|
+
function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors, toolResults, patternSummary, phaseTimings, skippedFiles) {
|
|
1072
1268
|
const completedAt = /* @__PURE__ */ new Date();
|
|
1073
1269
|
return {
|
|
1074
1270
|
metadata: {
|
|
@@ -1083,7 +1279,8 @@ function buildResult(startedAt, repositoryRoot, mode, capability, issues, errors
|
|
|
1083
1279
|
issues,
|
|
1084
1280
|
errors,
|
|
1085
1281
|
toolResults,
|
|
1086
|
-
...patternSummary ? { patternSummary } : {}
|
|
1282
|
+
...patternSummary ? { patternSummary } : {},
|
|
1283
|
+
...skippedFiles && skippedFiles.length > 0 ? { skippedFiles } : {}
|
|
1087
1284
|
};
|
|
1088
1285
|
}
|
|
1089
1286
|
|
|
@@ -1092,6 +1289,7 @@ var import_path4 = __toESM(require("path"));
|
|
|
1092
1289
|
var import_tooling4 = require("@codacy/tooling");
|
|
1093
1290
|
var import_ansis = __toESM(require("ansis"));
|
|
1094
1291
|
var import_cli_table3 = __toESM(require("cli-table3"));
|
|
1292
|
+
var MAX_SKIPPED_FILES_SHOWN = 5;
|
|
1095
1293
|
function formatOutput(result, format) {
|
|
1096
1294
|
switch (format) {
|
|
1097
1295
|
case "json":
|
|
@@ -1249,9 +1447,30 @@ function formatText(result) {
|
|
|
1249
1447
|
lines.push(...formatErrorSection(result));
|
|
1250
1448
|
lines.push("");
|
|
1251
1449
|
}
|
|
1450
|
+
if (result.skippedFiles && result.skippedFiles.length > 0) {
|
|
1451
|
+
lines.push(...formatSkippedFilesSection(result.skippedFiles));
|
|
1452
|
+
lines.push("");
|
|
1453
|
+
}
|
|
1252
1454
|
lines.push(formatTextSummary(result, isInspect));
|
|
1253
1455
|
return lines.join("\n");
|
|
1254
1456
|
}
|
|
1457
|
+
function formatSkippedFilesSection(skipped) {
|
|
1458
|
+
const limitKB = Math.round(MAX_FILE_SIZE_BYTES / 1024);
|
|
1459
|
+
const out = [""];
|
|
1460
|
+
out.push(
|
|
1461
|
+
import_ansis.default.yellow(
|
|
1462
|
+
`\u26A0 ${skipped.length} file${skipped.length !== 1 ? "s" : ""} skipped (exceeding the ${limitKB} KB size limit):`
|
|
1463
|
+
)
|
|
1464
|
+
);
|
|
1465
|
+
for (const f of skipped.slice(0, MAX_SKIPPED_FILES_SHOWN)) {
|
|
1466
|
+
out.push(import_ansis.default.dim(` - ${f.filePath} (${Math.round(f.sizeBytes / 1024)} KB)`));
|
|
1467
|
+
}
|
|
1468
|
+
const remaining = skipped.length - MAX_SKIPPED_FILES_SHOWN;
|
|
1469
|
+
if (remaining > 0) {
|
|
1470
|
+
out.push(import_ansis.default.dim(` \u2026 and ${remaining} more`));
|
|
1471
|
+
}
|
|
1472
|
+
return out;
|
|
1473
|
+
}
|
|
1255
1474
|
function formatTextSummary(result, isInspect) {
|
|
1256
1475
|
const lines = [];
|
|
1257
1476
|
const inspect = isInspect ?? result.metadata.executionMode === "inspect";
|
|
@@ -1301,13 +1520,14 @@ function formatTextSummary(result, isInspect) {
|
|
|
1301
1520
|
]);
|
|
1302
1521
|
}
|
|
1303
1522
|
for (const tool of unavailableTools) {
|
|
1523
|
+
const isUnsupported = tool.availableVia === "cloud";
|
|
1304
1524
|
table.push([
|
|
1305
|
-
import_ansis.default.dim.red(tool.displayName ?? tool.toolId),
|
|
1525
|
+
isUnsupported ? import_ansis.default.dim(tool.displayName ?? tool.toolId) : import_ansis.default.dim.red(tool.displayName ?? tool.toolId),
|
|
1306
1526
|
"-",
|
|
1527
|
+
isUnsupported ? import_ansis.default.dim("Unsupported") : "-",
|
|
1307
1528
|
"-",
|
|
1308
1529
|
"-",
|
|
1309
|
-
"
|
|
1310
|
-
tool.reason
|
|
1530
|
+
isUnsupported ? import_ansis.default.dim("No local analyzer (Codacy Cloud)") : tool.reason
|
|
1311
1531
|
]);
|
|
1312
1532
|
}
|
|
1313
1533
|
lines.push(table.toString());
|
|
@@ -1332,13 +1552,14 @@ function formatTextSummary(result, isInspect) {
|
|
|
1332
1552
|
]);
|
|
1333
1553
|
}
|
|
1334
1554
|
for (const tool of unavailableTools) {
|
|
1555
|
+
const isUnsupported = tool.availableVia === "cloud";
|
|
1335
1556
|
table.push([
|
|
1336
|
-
import_ansis.default.dim.red(tool.displayName ?? tool.toolId),
|
|
1337
|
-
"-",
|
|
1557
|
+
isUnsupported ? import_ansis.default.dim(tool.displayName ?? tool.toolId) : import_ansis.default.dim.red(tool.displayName ?? tool.toolId),
|
|
1338
1558
|
"-",
|
|
1559
|
+
isUnsupported ? import_ansis.default.dim("Unsupported") : "-",
|
|
1339
1560
|
"-",
|
|
1340
1561
|
"-",
|
|
1341
|
-
import_ansis.default.dim("missing binary"),
|
|
1562
|
+
isUnsupported ? import_ansis.default.dim("cloud-only") : import_ansis.default.dim("missing binary"),
|
|
1342
1563
|
"-"
|
|
1343
1564
|
]);
|
|
1344
1565
|
}
|
|
@@ -1391,6 +1612,13 @@ function formatTextSummary(result, isInspect) {
|
|
|
1391
1612
|
return lines.join("\n");
|
|
1392
1613
|
}
|
|
1393
1614
|
var MAX_ERRORS_PER_GROUP = 5;
|
|
1615
|
+
var MAX_ERROR_MESSAGE_LENGTH = 240;
|
|
1616
|
+
function flattenErrorMessage(message) {
|
|
1617
|
+
return message.replace(/\s+/g, " ").trim();
|
|
1618
|
+
}
|
|
1619
|
+
function truncateErrorMessage(message) {
|
|
1620
|
+
return message.length > MAX_ERROR_MESSAGE_LENGTH ? `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}\u2026` : message;
|
|
1621
|
+
}
|
|
1394
1622
|
function formatErrorSection(result) {
|
|
1395
1623
|
const lines = [];
|
|
1396
1624
|
const displayNameMap = /* @__PURE__ */ new Map();
|
|
@@ -1412,13 +1640,14 @@ function formatErrorSection(result) {
|
|
|
1412
1640
|
const deduped = [];
|
|
1413
1641
|
const seen = /* @__PURE__ */ new Map();
|
|
1414
1642
|
for (const err of groupErrors) {
|
|
1415
|
-
const
|
|
1643
|
+
const flat = flattenErrorMessage(err.message);
|
|
1644
|
+
const dedupKey = `${err.filePath ?? ""}|||${flat}`;
|
|
1416
1645
|
const idx = seen.get(dedupKey);
|
|
1417
1646
|
if (idx !== void 0) {
|
|
1418
1647
|
deduped[idx].count++;
|
|
1419
1648
|
} else {
|
|
1420
1649
|
seen.set(dedupKey, deduped.length);
|
|
1421
|
-
deduped.push({ filePath: err.filePath, message:
|
|
1650
|
+
deduped.push({ filePath: err.filePath, message: truncateErrorMessage(flat), count: 1 });
|
|
1422
1651
|
}
|
|
1423
1652
|
}
|
|
1424
1653
|
const shown = deduped.slice(0, MAX_ERRORS_PER_GROUP);
|
|
@@ -1745,7 +1974,7 @@ function applyConfigOperation(op, source, dest) {
|
|
|
1745
1974
|
// src/init.ts
|
|
1746
1975
|
var import_promises4 = __toESM(require("fs/promises"));
|
|
1747
1976
|
var import_path7 = __toESM(require("path"));
|
|
1748
|
-
var
|
|
1977
|
+
var import_child_process4 = require("child_process");
|
|
1749
1978
|
var import_tooling5 = require("@codacy/tooling");
|
|
1750
1979
|
|
|
1751
1980
|
// src/discover.ts
|
|
@@ -1954,6 +2183,12 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
1954
2183
|
["gson", { displayName: "Gson", kind: "library" }],
|
|
1955
2184
|
["okhttp", { displayName: "OkHttp", kind: "library" }],
|
|
1956
2185
|
["retrofit", { displayName: "Retrofit", kind: "library" }],
|
|
2186
|
+
["spock-core", { displayName: "Spock", kind: "library" }],
|
|
2187
|
+
// --- Groovy — Frameworks ---
|
|
2188
|
+
// Grails/GORM artifact IDs vary (grails-core, grails-web, gorm-hibernate5, …);
|
|
2189
|
+
// the `grails-`/`gorm-` prefix rules below catch the rest.
|
|
2190
|
+
["grails-core", { displayName: "Grails", kind: "framework" }],
|
|
2191
|
+
["gorm-core", { displayName: "GORM", kind: "library" }],
|
|
1957
2192
|
// --- Go — Frameworks ---
|
|
1958
2193
|
["github.com/gin-gonic/gin", { displayName: "Gin", kind: "framework" }],
|
|
1959
2194
|
["github.com/labstack/echo", { displayName: "Echo", kind: "framework" }],
|
|
@@ -1995,9 +2230,20 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
1995
2230
|
// --- PHP — Frameworks ---
|
|
1996
2231
|
["laravel/framework", { displayName: "Laravel", kind: "framework" }],
|
|
1997
2232
|
["symfony/framework-bundle", { displayName: "Symfony", kind: "framework" }],
|
|
2233
|
+
["symfony/symfony", { displayName: "Symfony", kind: "framework" }],
|
|
1998
2234
|
["codeigniter4/framework", { displayName: "CodeIgniter", kind: "framework" }],
|
|
1999
2235
|
["slim/slim", { displayName: "Slim", kind: "framework" }],
|
|
2000
2236
|
["cakephp/cakephp", { displayName: "CakePHP", kind: "framework" }],
|
|
2237
|
+
// WordPress is rarely a real Composer dependency; these are the common
|
|
2238
|
+
// "WordPress as a dependency" packages used by Bedrock/Composer-based sites.
|
|
2239
|
+
["johnpbloch/wordpress-core", { displayName: "WordPress", kind: "framework" }],
|
|
2240
|
+
["johnpbloch/wordpress", { displayName: "WordPress", kind: "framework" }],
|
|
2241
|
+
["roots/wordpress", { displayName: "WordPress", kind: "framework" }],
|
|
2242
|
+
["drupal/core", { displayName: "Drupal", kind: "framework" }],
|
|
2243
|
+
["drupal/core-recommended", { displayName: "Drupal", kind: "framework" }],
|
|
2244
|
+
["magento/product-community-edition", { displayName: "Magento", kind: "framework" }],
|
|
2245
|
+
["magento/product-enterprise-edition", { displayName: "Magento", kind: "framework" }],
|
|
2246
|
+
["magento/framework", { displayName: "Magento", kind: "framework" }],
|
|
2001
2247
|
// --- PHP — Libraries ---
|
|
2002
2248
|
["phpunit/phpunit", { displayName: "PHPUnit", kind: "library" }],
|
|
2003
2249
|
["guzzlehttp/guzzle", { displayName: "Guzzle", kind: "library" }],
|
|
@@ -2027,7 +2273,32 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2027
2273
|
["scalatest", { displayName: "ScalaTest", kind: "library" }],
|
|
2028
2274
|
["specs2-core", { displayName: "Specs2", kind: "library" }],
|
|
2029
2275
|
["cats-core", { displayName: "Cats", kind: "library" }],
|
|
2030
|
-
["circe-core", { displayName: "Circe", kind: "library" }]
|
|
2276
|
+
["circe-core", { displayName: "Circe", kind: "library" }],
|
|
2277
|
+
// --- Dart — Frameworks ---
|
|
2278
|
+
// `flutter` and `angular` appear as plain keys in pubspec.yaml.
|
|
2279
|
+
["flutter", { displayName: "Flutter", kind: "framework" }],
|
|
2280
|
+
["flutter_test", { displayName: "Flutter", kind: "framework" }],
|
|
2281
|
+
["angular", { displayName: "Angular", kind: "framework" }],
|
|
2282
|
+
// --- Dart — Libraries ---
|
|
2283
|
+
// Kept to Dart-specific names with negligible cross-ecosystem collision risk
|
|
2284
|
+
// (generic names like "get"/"provider" are intentionally omitted).
|
|
2285
|
+
["riverpod", { displayName: "Riverpod", kind: "library" }],
|
|
2286
|
+
["flutter_bloc", { displayName: "Bloc", kind: "library" }],
|
|
2287
|
+
["dio", { displayName: "Dio", kind: "library" }],
|
|
2288
|
+
// --- .NET (C# / VB) — Libraries ---
|
|
2289
|
+
// NuGet package IDs. Microsoft.* families are also covered by prefix rules below.
|
|
2290
|
+
["EntityFramework", { displayName: "Entity Framework", kind: "library" }],
|
|
2291
|
+
["NUnit", { displayName: "NUnit", kind: "library" }],
|
|
2292
|
+
["Moq", { displayName: "Moq", kind: "library" }],
|
|
2293
|
+
["NSubstitute", { displayName: "NSubstitute", kind: "library" }],
|
|
2294
|
+
["FluentAssertions", { displayName: "FluentAssertions", kind: "library" }],
|
|
2295
|
+
["Shouldly", { displayName: "Shouldly", kind: "library" }],
|
|
2296
|
+
["NLog", { displayName: "NLog", kind: "library" }],
|
|
2297
|
+
["log4net", { displayName: "log4net", kind: "library" }],
|
|
2298
|
+
["Newtonsoft.Json", { displayName: "Json.NET", kind: "library" }],
|
|
2299
|
+
["AutoMapper", { displayName: "AutoMapper", kind: "library" }],
|
|
2300
|
+
["Dapper", { displayName: "Dapper", kind: "library" }],
|
|
2301
|
+
["MediatR", { displayName: "MediatR", kind: "library" }]
|
|
2031
2302
|
]);
|
|
2032
2303
|
var DEPENDENCY_PREFIX_RULES = [
|
|
2033
2304
|
{ prefix: "@angular/", classification: { displayName: "Angular", kind: "framework" } },
|
|
@@ -2057,7 +2328,35 @@ var DEPENDENCY_PREFIX_RULES = [
|
|
|
2057
2328
|
{ prefix: "@shopify/", classification: { displayName: "Shopify", kind: "platform" } },
|
|
2058
2329
|
{ prefix: "@aws-sdk/", classification: { displayName: "AWS SDK", kind: "library" } },
|
|
2059
2330
|
{ prefix: "@google-cloud/", classification: { displayName: "Google Cloud", kind: "library" } },
|
|
2060
|
-
{ prefix: "@azure/", classification: { displayName: "Azure SDK", kind: "library" } }
|
|
2331
|
+
{ prefix: "@azure/", classification: { displayName: "Azure SDK", kind: "library" } },
|
|
2332
|
+
// Groovy — Grails/GORM artifact families (build.gradle artifactIds).
|
|
2333
|
+
{ prefix: "grails-", classification: { displayName: "Grails", kind: "framework" } },
|
|
2334
|
+
{ prefix: "gorm-", classification: { displayName: "GORM", kind: "library" } },
|
|
2335
|
+
// .NET — Microsoft package families and common test/logging libraries. More
|
|
2336
|
+
// specific prefixes (Blazor) must precede broader ones (ASP.NET Core).
|
|
2337
|
+
{
|
|
2338
|
+
prefix: "Microsoft.AspNetCore.Components.WebAssembly",
|
|
2339
|
+
classification: { displayName: "Blazor", kind: "framework" }
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
prefix: "Microsoft.AspNetCore.",
|
|
2343
|
+
classification: { displayName: "ASP.NET Core", kind: "framework" }
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
prefix: "Microsoft.EntityFrameworkCore",
|
|
2347
|
+
classification: { displayName: "Entity Framework", kind: "library" }
|
|
2348
|
+
},
|
|
2349
|
+
{
|
|
2350
|
+
prefix: "Microsoft.Azure.Functions",
|
|
2351
|
+
classification: { displayName: "Azure Functions", kind: "framework" }
|
|
2352
|
+
},
|
|
2353
|
+
{
|
|
2354
|
+
prefix: "Microsoft.Azure.WebJobs",
|
|
2355
|
+
classification: { displayName: "Azure Functions", kind: "framework" }
|
|
2356
|
+
},
|
|
2357
|
+
{ prefix: "xunit", classification: { displayName: "xUnit", kind: "library" } },
|
|
2358
|
+
{ prefix: "MSTest.", classification: { displayName: "MSTest", kind: "library" } },
|
|
2359
|
+
{ prefix: "Serilog", classification: { displayName: "Serilog", kind: "library" } }
|
|
2061
2360
|
];
|
|
2062
2361
|
function classifyDependency(name) {
|
|
2063
2362
|
const exact = DEPENDENCY_CATALOG.get(name);
|
|
@@ -2086,7 +2385,12 @@ var NOTABLE_ANY_DEPTH = /* @__PURE__ */ new Set([
|
|
|
2086
2385
|
"Package.swift",
|
|
2087
2386
|
"Jenkinsfile",
|
|
2088
2387
|
"Vagrantfile",
|
|
2089
|
-
"Procfile"
|
|
2388
|
+
"Procfile",
|
|
2389
|
+
"pubspec.yaml",
|
|
2390
|
+
"mix.exs",
|
|
2391
|
+
"shard.yml",
|
|
2392
|
+
"packages.config",
|
|
2393
|
+
"Directory.Packages.props"
|
|
2090
2394
|
]);
|
|
2091
2395
|
var NOTABLE_ROOT_ONLY = /* @__PURE__ */ new Set([
|
|
2092
2396
|
".dockerignore",
|
|
@@ -2146,6 +2450,7 @@ function isNotableFile(filePath) {
|
|
|
2146
2450
|
const isRoot = !filePath.includes("/");
|
|
2147
2451
|
if (NOTABLE_ANY_DEPTH.has(basename)) return true;
|
|
2148
2452
|
if (isRoot && NOTABLE_ROOT_ONLY.has(basename)) return true;
|
|
2453
|
+
if (/\.(csproj|vbproj|fsproj|sln)$/i.test(basename)) return true;
|
|
2149
2454
|
for (const prefix of NOTABLE_PATH_PREFIXES) {
|
|
2150
2455
|
if (filePath.startsWith(prefix)) return true;
|
|
2151
2456
|
}
|
|
@@ -2352,6 +2657,51 @@ function parsePackageSwift(content) {
|
|
|
2352
2657
|
}
|
|
2353
2658
|
return deps;
|
|
2354
2659
|
}
|
|
2660
|
+
function parsePubspecYaml(content) {
|
|
2661
|
+
const deps = [];
|
|
2662
|
+
let inSection = false;
|
|
2663
|
+
let childIndent = -1;
|
|
2664
|
+
for (const rawLine of content.split("\n")) {
|
|
2665
|
+
const trimmed = rawLine.trim();
|
|
2666
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
2667
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
2668
|
+
if (indent === 0) {
|
|
2669
|
+
inSection = /^(dependencies|dev_dependencies|dependency_overrides):/.test(trimmed);
|
|
2670
|
+
childIndent = -1;
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
if (!inSection) continue;
|
|
2674
|
+
if (childIndent === -1) childIndent = indent;
|
|
2675
|
+
if (indent !== childIndent) continue;
|
|
2676
|
+
const m = trimmed.match(/^([a-zA-Z0-9_.]+)\s*:(?:\s*["']?\^?([0-9][^"'#\s]*))?/);
|
|
2677
|
+
if (m) deps.push({ name: m[1], version: m[2] });
|
|
2678
|
+
}
|
|
2679
|
+
return deps;
|
|
2680
|
+
}
|
|
2681
|
+
function parseXmlDependencyElements(content, elementRegex, nameAttr) {
|
|
2682
|
+
const deps = [];
|
|
2683
|
+
const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
|
|
2684
|
+
const versionRegex = /\bversion\s*=\s*["']([^"']+)["']/i;
|
|
2685
|
+
let match;
|
|
2686
|
+
while ((match = elementRegex.exec(content)) !== null) {
|
|
2687
|
+
const attrs = match[1];
|
|
2688
|
+
const nameMatch = attrs.match(nameRegex);
|
|
2689
|
+
if (!nameMatch) continue;
|
|
2690
|
+
const versionMatch = attrs.match(versionRegex);
|
|
2691
|
+
deps.push({ name: nameMatch[1].trim(), version: versionMatch?.[1]?.trim() });
|
|
2692
|
+
}
|
|
2693
|
+
return deps;
|
|
2694
|
+
}
|
|
2695
|
+
function parseCsproj(content) {
|
|
2696
|
+
return parseXmlDependencyElements(
|
|
2697
|
+
content,
|
|
2698
|
+
/<Package(?:Reference|Version)\s+([^>]+?)\/?>/gi,
|
|
2699
|
+
"Include"
|
|
2700
|
+
);
|
|
2701
|
+
}
|
|
2702
|
+
function parsePackagesConfig(content) {
|
|
2703
|
+
return parseXmlDependencyElements(content, /<package\s+([^>]+?)\/?>/gi, "id");
|
|
2704
|
+
}
|
|
2355
2705
|
var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
|
|
2356
2706
|
["package.json", parsePackageJson],
|
|
2357
2707
|
["requirements.txt", parseRequirementsTxt],
|
|
@@ -2365,7 +2715,15 @@ var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
|
|
|
2365
2715
|
["Cargo.toml", parseCargoToml],
|
|
2366
2716
|
["composer.json", parseComposerJson],
|
|
2367
2717
|
["Podfile", parsePodfile],
|
|
2368
|
-
["Package.swift", parsePackageSwift]
|
|
2718
|
+
["Package.swift", parsePackageSwift],
|
|
2719
|
+
["pubspec.yaml", parsePubspecYaml],
|
|
2720
|
+
["packages.config", parsePackagesConfig],
|
|
2721
|
+
["Directory.Packages.props", parseCsproj]
|
|
2722
|
+
]);
|
|
2723
|
+
var MANIFEST_SUFFIX_PARSERS = /* @__PURE__ */ new Map([
|
|
2724
|
+
[".csproj", parseCsproj],
|
|
2725
|
+
[".vbproj", parseCsproj],
|
|
2726
|
+
[".fsproj", parseCsproj]
|
|
2369
2727
|
]);
|
|
2370
2728
|
|
|
2371
2729
|
// src/discover.ts
|
|
@@ -2415,39 +2773,39 @@ function findNotableFiles(files) {
|
|
|
2415
2773
|
}
|
|
2416
2774
|
return Array.from(notable).sort();
|
|
2417
2775
|
}
|
|
2418
|
-
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
manifestFiles.set(basename, existing);
|
|
2426
|
-
}
|
|
2776
|
+
function resolveManifestParser(filePath) {
|
|
2777
|
+
const basename = filePath.split("/").pop() ?? filePath;
|
|
2778
|
+
const byName = MANIFEST_PARSERS.get(basename);
|
|
2779
|
+
if (byName) return byName;
|
|
2780
|
+
const lowerBasename = basename.toLowerCase();
|
|
2781
|
+
for (const [suffix, parser] of MANIFEST_SUFFIX_PARSERS) {
|
|
2782
|
+
if (lowerBasename.endsWith(suffix)) return parser;
|
|
2427
2783
|
}
|
|
2784
|
+
return void 0;
|
|
2785
|
+
}
|
|
2786
|
+
async function extractDependencies(repositoryRoot, files) {
|
|
2428
2787
|
const allDeps = [];
|
|
2429
|
-
for (const
|
|
2430
|
-
const parser =
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
}
|
|
2788
|
+
for (const filePath of files) {
|
|
2789
|
+
const parser = resolveManifestParser(filePath);
|
|
2790
|
+
if (!parser) continue;
|
|
2791
|
+
let content;
|
|
2792
|
+
try {
|
|
2793
|
+
content = await import_promises2.default.readFile(import_path5.default.join(repositoryRoot, filePath), "utf-8");
|
|
2794
|
+
} catch {
|
|
2795
|
+
continue;
|
|
2796
|
+
}
|
|
2797
|
+
const rawDeps = parser(content);
|
|
2798
|
+
for (const raw of rawDeps) {
|
|
2799
|
+
const classification = classifyDependency(raw.name);
|
|
2800
|
+
if (classification) {
|
|
2801
|
+
allDeps.push({
|
|
2802
|
+
name: raw.name,
|
|
2803
|
+
displayName: classification.displayName,
|
|
2804
|
+
kind: classification.kind,
|
|
2805
|
+
source: "manifest",
|
|
2806
|
+
manifestFile: filePath,
|
|
2807
|
+
version: raw.version
|
|
2808
|
+
});
|
|
2451
2809
|
}
|
|
2452
2810
|
}
|
|
2453
2811
|
}
|
|
@@ -6083,13 +6441,44 @@ async function listIgnoredFiles(provider, org, repo) {
|
|
|
6083
6441
|
}
|
|
6084
6442
|
|
|
6085
6443
|
// src/init.ts
|
|
6444
|
+
function deriveApiToolId(tool) {
|
|
6445
|
+
return tool.prefix ? tool.prefix.replace(/_$/, "") : tool.shortName;
|
|
6446
|
+
}
|
|
6447
|
+
function isTrackableTool(tool) {
|
|
6448
|
+
if (tool.clientSide) return false;
|
|
6449
|
+
if (tool.standalone) return false;
|
|
6450
|
+
if (/\bdeprecated\b/i.test(tool.name ?? "")) return false;
|
|
6451
|
+
return true;
|
|
6452
|
+
}
|
|
6453
|
+
function buildAdapterByUuid(adapters, apiTools) {
|
|
6454
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6455
|
+
for (const adapter of adapters) {
|
|
6456
|
+
byKey.set(adapter.id.toLowerCase(), adapter);
|
|
6457
|
+
byKey.set(adapter.displayName.toLowerCase(), adapter);
|
|
6458
|
+
}
|
|
6459
|
+
const result = /* @__PURE__ */ new Map();
|
|
6460
|
+
for (const tool of apiTools) {
|
|
6461
|
+
const candidates = [];
|
|
6462
|
+
if (tool.prefix) candidates.push(tool.prefix.replace(/_$/, "").toLowerCase());
|
|
6463
|
+
if (tool.shortName) candidates.push(tool.shortName.toLowerCase());
|
|
6464
|
+
if (tool.name) candidates.push(tool.name.toLowerCase());
|
|
6465
|
+
for (const candidate of candidates) {
|
|
6466
|
+
const adapter = byKey.get(candidate);
|
|
6467
|
+
if (adapter) {
|
|
6468
|
+
result.set(tool.uuid, adapter);
|
|
6469
|
+
break;
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
return result;
|
|
6474
|
+
}
|
|
6086
6475
|
async function detectLanguages(repositoryRoot, precomputedFiles) {
|
|
6087
6476
|
let files;
|
|
6088
6477
|
if (precomputedFiles) {
|
|
6089
6478
|
files = precomputedFiles;
|
|
6090
6479
|
} else {
|
|
6091
6480
|
try {
|
|
6092
|
-
const output = (0,
|
|
6481
|
+
const output = (0, import_child_process4.execSync)("git ls-files", {
|
|
6093
6482
|
cwd: repositoryRoot,
|
|
6094
6483
|
encoding: "utf-8",
|
|
6095
6484
|
maxBuffer: 50 * 1024 * 1024
|
|
@@ -6185,34 +6574,32 @@ async function initDefaultConfig(repositoryRoot, adapters) {
|
|
|
6185
6574
|
);
|
|
6186
6575
|
const apiTools = await listTools();
|
|
6187
6576
|
const repoLanguages = new Set(base.metadata.languages);
|
|
6577
|
+
const adapterByUuid = buildAdapterByUuid(adapters, apiTools);
|
|
6188
6578
|
const updatedTools = [...base.tools];
|
|
6189
6579
|
const coveredToolIds = new Set(updatedTools.map((t) => t.toolId));
|
|
6190
6580
|
let addedTools = 0;
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
const
|
|
6194
|
-
|
|
6195
|
-
);
|
|
6196
|
-
if (!apiTool) continue;
|
|
6581
|
+
let addedUnsupported = 0;
|
|
6582
|
+
for (const apiTool of apiTools) {
|
|
6583
|
+
const adapter = adapterByUuid.get(apiTool.uuid);
|
|
6584
|
+
if (adapter && toolsWithLocalConfig.has(adapter.id)) continue;
|
|
6585
|
+
if (!adapter && !isTrackableTool(apiTool)) continue;
|
|
6197
6586
|
if (!apiTool.enabledByDefault) continue;
|
|
6198
6587
|
if (apiTool.languages.length > 0 && repoLanguages.size > 0) {
|
|
6199
6588
|
const hasOverlap = apiTool.languages.some((lang) => repoLanguages.has(lang));
|
|
6200
6589
|
if (!hasOverlap) continue;
|
|
6201
6590
|
}
|
|
6591
|
+
const toolId = adapter ? adapter.id : deriveApiToolId(apiTool);
|
|
6592
|
+
if (coveredToolIds.has(toolId)) continue;
|
|
6202
6593
|
let patterns = [];
|
|
6203
6594
|
try {
|
|
6204
6595
|
const apiPatterns = await listPatterns(apiTool.uuid);
|
|
6205
6596
|
patterns = apiPatterns.filter((p) => p.enabled).map((p) => ({ patternId: p.id }));
|
|
6206
6597
|
} catch {
|
|
6207
6598
|
}
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
});
|
|
6213
|
-
coveredToolIds.add(adapter.id);
|
|
6214
|
-
addedTools++;
|
|
6215
|
-
}
|
|
6599
|
+
updatedTools.push({ toolId, patterns });
|
|
6600
|
+
coveredToolIds.add(toolId);
|
|
6601
|
+
if (adapter) addedTools++;
|
|
6602
|
+
else addedUnsupported++;
|
|
6216
6603
|
}
|
|
6217
6604
|
const baseConfig = createCodacyConfig({
|
|
6218
6605
|
source: "default",
|
|
@@ -6224,7 +6611,10 @@ async function initDefaultConfig(repositoryRoot, adapters) {
|
|
|
6224
6611
|
repositoryRoot,
|
|
6225
6612
|
adapters
|
|
6226
6613
|
);
|
|
6227
|
-
return {
|
|
6614
|
+
return {
|
|
6615
|
+
config,
|
|
6616
|
+
stats: { totalApiTools: apiTools.length, addedTools, addedUnsupported, codacyYamlApplied }
|
|
6617
|
+
};
|
|
6228
6618
|
}
|
|
6229
6619
|
async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters, apiToken, onRemoteToolsFound, onToolProcessed, onIgnoredFilesFetched) {
|
|
6230
6620
|
configureApiToken(apiToken);
|
|
@@ -6236,56 +6626,61 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
6236
6626
|
} catch {
|
|
6237
6627
|
}
|
|
6238
6628
|
onIgnoredFilesFetched?.(ignoredFilesResult.totalFetched, ignoredFilesResult.capped);
|
|
6239
|
-
const adapterByName = /* @__PURE__ */ new Map();
|
|
6240
|
-
for (const adapter of adapters) {
|
|
6241
|
-
adapterByName.set(adapter.id, adapter);
|
|
6242
|
-
adapterByName.set(adapter.displayName.toLowerCase(), adapter);
|
|
6243
|
-
}
|
|
6244
6629
|
const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
|
|
6245
6630
|
const allApiTools = await listTools();
|
|
6246
|
-
const adapterByUuid =
|
|
6247
|
-
|
|
6248
|
-
const adapter = apiTool.prefix ? adapterByName.get(apiTool.prefix.replace(/_$/, "")) : adapterByName.get(apiTool.name);
|
|
6249
|
-
if (adapter) {
|
|
6250
|
-
adapterByUuid.set(apiTool.uuid, adapter);
|
|
6251
|
-
}
|
|
6252
|
-
}
|
|
6631
|
+
const adapterByUuid = buildAdapterByUuid(adapters, allApiTools);
|
|
6632
|
+
const apiToolByUuid = new Map(allApiTools.map((t) => [t.uuid, t]));
|
|
6253
6633
|
const matchedAdapterCount = remoteTools.filter((t) => adapterByUuid.has(t.uuid)).length;
|
|
6254
6634
|
onRemoteToolsFound?.(remoteTools.length, matchedAdapterCount);
|
|
6635
|
+
const fetchPatterns = async (uuid) => {
|
|
6636
|
+
try {
|
|
6637
|
+
const configuredPatterns = await listRepositoryToolPatterns(
|
|
6638
|
+
provider,
|
|
6639
|
+
organization,
|
|
6640
|
+
repositoryName,
|
|
6641
|
+
uuid
|
|
6642
|
+
);
|
|
6643
|
+
return configuredPatterns.map((cp) => {
|
|
6644
|
+
const params = cp.parameters.length > 0 ? Object.fromEntries(cp.parameters.map((p) => [p.name, p.value])) : void 0;
|
|
6645
|
+
return {
|
|
6646
|
+
patternId: cp.patternDefinition.id,
|
|
6647
|
+
...params ? { parameters: params } : {}
|
|
6648
|
+
};
|
|
6649
|
+
});
|
|
6650
|
+
} catch {
|
|
6651
|
+
return [];
|
|
6652
|
+
}
|
|
6653
|
+
};
|
|
6255
6654
|
const tools = [];
|
|
6256
6655
|
let matchedAdapters = 0;
|
|
6656
|
+
let unsupportedTools = 0;
|
|
6257
6657
|
for (const remoteTool of remoteTools) {
|
|
6258
|
-
const adapter = adapterByUuid.get(remoteTool.uuid);
|
|
6259
|
-
if (!adapter) continue;
|
|
6260
|
-
matchedAdapters++;
|
|
6261
6658
|
const usesLocalConfig = remoteTool.settings.usesConfigurationFile;
|
|
6262
|
-
const
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
patterns
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
};
|
|
6278
|
-
});
|
|
6279
|
-
} catch {
|
|
6280
|
-
}
|
|
6659
|
+
const adapter = adapterByUuid.get(remoteTool.uuid);
|
|
6660
|
+
if (adapter) {
|
|
6661
|
+
matchedAdapters++;
|
|
6662
|
+
const localConfigPath = localConfigs.get(adapter.id);
|
|
6663
|
+
const patterns2 = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid);
|
|
6664
|
+
onToolProcessed?.(adapter.displayName, patterns2.length, usesLocalConfig);
|
|
6665
|
+
tools.push({
|
|
6666
|
+
toolId: adapter.id,
|
|
6667
|
+
...localConfigPath ? { localConfigurationFile: localConfigPath } : {},
|
|
6668
|
+
// Honour local config when the API says usesConfigurationFile, OR when a
|
|
6669
|
+
// local config exists and no patterns were configured (backwards-compat).
|
|
6670
|
+
...usesLocalConfig || localConfigPath && patterns2.length === 0 ? { useLocalConfigurationFile: true } : {},
|
|
6671
|
+
patterns: patterns2
|
|
6672
|
+
});
|
|
6673
|
+
continue;
|
|
6281
6674
|
}
|
|
6282
|
-
|
|
6675
|
+
const apiTool = apiToolByUuid.get(remoteTool.uuid);
|
|
6676
|
+
if (!apiTool || !isTrackableTool(apiTool)) continue;
|
|
6677
|
+
const toolId = deriveApiToolId(apiTool);
|
|
6678
|
+
const patterns = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid);
|
|
6679
|
+
unsupportedTools++;
|
|
6680
|
+
onToolProcessed?.(apiTool.name ?? toolId, patterns.length, usesLocalConfig);
|
|
6283
6681
|
tools.push({
|
|
6284
|
-
toolId
|
|
6285
|
-
...
|
|
6286
|
-
// Honour local config when the API says usesConfigurationFile, OR when a
|
|
6287
|
-
// local config exists and no patterns were configured (backwards-compat).
|
|
6288
|
-
...usesLocalConfig || localConfigPath && patterns.length === 0 ? { useLocalConfigurationFile: true } : {},
|
|
6682
|
+
toolId,
|
|
6683
|
+
...usesLocalConfig ? { useLocalConfigurationFile: true } : {},
|
|
6289
6684
|
patterns
|
|
6290
6685
|
});
|
|
6291
6686
|
}
|
|
@@ -6311,6 +6706,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
6311
6706
|
stats: {
|
|
6312
6707
|
totalRemoteTools: remoteTools.length,
|
|
6313
6708
|
matchedAdapters,
|
|
6709
|
+
unsupportedTools,
|
|
6314
6710
|
codacyYamlApplied,
|
|
6315
6711
|
ignoredFilesCount: ignoredFilesResult.totalFetched,
|
|
6316
6712
|
ignoredFilesCapped: ignoredFilesResult.capped
|
|
@@ -6372,7 +6768,7 @@ async function initContainerConfig(repositoryRoot, adapters) {
|
|
|
6372
6768
|
}
|
|
6373
6769
|
return createCodacyConfig({ source: "container", tools, includeFiles });
|
|
6374
6770
|
}
|
|
6375
|
-
async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString) {
|
|
6771
|
+
async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns) {
|
|
6376
6772
|
const discoveryResult = await discover(repositoryRoot);
|
|
6377
6773
|
onDiscoveryComplete?.(discoveryResult);
|
|
6378
6774
|
const detectedKeys = /* @__PURE__ */ new Set();
|
|
@@ -6421,6 +6817,34 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
6421
6817
|
});
|
|
6422
6818
|
}
|
|
6423
6819
|
}
|
|
6820
|
+
let selectedUnsupported = 0;
|
|
6821
|
+
const selectedFamilies = new Set(tools.map((t) => toolFamily(t.toolId)));
|
|
6822
|
+
for (const desc of descriptors) {
|
|
6823
|
+
if (!desc.unsupported) continue;
|
|
6824
|
+
if (!loadUnsupportedPatterns) break;
|
|
6825
|
+
if (!desc.languages.includes("*") && desc.languages.length > 0 && repoLanguages.size > 0 && !desc.languages.some((lang) => repoLanguages.has(lang))) {
|
|
6826
|
+
continue;
|
|
6827
|
+
}
|
|
6828
|
+
const family = toolFamily(desc.id);
|
|
6829
|
+
if (selectedFamilies.has(family)) continue;
|
|
6830
|
+
const patternsMetadata = loadUnsupportedPatterns(desc.id);
|
|
6831
|
+
if (patternsMetadata.length === 0) continue;
|
|
6832
|
+
const selectedPatterns = filterAutoPatterns(
|
|
6833
|
+
patternsMetadata,
|
|
6834
|
+
desc.frameworkMappings,
|
|
6835
|
+
detectedKeys,
|
|
6836
|
+
repoLanguages,
|
|
6837
|
+
filters
|
|
6838
|
+
);
|
|
6839
|
+
if (selectedPatterns.length > 0) {
|
|
6840
|
+
tools.push({
|
|
6841
|
+
toolId: desc.id,
|
|
6842
|
+
patterns: selectedPatterns.map((p) => ({ patternId: p.id }))
|
|
6843
|
+
});
|
|
6844
|
+
selectedFamilies.add(family);
|
|
6845
|
+
selectedUnsupported++;
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6424
6848
|
const baseConfig = createCodacyConfig({
|
|
6425
6849
|
source: "auto",
|
|
6426
6850
|
languages: discoveryResult.languages,
|
|
@@ -6439,6 +6863,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
6439
6863
|
detectedFrameworks: discoveryResult.frameworks.length,
|
|
6440
6864
|
detectedLibraries: discoveryResult.libraries.length,
|
|
6441
6865
|
selectedTools: tools.length,
|
|
6866
|
+
selectedUnsupported,
|
|
6442
6867
|
codacyYamlApplied
|
|
6443
6868
|
}
|
|
6444
6869
|
};
|
|
@@ -6760,6 +7185,7 @@ function createLogger(options = {}) {
|
|
|
6760
7185
|
readCodacyYaml,
|
|
6761
7186
|
registerAdapter,
|
|
6762
7187
|
registerLazyAdapter,
|
|
7188
|
+
registerUnsupportedTool,
|
|
6763
7189
|
resolveConfigPath,
|
|
6764
7190
|
resolveEngineToAdapter,
|
|
6765
7191
|
rotateLogFiles,
|