| name | tzurot-code-quality |
| description | Use when fixing lint warnings, refactoring complex functions, or understanding ESLint rules. Covers complexity limits, refactoring patterns, and when to suppress rules. |
| lastUpdated | 2025-12-31 |
Code Quality & Linting
Use this skill when: Fixing lint warnings, hitting complexity limits, refactoring large functions, or understanding ESLint rule philosophy.
Quick Reference
# Run linting
pnpm lint # Check all
pnpm lint:fix # Auto-fix
# Check specific service
pnpm --filter @tzurot/api-gateway lint
ESLint Limits (eslint.config.js)
| Rule | Limit | Level | Fix Strategy |
|---|---|---|---|
max-lines |
500 | Error | Split into modules |
max-lines-per-function |
100 | Warn | Extract helpers |
complexity |
15 | Warn | Data-driven approach |
max-depth |
4 | Warn | Early returns, extract |
max-params |
5 | Warn | Options object pattern |
max-statements |
30 | Warn | Extract helpers |
max-nested-callbacks |
3 | Warn | Use async/await |
Refactoring Patterns
Options Object Pattern (max-params fix)
// ❌ BAD - 6 parameters
function processMatch(
ctx: MatchContext,
fullMatch: string,
persona: ResolvedPersona | null,
logContext: Record<string, unknown>,
refType: string,
fallbackName?: string
): MatchResult { ... }
// ✅ GOOD - Options object
interface ProcessMatchOptions {
ctx: MatchContext;
fullMatch: string;
persona: ResolvedPersona | null;
logContext: Record<string, unknown>;
refType: string;
fallbackName?: string;
}
function processMatch(opts: ProcessMatchOptions): MatchResult {
const { ctx, fullMatch, persona, logContext, refType, fallbackName } = opts;
...
}
Data-Driven Approach (complexity fix)
// ❌ BAD - High cyclomatic complexity from repeated if/else
function formatField(personality: Personality): string {
let result = '';
if (personality.characterInfo) {
result += `<character_info>${personality.characterInfo}</character_info>`;
}
if (personality.personalityTraits) {
result += `<personality_traits>${personality.personalityTraits}</personality_traits>`;
}
// ... 7 more similar blocks = complexity 10+
}
// ✅ GOOD - Data-driven, complexity stays at 2
const PERSONALITY_FIELDS = [
{ key: 'characterInfo', tag: 'character_info' },
{ key: 'personalityTraits', tag: 'personality_traits' },
// ...
] as const;
function formatField(personality: Personality): string {
return PERSONALITY_FIELDS.map(({ key, tag }) => {
const value = personality[key];
return value ? `<${tag}>${value}</${tag}>` : '';
})
.filter(Boolean)
.join('\n');
}
Helper Extraction (max-statements fix)
// ❌ BAD - 40+ statements in one function
async function handleRequest(req: Request): Promise<Response> {
// validation (10 statements)
// business logic (15 statements)
// response formatting (10 statements)
// error handling (5 statements)
}
// ✅ GOOD - Split into focused helpers
async function handleRequest(req: Request): Promise<Response> {
const validated = validateRequest(req); // 10 statements extracted
const result = await processRequest(validated); // 15 statements extracted
return formatResponse(result); // 10 statements extracted
}
Early Return Pattern (max-depth fix)
// ❌ BAD - Deep nesting
function process(data: Data | null): Result {
if (data !== null) {
if (data.isValid) {
if (data.items.length > 0) {
// actual logic at depth 4
}
}
}
}
// ✅ GOOD - Early returns, flat structure
function process(data: Data | null): Result {
if (data === null) return defaultResult;
if (!data.isValid) return invalidResult;
if (data.items.length === 0) return emptyResult;
// actual logic at depth 1
}
TypeScript Strict Rules
| Rule | Level | Alternative |
|---|---|---|
no-explicit-any |
Error | Use unknown + type guards |
no-unsafe-assignment |
Error | Validate with Zod |
no-non-null-assertion |
Warn | Use optional chaining + nullish coalescing |
strict-boolean-expressions |
Error | Be explicit: !== null, !== undefined |
// ❌ BAD
const data = response.json() as MyType;
if (data) { ... }
// ✅ GOOD
const data: unknown = await response.json();
const validated = MyTypeSchema.parse(data);
if (validated !== null) { ... }
When to Suppress Rules
OK to Suppress
// Generated code or external types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type PrismaPayload = any;
// Test utilities with intentional complexity
// eslint-disable-next-line max-lines-per-function
function createComplexTestFixture() { ... }
// One-off scripts (not production code)
/* eslint-disable complexity */
Never Suppress
- Production business logic
- API route handlers
- Core services
- Security-related code
If you need to suppress, ask: "Should I refactor instead?"
Pino Logger Format (ESLint Enforced)
Custom ESLint rule enforces correct pino format:
// ✅ CORRECT - Error object in first argument
logger.error({ err: error }, 'Failed to process request');
logger.warn({ err: error, userId }, 'User quota exceeded');
logger.info({ requestId, duration }, 'Request completed');
// ❌ WRONG - Will fail lint
logger.error(error, 'Failed to process');
logger.error('Failed:', error);
Error Handling Best Practices
Current Pattern (callGatewayApi)
const result = await callGatewayApi<PersonaResponse>('/user/persona', { userId });
if (!result.ok) {
logger.warn({ error: result.error, status: result.status }, 'API call failed');
await interaction.editReply(`❌ ${result.error}`);
return;
}
// result.data is typed correctly here
Aspirational: Result Pattern for New Services
For complex domain logic, consider typed error returns:
type Result<T, E = string> = { ok: true; data: T } | { ok: false; error: E };
// Explicit error types at compile time
type GetUserError = 'NOT_FOUND' | 'FORBIDDEN' | 'INVALID_ID';
async function getUser(id: string): Promise<Result<User, GetUserError>> {
if (!isValidId(id)) return { ok: false, error: 'INVALID_ID' };
const user = await prisma.user.findUnique({ where: { id } });
if (!user) return { ok: false, error: 'NOT_FOUND' };
return { ok: true, data: user };
}
Common Lint Fixes
Promise Handling
// ❌ Floating promise
someAsyncFunction();
// ✅ Explicit handling
await someAsyncFunction();
void someAsyncFunction(); // Fire-and-forget (intentional)
Boolean Expressions
// ❌ Implicit boolean coercion
if (user) { ... }
if (items.length) { ... }
// ✅ Explicit checks
if (user !== null && user !== undefined) { ... }
if (items.length > 0) { ... }
Unused Variables
// ❌ Unused
const result = await fetch();
// ✅ Prefix with underscore if intentionally unused
const _result = await fetch();
Related Skills
- tzurot-testing - Coverage requirements, test patterns
- tzurot-architecture - Service boundaries, SRP
- tzurot-git-workflow - Pre-push checks run lint
- tzurot-types - Zod validation, type safety
References
- ESLint config:
eslint.config.js - TypeScript config:
tsconfig.json - Prettier config:
.prettierrc