fix(javascript): keep unselected sources out of cancellation failures

With source_paths, the analysis loop checked cancellation before it
recognized an outside-analysis-scope file. Cancellation arriving after the
last selected source therefore recorded the next unselected file as a
cancelled analysis failure, and the completion loop skipped it as already
analyzed.

Give out-of-selection classification one owner, used by the loop, the
interruption handler and the completion loop. The cancellation is still
reported as the interruption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822
2026-10-11 21:36:10 +00:00
co-authored by Claude Opus 5.5
parent 1d4c4eb030
commit 8a8efb8226
2 changed files with 80 additions and 29 deletions
@@ -0,0 +1,62 @@
import { expect, it } from "vitest";
import { analyzeAndAdoptJavaScriptArtifactFiles } from "../../../../src/application/javascript/JavaScriptArtifactAnalysis.js";
import { createJavaScriptSemanticGraphProjection } from "../../../../src/application/javascript/JavaScriptSemanticGraphBuilder.js";
import { readJavaScriptArtifactFiles } from "../../../../src/artifacts/javascript/JavaScriptArtifactFiles.js";
import { createJavaScriptArtifactReader } from "../../../../src/artifacts/javascript/JavaScriptArtifactReader.js";
import { createJavaScriptSourceAnalysis } from "../../../../src/artifacts/javascript/JavaScriptSourceAnalysis.js";
import { JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS } from "../../../../src/domain/javascript/javascriptAnalysisResourceControls.js";
import {
scanCanonicalArtifactInventory,
withArtifactResourceScope,
} from "../../../fixtures/artifactInventory.js";
import { writeJavaScriptSourceSelectionFixture } from "../../../fixtures/javascriptSourceSelection.js";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
it("keeps unselected sources outside the selection when cancellation follows the last selected source", async () => {
const root = await createTestTempDirectory("rea-js-selected-late-cancel-");
await writeJavaScriptSourceSelectionFixture(root);
const snapshot = await scanCanonicalArtifactInventory(root, {});
const reader = createJavaScriptArtifactReader(root, "directory");
const worker = createJavaScriptSourceAnalysis(
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS,
1,
);
try {
const files = await withArtifactResourceScope((scope) =>
readJavaScriptArtifactFiles(reader, snapshot, scope, undefined, [
"main.mjs",
]),
);
const controller = new AbortController();
// Cancel after the only selected source completes, before the loop
// reaches the first unselected source.
const { analysis, interruption } =
await analyzeAndAdoptJavaScriptArtifactFiles(
files,
{
analyze: async (file, budget, signal) => {
const result = await worker.analyze(file, budget, signal);
controller.abort();
return result;
},
},
createJavaScriptSemanticGraphProjection(),
async () => {},
controller.signal,
);
expect(interruption?._tag).toBe("AnalysisCancelledError");
const unselected = analysis.files.filter(
({ file }) =>
!file.text.included && file.text.reason === "outside-analysis-scope",
);
expect(unselected).toHaveLength(5);
expect(
unselected.filter((entry) => entry.analysis_failure !== undefined),
).toEqual([]);
} finally {
await worker.close();
await reader.close();
}
});