Add analysis-kinds input and parse it

This commit is contained in:
Michael B. Gale
2025-08-27 18:15:47 +01:00
parent d062f2b421
commit e0104a269f
16 changed files with 169 additions and 6 deletions
+13
View File
@@ -42,6 +42,19 @@ inputs:
your workflow between the `init` and `analyze` steps. Available for all
compiled languages.
required: false
analysis-kinds:
description: >-
[Internal] A comma-separated list of analysis kinds to enable. This input is intended for
internal-use only at this time and the behaviour is subject to changes. Some features may
not be available depending on which analysis kinds are enabled.
Available options are:
- `code-scanning`: The default, security-focused analysis.
- `code-quality`: Analysis focused on code quality. This must be enabled in conjunction
with `code-scanning`.
default: 'code-scanning'
required: true
token:
description: GitHub token to use for authenticating with this instance of GitHub. To download custom packs from multiple registries, use the registries input.
default: ${{ github.token }}
+26 -1
View File
@@ -1,9 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalysisKind = void 0;
exports.supportedAnalysisKinds = exports.AnalysisKind = void 0;
exports.parseAnalysisKinds = parseAnalysisKinds;
const util_1 = require("./util");
var AnalysisKind;
(function (AnalysisKind) {
AnalysisKind["CodeScanning"] = "code-scanning";
AnalysisKind["CodeQuality"] = "code-quality";
})(AnalysisKind || (exports.AnalysisKind = AnalysisKind = {}));
// Exported for testing. A set of all known analysis kinds.
exports.supportedAnalysisKinds = new Set(Object.values(AnalysisKind));
/**
* Parses a comma-separated string into a list of unique analysis kinds.
* Throws a configuration error if the input contains unknown analysis kinds
* or doesn't contain at least one element.
*
* @param input The comma-separated string to parse.
* @returns The array of unique analysis kinds that were parsed from the input string.
*/
async function parseAnalysisKinds(input) {
const components = input.split(",");
if (components.length < 1) {
throw new util_1.ConfigurationError("At least one analysis kind must be configured.");
}
for (const component of components) {
if (!exports.supportedAnalysisKinds.has(component)) {
throw new util_1.ConfigurationError(`Unknown analysis kind: ${component}`);
}
}
// Return all unique elements.
return Array.from(new Set(components.map((component) => component)));
}
//# sourceMappingURL=analyses.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"analyses.js","sourceRoot":"","sources":["../src/analyses.ts"],"names":[],"mappings":";;;AAAA,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,8CAA8B,CAAA;IAC9B,4CAA4B,CAAA;AAC9B,CAAC,EAHW,YAAY,4BAAZ,YAAY,QAGvB"}
{"version":3,"file":"analyses.js","sourceRoot":"","sources":["../src/analyses.ts"],"names":[],"mappings":";;;AAkBA,gDAqBC;AAvCD,iCAA4C;AAE5C,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,8CAA8B,CAAA;IAC9B,4CAA4B,CAAA;AAC9B,CAAC,EAHW,YAAY,4BAAZ,YAAY,QAGvB;AAED,2DAA2D;AAC9C,QAAA,sBAAsB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;AAE3E;;;;;;;GAOG;AACI,KAAK,UAAU,kBAAkB,CACtC,KAAa;IAEb,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEpC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,yBAAkB,CAC1B,gDAAgD,CACjD,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,8BAAsB,CAAC,GAAG,CAAC,SAAyB,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,yBAAkB,CAAC,0BAA0B,SAAS,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,OAAO,KAAK,CAAC,IAAI,CACf,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAyB,CAAC,CAAC,CAClE,CAAC;AACJ,CAAC"}
+31
View File
@@ -0,0 +1,31 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ava_1 = __importDefault(require("ava"));
const analyses_1 = require("./analyses");
const util_1 = require("./util");
(0, ava_1.default)("All known analysis kinds can be parsed successfully", async (t) => {
for (const analysisKind of analyses_1.supportedAnalysisKinds) {
t.deepEqual(await (0, analyses_1.parseAnalysisKinds)(analysisKind), [analysisKind]);
}
});
(0, ava_1.default)("Parsing analysis kinds returns unique results", async (t) => {
const analysisKinds = await (0, analyses_1.parseAnalysisKinds)("code-scanning,code-quality,code-scanning");
t.deepEqual(analysisKinds, [
analyses_1.AnalysisKind.CodeScanning,
analyses_1.AnalysisKind.CodeQuality,
]);
});
(0, ava_1.default)("Parsing an unknown analysis kind fails with a configuration error", async (t) => {
await t.throwsAsync((0, analyses_1.parseAnalysisKinds)("code-scanning,foo"), {
instanceOf: util_1.ConfigurationError,
});
});
(0, ava_1.default)("Parsing analysis kinds requires at least one analysis kind", async (t) => {
await t.throwsAsync((0, analyses_1.parseAnalysisKinds)(","), {
instanceOf: util_1.ConfigurationError,
});
});
//# sourceMappingURL=analyses.test.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"analyses.test.js","sourceRoot":"","sources":["../src/analyses.test.ts"],"names":[],"mappings":";;;;;AAAA,8CAAuB;AAEvB,yCAIoB;AACpB,iCAA4C;AAE5C,IAAA,aAAI,EAAC,qDAAqD,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACtE,KAAK,MAAM,YAAY,IAAI,iCAAsB,EAAE,CAAC;QAClD,CAAC,CAAC,SAAS,CAAC,MAAM,IAAA,6BAAkB,EAAC,YAAY,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IACtE,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,+CAA+C,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAChE,MAAM,aAAa,GAAG,MAAM,IAAA,6BAAkB,EAC5C,0CAA0C,CAC3C,CAAC;IACF,CAAC,CAAC,SAAS,CAAC,aAAa,EAAE;QACzB,uBAAY,CAAC,YAAY;QACzB,uBAAY,CAAC,WAAW;KACzB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,mEAAmE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACpF,MAAM,CAAC,CAAC,WAAW,CAAC,IAAA,6BAAkB,EAAC,mBAAmB,CAAC,EAAE;QAC3D,UAAU,EAAE,yBAAkB;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAA,aAAI,EAAC,4DAA4D,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC7E,MAAM,CAAC,CAAC,WAAW,CAAC,IAAA,6BAAkB,EAAC,GAAG,CAAC,EAAE;QAC3C,UAAU,EAAE,yBAAkB;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
+4 -1
View File
@@ -69,6 +69,7 @@ const perf_hooks_1 = require("perf_hooks");
const yaml = __importStar(require("js-yaml"));
const semver = __importStar(require("semver"));
const actions_util_1 = require("./actions-util");
const analyses_1 = require("./analyses");
const api = __importStar(require("./api-client"));
const caching_utils_1 = require("./caching-utils");
const diff_informed_analysis_utils_1 = require("./diff-informed-analysis-utils");
@@ -263,12 +264,14 @@ async function getRawLanguages(languagesInput, repository, sourceRoot, logger) {
/**
* Get the default config, populated without user configuration file.
*/
async function getDefaultConfig({ languagesInput, queriesInput, qualityQueriesInput, packsInput, buildModeInput, dbLocation, trapCachingEnabled, dependencyCachingEnabled, debugMode, debugArtifactName, debugDatabaseName, repository, tempDir, codeql, sourceRoot, githubVersion, features, logger, }) {
async function getDefaultConfig({ analysisKindsInput, languagesInput, queriesInput, qualityQueriesInput, packsInput, buildModeInput, dbLocation, trapCachingEnabled, dependencyCachingEnabled, debugMode, debugArtifactName, debugDatabaseName, repository, tempDir, codeql, sourceRoot, githubVersion, features, logger, }) {
const analysisKinds = await (0, analyses_1.parseAnalysisKinds)(analysisKindsInput);
const languages = await getLanguages(codeql, languagesInput, repository, sourceRoot, logger);
const buildMode = await parseBuildModeInput(buildModeInput, languages, features, logger);
const augmentationProperties = await calculateAugmentation(packsInput, queriesInput, qualityQueriesInput, languages);
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(trapCachingEnabled, codeql, languages, logger);
return {
analysisKinds,
languages,
buildMode,
originalUserInput: {},
File diff suppressed because one or more lines are too long
+3
View File
@@ -43,6 +43,7 @@ const ava_1 = __importDefault(require("ava"));
const yaml = __importStar(require("js-yaml"));
const sinon = __importStar(require("sinon"));
const actionsUtil = __importStar(require("./actions-util"));
const analyses_1 = require("./analyses");
const api = __importStar(require("./api-client"));
const caching_utils_1 = require("./caching-utils");
const codeql_1 = require("./codeql");
@@ -59,6 +60,7 @@ const util_1 = require("./util");
const githubVersion = { type: util_1.GitHubVariant.DOTCOM };
function createTestInitConfigInputs(overrides) {
return Object.assign({}, {
analysisKindsInput: "code-scanning",
languagesInput: undefined,
queriesInput: undefined,
qualityQueriesInput: undefined,
@@ -272,6 +274,7 @@ function mockListLanguages(languages) {
fs.mkdirSync(path.join(tempDir, "foo"));
// And the config we expect it to parse to
const expectedConfig = {
analysisKinds: [analyses_1.AnalysisKind.CodeScanning],
languages: [languages_1.KnownLanguage.javascript],
buildMode: util_1.BuildMode.None,
originalUserInput: {
File diff suppressed because one or more lines are too long
+1
View File
@@ -218,6 +218,7 @@ async function run() {
}
}
config = await (0, init_1.initConfig)({
analysisKindsInput: (0, actions_util_1.getRequiredInput)("analysis-kinds"),
languagesInput: (0, actions_util_1.getOptionalInput)("languages"),
queriesInput: (0, actions_util_1.getOptionalInput)("queries"),
qualityQueriesInput: (0, actions_util_1.getOptionalInput)("quality-queries"),
File diff suppressed because one or more lines are too long
+36
View File
@@ -0,0 +1,36 @@
import test from "ava";
import {
AnalysisKind,
parseAnalysisKinds,
supportedAnalysisKinds,
} from "./analyses";
import { ConfigurationError } from "./util";
test("All known analysis kinds can be parsed successfully", async (t) => {
for (const analysisKind of supportedAnalysisKinds) {
t.deepEqual(await parseAnalysisKinds(analysisKind), [analysisKind]);
}
});
test("Parsing analysis kinds returns unique results", async (t) => {
const analysisKinds = await parseAnalysisKinds(
"code-scanning,code-quality,code-scanning",
);
t.deepEqual(analysisKinds, [
AnalysisKind.CodeScanning,
AnalysisKind.CodeQuality,
]);
});
test("Parsing an unknown analysis kind fails with a configuration error", async (t) => {
await t.throwsAsync(parseAnalysisKinds("code-scanning,foo"), {
instanceOf: ConfigurationError,
});
});
test("Parsing analysis kinds requires at least one analysis kind", async (t) => {
await t.throwsAsync(parseAnalysisKinds(","), {
instanceOf: ConfigurationError,
});
});
+36
View File
@@ -1,4 +1,40 @@
import { ConfigurationError } from "./util";
export enum AnalysisKind {
CodeScanning = "code-scanning",
CodeQuality = "code-quality",
}
// Exported for testing. A set of all known analysis kinds.
export const supportedAnalysisKinds = new Set(Object.values(AnalysisKind));
/**
* Parses a comma-separated string into a list of unique analysis kinds.
* Throws a configuration error if the input contains unknown analysis kinds
* or doesn't contain at least one element.
*
* @param input The comma-separated string to parse.
* @returns The array of unique analysis kinds that were parsed from the input string.
*/
export async function parseAnalysisKinds(
input: string,
): Promise<AnalysisKind[]> {
const components = input.split(",");
if (components.length < 1) {
throw new ConfigurationError(
"At least one analysis kind must be configured.",
);
}
for (const component of components) {
if (!supportedAnalysisKinds.has(component as AnalysisKind)) {
throw new ConfigurationError(`Unknown analysis kind: ${component}`);
}
}
// Return all unique elements.
return Array.from(
new Set(components.map((component) => component as AnalysisKind)),
);
}
+3
View File
@@ -7,6 +7,7 @@ import * as yaml from "js-yaml";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { AnalysisKind } from "./analyses";
import * as api from "./api-client";
import { CachingKind } from "./caching-utils";
import { createStubCodeQL } from "./codeql";
@@ -47,6 +48,7 @@ function createTestInitConfigInputs(
return Object.assign(
{},
{
analysisKindsInput: "code-scanning",
languagesInput: undefined,
queriesInput: undefined,
qualityQueriesInput: undefined,
@@ -322,6 +324,7 @@ test("load non-empty input", async (t) => {
// And the config we expect it to parse to
const expectedConfig: configUtils.Config = {
analysisKinds: [AnalysisKind.CodeScanning],
languages: [KnownLanguage.javascript],
buildMode: BuildMode.None,
originalUserInput: {
+10
View File
@@ -6,6 +6,7 @@ import * as yaml from "js-yaml";
import * as semver from "semver";
import { isAnalyzingPullRequest } from "./actions-util";
import { AnalysisKind, parseAnalysisKinds } from "./analyses";
import * as api from "./api-client";
import { CachingKind, getCachingKind } from "./caching-utils";
import { type CodeQL } from "./codeql";
@@ -93,6 +94,10 @@ interface IncludeQueryFilter {
* Format of the parsed config file.
*/
export interface Config {
/**
* Set of analysis kinds that are enabled.
*/
analysisKinds: AnalysisKind[];
/**
* Set of languages to run analysis for.
*/
@@ -483,6 +488,7 @@ export async function getRawLanguages(
/** Inputs required to initialize a configuration. */
export interface InitConfigInputs {
analysisKindsInput: string;
languagesInput: string | undefined;
queriesInput: string | undefined;
qualityQueriesInput: string | undefined;
@@ -511,6 +517,7 @@ export interface InitConfigInputs {
* Get the default config, populated without user configuration file.
*/
export async function getDefaultConfig({
analysisKindsInput,
languagesInput,
queriesInput,
qualityQueriesInput,
@@ -530,6 +537,8 @@ export async function getDefaultConfig({
features,
logger,
}: InitConfigInputs): Promise<Config> {
const analysisKinds = await parseAnalysisKinds(analysisKindsInput);
const languages = await getLanguages(
codeql,
languagesInput,
@@ -560,6 +569,7 @@ export async function getDefaultConfig({
);
return {
analysisKinds,
languages,
buildMode,
originalUserInput: {},
+1
View File
@@ -385,6 +385,7 @@ async function run() {
}
config = await initConfig({
analysisKindsInput: getRequiredInput("analysis-kinds"),
languagesInput: getOptionalInput("languages"),
queriesInput: getOptionalInput("queries"),
qualityQueriesInput: getOptionalInput("quality-queries"),