npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

code-smells

v0.2.11

Published

Code smell detector for React/TypeScript repos. Thin wrapper over code-pushup with curated ESLint rules, research-backed signals (Tornhill temporal coupling, Nagappan team ownership), and monorepo support.

Readme

code-smells

license node

A curated, research-backed static-analysis tool for React/TypeScript repos. Thin wrapper over code-pushup with opinionated defaults — hook overload, inline props, fan-out coupling, hidden temporal coupling (Tornhill), cross-team ownership (Nagappan 2007), and more. Zero setup against any TS/React repo.

cd into the repo you want to scan, then run:

npx code-smells

Reports land in ./reports/report.{json,md}. Requires Node.js 18+ and git.

Or install globally

npm install -g code-smells

Worth doing if you run the tool more than occasionally:

  • Faster startup — skips npx's package-resolution step (a few seconds saved per run; adds up in CI or across a sweep of many repos)
  • Offline after first install — npx requires network to check the registry each run unless you pin @x.y.z exactly
  • Pinned version you control — no surprise patch-version bumps the next time you run
  • Shell tab completion works out of the box; which code-smells resolves
  • No npx prefix — just code-smells from anywhere in your shell

What you get

Eight categories scored 0-100, composed from 50+ audits across 11 plugins:

| Category | Signal | |---|---| | Component Health | Body size, cognitive complexity, hook overload, multi-component files | | Render Performance | Inline props in JSX, unstable useSelector returns | | Coupling | Import fan-out, domain-boundary violations (opt-in), hidden temporal coupling | | Type Safety | TypeScript compiler diagnostics + type-coverage (inferred-any) | | Security & Dependencies | npm audit vulnerabilities + outdated prod deps | | Accessibility | jsx-a11y curated subset (alt text, ARIA, focus management) | | Test Quality | testing-library antipatterns + function/branch/line coverage | | Maintainability | Duplicated code, churn, bug-fix density, author + team dispersion, dead code |

Categories auto-adapt to target capabilities — no tsconfig skips Type Safety, no lockfile skips Security, no lcov skips the coverage portion of Test Quality.

Output looks like this:

●  max-lines-per-function                    47 violations
●  react-perf/jsx-no-new-function-as-prop    89 violations
●  sonarjs/cognitive-complexity              15 violations
●  code-smells/hook-count                    12 violations
●  temporal-coupling/hidden-coupling         8 pairs (max 100% co-change)
●  team-ownership/cross-team-churn           0 files
●  jsx-a11y/alt-text                         3 violations
●  testing-library/no-container              2 violations
●  ... 40 more audits ...

┌───────────────────────────┬─────────┬──────────┐
│  Category                 │  Score  │  Audits  │
├───────────────────────────┼─────────┼──────────┤
│  Component Health         │      0  │       5  │
│  Render Performance       │     43  │       4  │
│  Coupling                 │     97  │       3  │
│  Type Safety              │     73  │       5  │
│  Security & Dependencies  │    100  │       3  │
│  Accessibility            │     92  │      11  │
│  Test Quality             │     78  │      11  │
│  Maintainability          │     89  │      11  │
└───────────────────────────┴─────────┴──────────┘

Why this vs. running ESLint directly

ESLint gives you rule-by-rule violation counts. This gives you:

  • Category scores rolling 40+ audits into 6 top-level dials you can trend over time
  • Research-backed signals (temporal coupling, team ownership) that no ESLint rule covers — these predict defects better than any static-analysis metric in the Nagappan 2007 study
  • Monorepo support out of the box — auto-detects workspaces, expands tsconfig project references, gracefully degrades when plugins don't apply to a sub-workspace
  • CI ratchet via code-pushup compare --fail-on=regression — fail PRs that worsen the score without cargo-culting absolute thresholds
  • Thin by design — every audit is an existing tool (ESLint rule, maintained CLI, npm package), curated and weighted. No custom analysis engine to outgrow.

Usage

Single-package repo

From inside the repo:

npx code-smells

Defaults: src/**/*.{ts,tsx} source glob, src/ as the dependency-cruiser entry. Override either via env var when needed:

CP_PATTERNS='src/js/**/*.{ts,tsx}' CP_ENTRY='src/js' npx code-smells

Monorepo (yarn / pnpm / npm workspaces)

Auto-detects plugins/<ws>/src, libs/<ws>/src, packages/<ws>/src layouts. From the monorepo root:

CP_PATTERNS='{plugins,libs,packages}/*/src/**/*.{ts,tsx}' npx code-smells

If the root tsconfig.json uses project references, they're expanded automatically into per-workspace tsconfigs for the TypeScript plugin.

Running from elsewhere (CI, scripts)

If you can't cd first, point CP_TARGET at the repo root:

CP_TARGET=/path/to/repo npx code-smells

Env vars

