mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:41:09 +02:00
Run prettier --write to fix formatting inconsistencies in 80 files across calendar, contacts, picture, presi, storage, zitare apps and shared packages/documentation.
312 lines
8.2 KiB
Markdown
312 lines
8.2 KiB
Markdown
# Code Style Guidelines
|
|
|
|
## Formatting
|
|
|
|
### Prettier Configuration
|
|
|
|
All projects use the root `.prettierrc.json`:
|
|
|
|
```json
|
|
{
|
|
"useTabs": true,
|
|
"singleQuote": true,
|
|
"trailingComma": "es5",
|
|
"printWidth": 100,
|
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-astro"]
|
|
}
|
|
```
|
|
|
|
### Key Rules
|
|
|
|
- **Tabs** for indentation (not spaces)
|
|
- **Single quotes** for strings
|
|
- **Trailing commas** in ES5-compatible positions
|
|
- **100 character** line width
|
|
- **Semicolons** required
|
|
|
|
## Naming Conventions
|
|
|
|
### Files & Directories
|
|
|
|
| Type | Convention | Example |
|
|
| -------------------- | ----------------- | ------------------------------------------------ |
|
|
| **Components** | PascalCase | `MessageBubble.svelte`, `ChatInput.tsx` |
|
|
| **Services** | kebab-case | `auth.service.ts`, `user-credits.service.ts` |
|
|
| **Schemas** | kebab-case | `users.schema.ts`, `batch-generations.schema.ts` |
|
|
| **Utilities** | kebab-case | `format-date.ts`, `string-utils.ts` |
|
|
| **Types/Interfaces** | kebab-case | `user.types.ts`, `api-response.ts` |
|
|
| **Constants** | kebab-case | `error-codes.ts`, `config.ts` |
|
|
| **Test files** | `.spec.ts` suffix | `auth.service.spec.ts` |
|
|
|
|
### Code Identifiers
|
|
|
|
| Type | Convention | Example |
|
|
| ------------------ | ------------------------------------------------ | ---------------------------------- |
|
|
| **Classes** | PascalCase | `UserService`, `AuthController` |
|
|
| **Interfaces** | PascalCase | `UserData`, `CreateEventDto` |
|
|
| **Type aliases** | PascalCase | `Result<T>`, `ErrorCode` |
|
|
| **Functions** | camelCase | `findById`, `createUser` |
|
|
| **Variables** | camelCase | `userId`, `isLoading` |
|
|
| **Constants** | SCREAMING_SNAKE_CASE | `MAX_FILE_SIZE`, `DEFAULT_TIMEOUT` |
|
|
| **Enums** | PascalCase (type), SCREAMING_SNAKE_CASE (values) | `ErrorCode.NOT_FOUND` |
|
|
| **Private fields** | camelCase (no underscore prefix) | `private db: Database` |
|
|
|
|
### Database Naming
|
|
|
|
| Type | Convention | Example |
|
|
| ---------------- | ---------------------- | ------------------------------- |
|
|
| **Tables** | snake_case, plural | `users`, `user_sessions` |
|
|
| **Columns** | snake_case | `user_id`, `created_at` |
|
|
| **Foreign keys** | `{entity}_id` | `user_id`, `folder_id` |
|
|
| **Booleans** | `is_` or `has_` prefix | `is_deleted`, `has_password` |
|
|
| **Timestamps** | `_at` suffix | `created_at`, `deleted_at` |
|
|
| **Indexes** | `idx_` prefix | `idx_user_id`, `idx_created_at` |
|
|
|
|
## TypeScript
|
|
|
|
### Strict Mode
|
|
|
|
All projects use strict TypeScript:
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"strict": true,
|
|
"noImplicitAny": true,
|
|
"strictNullChecks": true,
|
|
"noUncheckedIndexedAccess": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Type Annotations
|
|
|
|
```typescript
|
|
// GOOD - Explicit return types for public APIs
|
|
async function findById(id: string): Promise<Result<User>> {
|
|
// ...
|
|
}
|
|
|
|
// GOOD - Interface for complex objects
|
|
interface CreateUserDto {
|
|
email: string;
|
|
name: string;
|
|
password: string;
|
|
}
|
|
|
|
// BAD - Avoid `any`
|
|
function process(data: any) {} // Never do this
|
|
|
|
// GOOD - Use `unknown` when type is truly unknown
|
|
function process(data: unknown) {
|
|
if (isUser(data)) {
|
|
// Now TypeScript knows it's a User
|
|
}
|
|
}
|
|
```
|
|
|
|
### Imports
|
|
|
|
```typescript
|
|
// Order: external → internal → relative
|
|
import { Injectable } from '@nestjs/common'; // 1. External
|
|
import { Result, ErrorCode } from '@manacore/shared-errors'; // 2. Internal packages
|
|
import { UserService } from '../services/user.service'; // 3. Relative
|
|
|
|
// Use named exports (not default)
|
|
export { UserService }; // GOOD
|
|
export default UserService; // AVOID
|
|
|
|
// Use type-only imports for types
|
|
import type { User } from './user.types';
|
|
```
|
|
|
|
## ESLint Rules
|
|
|
|
### Critical Rules (Errors)
|
|
|
|
```javascript
|
|
{
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"@typescript-eslint/explicit-function-return-type": "error", // For public APIs
|
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
|
"no-console": ["error", { "allow": ["warn", "error"] }],
|
|
}
|
|
```
|
|
|
|
### Recommended Rules (Warnings)
|
|
|
|
```javascript
|
|
{
|
|
"@typescript-eslint/no-floating-promises": "warn",
|
|
"@typescript-eslint/await-thenable": "warn",
|
|
"prefer-const": "warn",
|
|
}
|
|
```
|
|
|
|
## Comments
|
|
|
|
### When to Comment
|
|
|
|
```typescript
|
|
// GOOD - Explain WHY, not WHAT
|
|
// We use optimistic locking here because concurrent credit operations
|
|
// could otherwise result in race conditions and incorrect balances
|
|
const [updated] = await this.db
|
|
.update(balances)
|
|
.set({ amount: newAmount, version: sql`version + 1` })
|
|
.where(and(eq(balances.userId, userId), eq(balances.version, currentVersion)))
|
|
.returning();
|
|
|
|
// BAD - Explaining obvious code
|
|
// Loop through users
|
|
for (const user of users) {
|
|
}
|
|
|
|
// BAD - Outdated comment
|
|
// Returns the user's email <-- but function now returns full user object
|
|
function getUser() {}
|
|
```
|
|
|
|
### JSDoc for Public APIs
|
|
|
|
```typescript
|
|
/**
|
|
* Consumes credits from a user's balance.
|
|
*
|
|
* @param userId - The user's unique identifier
|
|
* @param amount - Number of credits to consume
|
|
* @param reason - Human-readable reason for the charge
|
|
* @returns Result with the updated balance or an error
|
|
*
|
|
* @example
|
|
* const result = await creditsService.consume(userId, 10, 'AI generation');
|
|
* if (!result.ok) {
|
|
* logger.error('Credit consumption failed', result.error);
|
|
* }
|
|
*/
|
|
async consume(userId: string, amount: number, reason: string): Promise<Result<Balance>> {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Code Organization
|
|
|
|
### File Size
|
|
|
|
- **Maximum**: ~300 lines per file
|
|
- **Ideal**: 100-200 lines
|
|
- Split large files into focused modules
|
|
|
|
### Function Size
|
|
|
|
- **Maximum**: ~50 lines per function
|
|
- **Ideal**: 10-25 lines
|
|
- Extract complex logic into helper functions
|
|
|
|
### Module Structure (NestJS)
|
|
|
|
```
|
|
feature/
|
|
├── feature.controller.ts # HTTP layer
|
|
├── feature.service.ts # Business logic
|
|
├── feature.module.ts # DI configuration
|
|
├── feature.spec.ts # Tests
|
|
└── dto/
|
|
├── create-feature.dto.ts
|
|
└── update-feature.dto.ts
|
|
```
|
|
|
|
### Component Structure (Svelte/React)
|
|
|
|
```
|
|
components/
|
|
├── feature/
|
|
│ ├── FeatureList.svelte # Container component
|
|
│ ├── FeatureItem.svelte # Presentational component
|
|
│ └── feature.types.ts # Shared types
|
|
└── ui/
|
|
├── Button.svelte # Reusable UI
|
|
└── Input.svelte
|
|
```
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
### 1. Magic Numbers/Strings
|
|
|
|
```typescript
|
|
// BAD
|
|
if (user.role === 'admin') {
|
|
}
|
|
if (credits < 10) {
|
|
}
|
|
|
|
// GOOD
|
|
const ROLES = { ADMIN: 'admin', USER: 'user' } as const;
|
|
const MIN_CREDITS_FOR_OPERATION = 10;
|
|
|
|
if (user.role === ROLES.ADMIN) {
|
|
}
|
|
if (credits < MIN_CREDITS_FOR_OPERATION) {
|
|
}
|
|
```
|
|
|
|
### 2. Nested Callbacks
|
|
|
|
```typescript
|
|
// BAD
|
|
getUser(id, (user) => {
|
|
getCredits(user.id, (credits) => {
|
|
updateBalance(credits, (result) => {
|
|
// ...
|
|
});
|
|
});
|
|
});
|
|
|
|
// GOOD
|
|
const user = await getUser(id);
|
|
const credits = await getCredits(user.id);
|
|
const result = await updateBalance(credits);
|
|
```
|
|
|
|
### 3. Mutating Parameters
|
|
|
|
```typescript
|
|
// BAD
|
|
function processUser(user: User): void {
|
|
user.name = user.name.trim(); // Mutates input
|
|
}
|
|
|
|
// GOOD
|
|
function processUser(user: User): User {
|
|
return { ...user, name: user.name.trim() }; // Returns new object
|
|
}
|
|
```
|
|
|
|
### 4. Boolean Trap
|
|
|
|
```typescript
|
|
// BAD - What does `true` mean?
|
|
createUser(email, password, true, false);
|
|
|
|
// GOOD - Use options object
|
|
createUser({
|
|
email,
|
|
password,
|
|
sendWelcomeEmail: true,
|
|
requireEmailVerification: false,
|
|
});
|
|
```
|
|
|
|
## Formatting Commands
|
|
|
|
```bash
|
|
# Format all files
|
|
pnpm format
|
|
|
|
# Check formatting without changes
|
|
pnpm format:check
|
|
|
|
# Format specific project
|
|
pnpm --filter @chat/backend format
|
|
```
|