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 4 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
- 4 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.
| Rule | Purpose | Catches | Fix |
|---|---|---|---|
no-viewport-height-classes | Prevents viewport height anti-patterns that break centralized layout architecture | h-screen, min-h-screen, calc(100vh-...) | Use h-full with grid container pattern |
no-direct-role-access | Enforces use of centralized access helpers instead of raw role checking | scopeContext?.userRole, membership.role, role === 'OWNER' | Client: useScopeAccess().isOwnerServer: getScopeAccess(scope).isOwner |
require-api-logging | Ensures all API v1 routes use withApiLogging wrapper for consistent logging | export const GET = async (req) => ...export async function POST(req) ... | export const GET = withApiLogging(async (req) => ...) |
use-deny-access | Enforces denyAccess() for page-level authorization failures | if (!access.isOwner) redirect('/hub') | denyAccess({ scope, programSlug, reason }) |
Internationalization
| Rule | Purpose |
|---|---|
i18next/no-literal-string | Catches hardcoded user-facing strings; all text must use t('key') from next-intl |
TypeScript
| Rule | Purpose |
|---|---|
ban-ts-comment | Restrict @ts-ignore, @ts-nocheck usage |
no-explicit-any | Block 'any' type - use specific types |
no-unused-vars | Warn on unused variables (allows _prefix for intentionally unused) |
no-require-imports | Block require() in TypeScript - use ES modules |
no-empty-object-type | Block { } type - use Record or object |
no-unsafe-function-type | Block Function type - use specific function signature |
prefer-as-const | Prefer 'as const' over literal types |
Next.js
| Rule | Purpose |
|---|---|
no-html-link-for-pages | Use <Link> not <a> for internal routes |
no-img-element | Use <Image> not <img> for optimization |
no-async-client-component | Block async client components |
no-head-element | Use next/head, not <head> |
google-font-display | Require font-display on Google Fonts |
no-sync-scripts | Use async scripts |
React & Hooks
| Rule | Purpose |
|---|---|
rules-of-hooks | Enforce Rules of Hooks |
exhaustive-deps | Warn on missing useEffect dependencies |
jsx-key | Require key prop in iterators |
jsx-no-target-blank | Require rel="noopener" with target="_blank" |
no-children-prop | Use children slot, not children prop |
no-unescaped-entities | Escape HTML entities in JSX |
Accessibility
| Rule | Purpose |
|---|---|
alt-text | Require alt on img, area, input[type=image] |
aria-props | Valid ARIA props |
aria-proptypes | Valid ARIA prop values |
role-has-required-aria-props | Roles have required ARIA attributes |
Import Restrictions
Repository pattern enforcement: Only repository.ts files may import Prisma directly. All other files must use repository functions.
| Blocked | Use Instead |
|---|---|
@prisma/client | Import from @/lib/db or use repository functions |
prisma from @/lib/db | Only in repository.ts files - use repository functions elsewhere |
Core JavaScript
| Rule | Purpose |
|---|---|
no-var | Use let/const, not var |
prefer-const | Use const when not reassigned |
no-undef | Block undefined variables |
no-unreachable | Block unreachable code |
no-const-assign | Don't reassign const |
no-dupe-keys | No 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()