983 lines
18 KiB
Markdown
983 lines
18 KiB
Markdown
# Code Style Guide
|
|
|
|
Coding standards and style conventions for Changemaker Lite V2.
|
|
|
|
## Overview
|
|
|
|
Consistent code style improves:
|
|
- **Readability:** Easier to understand code
|
|
- **Maintainability:** Easier to modify code
|
|
- **Collaboration:** Reduces merge conflicts
|
|
- **Quality:** Catches common errors
|
|
|
|
This guide covers TypeScript, ESLint, Prettier, and naming conventions.
|
|
|
|
## Tools
|
|
|
|
### TypeScript
|
|
|
|
**Version:** 5.x
|
|
**Config:** `tsconfig.json` (api/ and admin/)
|
|
|
|
**Strict Mode:** Enabled
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"strict": true,
|
|
"noImplicitAny": true,
|
|
"strictNullChecks": true,
|
|
"strictFunctionTypes": true,
|
|
"strictPropertyInitialization": true,
|
|
"noImplicitThis": true,
|
|
"alwaysStrict": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### ESLint
|
|
|
|
**Version:** 8.x
|
|
**Config:** `.eslintrc.js` (api/ and admin/)
|
|
|
|
**Plugins:**
|
|
- `@typescript-eslint/eslint-plugin`
|
|
- `eslint-plugin-react` (admin only)
|
|
- `eslint-plugin-react-hooks` (admin only)
|
|
|
|
### Prettier
|
|
|
|
**Version:** 3.x
|
|
**Config:** `.prettierrc`
|
|
|
|
**Format on save:** Enabled (VSCode)
|
|
|
|
## TypeScript Configuration
|
|
|
|
### API tsconfig.json
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"target": "ES2022",
|
|
"module": "commonjs",
|
|
"lib": ["ES2022"],
|
|
"outDir": "./dist",
|
|
"rootDir": "./src",
|
|
"strict": true,
|
|
"esModuleInterop": true,
|
|
"skipLibCheck": true,
|
|
"forceConsistentCasingInFileNames": true,
|
|
"resolveJsonModule": true,
|
|
"moduleResolution": "node",
|
|
"types": ["node"]
|
|
},
|
|
"include": ["src/**/*"],
|
|
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
}
|
|
```
|
|
|
|
### Admin tsconfig.json
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"target": "ES2020",
|
|
"useDefineForClassFields": true,
|
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
"module": "ESNext",
|
|
"skipLibCheck": true,
|
|
"moduleResolution": "bundler",
|
|
"allowImportingTsExtensions": true,
|
|
"resolveJsonModule": true,
|
|
"isolatedModules": true,
|
|
"noEmit": true,
|
|
"jsx": "react-jsx",
|
|
"strict": true,
|
|
"noUnusedLocals": true,
|
|
"noUnusedParameters": true,
|
|
"noFallthroughCasesInSwitch": true,
|
|
"types": ["vite/client"]
|
|
},
|
|
"include": ["src"],
|
|
"references": [{ "path": "./tsconfig.node.json" }]
|
|
}
|
|
```
|
|
|
|
## ESLint Rules
|
|
|
|
### API .eslintrc.js
|
|
|
|
```javascript
|
|
module.exports = {
|
|
parser: '@typescript-eslint/parser',
|
|
parserOptions: {
|
|
ecmaVersion: 2022,
|
|
sourceType: 'module',
|
|
project: './tsconfig.json'
|
|
},
|
|
extends: [
|
|
'eslint:recommended',
|
|
'plugin:@typescript-eslint/recommended',
|
|
'plugin:@typescript-eslint/recommended-requiring-type-checking'
|
|
],
|
|
plugins: ['@typescript-eslint'],
|
|
root: true,
|
|
env: {
|
|
node: true,
|
|
es2022: true
|
|
},
|
|
rules: {
|
|
// TypeScript
|
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
'@typescript-eslint/no-unused-vars': [
|
|
'error',
|
|
{ argsIgnorePattern: '^_' }
|
|
],
|
|
'@typescript-eslint/no-floating-promises': 'error',
|
|
'@typescript-eslint/await-thenable': 'error',
|
|
|
|
// General
|
|
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
'no-debugger': 'error',
|
|
'prefer-const': 'error',
|
|
'no-var': 'error',
|
|
'eqeqeq': ['error', 'always'],
|
|
'curly': ['error', 'all']
|
|
}
|
|
};
|
|
```
|
|
|
|
### Admin .eslintrc.js
|
|
|
|
```javascript
|
|
module.exports = {
|
|
parser: '@typescript-eslint/parser',
|
|
parserOptions: {
|
|
ecmaVersion: 2020,
|
|
sourceType: 'module',
|
|
ecmaFeatures: {
|
|
jsx: true
|
|
},
|
|
project: './tsconfig.json'
|
|
},
|
|
extends: [
|
|
'eslint:recommended',
|
|
'plugin:react/recommended',
|
|
'plugin:react-hooks/recommended',
|
|
'plugin:@typescript-eslint/recommended'
|
|
],
|
|
plugins: ['react', 'react-hooks', '@typescript-eslint'],
|
|
root: true,
|
|
env: {
|
|
browser: true,
|
|
es2020: true
|
|
},
|
|
settings: {
|
|
react: {
|
|
version: 'detect'
|
|
}
|
|
},
|
|
rules: {
|
|
// React
|
|
'react/react-in-jsx-scope': 'off', // React 17+
|
|
'react/prop-types': 'off', // Use TypeScript
|
|
'react-hooks/rules-of-hooks': 'error',
|
|
'react-hooks/exhaustive-deps': 'warn',
|
|
|
|
// TypeScript
|
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
'@typescript-eslint/no-unused-vars': [
|
|
'error',
|
|
{ argsIgnorePattern: '^_' }
|
|
],
|
|
|
|
// General
|
|
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
'no-debugger': 'error',
|
|
'prefer-const': 'error',
|
|
'no-var': 'error',
|
|
'eqeqeq': ['error', 'always']
|
|
}
|
|
};
|
|
```
|
|
|
|
### Key Rules Explained
|
|
|
|
**`@typescript-eslint/no-explicit-any`** - Prevents `any` type
|
|
```typescript
|
|
// ❌ Bad
|
|
function foo(data: any) {}
|
|
|
|
// ✅ Good
|
|
function foo(data: User) {}
|
|
function foo(data: unknown) {} // Use unknown instead
|
|
```
|
|
|
|
**`@typescript-eslint/no-unused-vars`** - Prevents unused variables
|
|
```typescript
|
|
// ❌ Bad
|
|
const foo = 1; // Never used
|
|
|
|
// ✅ Good
|
|
const _foo = 1; // Prefix with _ to ignore
|
|
```
|
|
|
|
**`@typescript-eslint/no-floating-promises`** - Requires await/catch
|
|
```typescript
|
|
// ❌ Bad
|
|
asyncFunction(); // Promise not handled
|
|
|
|
// ✅ Good
|
|
await asyncFunction();
|
|
asyncFunction().catch(console.error);
|
|
void asyncFunction(); // Explicitly ignore
|
|
```
|
|
|
|
**`react-hooks/exhaustive-deps`** - Validates useEffect dependencies
|
|
```typescript
|
|
// ❌ Bad
|
|
useEffect(() => {
|
|
fetchUser(userId);
|
|
}, []); // Missing userId dependency
|
|
|
|
// ✅ Good
|
|
useEffect(() => {
|
|
fetchUser(userId);
|
|
}, [userId]);
|
|
```
|
|
|
|
## Prettier Configuration
|
|
|
|
### .prettierrc
|
|
|
|
```json
|
|
{
|
|
"semi": true,
|
|
"singleQuote": true,
|
|
"tabWidth": 2,
|
|
"useTabs": false,
|
|
"trailingComma": "es5",
|
|
"printWidth": 100,
|
|
"arrowParens": "avoid",
|
|
"endOfLine": "lf"
|
|
}
|
|
```
|
|
|
|
### .prettierignore
|
|
|
|
```
|
|
node_modules
|
|
dist
|
|
build
|
|
coverage
|
|
.vite
|
|
.cache
|
|
*.min.js
|
|
*.min.css
|
|
package-lock.json
|
|
```
|
|
|
|
### Format Commands
|
|
|
|
```bash
|
|
# Format all files
|
|
npm run format
|
|
|
|
# Check formatting (CI)
|
|
npm run format:check
|
|
|
|
# Format specific file
|
|
npx prettier --write src/modules/auth/auth.service.ts
|
|
```
|
|
|
|
## Naming Conventions
|
|
|
|
### Files and Directories
|
|
|
|
**Files:** kebab-case
|
|
```
|
|
auth.service.ts
|
|
user.controller.ts
|
|
campaign.routes.ts
|
|
locations-page.tsx
|
|
```
|
|
|
|
**Components:** PascalCase
|
|
```
|
|
UserCard.tsx
|
|
LoginForm.tsx
|
|
MapView.tsx
|
|
```
|
|
|
|
**Test files:** Match source file with `.test` or `.spec`
|
|
```
|
|
auth.service.test.ts
|
|
UserCard.test.tsx
|
|
```
|
|
|
|
**Directories:** kebab-case
|
|
```
|
|
src/modules/auth/
|
|
src/components/map/
|
|
src/pages/public/
|
|
```
|
|
|
|
### Variables and Functions
|
|
|
|
**Variables:** camelCase
|
|
```typescript
|
|
const userName = 'John';
|
|
const isActive = true;
|
|
const totalCount = 100;
|
|
```
|
|
|
|
**Constants:** UPPER_SNAKE_CASE
|
|
```typescript
|
|
const API_URL = 'http://localhost:4000';
|
|
const MAX_RETRIES = 3;
|
|
const DEFAULT_PAGE_SIZE = 50;
|
|
```
|
|
|
|
**Functions:** camelCase
|
|
```typescript
|
|
function getUserById(id: number) {}
|
|
async function fetchCampaigns() {}
|
|
const handleClick = () => {};
|
|
```
|
|
|
|
**Private methods:** Prefix with underscore (optional)
|
|
```typescript
|
|
class UserService {
|
|
async getUser(id: number) {}
|
|
|
|
private async _hashPassword(password: string) {}
|
|
}
|
|
```
|
|
|
|
### Types and Interfaces
|
|
|
|
**Types/Interfaces:** PascalCase
|
|
```typescript
|
|
interface User {
|
|
id: number;
|
|
email: string;
|
|
}
|
|
|
|
type UserRole = 'USER' | 'ADMIN';
|
|
|
|
interface CreateUserInput {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
```
|
|
|
|
**Enums:** PascalCase, members UPPER_SNAKE_CASE
|
|
```typescript
|
|
enum UserRole {
|
|
USER = 'USER',
|
|
ADMIN = 'ADMIN',
|
|
SUPER_ADMIN = 'SUPER_ADMIN'
|
|
}
|
|
```
|
|
|
|
### React Components
|
|
|
|
**Components:** PascalCase
|
|
```typescript
|
|
export function UserCard({ user }: { user: User }) {
|
|
return <div>{user.name}</div>;
|
|
}
|
|
```
|
|
|
|
**Props interfaces:** ComponentNameProps
|
|
```typescript
|
|
interface UserCardProps {
|
|
user: User;
|
|
onEdit?: (user: User) => void;
|
|
}
|
|
|
|
export function UserCard({ user, onEdit }: UserCardProps) {
|
|
return <div>{user.name}</div>;
|
|
}
|
|
```
|
|
|
|
**Event handlers:** handle[Event] or on[Event]
|
|
```typescript
|
|
function UserForm() {
|
|
const handleSubmit = () => {};
|
|
const onEmailChange = (email: string) => {};
|
|
|
|
return <form onSubmit={handleSubmit}>...</form>;
|
|
}
|
|
```
|
|
|
|
### Database Models
|
|
|
|
**Prisma models:** PascalCase (singular)
|
|
```prisma
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
}
|
|
|
|
model Campaign {
|
|
id Int @id @default(autoincrement())
|
|
title String
|
|
}
|
|
```
|
|
|
|
**Table names:** snake_case (plural)
|
|
```prisma
|
|
model User {
|
|
@@map("users")
|
|
}
|
|
|
|
model Campaign {
|
|
@@map("campaigns")
|
|
}
|
|
```
|
|
|
|
**Fields:** camelCase in schema, snake_case in database
|
|
```prisma
|
|
model User {
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
}
|
|
```
|
|
|
|
## File Organization
|
|
|
|
### Module Structure
|
|
|
|
```
|
|
src/modules/auth/
|
|
├── auth.service.ts # Business logic
|
|
├── auth.routes.ts # Express routes
|
|
├── auth.schemas.ts # Zod validation schemas
|
|
└── auth.service.test.ts # Tests
|
|
```
|
|
|
|
### Import Order
|
|
|
|
1. External libraries
|
|
2. Internal modules (absolute imports)
|
|
3. Relative imports
|
|
4. Types
|
|
5. Styles (frontend)
|
|
|
|
```typescript
|
|
// 1. External libraries
|
|
import express from 'express';
|
|
import { z } from 'zod';
|
|
|
|
// 2. Internal modules
|
|
import { authenticate } from '@/middleware/auth';
|
|
import { UserService } from '@/modules/users/user.service';
|
|
|
|
// 3. Relative imports
|
|
import { AuthService } from './auth.service';
|
|
import { loginSchema } from './auth.schemas';
|
|
|
|
// 4. Types
|
|
import type { Request, Response } from 'express';
|
|
import type { User } from '@prisma/client';
|
|
|
|
// 5. Styles (frontend only)
|
|
import './auth.css';
|
|
```
|
|
|
|
### Export Patterns
|
|
|
|
**Named exports** (preferred)
|
|
```typescript
|
|
// auth.service.ts
|
|
export class AuthService {
|
|
async login() {}
|
|
}
|
|
|
|
// usage
|
|
import { AuthService } from './auth.service';
|
|
```
|
|
|
|
**Default exports** (React components)
|
|
```typescript
|
|
// UserCard.tsx
|
|
export default function UserCard() {
|
|
return <div>...</div>;
|
|
}
|
|
|
|
// usage
|
|
import UserCard from './UserCard';
|
|
```
|
|
|
|
**Re-exports** (index files)
|
|
```typescript
|
|
// modules/auth/index.ts
|
|
export { AuthService } from './auth.service';
|
|
export { authRoutes } from './auth.routes';
|
|
export * from './auth.schemas';
|
|
```
|
|
|
|
## Code Patterns
|
|
|
|
### Async/Await
|
|
|
|
Always use async/await (not callbacks or .then()):
|
|
|
|
**Good:**
|
|
```typescript
|
|
async function getUser(id: number) {
|
|
const user = await prisma.user.findUnique({ where: { id } });
|
|
return user;
|
|
}
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
function getUser(id: number) {
|
|
return prisma.user.findUnique({ where: { id } }).then(user => {
|
|
return user;
|
|
});
|
|
}
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
Use try/catch for error handling:
|
|
|
|
**Good:**
|
|
```typescript
|
|
async function createUser(data: CreateUserInput) {
|
|
try {
|
|
const user = await prisma.user.create({ data });
|
|
return user;
|
|
} catch (error) {
|
|
logger.error('Failed to create user', error);
|
|
throw new Error('User creation failed');
|
|
}
|
|
}
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
async function createUser(data: CreateUserInput) {
|
|
const user = await prisma.user.create({ data }); // Unhandled error
|
|
return user;
|
|
}
|
|
```
|
|
|
|
### Optional Chaining
|
|
|
|
Use optional chaining for nullable values:
|
|
|
|
**Good:**
|
|
```typescript
|
|
const email = user?.email;
|
|
const city = user?.address?.city;
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
const email = user && user.email;
|
|
const city = user && user.address && user.address.city;
|
|
```
|
|
|
|
### Nullish Coalescing
|
|
|
|
Use ?? for default values (not ||):
|
|
|
|
**Good:**
|
|
```typescript
|
|
const limit = query.limit ?? 50;
|
|
const name = user.name ?? 'Unknown';
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
const limit = query.limit || 50; // Fails for 0
|
|
const name = user.name || 'Unknown'; // Fails for ''
|
|
```
|
|
|
|
### Array Methods
|
|
|
|
Prefer functional array methods:
|
|
|
|
**Good:**
|
|
```typescript
|
|
const activeUsers = users.filter(u => u.isActive);
|
|
const emails = users.map(u => u.email);
|
|
const total = amounts.reduce((sum, amt) => sum + amt, 0);
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
const activeUsers = [];
|
|
for (let i = 0; i < users.length; i++) {
|
|
if (users[i].isActive) {
|
|
activeUsers.push(users[i]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Object Destructuring
|
|
|
|
Use destructuring for object properties:
|
|
|
|
**Good:**
|
|
```typescript
|
|
const { email, name, role } = user;
|
|
const { limit = 50, page = 1 } = query;
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
const email = user.email;
|
|
const name = user.name;
|
|
const role = user.role;
|
|
```
|
|
|
|
### Template Literals
|
|
|
|
Use template literals for string interpolation:
|
|
|
|
**Good:**
|
|
```typescript
|
|
const message = `Hello, ${user.name}!`;
|
|
const url = `/api/users/${userId}`;
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
const message = 'Hello, ' + user.name + '!';
|
|
const url = '/api/users/' + userId;
|
|
```
|
|
|
|
## Comments and Documentation
|
|
|
|
### JSDoc for Functions
|
|
|
|
Document public functions with JSDoc:
|
|
|
|
```typescript
|
|
/**
|
|
* Creates a new user with the given email and password.
|
|
*
|
|
* @param email - User's email address
|
|
* @param password - User's password (will be hashed)
|
|
* @returns Created user object
|
|
* @throws {Error} If user already exists
|
|
*/
|
|
async function createUser(email: string, password: string): Promise<User> {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Inline Comments
|
|
|
|
Use inline comments for complex logic:
|
|
|
|
```typescript
|
|
// Calculate pagination offset
|
|
const offset = (page - 1) * limit;
|
|
|
|
// Hash password with 10 salt rounds
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
// Point-in-polygon ray-casting algorithm
|
|
let inside = false;
|
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
// ... complex logic
|
|
}
|
|
```
|
|
|
|
### Avoid Obvious Comments
|
|
|
|
Don't comment obvious code:
|
|
|
|
**Good:**
|
|
```typescript
|
|
const isValid = email.includes('@');
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
// Check if email is valid
|
|
const isValid = email.includes('@');
|
|
```
|
|
|
|
### TODO Comments
|
|
|
|
Use TODO for future work:
|
|
|
|
```typescript
|
|
// TODO: Add pagination support
|
|
async function getUsers() {
|
|
return prisma.user.findMany();
|
|
}
|
|
|
|
// FIXME: This doesn't handle edge case when user is null
|
|
const userName = user.name;
|
|
```
|
|
|
|
## Git Commit Messages
|
|
|
|
### Conventional Commits
|
|
|
|
Use conventional commit format:
|
|
|
|
```
|
|
<type>(<scope>): <subject>
|
|
|
|
<body>
|
|
|
|
<footer>
|
|
```
|
|
|
|
**Types:**
|
|
- `feat:` New feature
|
|
- `fix:` Bug fix
|
|
- `docs:` Documentation
|
|
- `style:` Formatting
|
|
- `refactor:` Code restructuring
|
|
- `test:` Adding tests
|
|
- `chore:` Maintenance
|
|
|
|
**Examples:**
|
|
```bash
|
|
feat(auth): add JWT refresh token rotation
|
|
fix(map): correct point-in-polygon calculation
|
|
docs(api): update authentication guide
|
|
refactor(users): extract service layer
|
|
test(campaigns): add unit tests for CRUD operations
|
|
```
|
|
|
|
**With scope and body:**
|
|
```bash
|
|
git commit -m "feat(campaigns): add email sending
|
|
|
|
Implements BullMQ queue for async email delivery.
|
|
Adds retry logic and error handling.
|
|
|
|
Closes #123"
|
|
```
|
|
|
|
### Co-Authoring with Claude
|
|
|
|
When Claude assists with code:
|
|
|
|
```bash
|
|
git commit -m "$(cat <<'EOF'
|
|
feat(auth): add JWT refresh token rotation
|
|
|
|
Implemented atomic refresh token rotation to prevent
|
|
race conditions during concurrent refresh requests.
|
|
|
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
EOF
|
|
)"
|
|
```
|
|
|
|
## React Patterns
|
|
|
|
### Functional Components
|
|
|
|
Always use functional components (not class components):
|
|
|
|
**Good:**
|
|
```typescript
|
|
export function UserCard({ user }: UserCardProps) {
|
|
return <div>{user.name}</div>;
|
|
}
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
export class UserCard extends React.Component<UserCardProps> {
|
|
render() {
|
|
return <div>{this.props.user.name}</div>;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Hooks
|
|
|
|
Use hooks for state and side effects:
|
|
|
|
```typescript
|
|
function UserList() {
|
|
const [users, setUsers] = useState<User[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
async function fetchUsers() {
|
|
setLoading(true);
|
|
const data = await api.get('/users');
|
|
setUsers(data);
|
|
setLoading(false);
|
|
}
|
|
fetchUsers();
|
|
}, []);
|
|
|
|
if (loading) return <div>Loading...</div>;
|
|
|
|
return <div>{users.map(u => <UserCard key={u.id} user={u} />)}</div>;
|
|
}
|
|
```
|
|
|
|
### Props Destructuring
|
|
|
|
Destructure props in function signature:
|
|
|
|
**Good:**
|
|
```typescript
|
|
function UserCard({ user, onEdit }: UserCardProps) {
|
|
return <div onClick={() => onEdit?.(user)}>{user.name}</div>;
|
|
}
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
function UserCard(props: UserCardProps) {
|
|
return <div onClick={() => props.onEdit?.(props.user)}>{props.user.name}</div>;
|
|
}
|
|
```
|
|
|
|
### Key Prop
|
|
|
|
Always provide key for list items:
|
|
|
|
**Good:**
|
|
```typescript
|
|
{users.map(user => (
|
|
<UserCard key={user.id} user={user} />
|
|
))}
|
|
```
|
|
|
|
**Bad:**
|
|
```typescript
|
|
{users.map((user, index) => (
|
|
<UserCard key={index} user={user} />
|
|
))}
|
|
```
|
|
|
|
## Editor Integration
|
|
|
|
### VSCode Settings
|
|
|
|
Create `.vscode/settings.json`:
|
|
|
|
```json
|
|
{
|
|
"editor.formatOnSave": true,
|
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
"editor.codeActionsOnSave": {
|
|
"source.fixAll.eslint": true
|
|
},
|
|
"typescript.tsdk": "node_modules/typescript/lib",
|
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
|
"[typescript]": {
|
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
},
|
|
"[typescriptreact]": {
|
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Pre-commit Hook
|
|
|
|
Install husky for pre-commit checks:
|
|
|
|
```bash
|
|
npm install --save-dev husky lint-staged
|
|
npx husky install
|
|
```
|
|
|
|
**package.json:**
|
|
```json
|
|
{
|
|
"lint-staged": {
|
|
"*.{ts,tsx}": [
|
|
"eslint --fix",
|
|
"prettier --write"
|
|
]
|
|
},
|
|
"scripts": {
|
|
"prepare": "husky install"
|
|
}
|
|
}
|
|
```
|
|
|
|
**.husky/pre-commit:**
|
|
```bash
|
|
#!/bin/sh
|
|
. "$(dirname "$0")/_/husky.sh"
|
|
|
|
npx lint-staged
|
|
```
|
|
|
|
## Quick Reference
|
|
|
|
### Run Linting
|
|
|
|
```bash
|
|
# Lint
|
|
npm run lint
|
|
|
|
# Auto-fix
|
|
npm run lint:fix
|
|
|
|
# Format
|
|
npm run format
|
|
|
|
# Type-check
|
|
npm run type-check
|
|
```
|
|
|
|
### Common Fixes
|
|
|
|
```bash
|
|
# Fix all auto-fixable issues
|
|
npm run lint:fix && npm run format
|
|
|
|
# Type-check both projects
|
|
cd api && npm run type-check && cd ../admin && npm run type-check
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- **Setup:** [Local Development Setup](local-setup.md)
|
|
- **TypeScript:** [TypeScript Guide](typescript.md)
|
|
- **Testing:** [Testing Guide](testing.md)
|
|
- **Git:** [Git Workflow](git-workflow.md)
|
|
|
|
## Summary
|
|
|
|
You now know:
|
|
- ✅ TypeScript configuration (strict mode)
|
|
- ✅ ESLint rules and plugins
|
|
- ✅ Prettier configuration
|
|
- ✅ Naming conventions (files, variables, types)
|
|
- ✅ File organization patterns
|
|
- ✅ Code patterns (async/await, error handling)
|
|
- ✅ Comment and documentation standards
|
|
- ✅ Git commit message format
|
|
- ✅ React patterns and best practices
|
|
- ✅ Editor integration (VSCode, pre-commit hooks)
|
|
|
|
**Quick Start:**
|
|
```bash
|
|
# Auto-fix and format
|
|
npm run lint:fix && npm run format
|
|
|
|
# Check types
|
|
npm run type-check
|
|
|
|
# Pre-commit
|
|
npm run lint:fix && npm run format && npm run type-check
|
|
```
|