Skip to content

Commit a35fd5e

Browse files
author
Carolyn Zech
committed
Use concrete types instead of any
1 parent 8a99568 commit a35fd5e

File tree

2 files changed

+92
-153
lines changed

2 files changed

+92
-153
lines changed

src/ui/coverage/config.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,12 @@ class CoverageConfig {
2929
isWholeLine: false,
3030
};
3131

32-
const partialDecoration: DecorationRenderOptions = {
33-
backgroundColor: 'rgba(255, 255, 0, 0.2)', // Yellow background
34-
isWholeLine: false,
35-
};
36-
3732
const noDecoration: DecorationRenderOptions = {
3833
backgroundColor: 'rgba(255, 0, 0, 0.2)', // Red background
3934
isWholeLine: false,
4035
};
4136

4237
this.covered = window.createTextEditorDecorationType(fullDecoration);
43-
this.partialcovered = window.createTextEditorDecorationType(partialDecoration);
4438
this.uncovered = window.createTextEditorDecorationType(noDecoration);
4539
}
4640
}

src/ui/coverage/coverageInfo.ts

Lines changed: 92 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
import { ExecFileException, execFile } from 'child_process';
34
import * as fs from 'fs';
45
import * as path from 'path';
56

@@ -9,13 +10,22 @@ import Config from './config';
910
import GlobalConfig from '../../globalConfig';
1011
import { fullToRelativePath, getRootDir } from '../../utils';
1112

12-
const { execFile } = require('child_process');
13+
enum CoverageStatus {
14+
COVERED = 'COVERED',
15+
UNCOVERED = 'UNCOVERED',
16+
}
17+
18+
// Coverage information for a given file.
19+
type FileCoverageMap = Map<vscode.Range, CoverageStatus>;
20+
21+
// Map files to their coverage information.
22+
type CoverageMap = Map<string, FileCoverageMap>;
1323

