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-scent

v0.1.0

Published

A fast CLI that detects Martin Fowler-inspired code smells in JavaScript and TypeScript.

Readme

code-scent

code-scent is a Node CLI dev-tool for finding code smells in JavaScript and TypeScript. It feels like a test runner: point it at source paths, get file-and-line diagnostics, use the exit code in CI, or keep it running in watch mode.

The smell names and design intent follow Martin Fowler’s Refactoring: Improving the Design of Existing Code, Second Edition. Descriptions in this project are original paraphrases; the book remains the authoritative explanation. Fowler also maintains the official Refactoring catalog.

Code smells are prompts for investigation, not proof that code is wrong. Static rules are necessarily heuristic, so thresholds are configurable and individual rules can be disabled.

Quick start

Requires Node 20 or newer.

npm install --save-dev code-scent

npx code-scent src

With npm 7+, the locally installed binary can also be run through an npm script:

{
  "scripts": {
    "lint:smells": "code-scent src --fail-on error"
  }
}
npm run lint:smells

While developing this repository, the same command can be run directly:

node ./bin/code-scent.js src

Common forms:

code-scent                         # checks ./src
code-scent src packages/core      # checks multiple source trees
code-scent src --watch             # reruns after edits
code-scent . --format json         # machine-readable diagnostics
code-scent src --fail-on error     # warnings do not fail the process
code-scent src --rule loops=off    # one-run rule override
code-scent --list-smells           # all 24 catalog entries

Supported extensions are .js, .cjs, .mjs, .jsx, .ts, .cts, .mts, and .tsx. Common generated and dependency directories are skipped.

Configuration

The CLI automatically loads the first configuration file it finds in the working directory:

  • code-scent.config.js
  • code-scent.config.mjs
  • code-scent.config.cjs
  • code-scent.config.json

For example:

import { defineConfig } from 'code-scent/config';

export default defineConfig({
  failOn: 'warning', // warning | error | never
  format: 'pretty',  // pretty | json
  exclude: ['node_modules', 'dist', 'coverage', '.git', 'generated'],
  rules: {
    'long-function': { maxLines: 60, maxStatements: 35 },
    'long-parameter-list': { maxParameters: 5 },
    'mysterious-name': { enabled: false }
  }
});

See code-scent.config.example.js and src/config.js for every default threshold.

Detection coverage

The complete second-edition catalog is represented. The CLI separates smells that have useful static evidence from smells that need history or human design judgment.

| Smell | Detection | Evidence used | | --------------------------------------------- | -------------- | ------------------------------------------------------------ | | Mysterious Name | Heuristic | Repeated one-character or low-information declarations | | Duplicated Code | Heuristic | Exact normalized function bodies above size thresholds | | Long Function | Heuristic | Lines and statement count | | Long Parameter List | Heuristic | Declared parameter count | | Global Data | Heuristic | Module-scope let and var declarations | | Mutable Data | Heuristic | Assignment/update density inside a function | | Divergent Change | Manual review | Requires knowledge of responsibilities and reasons to change | | Shotgun Surgery | Change history | Requires observing one change spread across modules | | Feature Envy | Heuristic | Concentrated access through another object | | Data Clumps | Heuristic | Repeated parameter groups across functions | | Primitive Obsession | Heuristic | Large groups of primitive TypeScript parameters | | Repeated Switches | Heuristic | Repeated case-label shapes across the project | | Loops | Heuristic | Long imperative loops that may hide a transformation | | Lazy Element | Manual review | Usage and design intent are needed | | Speculative Generality | Manual review | Current and anticipated use must be understood | | Temporary Field | Heuristic | Empty-by-default fields used by only one method | | Message Chains | Heuristic | Member-access chain depth | | Middle Man | Heuristic | Ratio of methods that only delegate | | Insider Trading | Heuristic | Repeated access to private-looking foreign members | | Large Class | Heuristic | Class lines and method count | | Alternative Classes with Different Interfaces | Manual review | Semantic roles cannot be reliably inferred from syntax | | Data Class | Heuristic | Fields and accessors without detected behavior | | Refused Bequest | Heuristic | Subclass methods that reject inherited operations | | Comments | Heuristic | Comments that resemble disabled source code |

The conservative gaps are intentional. For example, import fan-out alone does not prove Shotgun Surgery, and an unused abstraction alone does not prove Speculative Generality.

Exit codes

  • 0: no finding at or above the configured failure threshold
  • 1: findings crossed failOn or --max-warnings
  • 2: the CLI itself could not run, such as an invalid option or missing path

Syntax errors are reported as error findings so one bad file does not prevent other files from being analyzed.

Programmatic API

import { analyze, defaultConfig } from 'code-scent';

const result = await analyze(['src/index.ts'], {
  cwd: process.cwd(),
  config: defaultConfig
});

for (const finding of result.findings) {
  console.log(finding.ruleId, finding.file, finding.line, finding.message);
}

Development

npm install
npm test
npm run check

The tests use Node’s built-in test runner. The only runtime dependency is Babel’s parser.

Publishing

After signing in to npm and choosing a new version, publish from a clean working tree:

npm test
npm run check
npm version patch
npm publish

npm publish is configured for public access and runs the same test and check commands through prepublishOnly. Verify the exact release contents first with npm pack --dry-run.

This project is not affiliated with or endorsed by Martin Fowler or Pearson.