Static Analysis & Language Tooling
The frontend is the IDE. Abstract interpretation, interprocedural analysis, linters, formatters, concrete syntax trees and the language server that serves them all.
Answering questions about every possible execution without running any of them. A compiler frontend is already a static analyser; the interesting part is not the machinery but the two ways it fails — noise you turn off, and silence you trust.
Execute the program over a deliberately impoverished set of values — signs, nullability, intervals — so that the analysis terminates and covers every input at once. Widening is the part that makes loops finish, and it is where the precision goes.
Which statements can run in which order, and — the genuinely hard case — which functions a call site can actually reach when the callee is a value. In a higher-order language you cannot build the call graph without the analysis, and cannot run the analysis without the call graph.
Facts that cross a function boundary. The dial is context sensitivity — whether two call sites of the same function get one answer or two — and every notch of precision is paid for in compile time. Summaries are the compromise everything real is built on.
A compiler error means the code violates the language rules. A lint means the code is suspicious, unidiomatic, or probably-wrong-but-legal. The boundary between them is not fixed — it moves by ecosystem, and knowing where yours put it explains most of your tooling.
Parse the source, throw the layout away, and print it again from the tree. It only works if the tree kept the comments and blank lines the AST discards — which is why a formatter is the first tool that forces you to build a concrete syntax tree.
A tree in which every byte of the source appears exactly once — whitespace, comments and the literal text of every token included. It is what you need the moment a tool has to write code back out rather than only read it.
A compiler frontend rebuilt under three constraints a batch compiler never has: it must be incremental, it must produce answers about code that does not compile, and it may never throw information away. That is a different engineering problem, not the same one with a socket attached.
JSON-RPC over a pipe, and one decision that mattered more than any of its message types: standardizing the interface turned M editors times N languages into M plus N. The gotcha worth knowing is that its positions are UTF-16 code units.
A rename is not a text replacement. It is a query against the symbol table for every reference bound to one declaration, plus a check that the new name does not collide anywhere those references live — and the difference between those two operations is a class of silent bug.