@codacy/analysis-runner 0.17.0 → 0.18.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 +97 -6
- package/dist/index.js +353 -54
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -469,8 +469,31 @@ declare function listIgnoredFiles(provider: string, org: string, repo: string):
|
|
|
469
469
|
* extensions/filenames against LANGUAGE_CONFIG. When `precomputedFiles` is
|
|
470
470
|
* provided, uses that list directly instead of running `git ls-files`.
|
|
471
471
|
* Returns languages sorted alphabetically for deterministic output.
|
|
472
|
+
*
|
|
473
|
+
* `exclude` (optional) drops files matching those globs before detection, so a
|
|
474
|
+
* language whose only files live in excluded paths is not reported. Every caller
|
|
475
|
+
* that turns the result into tool selection must pass the excludes `analyze`
|
|
476
|
+
* will apply — a language detected from a file `analyze` never reads produces a
|
|
477
|
+
* tool that analyzes zero files. Applied to `precomputedFiles` too (filtering is
|
|
478
|
+
* idempotent, so pre-filtered lists are safe to pass).
|
|
479
|
+
*/
|
|
480
|
+
declare function detectLanguages(repositoryRoot: string, precomputedFiles?: string[], exclude?: readonly string[]): Promise<Language[]>;
|
|
481
|
+
/**
|
|
482
|
+
* Options shared by the init modes that derive configuration from the local
|
|
483
|
+
* repository (`local`, `default`, `auto`).
|
|
472
484
|
*/
|
|
473
|
-
|
|
485
|
+
interface InitDiscoveryOptions {
|
|
486
|
+
/**
|
|
487
|
+
* The config currently on disk, when re-generating via `update-config`.
|
|
488
|
+
*
|
|
489
|
+
* Its `exclude` (global and per-tool) is unioned with the freshly-read
|
|
490
|
+
* `.codacy.yaml` to constrain re-discovery, so excludes the user hand-wrote
|
|
491
|
+
* into `codacy.config.json` — or that arrived from Codacy Cloud's
|
|
492
|
+
* ignored-files list — don't get re-populated with tools that analyze zero
|
|
493
|
+
* files. Omit for a fresh `init`, where only `.codacy.yaml` applies.
|
|
494
|
+
*/
|
|
495
|
+
existingConfig?: CodacyConfig;
|
|
496
|
+
}
|
|
474
497
|
/** Stats returned alongside the config from `initLocalConfig`. */
|
|
475
498
|
interface InitLocalStats {
|
|
476
499
|
/** Whether a .codacy.yaml file was found and its exclude paths were applied. */
|
|
@@ -482,7 +505,7 @@ interface InitLocalStats {
|
|
|
482
505
|
* that have a local configuration file present in the repository.
|
|
483
506
|
* If a .codacy.yaml file is present, its exclude paths are merged into the config.
|
|
484
507
|
*/
|
|
485
|
-
declare function initLocalConfig(repositoryRoot: string, adapters: Readonly<ToolAdapter>[]): Promise<{
|
|
508
|
+
declare function initLocalConfig(repositoryRoot: string, adapters: Readonly<ToolAdapter>[], options?: InitDiscoveryOptions): Promise<{
|
|
486
509
|
config: CodacyConfig;
|
|
487
510
|
stats: InitLocalStats;
|
|
488
511
|
}>;
|
|
@@ -507,7 +530,7 @@ interface InitDefaultStats {
|
|
|
507
530
|
*
|
|
508
531
|
* Returns both the config and stats so the caller can report what happened.
|
|
509
532
|
*/
|
|
510
|
-
declare function initDefaultConfig(repositoryRoot: string, adapters: Readonly<ToolAdapter>[]): Promise<{
|
|
533
|
+
declare function initDefaultConfig(repositoryRoot: string, adapters: Readonly<ToolAdapter>[], options?: InitDiscoveryOptions): Promise<{
|
|
511
534
|
config: CodacyConfig;
|
|
512
535
|
stats: InitDefaultStats;
|
|
513
536
|
}>;
|
|
@@ -604,7 +627,7 @@ interface InitAutoStats {
|
|
|
604
627
|
* those patterns are gated on detected frameworks exactly like supported tools;
|
|
605
628
|
* tools without mappings have every committed pattern treated as universal.
|
|
606
629
|
*/
|
|
607
|
-
declare function initAutoConfig(repositoryRoot: string, adapters: Readonly<ToolAdapter>[], descriptors: AdapterDescriptor[], filters?: AutoFilters, onDiscoveryComplete?: (result: DiscoveryResult) => void, rawFilterString?: string, loadUnsupportedPatterns?: (toolId: string) => readonly PatternEntry[]): Promise<{
|
|
630
|
+
declare function initAutoConfig(repositoryRoot: string, adapters: Readonly<ToolAdapter>[], descriptors: AdapterDescriptor[], filters?: AutoFilters, onDiscoveryComplete?: (result: DiscoveryResult) => void, rawFilterString?: string, loadUnsupportedPatterns?: (toolId: string) => readonly PatternEntry[], options?: InitDiscoveryOptions): Promise<{
|
|
608
631
|
config: CodacyConfig;
|
|
609
632
|
stats: InitAutoStats;
|
|
610
633
|
}>;
|
|
@@ -666,6 +689,41 @@ declare function resolveEngineToAdapter(engineName: string, adapters: Readonly<T
|
|
|
666
689
|
* Returns a new config object (does not mutate the input).
|
|
667
690
|
*/
|
|
668
691
|
declare function applyCodacyYamlExcludes(config: CodacyConfig, yamlConfig: CodacyYamlConfig, adapters: Readonly<ToolAdapter>[], logger?: Logger): CodacyConfig;
|
|
692
|
+
/**
|
|
693
|
+
* The exclude patterns repository discovery must honor, resolved from every
|
|
694
|
+
* source that will also constrain `analyze`.
|
|
695
|
+
*/
|
|
696
|
+
interface DiscoveryExcludes {
|
|
697
|
+
/** Parsed `.codacy.yaml`, or null when the repository has none. Reused by `applyCodacyYamlExcludes` so the file is read once per init. */
|
|
698
|
+
yamlConfig: CodacyYamlConfig | null;
|
|
699
|
+
/** Global patterns — applied to every file-derived decision (languages, frameworks, tool selection). */
|
|
700
|
+
global: string[];
|
|
701
|
+
/** Per-adapter-ID patterns, applied on top of `global` when deciding whether that specific tool has files. */
|
|
702
|
+
perTool: Map<string, string[]>;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Resolves the exclude patterns that repository discovery must honor so that
|
|
706
|
+
* tool/language selection sees the same file set `analyze` will.
|
|
707
|
+
*
|
|
708
|
+
* Without this, discovery reads the raw `git ls-files` output while `analyze`
|
|
709
|
+
* reads that output minus `exclude` — so `init --auto` enables tools whose only
|
|
710
|
+
* evidence sits in excluded paths (e.g. language fixtures under
|
|
711
|
+
* `**\/test/fixtures/**`), and every one of them then receives zero files.
|
|
712
|
+
*
|
|
713
|
+
* Sources, unioned:
|
|
714
|
+
* - `.codacy.yaml` global `exclude_paths` → `global`
|
|
715
|
+
* - `.codacy.yaml` per-engine `exclude_paths` → `perTool` (engine names resolved
|
|
716
|
+
* via {@link resolveEngineToAdapter}; unresolvable names are skipped)
|
|
717
|
+
* - `existingConfig.exclude` / `existingConfig.tools[].exclude` → both, for
|
|
718
|
+
* `update-config`, so excludes the user hand-wrote into `codacy.config.json`
|
|
719
|
+
* (or that arrived from Codacy Cloud's ignored-files list) also constrain
|
|
720
|
+
* re-discovery
|
|
721
|
+
*
|
|
722
|
+
* `.codacy.yaml` is always re-read rather than trusted from the existing config,
|
|
723
|
+
* since it is the user-editable source of truth and may have changed since the
|
|
724
|
+
* config was generated.
|
|
725
|
+
*/
|
|
726
|
+
declare function resolveDiscoveryExcludes(repositoryRoot: string, adapters: Readonly<ToolAdapter>[], existingConfig?: CodacyConfig): Promise<DiscoveryExcludes>;
|
|
669
727
|
|
|
670
728
|
/**
|
|
671
729
|
* Computes pattern-level summary statistics for analysis results.
|
|
@@ -834,6 +892,18 @@ declare function resolveApiToken(): Promise<string>;
|
|
|
834
892
|
interface DiscoverOptions {
|
|
835
893
|
/** Skip dependency extraction from manifests (only detect files and languages). */
|
|
836
894
|
skipDependencies?: boolean;
|
|
895
|
+
/**
|
|
896
|
+
* Glob patterns to exclude from the scan, on top of `.gitignore`.
|
|
897
|
+
*
|
|
898
|
+
* Callers that feed discovery into configuration decisions **must** pass the
|
|
899
|
+
* same excludes `analyze` will apply (see `resolveDiscoveryExcludes` in
|
|
900
|
+
* `./codacy-yaml`). Otherwise discovery reports languages, frameworks, and
|
|
901
|
+
* libraries that only exist in excluded paths — e.g. a tool-orchestration
|
|
902
|
+
* monorepo whose per-language sample files all live under
|
|
903
|
+
* `**\/test/fixtures/**` looks like a 20-language polyglot repo, and every
|
|
904
|
+
* tool selected from those languages then analyzes zero files.
|
|
905
|
+
*/
|
|
906
|
+
exclude?: readonly string[];
|
|
837
907
|
}
|
|
838
908
|
/**
|
|
839
909
|
* Scans a repository and returns a structured report of languages, file patterns,
|
|
@@ -902,6 +972,10 @@ declare function resolveHeadCommit(repositoryRoot: string): string;
|
|
|
902
972
|
* Codacy patterns). We normalize either format into a flat issue list and build the
|
|
903
973
|
* payload directly.
|
|
904
974
|
*
|
|
975
|
+
* Issues from tools that group many findings under one pattern additionally carry
|
|
976
|
+
* `sourceId` (e.g. Trivy's CVE id), gated on the adapter descriptor's
|
|
977
|
+
* `needsSourceIdUpload` — see {@link buildPayloads}.
|
|
978
|
+
*
|
|
905
979
|
* ## Where it uploads
|
|
906
980
|
*
|
|
907
981
|
* The **legacy v2 API** (`https://api.codacy.com/2.0`), matching the Go CLI: one
|
|
@@ -933,6 +1007,12 @@ interface NormalizedIssue {
|
|
|
933
1007
|
message: string;
|
|
934
1008
|
/** Codacy level string (Info/Warning/High/Error for json; Info/Warning/Error for sarif). */
|
|
935
1009
|
level: string;
|
|
1010
|
+
/**
|
|
1011
|
+
* The tool's own finding id (e.g. a CVE), when the report carries one. Only
|
|
1012
|
+
* forwarded to the payload for tools whose descriptor sets
|
|
1013
|
+
* `needsSourceIdUpload` — see {@link buildPayloads}.
|
|
1014
|
+
*/
|
|
1015
|
+
sourceId?: string;
|
|
936
1016
|
}
|
|
937
1017
|
/** Authentication + routing for the upload. */
|
|
938
1018
|
type UploadAuth = {
|
|
@@ -995,6 +1075,8 @@ interface PayloadIssue {
|
|
|
995
1075
|
line: number;
|
|
996
1076
|
};
|
|
997
1077
|
};
|
|
1078
|
+
/** Present only for tools that need it; see {@link buildPayloads}. */
|
|
1079
|
+
sourceId?: string;
|
|
998
1080
|
}
|
|
999
1081
|
/**
|
|
1000
1082
|
* Resolves the base URL for the legacy v2 results API. Defaults to Codacy SaaS;
|
|
@@ -1012,8 +1094,17 @@ declare function parseReport(fileContents: string): NormalizedIssue[];
|
|
|
1012
1094
|
* Builds one v2 payload per tool, grouping issues by file. `resolveShortName` maps
|
|
1013
1095
|
* our internal tool id to the Codacy tool short-name expected in the `tool` field
|
|
1014
1096
|
* (which becomes an S3 key segment consumed downstream).
|
|
1097
|
+
*
|
|
1098
|
+
* `needsSourceId` decides, per tool, whether `sourceId` is included on each issue —
|
|
1099
|
+
* mirroring the Go CLI, which gates it on the tool's `needs_source_id_upload`
|
|
1100
|
+
* manifest flag. This is not cosmetic: `sourceId` is one of the four inputs to
|
|
1101
|
+
* Codacy's deterministic result UUID, so sending it for a tool the cloud engine
|
|
1102
|
+
* doesn't send it for would give the same finding a different UUID. Tools that
|
|
1103
|
+
* don't need it omit the field entirely rather than sending an empty string.
|
|
1104
|
+
* Defaults to never including it, so a caller that hasn't opted in can't change
|
|
1105
|
+
* UUIDs by accident.
|
|
1015
1106
|
*/
|
|
1016
|
-
declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (toolId: string) => string): ToolPayload[];
|
|
1107
|
+
declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (toolId: string) => string, needsSourceId?: (toolId: string) => boolean): ToolPayload[];
|
|
1017
1108
|
/**
|
|
1018
1109
|
* Uploads an analysis report to Codacy. Reads and parses the report, resolves tool
|
|
1019
1110
|
* short-names, POSTs one `issuesRemoteResults` per tool, then a single `resultsFinal`.
|
|
@@ -1028,4 +1119,4 @@ declare function buildPayloads(issues: NormalizedIssue[], resolveShortName: (too
|
|
|
1028
1119
|
*/
|
|
1029
1120
|
declare function uploadReport(options: UploadOptions): Promise<UploadResult>;
|
|
1030
1121
|
|
|
1031
|
-
export { type AdapterFactory, type AnalyzeOptions, type AutoFilters, type CategoryFilter, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ConfigOperation, type DiscoverOptions, type ExecutionMode, type IgnoredFilesResult, type InitAutoStats, type InitDefaultStats, type InitLocalStats, type InitRemoteStats, type LoggerOptions, type MergeOptions, type NormalizedIssue, type PatternMetadataLookup, type SeverityFilter, type UploadAuth, type UploadOptions, type UploadResult, analyze, applyCodacyYamlExcludes, applyConfigOperation, buildPayloads, buildToolConfig, clearAdapters, computePatternSummary, configureApiToken, createCodacyConfig, createLogger, createNullLogger, deleteCredentials, detectLanguages, diffConfigs, discover, formatOutput, formatTextSummary, getAdapter, getKnownDisplayNames, getRegisteredAdapters, getRegisteredDescriptors, initAutoConfig, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, intersectConfigs, listDiffFiles, listIgnoredFiles, listPrFiles, listStagedFiles, loadCredentials, mergeConfigs, parseAutoFilters, parseReport, pruneEmptyTools, readBaselineConfig, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, registerUnsupportedTool, resolveApiToken, resolveBaselinePath, resolveConfigPath, resolveEngineToAdapter, resolveHeadCommit, resolveRepositoryRoot, resolveResultsApiBaseUrl, rotateLogFiles, saveCredentials, updateConfigIncremental, uploadReport, validateApiToken, writeBaselineConfig, writeCodacyConfig, writeCodacyConfigFile };
|
|
1122
|
+
export { type AdapterFactory, type AnalyzeOptions, type AutoFilters, type CategoryFilter, type CodacyYamlConfig, type CodacyYamlEngineConfig, type ConfigOperation, type DiscoverOptions, type DiscoveryExcludes, type ExecutionMode, type IgnoredFilesResult, type InitAutoStats, type InitDefaultStats, type InitDiscoveryOptions, type InitLocalStats, type InitRemoteStats, type LoggerOptions, type MergeOptions, type NormalizedIssue, type PatternMetadataLookup, type SeverityFilter, type UploadAuth, type UploadOptions, type UploadResult, analyze, applyCodacyYamlExcludes, applyConfigOperation, buildPayloads, buildToolConfig, clearAdapters, computePatternSummary, configureApiToken, createCodacyConfig, createLogger, createNullLogger, deleteCredentials, detectLanguages, diffConfigs, discover, formatOutput, formatTextSummary, getAdapter, getKnownDisplayNames, getRegisteredAdapters, getRegisteredDescriptors, initAutoConfig, initContainerConfig, initDefaultConfig, initLocalConfig, initRemoteConfig, intersectConfigs, listDiffFiles, listIgnoredFiles, listPrFiles, listStagedFiles, loadCredentials, mergeConfigs, parseAutoFilters, parseReport, pruneEmptyTools, readBaselineConfig, readCodacyConfig, readCodacyYaml, registerAdapter, registerLazyAdapter, registerUnsupportedTool, resolveApiToken, resolveBaselinePath, resolveConfigPath, resolveDiscoveryExcludes, resolveEngineToAdapter, resolveHeadCommit, resolveRepositoryRoot, resolveResultsApiBaseUrl, rotateLogFiles, saveCredentials, updateConfigIncremental, uploadReport, validateApiToken, writeBaselineConfig, writeCodacyConfig, writeCodacyConfigFile };
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ __export(index_exports, {
|
|
|
76
76
|
resolveApiToken: () => resolveApiToken,
|
|
77
77
|
resolveBaselinePath: () => resolveBaselinePath,
|
|
78
78
|
resolveConfigPath: () => resolveConfigPath,
|
|
79
|
+
resolveDiscoveryExcludes: () => resolveDiscoveryExcludes,
|
|
79
80
|
resolveEngineToAdapter: () => resolveEngineToAdapter,
|
|
80
81
|
resolveHeadCommit: () => resolveHeadCommit,
|
|
81
82
|
resolveProxyConfig: () => import_tooling6.resolveProxyConfig,
|
|
@@ -515,7 +516,7 @@ function isDependencyManifest(relativePath) {
|
|
|
515
516
|
if (DEPENDENCY_MANIFEST_FILES.has(base)) return true;
|
|
516
517
|
return DEPENDENCY_MANIFEST_SUFFIXES.some((suffix) => base.endsWith(suffix));
|
|
517
518
|
}
|
|
518
|
-
function listRepoFiles(repositoryRoot) {
|
|
519
|
+
function listRepoFiles(repositoryRoot, exclude) {
|
|
519
520
|
try {
|
|
520
521
|
const tracked = (0, import_child_process2.execSync)("git ls-files", {
|
|
521
522
|
cwd: repositoryRoot,
|
|
@@ -534,7 +535,8 @@ function listRepoFiles(repositoryRoot) {
|
|
|
534
535
|
for (const f of untracked.split("\n")) {
|
|
535
536
|
if (f) files.add(f);
|
|
536
537
|
}
|
|
537
|
-
|
|
538
|
+
const all = Array.from(files).sort();
|
|
539
|
+
return exclude && exclude.length > 0 ? applyExclusions(all, [...exclude], []) : all;
|
|
538
540
|
} catch {
|
|
539
541
|
return [];
|
|
540
542
|
}
|
|
@@ -1652,6 +1654,11 @@ function formatSarif(result) {
|
|
|
1652
1654
|
ruleIndex: ruleIndex.get(issue.patternId) ?? 0,
|
|
1653
1655
|
level: mapSeverityToSarif(issue.severity),
|
|
1654
1656
|
message: { text: issue.message },
|
|
1657
|
+
// SARIF has no first-class field for a tool's own finding id, so it rides in
|
|
1658
|
+
// the result property bag. `upload` reads it back from here — without it the
|
|
1659
|
+
// SARIF path would silently drop `sourceId` and produce different Codacy
|
|
1660
|
+
// result UUIDs than the JSON path for the same analysis.
|
|
1661
|
+
...issue.sourceId ? { properties: { sourceId: issue.sourceId } } : {},
|
|
1655
1662
|
locations: [
|
|
1656
1663
|
{
|
|
1657
1664
|
physicalLocation: {
|
|
@@ -1716,7 +1723,8 @@ function formatContainer(result) {
|
|
|
1716
1723
|
patternId: issue.patternId,
|
|
1717
1724
|
line: issue.line,
|
|
1718
1725
|
message: issue.message,
|
|
1719
|
-
suggestion: issue.suggestion
|
|
1726
|
+
suggestion: issue.suggestion,
|
|
1727
|
+
sourceId: issue.sourceId
|
|
1720
1728
|
})
|
|
1721
1729
|
).join("\n");
|
|
1722
1730
|
}
|
|
@@ -2402,6 +2410,8 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2402
2410
|
["graphql", { displayName: "GraphQL", kind: "library" }],
|
|
2403
2411
|
["@apollo/client", { displayName: "Apollo Client", kind: "library" }],
|
|
2404
2412
|
["socket.io", { displayName: "Socket.IO", kind: "library" }],
|
|
2413
|
+
["jquery", { displayName: "jQuery", kind: "library" }],
|
|
2414
|
+
["mongodb", { displayName: "MongoDB", kind: "library" }],
|
|
2405
2415
|
["three", { displayName: "Three.js", kind: "library" }],
|
|
2406
2416
|
["d3", { displayName: "D3.js", kind: "library" }],
|
|
2407
2417
|
["chart.js", { displayName: "Chart.js", kind: "library" }],
|
|
@@ -2484,6 +2494,10 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2484
2494
|
["matplotlib", { displayName: "Matplotlib", kind: "library" }],
|
|
2485
2495
|
["seaborn", { displayName: "Seaborn", kind: "library" }],
|
|
2486
2496
|
["plotly", { displayName: "Plotly", kind: "library" }],
|
|
2497
|
+
// MongoDB drivers, one entry per ecosystem — they all gate the same
|
|
2498
|
+
// MongoDB-specific patterns (Opengrep's `java.mongodb.`/`python.pymongo.`).
|
|
2499
|
+
["pymongo", { displayName: "MongoDB", kind: "library" }],
|
|
2500
|
+
["motor", { displayName: "MongoDB", kind: "library" }],
|
|
2487
2501
|
// --- Python — Tools ---
|
|
2488
2502
|
["black", { displayName: "Black", kind: "tool" }],
|
|
2489
2503
|
["ruff", { displayName: "Ruff", kind: "tool" }],
|
|
@@ -2514,9 +2528,16 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2514
2528
|
["minitest", { displayName: "Minitest", kind: "library" }],
|
|
2515
2529
|
["factory_bot", { displayName: "FactoryBot", kind: "library" }],
|
|
2516
2530
|
["dry-validation", { displayName: "dry-validation", kind: "library" }],
|
|
2531
|
+
// NOTE: `faker` and `rake` are Gemfile-scoped (MANIFEST_SCOPED_CATALOG), not
|
|
2532
|
+
// global — they are bare words that also name npm/PyPI packages.
|
|
2517
2533
|
// --- Ruby — Tools ---
|
|
2518
2534
|
["rubocop", { displayName: "RuboCop", kind: "tool" }],
|
|
2519
2535
|
["bundler", { displayName: "Bundler", kind: "tool" }],
|
|
2536
|
+
// Sorbet ships as several gems; any of them means the project is Sorbet-typed.
|
|
2537
|
+
["sorbet", { displayName: "Sorbet", kind: "tool" }],
|
|
2538
|
+
["sorbet-runtime", { displayName: "Sorbet", kind: "tool" }],
|
|
2539
|
+
["sorbet-static", { displayName: "Sorbet", kind: "tool" }],
|
|
2540
|
+
["sorbet-static-and-runtime", { displayName: "Sorbet", kind: "tool" }],
|
|
2520
2541
|
["solargraph", { displayName: "Solargraph", kind: "tool" }],
|
|
2521
2542
|
// --- Java / Kotlin — Frameworks ---
|
|
2522
2543
|
["spring-boot-starter", { displayName: "Spring Boot", kind: "framework" }],
|
|
@@ -2547,6 +2568,10 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2547
2568
|
["okhttp", { displayName: "OkHttp", kind: "library" }],
|
|
2548
2569
|
["retrofit", { displayName: "Retrofit", kind: "library" }],
|
|
2549
2570
|
["spock-core", { displayName: "Spock", kind: "library" }],
|
|
2571
|
+
["mongodb-driver-sync", { displayName: "MongoDB", kind: "library" }],
|
|
2572
|
+
["mongodb-driver-core", { displayName: "MongoDB", kind: "library" }],
|
|
2573
|
+
["mongodb-driver-reactivestreams", { displayName: "MongoDB", kind: "library" }],
|
|
2574
|
+
["mongo-java-driver", { displayName: "MongoDB", kind: "library" }],
|
|
2550
2575
|
// --- Groovy — Frameworks ---
|
|
2551
2576
|
// Grails/GORM artifact IDs vary (grails-core, grails-web, gorm-hibernate5, …);
|
|
2552
2577
|
// the `grails-`/`gorm-` prefix rules below catch the rest.
|
|
@@ -2630,7 +2655,19 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2630
2655
|
["RxSwift", { displayName: "RxSwift", kind: "library" }],
|
|
2631
2656
|
["RealmSwift", { displayName: "Realm", kind: "library" }],
|
|
2632
2657
|
// --- Scala — Frameworks ---
|
|
2658
|
+
// Play is declared as an sbt *plugin*, so `group:artifact` (from
|
|
2659
|
+
// `addSbtPlugin`) is the reliable signal; the library artifacts below only
|
|
2660
|
+
// appear in some builds. `play-json` is deliberately absent — it is a
|
|
2661
|
+
// standalone JSON library used widely outside Play.
|
|
2662
|
+
["com.typesafe.play:sbt-plugin", { displayName: "Play Framework", kind: "framework" }],
|
|
2663
|
+
["org.playframework:sbt-plugin", { displayName: "Play Framework", kind: "framework" }],
|
|
2664
|
+
["play", { displayName: "Play Framework", kind: "framework" }],
|
|
2633
2665
|
["play-server", { displayName: "Play Framework", kind: "framework" }],
|
|
2666
|
+
["play-ws", { displayName: "Play Framework", kind: "framework" }],
|
|
2667
|
+
["play-test", { displayName: "Play Framework", kind: "framework" }],
|
|
2668
|
+
["play-slick", { displayName: "Play Framework", kind: "framework" }],
|
|
2669
|
+
["twirl-api", { displayName: "Play Framework", kind: "framework" }],
|
|
2670
|
+
["scalatestplus-play", { displayName: "Play Framework", kind: "framework" }],
|
|
2634
2671
|
["akka-actor", { displayName: "Akka", kind: "framework" }],
|
|
2635
2672
|
["spray-can", { displayName: "Spray", kind: "framework" }],
|
|
2636
2673
|
// --- Scala — Libraries ---
|
|
@@ -2638,6 +2675,22 @@ var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
|
|
|
2638
2675
|
["specs2-core", { displayName: "Specs2", kind: "library" }],
|
|
2639
2676
|
["cats-core", { displayName: "Cats", kind: "library" }],
|
|
2640
2677
|
["circe-core", { displayName: "Circe", kind: "library" }],
|
|
2678
|
+
// Slick gates Opengrep's `scala.slick.` SQL-injection patterns.
|
|
2679
|
+
["slick", { displayName: "Slick", kind: "library" }],
|
|
2680
|
+
["slick-hikaricp", { displayName: "Slick", kind: "library" }],
|
|
2681
|
+
// jwt-scala publishes one artifact per JSON backend; all gate the same
|
|
2682
|
+
// hardcoded-secret patterns.
|
|
2683
|
+
["jwt-core", { displayName: "jwt-scala", kind: "library" }],
|
|
2684
|
+
["jwt-circe", { displayName: "jwt-scala", kind: "library" }],
|
|
2685
|
+
["jwt-play-json", { displayName: "jwt-scala", kind: "library" }],
|
|
2686
|
+
["jwt-json4s", { displayName: "jwt-scala", kind: "library" }],
|
|
2687
|
+
["jwt-spray-json", { displayName: "jwt-scala", kind: "library" }],
|
|
2688
|
+
["jwt-upickle", { displayName: "jwt-scala", kind: "library" }],
|
|
2689
|
+
["jwt-argonaut", { displayName: "jwt-scala", kind: "library" }],
|
|
2690
|
+
["munit", { displayName: "MUnit", kind: "library" }],
|
|
2691
|
+
["scalacheck", { displayName: "ScalaCheck", kind: "library" }],
|
|
2692
|
+
["scala-logging", { displayName: "scala-logging", kind: "library" }],
|
|
2693
|
+
["chimney", { displayName: "Chimney", kind: "library" }],
|
|
2641
2694
|
// --- Elixir — Frameworks ---
|
|
2642
2695
|
["phoenix", { displayName: "Phoenix", kind: "framework" }],
|
|
2643
2696
|
["plug", { displayName: "Plug", kind: "framework" }],
|
|
@@ -2713,6 +2766,25 @@ var DEPENDENCY_PREFIX_RULES = [
|
|
|
2713
2766
|
// Groovy — Grails/GORM artifact families (build.gradle artifactIds).
|
|
2714
2767
|
{ prefix: "grails-", classification: { displayName: "Grails", kind: "framework" } },
|
|
2715
2768
|
{ prefix: "gorm-", classification: { displayName: "GORM", kind: "library" } },
|
|
2769
|
+
// Scala — artifact families published one module per feature (sbt artifactIds).
|
|
2770
|
+
// Enumerating members would go stale on every release; the prefixes are
|
|
2771
|
+
// distinctive enough not to collide with other ecosystems.
|
|
2772
|
+
{ prefix: "http4s-", classification: { displayName: "http4s", kind: "framework" } },
|
|
2773
|
+
{ prefix: "akka-", classification: { displayName: "Akka", kind: "framework" } },
|
|
2774
|
+
{ prefix: "pekko-", classification: { displayName: "Pekko", kind: "framework" } },
|
|
2775
|
+
{ prefix: "zio-", classification: { displayName: "ZIO", kind: "library" } },
|
|
2776
|
+
// Must precede any broader `cats-` rule: cats-effect is its own project.
|
|
2777
|
+
{ prefix: "cats-effect", classification: { displayName: "Cats Effect", kind: "library" } },
|
|
2778
|
+
{ prefix: "doobie-", classification: { displayName: "Doobie", kind: "library" } },
|
|
2779
|
+
{ prefix: "fs2-", classification: { displayName: "FS2", kind: "library" } },
|
|
2780
|
+
{ prefix: "circe-", classification: { displayName: "Circe", kind: "library" } },
|
|
2781
|
+
{ prefix: "pureconfig", classification: { displayName: "PureConfig", kind: "library" } },
|
|
2782
|
+
{ prefix: "kamon-", classification: { displayName: "Kamon", kind: "library" } },
|
|
2783
|
+
{ prefix: "flyway-", classification: { displayName: "Flyway", kind: "library" } },
|
|
2784
|
+
{
|
|
2785
|
+
prefix: "testcontainers-scala",
|
|
2786
|
+
classification: { displayName: "Testcontainers", kind: "library" }
|
|
2787
|
+
},
|
|
2716
2788
|
// .NET — Microsoft package families and common test/logging libraries. More
|
|
2717
2789
|
// specific prefixes (Blazor) must precede broader ones (ASP.NET Core).
|
|
2718
2790
|
{
|
|
@@ -2740,7 +2812,14 @@ var DEPENDENCY_PREFIX_RULES = [
|
|
|
2740
2812
|
{ prefix: "Serilog", classification: { displayName: "Serilog", kind: "library" } }
|
|
2741
2813
|
];
|
|
2742
2814
|
var MANIFEST_SCOPED_CATALOG = /* @__PURE__ */ new Map([
|
|
2743
|
-
["package.json", /* @__PURE__ */ new Map([["angular", { displayName: "AngularJS", kind: "framework" }]])]
|
|
2815
|
+
["package.json", /* @__PURE__ */ new Map([["angular", { displayName: "AngularJS", kind: "framework" }]])],
|
|
2816
|
+
[
|
|
2817
|
+
"Gemfile",
|
|
2818
|
+
/* @__PURE__ */ new Map([
|
|
2819
|
+
["faker", { displayName: "Faker", kind: "library" }],
|
|
2820
|
+
["rake", { displayName: "Rake", kind: "tool" }]
|
|
2821
|
+
])
|
|
2822
|
+
]
|
|
2744
2823
|
]);
|
|
2745
2824
|
function classifyDependency(name, manifestBasename) {
|
|
2746
2825
|
if (manifestBasename) {
|
|
@@ -2812,6 +2891,43 @@ var FILE_SIGNAL_RULES = [
|
|
|
2812
2891
|
name: "azure-pipelines.yml",
|
|
2813
2892
|
matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
|
|
2814
2893
|
classification: { displayName: "Azure Pipelines", kind: "framework" }
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
// Jenkins pipelines are Groovy scripts with no manifest at all — the
|
|
2897
|
+
// Jenkinsfile *is* the declaration. Gates CodeNarc's `jenkins`/
|
|
2898
|
+
// `jenkinspipeline` tagged patterns.
|
|
2899
|
+
name: "Jenkinsfile",
|
|
2900
|
+
matches: (f) => {
|
|
2901
|
+
const base = pathBasename(f);
|
|
2902
|
+
return base === "Jenkinsfile" || base.startsWith("Jenkinsfile.");
|
|
2903
|
+
},
|
|
2904
|
+
classification: { displayName: "Jenkins", kind: "tool" }
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
// A `sorbet/` directory (typically `sorbet/config` + RBI files) marks a
|
|
2908
|
+
// Sorbet-typed Ruby project even when the gem is pulled in transitively.
|
|
2909
|
+
name: "sorbet/config",
|
|
2910
|
+
matches: (f) => f === "sorbet/config" || f.endsWith("/sorbet/config"),
|
|
2911
|
+
classification: { displayName: "Sorbet", kind: "tool" }
|
|
2912
|
+
},
|
|
2913
|
+
{
|
|
2914
|
+
name: "Rakefile",
|
|
2915
|
+
matches: (f) => {
|
|
2916
|
+
const base = pathBasename(f);
|
|
2917
|
+
return base === "Rakefile" || base === "rakefile" || base.endsWith(".rake");
|
|
2918
|
+
},
|
|
2919
|
+
classification: { displayName: "Rake", kind: "tool" }
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
// Play's routes file — `conf/routes`, plus the `conf/*.routes` sub-files a
|
|
2923
|
+
// multi-module app includes from it (Codacy's own website uses
|
|
2924
|
+
// `app/conf/website.routes`, `modules/legacy-api/conf/api.routes`). Nothing
|
|
2925
|
+
// but Play uses this layout, and it is version-independent, unlike the
|
|
2926
|
+
// artifact names. Note `conf/application.conf` is NOT a signal: plain
|
|
2927
|
+
// Typesafe Config uses the same filename without any Play involvement.
|
|
2928
|
+
name: "conf/routes",
|
|
2929
|
+
matches: (f) => /(^|\/)conf\/([^/]+\.)?routes$/.test(f),
|
|
2930
|
+
classification: { displayName: "Play Framework", kind: "framework" }
|
|
2815
2931
|
}
|
|
2816
2932
|
];
|
|
2817
2933
|
function detectFileSignals(files) {
|
|
@@ -2976,18 +3092,76 @@ function parsePackageJson(content) {
|
|
|
2976
3092
|
return [];
|
|
2977
3093
|
}
|
|
2978
3094
|
}
|
|
3095
|
+
function parseRequirementLine(rawLine) {
|
|
3096
|
+
const line = rawLine.trim();
|
|
3097
|
+
if (!line || line.startsWith("#") || line.startsWith("-")) return void 0;
|
|
3098
|
+
const noComment = line.split("#")[0].trim();
|
|
3099
|
+
if (!noComment) return void 0;
|
|
3100
|
+
const match = noComment.match(/^([a-zA-Z0-9_.-]+(?:\[[^\]]*\])?)\s*([<>=!~@;].*)?$/);
|
|
3101
|
+
if (!match) return void 0;
|
|
3102
|
+
const rawName = match[1].replace(/\[.*\]$/, "");
|
|
3103
|
+
return { name: rawName.toLowerCase().replace(/_/g, "-"), version: match[2]?.trim() };
|
|
3104
|
+
}
|
|
2979
3105
|
function parseRequirementsTxt(content) {
|
|
2980
3106
|
const deps = [];
|
|
2981
3107
|
for (const rawLine of content.split("\n")) {
|
|
2982
|
-
const
|
|
2983
|
-
if (
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3108
|
+
const dep = parseRequirementLine(rawLine);
|
|
3109
|
+
if (dep) deps.push(dep);
|
|
3110
|
+
}
|
|
3111
|
+
return deps;
|
|
3112
|
+
}
|
|
3113
|
+
function sliceBalanced(content, fromIndex, open, close) {
|
|
3114
|
+
const start = content.indexOf(open, fromIndex);
|
|
3115
|
+
if (start === -1) return void 0;
|
|
3116
|
+
let depth = 0;
|
|
3117
|
+
let quote;
|
|
3118
|
+
for (let i = start; i < content.length; i++) {
|
|
3119
|
+
const ch = content[i];
|
|
3120
|
+
if (quote) {
|
|
3121
|
+
if (ch === "\\") i++;
|
|
3122
|
+
else if (ch === quote) quote = void 0;
|
|
3123
|
+
continue;
|
|
3124
|
+
}
|
|
3125
|
+
if (ch === '"' || ch === "'") {
|
|
3126
|
+
quote = ch;
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
if (ch === open) depth++;
|
|
3130
|
+
else if (ch === close && --depth === 0) {
|
|
3131
|
+
return { body: content.slice(start + 1, i), endIndex: i };
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
return void 0;
|
|
3135
|
+
}
|
|
3136
|
+
function parseSetupPy(content) {
|
|
3137
|
+
const deps = [];
|
|
3138
|
+
const collectQuoted = (block) => {
|
|
3139
|
+
for (const quoted of block.matchAll(/["']([^"']+)["']/g)) {
|
|
3140
|
+
const dep = parseRequirementLine(quoted[1]);
|
|
3141
|
+
if (dep) deps.push(dep);
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
const valueStartOf = (key) => {
|
|
3145
|
+
const match = new RegExp(`\\b${key}\\s*=\\s*`).exec(content);
|
|
3146
|
+
return match ? match.index + match[0].length : -1;
|
|
3147
|
+
};
|
|
3148
|
+
for (const key of ["install_requires", "tests_require", "setup_requires"]) {
|
|
3149
|
+
const start = valueStartOf(key);
|
|
3150
|
+
if (start === -1 || content[start] !== "[") continue;
|
|
3151
|
+
const list = sliceBalanced(content, start, "[", "]");
|
|
3152
|
+
if (list) collectQuoted(list.body);
|
|
3153
|
+
}
|
|
3154
|
+
const extrasStart = valueStartOf("extras_require");
|
|
3155
|
+
if (extrasStart !== -1 && content[extrasStart] === "{") {
|
|
3156
|
+
const dict = sliceBalanced(content, extrasStart, "{", "}");
|
|
3157
|
+
if (dict) {
|
|
3158
|
+
let cursor = 0;
|
|
3159
|
+
while (cursor < dict.body.length) {
|
|
3160
|
+
const list = sliceBalanced(dict.body, cursor, "[", "]");
|
|
3161
|
+
if (!list) break;
|
|
3162
|
+
collectQuoted(list.body);
|
|
3163
|
+
cursor = list.endIndex + 1;
|
|
3164
|
+
}
|
|
2991
3165
|
}
|
|
2992
3166
|
}
|
|
2993
3167
|
return deps;
|
|
@@ -3105,15 +3279,52 @@ function parseBuildGradle(content) {
|
|
|
3105
3279
|
}
|
|
3106
3280
|
return deps;
|
|
3107
3281
|
}
|
|
3282
|
+
function parseVersionCatalogToml(content) {
|
|
3283
|
+
const deps = [];
|
|
3284
|
+
let inLibraries = false;
|
|
3285
|
+
for (const rawLine of content.split("\n")) {
|
|
3286
|
+
const line = rawLine.trim();
|
|
3287
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
3288
|
+
if (line.startsWith("[")) {
|
|
3289
|
+
inLibraries = /^\[\s*libraries\s*\]/.test(line);
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
if (!inLibraries) continue;
|
|
3293
|
+
const moduleMatch = line.match(/\bmodule\s*=\s*["'][^"':]+:([^"']+)["']/);
|
|
3294
|
+
const nameMatch = line.match(/\bname\s*=\s*["']([^"']+)["']/);
|
|
3295
|
+
const versionMatch = line.match(/\bversion\s*=\s*["']([^"']+)["']/);
|
|
3296
|
+
if (moduleMatch || nameMatch) {
|
|
3297
|
+
deps.push({
|
|
3298
|
+
name: (moduleMatch?.[1] ?? nameMatch?.[1] ?? "").trim(),
|
|
3299
|
+
version: versionMatch?.[1]?.trim()
|
|
3300
|
+
});
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
const shorthand = line.match(/^[A-Za-z0-9_.-]+\s*=\s*["'][^"':]+:([^"':]+)(?::([^"']+))?["']/);
|
|
3304
|
+
if (shorthand) {
|
|
3305
|
+
deps.push({ name: shorthand[1].trim(), version: shorthand[2]?.trim() });
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
return deps;
|
|
3309
|
+
}
|
|
3108
3310
|
function parseBuildSbt(content) {
|
|
3109
3311
|
const deps = [];
|
|
3110
|
-
const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s
|
|
3312
|
+
const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%(?!%)\s*(?:"([^"]+)")?/g;
|
|
3111
3313
|
let match;
|
|
3112
3314
|
while ((match = depRegex.exec(content)) !== null) {
|
|
3113
3315
|
deps.push({ name: match[2], version: match[3] });
|
|
3114
3316
|
}
|
|
3115
3317
|
return deps;
|
|
3116
3318
|
}
|
|
3319
|
+
function parseSbtBuildDefinition(content) {
|
|
3320
|
+
const deps = parseBuildSbt(content);
|
|
3321
|
+
const pluginRegex = /addSbtPlugin\s*\(\s*"([^"]+)"\s*%%?\s*"([^"]+)"/g;
|
|
3322
|
+
let match;
|
|
3323
|
+
while ((match = pluginRegex.exec(content)) !== null) {
|
|
3324
|
+
deps.push({ name: `${match[1]}:${match[2]}` });
|
|
3325
|
+
}
|
|
3326
|
+
return deps;
|
|
3327
|
+
}
|
|
3117
3328
|
function parseCargoToml(content) {
|
|
3118
3329
|
const deps = [];
|
|
3119
3330
|
let inDepsSection = false;
|
|
@@ -3263,6 +3474,8 @@ var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
|
|
|
3263
3474
|
["pom.xml", parsePomXml],
|
|
3264
3475
|
["build.gradle", parseBuildGradle],
|
|
3265
3476
|
["build.gradle.kts", parseBuildGradle],
|
|
3477
|
+
["libs.versions.toml", parseVersionCatalogToml],
|
|
3478
|
+
["setup.py", parseSetupPy],
|
|
3266
3479
|
["build.sbt", parseBuildSbt],
|
|
3267
3480
|
["Cargo.toml", parseCargoToml],
|
|
3268
3481
|
["composer.json", parseComposerJson],
|
|
@@ -3279,10 +3492,32 @@ var MANIFEST_SUFFIX_PARSERS = /* @__PURE__ */ new Map([
|
|
|
3279
3492
|
[".vbproj", parseCsproj],
|
|
3280
3493
|
[".fsproj", parseCsproj]
|
|
3281
3494
|
]);
|
|
3495
|
+
var MANIFEST_PATH_PARSERS = [
|
|
3496
|
+
{
|
|
3497
|
+
matches: (filePath, basename) => /^requirements[a-zA-Z0-9_.-]*\.(txt|in)$/i.test(basename) || // Any .txt/.in directly inside a `requirements/` directory: base.txt,
|
|
3498
|
+
// dev.txt, prod.in — the other common split-layout convention.
|
|
3499
|
+
/(^|\/)requirements\/[^/]+\.(txt|in)$/i.test(filePath),
|
|
3500
|
+
parser: parseRequirementsTxt
|
|
3501
|
+
},
|
|
3502
|
+
{
|
|
3503
|
+
// sbt build definition: `project/Dependencies.scala`, `project/plugins.sbt`
|
|
3504
|
+
// and the meta-build's own `project/project/*`. Matching by directory rather
|
|
3505
|
+
// than basename is what this rule exists for — the Scala file can be called
|
|
3506
|
+
// anything, and `*.scala` on its own is source code, not a manifest.
|
|
3507
|
+
//
|
|
3508
|
+
// Case-sensitive deliberately: sbt's directory is always lowercase `project`,
|
|
3509
|
+
// while `Project` is an ordinary domain-package name, and reading
|
|
3510
|
+
// `…/domain/Project/Project.scala` as a manifest would be wrong.
|
|
3511
|
+
matches: (filePath) => /(^|\/)project\/[^/]+\.(scala|sbt)$/.test(filePath),
|
|
3512
|
+
parser: parseSbtBuildDefinition
|
|
3513
|
+
}
|
|
3514
|
+
];
|
|
3282
3515
|
|
|
3283
3516
|
// src/discover.ts
|
|
3284
3517
|
async function discover(repositoryRoot, options) {
|
|
3285
|
-
const
|
|
3518
|
+
const unfiltered = listRepoFiles(repositoryRoot);
|
|
3519
|
+
const files = applyExclusions(unfiltered, [...options?.exclude ?? []], []);
|
|
3520
|
+
const excludedFiles = unfiltered.length - files.length;
|
|
3286
3521
|
const filePatterns = extractFilePatterns(files);
|
|
3287
3522
|
const notableFiles = findNotableFiles(files);
|
|
3288
3523
|
const languages = await detectLanguages(repositoryRoot, files);
|
|
@@ -3305,7 +3540,10 @@ async function discover(repositoryRoot, options) {
|
|
|
3305
3540
|
metadata: {
|
|
3306
3541
|
repositoryRoot: import_path6.default.resolve(repositoryRoot),
|
|
3307
3542
|
totalFiles: files.length,
|
|
3308
|
-
scannedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3543
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3544
|
+
// Surfaced so a scan narrowed by excludes is never mistaken for a repo
|
|
3545
|
+
// that genuinely lacks those languages.
|
|
3546
|
+
...excludedFiles > 0 ? { excludedFiles } : {}
|
|
3309
3547
|
}
|
|
3310
3548
|
};
|
|
3311
3549
|
}
|
|
@@ -3336,6 +3574,9 @@ function resolveManifestParser(filePath) {
|
|
|
3336
3574
|
for (const [suffix, parser] of MANIFEST_SUFFIX_PARSERS) {
|
|
3337
3575
|
if (lowerBasename.endsWith(suffix)) return parser;
|
|
3338
3576
|
}
|
|
3577
|
+
for (const rule of MANIFEST_PATH_PARSERS) {
|
|
3578
|
+
if (rule.matches(filePath, basename)) return rule.parser;
|
|
3579
|
+
}
|
|
3339
3580
|
return void 0;
|
|
3340
3581
|
}
|
|
3341
3582
|
async function extractDependencies(repositoryRoot, files) {
|
|
@@ -3469,6 +3710,28 @@ function applyCodacyYamlExcludes(config, yamlConfig, adapters, logger) {
|
|
|
3469
3710
|
function deduplicateStrings(arr) {
|
|
3470
3711
|
return [...new Set(arr)];
|
|
3471
3712
|
}
|
|
3713
|
+
async function resolveDiscoveryExcludes(repositoryRoot, adapters, existingConfig) {
|
|
3714
|
+
const yamlConfig = await readCodacyYaml(repositoryRoot);
|
|
3715
|
+
const global = deduplicateStrings([
|
|
3716
|
+
...existingConfig?.exclude ?? [],
|
|
3717
|
+
...yamlConfig?.excludePaths ?? []
|
|
3718
|
+
]);
|
|
3719
|
+
const perTool = /* @__PURE__ */ new Map();
|
|
3720
|
+
const addToolExcludes = (toolId, patterns) => {
|
|
3721
|
+
if (patterns.length === 0) return;
|
|
3722
|
+
perTool.set(toolId, deduplicateStrings([...perTool.get(toolId) ?? [], ...patterns]));
|
|
3723
|
+
};
|
|
3724
|
+
for (const tool of existingConfig?.tools ?? []) {
|
|
3725
|
+
addToolExcludes(tool.toolId, tool.exclude ?? []);
|
|
3726
|
+
}
|
|
3727
|
+
for (const [engineName, engineConfig] of yamlConfig?.engines ?? []) {
|
|
3728
|
+
if (engineConfig.excludePaths.length === 0) continue;
|
|
3729
|
+
const adapterId = resolveEngineToAdapter(engineName, adapters);
|
|
3730
|
+
if (!adapterId) continue;
|
|
3731
|
+
addToolExcludes(adapterId, engineConfig.excludePaths);
|
|
3732
|
+
}
|
|
3733
|
+
return { yamlConfig, global, perTool };
|
|
3734
|
+
}
|
|
3472
3735
|
|
|
3473
3736
|
// src/api/client/core/OpenAPI.ts
|
|
3474
3737
|
var OpenAPI = {
|
|
@@ -7033,7 +7296,7 @@ function buildAdapterByUuid(adapters, apiTools) {
|
|
|
7033
7296
|
}
|
|
7034
7297
|
return result;
|
|
7035
7298
|
}
|
|
7036
|
-
async function detectLanguages(repositoryRoot, precomputedFiles) {
|
|
7299
|
+
async function detectLanguages(repositoryRoot, precomputedFiles, exclude) {
|
|
7037
7300
|
let files;
|
|
7038
7301
|
if (precomputedFiles) {
|
|
7039
7302
|
files = precomputedFiles;
|
|
@@ -7049,6 +7312,9 @@ async function detectLanguages(repositoryRoot, precomputedFiles) {
|
|
|
7049
7312
|
return [];
|
|
7050
7313
|
}
|
|
7051
7314
|
}
|
|
7315
|
+
if (exclude && exclude.length > 0) {
|
|
7316
|
+
files = applyExclusions(files, [...exclude], []);
|
|
7317
|
+
}
|
|
7052
7318
|
const detected = /* @__PURE__ */ new Set();
|
|
7053
7319
|
for (const file of files) {
|
|
7054
7320
|
const basename = import_path8.default.basename(file);
|
|
@@ -7087,16 +7353,18 @@ async function detectLocalConfigFiles(repositoryRoot, adapters) {
|
|
|
7087
7353
|
}
|
|
7088
7354
|
return found;
|
|
7089
7355
|
}
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
if (!yamlConfig) return { config, codacyYamlApplied: false };
|
|
7356
|
+
function enrichWithCodacyYaml(config, adapters, excludes) {
|
|
7357
|
+
if (!excludes.yamlConfig) return { config, codacyYamlApplied: false };
|
|
7093
7358
|
return {
|
|
7094
|
-
config: applyCodacyYamlExcludes(config, yamlConfig, adapters),
|
|
7359
|
+
config: applyCodacyYamlExcludes(config, excludes.yamlConfig, adapters),
|
|
7095
7360
|
codacyYamlApplied: true
|
|
7096
7361
|
};
|
|
7097
7362
|
}
|
|
7098
|
-
async function initLocalConfigBase(repositoryRoot, adapters) {
|
|
7099
|
-
const languages = await detectLanguages(
|
|
7363
|
+
async function initLocalConfigBase(repositoryRoot, adapters, excludes) {
|
|
7364
|
+
const languages = await detectLanguages(
|
|
7365
|
+
repositoryRoot,
|
|
7366
|
+
listRepoFiles(repositoryRoot, excludes.global)
|
|
7367
|
+
);
|
|
7100
7368
|
const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
|
|
7101
7369
|
const tools = [];
|
|
7102
7370
|
for (const [toolId, configPath] of localConfigs.entries()) {
|
|
@@ -7109,17 +7377,23 @@ async function initLocalConfigBase(repositoryRoot, adapters) {
|
|
|
7109
7377
|
}
|
|
7110
7378
|
return createCodacyConfig({ source: "local", languages, tools });
|
|
7111
7379
|
}
|
|
7112
|
-
async function initLocalConfig(repositoryRoot, adapters) {
|
|
7113
|
-
const
|
|
7114
|
-
const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
|
|
7115
|
-
baseConfig,
|
|
7380
|
+
async function initLocalConfig(repositoryRoot, adapters, options) {
|
|
7381
|
+
const excludes = await resolveDiscoveryExcludes(
|
|
7116
7382
|
repositoryRoot,
|
|
7117
|
-
adapters
|
|
7383
|
+
adapters,
|
|
7384
|
+
options?.existingConfig
|
|
7118
7385
|
);
|
|
7386
|
+
const baseConfig = await initLocalConfigBase(repositoryRoot, adapters, excludes);
|
|
7387
|
+
const { config, codacyYamlApplied } = enrichWithCodacyYaml(baseConfig, adapters, excludes);
|
|
7119
7388
|
return { config, stats: { codacyYamlApplied } };
|
|
7120
7389
|
}
|
|
7121
|
-
async function initDefaultConfig(repositoryRoot, adapters) {
|
|
7122
|
-
const
|
|
7390
|
+
async function initDefaultConfig(repositoryRoot, adapters, options) {
|
|
7391
|
+
const excludes = await resolveDiscoveryExcludes(
|
|
7392
|
+
repositoryRoot,
|
|
7393
|
+
adapters,
|
|
7394
|
+
options?.existingConfig
|
|
7395
|
+
);
|
|
7396
|
+
const base = await initLocalConfigBase(repositoryRoot, adapters, excludes);
|
|
7123
7397
|
const toolsWithLocalConfig = new Set(
|
|
7124
7398
|
base.tools.filter((t) => t.useLocalConfigurationFile).map((t) => t.toolId)
|
|
7125
7399
|
);
|
|
@@ -7157,11 +7431,7 @@ async function initDefaultConfig(repositoryRoot, adapters) {
|
|
|
7157
7431
|
languages: base.metadata.languages,
|
|
7158
7432
|
tools: updatedTools
|
|
7159
7433
|
});
|
|
7160
|
-
const { config, codacyYamlApplied } =
|
|
7161
|
-
baseConfig,
|
|
7162
|
-
repositoryRoot,
|
|
7163
|
-
adapters
|
|
7164
|
-
);
|
|
7434
|
+
const { config, codacyYamlApplied } = enrichWithCodacyYaml(baseConfig, adapters, excludes);
|
|
7165
7435
|
return {
|
|
7166
7436
|
config,
|
|
7167
7437
|
stats: { totalApiTools: apiTools.length, addedTools, addedUnsupported, codacyYamlApplied }
|
|
@@ -7247,10 +7517,10 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
7247
7517
|
if (ignoredFilesResult.files.length > 0) {
|
|
7248
7518
|
baseConfig.exclude = [.../* @__PURE__ */ new Set([...baseConfig.exclude ?? [], ...ignoredFilesResult.files])];
|
|
7249
7519
|
}
|
|
7250
|
-
const { config, codacyYamlApplied } =
|
|
7520
|
+
const { config, codacyYamlApplied } = enrichWithCodacyYaml(
|
|
7251
7521
|
baseConfig,
|
|
7252
|
-
|
|
7253
|
-
adapters
|
|
7522
|
+
adapters,
|
|
7523
|
+
await resolveDiscoveryExcludes(repositoryRoot, adapters)
|
|
7254
7524
|
);
|
|
7255
7525
|
return {
|
|
7256
7526
|
config,
|
|
@@ -7324,8 +7594,13 @@ function hasMatchingFiles(files, filePatterns) {
|
|
|
7324
7594
|
if (matcher.matchAll) return files.length > 0;
|
|
7325
7595
|
return files.some((file) => matcher.matches(file, file.split("/").pop() ?? ""));
|
|
7326
7596
|
}
|
|
7327
|
-
async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns) {
|
|
7328
|
-
const
|
|
7597
|
+
async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns, options) {
|
|
7598
|
+
const excludes = await resolveDiscoveryExcludes(
|
|
7599
|
+
repositoryRoot,
|
|
7600
|
+
adapters,
|
|
7601
|
+
options?.existingConfig
|
|
7602
|
+
);
|
|
7603
|
+
const discoveryResult = await discover(repositoryRoot, { exclude: excludes.global });
|
|
7329
7604
|
onDiscoveryComplete?.(discoveryResult);
|
|
7330
7605
|
const detectedKeys = /* @__PURE__ */ new Set();
|
|
7331
7606
|
for (const lang of discoveryResult.languages) detectedKeys.add(lang);
|
|
@@ -7333,15 +7608,26 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7333
7608
|
for (const lib of discoveryResult.libraries) detectedKeys.add(lib.displayName);
|
|
7334
7609
|
const repoLanguages = new Set(discoveryResult.languages);
|
|
7335
7610
|
const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
|
|
7336
|
-
const repoFiles = listRepoFiles(repositoryRoot);
|
|
7611
|
+
const repoFiles = listRepoFiles(repositoryRoot, excludes.global);
|
|
7337
7612
|
const descriptorMap = new Map(descriptors.map((d) => [d.id, d]));
|
|
7613
|
+
const perToolFiles = /* @__PURE__ */ new Map();
|
|
7614
|
+
const filesVisibleTo = (toolId) => {
|
|
7615
|
+
const toolExclude = excludes.perTool.get(toolId);
|
|
7616
|
+
if (!toolExclude || toolExclude.length === 0) return repoFiles;
|
|
7617
|
+
let files = perToolFiles.get(toolId);
|
|
7618
|
+
if (!files) {
|
|
7619
|
+
files = applyExclusions(repoFiles, [], [...toolExclude]);
|
|
7620
|
+
perToolFiles.set(toolId, files);
|
|
7621
|
+
}
|
|
7622
|
+
return files;
|
|
7623
|
+
};
|
|
7338
7624
|
const candidates = [];
|
|
7339
7625
|
for (const adapter of adapters) {
|
|
7340
7626
|
if (!adapter.languages.includes("*") && adapter.languages.length > 0 && repoLanguages.size > 0) {
|
|
7341
7627
|
const hasOverlap = adapter.languages.some((lang) => repoLanguages.has(lang));
|
|
7342
7628
|
if (!hasOverlap) continue;
|
|
7343
7629
|
}
|
|
7344
|
-
if (!hasMatchingFiles(
|
|
7630
|
+
if (!hasMatchingFiles(filesVisibleTo(adapter.id), adapter.filePatterns)) continue;
|
|
7345
7631
|
candidates.push(adapter);
|
|
7346
7632
|
}
|
|
7347
7633
|
const deduped = deduplicateToolFamilies(candidates, localConfigs);
|
|
@@ -7374,7 +7660,12 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7374
7660
|
patterns: selectedPatterns.map((p) => ({ patternId: p.id }))
|
|
7375
7661
|
};
|
|
7376
7662
|
tools.push(toolEntry);
|
|
7377
|
-
await applyAdapterParameterSuggestions(
|
|
7663
|
+
await applyAdapterParameterSuggestions(
|
|
7664
|
+
adapter,
|
|
7665
|
+
toolEntry,
|
|
7666
|
+
repositoryRoot,
|
|
7667
|
+
filesVisibleTo(adapter.id)
|
|
7668
|
+
);
|
|
7378
7669
|
}
|
|
7379
7670
|
}
|
|
7380
7671
|
let selectedUnsupported = 0;
|
|
@@ -7389,7 +7680,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7389
7680
|
}
|
|
7390
7681
|
const family = toolGroupKey(desc.id, desc.alternativeGroup);
|
|
7391
7682
|
if (selectedFamilies.has(family)) continue;
|
|
7392
|
-
if (!hasMatchingFiles(
|
|
7683
|
+
if (!hasMatchingFiles(filesVisibleTo(desc.id), desc.filePatterns)) continue;
|
|
7393
7684
|
const patternsMetadata = loadUnsupportedPatterns(desc.id);
|
|
7394
7685
|
if (patternsMetadata.length === 0) continue;
|
|
7395
7686
|
const selectedPatterns = filterAutoPatterns(
|
|
@@ -7414,11 +7705,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7414
7705
|
tools,
|
|
7415
7706
|
autoFilters: rawFilterString || void 0
|
|
7416
7707
|
});
|
|
7417
|
-
const { config, codacyYamlApplied } =
|
|
7418
|
-
baseConfig,
|
|
7419
|
-
repositoryRoot,
|
|
7420
|
-
adapters
|
|
7421
|
-
);
|
|
7708
|
+
const { config, codacyYamlApplied } = enrichWithCodacyYaml(baseConfig, adapters, excludes);
|
|
7422
7709
|
return {
|
|
7423
7710
|
config,
|
|
7424
7711
|
stats: {
|
|
@@ -7809,11 +8096,12 @@ function parseSarif(runs) {
|
|
|
7809
8096
|
if (!patternId) continue;
|
|
7810
8097
|
const level = mapSarifLevelToCodacy(result.level);
|
|
7811
8098
|
const message = result.message?.text ?? "";
|
|
8099
|
+
const sourceId = result.properties?.sourceId;
|
|
7812
8100
|
for (const loc of result.locations ?? []) {
|
|
7813
8101
|
const filePath = loc.physicalLocation?.artifactLocation?.uri;
|
|
7814
8102
|
const line = loc.physicalLocation?.region?.startLine;
|
|
7815
8103
|
if (!filePath || !line) continue;
|
|
7816
|
-
issues.push({ toolId, patternId, filePath, line, message, level });
|
|
8104
|
+
issues.push({ toolId, patternId, filePath, line, message, level, sourceId });
|
|
7817
8105
|
}
|
|
7818
8106
|
}
|
|
7819
8107
|
}
|
|
@@ -7827,10 +8115,11 @@ function parseAnalysisResult(result) {
|
|
|
7827
8115
|
line: issue.line,
|
|
7828
8116
|
message: issue.message,
|
|
7829
8117
|
// JSON carries the authoritative Codacy severity; pass it through unchanged.
|
|
7830
|
-
level: issue.severity
|
|
8118
|
+
level: issue.severity,
|
|
8119
|
+
sourceId: issue.sourceId
|
|
7831
8120
|
}));
|
|
7832
8121
|
}
|
|
7833
|
-
function buildPayloads(issues, resolveShortName) {
|
|
8122
|
+
function buildPayloads(issues, resolveShortName, needsSourceId = () => false) {
|
|
7834
8123
|
const byTool = /* @__PURE__ */ new Map();
|
|
7835
8124
|
for (const issue of issues) {
|
|
7836
8125
|
const list = byTool.get(issue.toolId);
|
|
@@ -7839,6 +8128,7 @@ function buildPayloads(issues, resolveShortName) {
|
|
|
7839
8128
|
}
|
|
7840
8129
|
const payloads = [];
|
|
7841
8130
|
for (const [toolId, toolIssues] of byTool) {
|
|
8131
|
+
const withSourceId = needsSourceId(toolId);
|
|
7842
8132
|
const byFile = /* @__PURE__ */ new Map();
|
|
7843
8133
|
for (const issue of toolIssues) {
|
|
7844
8134
|
const entry = {
|
|
@@ -7847,7 +8137,8 @@ function buildPayloads(issues, resolveShortName) {
|
|
|
7847
8137
|
filename: issue.filePath,
|
|
7848
8138
|
message: { text: issue.message },
|
|
7849
8139
|
level: issue.level,
|
|
7850
|
-
location: { LineLocation: { line: issue.line } }
|
|
8140
|
+
location: { LineLocation: { line: issue.line } },
|
|
8141
|
+
...withSourceId && issue.sourceId ? { sourceId: issue.sourceId } : {}
|
|
7851
8142
|
}
|
|
7852
8143
|
};
|
|
7853
8144
|
const list = byFile.get(issue.filePath);
|
|
@@ -7879,6 +8170,13 @@ async function buildShortNameResolver(onProgress) {
|
|
|
7879
8170
|
}
|
|
7880
8171
|
return (toolId) => byName.get(toolId.toLowerCase()) ?? toolId.toLowerCase();
|
|
7881
8172
|
}
|
|
8173
|
+
function buildSourceIdResolver() {
|
|
8174
|
+
const needsIt = /* @__PURE__ */ new Set();
|
|
8175
|
+
for (const descriptor of getRegisteredDescriptors()) {
|
|
8176
|
+
if (descriptor.needsSourceIdUpload) needsIt.add(descriptor.id.toLowerCase());
|
|
8177
|
+
}
|
|
8178
|
+
return (toolId) => needsIt.has(toolId.toLowerCase());
|
|
8179
|
+
}
|
|
7882
8180
|
var COORDINATE_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7883
8181
|
function assertValidCoordinate(kind, value) {
|
|
7884
8182
|
if (!COORDINATE_PATTERN.test(value)) {
|
|
@@ -7918,7 +8216,7 @@ async function uploadReport(options) {
|
|
|
7918
8216
|
onProgress?.("No issues found in the report; nothing to upload.");
|
|
7919
8217
|
}
|
|
7920
8218
|
const resolveShortName = await buildShortNameResolver(onProgress);
|
|
7921
|
-
const payloads = buildPayloads(issues, resolveShortName);
|
|
8219
|
+
const payloads = buildPayloads(issues, resolveShortName, buildSourceIdResolver());
|
|
7922
8220
|
const base = resolveResultsApiBaseUrl();
|
|
7923
8221
|
const urls = buildUrls(base, auth, commitUuid);
|
|
7924
8222
|
const headers = { "content-type": "application/json", ...authHeader(auth) };
|
|
@@ -8019,6 +8317,7 @@ async function safeReadBody(res) {
|
|
|
8019
8317
|
resolveApiToken,
|
|
8020
8318
|
resolveBaselinePath,
|
|
8021
8319
|
resolveConfigPath,
|
|
8320
|
+
resolveDiscoveryExcludes,
|
|
8022
8321
|
resolveEngineToAdapter,
|
|
8023
8322
|
resolveHeadCommit,
|
|
8024
8323
|
resolveProxyConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codacy/analysis-runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Core orchestration engine for Codacy analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"cli-table3": "^0.6",
|
|
13
13
|
"minimatch": "10.2.4",
|
|
14
14
|
"yaml": "^2.6.0",
|
|
15
|
-
"@codacy/tooling": "0.
|
|
15
|
+
"@codacy/tooling": "0.18.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@codacy/openapi-typescript-codegen": "0.0.9",
|
|
19
|
-
"@codacy/tools-dummy-1": "0.
|
|
19
|
+
"@codacy/tools-dummy-1": "0.18.0"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|