Development Guides Code Styles - hiraishikentaro/rails-factorybot-jump GitHub Wiki

Development Guides: Code Styles

Code Style Overview

Rails FactoryBot Jump follows consistent TypeScript coding conventions to maintain code readability, maintainability, and team collaboration. The project uses ESLint for automated code quality enforcement.

TypeScript Style Guidelines

1. Naming Conventions

Classes: PascalCase

// Good
class FactoryLinkProvider implements vscode.DocumentLinkProvider {
  // ...
}

class CacheManager {
  // ...
}

// Avoid
class factoryLinkProvider {
  // ...
}

Methods and Variables: camelCase

// Good
private factoryCache: Map<string, FactoryDefinition>;
private async initializeFactoryFiles(): Promise<void> {
  // ...
}

// Avoid
private factory_cache: Map<string, FactoryDefinition>;
private async initialize_factory_files(): Promise<void> {
  // ...
}

Constants: SCREAMING_SNAKE_CASE

// Good
const DEFAULT_FACTORY_PATH = "spec/factories/**/*.rb";
const MAX_CACHE_SIZE = 1000;

// Avoid
const defaultFactoryPath = "spec/factories/**/*.rb";

Interfaces: PascalCase with descriptive names

// Good
interface FactoryDefinition {
  uri: vscode.Uri;
  lineNumber: number;
}

interface TraitDefinition extends FactoryDefinition {
  factory: string;
}

// Avoid
interface IFactoryDefinition {
  // Hungarian notation not used
}

Source: src/providers/factoryLinkProvider.ts

2. Function Declaration Style

Async Functions: Always explicitly declare async

// Good
async initializeFactoryFiles(): Promise<void> {
  await this.buildCache();
}

// Good for arrow functions
const processFile = async (uri: vscode.Uri): Promise<void> => {
  await vscode.workspace.openTextDocument(uri);
};

// Avoid
initializeFactoryFiles() {
  return this.buildCache(); // Implicit Promise return
}

Method Visibility: Be explicit about access modifiers

// Good
export class FactoryLinkProvider {
  public async initializeFactoryFiles(): Promise<void> {
    // Public interface
  }

  private async cacheFactoryDefinitions(): Promise<void> {
    // Internal implementation
  }

  protected getConfiguration(): vscode.WorkspaceConfiguration {
    // For potential subclassing
  }
}

3. Type Annotations

Explicit Return Types: Always specify return types for public methods

// Good
provideDocumentLinks(document: vscode.TextDocument): vscode.DocumentLink[] {
  return this.generateLinks(document);
}

// Good for complex types
private buildFactoryCache(): Map<string, FactoryDefinition> {
  const cache = new Map<string, FactoryDefinition>();
  return cache;
}

// Avoid
provideDocumentLinks(document: vscode.TextDocument) {
  return this.generateLinks(document); // Inferred return type
}

Parameter Types: Always specify parameter types

// Good
private createDocumentLink(
  range: vscode.Range,
  factoryName: string,
  lineNumber: number
): vscode.DocumentLink {
  // Implementation
}

// Avoid
private createDocumentLink(range, factoryName, lineNumber) {
  // No type information
}

Code Organization

1. Import Organization

Import Order:

// 1. Node.js built-in modules
import * as path from "path";
import * as fs from "fs";

// 2. External dependencies
import * as vscode from "vscode";

// 3. Internal modules (relative imports)
import { FactoryDefinition } from "./types";
import { ConfigurationManager } from "../utils/config";

Import Style:

// Good: Specific imports
import { Uri, TextDocument, DocumentLink } from "vscode";

// Good: Namespace import for many items
import * as vscode from "vscode";

// Avoid: Mix of specific and namespace
import vscode, { Uri, TextDocument } from "vscode";

2. Class Structure

Consistent Class Organization:

export class FactoryLinkProvider implements vscode.DocumentLinkProvider {
  // 1. Private fields
  private factoryCache: Map<string, FactoryDefinition> = new Map();
  private traitCache: Map<string, TraitDefinition> = new Map();
  private isInitialized = false;

  // 2. Constructor
  constructor() {
    this.initializeFactoryFiles();
  }

  // 3. Public interface methods
  public async provideDocumentLinks(
    document: vscode.TextDocument
  ): Promise<vscode.DocumentLink[]> {
    // Implementation
  }

  // 4. Public utility methods
  public async findFactoryFile(
    factoryName: string
  ): Promise<vscode.Uri | undefined> {
    // Implementation
  }

  // 5. Private implementation methods
  private async initializeFactoryFiles(): Promise<void> {
    // Implementation
  }

  private async cacheFactoryDefinitions(): Promise<void> {
    // Implementation
  }
}

3. Method Organization

Method Grouping: Group related methods together

// Cache management methods
private async initializeFactoryFiles(): Promise<void> {}
private async cacheFactoryDefinitions(): Promise<void> {}
private async cacheTraitDefinitions(): Promise<void> {}

// Link generation methods
private generateFactoryLinks(): vscode.DocumentLink[] {}
private generateTraitLinks(): vscode.DocumentLink[] {}
private createDocumentLink(): vscode.DocumentLink {}

// Utility methods
private getConfiguration(): vscode.WorkspaceConfiguration {}
private normalizeFactoryPath(): string {}

Error Handling Style

1. Error Propagation

Graceful Error Handling:

