Skip to content

Commit f638e8b

Browse files
committed
Add applyAnalysisKindConfig
1 parent 391a05d commit f638e8b

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

src/config/db-config.test.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import test, { ExecutionContext } from "ava";
22

3+
import { AnalysisKind } from "../analyses";
34
import { RepositoryProperties } from "../feature-flags/properties";
45
import { BuiltInLanguage, Language } from "../languages";
56
import { getRunnerLogger } from "../logging";
@@ -14,6 +15,117 @@ import { ConfigurationError, prettyPrintPack } from "../util";
1415

1516
import * as dbConfig from "./db-config";
1617

18+
function makeUserConfigWithActionExtensions(options?: {
19+
analysisConfig?: dbConfig.UserConfig;
20+
analysisKinds?: AnalysisKind[];
21+
baseConfig?: dbConfig.UserConfig;
22+
}) {
23+
const testConfig = { ...options?.baseConfig };
24+
25+
const allAnalysisKinds = Object.values(AnalysisKind);
26+
const analysisKinds = options?.analysisKinds ?? allAnalysisKinds;
27+
for (const analysisKind of analysisKinds) {
28+
testConfig[analysisKind] = { ...options?.analysisConfig };
29+
}
30+
31+
return testConfig as dbConfig.UserConfigWithActionExtensions;
32+
}
33+
34+
test("hasAnalysisKindKey - finds `AnalysisKind` sections", async (t) => {
35+
const testConfig = makeUserConfigWithActionExtensions();
36+
37+
for (const analysisKind of Object.values(AnalysisKind)) {
38+
// Check that it finds the `analysisKind`-specific section among multiple.
39+
t.true(dbConfig.hasAnalysisKindKey(analysisKind, testConfig));
40+
// And for just the `analysisKind`-specific section.
41+
t.true(dbConfig.hasAnalysisKindKey(analysisKind, { [analysisKind]: {} }));
42+
}
43+
});
44+
45+
test("hasAnalysisKindKey - returns false if there is no relevant section", async (t) => {
46+
const testConfig = makeUserConfigWithActionExtensions();
47+
48+
for (const analysisKind of Object.values(AnalysisKind)) {
49+
// Clone the `testConfig` and remove the `analysisKind`-specific key.
50+
const testConfigClone = { ...testConfig };
51+
delete testConfigClone[analysisKind];
52+
53+
// Check that it doesn't find an `analysisKind`-specific section.
54+
t.false(dbConfig.hasAnalysisKindKey(analysisKind, testConfigClone));
55+
}
56+
});
57+
58+
test("applyAnalysisKindConfig - applies analysis-specific settings", async (t) => {
59+
for (const analysisKind of Object.values(AnalysisKind)) {
60+
const testConfig = makeUserConfigWithActionExtensions({
61+
analysisKinds: [analysisKind],
62+
analysisConfig: {
63+
"paths-ignore": [],
64+
},
65+
baseConfig: {
66+
"paths-ignore": ["a", "b", "c"],
67+
"threat-models": ["local"],
68+
},
69+
});
70+
const clonedTestConfig = { ...testConfig };
71+
72+
const result = dbConfig.applyAnalysisKindConfig(analysisKind, testConfig);
73+
74+
// The `analysisKind`-specific section should have been removed.
75+
// `threat-models` should be unchanged from the `baseConfig`.
76+
// `paths-ignore` should have been overwritten with the `analysisKind`-specific config.
77+
t.deepEqual(result, {
78+
"threat-models": ["local"],
79+
"paths-ignore": [],
80+
});
81+
82+
// The input `testConfig` should not have changed.
83+
t.deepEqual(testConfig, clonedTestConfig);
84+
}
85+
});
86+
87+
test("applyAnalysisKindConfig - removes all `AnalysisKind`-specific keys", async (t) => {
88+
for (const analysisKind of Object.values(AnalysisKind)) {
89+
const testConfig = makeUserConfigWithActionExtensions({
90+
analysisConfig: {
91+
"paths-ignore": ["a", "b", "c"],
92+
},
93+
baseConfig: {},
94+
});
95+
96+
const result = dbConfig.applyAnalysisKindConfig(analysisKind, testConfig);
97+
98+
// All `AnalysisKind`-specific sections should have been removed.
99+
// `paths-ignore` should have been overwritten with the analysis-specific config.
100+
t.deepEqual(result, {
101+
"paths-ignore": ["a", "b", "c"],
102+
});
103+
}
104+
});
105+
106+
test("applyAnalysisKindConfig - returns the `baseConfig` if there is no `analysisKind`-specific key", async (t) => {
107+
const baseConfig = {
108+
"disable-default-queries": true,
109+
"paths-ignore": ["a", "b", "c"],
110+
queries: [{ name: "foo", uses: "something" }],
111+
packs: { test: ["bar"] },
112+
"default-setup": { org: { "model-packs": ["pack"] } },
113+
} satisfies dbConfig.UserConfig;
114+
115+
const testConfig = makeUserConfigWithActionExtensions({
116+
analysisKinds: [],
117+
baseConfig,
118+
});
119+
120+
const result = dbConfig.applyAnalysisKindConfig(
121+
AnalysisKind.CodeScanning,
122+
testConfig,
123+
);
124+
125+
// The result should be the same as the `baseConfig`.
126+
t.deepEqual(result, baseConfig);
127+
});
128+
17129
/**
18130
* Test macro for ensuring the packs block is valid
19131
*/

