JS Code Quality Checklist: Lint, Types, Tests, CI

#js code
Sandor Farkas - Co-founder & CTO of Wolf-Tech

Sandor Farkas

Co-founder & CTO

Expert in software development and legacy code optimization

JS Code Quality Checklist: Lint, Types, Tests, CI

JavaScript lets teams move fast, but without guardrails it also lets defects move fast. The goal of a JS code quality checklist is not “perfect code”, it is predictable changes: fewer regressions, smaller PRs, faster reviews, and confidence that what shipped is what you intended.

This checklist focuses on four high-leverage pillars you can automate: linting, types, tests, and CI. Treat them as a system, not separate initiatives.

How to use this checklist: gates vs signals

Not every quality practice should block merges on day one. A practical approach is to distinguish:

  • Quality gates: automated checks that must pass to merge.
  • Quality signals: measurements you track and improve over time, but that do not block merges yet.

Here is a pragmatic default you can adapt.

AreaWhat you automateGate or signal (recommended)Why it matters
Lint + formattingESLint + Prettier (or equivalent)GateStops basic mistakes and noisy diffs, keeps reviews focused on logic
TypesType-check in CI (tsc)GateEliminates whole classes of runtime errors before tests run
Unit/component testsFast test suiteGateCatches behavioral regressions in core logic and UI components
E2E testsMinimal “critical path” smoke suiteGate or signalVerifies wiring and deploy readiness, but can be flaky if overused
Coverage, complexity, duplicationReports and trendsSignalUseful when used for steering, risky when used as a hard target

If you want a broader delivery-system view, Wolf-Tech’s guide on CI/CD technology complements this checklist well.

1) Lint and formatting: make “bad code” harder to write

Linting is the fastest win because it runs in milliseconds, catches obvious problems early, and produces consistent code reviews.

Baseline: Prettier (or an equivalent formatter) is non-negotiable

A formatter is not about style preferences, it is about diff quality and review throughput.

Practical defaults:

  • Run formatting in the editor on save.
  • Enforce formatting in CI so it cannot drift.
  • Prefer a single formatter for the repo, avoid “formatting by convention”.

The canonical reference is Prettier’s documentation.

ESLint: start with rules that prevent real bugs

ESLint can become noisy if you enable everything. Start with rules that have a strong bug-to-noise ratio.

A good baseline typically includes:

  • The core recommended config (eslint:recommended)
  • TypeScript linting if you use TS (@typescript-eslint)
  • Import hygiene (unused imports, cycles if relevant)
  • Promise and async safety (unhandled promises, misused await)
  • React rules (Hooks rules, JSX key issues) if you are building React UIs

Two practical tips that keep linting effective:

  1. Make lint results deterministic. If developers can’t reproduce CI lint failures locally, trust erodes quickly.
  2. Prefer “error” for correctness, “warn” for style. Then progressively promote warnings to errors as the codebase stabilizes.

For a deeper “fast feedback” workflow (editor, pre-commit, PR, nightly), see Wolf-Tech’s React dev setup guide. The concepts apply to most JS/TS repos.

Add lint rules that match your architecture

As projects grow, you get more value from rules that enforce boundaries, not just syntax.

Examples of architecture-aligned linting (tooling varies):

  • Restrict cross-module imports to prevent “spaghetti dependencies”
  • Enforce a single direction of dependencies (for example, UI cannot import server code)
  • Prevent direct access to low-level clients (database, raw HTTP) outside dedicated modules

These rules are often a better investment than adding more stylistic rules.

A simple four-step diagram titled “JS Code Quality Fast Feedback Ladder” showing levels: Editor (lint + format), Pre-commit (quick tests), Pull Request CI (typecheck + full unit suite), Nightly (E2E + heavier checks).

2) Types: use TypeScript to reduce runtime uncertainty

TypeScript is one of the most cost-effective ways to improve JS code quality because it turns many failures into compile-time feedback.

Treat “strict” as the goal, even if you can’t start there

If you can, enable strict typing early ("strict": true). If you have a legacy codebase, you can still move toward strictness incrementally.

Practical path for existing repos:

  • Start by running tsc --noEmit in CI (even if not strict yet)
  • Remove the highest-risk implicit any paths
  • Make new or refactored modules stricter first
  • Add rules that prevent “type escapes” from spreading

The key is that types become a delivery safety mechanism, not a documentation project.

Guard your boundaries: types are not runtime validation

Types are erased at runtime. Many JavaScript production bugs happen at boundaries:

  • HTTP requests and responses
  • Webhooks
  • Message queues
  • User-generated input
  • Third-party SDK payloads

A strong default is:

  • Type internally (your application code)
  • Validate at the boundary (parse and validate inputs)

Runtime schema validation libraries (for example, Zod in TS ecosystems) help here, but the principle matters more than the library.

Make type-checking a required CI check

If your code can compile with type errors, TypeScript becomes optional. In practice, teams keep quality high when:

  • typecheck is a required PR check
  • build steps do not silently ignore type errors
  • generated types (OpenAPI, GraphQL, etc.) are part of the pipeline, not “run sometimes”

3) Tests: choose a small set that protects change

Testing should reduce uncertainty about changes. The mistake is to equate “more tests” with “safer”, or to rely on a single test type.

Use a test portfolio (not just unit tests, not just E2E)

