-
Notifications
You must be signed in to change notification settings - Fork 453
Expand file tree
/
Copy pathanalyses.ts
More file actions
158 lines (139 loc) · 5.47 KB
/
analyses.ts
File metadata and controls
158 lines (139 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import {
fixCodeQualityCategory,
getOptionalInput,
getRequiredInput,
} from "./actions-util";
import { Logger } from "./logging";
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)),
);
}
// Used to avoid re-parsing the input after we have done it once.
let cachedAnalysisKinds: AnalysisKind[] | undefined;
/**
* Initialises the analysis kinds for the analysis based on the `analysis-kinds` input.
* If the `analysis-kinds` input cannot be parsed, a `ConfigurationError` is thrown.
*
* @param _logger The logger to use.
* @param skipCache For testing, whether to ignore the cached values (default: false).
*
* @returns The array of enabled analysis kinds.
* @throws A `ConfigurationError` if the `analysis-kinds` input cannot be parsed.
*/
export async function getAnalysisKinds(
_logger: Logger,
skipCache: boolean = false,
): Promise<AnalysisKind[]> {
if (!skipCache && cachedAnalysisKinds !== undefined) {
return cachedAnalysisKinds;
}
cachedAnalysisKinds = await parseAnalysisKinds(
getRequiredInput("analysis-kinds"),
);
// Throw if there is an argument for `quality-queries`.
const qualityQueriesInput = getOptionalInput("quality-queries");
if (qualityQueriesInput !== undefined) {
throw new ConfigurationError(
"The `quality-queries` input is no longer supported. Use `analysis-kinds` instead.",
);
}
return cachedAnalysisKinds;
}
/** The queries to use for Code Quality analyses. */
export const codeQualityQueries: string[] = ["code-quality"];
// Enumerates API endpoints that accept SARIF files.
enum SARIF_UPLOAD_ENDPOINT {
CODE_SCANNING = "PUT /repos/:owner/:repo/code-scanning/analysis",
CODE_QUALITY = "PUT /repos/:owner/:repo/code-quality/analysis",
}
// Represents configurations for different analysis kinds.
export interface AnalysisConfig {
/** The analysis kind the configuration is for. */
kind: AnalysisKind;
/** A display friendly name for logs. */
name: string;
/** The API endpoint to upload SARIF files to. */
target: SARIF_UPLOAD_ENDPOINT;
/** The file extension for SARIF files generated by this kind of analysis. */
sarifExtension: string;
/** A predicate on filenames to decide whether a SARIF file
* belongs to this kind of analysis. */
sarifPredicate: (name: string) => boolean;
/** Analysis-specific adjustment of the category. */
fixCategory: (logger: Logger, category?: string) => string | undefined;
/** A prefix for environment variables used to track the uniqueness of SARIF uploads. */
sentinelPrefix: string;
}
// Represents the Code Scanning analysis configuration.
export const CodeScanning: AnalysisConfig = {
kind: AnalysisKind.CodeScanning,
name: "code scanning",
target: SARIF_UPLOAD_ENDPOINT.CODE_SCANNING,
sarifExtension: ".sarif",
sarifPredicate: (name) =>
name.endsWith(CodeScanning.sarifExtension) &&
!CodeQuality.sarifPredicate(name),
fixCategory: (_, category) => category,
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
};
// Represents the Code Quality analysis configuration.
export const CodeQuality: AnalysisConfig = {
kind: AnalysisKind.CodeQuality,
name: "code quality",
target: SARIF_UPLOAD_ENDPOINT.CODE_QUALITY,
sarifExtension: ".quality.sarif",
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
fixCategory: fixCodeQualityCategory,
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
};
/**
* Gets the `AnalysisConfig` corresponding to `kind`.
* @param kind The analysis kind to get the `AnalysisConfig` for.
* @returns The `AnalysisConfig` corresponding to `kind`.
*/
export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig {
// Using a switch statement here accomplishes two things:
// 1. The type checker believes us that we have a case for every `AnalysisKind`.
// 2. If we ever add another member to `AnalysisKind`, the type checker will alert us that we have to add a case.
switch (kind) {
case AnalysisKind.CodeScanning:
return CodeScanning;
case AnalysisKind.CodeQuality:
return CodeQuality;
}
}
// Since we have overlapping extensions (i.e. ".sarif" includes ".quality.sarif"),
// we want to scan a folder containing SARIF files in an order that finds the more
// specific extensions first. This constant defines an array in the order of analyis
// configurations with more specific extensions to less specific extensions.
export const SarifScanOrder = [CodeQuality, CodeScanning];