// Good: Graceful degradation
async initializeFactoryFiles(): Promise<void> {
  try {
    const factoryFiles = await this.discoverFactoryFiles();
    await this.processFactoryFiles(factoryFiles);
  } catch (error) {
    console.warn("Factory initialization failed, continuing with empty cache:", error);
    // Continue with empty cache instead of failing completely
  }
}

// Good: Specific error handling
private async processFactoryFile(uri: vscode.Uri): Promise<void> {
  try {
    const document = await vscode.workspace.openTextDocument(uri);
    this.parseFactoryDefinitions(document);
  } catch (error) {
    console.warn(`Failed to process factory file ${uri.path}:`, error);
    // Skip this file and continue with others
  }
}

2. Error Logging

Consistent Error Logging:

// Good: Context-rich error messages
try {
  await this.operation();
} catch (error) {
  console.error("FactoryLinkProvider: Failed to initialize factory files", {
    error: error.message,
    stack: error.stack,
    context: "initializeFactoryFiles",
  });
}

// Good: User-friendly fallbacks
provideDocumentLinks(document: vscode.TextDocument): vscode.DocumentLink[] {
  try {
    return this.generateLinks(document);
  } catch (error) {
    console.error("Failed to generate document links:", error);
    return []; // Return empty array instead of throwing
  }
}

Documentation Style

1. Code Comments

JSDoc Comments: For public APIs

/**
 * Provides document links for factory calls in Ruby test files.
 *
 * @param document The text document to analyze
 * @returns Array of clickable document links
 */
public async provideDocumentLinks(
  document: vscode.TextDocument
): Promise<vscode.DocumentLink[]> {
  // Implementation
}

/**
 * Finds the factory file containing the specified factory definition.
 *
 * @param factoryName Name of the factory to locate
 * @returns URI of the factory file, or undefined if not found
 */
public async findFactoryFile(factoryName: string): Promise<vscode.Uri | undefined> {
  // Implementation
}

Inline Comments: For complex logic

// Cache factory definitions for O(1) lookup performance
private factoryCache: Map<string, FactoryDefinition> = new Map();

// Use regex to detect factory calls with various syntax patterns
const factoryCallPattern = /(?:create|build|create_list|build_list|build_stubbed|build_stubbed_list)\s*(?:\(\s*)?((:[a-zA-Z0-9_]+)(?:\s*,\s*(:[a-zA-Z0-9_]+))*)/g;

// Calculate line number by counting newlines before match position
const lines = text.substring(0, match.index).split("\n");
const lineNumber = lines.length - 1;

2. TODO Comments

Structured TODO Format:

// TODO(priority): Description
// TODO(high): Implement support for factory inheritance
// TODO(medium): Add caching for regex compilation
// TODO(low): Consider supporting custom factory methods

// FIXME: Handle edge case where factory file is deleted while extension is running
// HACK: Temporary workaround for VSCode API limitation

Testing Code Style

1. Test Organization

Descriptive Test Names:

suite("Factory Detection", () => {
  test("should detect basic factory calls with parentheses", () => {
    // Test implementation
  });

  test("should detect factory calls without parentheses", () => {
    // Test implementation
  });

  test("should handle factory calls with multiple traits", () => {
    // Test implementation
  });

  test("should ignore factory calls in comments", () => {
    // Test implementation
  });
});

Consistent Test Structure:

test("should perform specific behavior", async () => {
  // Arrange: Set up test data
  const mockDocument = createMockDocument("user = create(:user)");
  const provider = new FactoryLinkProvider();

  // Act: Execute the behavior
  const links = await provider.provideDocumentLinks(mockDocument);

  // Assert: Verify expected outcome
  assert.strictEqual(links.length, 1);
  assert.ok(links[0].target?.toString().includes("gotoLine"));
});

2. Mock and Stub Style

Clear Mock Setup:

setup(() => {
  // Create fresh mocks for each test
  mockWorkspace = sinon.stub(vscode.workspace, "findFiles");
  mockConfiguration = sinon.stub(vscode.workspace, "getConfiguration");

  // Set up default behavior
  mockWorkspace.resolves([]);
  mockConfiguration.returns({
    get: sinon
      .stub()
      .withArgs("factoryPaths")
      .returns(["spec/factories/**/*.rb"]),
  });
});

teardown(() => {
  // Clean up all mocks
  sinon.restore();
});

ESLint Configuration

1. Rules Enforcement

TypeScript ESLint Rules (.eslintrc.json):

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/prefer-const": "error",
    "prefer-arrow-callback": "error",
    "no-var": "error"
  }
}

Source: package.json#L82-L83

2. Code Quality Commands

Linting Commands:

# Run linting
npm run lint

# Fix auto-fixable issues
npm run lint -- --fix

# Lint specific files
npm run lint src/providers/factoryLinkProvider.ts

Best Practices Summary

1. Consistency Guidelines

  • Use consistent naming conventions throughout the codebase
  • Maintain consistent indentation (2 spaces)
  • Follow the established import order
  • Use explicit types for public APIs
  • Handle errors gracefully without breaking user experience

2. Performance Considerations

  • Use appropriate data structures (Map for O(1) lookups)
  • Cache expensive operations
  • Minimize regex recompilation
  • Use lazy initialization where appropriate

3. Maintainability

  • Keep methods focused and single-purpose
  • Use descriptive variable and function names
  • Add comments for complex business logic
  • Write comprehensive tests for new features

Source: src/providers/factoryLinkProvider.ts

Following these code style guidelines ensures the Rails FactoryBot Jump extension maintains high code quality, readability, and consistency across all contributions.

⚠️ **GitHub.com Fallback** ⚠️