mirror of
https://github.com/github/codeql-action.git
synced 2026-10-30 01:27:22 +00:00
Merge branch 'main' into mbg/improve-checkout-path
This commit is contained in:
@@ -131,3 +131,38 @@ export interface Config {
|
||||
*/
|
||||
enableFileCoverageInformation: boolean;
|
||||
}
|
||||
|
||||
/** Properties available in `Config`. */
|
||||
export type ActionConfigProperties = keyof Config;
|
||||
|
||||
/**
|
||||
* The CodeQL Action configuration state that is persisted
|
||||
* between steps of the CodeQL Action in a CodeQL workflow.
|
||||
*
|
||||
* By default, this is equivalent to `Config`. By explicitly
|
||||
* providing required property names for `T` this can be restricted
|
||||
* to requiring only those. For example, in `ActionConfig<"codeQLCmd">`
|
||||
* all properties except `codeqlCmd` are optional.
|
||||
*/
|
||||
export type ActionConfig<
|
||||
T extends ActionConfigProperties = ActionConfigProperties,
|
||||
> = Partial<Config> & Pick<Config, T>;
|
||||
|
||||
/** Helper function to check whether `config` has `key` to narrow the type of `config`. */
|
||||
export function hasConfigKey<Key extends ActionConfigProperties>(
|
||||
config: Partial<Config>,
|
||||
key: Key,
|
||||
): config is ActionConfig<Key> {
|
||||
return config[key] !== undefined;
|
||||
}
|
||||
|
||||
/** A minimal configuration that we can make available even when `init` aborts. */
|
||||
export type AbortedInitConfig = ActionConfig<
|
||||
| "version"
|
||||
| "tempDir"
|
||||
| "gitHubVersion"
|
||||
| "debugMode"
|
||||
| "languages"
|
||||
| "analysisKinds"
|
||||
| "codeQLCmd"
|
||||
>;
|
||||
|
||||
@@ -625,3 +625,73 @@ test("mergeDefaultSetupAndUserConfigs - warns about invalid keys from Default Se
|
||||
`Invalid keys in Default Setup configuration: ${expectedInvalidKeys}`,
|
||||
]);
|
||||
});
|
||||
|
||||
/** Parses `contents` as a configuration without validating it. */
|
||||
function parseUnvalidatedConfig(contents: string): dbConfig.UserConfig {
|
||||
return dbConfig.parseUserConfig(
|
||||
getRunnerLogger(true),
|
||||
"test",
|
||||
contents,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
test("matchesDefaultSetupConfigSchema - returns true for configurations that only use Default Setup properties", (t) => {
|
||||
for (const contents of [
|
||||
[
|
||||
"default-setup:",
|
||||
" org:",
|
||||
" model-packs: [ github/immutable-actions-list@0.0.1 ]",
|
||||
"threat-models: [ ]",
|
||||
].join("\n"),
|
||||
"threat-models: [ local ]",
|
||||
"{}",
|
||||
]) {
|
||||
t.true(
|
||||
dbConfig.matchesDefaultSetupConfigSchema(
|
||||
parseUnvalidatedConfig(contents),
|
||||
),
|
||||
contents,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("matchesDefaultSetupConfigSchema - returns false for configurations that use other properties", (t) => {
|
||||
for (const contents of [
|
||||
"queries: [ { uses: ./queries/show_ifs.ql } ]",
|
||||
"paths-ignore: [ tests ]",
|
||||
"default-setup: { org: { model-packs: [], queries: [] } }",
|
||||
]) {
|
||||
t.false(
|
||||
dbConfig.matchesDefaultSetupConfigSchema(
|
||||
parseUnvalidatedConfig(contents),
|
||||
),
|
||||
contents,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("matchesDefaultSetupConfigSchema - returns false for invalid Default Setup properties", (t) => {
|
||||
for (const contents of [
|
||||
"threat-models: local",
|
||||
"default-setup: { org: { model-packs: [ 1 ] } }",
|
||||
]) {
|
||||
t.false(
|
||||
dbConfig.matchesDefaultSetupConfigSchema(
|
||||
parseUnvalidatedConfig(contents),
|
||||
),
|
||||
contents,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("matchesDefaultSetupConfigSchema - returns false for configurations that aren't mappings", (t) => {
|
||||
for (const contents of ["- threat-models", "local", "null"]) {
|
||||
t.false(
|
||||
dbConfig.matchesDefaultSetupConfigSchema(
|
||||
parseUnvalidatedConfig(contents),
|
||||
),
|
||||
contents,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,6 +33,15 @@ export interface QuerySpec {
|
||||
uses: string;
|
||||
}
|
||||
|
||||
// A set of default query suite names that are understood by the CLI.
|
||||
export const defaultSuites: Set<string> = new Set([
|
||||
"security-experimental",
|
||||
"security-extended",
|
||||
"security-and-quality",
|
||||
"code-quality",
|
||||
"code-scanning",
|
||||
]);
|
||||
|
||||
const ORG_SCHEMA = {
|
||||
/** An array of model pack names. */
|
||||
"model-packs": json.optional(json.array(json.string)),
|
||||
@@ -77,7 +86,11 @@ export interface UserConfig {
|
||||
"default-setup"?: DefaultSetupConfig;
|
||||
}
|
||||
|
||||
/** A subset of the `UserConfig` schema that is used by Default Setup. */
|
||||
/**
|
||||
* A subset of the `UserConfig` schema that is known to be used by Default Setup. None of these
|
||||
* properties may add queries, since a per-language CodeQL bundle can be used with a `config` input
|
||||
* that only sets them.
|
||||
*/
|
||||
const DEFAULT_SETUP_CONFIG_SCHEMA = {
|
||||
"threat-models": json.optional(json.array(json.string)),
|
||||
"default-setup": json.optional<DefaultSetupConfig>(
|
||||
@@ -85,6 +98,24 @@ const DEFAULT_SETUP_CONFIG_SCHEMA = {
|
||||
),
|
||||
} as const satisfies json.Schema;
|
||||
|
||||
/**
|
||||
* Returns whether `config` matches what we expect Default Setup to send in the `config` input: a
|
||||
* mapping that only sets properties in `DEFAULT_SETUP_CONFIG_SCHEMA`, with values of the expected
|
||||
* types.
|
||||
*/
|
||||
export function matchesDefaultSetupConfigSchema(config: UserConfig): boolean {
|
||||
// Unless validation is enabled, `parseUserConfig` doesn't check that the YAML is a mapping.
|
||||
if (!json.isObject(config)) {
|
||||
return false;
|
||||
}
|
||||
const result = json.checkSchema(
|
||||
DEFAULT_SETUP_CONFIG_SCHEMA,
|
||||
config as json.UnvalidatedObject<any>,
|
||||
);
|
||||
// `valid` doesn't account for unknown properties.
|
||||
return result.valid && result.unknownKeys.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges supported properties from two configuration files. This is intended only for
|
||||
* use with merging the `config` input provided by Default Setup with a potentially
|
||||
@@ -437,63 +468,60 @@ export async function calculateAugmentation(
|
||||
languages,
|
||||
packsInputCombines,
|
||||
);
|
||||
const queriesInputCombines = shouldCombine(rawQueriesInput);
|
||||
const queriesInput = parseQueriesFromInput(
|
||||
rawQueriesInput,
|
||||
queriesInputCombines,
|
||||
const queries = parseQueriesFromInput(rawQueriesInput);
|
||||
const repoPropertyQueries = parseQueriesFromInput(
|
||||
repositoryProperties[RepositoryPropertyName.EXTRA_QUERIES],
|
||||
RepositoryPropertyName.EXTRA_QUERIES,
|
||||
);
|
||||
|
||||
const repoExtraQueries =
|
||||
repositoryProperties[RepositoryPropertyName.EXTRA_QUERIES];
|
||||
const repoExtraQueriesCombines = shouldCombine(repoExtraQueries);
|
||||
const repoPropertyQueries = {
|
||||
combines: repoExtraQueriesCombines,
|
||||
input: parseQueriesFromInput(
|
||||
repoExtraQueries,
|
||||
repoExtraQueriesCombines,
|
||||
new ConfigurationError(
|
||||
errorMessages.getRepoPropertyError(
|
||||
RepositoryPropertyName.EXTRA_QUERIES,
|
||||
errorMessages.getEmptyCombinesError(),
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
packsInputCombines,
|
||||
packsInput: packsInput?.[languages[0]],
|
||||
queriesInput,
|
||||
queriesInputCombines,
|
||||
queriesInput: queries.input,
|
||||
queriesInputCombines: queries.combines,
|
||||
repoPropertyQueries,
|
||||
};
|
||||
}
|
||||
|
||||
function parseQueriesFromInput(
|
||||
rawQueriesInput: string | undefined,
|
||||
queriesInputCombines: boolean,
|
||||
errorToThrow?: ConfigurationError,
|
||||
) {
|
||||
if (!rawQueriesInput) {
|
||||
return undefined;
|
||||
/**
|
||||
* Parses a comma-separated list of queries, which may have a '+' prefix. Entries aren't validated,
|
||||
* so an empty entry becomes `{ uses: "" }`.
|
||||
*
|
||||
* @param value The list of queries.
|
||||
* @param repositoryProperty The repository property that `value` comes from, if any. Error messages
|
||||
* refer to this property, or to the `queries` input if no property is given.
|
||||
* @returns An `Augmentation` containing the parsed queries and whether `value` has a '+' prefix.
|
||||
* The queries are `undefined` if `value` is unset or empty.
|
||||
* @throws A `ConfigurationError` if `value` is a '+' with no queries after it.
|
||||
*/
|
||||
export function parseQueriesFromInput(
|
||||
value: string | undefined,
|
||||
repositoryProperty?: RepositoryPropertyName,
|
||||
): Augmentation<QuerySpec[]> {
|
||||
if (!value) {
|
||||
return { combines: false, input: undefined };
|
||||
}
|
||||
|
||||
const trimmedInput = queriesInputCombines
|
||||
? rawQueriesInput.trim().slice(1).trim()
|
||||
: (rawQueriesInput?.trim() ?? "");
|
||||
if (queriesInputCombines && trimmedInput.length === 0) {
|
||||
if (errorToThrow) {
|
||||
throw errorToThrow;
|
||||
}
|
||||
const combines = shouldCombine(value);
|
||||
const trimmedInput = combines ? value.trim().slice(1).trim() : value.trim();
|
||||
if (combines && trimmedInput.length === 0) {
|
||||
throw new ConfigurationError(
|
||||
errorMessages.getConfigFilePropertyError(
|
||||
undefined,
|
||||
"queries",
|
||||
"A '+' was used in the 'queries' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs.",
|
||||
),
|
||||
repositoryProperty !== undefined
|
||||
? errorMessages.getRepoPropertyError(
|
||||
repositoryProperty,
|
||||
errorMessages.getEmptyCombinesError(),
|
||||
)
|
||||
: errorMessages.getConfigFilePropertyError(
|
||||
undefined,
|
||||
"queries",
|
||||
"A '+' was used in the 'queries' input to specify that you wished to add some packs to your CodeQL analysis. However, no packs were specified. Please either remove the '+' or specify some packs.",
|
||||
),
|
||||
);
|
||||
}
|
||||
return trimmedInput.split(",").map((query) => ({ uses: query.trim() }));
|
||||
return {
|
||||
combines,
|
||||
input: trimmedInput.split(",").map((query) => ({ uses: query.trim() })),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,11 +638,13 @@ export function generateCodeScanningConfig(
|
||||
* Attempts to parse `contents` into a `UserConfig` value.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param pathInput The path to the file where `contents` was obtained from, for use in error messages.
|
||||
* @param contents The string contents of a YAML file to try and parse as a `UserConfig`.
|
||||
* @param validateConfig Whether to validate the configuration file against the schema.
|
||||
* @returns The `UserConfig` corresponding to `contents`, if parsing was successful.
|
||||
* @throws A `ConfigurationError` if parsing failed.
|
||||
* @param pathInput Where `contents` came from, such as the path to a file, for use in error messages.
|
||||
* @param contents The YAML to try and parse as a `UserConfig`.
|
||||
* @param validateConfig Whether to validate the configuration against the schema.
|
||||
* @returns The `UserConfig` corresponding to `contents`, if parsing was successful. Unless
|
||||
* `validateConfig` is set, the result might not be a mapping.
|
||||
* @throws A `ConfigurationError` if `contents` isn't valid YAML or, when `validateConfig` is set,
|
||||
* isn't a valid configuration.
|
||||
*/
|
||||
export function parseUserConfig(
|
||||
logger: Logger,
|
||||
|
||||
Reference in New Issue
Block a user