Merge branch 'main' into henrymercer/cleanup-for-mrva

This commit is contained in:
Henry Mercer
2025-08-07 15:06:26 +01:00
29 changed files with 495 additions and 495 deletions

View File

@@ -1,9 +1,14 @@
import * as github from "@actions/github";
import test from "ava";
import { getPullRequestBranches, isAnalyzingPullRequest } from "./actions-util";
import {
fixCodeQualityCategory,
getPullRequestBranches,
isAnalyzingPullRequest,
} from "./actions-util";
import { computeAutomationID } from "./api-client";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import { setupTests } from "./testing-utils";
import { initializeEnvironment } from "./util";
@@ -193,3 +198,51 @@ test("initializeEnvironment", (t) => {
initializeEnvironment("1.2.3");
t.deepEqual(process.env[EnvVar.VERSION], "1.2.3");
});
test("fixCodeQualityCategory", (t) => {
withMockedEnv(
{
GITHUB_EVENT_NAME: "dynamic",
},
() => {
const logger = getRunnerLogger(true);
// Categories that should get adjusted.
t.is(fixCodeQualityCategory(logger, "/language:c#"), "/language:csharp");
t.is(fixCodeQualityCategory(logger, "/language:cpp"), "/language:c-cpp");
t.is(fixCodeQualityCategory(logger, "/language:c"), "/language:c-cpp");
t.is(
fixCodeQualityCategory(logger, "/language:java"),
"/language:java-kotlin",
);
t.is(
fixCodeQualityCategory(logger, "/language:javascript"),
"/language:javascript-typescript",
);
t.is(
fixCodeQualityCategory(logger, "/language:typescript"),
"/language:javascript-typescript",
);
t.is(
fixCodeQualityCategory(logger, "/language:kotlin"),
"/language:java-kotlin",
);
// Categories that should not get adjusted.
t.is(
fixCodeQualityCategory(logger, "/language:csharp"),
"/language:csharp",
);
t.is(fixCodeQualityCategory(logger, "/language:go"), "/language:go");
t.is(
fixCodeQualityCategory(logger, "/language:actions"),
"/language:actions",
);
// Other cases.
t.is(fixCodeQualityCategory(logger, undefined), undefined);
t.is(fixCodeQualityCategory(logger, "random string"), "random string");
t.is(fixCodeQualityCategory(logger, "kotlin"), "kotlin");
},
);
});

View File