| Var | Default | Purpose | |---|---|---| | CP_TARGET | current directory | Override the repo under analysis. Useful in CI / wrappers where you can't cd first. | | CP_PATTERNS | src/**/*.{ts,tsx} (auto-detects workspaces in monorepos) | Glob for source files (ESLint, jsdocs, type-coverage) | | CP_ENTRY | src (auto-detects workspaces in monorepos) | dependency-cruiser entry point(s); comma-separated list | | CP_TSCONFIG | auto-detected from references | Explicit tsconfig path(s), comma-separated | | CP_COVERAGE_LCOV | coverage/lcov.info if present | Path to lcov file for the coverage audit | | CP_ENABLE_FORMATJS | off | Set =true to enable formatjs/no-literal-string-in-jsx (react-intl repos) | | CP_OUTPUT_DIR | OS cache dir | Where reports land. Set =./reports to keep them inside the target repo. | | CP_OPEN | off | Set =md or =json to auto-open the report after the run (macOS open, Linux xdg-open, Windows start). |

Custom ESLint rules

Ships 4 rules under the code-smells/ plugin namespace, each filling a gap where no community rule does the job:

| Rule | Flags | |---|---| | hook-count | Components with more than N total hook calls (default 10) | | use-effect-count | Components with more than N useEffect calls (default 3) | | unstable-selector-returns | useSelector with inline object-literal return — unstable reference, re-renders every time | | domain-boundaries | Opt-in. Files referencing N+ distinct domain buckets. Bring your own { token: bucket } map — see the rule source for examples |

CI

Drop examples/workflows/code-smells.yml into your repo's .github/workflows/. It seeds a baseline on main, compares PRs against it, posts a single sticky summary comment, and fails on category regressions.

Full setup guide in docs/ci-integration.md. Three Google Tricorder principles applied: delta over absolute thresholds, summary over per-line annotations, PR-diff-only reporting.

Architecture

code-smells/
├── code-pushup.config.mjs            # 11 plugins + 8 categories
├── eslint.target-rules.mjs           # Opinionated ESLint config applied to target source
├── eslint-rules/                     # Custom ESLint rules (~180 lines total)
├── plugins/                          # Thin adapters over existing tools
│   ├── eslint.plugin.mjs             # Programmatic ESLint — drives all rule-backed audits
│   ├── coupling.plugin.mjs           # dependency-cruiser (fan-out)
│   ├── duplication.plugin.mjs        # jscpd (duplicated lines)
│   ├── knip.plugin.mjs               # knip (dead code)
│   ├── type-coverage.plugin.mjs      # type-coverage (inferred-any)
│   ├── churn.plugin.mjs              # git log — file-change frequency
│   ├── bug-fix-density.plugin.mjs    # git log — fix commit density
│   ├── author-dispersion.plugin.mjs  # git log — individual author dispersion
│   ├── temporal-coupling.plugin.mjs  # git log + dep-cruiser — Tornhill hidden coupling
│   └── team-ownership.plugin.mjs     # git log + CODEOWNERS — Nagappan cross-team defect predictor
├── examples/workflows/               # Drop-in GitHub Actions template
├── docs/
│   ├── ci-integration.md
│   └── decisions/                    # ADRs
├── VISION.md                         # Boundaries + research footing
└── TASKS.md                          # Roadmap

Why thin is the design

Per VISION.md: value is curation + defaults + plumbing, not analysis logic. Every audit is an existing tool (ESLint rule, maintained CLI, npm package) wired into code-pushup's audit/category model. Justified custom code:

  • ~180 lines total for the 4 custom ESLint rules — each a gap no community rule fills
  • ~190 lines for temporal-coupling (no maintained Node tool; code-maat is JVM-based)
  • ~180 lines for team-ownership (wraps codeowners-utils with Nagappan cross-team-commit aggregation)
  • ~100 lines each for the thin plugin wrappers (coupling, duplication, churn, bug-fix-density, author-dispersion, knip, type-coverage)

Every custom file's JSDoc header explains why that specific wrapper exists — the gap it fills, what alternative was ruled out, what constraints shaped it.

Decisions (ADRs)

  • ADR-0001 — use eslint-plugin-jsx-a11y for accessibility; do not adopt @code-pushup/axe-plugin.

Development

git clone https://github.com/fyodoriv/code-smells && cd code-smells
npm install
node ./bin/code-smells.mjs  # run against cwd (the tool's own repo)
npm test                    # vitest (200+ unit tests)
npm run test:coverage       # v8 coverage, thresholds: 95% stmts/funcs/lines, 90% branches
npm run format              # prettier

CI runs tests + coverage on every push / PR via .github/workflows/ci.yml. Coverage thresholds live in vitest.config.mjs — the build fails if any metric drops below target.

Releases are automated via release-please — conventional commits on main accumulate into a release PR; when it merges, a GitHub release fires and .github/workflows/publish.yml publishes to npm with provenance. To bootstrap: add an NPM_TOKEN repo secret with an npm automation/granular token scoped to the code-smells package.

Follow-up work lives in TASKS.md. Highlights: tune-weights (calibrate category weights against curated hotspots), bundle-size-plugin, stylelint-plugin, evaluate-sonarcloud.

License

MIT