Development Guides Code Styles - aku11i/phantom GitHub Wiki
This guide documents Phantom's coding conventions and style guidelines. Following these ensures consistency across the codebase.
Source: CLAUDE.md#L10
- No Code Comments - Write self-documenting code
- Follow Existing Patterns - Consistency over personal preference
- English Only - All code, commits, and documentation in English
- Type Safety - Leverage TypeScript fully
Source: biome.json
Phantom uses Biome for formatting and linting:
{
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf",
"quoteStyle": "double",
"trailingComma": "all",
"semicolons": "always"
}
}
- Use tabs for indentation
- Tab width displays as 2 spaces
// ✓ Good
function example() {
if (condition) {
doSomething();
}
}
// ✗ Bad - spaces
function example() {
if (condition) {
doSomething();
}
}
- Use double quotes for strings
// ✓ Good
const message = "Hello, world!";
const html = `<div class="container">`;
// ✗ Bad
const message = 'Hello, world!';
- Always use semicolons
// ✓ Good
const name = "phantom";
import { Result } from "./types.js";
// ✗ Bad
const name = "phantom"
import { Result } from "./types.js"
- Use trailing commas in multi-line structures
// ✓ Good
const config = {
name: "phantom",
version: "1.0.0",
author: "aku11i",
};
// ✗ Bad
const config = {
name: "phantom",
version: "1.0.0",
author: "aku11i"
};
Always use explicit types for function parameters and return values:
// ✓ Good
function createWorktree(name: string, branch?: string): Promise<Result<WorktreeInfo>> {
// Implementation
}
// ✗ Bad - missing types
function createWorktree(name, branch) {
// Implementation
}
Use interface
for object shapes, type
for unions and primitives:
// ✓ Good - interface for objects
interface WorktreeInfo {
name: string;
path: string;
branch: string;
isMain: boolean;
}
// ✓ Good - type for unions
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
// ✗ Bad - type for simple objects
type WorktreeInfo = {
name: string;
// ...
};
Use as const
for literal types:
// ✓ Good
const COMMANDS = ["create", "delete", "list", "exec", "shell"] as const;
type Command = typeof COMMANDS[number];
// ✗ Bad
const COMMANDS = ["create", "delete", "list", "exec", "shell"];
Source: src/cli/handlers/create.ts
Order imports consistently:
// 1. Node.js built-ins
import { existsSync } from "node:fs";
import { join } from "node:path";
// 2. External dependencies (none in runtime)
// 3. Internal - absolute paths
import { Result } from "../../core/types/result.js";
import { createWorktree } from "../../core/worktree/create.js";
// 4. Internal - relative paths
import { output } from "../output.js";
Always include .js
extension in imports:
// ✓ Good - ESM requires extensions
import { createWorktree } from "./create.js";
// ✗ Bad - will fail at runtime
import { createWorktree } from "./create";
Use named exports, avoid default exports:
// ✓ Good - named exports
export function createWorktree() { }
export function deleteWorktree() { }
// ✗ Bad - default export
export default function createWorktree() { }
Use camelCase:
// ✓ Good
const worktreeName = "feature";
function validateWorktreeName(name: string) { }
// ✗ Bad
const worktree_name = "feature";
function ValidateWorktreeName(name: string) { }
Use UPPER_SNAKE_CASE for true constants:
// ✓ Good
const MAX_NAME_LENGTH = 50;
const DEFAULT_BRANCH = "main";
// ✗ Bad
const maxNameLength = 50;
const default_branch = "main";
Use PascalCase:
// ✓ Good
interface WorktreeInfo { }
type CommandHandler = () => void;
// ✗ Bad
interface worktreeInfo { }
type commandHandler = () => void;
Use kebab-case for file names:
✓ Good:
- create-worktree.ts
- git-executor.ts
- list-worktrees.test.ts
✗ Bad:
- createWorktree.ts
- GitExecutor.ts
- listWorktrees.test.ts
Keep functions short and focused:
// ✓ Good - single responsibility
function validateWorktreeName(name: string): Result<void> {
if (!name) {
return Result.error(new Error("Name is required"));
}
if (name.includes("/") || name.includes("..")) {
return Result.error(new Error("Invalid characters in name"));
}
return Result.ok();
}
// ✗ Bad - doing too much
function createAndValidateWorktree(name: string, showOutput: boolean) {
// Validation logic
// Creation logic
// Output logic
// Too many responsibilities
}
Use early returns to reduce nesting:
// ✓ Good - early returns
function processWorktree(name: string): Result<void> {
if (!name) {
return Result.error(new Error("Name required"));
}
if (invalidCharacters(name)) {
return Result.error(new Error("Invalid name"));
}
// Main logic
return Result.ok();
}
// ✗ Bad - nested conditions
function processWorktree(name: string): Result<void> {
if (name) {
if (!invalidCharacters(name)) {
// Main logic
return Result.ok();
} else {
return Result.error(new Error("Invalid name"));
}
} else {
return Result.error(new Error("Name required"));
}
}
Prefer pure functions without side effects:
// ✓ Good - pure function
function calculatePath(basePath: string, name: string): string {
return join(basePath, name);
}
// ✗ Bad - side effects
let globalPath: string;
function calculatePath(basePath: string, name: string): void {
globalPath = join(basePath, name); // Side effect
}
Always use Result type for operations that can fail:
// ✓ Good - explicit error handling
function parseConfig(content: string): Result<Config> {
try {
const config = JSON.parse(content);
return Result.ok(config);
} catch (error) {
return Result.error(new Error(`Invalid config: ${error.message}`));
}
}
// ✗ Bad - throwing exceptions
function parseConfig(content: string): Config {
return JSON.parse(content); // Throws on error
}
Make error messages helpful:
// ✓ Good - descriptive error
return Result.error(
new Error(`Worktree '${name}' already exists at ${path}`)
);
// ✗ Bad - vague error
return Result.error(new Error("Error"));
Prefer async/await over promises:
// ✓ Good
async function loadConfig(): Promise<Result<Config>> {
const result = await readFile("config.json");
if (!result.ok) {
return result;
}
return parseConfig(result.value);
}
// ✗ Bad - promise chains
function loadConfig(): Promise<Result<Config>> {
return readFile("config.json")
.then(result => {
if (!result.ok) {
return result;
}
return parseConfig(result.value);
});
}
Handle errors properly in async functions:
// ✓ Good
async function execute(): Promise<Result<void>> {
try {
await riskyOperation();
return Result.ok();
} catch (error) {
return Result.error(error as Error);
}
}
// ✗ Bad - unhandled rejection
async function execute(): Promise<void> {
await riskyOperation(); // Can throw
}
Each module should have one clear purpose:
// ✓ Good - focused modules
// validate.ts - only validation
export function validateWorktreeName(name: string): Result<void> { }
// create.ts - only creation
export function createWorktree(name: string): Result<WorktreeInfo> { }
// ✗ Bad - mixed responsibilities
// worktree.ts - doing everything
export function validateAndCreateWorktree() { }
export function formatWorktreeOutput() { }
export function deleteWorktreeWithLogging() { }
Dependencies should flow downward:
✓ Good:
CLI → Core → Infrastructure
✗ Bad:
Core → CLI (upward dependency)
Follow the Arrange-Act-Assert pattern:
it("should create worktree with branch", async () => {
// Arrange
const name = "feature";
const branch = "develop";
mockGit.execute.mockReturnValue(Result.ok(""));
// Act
const result = await createWorktree(name, branch);
// Assert
assert.strictEqual(result.ok, true);
if (result.ok) {
assert.strictEqual(result.value.branch, branch);
}
});
Use descriptive test names:
// ✓ Good
describe("validateWorktreeName", () => {
it("should accept alphanumeric names with hyphens");
it("should reject names with path separators");
it("should reject empty names");
});
// ✗ Bad
describe("validate", () => {
it("works");
it("fails");
});
Write self-documenting code instead of comments:
// ✗ Bad - needs comment
// Check if name contains invalid characters
if (n.indexOf("/") >= 0 || n.indexOf("..") >= 0) {
return false;
}
// ✓ Good - self-documenting
const containsPathSeparator = name.includes("/");
const containsParentReference = name.includes("..");
if (containsPathSeparator || containsParentReference) {
return Result.error(new Error("Name cannot contain path separators"));
}
Use descriptive function names:
// ✓ Good
function isValidWorktreeName(name: string): boolean { }
function getWorktreeByName(name: string): Result<WorktreeInfo> { }
// ✗ Bad
function check(n: string): boolean { }
function get(n: string): Result<WorktreeInfo> { }
Follow conventional commits:
✓ Good:
feat: add support for custom worktree paths
fix: handle spaces in repository names
docs: update README with new commands
test: add tests for error scenarios
refactor: extract git operations to separate module
✗ Bad:
Updated stuff
Fix
Changes
WIP
Keep commits focused:
✓ Good:
- One logical change per commit
- All tests pass
- No unrelated changes
✗ Bad:
- Multiple unrelated changes
- Broken tests
- Debug code included
Source: biome.json
Key linting rules:
{
"linter": {
"rules": {
"recommended": true,
"style": {
"noParameterAssign": "error",
"noVar": "error",
"useConst": "error",
"useTemplate": "warn"
},
"complexity": {
"noForEach": "off",
"useOptionalChain": "warn"
},
"correctness": {
"noUnusedVariables": "error",
"noUnusedImports": "error"
}
}
}
}
# Check code
pnpm lint
# Auto-fix issues
pnpm fix
# Check specific file
npx biome check src/core/worktree/create.ts
- Consistency - Follow existing patterns
- Clarity - Write readable, self-documenting code
- Simplicity - Avoid clever code, prefer obvious solutions
- Type Safety - Use TypeScript features fully
- Error Handling - Use Result types consistently
- Testing - Write tests for all new code
- No Comments - If code needs comments, refactor it
Remember: Code is read far more often than it's written. Optimize for readability.