code-quality
Complete code quality workflow with ESLint, Prettier, TypeScript, Husky hooks, and type safety standards. Run before every commit/push.
$ 安裝
git clone https://github.com/ToonVos/empty-opensaas /tmp/empty-opensaas && cp -r /tmp/empty-opensaas/.claude/skills/code-quality ~/.claude/skills/empty-opensaas// tip: Run this command in your terminal to install the skill
name: code-quality description: Complete code quality workflow with ESLint, Prettier, TypeScript, Husky hooks, and type safety standards. Run before every commit/push. triggers: [ "lint", "eslint", "prettier", "typescript", "type check", "code quality", "clean code", "format", "husky", "pre-commit", "pre-push", "tsc", "type error", "linting error", ] version: 1.0 last_updated: 2025-10-24
Code Quality & Linting
Complete workflow for maintaining code quality through automated checks, linting, formatting, and type safety.
Purpose
Ensure code quality before every commit/push through:
- ESLint - Code style and best practices
- Prettier - Consistent formatting
- TypeScript - Type safety
- Husky - Automated git hooks
- Type Safety Standards - 2-tier approach (production strict, tests pragmatic)
Use when:
- Before committing code
- Fixing linting errors
- Resolving type errors
- Setting up code quality workflow
- Understanding what checks run automatically
Quick Reference
Manual Checks (Run Anytime)
# Prettier (formatting)
cd app && npx prettier --check . # Check formatting
cd app && npx prettier --write . # Fix formatting
# ESLint (code quality)
cd app && npx eslint . --ext .ts,.tsx # Check for issues
cd app && npx eslint . --ext .ts,.tsx --fix # Auto-fix issues
# TypeScript (type safety)
cd app && npx tsc --noEmit # Full type check
cd app && npx tsc -p tsconfig.json --noEmit # Use config
# All checks at once (pre-push equivalent)
cd app && npx prettier --check . && npx tsc --noEmit && npx eslint . --ext .ts,.tsx
Automated Checks
| Trigger | When | What Runs |
|---|---|---|
| Pre-commit | git commit | Lint-staged, Prettier, TypeScript (staged only) |
| Pre-push | git push | Full TypeScript, Wasp validation, ESLint |
| CI/CD | PR to develop/main | All checks + tests |
| Manual | Run commands above | Individual checks |
Pre-Commit Checks
Automatically runs on git commit (via Husky hook: .husky/_pre-commit.sh)
1. Block Secret Files
Prevents committing:
.env
.env.server
.env.client
.env.local
.env.server.local
.env.client.local
Allow example files:
.env.server.example # ✅ OK to commit
.env.client.example # ✅ OK to commit
If blocked:
git restore --staged .env.server
git commit # Try again
2. Lint-Staged (Fast Checks)
Only checks staged files (configured in .lintstagedrc.json):
| File Pattern | Actions |
|---|---|
*.md | Prettier format |
app/src/**/*.{ts,tsx} | ESLint auto-fix + Prettier format |
app/src/**/*.{js,jsx} | ESLint auto-fix + Prettier format |
app/**/*.{json,css} | Prettier format |
Auto-fixes applied before commit - staged files updated automatically.
3. Prettier Check (All Files)
Checks ALL files, not just staged (same as CI):
cd app && npx prettier --check .
If fails:
cd app && npx prettier --write . # Fix all files
git add . # Re-stage formatted files
git commit # Try again
4. TypeScript Check (Staged Files Only)
Fast check - only runs if .ts or .tsx files staged:
cd app && npx tsc -p tsconfig.json --noEmit
If fails: See Type Errors section below.
5. Cleanup .tmp/ Directory
Non-blocking - removes files >7 days old from .tmp/:
- Preserves
README.md,CLAUDE.md,.gitkeep - Removes empty directories (keeps structure)
- Warns about temp files outside
.tmp/
Pre-Push Checks
Automatically runs on git push (via Husky hook: .husky/_pre-push.sh)
1. Full TypeScript Check
All files, not just staged:
cd app && npx tsc -p tsconfig.json --noEmit
More thorough than pre-commit - catches cross-file type issues.
2. Wasp Configuration Validation
Validates main.wasp syntax:
cd app && wasp version
Catches:
- Syntax errors in main.wasp
- Missing entity declarations
- Invalid route definitions
3. ESLint (Max 50 Warnings)
Full ESLint check with warning tolerance:
cd app && npx eslint . --ext .ts,.tsx,.js,.jsx --max-warnings 50
Why 50 warnings?
- Legacy code cleanup in progress
- Errors always block (0 tolerance)
- Warnings reduced over time
Current warning count: 20 (as of 2025-10-24)
Type Safety Standards
Reference: docs/LINTING-STANDARDS.md
2-Tier Standard
| Context | Standard | any Usage | Why |
|---|---|---|---|
| Production code | Strict (Tier 1) | 0 preferred | Type safety critical |
| Tests/mocks/helpers | Pragmatic | With comment | Practicality over purity |
Key Patterns
Operations (Production Code)
// ✅ CORRECT - Wasp generates types automatically
import type { GetDocument } from "wasp/server/operations";
export const getDocument: GetDocument<{ id: string }, A3Document> = async (
args,
context,
) => {
if (!context.user) throw new HttpError(401);
// context.entities is typed automatically
return context.entities.A3Document.findUnique({ where: { id: args.id } });
};
Helper Functions (Choose Pattern)
// 🥇 PREFERRED - Delegate pattern (0 `any`)
export async function createWithAudit(
userId: string,
data: Prisma.A3DocumentCreateInput,
a3Delegate: Prisma.A3DocumentDelegate, // Pass specific delegate
) {
return a3Delegate.create({ data: { ...data, userId } });
}
// ⚠️ FALLBACK - Context any (when 3+ entities needed)
/**
* Multi-entity helper - accepts any context.
* Reason: Needs A3Document, User, Department delegates.
*/
export async function complexHelper(
data: any,
context: any, // eslint-disable-line @typescript-eslint/no-explicit-any -- Wasp operation context (see file header)
) {
// ... uses multiple entities
}
Test Mocks (Pragmatic)
import { vi } from "vitest";
// ✅ PREFERRED - Try vi.mocked() first (Vitest 3.x)
const mockCreate = vi.mocked(context.entities.A3Document.create);
// ✅ ACCEPTABLE - If vi.mocked() has type mismatches
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Mock cast: Vitest types don't match Prisma delegates
const mockCreate = context.entities.A3Document.create as any;
See LINTING-STANDARDS.md for:
- Complete 2-tier philosophy
- Helper function decision tree
- ESLint configuration (disable no-undef for TS)
- unknown vs any guidance
- Industry standards alignment
Common Fixes
Type Errors
"Property X does not exist on type Y"
After schema.prisma or main.wasp changes:
# 1. Restart Wasp (regenerates types)
./scripts/safe-start.sh
# 2. If still fails, clean build
wasp clean
./scripts/safe-start.sh
Missing type annotation:
// ❌ WRONG - No type annotation
export const getDocument = async (args, context) => {
// context.entities is undefined!
};
// ✅ CORRECT - Type annotation
export const getDocument: GetDocument<{ id: string }, A3Document> = async (
args,
context,
) => {
// context.entities is typed
};
"Cannot find module 'wasp/...'"
# Restart Wasp to regenerate types
./scripts/safe-start.sh
"Unexpected any"
Use inline eslint-disable with reason:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Mock cast: Vitest types don't match Prisma delegates
const mockContext = createMockContext() as any;
Or fix with vi.mocked():
// Better approach (Vitest 3.x)
const mockCreate = vi.mocked(context.entities.A3Document.create);
Prettier Errors
"Code style issues found"
# Fix all files
cd app && npx prettier --write .
# Check specific file
cd app && npx prettier --check src/path/to/file.ts
# Fix specific file
cd app && npx prettier --write src/path/to/file.ts
ESLint Errors
"ESLint found issues"
# Auto-fix what's possible
cd app && npx eslint . --ext .ts,.tsx --fix
# See all issues
cd app && npx eslint . --ext .ts,.tsx
# Check specific file
cd app && npx eslint src/path/to/file.ts
Common ESLint errors:
| Error | Fix |
|---|---|
no-unused-vars | Remove unused variable or prefix with _ |
no-explicit-any | Use proper type or add inline eslint-disable |
react-hooks/exhaustive-deps | Add missing dependency or suppress with comment |
Hook Failures
Pre-commit fails:
# See what failed in error output, then:
# Fix Prettier
cd app && npx prettier --write .
# Fix TypeScript
./scripts/safe-start.sh # If types missing
cd app && npx tsc --noEmit # Check again
# Fix ESLint
cd app && npx eslint . --ext .ts,.tsx --fix
# Unstage secret files
git restore --staged .env.server
Pre-push fails:
# Full check locally before pushing
cd app && npx prettier --check . && npx tsc --noEmit && npx eslint . --ext .ts,.tsx
# Fix each error from output
Bypass hooks (emergency only):
git commit --no-verify # ⚠️ Use with extreme caution
git push --no-verify # ⚠️ CI will still check!
Complete Workflow
Before Every Commit
Automated (no action needed):
- ✅ Lint-staged runs on staged files
- ✅ Prettier formats code
- ✅ ESLint auto-fixes issues
- ✅ TypeScript checks staged files
If hook fails:
# Read error output
# Fix the issue (see Common Fixes above)
# Try commit again
git commit
Before Every Push
Automated (no action needed):
- ✅ Full TypeScript check
- ✅ Wasp configuration validated
- ✅ ESLint runs (max 50 warnings)
If hook fails:
# Run checks manually to see details
cd app && npx tsc --noEmit
cd app && npx eslint . --ext .ts,.tsx
# Fix issues
# Try push again
git push
Manual Quality Check
Run before creating PR:
# 1. Format all code
cd app && npx prettier --write .
# 2. Fix linting issues
cd app && npx eslint . --ext .ts,.tsx --fix
# 3. Check types
cd app && npx tsc --noEmit
# 4. Run tests
cd app && wasp test client run
# 5. Commit and push
git add .
git commit -m "fix: code quality improvements"
git push
Troubleshooting
"Husky hooks not running"
# Reinstall hooks
npx husky install
# Check hook files exist
ls -la .husky/
"lint-staged skipping files"
# Check .lintstagedrc.json patterns
cat .lintstagedrc.json
# Files must be staged
git add src/path/to/file.ts
git commit
"TypeScript errors after schema change"
# ALWAYS restart after schema/main.wasp changes
./scripts/safe-start.sh
# If still fails
wasp clean
./scripts/safe-start.sh
"Too many ESLint warnings"
Current: 20 warnings (target: 0)
# See all warnings
cd app && npx eslint . --ext .ts,.tsx
# Fix incrementally
cd app && npx eslint src/specific/area --ext .ts,.tsx --fix
Priority order:
- Fix errors first (always block)
- Fix warnings in new code
- Gradually fix legacy warnings
"React 19 issue after wasp clean"
# Wasp clean installs React 19 (breaks app)
# Use safe-start.sh --clean instead
./scripts/safe-start.sh --clean # Auto-fixes React version
# Or fix manually
./scripts/fix-react-version.sh
Configuration Files
.lintstagedrc.json
What: Fast checks on staged files only (pre-commit)
Patterns:
*.md→ Prettierapp/src/**/*.{ts,tsx}→ ESLint + Prettierapp/src/**/*.{js,jsx}→ ESLint + Prettierapp/**/*.{json,css}→ Prettier
.husky/_pre-commit.sh
What: Pre-commit hook script
Runs:
- Block .env files
- Lint-staged
- Prettier check (all files)
- TypeScript check (staged files)
- Cleanup .tmp/
.husky/_pre-push.sh
What: Pre-push hook script
Runs:
- Full TypeScript check
- Wasp validation
- ESLint (max 50 warnings)
app/.eslintrc.json
What: ESLint configuration
Key rules:
@typescript-eslint/no-explicit-any: warn (not error)@typescript-eslint/no-undef: off (TypeScript handles it)- React hooks rules enabled
- Max warnings: 50 (enforced in pre-push)
app/.prettierrc
What: Prettier configuration
Settings:
- 2 spaces indent
- Single quotes
- Semicolons
- Max line length: 100
References
- Type Safety: docs/LINTING-STANDARDS.md
- Troubleshooting: wasp-troubleshooting skill
- CI/CD: .github/CLAUDE.md
- Temp Files: .tmp/CLAUDE.md
Examples
Fixing Common Pre-Commit Failures
Scenario 1: Prettier fails
[pre-commit] ❌ Prettier check failed
# Fix
cd app && npx prettier --write .
git add .
git commit
Scenario 2: TypeScript error in staged file
[pre-commit] TypeScript error: Property 'name' does not exist
# Option 1: Fix the error
# Edit file, fix type issue
# Option 2: Restart if schema changed
./scripts/safe-start.sh
git add src/path/to/file.ts
git commit
Scenario 3: Secret file blocked
[pre-commit] Refusing to commit env files
# Unstage
git restore --staged .env.server
# Verify it's in .gitignore
cat .gitignore | grep .env.server
git commit
Fixing Common Pre-Push Failures
Scenario 1: Full TypeScript check fails
[pre-push] TypeScript error in src/utils/helper.ts
# Run locally to see details
cd app && npx tsc --noEmit
# Fix error
# ... edit file
# Commit fix
git add src/utils/helper.ts
git commit -m "fix: resolve type error in helper"
git push
Scenario 2: ESLint exceeds warning limit
[pre-push] ESLint found 55 warnings (max 50)
# See warnings
cd app && npx eslint . --ext .ts,.tsx
# Auto-fix what's possible
cd app && npx eslint . --ext .ts,.tsx --fix
# Commit fixes
git add .
git commit -m "fix: resolve ESLint warnings"
git push
Manual Code Quality Workflow
Before creating PR:
# 1. Ensure code is formatted
cd app && npx prettier --write .
git add .
git commit -m "style: format code with prettier"
# 2. Fix linting issues
cd app && npx eslint . --ext .ts,.tsx --fix
git add .
git commit -m "fix: resolve linting issues"
# 3. Verify types are correct
cd app && npx tsc --noEmit
# Fix any errors that appear
# 4. Run tests
cd app && wasp test client run
# Ensure all tests pass
# 5. Push with confidence
git push
Notes
- Hooks are FAST: Pre-commit averages <5s for typical commits
- Pre-push is thorough: Full checks ensure CI won't fail
- Auto-fix is safe: Lint-staged only modifies staged files
- Warnings tolerated: 50 max (legacy code cleanup in progress)
- Types regenerate on restart: After schema/main.wasp changes
- React 19 bug: Use
safe-start.sh --clean, NOTwasp cleandirectly
Philosophy: Catch issues early (pre-commit) → Comprehensive check before sharing (pre-push) → CI validates (final gate).
Repository
