Skip to content

Commit 72e4aae

Browse files
committed
Replace the analysis logic by reading json output from Reanalyze.
1 parent 6a62d21 commit 72e4aae

File tree

1 file changed

+46
-119
lines changed

1 file changed

+46
-119
lines changed

client/src/commands/code_analysis.ts

+46-119
Original file line numberDiff line numberDiff line change
@@ -19,122 +19,41 @@ export type DiagnosticsResultCodeActionsMap = Map<
1919
{ range: Range; codeAction: CodeAction }[]
2020
>;
2121

22-
let fileInfoRegex = /File "(.+)", line (\d+), characters ([\d-]+)/g;
23-
24-
let extractFileInfo = (
25-
fileInfo: string
26-
): {
27-
filePath: string;
28-
line: string;
29-
characters: string;
30-
} | null => {
31-
let m;
32-
33-
let filePath: string | null = null;
34-
let line: string | null = null;
35-
let characters: string | null = null;
36-
37-
while ((m = fileInfoRegex.exec(fileInfo)) !== null) {
38-
if (m.index === fileInfoRegex.lastIndex) {
39-
fileInfoRegex.lastIndex++;
22+
let resultsToDiagnostics = (
23+
results: [
24+
{
25+
name: string;
26+
kind: string;
27+
file: string;
28+
range: [number, number, number, number];
29+
message: string;
30+
annotate?: { line: number; character: number; text: string };
4031
}
41-
42-
m.forEach((match: string, groupIndex: number) => {
43-
switch (groupIndex) {
44-
case 1: {
45-
filePath = match;
46-
break;
47-
}
48-
case 2: {
49-
line = match;
50-
break;
51-
}
52-
case 3: {
53-
characters = match;
54-
break;
55-
}
56-
}
57-
});
58-
}
59-
60-
if (filePath != null && line != null && characters != null) {
61-
return {
62-
filePath,
63-
line,
64-
characters,
65-
};
66-
}
67-
68-
return null;
69-
};
70-
71-
let dceTextToDiagnostics = (
72-
dceText: string,
32+
],
7333
diagnosticsResultCodeActions: DiagnosticsResultCodeActionsMap
7434
): {
7535
diagnosticsMap: Map<string, Diagnostic[]>;
7636
} => {
7737
let diagnosticsMap: Map<string, Diagnostic[]> = new Map();
7838

79-
// Each section with a single issue found is seprated by two line breaks in
80-
// the reanalyze output. The section contains information about the issue
81-
// itself, what line/char and in what file it was found, as well as a
82-
// suggestion for what you can replace the line containing the issue with to
83-
// suppress the issue reported.
84-
//
85-
// Here's an example of how a section typically looks:
86-
//
87-
// Warning Dead Value
88-
// File "/Users/zth/git/rescript-intro/src/Machine.res", line 2, characters 0-205
89-
// +use is never used
90-
// <-- line 2
91-
// @dead("+use") let use = (initialState: 'a, handleEvent: ('a, 'b) => 'a) => {
92-
93-
dceText.split("\n\n").forEach((chunk) => {
94-
let lines = chunk.split("\n").filter((line) => line != "");
95-
let [
96-
_title,
97-
fileInfo,
98-
text,
99-
100-
// These, if they exist, will power code actions for inserting the "fixed"
101-
// line that reanalyze might suggest.
102-
lineNumToReplace,
103-
lineContentToReplace,
104-
] = lines;
105-
106-
let processedFileInfo = extractFileInfo(fileInfo);
107-
108-
if (processedFileInfo != null) {
109-
let [startCharacter, endCharacter] =
110-
processedFileInfo.characters.split("-");
111-
112-
let parsedLine = parseInt(processedFileInfo.line, 10);
113-
114-
let startPos = new Position(
115-
// reanalyze reports lines as index 1 based, while VSCode wants them
116-
// index 0 based. reanalyze reports diagnostics for an entire file on
117-
// line 0 (and chars 0-0). So, we need to ensure that we don't give
118-
// VSCode a negative line index, or it'll be sad.
119-
Math.max(0, parsedLine - 1),
120-
Math.max(0, parseInt(startCharacter, 10))
121-
);
122-
123-
let endPos = new Position(
124-
Math.max(0, parsedLine - 1),
125-
Math.max(0, parseInt(endCharacter, 10))
126-
);
39+
results.forEach((item) => {
40+
{
41+
let startPos: Position, endPos: Position;
42+
let [startLine, startCharacter, endLine, endCharacter] = item.range;
12743

12844
// Detect if this diagnostic is for the entire file. If so, reanalyze will
129-
// say that the issue is on line 0 and chars 0-0. This code below ensures
45+
// say that the issue is on line -1. This code below ensures
13046
// that the full file is highlighted, if that's the case.
131-
if (parsedLine === 0 && processedFileInfo.characters === "0-0") {
47+
if (startLine < 0 || endLine < 0) {
13248
startPos = new Position(0, 0);
13349
endPos = new Position(99999, 0);
50+
} else {
51+
startPos = new Position(startLine, startCharacter);
52+
endPos = new Position(endLine, endCharacter);
13453
}
13554

13655
let issueLocationRange = new Range(startPos, endPos);
137-
let diagnosticText = text.trim();
56+
let diagnosticText = item.message.trim();
13857

13958
let diagnostic = new Diagnostic(
14059
issueLocationRange,
@@ -146,52 +65,53 @@ let dceTextToDiagnostics = (
14665
// optional arguments. This will ensure that everything but reduntant
14766
// optional arguments is highlighted as unecessary/unused code in the
14867
// editor.
149-
if (!diagnosticText.toLowerCase().startsWith("optional argument")) {
68+
if (!item.message.toLowerCase().startsWith("optional argument")) {
15069
diagnostic.tags = [DiagnosticTag.Unnecessary];
15170
}
15271

153-
if (diagnosticsMap.has(processedFileInfo.filePath)) {
154-
diagnosticsMap.get(processedFileInfo.filePath).push(diagnostic);
72+
if (diagnosticsMap.has(item.file)) {
73+
diagnosticsMap.get(item.file).push(diagnostic);
15574
} else {
156-
diagnosticsMap.set(processedFileInfo.filePath, [diagnostic]);
75+
diagnosticsMap.set(item.file, [diagnostic]);
15776
}
15877

15978
// If reanalyze suggests a fix, we'll set that up as a refactor code
16079
// action in VSCode. This way, it'll be easy to suppress the issue
16180
// reported if wanted. We also save the range of the issue, so we can
16281
// leverage that to make looking up the code actions for each cursor
16382
// position very cheap.
164-
if (lineNumToReplace != null && lineContentToReplace != null) {
165-
let [_, actualLineToReplaceStr] = lineNumToReplace.split("<-- line ");
166-
167-
if (actualLineToReplaceStr != null) {
83+
if (item.annotate != null) {
84+
{
16885
let codeAction = new CodeAction(`Suppress dead code warning`);
16986
codeAction.kind = CodeActionKind.RefactorRewrite;
17087

17188
let codeActionEdit = new WorkspaceEdit();
17289

90+
let { line, character, text } = item.annotate;
91+
17392
// In the future, it would be cool to have an additional code action
17493
// here for automatically removing whatever the thing that's dead is.
17594
codeActionEdit.replace(
176-
Uri.parse(processedFileInfo.filePath),
95+
Uri.parse(item.file),
17796
// Make sure the full line is replaced
97+
17898
new Range(
179-
new Position(issueLocationRange.start.line, 0),
180-
new Position(issueLocationRange.start.line, 999999)
99+
new Position(line, character),
100+
new Position(line, character)
181101
),
182102
// reanalyze seems to add two extra spaces at the start of the line
183103
// content to replace.
184-
lineContentToReplace.slice(2)
104+
text
185105
);
186106

187107
codeAction.edit = codeActionEdit;
188108

189-
if (diagnosticsResultCodeActions.has(processedFileInfo.filePath)) {
109+
if (diagnosticsResultCodeActions.has(item.file)) {
190110
diagnosticsResultCodeActions
191-
.get(processedFileInfo.filePath)
111+
.get(item.file)
192112
.push({ range: issueLocationRange, codeAction });
193113
} else {
194-
diagnosticsResultCodeActions.set(processedFileInfo.filePath, [
114+
diagnosticsResultCodeActions.set(item.file, [
195115
{ range: issueLocationRange, codeAction },
196116
]);
197117
}
@@ -213,7 +133,7 @@ export const runCodeAnalysisWithReanalyze = (
213133
let currentDocument = window.activeTextEditor.document;
214134
let cwd = targetDir ?? path.dirname(currentDocument.uri.fsPath);
215135

216-
let p = cp.spawn("npx", ["reanalyze@2.21.1"], {
136+
let p = cp.spawn("npx", ["reanalyze@2.22.0", "-json"], {
217137
cwd,
218138
});
219139

@@ -246,8 +166,15 @@ export const runCodeAnalysisWithReanalyze = (
246166

247167
p.on("close", () => {
248168
diagnosticsResultCodeActions.clear();
249-
let { diagnosticsMap } = dceTextToDiagnostics(
250-
data,
169+
try {
170+
var json = JSON.parse(data);
171+
} catch (e) {
172+
window.showErrorMessage(
173+
`Something went wrong parsing the json output of reanalyze: '${e}'`
174+
);
175+
}
176+
let { diagnosticsMap } = resultsToDiagnostics(
177+
json,
251178
diagnosticsResultCodeActions
252179
);
253180

0 commit comments

Comments
 (0)