src/config/db-config.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as yaml from "js-yaml";
44
import * as jsonschema from "jsonschema";
55
import * as semver from "semver";
66

7+
import { AnalysisKind } from "../analyses";
78
import {
89
addNoLanguageDiagnostic,
910
makeTelemetryDiagnostic,
@@ -77,6 +78,68 @@ export interface UserConfig {
7778
"default-setup"?: DefaultSetupConfig;
7879
}
7980

81+
/** Represents a `UserConfig` with CodeQL Action extensions. */
82+
export type UserConfigWithActionExtensions = {
83+
/**
84+
* We add a key for each analysis kind that may contain options
85+
* specific to that kind of analysis.
86+
*/
87+
[T in keyof typeof AnalysisKind as (typeof AnalysisKind)[T]]?: UserConfig;
88+
} & UserConfig;
89+
90+
/**
91+
* Determines whether `config` contains an `analysisKind`-specific section.
92+
*
93+
* @param analysisKind The analysis section to check for.
94+
* @param config The base `UserConfig` value.
95+
*/
96+
export function hasAnalysisKindKey(
97+
analysisKind: AnalysisKind,
98+
config: UserConfig,
99+
): config is UserConfigWithActionExtensions {
100+
return analysisKind in config && typeof config[analysisKind] === "object";
101+
}
102+
103+
/**
104+
* Clones `config` and removes any `AnalysisKind`-specific sections.
105+
*/
106+
export function removeAnalysisKindConfigs(config: UserConfig): UserConfig {
107+
// Clone the configuration and remove any `AnalysisKind`-specific sections.
108+
const configWithoutScopes = { ...config };
109+
for (const kind of Object.values(AnalysisKind)) {
110+
delete configWithoutScopes[kind];
111+
}
112+
return configWithoutScopes;
113+
}
114+
115+
/**
116+
* Applies an `analysisKind`-specific configuration, if any, to the resulting configuration
117+
* and removes all `AnalysisKind`-specific configuration sections.
118+
*
119+
* @param analysisKind The `AnalysisKind` to apply specific configuration options for.
120+
* @param config The loaded configuration, possibly including `analysisKind`-specific settings.
121+
*/
122+
export function applyAnalysisKindConfig(
123+
analysisKind: AnalysisKind,
124+
config: UserConfig,
125+
): UserConfig {
126+
// Clone the configuration and remove any `AnalysisKind`-specific sections.
127+
const configWithoutScopes = removeAnalysisKindConfigs(config);
128+
129+
// If there is no `analysisKind`-specific section, return the input configuration
130+
// without any `AnalysisKind`-specific sections.
131+
if (!hasAnalysisKindKey(analysisKind, config)) {
132+
return configWithoutScopes;
133+
}
134+
135+
// Otherwise, extract the `analysisKind`-specific configuration from the input `config`.
136+
const analysisKindSpecificConfig = config[analysisKind];
137+
138+
// Return the base configuration (without any `AnalysisKind`-specific sections) and
139+
// override the top-level properties with the `analysisKind`-specific settings.
140+
return { ...configWithoutScopes, ...analysisKindSpecificConfig };
141+
}
142+
80143
/** A subset of the `UserConfig` schema that is used by Default Setup. */
81144
const DEFAULT_SETUP_CONFIG_SCHEMA = {
82145
"threat-models": json.optional(json.array(json.string)),

0 commit comments

Comments
 (0)