Trovella Wiki

Complexity and Naming

ESLint complexity limits, hand-picked sonarjs rules, and naming convention enforcement -- the editor-time layer of architecture enforcement.

The editor-time enforcement layer uses ESLint to catch complexity violations and naming inconsistencies while code is being written. This gives AI agents (and the founder) instant red squiggles -- the fastest possible feedback loop.

For the complete ESLint configuration reference (preset architecture, all config options, SDK boundary enforcement), see Quality Gates — ESLint Configuration. This page focuses on the architecture enforcement rationale behind complexity limits and naming conventions.

Complexity Limits

Six metrics are enforced globally, with relaxed thresholds for specific file types.

MetricRuleThresholdWhat It Measures
File sizemax-lines400 (skip blank/comments)Total lines of logic per file
Function sizemax-lines-per-function100 (skip blank/comments)Total lines of logic per function
Cyclomatic complexitycomplexity20Number of independent paths through a function
Cognitive complexitysonarjs/cognitive-complexity20How hard a function is to understand (penalizes nesting, flow breaks, recursion)
Nesting depthmax-depth4Maximum nesting level of blocks
Parameter countmax-params4Forces use of option objects for complex signatures

Cyclomatic vs Cognitive Complexity

Cyclomatic complexity counts branch points (decision paths through code). Cognitive complexity measures how hard code is to understand for a human reader. The difference matters:

// Cyclomatic: 5, Cognitive: 5 (simple branching)
function route(method: string) {
  if (method === "GET") return handleGet();
  if (method === "POST") return handlePost();
  if (method === "PUT") return handlePut();
  if (method === "DELETE") return handleDelete();
  return handleNotAllowed();
}

// Cyclomatic: 4, Cognitive: 10 (nesting makes it harder to read)
function process(data: unknown) {
  if (data) {
    // +1 cyclo, +1 cogn
    if (Array.isArray(data)) {
      // +1 cyclo, +2 cogn (nesting)
      for (const item of data) {
        // +1 cyclo, +3 cogn (nesting)
        if (typeof item === "string") {
          // +1 cyclo, +4 cogn (nesting)
          // ...
        }
      }
    }
  }
}

Both are capped at 20 in this project. The founder chose 20 over the commonly recommended 15 because the codebase includes data-heavy handlers that naturally branch more than typical application code.

Relaxed File Types

Complexity limits are turned off for files that are inherently large or repetitive:

  • **/seeds/** -- seed data files
  • **/migrations/** -- generated migration SQL
  • **/schema/** -- Drizzle schema definitions (table declarations, not logic)
  • **/__tests__/**, **/*.test.*, **/*.spec.* -- test suites

JSDoc requirements are also disabled for these file types.

Hand-Picked sonarjs Rules

9 rules were selected from eslint-plugin-sonarjs, explicitly rejecting the full recommended preset (150+ error-level rules that produced unacceptable false positives). These 9 target the specific code smells AI agents produce most frequently.

RuleWhat It Catches
sonarjs/cognitive-complexityFunctions too hard to understand (threshold: 20)
sonarjs/no-identical-functionsTwo functions with identical bodies (copy-paste artifact)
sonarjs/no-collapsible-ifNested if that can be combined with &&
sonarjs/no-duplicated-branchesif/else with identical code in branches
sonarjs/no-all-duplicated-branchesAll branches of a conditional are identical
sonarjs/no-identical-expressionsa === a or x && x (always a bug)
sonarjs/prefer-single-boolean-returnif (x) return true; return false;
sonarjs/no-redundant-jumpcontinue, break, return with no effect
sonarjs/no-nested-conditionalTernaries inside ternaries

Naming Conventions

@typescript-eslint/naming-convention enforces consistent casing across the monorepo. The rules are tuned for the project's specific needs (React components, Next.js route handlers, external API boundary types).

SelectorAllowed FormatsNotes
DefaultcamelCaseBaseline for all identifiers
VariablescamelCase, PascalCase, UPPER_CASEPascalCase for React components, UPPER_CASE for constants
Destructured variablesExemptMatch external APIs, props, env vars
__ prefix variablesExemptNode.js shims like __dirname
FunctionscamelCase, PascalCasePascalCase for React components
Exported functions matching HTTP verbsUPPER_CASE onlyGET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
ParameterscamelCase, PascalCaseLeading underscore allowed for unused
Object literal propertiesExemptDrizzle schemas, API payloads, config objects
Type propertiescamelCase, snake_casesnake_case for external API boundary types (Anthropic, Typesense)
Types and classesPascalCaseIncludes interfaces, type aliases, enums
Type parametersPascalCase with T prefixTResult, TItem, TContext
ImportscamelCase, PascalCaseCovers both utility and component imports

Other Editor-Time Rules

RuleSeverityWhat It Enforces
no-consoleerrorBans console.log -- use @repo/logger instead
@typescript-eslint/only-throw-errorerrorOnly throw Error objects, not strings or plain objects
unicorn/expiring-todo-commentserrorEvery TODO/FIXME/HACK must have [YYYY-MM-DD] or [TRO-*]
tsdoc/syntaxwarnCatches malformed TSDoc tags and non-standard syntax
jsdoc/require-jsdocerrorExported functions/classes/types with 3+ lines must have JSDoc
simple-import-sort/importserrorEnforces import group ordering (side effects, React/Next, external, @repo/*, relative, CSS)
@typescript-eslint/consistent-type-importserrorUse type imports for type-only references
@typescript-eslint/no-explicit-anyerrorBans any type annotations

Import Sort Order

Imports are sorted into six groups by simple-import-sort:

  1. Side-effect imports (import "...")
  2. React, React DOM, Next.js
  3. External packages (@?\\w)
  4. Internal packages (@repo/*)
  5. Relative imports (./)
  6. CSS imports

On this page