TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
87: Language Service API Basics
When you first dive into the TypeScript Language Service API, it's easy to assume it works like the tsc compiler: you feed it a file, and it gives you errors. But the Language Service is designed for IDEs. It's a "pull" system, not a "push" system. If you don't set up the communication channel between the service and your files correctly, the API will just return empty arrays and leave you wondering why your code isn't being analyzed.
import ts from 'typescript';
const code = 'const x: number = "I am a string";';
const fileName = 'example.ts';
// A naive attempt at a Language Service Host
const host: ts.LanguageServiceHost = {
getScriptFileNames: () => [fileName],
getScriptVersion: () => '1',
getScriptSnapshot: (fileName) => {
return ts.createTextSnapshot(code);
},
getTypeScriptVersion: () => ts.version,
// We forgot to implement getScriptFile!
};
const service = ts.createLanguageService(host);
const diagnostics = service.getSemanticDiagnostics(fileName);
console.log(diagnostics); // Result: [] (Empty array!)
The Silence of the Diagnostics
Looking at the code above, you'd expect an error because we're assigning a string to a number. But the result is an empty array. Why? Because the Language Service is lazy—in a good way. To save memory in a massive project, it doesn't load everything. When it needs to actually analyze the semantics of a file, it asks the host for the SourceFile object via the getScriptFile method.
In the broken example, I omitted getScriptFile. When the service tries to check for type errors, it asks the host, "Can you give me the parsed AST for example.ts?" The host returns undefined, and the service simply gives up on that file. It doesn't throw an error; it just assumes there's nothing to check.
Bridging the Host and the Source
To fix this, we need to implement getScriptFile. This method is where the raw text (the snapshot) gets turned into a ts.SourceFile (the AST). I usually recommend using a simple cache here, because parsing the same string into an AST over and over again will tank your performance as soon as your "mini-IDE" grows beyond one file.
import ts from 'typescript';
const code = 'const x: number = "I am a string";';
const fileName = 'example.ts';
const host: ts.LanguageServiceHost = {
getScriptFileNames: () => [fileName],
getScriptVersion: () => '1',
getScriptSnapshot: (fileName) => ts.createTextSnapshot(code),
getTypeScriptVersion: () => ts.version,
// The missing link:
getScriptFile: (fileName) => {
// In a real app, cache this SourceFile object!
return ts.createSourceFile(
fileName,
code,
ts.ScriptTarget.Latest,
true
);
}
};
const service = ts.createLanguageService(host);
const diagnostics = service.getSemanticDiagnostics(fileName);
console.log(diagnostics.map(d => d.messageText));
// Result: ["Type 'string' is not assignable to type 'number'."]
Now the service has a path: it asks for the snapshot to see if the file has changed, and it asks for the SourceFile to actually perform the type checking. I've found that most developers struggle here because the LanguageServiceHost interface is huge and intimidating, but for basic in-memory tools, these are the only few methods that actually matter.
Semantic vs. Syntactic Diagnostics
One last thing to keep in mind: notice I used getSemanticDiagnostics. If you had a syntax error (like a missing closing brace), getSemanticDiagnostics might return nothing because the code isn't even valid enough to check types. If you're building a real-time linter, you'll want to call both getSyntacticDiagnostics (for "your code is broken") and getSemanticDiagnostics (for "your types are wrong").
📋 Practical Task
Building a "Type-Mismatch" Detector
Your task is to create a small utility script using the TypeScript Language Service API. This utility should take a string of TypeScript code and a target variable name, and determine if that specific variable is being assigned a value of the wrong type.
Requirements:
- Implement a
LanguageServiceHostthat handles in-memory strings. - Use
service.getSemanticDiagnostics(fileName)to retrieve type errors. - Filter the diagnostics to find any error that occurs on the line where your target variable is defined.
- The script should output
"Type Error Found"if the variable has a type mismatch, or"Type OK"otherwise.
Test Case:
const inputCode = 'let age: number = "twenty";';
const targetVar = 'age';
// Should output: "Type Error Found"
There are no comments for now.