Source code for ralph.guidelines.javascript

"""JavaScript-specific review guideline categories.

Ported from the canonical Rust implementation in
``ralph-workflow/src/guidelines/javascript.rs`` and adapted for the Python port.
The module models core JavaScript guidance plus optional framework-specific
extensions for React, Vue, Angular, Node backends, SSR stacks, and TypeScript.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Iterable


[docs] class JavaScriptGuidelines: """JavaScript and TypeScript review checks. Args: frameworks: Optional framework names used to add stack-specific guidance. typescript: When true, include the TypeScript-specific checks from the Rust implementation alongside the JavaScript baseline. """ __slots__ = ( "anti_patterns", "api_design_checks", "concurrency_checks", "documentation_checks", "frameworks", "idioms", "observability_checks", "performance_checks", "quality_checks", "resource_checks", "secrets_checks", "security_checks", "testing_checks", "typescript", ) frameworks: tuple[str, ...] typescript: bool quality_checks: list[str] security_checks: list[str] performance_checks: list[str] testing_checks: list[str] documentation_checks: list[str] idioms: list[str] anti_patterns: list[str] concurrency_checks: list[str] resource_checks: list[str] observability_checks: list[str] secrets_checks: list[str] api_design_checks: list[str] def __init__(self, frameworks: Iterable[str] = (), typescript: bool = False) -> None: normalized_frameworks = tuple(frameworks) self.frameworks = normalized_frameworks self.typescript = typescript self.quality_checks = [ "Use const/let, never var.", "Handle Promise rejections explicitly.", "Use async/await over raw Promises when it improves readability.", "Avoid deeply nested callbacks.", ] self.security_checks = [ "Sanitize user input before DOM insertion.", "Use Content Security Policy headers.", "Validate data from external APIs.", "Check for prototype pollution vulnerabilities.", ] self.performance_checks = [ "Debounce or throttle frequent event handlers.", "Use appropriate data structures for lookup and iteration patterns.", "Minimize DOM manipulation.", ] self.testing_checks = [ "Test async control flow, Promise rejection paths, and error boundaries.", "Cover user-driven UI events and state transitions with behavior-focused tests.", ( "Exercise API validation, middleware, and serialization boundaries " "in integration tests." ), ] self.documentation_checks = [ "Document public modules, exported APIs, and non-obvious framework conventions.", "Keep README and setup docs aligned with the runtime, build, and framework stack.", ( "Explain security-sensitive data flow, hydration assumptions, " "and state ownership rules." ), ] self.idioms = [ "Prefer immutable updates and small pure helpers where practical.", "Use language and framework conventions instead of custom abstractions.", "Keep modules focused and exports intentional.", ] self.anti_patterns = [ "Avoid == for comparisons (use ===).", "Do not mutate function arguments.", "Avoid synchronous I/O in Node.js request or worker paths.", ] self.concurrency_checks = [ "Guard shared state in async flows to avoid race conditions.", "Use cancellation/timeout handling for long-running asynchronous work.", ] self.resource_checks = [ "Close streams, sockets, and DB handles reliably on success and failure.", "Bound memory use for large payload processing and caching.", ] self.observability_checks = [ "Emit structured logs with correlation identifiers for async requests.", "Expose actionable metrics for latency, error rate, and saturation.", ] self.secrets_checks = [ "Load secrets from secure environment/config providers, never hardcode.", "Ensure logs and error payloads redact API keys, tokens, and credentials.", ] self.api_design_checks = [ "Keep API contracts explicit, versioned, and backwards-compatible where required.", "Validate and document request/response schemas at boundaries.", ] if any(framework.casefold() in {"react", "vue"} for framework in normalized_frameworks): self._apply_frontend_guidelines() for framework in normalized_frameworks: self._apply_framework(framework) if typescript: self._apply_typescript_guidelines() def _apply_typescript_guidelines(self) -> None: """Add the TypeScript-specific checks from the Rust implementation.""" self.quality_checks.extend( [ "Use strict TypeScript mode.", "Prefer interfaces over type aliases for object shapes.", "Use explicit return types for public functions.", "Avoid the any type; use unknown when narrowing is required.", ] ) self.idioms.extend( [ "Use union types for discriminated unions.", "Leverage type inference where intent stays clear.", "Use generics appropriately.", ] ) self.anti_patterns.extend( [ "Do not use as casts to bypass type checking.", "Avoid non-null assertions (!) without justification.", ] ) def _apply_frontend_guidelines(self) -> None: """Add shared frontend guidance for React and Vue stacks.""" self.quality_checks.extend( [ "Components are properly modularized.", "State management is predictable.", "Accessibility (a11y) is considered.", ] ) self.performance_checks.extend( [ "Avoid unnecessary re-renders.", "Use lazy loading for large components.", "Optimize bundle size.", ] ) def _apply_framework(self, framework: str) -> None: """Add framework-specific guideline extensions.""" framework_name = framework.casefold() if framework_name == "react": self.quality_checks.extend( [ "Use hooks correctly (rules of hooks).", "Properly manage component lifecycle.", "Use React.memo for expensive renders.", ] ) self.anti_patterns.extend( [ "Avoid prop drilling when context or state management fits better.", "Do not mutate state directly.", "Avoid inline functions in render when they create avoidable churn.", ] ) return if framework_name == "vue": self.quality_checks.extend( [ "Use the Composition API for complex logic.", "Follow the Vue style guide.", "Use computed properties appropriately.", ] ) self.anti_patterns.extend( [ "Avoid watchers when computed properties express the same intent.", "Do not directly mutate props.", ] ) return if framework_name == "angular": self.quality_checks.extend( [ "Use OnPush change detection where possible.", "Follow the Angular style guide.", "Use RxJS operators effectively.", ] ) self.security_checks.append("Use Angular's built-in sanitization.") self.anti_patterns.extend( [ "Avoid subscribing without unsubscribing.", "Do not use the any type.", ] ) return if framework_name in {"express", "fastify", "nestjs"}: self.quality_checks.extend( [ "Use middleware patterns effectively.", "Handle errors in middleware.", "Use environment variables for configuration.", ] ) self.security_checks.extend( [ "Use helmet or equivalent security headers.", "Implement rate limiting.", "Validate request body schemas.", ] ) return if framework_name in {"next.js", "nuxt"}: self.quality_checks.extend( [ "Use the appropriate rendering strategy (SSR, SSG, or ISR).", "Handle hydration correctly.", "Optimize for Core Web Vitals.", ] ) self.performance_checks.extend( [ "Minimize client-side JavaScript.", "Use image optimization features.", ] )
[docs] def summary(self) -> str: """Return a short human-readable summary.""" return ( f"{len(self.quality_checks)} quality checks, " f"{len(self.security_checks)} security checks, " f"{len(self.anti_patterns)} anti-patterns" )
[docs] def total_checks(self) -> int: """Return the total number of configured checks.""" return sum( [ len(self.quality_checks), len(self.security_checks), len(self.performance_checks), len(self.testing_checks), len(self.documentation_checks), len(self.idioms), len(self.anti_patterns), ] )
__all__ = ["JavaScriptGuidelines"]