typescript · sanjeed5 — Cursor rule: what it does, how to install
Guides Cursor to enforce TypeScript best practices across JavaScript and JSX files using JSDoc, strict compiler flags, and runtime type guards.
.cursor/rules/typescript.mdcWhat typescript does
This rule directs Cursor when editing .js and .jsx files to enforce TypeScript-grade type checking without migrating everything to .ts. It requires strict compiler options in tsconfig.json—such as strict, strictNullChecks, noImplicitAny, and checkJs—paired with interface definitions imported into JSDoc annotations.
It bans any in favor of unknown, mandating runtime type guards and typeof checks before accessing properties on incoming data. It also steers the model away from numeric enums toward string literal union types and const enum.
Because the rule glob specifically targets **/*.{js,jsx}, its instructions trigger strictly on JavaScript and JSX source files.
What it helps with
- type-check JavaScript and JSX files via JSDoc without renaming them to
.ts - replace untyped
anysignatures withunknownand runtime type guards - declare reusable data contracts in
.d.tsfiles for JSDoc imports - prevent numeric enum hazards by using string literal unions
Example prompts for the agent
- Document
processDataparameters with JSDoc and narrowunknowndata with type guards - Configure
tsconfig.jsonfor strict type checking on.jsand.jsxfiles - Create a custom type guard to validate API payloads in
user-service.js
How to install typescript
Manually: put the files in place
.cursor/rules/typescript.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/typescript.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/typescript.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/typescript.mdManually: put the files in place
.clinerules/typescript.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about typescript
How to install typescript in Cursor?
Save the rule file to .cursor/rules/typescript.mdc in your project root.
What does the typescript rule do?
It instructs Cursor to apply strict TypeScript safety to .js and .jsx files using JSDoc, tsconfig.json strict flags, unknown with runtime guards, and string literal unions.
Why does this rule prefer union types over enums?
Numeric TypeScript enums accept arbitrary runtime numbers without warning, whereas string literal unions enforce strict values at compile time.
Source text
typescript.mdc · CC0-1.0
---
description: Enforce modern TypeScript best practices for robust, type-safe JavaScript applications, focusing on strictness, clear type definitions, and runtime validation.
globs: **/*.{js,jsx}
---
# TypeScript Best Practices (for JS/JSX Type-Checking)
This guide outlines essential TypeScript best practices for teams working with JavaScript or JSX files that are type-checked by TypeScript (e.g., via `tsconfig.json` with `allowJs` and/or JSDoc annotations). While many examples use native TypeScript syntax for clarity and conciseness, the underlying principles and type-safety benefits apply directly to your `.js`/`.jsx` codebase.
## 1. Enable Strict Mode in `tsconfig.json`
This is the single most impactful change you can make. `strict: true` enables a suite of crucial checks that catch the vast majority of common type-related bugs at compile time.
**Action**: Ensure your `tsconfig.json` includes:
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true, // Enables all strict type-checking options
"noImplicitAny": true, // Catches untyped variables/parameters
"strictNullChecks": true, // Prevents `null`/`undefined` access without checks
"strictPropertyInitialization": true, // Ensures class properties are initialized
"allowJs": true, // Crucial for type-checking .js/.jsx files
"checkJs": true, // Enables type-checking in .js/.jsx files
// ... other options
},
"include": ["**/*.js", "**/*.jsx"] // Ensure your JS/JSX files are included
}
```
## 2. Define Clear Type Contracts
Use interfaces and type aliases to describe the shape of your data, especially for API payloads, component props, and complex objects. For `.js`/`.jsx` files, leverage JSDoc to apply these types.
### ✅ GOOD: Interfaces for Object Shapes & Classes
Interfaces are ideal for defining the shape of objects and for implementing explicit contracts when working with classes. Define these in `.d.ts` or `.ts` files, then reference them in JSDoc.
```typescript
// types.d.ts or types.ts
interface UserProfile {
id: string;
name: string;
email: string;
age?: number; // Optional property
}
interface Point {
readonly x: number;
readonly y: number;
}
```
```javascript
// user-service.js
/**
* @typedef {import('./types').UserProfile} UserProfile
*/
/**
* @param {UserProfile} user
* @returns {string}
*/
export function greetUser(user) {
return `Hello, ${user.name}!`;
}
```
### ✅ GOOD: Type Aliases for Complex Types & Unions
Type aliases are powerful for defining unions, intersections, primitive aliases, and tuples. Define these in `.d.ts` or `.ts` files, then reference them in JSDoc.
```typescript
// types.d.ts or types.ts
type ID = string | number;
type UserRole = "admin" | "user" | "guest"; // Strict literal type
type Coords = [number, number];
```
```javascript
// auth-service.js
/**
* @typedef {import('./types').ID} ID
* @typedef {import('./types').UserRole} UserRole
*/
/**
* @param {ID} userId
* @param {UserRole} role
* @returns {void}
*/
export function assignRole(userId, role) {
console.log(`User ${userId} assigned role: ${role}`);
}
```
## 3. Avoid `any` and Prefer `unknown` for Untyped Data
`any` completely bypasses TypeScript's checks, reintroducing JavaScript's runtime errors. `unknown` is a safer alternative that forces you to narrow the type before use.
### ❌ BAD: Using `any`
```javascript
// data-processor.js
/**
* @param {any} data // Bypasses all type checks
*/
export function processDataBad(data) {
// No type safety here, data.foo might not exist
console.log(data.foo.bar);
}
```
### ✅ GOOD: Using `unknown` (and Type Guards)
When dealing with data from external sources (e.g., API responses), use `unknown` and then narrow its type using runtime checks.
```javascript
// data-processor.js
/**
* @param {unknown} data // Forces runtime checks
*/
export function processDataGood(data) {
if (typeof data === 'object' && data !== null && 'foo' in data) {
/** @type {{ foo: { bar: string } }} */
const typedData = data; // Type assertion after narrowing
console.log(typedData.foo.bar);
} else {
console.error('Invalid data structure');
}
}
```
## 4. Implement Robust Runtime Type Validation (Type Guards)
TypeScript's compile-time checks are erased at runtime. For data from external sources (APIs, user input), you *must* perform runtime validation to prevent crashes.
### ✅ GOOD: `typeof` for Primitives
```javascript
// utils.js
/**
* @param {unknown} value
* @returns {boolean}
*/
export function isString(value) {
return typeof value === 'string';
}
/**
* @param {unknown} value
* @returns {boolean}
*/
export function isNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
```
### ✅ GOOD: Custom Type Guards for Complex Objects
For interfaces or complex object shapes, create custom functions that perform runtime checks and act as type predicates.
```typescript
// types.d.ts or types.ts
interface Product {
id: string;
name: string;
price: number;
}
// Custom type guard (can be in a .ts file, or its logic used directly in .js)
/**
* @param {unknown} obj
* @returns {obj is Product}
*/
function isProduct(obj) {
return (
typeof obj === 'object' && obj !== null &&
'id' in obj && typeof obj.id === 'string' &&
'name' in obj && typeof obj.name === 'string' &&
'price' in obj && typeof obj.price === 'number'
);
}
```
```javascript
// api-client.js
/**
* @typedef {import('./types').Product} Product
* @type {typeof import('./types').isProduct}
*/
import { isProduct } from './types'; // Assuming isProduct is exported from types.ts/d.ts
/**
* @param {unknown} apiResponse
*/
export function handleApiResponse(apiResponse) {
if (isProduct(apiResponse)) {
// TypeScript now knows apiResponse is Product here
console.log(`Fetched product: ${apiResponse.name} at $${apiResponse.price}`);
} else {
console.error('API response is not a valid Product:', apiResponse);
}
}
```
## 5. Prefer Union Types over Traditional Enums
For simple sets of related constants, strict literal union types (`"admin" | "user"`) are generally preferred over TypeScript's `enum` keyword because they offer better type safety and simpler runtime representation. If you must use enums, prefer `const enum` or string enums.
### ❌ BAD: Numeric Enums
```typescript
// In a .ts file or JSDoc
enum UserStatus {
Active, // 0
Inactive, // 1
Pending // 2
}
// Problem: Can be assigned any number at runtime, e.g., UserStatus.Active = 99
```
### ✅ GOOD: String Literal Unions or `const enum`
```typescript
// types.d.ts or types.ts
/** @typedef {'active' | 'inactive' | 'pending'} UserStatus */
// Or using native TS:
type UserStatus = 'active' | 'inactive' | 'pending';
// If an enum is truly necessary, use a const string enum for compile-time safety and runtime efficiency:
const enum UserRole {
Admin = "admin",
User = "user",
Guest = "guest",
}
```
```javascript
// user-management.js
/**
* @typedef {import('./types').UserStatus} UserStatus
*/
/**
* @param {UserStatus} status
*/
export function updateUserStatus(status) {
console.log(`User status updated to: ${status}`);
}
updateUserStatus('active');
// updateUserStatus('invalid'); // Type error
```
## 6. Use Generics for Reusable Components/Functions
Generics allow you to write flexible and reusable code that works with a variety of types while maintaining type safety.
```javascript
// utils.js
/**
* @template T
* @param {T} arg
* @returns {T}
*/
export function identity(arg) {
return arg;
}
const num = identity(123); // num is inferred as number
const str = identity("hello"); // str is inferred as string
// With constraints (requires native TS syntax or complex JSDoc)
// For complex generics with constraints, consider writing them in a .ts file
// and importing their JSDoc type definitions.
```
## 7. Enforce Consistent Code Organization
Maintainable codebases rely on clear structure.
### ✅ GOOD: Named Exports (No Default Exports)
Named exports promote explicit imports and make refactoring easier. Avoid default exports entirely.
```javascript
// user-service.js
// ❌ BAD: default export
// export default class UserService { /* ... */ }
// ✅ GOOD: named export
export class UserService { /* ... */ }
export const DEFAULT_USER = { /* ... */ };
```
### ✅ GOOD: Organized Imports
Group imports by type (e.g., library, absolute path, relative path) and sort them alphabetically. Use path aliases for cleaner imports from deeply nested modules.
```javascript
// some-component.jsx
import React from 'react'; // Library imports
import { useSelector } from 'react-redux';
import { API_URL } from 'config/constants'; // Absolute imports (e.g., using path aliases)
import { selectUser } from 'store/selectors';
import { Button } from './components/Button'; // Relative imports
import { formatCurrency } from '../utils/formatters';
```
##
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc
Similar IDE rules
All IDE rules for Programming languages →typescript by other authors comes first.