microsoft/add-diagnostic-parser
> Use when adding a compiler or tool output parser for the Problems panel. Touches src/diagnostics/<name>.ts, src/diagnostics/build.ts, package.json "new diagnostic parser", "parse compiler output".
npx skills add https://github.com/microsoft/vscode-cmake-tools --skill add-diagnostic-parser
Recipe for adding a new compiler or tool output parser to CMake Tools. A parser extracts diagnostics (errors, warnings, notes) from build output and surfaces them in the VS Code Problems panel.
Adding a parser touches 4 files (plus tests and changelog):
| File | What to do |
|---|---|
| src/diagnostics/<name>.ts | Create the parser class |
| src/diagnostics/build.ts | Register the parser in Compilers |
| package.json | Add to cmake.enabledOutputParsers enum |
| package.nls.json | Update description if user-visible text changes |
Create src/diagnostics/<name>.ts. Every parser extends RawDiagnosticParser (defined in src/diagnostics/util.ts) and exports a class named Parser.
// src/diagnostics/util.ts (simplified)
export abstract class RawDiagnosticParser {
get diagnostics(): readonly RawDiagnostic[] { /* accumulated results */ }
/** Called by the build consumer for every line of output. */
handleLine(line: string): boolean {
const result = this.doHandleLine(line);
if (result === FeedLineResult.Ok) return true; // line consumed, no new diagnostic
if (result === FeedLineResult.NotMine) return false; // line not recognized
// Otherwise result is a RawDiagnostic — it gets stored automatically
this._diagnostics.push(result);
return true;
}
/** Implement this. Return a RawDiagnostic to emit, or a FeedLineResult. */
protected abstract doHandleLine(line: string): RawDiagnostic | FeedLineResult;
}
/**
* Module for parsing <ToolName> diagnostics
*/ /** */
import * as vscode from 'vscode';
import { oneLess, RawDiagnosticParser, FeedLineResult } from '@cmt/diagnostics/util';
// Regex that captures: file, line, severity, code (optional), message
export const REGEX = /^(.+):(\d+):\s+(error|warning|info)\s+(\w+):\s+(.*)$/;
export class Parser extends RawDiagnosticParser {
doHandleLine(line: string) {
const mat = REGEX.exec(line);
if (!mat) {
return FeedLineResult.NotMine;
}
const [full, file, lineStr, severity, code, message] = mat;
const lineno = oneLess(lineStr); // convert 1-based to 0-based
return {
full,
file,
location: new vscode.Range(lineno, 0, lineno, 999),
severity,
message,
code,
related: []
};
}
}
For tools whose diagnostics span multiple lines, use internal state:
import { RawDiagnosticParser, RawDiagnostic, FeedLineResult, oneLess } from '@cmt/diagnostics/util';
enum ParserState { init, pending_message }
export class Parser extends RawDiagnosticParser {
private state = ParserState.init;
private pending: RawDiagnostic | null = null;
doHandleLine(line: string): RawDiagnostic | FeedLineResult {
switch (this.state) {
case ParserState.init: {
const mat = FIRST_LINE_REGEX.exec(line);
if (!mat) return FeedLineResult.NotMine;
this.pending = { /* ... build partial diagnostic ... */ };
this.state = ParserState.pending_message;
return FeedLineResult.Ok; // consumed, but not complete yet
}
case ParserState.pending_message: {
if (/* line completes the diagnostic */) {
const diag = this.pending!;
this.reset();
return diag; // emits the diagnostic
}
return FeedLineResult.Ok; // still accumulating
}
}
return FeedLineResult.NotMine;
}
}
// src/diagnostics/util.ts
interface RawDiagnostic {
full: string; // the full matched line(s)
file: string; // source file path
location: vscode.Range; // 0-based range (use oneLess() for 1-based input)
severity: string; // 'error' | 'warning' | 'note' | 'info' | 'fatal error' | 'remark'
message: string;
code?: string; // optional diagnostic code (e.g. 'C4996', 'LNK2019')
related: RawRelated[]; // related diagnostics (notes, template backtraces)
}
enum FeedLineResult {
Ok, // line was consumed (but no new diagnostic produced yet)
NotMine, // line not recognized by this parser
}
diagnosticSeverity() in util.ts maps the severity string to vscode.DiagnosticSeverity. Recognized values: 'error', 'fatal error', 'catastrophic error', 'warning', 'note', 'info', 'remark'.
src/diagnostics/build.tsImport the new parser module and add an instance to the Compilers class. The property name becomes the parser identifier used in cmake.enabledOutputParsers.
// At the top — add import
import * as myparser from '@cmt/diagnostics/myparser';
// Inside the Compilers class — add property
export class Compilers {
[compiler: string]: RawDiagnosticParser;
gcc = new gcc.Parser();
gnuld = new gnu_ld.Parser();
ghs = new ghs.Parser();
diab = new diab.Parser();
msvc = new mvsc.Parser();
iar = new iar.Parser();
iwyu = new iwyu.Parser();
myparser = new myparser.Parser(); // ← new parser
}
The CompileOutputConsumer.error() method iterates over all Compilers properties in declaration order, calling parser.handleLine(line). The first parser to return true claims the line — order matters if your parser's regex could match lines from another compiler.
The resolveDiagnostics() method filters diagnostics by config.enableOutputParsers — only parsers whose name appears in that array will have their diagnostics shown in the Problems panel.
package.jsonAdd the parser name to the cmake.enabledOutputParsers enum. Decide whether it should be default-enabled or opt-in.
// package.json → contributes.configuration → cmake.enabledOutputParsers
"cmake.enabledOutputParsers": {
"type": "array",
"items": {
"type": "string",
"enum": [
"cmake",
"gcc",
"gnuld",
"msvc",
"ghs",
"diab",
"iar",
"iwyu",
"myparser" // ← add to enum
]
},
"default": [
"cmake",
"gcc",
"gnuld",
"msvc",
"ghs",
"diab"
// only add here if default-enabled
]
}
package.nls.jsonIf any user-visible setting descriptions changed, update the localization key in package.nls.json. The cmake.enabledOutputParsers description uses the key cmake-tools.configuration.cmake.enabledOutputParsers.description.
Add tests in test/unit-tests/diagnostics.test.ts. The test pattern:
test('Parse <tool> error', () => {
const build_consumer = new diags.CompileOutputConsumer(
new ConfigurationReader({} as ExtensionConfigurationSettings)
);
// Feed real compiler output lines
build_consumer.error('<tool-specific output line>');
// Verify diagnostics
const parser = build_consumer.compilers.myparser;
expect(parser.diagnostics).to.have.length(1);
expect(parser.diagnostics[0].severity).to.eq('error');
expect(parser.diagnostics[0].file).to.eq('expected/file.cpp');
});
CHANGELOG.mdAdd an entry under the current version:
Features:
- Add `myparser` diagnostic parser for <ToolName> compiler output. [PR #XXXX](https://github.com/microsoft/vscode-cmake-tools/pull/XXXX)
| Property in Compilers | Module file | Default-enabled |
|---|---|---|
| gcc | src/diagnostics/gcc.ts | ✅ |
| gnuld | src/diagnostics/gnu-ld.ts | ✅ |
| ghs | src/diagnostics/ghs.ts | ✅ |
| diab | src/diagnostics/diab.ts | ✅ |
| msvc | src/diagnostics/msvc.ts | ✅ |
| iar | src/diagnostics/iar.ts | ❌ opt-in |
| iwyu | src/diagnostics/iwyu.ts | ❌ opt-in |
The cmake parser (in src/diagnostics/cmake.ts) is separate — it handles CMake's own configure/generate output via CMakeOutputConsumer, not build compiler output. It is listed in the enabledOutputParsers default array but is not part of the Compilers class.
cmake, gcc, gnuld, msvc, ghs, diab) are included in the "default" array in package.json. Users get them automatically.iar, iwyu) are in the "enum" but not in "default". Users must add them to their cmake.enabledOutputParsers setting.RawDiagnosticParser and exports as ParserdoHandleLine() returns FeedLineResult.NotMine for unrecognized linesdoHandleLine() returns a RawDiagnostic (not FeedLineResult.Ok) when a diagnostic is completeoneLess()Compilers class in src/diagnostics/build.tscmake.enabledOutputParsers enum in package.jsontest/unit-tests/diagnostics.test.ts passyarn compile succeedsCHANGELOG.md entry addedSee also: .github/copilot-instructions.md for project-wide conventions.
Take microsoft/add-diagnostic-parser from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.