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.
| Area | What you automate | Gate or signal (recommended) | Why it matters |
|---|---|---|---|
| Lint + formatting | ESLint + Prettier (or equivalent) | Gate | Stops basic mistakes and noisy diffs, keeps reviews focused on logic |
| Types | Type-check in CI (tsc) | Gate | Eliminates whole classes of runtime errors before tests run |
| Unit/component tests | Fast test suite | Gate | Catches behavioral regressions in core logic and UI components |
| E2E tests | Minimal “critical path” smoke suite | Gate or signal | Verifies wiring and deploy readiness, but can be flaky if overused |
| Coverage, complexity, duplication | Reports and trends | Signal | Useful 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:
- Make lint results deterministic. If developers can’t reproduce CI lint failures locally, trust erodes quickly.
- 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.

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 --noEmitin CI (even if not strict yet) - Remove the highest-risk implicit
anypaths - 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:
typecheckis 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 type | What it protects | Typical tools (examples) | What to keep small |
|---|---|---|---|
| Unit | Pure logic, utilities, reducers, domain rules | Vitest, Jest | Avoid excessive mocking that tests implementation details |
| Component/UI | UI behavior and states (loading, error, empty) | Testing Library | Avoid snapshot sprawl, focus on user-observable behavior |
| Integration | Modules that touch DB, cache, queues, external services (with fakes) | Jest/Vitest + test containers or fakes | Avoid “almost-E2E” suites that are slow and flaky |
| E2E | Critical user journeys across the full stack | Playwright, Cypress | Avoid 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:checklinttypechecktest
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 item | What “done” looks like | Automation |
|---|---|---|
| Formatting | One formatter, enforced, no style diffs in PRs | Pre-commit and CI |
| Linting | ESLint runs locally and in CI, rules focus on bug prevention | CI gate |
| Type safety | Type-checking is required, type escapes are controlled | CI gate |
| Test strategy | Unit/component suite is fast, E2E smoke covers critical path | CI gate (unit) + optional gate (smoke E2E) |
| CI reliability | Builds are deterministic, failures are diagnosable, pipeline is fast | Branch protections + stable runners |
| Metrics | Coverage, flaky rate, runtime error rate tracked as signals | Dashboards/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.

