TypeScript
Diagnostic production
Add a custom diagnostic producer.
Diagnostic structure
A ts.Diagnostic object carries file, start, length, category, code and messageText fields. When a rule violation is found in the plugin, you produce a diagnostic with these fields.
const diag: ts.Diagnostic = {
file: sf,
start: node.getStart(sf),
length: node.getWidth(sf),
category: ts.DiagnosticCategory.Warning,
code: 9001,
messageText: "Missing type annotation",
};
Producing diagnostics
To produce diagnostics inside the plugin you push ts.Diagnostic objects into an extras array. Then you merge them with ls's prior diagnostics and return.
const extras: ts.Diagnostic[] = [];
extras.push({
file: sf,
start: node.getStart(sf),
length: node.getWidth(sf),
category: ts.DiagnosticCategory.Warning,
code: 9001,
messageText: "Missing type annotation",
});
return [...prior, ...extras];
Exercise Add a custom diagnostic
Write a plugin that wraps getSemanticDiagnostics. First take ls's diagnostics (prior). Get the SourceFile via ls.getProgram().getSourceFile(fileName). Walk the AST: for each VariableDeclaration without a type annotation, produce a ts.Diagnostic (category: Warning). Merge prior and extras. Test: source let x = 1; let y: number = 2;; print the total diagnostic count.
Show example solution
import ts from "typescript";
function createPlugin(info: ts.server.PluginCreateInfo): ts.LanguageService {
const ls = info.languageService;
return {
...ls,
getSemanticDiagnostics: function(fileName: string) {
const prior = ls.getSemanticDiagnostics(fileName);
const sf = ls.getProgram().getSourceFile(fileName);
if (!sf) return prior;
const extras: ts.Diagnostic[] = [];
function visit(node: ts.Node) {
if (ts.isVariableDeclaration(node) && !node.type) {
extras.push({
file: sf,
start: node.getStart(sf),
length: node.getWidth(sf),
category: ts.DiagnosticCategory.Warning,
code: 9001,
messageText: "Missing type annotation",
});
}
ts.forEachChild(node, visit);
}
visit(sf);
return [...prior, ...extras];
},
};
}
const sf = ts.createSourceFile("app.ts", "let x = 1; let y: number = 2;", ts.ScriptTarget.Latest, true);
const fakeLs = {
getSemanticDiagnostics: function() { return [] as ts.Diagnostic[]; },
getProgram: function() { return { getSourceFile: function(n: string) { return n === "app.ts" ? sf : undefined; } } as unknown as ts.Program; },
} as unknown as ts.LanguageService;
const info = { languageService: fakeLs, config: {} } as unknown as ts.server.PluginCreateInfo;
const plugin = createPlugin(info);
const diags = plugin.getSemanticDiagnostics("app.ts");
console.log(diags.length);
Test yourself
-
What does ts.DiagnosticCategory.Warning mean?
- A compiler error
- A suggestion
- An informational message
- A warning, does not stop compilation
Show answer
A warning, does not stop compilation — The Warning category issues a warning but does not stop compilation; Error stops it, Suggestion is a proposal, and Message is informational.
-
Create a diagnostic
extras.push({
file: sf,
start: node.getStart(sf),
length: node.getWidth(sf),
category: ts.DiagnosticCategory.____,
code: 9001,
messageText: "Missing type annotation",
});
Show answer
extras.push({
file: sf,
start: node.getStart(sf),
length: node.getWidth(sf),
category: ts.DiagnosticCategory.Warning,
code: 9001,
messageText: "Missing type annotation",
});
-
Diagnostic parts
- file
- Carries the source file
- start
- Start offset
- category
- Severity
- messageText
- Message text
Next lesson: Code fix