Building a Simple Linting Tool
Create a custom diagnostic tool using the compiler API.
Building a Simple Linting Tool is a free TypeScript Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Linting Tool?
A linting tool analyzes source code to report style or correctness issues. Using the TypeScript compiler API lets you build type-aware rules that ESLint plugins cannot achieve alone.
// Goal: warn when console.log is called in TypeScript filesProject Setup
Create a Node.js script that loads a TypeScript program, traverses the AST, and reports diagnostics.
import ts from "typescript";
import path from "path";
const files = ["src/index.ts"];
const program = ts.createProgram(files, { strict: true });Defining a Rule
A rule is a function that receives an AST node and returns an optional diagnostic message if the rule is violated.
type Diagnostic = { file: string; line: number; message: string };
function noConsoleLog(node: ts.Node, sf: ts.SourceFile): Diagnostic | null {
if (!ts.isCallExpression(node)) return null;
const expr = node.expression.getText(sf);
if (expr !== "console.log") return null;
const { line } = sf.getLineAndCharacterOfPosition(node.pos);
return { file: sf.fileName, line: line + 1, message: "No console.log allowed" };
}Walking the AST
Write a recursive walk that applies all rules to every node in each source file.
function walk(
node: ts.Node,
sf: ts.SourceFile,
rules: ((n: ts.Node, sf: ts.SourceFile) => Diagnostic | null)[]
): Diagnostic[] {
const diags: Diagnostic[] = [];
for (const rule of rules) {
const d = rule(node, sf);
if (d) diags.push(d);
}
ts.forEachChild(node, child => diags.push(...walk(child, sf, rules)));
return diags;
}Running the Linter
Loop over all source files, apply the rules, and print diagnostics.
const rules = [noConsoleLog];
const allDiagnostics: Diagnostic[] = [];
for (const sf of program.getSourceFiles()) {
if (!sf.isDeclarationFile) {
allDiagnostics.push(...walk(sf, sf, rules));
}
}
allDiagnostics.forEach(d =>
console.log(`${d.file}:${d.line} — ${d.message}`)
);Adding a Type-Aware Rule
Use the type checker to build rules that depend on the TypeScript type of an expression.
const checker = program.getTypeChecker();
function noAnyReturn(node: ts.Node, sf: ts.SourceFile): Diagnostic | null {
if (!ts.isFunctionDeclaration(node)) return null;
const sig = checker.getSignatureFromDeclaration(node);
if (!sig) return null;
const ret = checker.getReturnTypeOfSignature(sig);
if (ret.flags & ts.TypeFlags.Any) {
const { line } = sf.getLineAndCharacterOfPosition(node.pos);
return { file: sf.fileName, line: line + 1, message: "Function returns any" };
}
return null;
}Exit Code for CI
Return a non-zero exit code when diagnostics are found so CI pipelines can fail the build.
if (allDiagnostics.length > 0) {
console.error(`${allDiagnostics.length} lint error(s)`);
process.exit(1);
}
process.exit(0);Rule: No Unused Variables
The compiler API exposes unused-variable diagnostics via ts.getPreEmitDiagnostics, augmenting your custom rules with built-in checks.
const preEmit = ts.getPreEmitDiagnostics(program);
preEmit.forEach(d => {
const msg = ts.flattenDiagnosticMessageText(d.messageText, "
");
console.log(msg);
});Rule: Require Return Types
A rule that warns when functions lack explicit return type annotations helps enforce documentation discipline.
function requireReturnType(node: ts.Node, sf: ts.SourceFile): Diagnostic | null {
if (!ts.isFunctionDeclaration(node)) return null;
if (!node.type) {
const { line } = sf.getLineAndCharacterOfPosition(node.pos);
return { file: sf.fileName, line: line + 1, message: "Missing return type" };
}
return null;
}Comparing to ESLint
Your custom linter complements ESLint. Use ESLint for style rules and your TypeScript compiler API tool for type-aware rules that ESLint cannot express.
// ESLint: stylistic and common pattern rules
// Compiler API linter: deep type reasoning, custom constraintsRecap: Building a Linter
A TypeScript compiler API linter: create a program, walk the AST with rules, optionally query the type checker, and exit non-zero on errors. This approach enables truly type-aware lint rules.
Quick Check
What gives a compiler API linter an advantage over ESLint?
What You Learned
You built a simple TypeScript linter using the compiler API: load a program, walk the AST, apply rules (optionally type-aware), and report diagnostics. This is the foundation for custom static analysis in any TypeScript project.
Frequently asked questions
Is the “Building a Simple Linting Tool” lesson free?
Yes — the full text of “Building a Simple Linting Tool” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Building a Simple Linting Tool”?
Create a custom diagnostic tool using the compiler API. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Building a Simple Linting Tool” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Creating a TypeScript Program with the API
- Traversing the AST with Visitors
- Custom Transformers and Code Generation
- Building a Simple Linting Tool