@@ -8,6 +8,7 @@ import * as io from "@actions/io";
import { JSONSchemaForNPMPackageJsonFiles } from "@schemastore/package";
import type { Config } from "./config-utils";
import { Logger } from "./logging";
import {
doesDirectoryExist,
getCodeQLDatabasePath,
@@ -409,3 +410,47 @@ export function getPullRequestBranches(): PullRequestBranches | undefined {
export function isAnalyzingPullRequest(): boolean {
return getPullRequestBranches() !== undefined;
}
/**
* A workaround for code quality to map category names from old default setup workflows
* to ones that the code quality service expects.
*/
const qualityCategoryMapping: Record<string, string> = {
"c#": "csharp",
cpp: "c-cpp",
c: "c-cpp",
"c++": "c-cpp",
java: "java-kotlin",
javascript: "javascript-typescript",
typescript: "javascript-typescript",
kotlin: "java-kotlin",
};
/** Adjusts the category string for a Code Quality SARIF file if an "old"
* category identifier is used by Default Setup.
*/
export function fixCodeQualityCategory(
logger: Logger,
category?: string,
): string | undefined {
// The `category` should always be set by Default Setup. We perform this check
// to avoid potential issues if Code Quality supports Advanced Setup in the future
// and before this workaround is removed.
if (
category !== undefined &&
isDefaultSetup() &&
category.startsWith("/language:")
) {
const language = category.substring("/language:".length);
const mappedLanguage = qualityCategoryMapping[language];
if (mappedLanguage) {
const newCategory = `/language:${mappedLanguage}`;
logger.info(
`Adjusted category for Code Quality from '${category}' to '${newCategory}'.`,
);
return newCategory;
}
}
return category;
}

View File

@@ -336,7 +336,10 @@ async function run() {
const qualityUploadResult = await uploadLib.uploadFiles(
outputDir,
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
actionsUtil.fixCodeQualityCategory(
logger,
actionsUtil.getOptionalInput("category"),
),
features,
logger,
uploadLib.CodeQualityTarget,

View File

@@ -7,6 +7,7 @@ import del from "del";
import * as yaml from "js-yaml";
import {
fixCodeQualityCategory,
getRequiredInput,
getTemporaryDirectory,
PullRequestBranches,
@@ -698,24 +699,29 @@ export async function runQueries(
undefined,
sarifFile,
config.debugMode,
automationDetailsId,
);
let qualityAnalysisSummary: string | undefined;
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
logger.info(`Interpreting quality results for ${language}`);
const qualityCategory = fixCodeQualityCategory(
logger,
automationDetailsId,
);
const qualitySarifFile = path.join(
sarifFolder,
`${language}.quality.sarif`,
);
const qualityAnalysisSummary = await runInterpretResults(
qualityAnalysisSummary = await runInterpretResults(
language,
config.augmentationProperties.qualityQueriesInput.map((i) =>
resolveQuerySuiteAlias(language, i.uses),
),
qualitySarifFile,
config.debugMode,
qualityCategory,
);
// TODO: move
logger.info(qualityAnalysisSummary);
}
const endTimeInterpretResults = new Date();
statusReport[`interpret_results_${language}_duration_ms`] =
@@ -723,6 +729,10 @@ export async function runQueries(
logger.endGroup();
logger.info(analysisSummary);
if (qualityAnalysisSummary) {
logger.info(qualityAnalysisSummary);
}
if (await features.getValue(Feature.QaTelemetryEnabled)) {
const perQueryAlertCounts = getPerQueryAlertCounts(sarifFile);
@@ -759,6 +769,7 @@ export async function runQueries(
queries: string[] | undefined,
sarifFile: string,
enableDebugLogging: boolean,
category: string | undefined,
): Promise<string> {
const databasePath = util.getCodeQLDatabasePath(config, language);
return await codeql.databaseInterpretResults(
@@ -769,7 +780,7 @@ export async function runQueries(
threadsFlag,
enableDebugLogging ? "-vv" : "-v",
sarifRunPropertyFlag,
automationDetailsId,
category,
config,
features,
);

View File

@@ -1159,6 +1159,7 @@ const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
codeQL,
args.languagesInput,
mockRepositoryNwo,
".",
mockLogger,
);
@@ -1171,6 +1172,7 @@ const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
codeQL,
args.languagesInput,
mockRepositoryNwo,
".",
mockLogger,
),
{ message: args.expectedError },

View File

@@ -321,23 +321,58 @@ export async function getSupportedLanguageMap(
return supportedLanguages;
}
const baseWorkflowsPath = ".github/workflows";
/**
* Determines if there exists a `.github/workflows` directory with at least
* one file in it, which we use as an indicator that there are Actions
* workflows in the workspace. This doesn't perfectly detect whether there
* are actually workflows, but should be a good approximation.
*
* Alternatively, we could check specifically for yaml files, or call the
* API to check if it knows about workflows.
*
* @returns True if the non-empty directory exists, false if not.
*/
export function hasActionsWorkflows(sourceRoot: string): boolean {
const workflowsPath = path.resolve(sourceRoot, baseWorkflowsPath);
const stats = fs.lstatSync(workflowsPath);
return (
stats !== undefined &&
stats.isDirectory() &&
fs.readdirSync(workflowsPath).length > 0
);
}
/**
* Gets the set of languages in the current repository.
*/
export async function getRawLanguagesInRepo(
repository: RepositoryNwo,
sourceRoot: string,
logger: Logger,
): Promise<string[]> {
logger.debug(`GitHub repo ${repository.owner} ${repository.repo}`);
logger.debug(
`Automatically detecting languages (${repository.owner}/${repository.repo})`,
);
const response = await api.getApiClient().rest.repos.listLanguages({
owner: repository.owner,
repo: repository.repo,
});
logger.debug(`Languages API response: ${JSON.stringify(response)}`);
return Object.keys(response.data as Record<string, number>).map((language) =>
language.trim().toLowerCase(),
const result = Object.keys(response.data as Record<string, number>).map(
(language) => language.trim().toLowerCase(),
);
if (hasActionsWorkflows(sourceRoot)) {
logger.debug(`Found a .github/workflows directory`);
result.push("actions");
}
logger.debug(`Raw languages in repository: ${result.join(", ")}`);
return result;
}
/**
@@ -354,12 +389,14 @@ export async function getLanguages(
codeql: CodeQL,
languagesInput: string | undefined,
repository: RepositoryNwo,
sourceRoot: string,
logger: Logger,
): Promise<Language[]> {
// Obtain languages without filtering them.
const { rawLanguages, autodetected } = await getRawLanguages(
languagesInput,
repository,
sourceRoot,
logger,
);
@@ -420,6 +457,7 @@ export function getRawLanguagesNoAutodetect(
export async function getRawLanguages(
languagesInput: string | undefined,
repository: RepositoryNwo,
sourceRoot: string,
logger: Logger,
): Promise<{
rawLanguages: string[];
@@ -432,7 +470,7 @@ export async function getRawLanguages(
}
// Otherwise, autodetect languages in the repository.
return {
rawLanguages: await getRawLanguagesInRepo(repository, logger),
rawLanguages: await getRawLanguagesInRepo(repository, sourceRoot, logger),
autodetected: true,
};
}
@@ -481,6 +519,7 @@ export async function getDefaultConfig({
repository,
tempDir,
codeql,
sourceRoot,
githubVersion,
features,
logger,
@@ -489,6 +528,7 @@ export async function getDefaultConfig({
codeql,
languagesInput,
repository,
sourceRoot,
logger,
);

View File

@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.22.2",
"cliVersion": "2.22.2",
"priorBundleVersion": "codeql-bundle-v2.22.1",
"priorCliVersion": "2.22.1"
"bundleVersion": "codeql-bundle-v2.22.3",
"cliVersion": "2.22.3",
"priorBundleVersion": "codeql-bundle-v2.22.2",
"priorCliVersion": "2.22.2"
}

View File

@@ -112,7 +112,7 @@ async function run() {
await upload_lib.uploadSpecifiedFiles(
qualitySarifFiles,
checkoutPath,
category,
actionsUtil.fixCodeQualityCategory(logger, category),
features,
logger,
upload_lib.CodeQualityTarget,