CentercodePorygon
DittoPorygonAPI

ESLint Rules

Build-time code quality and architecture enforcement

SafeReliable

Overview

ESLint rules enforce code quality and architectural patterns at build time. Custom Centercode rules prevent drift in critical patterns like API logging, access control, and layout architecture.

What it is

107 active ESLint rules including 6 custom Centercode rules for architecture enforcement, plus TypeScript, React, Next.js, and accessibility rules.

Why we use it

Catch violations before runtime, prevent architectural drift, enforce consistent patterns across the codebase, and maintain code quality automatically.

When to use

Rules run automatically on every build. Run pnpm lint to check violations. Fix all errors before committing.

Key Features

  • 6 custom Centercode rules for architecture enforcement
  • Build fails on violations - cannot deploy broken code
  • Helpful error messages point to correct patterns
  • Exemptions for implementation files that need direct access

Centercode Custom Rules

Architecture enforcement: These rules prevent drift by catching violations at build time. They enforce patterns documented throughout Porygon.

RulePurposeCatchesFix
no-viewport-height-classesPrevents viewport height anti-patterns that break centralized layout architectureh-screen, min-h-screen, calc(100vh-...)Use h-full with grid container pattern
no-direct-role-accessEnforces use of centralized access helpers instead of raw role checkingscopeContext?.userRole, membership.role, role === 'OWNER'Client: useScopeAccess().isOwner
Server: getScopeAccess(scope).isOwner
require-api-loggingEnsures all API v1 routes use withApiLogging wrapper for consistent loggingexport const GET = async (req) => ...
export async function POST(req) ...
export const GET = withApiLogging(async (req) => ...)
use-deny-accessEnforces denyAccess() for page-level authorization failuresif (!access.isOwner) redirect('/hub')denyAccess({ scope, programSlug, reason })
require-api-authEnsures all API v1 routes use withAuth wrapper for authenticationexport const GET = withApiLogging(async (req) => ...)
(no auth wrapper)
withAuth({ scope: 'project' }, handler)
Or add @public-route comment
no-direct-sessionPrevents direct getSession() calls in API routesimport { getSession } from '@/lib/auth/server'Use withAuth() wrapper from @/lib/api-auth

Internationalization

RulePurpose
i18next/no-literal-stringCatches hardcoded user-facing strings; all text must use t('key') from next-intl

TypeScript

RulePurpose
ban-ts-commentRestrict @ts-ignore, @ts-nocheck usage
no-explicit-anyBlock 'any' type - use specific types
no-unused-varsWarn on unused variables (allows _prefix for intentionally unused)
no-require-importsBlock require() in TypeScript - use ES modules
no-empty-object-typeBlock { } type - use Record or object
no-unsafe-function-typeBlock Function type - use specific function signature
prefer-as-constPrefer 'as const' over literal types

Next.js

RulePurpose
no-html-link-for-pagesUse <Link> not <a> for internal routes
no-img-elementUse <Image> not <img> for optimization
no-async-client-componentBlock async client components
no-head-elementUse next/head, not <head>
google-font-displayRequire font-display on Google Fonts
no-sync-scriptsUse async scripts

React & Hooks

RulePurpose
rules-of-hooksEnforce Rules of Hooks
exhaustive-depsWarn on missing useEffect dependencies
jsx-keyRequire key prop in iterators
jsx-no-target-blankRequire rel="noopener" with target="_blank"
no-children-propUse children slot, not children prop
no-unescaped-entitiesEscape HTML entities in JSX

Accessibility

RulePurpose
alt-textRequire alt on img, area, input[type=image]
aria-propsValid ARIA props
aria-proptypesValid ARIA prop values
role-has-required-aria-propsRoles have required ARIA attributes

Import Restrictions

Repository pattern enforcement: Only repository.ts files may import Prisma directly. All other files must use repository functions.

BlockedUse Instead
@prisma/clientImport from @/lib/db or use repository functions
prisma from @/lib/dbOnly in repository.ts files - use repository functions elsewhere

Security Restrictions

RLS bypass protection: The withRLSBypass function bypasses PostgreSQL Row-Level Security (tenant isolation). ESLint blocks its use in src/app/ to prevent security holes in API routes and pages.

BlockedUse InsteadWhy
withRLSBypass from @/lib/dbimport { withRLSBypass } from '@/lib/jobs'Only for Inngest jobs and seed scripts
setRLSBypass from @/lib/dbUse withRLSBypass wrapper insteadLow-level function for infrastructure only
@/lib/db/rls-contextNever import directly in application codeInternal module for infrastructure only

Why this matters: API routes automatically get RLS context via withApiLogging(). Using withRLSBypass in an API route would expose data from ALL tenants - a critical security vulnerability. Background jobs need bypass because they run without user context.

Core JavaScript

RulePurpose
no-varUse let/const, not var
prefer-constUse const when not reassigned
no-undefBlock undefined variables
no-unreachableBlock unreachable code
no-const-assignDon't reassign const
no-dupe-keysNo duplicate object keys

Watch Out

  • Never use eslint-disable without explicit approval - it defeats the purpose
  • The 'any' type is blocked - always use specific types
  • console.log is not blocked by ESLint but CLAUDE.md forbids it - use logger
  • i18next/no-literal-string catches hardcoded strings - use t()

Related

API Logging

require-api-logging rule enforces the wrapper

Authorization

use-deny-access, require-api-auth, no-direct-session rules

Validation

Zod schemas for runtime validation