1.5 Developer Tooling
Writing correct code matters, but having good development tools in place dramatically reduces mistakes. TypeScript integrates deeply with IDEs to deliver autocomplete, instant error highlighting, and safe refactoring. This chapter covers everything from optimizing VS Code for TypeScript, to maintaining code quality with ESLint, enforcing consistent style with Prettier, and understanding how to read TypeScript error messages.
VS Code TypeScript Support
TypeScript was created by Microsoft, and so was VS Code. These two tools were designed together from the ground up. VS Code supports TypeScript completely out of the box — no additional plugins required.
The built-in TypeScript server (tsserver)
VS Code runs an internal TypeScript language server called tsserver. Running in the background, tsserver analyzes your code and provides the following features:
- IntelliSense: Automatically suggests object properties, function parameters, and return types based on type information
- Instant error display: Highlights errors with red underlines the moment you type code — no need to save the file
- Hover type information: Shows the type of a variable or function when you hover over it with the mouse
- Go to Definition: Jump directly to the definition of a function or type with
F12orCtrl+Click - Find All References: Find every location a function or type is used with
Shift+F12 - Rename Symbol: Safely rename variables and functions across the entire project with
F2
Switching the TypeScript version in VS Code
The TypeScript version installed locally in your project may differ from the version built into VS Code. It is recommended to configure VS Code to use the project's TypeScript version.
- Open any
.tsfile and pressCtrl+Shift+P(open the Command Palette) - Search for "TypeScript: Select TypeScript Version"
- Select "Use Workspace Version"
This setting is saved in .vscode/settings.json.
{
"typescript.tsdk": "node_modules/typescript/lib"
}
Useful VS Code settings (settings.json)
Add project-specific VS Code settings in .vscode/settings.json. Commit this file to Git so that all team members share the same settings.
{
// TypeScript version — use the project's local version
"typescript.tsdk": "node_modules/typescript/lib",
// Enable auto-import suggestions
"typescript.suggest.autoImports": true,
// Automatically update import paths when files are moved
"typescript.updateImportsOnFileMove.enabled": "always",
// Inlay hints — display inferred types directly in the code
"typescript.inlayHints.parameterNames.enabled": "literals",
"typescript.inlayHints.variableTypes.enabled": false,
"typescript.inlayHints.returnTypes.enabled": true,
"typescript.inlayHints.functionLikeReturnTypes.enabled": true,
// Auto-format on save (when using Prettier)
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
// Auto-fix ESLint issues on save
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
Inlay hints
Inlay hints display type information as a semi-transparent overlay directly in your code. Even without explicit type annotations, you can see the inferred types right in the editor.
// What you see with inlayHints.parameterNames.enabled: "all"
const result = arr.reduce(/*accumulator:*/ (acc, /*currentValue:*/ cur) => acc + cur, 0);
// What you see with inlayHints.returnTypes.enabled: true
function add(a: number, b: number) /*: number*/ {
return a + b;
}
ESLint + typescript-eslint
The TypeScript compiler catches type errors. ESLint catches everything else: unused variables, unnecessary any types, potentially buggy patterns, and more. In TypeScript projects, ESLint is used together with the typescript-eslint plugin.
Installation
npm install --save-dev eslint @eslint/js typescript-eslint
eslint.config.mjs configuration (ESLint v9 Flat Config)
ESLint v9 and later use the eslint.config.mjs file (the Flat Config format).
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
// JavaScript recommended rules
eslint.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
// Project-specific custom rules
{
rules: {
// Warn on any usage (warning instead of error)
'@typescript-eslint/no-explicit-any': 'warn',
// Error on unused variables
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
],
// Allow empty functions
'@typescript-eslint/no-empty-function': 'off',
},
}
);
argsIgnorePattern: '^_' ignores variables whose names start with _ even if they are unused. This is compatible with the convention of marking intentionally unused parameters as _name.
Key rules in @typescript-eslint/recommended
Representative rules included in tseslint.configs.recommended:
| Rule | Description |
|---|---|
no-explicit-any | Disallow or warn on any type usage |
no-unused-vars | Error on unused variables |
no-non-null-assertion | Disallow the non-null assertion operator ! |
prefer-as-const | Recommend using as const |
ban-types | Disallow wrapper types like Object, String, etc. |
no-inferrable-types | Disallow type annotations where types can be inferred |
Adding package.json scripts
{
"scripts": {
"lint": "eslint src",
"lint:fix": "eslint src --fix"
}
}
npm run lint # Print list of errors
npm run lint:fix # Auto-fix errors that can be fixed automatically
Prettier Integration
ESLint handles code quality; Prettier enforces code style — indentation, quotes, semicolons, and so on. With Prettier, every team member writes code in the same format.
Installation
npm install --save-dev prettier eslint-config-prettier
prettier: The code formattereslint-config-prettier: Disables ESLint rules that conflict with Prettier
.prettierrc configuration
Create a .prettierrc file at the project root.
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "lf"
}
| Option | Default | Description |
|---|---|---|
semi | true | Add semicolons |
trailingComma | "all" | Add trailing comma after the last item |
singleQuote | false | Use single quotes |
printWidth | 80 | Maximum line length |
tabWidth | 2 | Number of spaces per indent |
endOfLine | "lf" | Line ending character (LF/CRLF) |
Integrating ESLint and Prettier
Add eslint-config-prettier to eslint.config.mjs. It must be placed last.
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
},
},
// Added last — disables all ESLint rules that conflict with Prettier
eslintConfigPrettier,
);
Adding formatting scripts to package.json
{
"scripts": {
"format": "prettier --write src",
"format:check": "prettier --check src"
}
}
npm run format # Auto-format all files
npm run format:check # Check only whether formatting is needed (for CI)
.editorconfig Configuration
.editorconfig establishes a consistent baseline coding style across different editors. VS Code, IntelliJ, Vim, and many other editors all recognize this file. Used together with Prettier, it unifies even editor-level default settings.
Create an .editorconfig file at the project root.
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
To apply .editorconfig in VS Code, install the "EditorConfig for VS Code" extension.
Reading TypeScript Error Messages
TypeScript error messages can feel unfamiliar at first, but once you understand the pattern they become quick to decode.
Error message structure
src/index.ts:10:20 - error TS2345: Argument of type 'string' is not assignable
to parameter of type 'number'.
src/index.ts:10:20— File path, line number (10), column number (20)error— Severity (error / warning)TS2345— TypeScript error code- The rest — A human-readable description
Commonly encountered error codes
TS2322 — Type 'X' is not assignable to type 'Y'
The most frequently seen error. Occurs when you try to assign a value whose type doesn't match.
const age: number = "30";
// TS2322: Type 'string' is not assignable to type 'number'.
TS2345 — Argument of type 'X' is not assignable to parameter of type 'Y'
Occurs when you pass an argument of the wrong type to a function.
function greet(name: string): void { }
greet(42);
// TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
TS2339 — Property 'X' does not exist on type 'Y'
Occurs when you access a property that doesn't exist on an object.
const user = { name: "Alice" };
console.log(user.age);
// TS2339: Property 'age' does not exist on type '{ name: string; }'.
TS2531 — Object is possibly 'null'
Occurs in strict mode when you access a value that could be null.
const element = document.getElementById("app");
element.textContent = "Hello";
// TS2531: Object is possibly 'null'.
// Fix
element?.textContent = "Hello"; // optional chaining
// or
if (element) element.textContent = "Hello";
TS7006 — Parameter 'X' implicitly has an 'any' type
Occurs in strict mode when a function parameter has no type annotation.
function process(data) { // no type on data
// TS7006: Parameter 'data' implicitly has an 'any' type.
}
// Fix
function process(data: string) { }
TS2304 — Cannot find name 'X'
Occurs when you use a variable or type that has not been declared.
console.log(undeclaredVariable);
// TS2304: Cannot find name 'undeclaredVariable'.
Practical Example: Full Development Environment Setup Step by Step
This is the complete process for setting up a development environment for a new TypeScript project from scratch.
Step 1: Initialize the project
mkdir my-typescript-app
cd my-typescript-app
npm init -y
mkdir src
Step 2: Install core packages
# TypeScript and execution tools
npm install --save-dev typescript tsx @types/node
# ESLint and TypeScript plugin
npm install --save-dev eslint @eslint/js typescript-eslint
# Prettier and ESLint integration
npm install --save-dev prettier eslint-config-prettier
Step 3: Configure tsconfig.json
npx tsc --init
Edit tsconfig.json to look like this:
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"lib": ["ES2022"],
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Step 4: Configure ESLint
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
},
eslintConfigPrettier,
);
Step 5: Configure Prettier
// .prettierrc
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "lf"
}
Step 6: Configure VS Code
// .vscode/settings.json
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"typescript.inlayHints.parameterNames.enabled": "literals",
"typescript.inlayHints.returnTypes.enabled": true
}
Step 7: Configure .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
Step 8: Add .gitignore
node_modules/
dist/
*.js.map
.DS_Store
Step 9: Add package.json scripts
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "npm run type-check && tsc",
"start": "node dist/index.js",
"type-check": "tsc --noEmit",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"format": "prettier --write src",
"format:check": "prettier --check src"
}
}
Step 10: Write the first source file and verify everything works
// src/index.ts
interface User {
id: number;
name: string;
email: string;
}
function createWelcomeMessage(user: User): string {
return `Welcome, ${user.name}! (${user.email})`;
}
const user: User = {
id: 1,
name: 'Developer',
email: 'dev@example.com',
};
console.log(createWelcomeMessage(user));
npm run dev
# Welcome, Developer! (dev@example.com)
npm run type-check
# No type errors
npm run lint
# No lint errors
Pro Tips
// @ts-expect-error vs // @ts-ignore
Both comments suppress TypeScript errors on the next line. However, there is an important difference.
// @ts-ignore — suppresses unconditionally, whether or not an error exists
// @ts-ignore
const x: number = "hello"; // error suppressed
// @ts-expect-error — suppresses only when an error is present
// becomes an error itself if no error exists
// @ts-expect-error
const y: number = "world"; // error suppressed
// What if you add @ts-expect-error but there is no error?
// @ts-expect-error
const z: number = 42; // Error: Unused '@ts-expect-error' directive.
@ts-expect-error is safer. If the code is later fixed so the error disappears, the @ts-expect-error comment itself becomes an error and notifies you. By contrast, @ts-ignore silently stays in place even after the error is gone.
In practice, prefer @ts-expect-error over @ts-ignore. Always leave a comment explaining why the error is being suppressed.
// @ts-expect-error — incorrect type in third-party library definitions, remove after library update
someLibrary.undocumentedMethod();
Exploring built-in types with "Go to Definition"
Ctrl+Click (or F12) lets you jump to the definition of any TypeScript built-in type. You can see exactly how the methods and properties of built-ins like Array, Promise, and Map are defined.
For example, navigating to the type definition of Array.prototype.map reveals:
// lib.es5.d.ts (TypeScript built-in type definitions)
interface Array<T> {
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}
Reading this type definition tells you precisely how map uses generics and what the callback's parameters are. It is more accurate than looking things up in library documentation.
Share recommended VS Code extensions with your team
Adding a list of recommended extensions to .vscode/extensions.json causes VS Code to prompt team members to install them automatically when they open the project.
// .vscode/extensions.json
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"usernamehw.errorlens"
]
}
Summary
| Tool | Role | Config file |
|---|---|---|
| VS Code tsserver | Type checking, autocomplete, error display | .vscode/settings.json |
| ESLint + typescript-eslint | Code quality, anti-pattern detection | eslint.config.mjs |
| Prettier | Consistent code style | .prettierrc |
| eslint-config-prettier | Prevent ESLint + Prettier conflicts | Last entry in eslint.config.mjs |
| .editorconfig | Unify baseline editor settings | .editorconfig |
| Error code | Meaning |
|---|---|
| TS2322 | Type mismatch assignment |
| TS2345 | Function argument type mismatch |
| TS2339 | Accessing a non-existent property |
| TS2531 | Possible null/undefined value |
| TS7006 | Implicit any type on a parameter |
| TS2304 | Using an undeclared name |
The next chapter dives into TypeScript's type system in earnest — starting from the primitives string, number, and boolean, and building up to union types, intersection types, and literal types that form the foundation of TypeScript's type model.