Code Quality
Seed maintains high code quality standards through automated tools and conventions. This guide covers linting, formatting, type checking, and best practices.
Overview
| Tool | Purpose | Configuration |
|---|---|---|
| TypeScript | Type safety and modern JavaScript features | tsconfig.json |
| ESLint | Code linting and error detection | eslint.config.js |
| Prettier | Code formatting and style consistency | .prettierrc |
TypeScript
Configuration
File: tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}Strict Mode Features
strict: true enables:
strictNullChecks- No implicit null/undefinedstrictFunctionTypes- Strict function type checkingstrictBindCallApply- Strict bind/call/applystrictPropertyInitialization- Properties must be initializednoImplicitAny- No implicit any typesnoImplicitThis- No implicit thisalwaysStrict- Use strict mode in all files
Additional strict checks:
noUnusedLocals- Error on unused local variablesnoUnusedParameters- Error on unused parametersnoImplicitReturns- All code paths must returnnoFallthroughCasesInSwitch- No switch fallthroughexactOptionalPropertyTypes- Optional properties cannot be undefinednoUncheckedIndexedAccess- Array/object access returns T | undefined
Module System
Uses ES Modules (ESM) with .js extensions:
// Correct: .js extension required
import { config } from "./config/index.js";
// Incorrect: TypeScript will error
import { config } from "./config/index";Why .js extensions?
- Node.js requires extensions for ESM
- TypeScript compiles
.ts→.js - Import paths must reference the runtime file extension
Type Safety Best Practices
1. Avoid any
// Bad
function process(data: any) {
return data.value;
}
// Good
interface Data {
value: string;
}
function process(data: Data): string {
return data.value;
}2. Use type guards
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(input: unknown) {
if (isString(input)) {
console.log(input.toUpperCase()); // TypeScript knows input is string
}
}3. Handle array access safely
// With noUncheckedIndexedAccess
const items = ["a", "b", "c"];
const first = items[0]; // Type: string | undefined
// Must handle undefined
if (first) {
console.log(first.toUpperCase());
}4. Use strict null checks
interface User {
name: string;
email?: string; // Explicitly optional
}
function getEmail(user: User): string {
// Error: email might be undefined
// return user.email;
// Correct: handle undefined
return user.email ?? "no-email@example.com";
}Running Type Checks
# Check types without emitting files
npm run typecheck
# Check and emit files
npm run buildESLint
Configuration
File: eslint.config.js
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
prettier,
{
ignores: ["dist/**", "node_modules/**", "coverage/**"],
},
{
files: ["src/**/*.ts"],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-import-type-side-effects": "error",
},
},
{
files: ["src/**/*.test.ts"],
rules: {
"@typescript-eslint/unbound-method": "off",
},
}
);Key Rules
Unused variables:
// Error: 'unused' is defined but never used
const unused = "value";
// OK: Prefixed with underscore
function handler(_req: Request, res: Response) {
// _req is intentionally unused
res.send("ok");
}Consistent type imports:
// Error: Use type import
import { Request } from "express";
// Correct: Type-only import
import type { Request } from "express";No import side effects:
// Error: Mixing type and value imports
import { type Request, Router } from "express";
// Correct: Separate imports
import type { Request } from "express";
import { Router } from "express";Running ESLint
# Lint all source files
npm run lint
# Lint specific file
npx eslint src/routes/health.ts
# Auto-fix issues
npx eslint src/routes/health.ts --fixDisabling Rules
Inline disable (use sparingly):
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function legacy(data: any) {
// ...
}
/* eslint-disable @typescript-eslint/no-floating-promises */
void someAsyncFunction();
/* eslint-enable @typescript-eslint/no-floating-promises */Prettier
Configuration
File: .prettierrc
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"endOfLine": "lf"
}Style Guidelines
Semicolons: Required
const value = "test"; // ✓
const value = "test" // ✗Quotes: Double quotes
const name = "value"; // ✓
const name = 'value'; // ✗Tab width: 2 spaces
function example() {
if (true) {
return "formatted"; // 2 spaces per level
}
}Trailing commas: Always
const obj = {
a: 1,
b: 2, // ✓ Trailing comma
};
const arr = [
"a",
"b", // ✓ Trailing comma
];Line width: 100 characters
// ✓ Under 100 chars
const short = "This line is short enough to fit";
// ✗ Over 100 chars - Prettier will break it
const long = "This is a very long line that exceeds one hundred characters and should be broken";Running Prettier
# Format all source files
npm run format
# Check formatting without changing files
npm run format:check
# Format specific file
npx prettier --write src/routes/health.tsIDE Integration
VS Code (.vscode/settings.json):
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}WebStorm:
- Settings → Languages & Frameworks → JavaScript → Prettier
- Check "On save" and "On code reformat"
Validation Workflow
Pre-Commit Checklist
Before committing code:
# Run full validation
npm run validateThis runs:
- Prettier (
npm run format) - Format code - ESLint (
npm run lint) - Lint code - TypeScript (
npm run typecheck) - Check types - Tests (
npm run test:coverage) - Run tests with coverage
CI/CD Validation
The same validation runs in GitLab CI:
# .gitlab-ci.yml (example)
validate:
script:
- npm run validateCommon Issues
1. Import Extension Errors
Error:
Relative import paths need explicit file extensions in ESM importsSolution:
// Wrong
import { config } from "./config";
// Correct
import { config } from "./config.js";2. Type vs Value Imports
Error:
'Request' is a type and must be imported using a type-only importSolution:
// Wrong
import { Request } from "express";
// Correct
import type { Request } from "express";3. Unused Variables
Error:
'value' is defined but never usedSolutions:
// Option 1: Remove unused variable
// const value = "unused"; // Remove this line
// Option 2: Use underscore prefix
const _value = "intentionally unused";
// Option 3: Disable rule (rare cases)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const value = "required by interface";4. Implicit Any
Error:
Parameter 'data' implicitly has an 'any' typeSolution:
// Wrong
function process(data) {
return data;
}
// Correct
function process(data: unknown) {
// Use type guards to narrow type
if (typeof data === "string") {
return data.toUpperCase();
}
return data;
}5. Null/Undefined Checks
Error:
Object is possibly 'undefined'Solution:
const items = ["a", "b", "c"];
// Wrong
console.log(items[10].toUpperCase()); // Error
// Correct: Optional chaining
console.log(items[10]?.toUpperCase());
// Correct: Explicit check
const item = items[10];
if (item) {
console.log(item.toUpperCase());
}
// Correct: Nullish coalescing
const value = items[10] ?? "default";Code Review Guidelines
What to Look For
1. Type safety:
- No
anytypes (useunknowninstead) - Proper type annotations on functions
- Type guards for runtime validation
2. Error handling:
- Try-catch for async operations
- Meaningful error messages
- Proper error types
3. Code organization:
- Single responsibility per function
- Clear naming conventions
- Appropriate file organization
4. Testing:
- Unit tests for new functions
- Integration tests for new endpoints
- Edge cases covered
5. Documentation:
- JSDoc comments for complex functions
- README updates for new features
- API documentation for new endpoints
Naming Conventions
Variables and functions: camelCase
const userName = "test";
function getUserById(id: string) {}Classes and interfaces: PascalCase
class UserService {}
interface UserData {}Constants: UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const DEFAULT_TIMEOUT_MS = 5000;Files: kebab-case
user-service.ts
auth-middleware.tsType imports: Prefix with type keyword
import type { Request, Response } from "express";Best Practices
1. Prefer Const
// Prefer const over let
const value = "immutable";
// Use let only when reassignment is needed
let counter = 0;
counter++;2. Avoid Mutation
// Bad: Mutation
function addItem(arr: string[], item: string) {
arr.push(item);
return arr;
}
// Good: Immutable
function addItem(arr: string[], item: string): string[] {
return [...arr, item];
}3. Use Template Literals
// Bad: String concatenation
const message = "Hello, " + name + "!";
// Good: Template literal
const message = `Hello, ${name}!`;4. Destructuring
// Bad: Accessing properties repeatedly
function formatUser(user) {
return `${user.name} (${user.email})`;
}
// Good: Destructuring
function formatUser({ name, email }: User): string {
return `${name} (${email})`;
}5. Optional Chaining
// Bad: Nested checks
const email = user && user.contact && user.contact.email;
// Good: Optional chaining
const email = user?.contact?.email;6. Nullish Coalescing
// Bad: Logical OR (falsy values)
const port = config.port || 3000; // 0 would fallback to 3000
// Good: Nullish coalescing (null/undefined only)
const port = config.port ?? 3000; // 0 is validNext Steps
- Adding MCP Tools - Create quality MCP tools
- Contributing Guide - Submit quality code
- Testing Guide - Test your code