Skip to content

Commit e520207

Browse files
committed
Add output parser for include-what-you-use
1 parent 732a716 commit e520207

File tree

6 files changed

+312
-4
lines changed

6 files changed

+312
-4
lines changed

docs/cmake-settings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Options that support substitution, in the table below, allow variable references
4848
| `cmake.deleteBuildDirOnCleanConfigure` | If `true`, delete build directory during clean configure. | `false` | no |
4949
| `cmake.emscriptenSearchDirs` | List of paths to search for Emscripten. | `[]` | no |
5050
| `cmake.enableAutomaticKitScan` | Enable automatic kit scanning. | `true` | no |
51-
| `cmake.enabledOutputParsers` | List of enabled output parsers. | `["cmake", "gcc", "gnuld", "msvc", "ghs", "diab"]` | no |
51+
| `cmake.enabledOutputParsers` | List of enabled output parsers. | `["cmake", "gcc", "gnuld", "msvc", "ghs", "diab", "iwyu"]` | no |
5252
| `cmake.enableLanguageServices` | If `true`, enable CMake language services. | `true` | no |
5353
| `cmake.enableTraceLogging` | If `true`, enable trace logging. | `false` | no |
5454
| `cmake.environment` | An object containing `key:value` pairs of environment variables, which will be available when configuring, building, or testing with CTest. | `{}` (no environment variables) | yes |

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2295,7 +2295,8 @@
22952295
"msvc",
22962296
"ghs",
22972297
"diab",
2298-
"iar"
2298+
"iar",
2299+
"iwyu"
22992300
]
23002301
},
23012302
"default": [

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
"cmake-tools.configuration.cmake.ctest.debugLaunchTarget.description": "Target name from launch.json to start when debugging a test with CTest. By default and in case of a non-existing target, this will show a picker with all available targets.",
139139
"cmake-tools.configuration.cmake.parseBuildDiagnostics.description": "Parse compiler output for warnings and errors.",
140140
"cmake-tools.configuration.cmake.enabledOutputParsers.description": {
141-
"message": "Output parsers to use. Supported parsers `cmake`, `gcc`, `gnuld` for GNULD-style linker output, `msvc` for Microsoft Visual C++, `ghs` for the Green Hills compiler with --no_wrap_diagnostics --brief_diagnostics, and `diab` for the Wind River Diab compiler.",
141+
"message": "Output parsers to use. Supported parsers `cmake`, `gcc`, `gnuld` for GNULD-style linker output, `msvc` for Microsoft Visual C++, `ghs` for the Green Hills compiler with --no_wrap_diagnostics --brief_diagnostics, `diab` for the Wind River Diab compiler, and `iwyu` for include-what-you-use diagnostics.",
142142
"comment": [
143143
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
144144
]

src/diagnostics/build.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as diab from '@cmt/diagnostics/diab';
1414
import * as gnu_ld from '@cmt/diagnostics/gnu-ld';
1515
import * as mvsc from '@cmt/diagnostics/msvc';
1616
import * as iar from '@cmt/diagnostics/iar';
17+
import * as iwyu from '@cmt/diagnostics/iwyu';
1718
import { FileDiagnostic, RawDiagnosticParser } from '@cmt/diagnostics/util';
1819
import { ConfigurationReader } from '@cmt/config';
1920

@@ -26,6 +27,7 @@ export class Compilers {
2627
diab = new diab.Parser();
2728
msvc = new mvsc.Parser();
2829
iar = new iar.Parser();
30+
iwyu = new iwyu.Parser();
2931
}
3032

3133
export class CompileOutputConsumer implements OutputConsumer {
@@ -83,7 +85,8 @@ export class CompileOutputConsumer implements OutputConsumer {
8385
GHS: this.compilers.ghs.diagnostics,
8486
DIAB: this.compilers.diab.diagnostics,
8587
GNULD: this.compilers.gnuld.diagnostics,
86-
IAR: this.compilers.iar.diagnostics
88+
IAR: this.compilers.iar.diagnostics,
89+
IWYU: this.compilers.iwyu.diagnostics
8790
};
8891
const parsers = util.objectPairs(by_source)
8992
.filter(([source, _]) => this.config.enableOutputParsers?.includes(source.toLowerCase()) ?? false);

src/diagnostics/iwyu.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Module for handling include-what-you-use diagnostics
3+
*/ /** */
4+
5+
import * as vscode from 'vscode';
6+
7+
import { RawDiagnosticParser, RawDiagnostic, FeedLineResult, oneLess } from '@cmt/diagnostics/util';
8+
9+
const HEADER_RE = /^(.*)\s+((should\s+(add|remove))\s+these\slines:)/i;
10+
const FULL_HEADER_RE = /^\s*(the\s+full\s+include[-\s]+list)\s+for\s+(.*):\s*$/i;
11+
const REMOVE_RE = /^\s*-\s*(.*?)\s*\/\/\s*lines\s(\d+)-(\d+)/i;
12+
const SUGGESTION_RE = /^\s*(.*?[^\s].*?)\s*$/;
13+
const END_RE = /^\s*-+\s*$/;
14+
15+
type State = 'wait-header' | 'collect' | 'collect-removes';
16+
17+
export class Parser extends RawDiagnosticParser {
18+
private state: State = 'wait-header';
19+
private file = '';
20+
private messagePrefix = '';
21+
private suggestedLines: string[] = [];
22+
private full: string[] = [];
23+
private severity: string = '';
24+
25+
protected doHandleLine(line: string): RawDiagnostic | FeedLineResult {
26+
let mat: RegExpExecArray | null;
27+
let change: string;
28+
let addPrefix: string;
29+
let removePrefix: string;
30+
31+
switch (this.state) {
32+
case 'wait-header':
33+
this.full = [line];
34+
this.suggestedLines = [];
35+
mat = HEADER_RE.exec(line);
36+
if (mat) {
37+
[, this.file, addPrefix, removePrefix, change] = mat;
38+
this.severity = 'warning';
39+
if (change === 'add') {
40+
this.messagePrefix = addPrefix;
41+
this.state = 'collect';
42+
} else {
43+
this.messagePrefix = removePrefix;
44+
this.state = 'collect-removes';
45+
}
46+
return FeedLineResult.Ok;
47+
}
48+
mat = FULL_HEADER_RE.exec(line);
49+
if (mat) {
50+
[, this.messagePrefix, this.file] = mat;
51+
this.severity = 'note';
52+
this.messagePrefix += ':';
53+
this.state = 'collect';
54+
return FeedLineResult.Ok;
55+
}
56+
return FeedLineResult.NotMine;
57+
58+
case 'collect-removes':
59+
mat = REMOVE_RE.exec(line);
60+
if (mat) {
61+
const [, msg, start, end] = mat;
62+
return this.makeDiagnostic(
63+
this.messagePrefix + ': ' + msg,
64+
new vscode.Range(oneLess(start), 0, oneLess(end), 999),
65+
join(this.full, line)
66+
);
67+
} else {
68+
this.state = 'wait-header';
69+
return FeedLineResult.Ok;
70+
}
71+
72+
case 'collect':
73+
mat = SUGGESTION_RE.exec(line);
74+
if (mat && !END_RE.exec(line)) {
75+
const [, msg] = mat;
76+
this.full.push(line);
77+
this.suggestedLines.push(msg);
78+
return FeedLineResult.Ok;
79+
} else {
80+
this.state = 'wait-header';
81+
if (this.suggestedLines.length) {
82+
return this.makeDiagnostic(
83+
join(this.messagePrefix, this.suggestedLines),
84+
new vscode.Range(0, 0, 0, 999),
85+
join(this.full)
86+
);
87+
} else {
88+
return FeedLineResult.Ok;
89+
}
90+
}
91+
}
92+
}
93+
94+
private makeDiagnostic(
95+
message: string, location: vscode.Range, full: string
96+
): RawDiagnostic {
97+
return {
98+
message: message,
99+
location: location,
100+
full: full,
101+
file: this.file,
102+
related: [],
103+
severity: this.severity
104+
};
105+
}
106+
}
107+
108+
/** join a grab bag of strings and string[]s with \n */
109+
function join(...lines: (string|string[])[]): string {
110+
return lines.map(
111+
(v) => typeof(v) === 'string' ? v : v.join('\n')
112+
).join('\n');
113+
}

test/unit-tests/diagnostics.test.ts

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,4 +784,195 @@ suite('Diagnostics', () => {
784784
diagnostic = resolved[0];
785785
expect(diagnostic.filepath).to.eq(resolvePath('main.cpp', project_dir));
786786
});
787+
788+
test('Parse IWYU', () => {
789+
const lines = [
790+
'/home/user/src/project/main.c should add these lines:',
791+
'#include <stdbool.h> // for bool',
792+
'#include <stdint.h> // for uint32_t, uint8_t',
793+
'',
794+
'/home/user/src/project/main.c should remove these lines:',
795+
'- #include <alloca.h> // lines 24-24',
796+
'- #include <stdalign.h> // lines 25-26',
797+
'',
798+
'The full include-list for /home/user/src/project/main.c:',
799+
'#include <stdbool.h> // for bool',
800+
'#include <stdint.h> // for uint32_t, uint8_t',
801+
'#include <stdio.h> // for fprintf, FILE, printf, NULL, stdout',
802+
'#include "array.h" // for ARRAY_SIZE',
803+
'---'
804+
];
805+
806+
feedLines(build_consumer, [], lines);
807+
expect(build_consumer.compilers.iwyu.diagnostics).to.have.length(4);
808+
const [add, rem1, rem2, all] = build_consumer.compilers.iwyu.diagnostics;
809+
810+
expect(add.file).to.eq('/home/user/src/project/main.c');
811+
expect(add.location.start.line).to.eq(0);
812+
expect(add.location.start.character).to.eq(0);
813+
expect(add.location.end.line).to.eq(0);
814+
expect(add.location.end.character).to.eq(999);
815+
expect(add.code).to.eq(undefined);
816+
expect(add.message).to.eq('should add these lines:\n#include <stdbool.h> // for bool\n#include <stdint.h> // for uint32_t, uint8_t');
817+
expect(add.severity).to.eq('warning');
818+
819+
expect(rem1.file).to.eq('/home/user/src/project/main.c');
820+
expect(rem1.location.start.line).to.eq(23);
821+
expect(rem1.location.start.character).to.eq(0);
822+
expect(rem1.location.end.line).to.eq(23);
823+
expect(rem1.location.end.character).to.eq(999);
824+
expect(rem1.code).to.eq(undefined);
825+
expect(rem1.message).to.eq('should remove: #include <alloca.h>');
826+
expect(rem1.severity).to.eq('warning');
827+
828+
expect(rem2.file).to.eq('/home/user/src/project/main.c');
829+
expect(rem2.location.start.line).to.eq(24);
830+
expect(rem2.location.start.character).to.eq(0);
831+
expect(rem2.location.end.line).to.eq(25);
832+
expect(rem2.location.end.character).to.eq(999);
833+
expect(rem2.code).to.eq(undefined);
834+
expect(rem2.message).to.eq('should remove: #include <stdalign.h>');
835+
expect(rem2.severity).to.eq('warning');
836+
837+
expect(all.file).to.eq('/home/user/src/project/main.c');
838+
expect(all.location.start.line).to.eq(0);
839+
expect(all.location.start.character).to.eq(0);
840+
expect(all.location.end.line).to.eq(0);
841+
expect(all.location.end.character).to.eq(999);
842+
expect(all.code).to.eq(undefined);
843+
expect(all.message).to.eq('The full include-list:\n#include <stdbool.h> // for bool\n#include <stdint.h> // for uint32_t, uint8_t\n#include <stdio.h> // for fprintf, FILE, printf, NULL, stdout\n#include "array.h" // for ARRAY_SIZE');
844+
expect(all.severity).to.eq('note');
845+
});
846+
847+
test('Parse IWYU with only additions', () => {
848+
const lines = [
849+
'/home/user/src/project/main.c should add these lines:',
850+
'#include <stdbool.h> // for bool',
851+
'',
852+
'/home/user/src/project/main.c should remove these lines:',
853+
'',
854+
'The full include-list for /home/user/src/project/main.c:',
855+
'#include <stdbool.h> // for bool',
856+
'#include "array.h" // for ARRAY_SIZE',
857+
'---'
858+
];
859+
860+
feedLines(build_consumer, [], lines);
861+
expect(build_consumer.compilers.iwyu.diagnostics).to.have.length(2);
862+
const [add, all] = build_consumer.compilers.iwyu.diagnostics;
863+
864+
expect(add.file).to.eq('/home/user/src/project/main.c');
865+
expect(add.location.start.line).to.eq(0);
866+
expect(add.location.start.character).to.eq(0);
867+
expect(add.location.end.line).to.eq(0);
868+
expect(add.location.end.character).to.eq(999);
869+
expect(add.code).to.eq(undefined);
870+
expect(add.message).to.eq('should add these lines:\n#include <stdbool.h> // for bool');
871+
expect(add.severity).to.eq('warning');
872+
873+
expect(all.file).to.eq('/home/user/src/project/main.c');
874+
expect(all.location.start.line).to.eq(0);
875+
expect(all.location.start.character).to.eq(0);
876+
expect(all.location.end.line).to.eq(0);
877+
expect(all.location.end.character).to.eq(999);
878+
expect(all.code).to.eq(undefined);
879+
expect(all.message).to.eq('The full include-list:\n#include <stdbool.h> // for bool\n#include "array.h" // for ARRAY_SIZE');
880+
expect(all.severity).to.eq('note');
881+
});
882+
883+
test('Parse IWYU with only removals', () => {
884+
const lines = [
885+
'/home/user/src/project/main.c should add these lines:',
886+
'',
887+
'/home/user/src/project/main.c should remove these lines:',
888+
'- #include <alloca.h> // lines 24-24',
889+
'',
890+
'The full include-list for /home/user/src/project/main.c:',
891+
'#include "array.h" // for ARRAY_SIZE',
892+
'---'
893+
];
894+
895+
feedLines(build_consumer, [], lines);
896+
expect(build_consumer.compilers.iwyu.diagnostics).to.have.length(2);
897+
const [rem, all] = build_consumer.compilers.iwyu.diagnostics;
898+
899+
expect(rem.file).to.eq('/home/user/src/project/main.c');
900+
expect(rem.location.start.line).to.eq(23);
901+
expect(rem.location.start.character).to.eq(0);
902+
expect(rem.location.end.line).to.eq(23);
903+
expect(rem.location.end.character).to.eq(999);
904+
expect(rem.code).to.eq(undefined);
905+
expect(rem.message).to.eq('should remove: #include <alloca.h>');
906+
expect(rem.severity).to.eq('warning');
907+
908+
expect(all.file).to.eq('/home/user/src/project/main.c');
909+
expect(all.location.start.line).to.eq(0);
910+
expect(all.location.start.character).to.eq(0);
911+
expect(all.location.end.line).to.eq(0);
912+
expect(all.location.end.character).to.eq(999);
913+
expect(all.code).to.eq(undefined);
914+
expect(all.message).to.eq('The full include-list:\n#include "array.h" // for ARRAY_SIZE');
915+
expect(all.severity).to.eq('note');
916+
});
917+
918+
test('Parse IWYU with multiple files', () => {
919+
const lines = [
920+
'/home/user/src/project/main.c should add these lines:',
921+
'#include <stdbool.h> // for bool',
922+
'',
923+
'/home/user/src/project/main.c should remove these lines:',
924+
'',
925+
'The full include-list for /home/user/src/project/main.c:',
926+
'#include <stdbool.h> // for bool',
927+
'---',
928+
'/home/user/src/project/module.c should add these lines:',
929+
'',
930+
'/home/user/src/project/module.c should remove these lines:',
931+
'- #include <alloca.h> // lines 24-24',
932+
'',
933+
'The full include-list for /home/user/src/project/module.c:',
934+
'#include "array.h" // for ARRAY_SIZE',
935+
'---'
936+
];
937+
938+
feedLines(build_consumer, [], lines);
939+
expect(build_consumer.compilers.iwyu.diagnostics).to.have.length(4);
940+
const [add, all1, rem, all2] = build_consumer.compilers.iwyu.diagnostics;
941+
942+
expect(add.file).to.eq('/home/user/src/project/main.c');
943+
expect(add.location.start.line).to.eq(0);
944+
expect(add.location.start.character).to.eq(0);
945+
expect(add.location.end.line).to.eq(0);
946+
expect(add.location.end.character).to.eq(999);
947+
expect(add.code).to.eq(undefined);
948+
expect(add.message).to.eq('should add these lines:\n#include <stdbool.h> // for bool');
949+
expect(add.severity).to.eq('warning');
950+
951+
expect(all1.file).to.eq('/home/user/src/project/main.c');
952+
expect(all1.location.start.line).to.eq(0);
953+
expect(all1.location.start.character).to.eq(0);
954+
expect(all1.location.end.line).to.eq(0);
955+
expect(all1.location.end.character).to.eq(999);
956+
expect(all1.code).to.eq(undefined);
957+
expect(all1.message).to.eq('The full include-list:\n#include <stdbool.h> // for bool');
958+
expect(all1.severity).to.eq('note');
959+
960+
expect(rem.file).to.eq('/home/user/src/project/module.c');
961+
expect(rem.location.start.line).to.eq(23);
962+
expect(rem.location.start.character).to.eq(0);
963+
expect(rem.location.end.line).to.eq(23);
964+
expect(rem.location.end.character).to.eq(999);
965+
expect(rem.code).to.eq(undefined);
966+
expect(rem.message).to.eq('should remove: #include <alloca.h>');
967+
expect(rem.severity).to.eq('warning');
968+
969+
expect(all2.file).to.eq('/home/user/src/project/module.c');
970+
expect(all2.location.start.line).to.eq(0);
971+
expect(all2.location.start.character).to.eq(0);
972+
expect(all2.location.end.line).to.eq(0);
973+
expect(all2.location.end.character).to.eq(999);
974+
expect(all2.code).to.eq(undefined);
975+
expect(all2.message).to.eq('The full include-list:\n#include "array.h" // for ARRAY_SIZE');
976+
expect(all2.severity).to.eq('note');
977+
});
787978
});

0 commit comments

Comments
 (0)