From 5b3261bcbf50d677ad985c371e13611679173bd0 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 11 Feb 2026 20:14:37 +0000 Subject: [PATCH] Enforce that only compatible kinds can be enabled concurrently --- lib/init-action.js | 22 +++++++++++++++++++--- src/analyses.test.ts | 34 ++++++++++++++++++++++++++++++++++ src/analyses.ts | 30 +++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/lib/init-action.js b/lib/init-action.js index 1dcae2005..6bf9880a5 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -103836,6 +103836,11 @@ var AnalysisKind = /* @__PURE__ */ ((AnalysisKind3) => { AnalysisKind3["CSRA"] = "csra"; return AnalysisKind3; })(AnalysisKind || {}); +var compatibilityMatrix = { + ["code-scanning" /* CodeScanning */]: /* @__PURE__ */ new Set(["code-quality" /* CodeQuality */]), + ["code-quality" /* CodeQuality */]: /* @__PURE__ */ new Set(["code-scanning" /* CodeScanning */]), + ["csra" /* CSRA */]: /* @__PURE__ */ new Set() +}; var supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); async function parseAnalysisKinds(input) { const components = input.split(","); @@ -103858,7 +103863,7 @@ async function getAnalysisKinds(logger, skipCache = false) { if (!skipCache && cachedAnalysisKinds !== void 0) { return cachedAnalysisKinds; } - cachedAnalysisKinds = await parseAnalysisKinds( + const analysisKinds = await parseAnalysisKinds( getRequiredInput("analysis-kinds") ); const qualityQueriesInput = getOptionalInput("quality-queries"); @@ -103867,9 +103872,20 @@ async function getAnalysisKinds(logger, skipCache = false) { "The `quality-queries` input is deprecated and will be removed in a future version of the CodeQL Action. Use the `analysis-kinds` input to configure different analysis kinds instead." ); } - if (!cachedAnalysisKinds.includes("code-quality" /* CodeQuality */) && qualityQueriesInput !== void 0) { - cachedAnalysisKinds.push("code-quality" /* CodeQuality */); + if (!analysisKinds.includes("code-quality" /* CodeQuality */) && qualityQueriesInput !== void 0) { + analysisKinds.push("code-quality" /* CodeQuality */); } + for (const analysisKind of analysisKinds) { + for (const otherAnalysisKind of analysisKinds) { + if (analysisKind === otherAnalysisKind) continue; + if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) { + throw new ConfigurationError( + `${otherAnalysisKind} cannot be enabled at the same time as ${analysisKind}` + ); + } + } + } + cachedAnalysisKinds = analysisKinds; return cachedAnalysisKinds; } var codeQualityQueries = ["code-quality"]; diff --git a/src/analyses.test.ts b/src/analyses.test.ts index 9178ffbd5..d3cd584d0 100644 --- a/src/analyses.test.ts +++ b/src/analyses.test.ts @@ -4,6 +4,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { AnalysisKind, + compatibilityMatrix, getAnalysisKinds, parseAnalysisKinds, supportedAnalysisKinds, @@ -67,3 +68,36 @@ test("getAnalysisKinds - throws if `analysis-kinds` input is invalid", async (t) requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing"); await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true)); }); + +// Test the compatibility matrix by looping through all analysis kinds. +const analysisKinds = Object.values(AnalysisKind); +for (let i = 0; i < analysisKinds.length; i++) { + const analysisKind = analysisKinds[i]; + + for (let j = i + 1; j < analysisKinds.length; j++) { + const otherAnalysis = analysisKinds[j]; + + if (analysisKind === otherAnalysis) continue; + if (compatibilityMatrix[analysisKind].has(otherAnalysis)) { + test(`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, async (t) => { + const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); + requiredInputStub + .withArgs("analysis-kinds") + .returns([analysisKind, otherAnalysis].join(",")); + const result = await getAnalysisKinds(getRunnerLogger(true), true); + t.is(result.length, 2); + }); + } else { + test(`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, async (t) => { + const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); + requiredInputStub + .withArgs("analysis-kinds") + .returns([analysisKind, otherAnalysis].join(",")); + await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), { + instanceOf: ConfigurationError, + message: `${otherAnalysis} cannot be enabled at the same time as ${analysisKind}`, + }); + }); + } + } +} diff --git a/src/analyses.ts b/src/analyses.ts index 33aa68f56..522d3aee8 100644 --- a/src/analyses.ts +++ b/src/analyses.ts @@ -12,6 +12,15 @@ export enum AnalysisKind { CSRA = "csra", } +export type CompatibilityMatrix = Record>; + +/** A mapping from analysis kinds to other analysis kinds which can be enabled concurrently. */ +export const compatibilityMatrix: CompatibilityMatrix = { + [AnalysisKind.CodeScanning]: new Set([AnalysisKind.CodeQuality]), + [AnalysisKind.CodeQuality]: new Set([AnalysisKind.CodeScanning]), + [AnalysisKind.CSRA]: new Set(), +}; + // Exported for testing. A set of all known analysis kinds. export const supportedAnalysisKinds = new Set(Object.values(AnalysisKind)); @@ -68,7 +77,7 @@ export async function getAnalysisKinds( return cachedAnalysisKinds; } - cachedAnalysisKinds = await parseAnalysisKinds( + const analysisKinds = await parseAnalysisKinds( getRequiredInput("analysis-kinds"), ); @@ -86,12 +95,27 @@ export async function getAnalysisKinds( // if an input to `quality-queries` was specified. We should remove this once // `quality-queries` is no longer used. if ( - !cachedAnalysisKinds.includes(AnalysisKind.CodeQuality) && + !analysisKinds.includes(AnalysisKind.CodeQuality) && qualityQueriesInput !== undefined ) { - cachedAnalysisKinds.push(AnalysisKind.CodeQuality); + analysisKinds.push(AnalysisKind.CodeQuality); } + // Check that all enabled analysis kinds are compatible with each other. + for (const analysisKind of analysisKinds) { + for (const otherAnalysisKind of analysisKinds) { + if (analysisKind === otherAnalysisKind) continue; + + if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) { + throw new ConfigurationError( + `${otherAnalysisKind} cannot be enabled at the same time as ${analysisKind}`, + ); + } + } + } + + // Cache the analysis kinds and return them. + cachedAnalysisKinds = analysisKinds; return cachedAnalysisKinds; }