CodeBlanks Logo Icon CodeBlanks

Challenge 4: Mini plugin

Solve a task testing the full expert level.

Mini plugin

This final challenge tests your full expert level. Write a lint plugin skeleton, wrap getSemanticDiagnostics, and test it with a mock language service. Then add a custom diagnostic producer that flags any types. You will use ts.Diagnostic, ts.forEachChild and ts.isVariableDeclaration together.

Practice Plugin skeleton

Write a plugin function that takes ts.server.PluginCreateInfo and returns ts.LanguageService. Get ls from info.languageService, wrap getSemanticDiagnostics and return ls's diagnostics as-is. A mock language service returns 2 diagnostics. Call the plugin and print the diagnostic count.

Starter code

index.ts
import * as ts from "typescript";

// Bir plugin fonksiyonu yaz:
// - PluginCreateInfo alır, LanguageService döndürür
// - info'daki dil servisini al
// - Diagnostic metodunu sar ve ls'nin diagnostics'ini döndür
// Mock dil servisi 2 diagnostic döndürsün
// Plugin'i çağır ve diagnostic sayısını yazdır

// kodunu buraya yaz

Sample solution

index.ts
import ts from "typescript";

function createPlugin(info: ts.server.PluginCreateInfo): ts.LanguageService {
  const ls = info.languageService;
  return {
    ...ls,
    getSemanticDiagnostics: function(fileName: string) {
      return ls.getSemanticDiagnostics(fileName);
    },
  };
}

const fakeLs = {
  getSemanticDiagnostics: function(_fileName: string): ts.Diagnostic[] {
    return [
      { category: ts.DiagnosticCategory.Error, code: 1, messageText: "a", file: undefined, start: 0, length: 1 } as ts.Diagnostic,
      { category: ts.DiagnosticCategory.Warning, code: 2, messageText: "b", file: undefined, start: 0, length: 1 } as ts.Diagnostic,
    ];
  },
} 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);

Expected output

2

Practice Flag any types

Write a plugin that wraps getSemanticDiagnostics. Get the SourceFile via ls.getProgram().getSourceFile(fileName). Walk the AST: for each VariableDeclaration whose type annotation is any, produce a ts.Diagnostic (category: Warning, messageText: "Avoid any"). Merge prior and extras. Test: source let a: any = 1; let b: number = 2;; print the total diagnostic count.

Starter code

index.ts
import * as ts from "typescript";

// Bir plugin yaz: Diagnostic metodunu sar
// - SourceFile'ı al
// - AST'yı gez: any türü olan VariableDeclaration bul
// - Her biri için bir Diagnostic üret (Warning kategorisi)
// - prior ve extras'ı birleştir
// Test: kaynak "let a: any = 1; let b: number = 2;"
// Toplam diagnostic sayısını yazdır

// kodunu buraya yaz

Sample solution

index.ts
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 && node.type.kind === ts.SyntaxKind.AnyKeyword) {
          extras.push({
            file: sf,
            start: node.getStart(sf),
            length: node.getWidth(sf),
            category: ts.DiagnosticCategory.Warning,
            code: 9001,
            messageText: "Avoid any",
          });
        }
        ts.forEachChild(node, visit);
      }
      visit(sf);
      return [...prior, ...extras];
    },
  };
}

const sf = ts.createSourceFile("app.ts", "let a: any = 1; let b: 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);

Expected output

1

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Which method does a plugin wrap to produce diagnostics?

  • getCompilerDiagnostics
  • getSyntacticDiagnostics
  • getSemanticDiagnostics Correct answer
  • getSuggestionDiagnostics

Explanation: A lint plugin wraps getSemanticDiagnostics and adds custom diagnostics. getSyntacticDiagnostics, getSuggestionDiagnostics and getCompilerDiagnostics are not used in the plugin pattern.

2 Get the message text

Question
console.log((diags[0] as ts.Diagnostic).____);
Answer
console.log((diags[0] as ts.Diagnostic).messageText);

Explanation: ts.Diagnostic.messageText carries the message text; message, text and description are not properties on Diagnostic.

3 Plugin parts

Term Meaning
PluginCreateInfo Plugin input
info.languageService Existing service
ts.Diagnostic Error/warning object
ts.forEachChild Walks the AST

All lessons in Expert