14-
// Interface for storing the coverage status for each line of a document.
15-
export interface CoverageLines {
16-
full: vscode.Range[];
17-
partial: vscode.Range[];
18-
none: vscode.Range[];
24+
// Result of running the coverage command.
25+
interface CoverageCommandResult {
26+
statusCode: number;
27+
coverage?: CoverageMap;
28+
error?: string;
1929
}
2030

2131
const warningMessage = `Source-based coverage is an unstable feature.`;
@@ -32,15 +42,17 @@ export async function runCodeCoverageAction(
3242

3343
// Run this command: cargo kani --coverage -Z source-coverage --harness verify_success
3444
// to generate the source coverage output
35-
const playbackCommand: string = `${kaniBinaryPath} --coverage -Z source-coverage --harness ${functionName}`;
36-
const processOutput = await runCoverageCommand(playbackCommand, functionName);
37-
if (processOutput.statusCode == 0) {
38-
const coverageGlobalMap = processOutput.result;
45+
const coverageCommand: string = `${kaniBinaryPath} --coverage -Z source-coverage --harness ${functionName}`;
46+
const processOutput = await runCoverageCommand(coverageCommand, functionName);
3947

48+
if (processOutput.statusCode === 0 && processOutput.coverage) {
4049
// Convert the array of (file, line, status) objects into Map<file <line, status>>
4150
// since we need to cache this globally
42-
globalConfig.setCoverage(coverageGlobalMap);
43-
renderer.renderInterface(vscode.window.visibleTextEditors, coverageGlobalMap);
51+
globalConfig.setCoverage(processOutput.coverage);
52+
renderer.renderInterface(vscode.window.visibleTextEditors, processOutput.coverage);
53+
} else if (processOutput.error) {
54+
renderer.clearAllHighlights(); // Clear highlights if there's an error
55+
vscode.window.showErrorMessage(`Coverage generation failed: ${processOutput.error}`);
4456
}
4557
}
4658

@@ -51,7 +63,10 @@ export async function runCodeCoverageAction(
5163
* @param harnessName - name of the harness
5264
* @returns - the result of executing the --coverage command and parsing the output
5365
*/
54-
async function runCoverageCommand(command: string, harnessName: string): Promise<any> {
66+
async function runCoverageCommand(
67+
command: string,
68+
harnessName: string,
69+
): Promise<CoverageCommandResult> {
5570
// Get the full resolved path for the root directory of the crate
5671
const directory = path.resolve(getRootDir());
5772
const commmandSplit = command.split(' ');
@@ -68,13 +83,27 @@ async function runCoverageCommand(command: string, harnessName: string): Promise
6883
const kaniBinaryPath = globalConfig.getFilePath();
6984

7085
vscode.window.showInformationMessage(`Generating coverage for ${harnessName}`);
71-
return new Promise((resolve, _reject) => {
72-
execFile(kaniBinaryPath, args, options, async (_error: any, stdout: any, _stderr: any) => {
73-
if (stdout) {
74-
const parseResult = await parseKaniCoverageOutput(stdout);
75-
resolve({ statusCode: 0, result: parseResult });
76-
}
77-
});
86+
return new Promise<CoverageCommandResult>((resolve) => {
87+
execFile(
88+
kaniBinaryPath,
89+
args,
90+
options,
91+
(error: ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => {
92+
if (error) {
93+
resolve({
94+
statusCode: typeof error.code === 'number' ? error.code : 1,
95+
error: error.message,
96+
});
97+
} else if (stdout) {
98+
parseKaniCoverageOutput(stdout.toString()).then(resolve);
99+
} else {
100+
resolve({
101+
statusCode: 1,
102+
error: stderr.toString() || 'No output from coverage command',
103+
});
104+
}
105+
},
106+
);
78107
});
79108
}
80109

@@ -107,10 +136,9 @@ function readJsonFromPath(filePath: string): any {
107136
* @returns - undefined (error) or a result that indicates if the extension is executed on a local
108137
* or remote environment. The result includes a `path` (if local) or a `command` (if remote).
109138
*/
110-
async function parseKaniCoverageOutput(stdout: string): Promise<any | undefined> {
111-
const kaniOutput: string = stdout;
112-
const jsonFilePath = getCoverageJsonPath(kaniOutput);
113-
const coverageMap: Map<string, Map<any, string>> = new Map();
139+
async function parseKaniCoverageOutput(stdout: string): Promise<CoverageCommandResult> {
140+
const jsonFilePath = getCoverageJsonPath(stdout);
141+
const coverageMap: CoverageMap = new Map();
114142

115143
if (jsonFilePath) {
116144
const files = fs.readdirSync(jsonFilePath);
@@ -123,20 +151,19 @@ async function parseKaniCoverageOutput(stdout: string): Promise<any | undefined>
123151
const filePath = path.join(jsonFilePath, kanirawJson);
124152
const jsonContent = readJsonFromPath(filePath);
125153

126-
// Take this jsonContent and create an Map<file <region, status>>
127-
if (jsonContent) {
128-
for (const file in jsonContent['data']) {
129-
const regions = jsonContent['data'][file];
130-
const regionMap: Map<any, string> = new Map();
154+
if (jsonContent && 'data' in jsonContent) {
155+
for (const file in jsonContent.data) {
156+
const regions = jsonContent.data[file];
157+
const regionMap: FileCoverageMap = new Map();
131158

132159
for (const region of regions) {
133160
const startLine = region.region.start[0];
134161
const startCol = region.region.start[1];
135162
const endLine = region.region.end[0];
136163
const endCol = region.region.end[1];
137164

138-
const status = region.status;
139-
// Create a VSCode range with start line and column
165+
const status =
166+
region.status === 'COVERED' ? CoverageStatus.COVERED : CoverageStatus.UNCOVERED;
140167
const start = new vscode.Position(startLine - 1, startCol - 1);
141168
const end = new vscode.Position(endLine - 1, endCol - 1);
142169
const range = new vscode.Range(start, end);
@@ -151,8 +178,11 @@ async function parseKaniCoverageOutput(stdout: string): Promise<any | undefined>
151178
}
152179
}
153180

154-
if (coverageMap === undefined || coverageMap.size == 0) {
155-
return '';
181+
if (coverageMap.size === 0) {
182+
return {
183+
statusCode: 1,
184+
error: 'No coverage data found',
185+
};
156186
}
157187

158188
// If the global setting for showing output is on, show the output
@@ -168,8 +198,10 @@ async function parseKaniCoverageOutput(stdout: string): Promise<any | undefined>
168198
terminal.show();
169199
}
170200

171-
// No command found from Kani
172-
return coverageMap;
201+
return {
202+
statusCode: 0,
203+
coverage: coverageMap,
204+
};
173205
}
174206

175207
// Class representing the Renderer that handles rendering coverage highlights in the editor.
@@ -180,136 +212,49 @@ export class CoverageRenderer {
180212
}
181213

182214
/**
183-
* Renders coverage highlights for multiple files.
184-
* @param editors - An array of text editor files to render coverage highlights for.
185-
* @param coverageMap - A map containing coverage data for each file.
215+
* Clears all existing coverage highlights.
186216
*/
187-
public renderInterface(
188-
editors: readonly vscode.TextEditor[],
189-
coverageMap: Map<string, Map<any, any>>,
190-
): void {
191-
editors.forEach((editor) => {
192-
// If coverageMap is empty, de-highlight the files
193-
if (coverageMap.size == 0) {
194-
const coverageLines: CoverageLines = {
195-
full: [],
196-
none: [],
197-
partial: [],
198-
};
199-
200-
this.renderHighlight(editor, coverageLines);
201-
return;
202-
}
203-
204-
// Fetch the coverage data for a file from the coverageMap.
205-
const relativePath = fullToRelativePath(editor.document.fileName);
206-
const fileMap = coverageMap.get(relativePath)!;
207-
208-
const coverageLines = this.convertMapToLines(fileMap);
209-
this.renderHighlight(editor, coverageLines);
210-
return;
217+
public clearAllHighlights(): void {
218+
vscode.window.visibleTextEditors.forEach((editor) => {
219+
this.renderHighlight(editor, new Map());
211220
});
212221
}
213222

214-
public convertMapToLines(coverageMap: Map<any, any>): CoverageLines {
215-
// Parse this into coverageLines i.e if the status of the range in coverageMap is COVERED, add it to full or else if it is UNCOVERED, add it to None
216-
const coverageLines: CoverageLines = {
217-
full: [],
218-
none: [],
219-
partial: [],
220-
};
221-
222-
for (const [range, status] of coverageMap) {
223-
if (status === 'COVERED') {
224-
coverageLines.full.push(range);
225-
} else if (status === 'UNCOVERED') {
226-
coverageLines.none.push(range);
227-
}
228-
}
229-
230-
return coverageLines;
223+
/**
224+
* Renders coverage highlights for multiple files.
225+
* @param editors - An array of text editor files to render coverage highlights for.
226+
* @param coverageMap - A map containing coverage data for each file.
227+
*/
228+
public renderInterface(editors: readonly vscode.TextEditor[], coverageMap: CoverageMap): void {
229+
this.clearAllHighlights();
230+
editors.forEach((editor) => this.renderInterfaceForFile(editor, coverageMap));
231231
}
232232

233233
/**
234234
* Renders coverage highlights for a single text editor file.
235235
* @param editor - The text editor to render coverage highlights for.
236236
* @param coverageMap - A map containing coverage data for each file.
237237
*/
238-
public renderInterfaceForFile(
239-
editor: vscode.TextEditor,
240-
coverageMap: Map<string, Map<any, string>>,
241-
): void {
242-
if (coverageMap.size == 0) {
243-
const coverageLines: CoverageLines = {
244-
full: [],
245-
none: [],
246-
partial: [],
247-
};
248-
249-
this.renderHighlight(editor, coverageLines);
250-
}
251-
238+
public renderInterfaceForFile(editor: vscode.TextEditor, coverageMap: CoverageMap): void {
252239
const relativePath = fullToRelativePath(editor.document.fileName);
253-
const fileMap = coverageMap.get(relativePath)!;
254-
if (fileMap === undefined) {
255-
return;
256-
}
257-
258-
const coverageLines = this.convertMapToLines(fileMap);
259-
this.renderHighlight(editor, coverageLines);
240+
const fileMap = coverageMap.get(relativePath);
241+
this.renderHighlight(editor, fileMap || new Map());
260242
}
261243

262244
/**
263-
* Creates coverage highlights for a given text document based on the coverageFileMap.
264-
* @param doc - The text document for which coverage highlights are to be created.
265-
* @param coverageFileMap - A map containing coverage status for each line number.
266-
* @returns An object containing coverage highlights categorized as 'full', 'partial', and 'none'.
245+
* Applies the coverage highlights to the given text editor.
246+
* @param editor - The text editor to apply the coverage highlights to.
247+
* @param fileCoverageMap - A map containing coverage highlights for the file.
267248
*/
268-
public createCoverage(
269-
doc: vscode.TextDocument,
270-
coverageFileMap: Map<number, string>,
271-
): CoverageLines {
272-
const coverageLines: CoverageLines = {
273-
full: [],
274-
none: [],
275-
partial: [],
276-
};
249+
public renderHighlight(editor: vscode.TextEditor, fileCoverageMap: FileCoverageMap): void {
250+
const coveredRanges: vscode.Range[] = [];
251+
const uncoveredRanges: vscode.Range[] = [];
277252

278-
for (let lineNum = 1; lineNum <= doc.lineCount; lineNum++) {
279-
const line = doc.lineAt(lineNum - 1);
280-
const status = coverageFileMap.get(lineNum);
281-
282-
if (status === undefined) {
283-
continue;
284-
}
285-
286-
const range = new vscode.Range(line.range.start, line.range.end);
287-
switch (status) {
288-
case 'FULL':
289-
coverageLines.full.push(range);
290-
break;
291-
case 'PARTIAL':
292-
coverageLines.partial.push(range);
293-
break;
294-
case 'NONE':
295-
coverageLines.none.push(range);
296-
break;
297-
default:
298-
break;
299-
}
253+
for (const [range, status] of fileCoverageMap) {
254+
(status === CoverageStatus.COVERED ? coveredRanges : uncoveredRanges).push(range);
300255
}
301256

302-
return coverageLines;
303-
}
304-
305-
/**
306-
* Applies the coverage highlights to the given text editor.
307-
* @param editor - The text editor to apply the coverage highlights to.
308-
* @param coverageLinesInfo - An object containing coverage highlights categorized as 'full', 'partial', and 'none'.
309-
*/
310-
public renderHighlight(editor: vscode.TextEditor, coverageLinesInfo: CoverageLines): void {
311-
editor.setDecorations(this.configStore.covered, coverageLinesInfo.full);
312-
editor.setDecorations(this.configStore.partialcovered, coverageLinesInfo.partial);
313-
editor.setDecorations(this.configStore.uncovered, coverageLinesInfo.none);
257+
editor.setDecorations(this.configStore.covered, coveredRanges);
258+
editor.setDecorations(this.configStore.uncovered, uncoveredRanges);
314259
}
315260
}

0 commit comments

Comments
 (0)