Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 31 additions & 57 deletions src/commands/analyze/sqaa-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@

// Sequential chunked execution engine for SQAA change-set analysis

import { CommandFailedError } from '@/core/commands/command-error.ts';
import { getSqaaRetry503BaseDelayMs } from '@/core/config-constants.ts';
import type { SonarConnection } from '@/core/server/connection.ts';

import {
fetchChunkWith413Split,
fetchPreparedChunkWith413Split,
MAX_503_RETRIES,
readSqaaFileContent,
type SqaaChunkGroupError,
type SqaaChunkResponse,
} from './sqaa-api.ts';
import { type SqaaChunk, type SqaaChunkFile } from './sqaa-chunking.ts';
import type { SqaaDeepWireDepth } from './sqaa-depth.ts';
import { isGlobalSqaaError, isPayloadTooLargeCommandError } from './sqaa-errors.ts';
import { type SqaaPath, withDisplayFilePath } from './sqaa-paths.ts';
import type { SqaaProgress } from './sqaa-progress.ts';
import type { SqaaAnalysisDepth, SqaaIssue } from './sqaa-wire-types.ts';
import type { PreparedSqaaProject } from './sqaa-request.ts';
import type { SqaaIssue } from './sqaa-wire-types.ts';

export type FileSuccess = {
file: string;
Expand All @@ -49,11 +49,7 @@ export type FileResult = FileSuccess | FileFailure;
export interface RunContext {
paths: SqaaPath[];
connection: SonarConnection;
projectKey: string;
branch: string | undefined;
progress: SqaaProgress;
analysisDepth?: SqaaDeepWireDepth;
displayAnalysisDepth: SqaaAnalysisDepth;
}

export interface RunTally {
Expand All @@ -74,17 +70,26 @@ export interface RunTally {
* Run analyses as sequential multi-file chunks. Returns the merged tally once
* every chunk has been processed (or fail-fast stops remaining chunks).
*/
export async function runAnalyses(ctx: RunContext): Promise<RunTally> {
export async function executePreparedAnalyses(
ctx: RunContext,
prepared: PreparedSqaaProject,
): Promise<RunTally> {
const tally = emptyTally();
if (ctx.paths.length === 0) return tally;

const fileIndexByAbsolutePath = new Map(ctx.paths.map((p, i) => [p.absolutePath, i]));
const { chunks, chunkFileIndices } = prepareChunks(
ctx,
fileIndexByAbsolutePath,
tally,
ctx.progress,
);
for (const failure of prepared.failures) {
const index = fileIndexForPath(fileIndexByAbsolutePath, failure.file);
recordFileFailure(ctx.progress, tally, index, ctx.paths[index], failure.failure);
}
const chunkFiles = prepared.payload.files.map((file, index) => ({
absolutePath: prepared.paths[index].absolutePath,
relativePath: file.path,
content: file.content,
}));
const chunks: SqaaChunk[] = chunkFiles.length ? [{ files: chunkFiles }] : [];
const chunkFileIndices = [
chunkFiles.map((file) => fileIndexForPath(fileIndexByAbsolutePath, file.absolutePath)),
];
ctx.progress.start();

for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
Expand All @@ -93,6 +98,7 @@ export async function runAnalyses(ctx: RunContext): Promise<RunTally> {
tally,
chunkIndex,
chunks[chunkIndex],
prepared,
chunkFileIndices[chunkIndex],
fileIndexByAbsolutePath,
);
Expand All @@ -106,6 +112,12 @@ export async function runAnalyses(ctx: RunContext): Promise<RunTally> {
return tally;
}

function fileIndexForPath(indices: Map<string, number>, path: string): number {
const index = indices.get(path);
if (index === undefined) throw new CommandFailedError(`Missing file index for ${path}`);
return index;
}

function emptyTally(): RunTally {
return { allResults: [], totalIssues: 0, totalErrors: 0, totalFailures: 0 };
}
Expand All @@ -123,43 +135,6 @@ export function mergeTallies(tallies: RunTally[]): RunTally {
return merged;
}

function prepareChunks(
ctx: RunContext,
fileIndexByAbsolutePath: Map<string, number>,
tally: RunTally,
progress: SqaaProgress,
): { chunks: SqaaChunk[]; chunkFileIndices: number[][] } {
const readableChunkFiles: SqaaChunkFile[] = [];
for (let idx = 0; idx < ctx.paths.length; idx++) {
const path = ctx.paths[idx];
try {
readableChunkFiles.push({
absolutePath: path.absolutePath,
relativePath: path.serverPath,
content: readSqaaFileContent(path.absolutePath),
});
} catch (err) {
recordFileFailure(progress, tally, idx, path, err as Error);
}
}

if (readableChunkFiles.length === 0) {
return { chunks: [], chunkFileIndices: [] };
}

const chunks: SqaaChunk[] = [{ files: readableChunkFiles }];
const chunkFileIndices = [
readableChunkFiles.map((f) => {
const idx = fileIndexByAbsolutePath.get(f.absolutePath);
if (idx === undefined) {
throw new Error(`Missing file index for ${f.absolutePath}`);
}
return idx;
}),
];
return { chunks, chunkFileIndices };
}

function chunkPathsForFiles(
ctx: RunContext,
chunkFiles: SqaaChunkFile[],
Expand Down Expand Up @@ -232,18 +207,18 @@ async function processChunk(
tally: RunTally,
chunkIndex: number,
chunk: SqaaChunk,
prepared: PreparedSqaaProject,
fileIndices: number[],
fileIndexByAbsolutePath: Map<string, number>,
): Promise<boolean> {
const chunkPaths = chunkPathsForIndices(ctx, fileIndices);
markChunkAnalyzing(ctx.progress, chunkIndex, fileIndices);

try {
const { parts, groupErrors } = await fetchChunkWith413Split(
const { parts, groupErrors } = await fetchPreparedChunkWith413Split(
ctx.connection,
ctx.projectKey,
prepared.payload,
chunk.files,
ctx.branch,
(attempt) =>
ctx.progress.retryingChunk(
chunkIndex,
Expand All @@ -254,7 +229,6 @@ async function processChunk(
() => {
ctx.progress.warnPayloadSplit();
},
ctx.analysisDepth,
);

recordSuccessfulParts(ctx, tally, ctx.progress, parts, fileIndexByAbsolutePath);
Expand Down
Loading