Use applyAnalysisKindConfig in loadUserConfig

This commit is contained in:
Michael B. Gale
2026-07-28 13:52:29 +01:00
parent f638e8b5fb
commit af8f50bf94
3 changed files with 121 additions and 5 deletions

34
lib/entry-points.js generated
View File

@@ -148115,6 +148115,24 @@ var ORG_SCHEMA = {
var DEFAULT_SETUP_SCHEMA = {
org: optional(object(ORG_SCHEMA))
};
function hasAnalysisKindKey(analysisKind, config) {
return analysisKind in config && typeof config[analysisKind] === "object";
}
function removeAnalysisKindConfigs(config) {
const configWithoutScopes = { ...config };
for (const kind of Object.values(AnalysisKind)) {
delete configWithoutScopes[kind];
}
return configWithoutScopes;
}
function applyAnalysisKindConfig(analysisKind, config) {
const configWithoutScopes = removeAnalysisKindConfigs(config);
if (!hasAnalysisKindKey(analysisKind, config)) {
return configWithoutScopes;
}
const analysisKindSpecificConfig = config[analysisKind];
return { ...configWithoutScopes, ...analysisKindSpecificConfig };
}
var DEFAULT_SETUP_CONFIG_SCHEMA = {
"threat-models": optional(array(string)),
"default-setup": optional(
@@ -149391,7 +149409,7 @@ async function downloadCacheWithTime(codeQL, languages, logger) {
const trapCacheDownloadTime = import_perf_hooks.performance.now() - start;
return { trapCaches, trapCacheDownloadTime };
}
async function loadUserConfig(actionState, configFile, workspacePath, apiDetails, tempDir) {
async function loadUserConfig(actionState, analysisKinds, configFile, workspacePath, apiDetails, tempDir) {
let config;
if (isLocal(configFile)) {
if (configFile !== userConfigFromActionPath(tempDir)) {
@@ -149412,7 +149430,17 @@ async function loadUserConfig(actionState, configFile, workspacePath, apiDetails
}
config = await getRemoteConfig(actionState, configFile, apiDetails);
}
return config;
const allowScopedConfig = await actionState.features.getValue(
"scoped_configurations" /* ScopedConfigurations */
);
if (allowScopedConfig && analysisKinds.length === 1) {
return applyAnalysisKindConfig(analysisKinds[0], config);
} else if (allowScopedConfig) {
actionState.logger.info(
`Ignoring '${"scoped_configurations" /* ScopedConfigurations */}' feature, because multiple analysis kinds are enabled.`
);
}
return removeAnalysisKindConfigs(config);
}
var OVERLAY_ANALYSIS_FEATURES = {
cpp: "overlay_analysis_cpp" /* OverlayAnalysisCpp */,
@@ -149713,6 +149741,7 @@ async function determineUserConfig(action, tempDir, inputs) {
);
const fromConfigFile = await loadUserConfig(
action,
inputs.analysisKinds,
inputs.configFile,
inputs.workspacePath,
inputs.apiDetails,
@@ -149749,6 +149778,7 @@ async function determineUserConfig(action, tempDir, inputs) {
action.logger.debug(`Using configuration file: ${inputs.configFile}`);
return await loadUserConfig(
action,
inputs.analysisKinds,
inputs.configFile,
inputs.workspacePath,
inputs.apiDetails,

View File

@@ -12,7 +12,7 @@ import { AnalysisKind, supportedAnalysisKinds } from "./analyses";
import * as api from "./api-client";
import { CachingKind } from "./caching-utils";
import { createStubCodeQL } from "./codeql";
import { UserConfig } from "./config/db-config";
import { UserConfig, UserConfigWithActionExtensions } from "./config/db-config";
import * as file from "./config/file";
import * as configUtils from "./config-utils";
import * as errorMessages from "./error-messages";
@@ -2544,6 +2544,7 @@ test("loadUserConfig - loads local configuration files", async (t) => {
) =>
configUtils.loadUserConfig(
actionState,
[AnalysisKind.CodeScanning],
filePath,
workspaceDir,
SAMPLE_DOTCOM_API_DETAILS,
@@ -2587,7 +2588,13 @@ test.serial("loadUserConfig - loads remote configuration files", async (t) => {
const remoteAddress = "owner/repo/file@ref";
await callee(configUtils.loadUserConfig)
.withArgs(remoteAddress, tmpDir, SAMPLE_DOTCOM_API_DETAILS, tmpDir)
.withArgs(
[AnalysisKind.CodeScanning],
remoteAddress,
tmpDir,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
)
.passes(t.deepEqual, {});
t.true(
@@ -2665,6 +2672,7 @@ test.serial(
// Prepare the test call to `loadUserConfig`.
const targetWithArgs = target.withArgs(
[AnalysisKind.CodeScanning],
address,
tmpDir,
SAMPLE_DOTCOM_API_DETAILS,
@@ -2705,3 +2713,57 @@ test.serial(
});
},
);
test("loadUserConfig - applies analysis-specific settings", async (t) => {
await withTmpDir(async (workspaceDir) => {
await withTmpDir(async (tmpDir) => {
// Construct the test target.
const loadUserConfig = (
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
filePath: string,
analysisKind: AnalysisKind,
) =>
configUtils.loadUserConfig(
actionState,
[analysisKind],
filePath,
workspaceDir,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
);
const target = callee(loadUserConfig);
for (const analysisKind of Object.values(AnalysisKind)) {
const analysisSpecificConfig: UserConfig = {
paths: ["custom"],
queries: [{ uses: "foo" }],
};
const baseConfig: UserConfig = { "threat-models": ["remote"] };
// Write a local configuration file with an `analysisKind`-specific section.
const configPath = path.join(workspaceDir, "codeql.yml");
fs.writeFileSync(
configPath,
JSON.stringify({
...baseConfig,
[analysisKind]: analysisSpecificConfig,
} satisfies UserConfigWithActionExtensions),
"utf8",
);
// The resulting configuration should be the `baseConfig` with the `analysisSpecificConfig`
// merged into it if the FF is enabled.
await target
.withArgs(configPath, analysisKind)
.withFeatures([Feature.ScopedConfigurations])
.passes(t.deepEqual, { ...baseConfig, ...analysisSpecificConfig });
// Without the FF, it should be the `baseConfig`. This also validates that the
// `AnalysisKind`-specific sections are removed even if the FF is off.
await target
.withArgs(configPath, analysisKind)
.passes(t.deepEqual, baseConfig);
}
});
});
});

View File

@@ -24,11 +24,13 @@ import { getCachingKind } from "./caching-utils";
import { type CodeQL } from "./codeql";
import { type Config } from "./config/action-config";
import {
applyAnalysisKindConfig,
calculateAugmentation,
ExcludeQueryFilter,
generateCodeScanningConfig,
mergeDefaultSetupAndUserConfigs,
parseUserConfig,
removeAnalysisKindConfigs,
UserConfig,
} from "./config/db-config";
import {
@@ -484,6 +486,7 @@ async function downloadCacheWithTime(
*/
export async function loadUserConfig(
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
analysisKinds: AnalysisKind[],
configFile: string,
workspacePath: string,
apiDetails: api.GitHubApiCombinedDetails,
@@ -516,7 +519,26 @@ export async function loadUserConfig(
config = await getRemoteConfig(actionState, configFile, apiDetails);
}
return config;
// If the `ScopedConfigurations` feature is enabled, we allow options specific to the
// current `AnalysisKind` to be applied on top of a base configuration.
const allowScopedConfig = await actionState.features.getValue(
Feature.ScopedConfigurations,
);
if (allowScopedConfig && analysisKinds.length === 1) {
// Apply the `analysisKind`-specific configuration.
return applyAnalysisKindConfig(analysisKinds[0], config);
} else if (allowScopedConfig) {
actionState.logger.info(
`Ignoring '${Feature.ScopedConfigurations}' feature, because multiple analysis kinds are enabled.`,
);
}
// Remove `AnalysisKind`-specific sections from `config` even if the FF is not enabled,
// because the CLI will warn about unknown keys in the configuration. This is to guard
// against the case where we ship the feature, enable the FF, start using the new keys,
// and then need to roll the FF back.
return removeAnalysisKindConfigs(config);
}
/**
@@ -1075,6 +1097,7 @@ export async function determineUserConfig(
);
const fromConfigFile = await loadUserConfig(
action,
inputs.analysisKinds,
inputs.configFile,
inputs.workspacePath,
inputs.apiDetails,
@@ -1122,6 +1145,7 @@ export async function determineUserConfig(
action.logger.debug(`Using configuration file: ${inputs.configFile}`);
return await loadUserConfig(
action,
inputs.analysisKinds,
inputs.configFile,
inputs.workspacePath,
inputs.apiDetails,