A practical, production-friendly split for many JS/TS codebases looks like this:

Test typeWhat it protectsTypical tools (examples)What to keep small
UnitPure logic, utilities, reducers, domain rulesVitest, JestAvoid excessive mocking that tests implementation details
Component/UIUI behavior and states (loading, error, empty)Testing LibraryAvoid snapshot sprawl, focus on user-observable behavior
IntegrationModules that touch DB, cache, queues, external services (with fakes)Jest/Vitest + test containers or fakesAvoid “almost-E2E” suites that are slow and flaky
E2ECritical user journeys across the full stackPlaywright, CypressAvoid covering every edge case, keep it to the critical paths

If you want a UI-release-focused checklist that goes beyond tests into performance and observability, Wolf-Tech’s front end development checklist is a useful companion.

Prefer deterministic tests over clever tests

Most “testing pain” comes from flakiness and slow feedback, not from writing assertions.

Practical quality rules that prevent flaky tests:

  • Freeze time when testing time-dependent logic
  • Avoid real network calls in unit/component tests
  • Reset global state between tests
  • Make async expectations explicit (await what you assert)
  • Keep E2E tests to a minimal smoke suite, then expand carefully

Make your tests meaningful in CI

In CI, “tests passed” only helps if tests reflect how you ship.

Checks that raise the signal:

  • Run tests in the same Node major version you deploy
  • Use the same package manager and lockfile behavior as production builds
  • Fail fast on unhandled promise rejections

DORA research consistently links technical practices like CI and automated testing to delivery performance outcomes. Google Cloud’s DORA research is a good starting point if you need evidence to justify investment.

4) CI: turn your checklist into a repeatable quality gate

A checklist that relies on human memory will drift. CI turns quality into a system property.

Minimal CI workflow for JS/TS

Your CI should do three things reliably:

  • Install dependencies deterministically
  • Run checks in a predictable order
  • Produce artifacts and logs that make failures easy to diagnose

A minimal PR pipeline commonly includes:

  • format:check
  • lint
  • typecheck
  • test

Optionally, add a small e2e:smoke job if your stack supports stable preview environments.

Here is an example of a minimal GitHub Actions workflow skeleton (adapt it to your repo). It is intentionally small so teams actually keep it healthy:

name: ci
on:
  pull_request:
  push:
    branches: [main]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - run: npm run format:check
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test

Keep CI fast, or developers will route around it

A slow pipeline creates pressure to skip checks, merge larger PRs, and defer quality.

High-leverage performance tactics:

  • Cache dependencies (but keep it deterministic)
  • Split jobs that can run in parallel
  • Keep “PR checks” small, move heavier suites (full E2E, mutation tests) to nightly

The goal is fast feedback for most changes, with deeper confidence checks running on a schedule.

Make CI status meaningful with branch protections

CI only works as a quality gate if:

  • Required checks are required (branch protection)
  • The pipeline is stable enough that developers trust it
  • Failures are actionable, not “random red builds”

A good rule: treat flaky CI as production risk. Fix it like you would fix a recurring outage.

5) Put it together: a copyable JS code quality checklist

Use this as your “definition of done” for the repository, not just for individual PRs.

Checklist itemWhat “done” looks likeAutomation
FormattingOne formatter, enforced, no style diffs in PRsPre-commit and CI
LintingESLint runs locally and in CI, rules focus on bug preventionCI gate
Type safetyType-checking is required, type escapes are controlledCI gate
Test strategyUnit/component suite is fast, E2E smoke covers critical pathCI gate (unit) + optional gate (smoke E2E)
CI reliabilityBuilds are deterministic, failures are diagnosable, pipeline is fastBranch protections + stable runners
MetricsCoverage, flaky rate, runtime error rate tracked as signalsDashboards/reports

If you want help prioritizing and rolling these out in a real codebase (especially legacy JavaScript or mixed JS/TS monorepos), Wolf-Tech provides code quality consulting and modernization support focused on measurable risk reduction.

Rolling this out in a legacy JS codebase without stopping delivery

Most teams inherit a repository where “turn everything on” causes hundreds of failures. A practical rollout keeps shipping while raising the floor:

Start with formatting and a narrow lint baseline

Formatting removes noise immediately. Then pick a small set of lint rules that clearly prevent bugs. Make those rules pass, then lock them in as a gate.

Add TypeScript where it pays first

You do not need to convert the whole repo to benefit from types. Start at the seams:

  • API contracts and data parsing
  • Shared domain logic
  • Modules with high change frequency

Stabilize tests before you expand them

A small, trusted test suite beats a large flaky one. Prefer:

  • A reliable unit/component suite as the default gate
  • A minimal E2E smoke suite that proves deployments are viable

Use metrics to steer, not to punish

For teams that need guidance on which metrics actually predict outcomes, Wolf-Tech’s article on code quality metrics that matter aligns well with this checklist.

What “good” looks like after 30 days

If you implement the checklist in a focused, incremental way, your first-month target state is realistic and measurable:

  • PRs consistently run lint, typecheck, and tests before merge
  • Formatting diffs are gone
  • The team trusts CI (few flaky failures)
  • You can point to fewer regressions, faster reviews, or reduced rework (pick one and track it)

That is the point of a JS code quality checklist: not more process, just more predictable shipping.