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

@amashukov/eslint-plugin-mess-detector

v0.2.0

Published

ESLint plugin that fails the build on low-signal code patterns: inline narration, suppression directives, defensive nullish guards, tautological JSDoc, banal error wrappers, env branching, direct Date.now, type-only test assertions, TODO markers.

Readme

@amashukov/eslint-plugin-mess-detector

CI npm License Node

ESLint plugin that fails the build on the low-signal patterns that bloat a TypeScript / JavaScript codebase: inline narration, suppression directives, defensive nullish guards, tautological JSDoc, banal throw new Error(...) wrappers, runtime environment branching, scattered process.env reads, direct Date.now(), type-only test assertions, and TODO / FIXME markers.

Counterpart of go-lint for Go and rector-php-rules for PHP. Same philosophy, different syntax tree.


Why

Low-effort code tends to drift in the same direction every time:

  • Each line gets an inline // comment that paraphrases the line itself.
  • Every exported function gets a JSDoc that restates its signature in English (/** Add adds two numbers */).
  • x == null checks appear on values that, by their TypeScript type, can never be null or undefined.
  • Tests assert toBeDefined(), toBeTruthy(), or toBeInstanceOf(...) — checks the type system already does — instead of pinning the actual value.
  • Errors get wrapped in throw new Error("failed to read: " + err.message) — strictly worse than re-throwing the original because it lengthens the chain and drops the stack.
  • if (process.env.NODE_ENV === "production") branches sneak into production code, creating divergent test- and prod-only paths.
  • // eslint-disable-next-line appears next to anything the linter complained about.

This plugin is a single hard gate that flags every one of these in one pass. It does not autofix. The point is to make the human re-think the code, not regex it.


Install

npm install --save-dev @amashukov/eslint-plugin-mess-detector

Requires ESLint v9+ and Node 22+. For the type-aware rules (no-dead-nullish-guard, no-redundant-optional-chain) you also need @typescript-eslint/parser with parserOptions.project set.


Prerequisites — parsers

ESLint parses every file into an AST before any plugin rule runs. For source that is not plain JavaScript, the host project must register the matching parser in flat config — otherwise lint exits with Parsing error: Unexpected token and the plugin never gets a chance to inspect the code. This is the standard ESLint contract; mess-detector follows it just like every other plugin in the ecosystem.

The three common setups:

Plain TypeScript. Install @typescript-eslint/parser and register it for .ts files:

import mess from "@amashukov/eslint-plugin-mess-detector";
import tsParser from "@typescript-eslint/parser";

export default [
  { files: ["**/*.ts"], languageOptions: { parser: tsParser } },
  { files: ["**/*.ts"], plugins: { "mess-detector": mess }, rules: mess.configs.recommended.rules },
];

Vue single-file components. Install vue-eslint-parser (which delegates <script lang="ts"> blocks to @typescript-eslint/parser):

import mess from "@amashukov/eslint-plugin-mess-detector";
import vueParser from "vue-eslint-parser";
import tsParser from "@typescript-eslint/parser";

export default [
  {
    files: ["**/*.vue"],
    languageOptions: { parser: vueParser, parserOptions: { parser: tsParser } },
  },
  { plugins: { "mess-detector": mess }, rules: mess.configs.recommended.rules },
];

Nuxt. Add @nuxt/eslint to the modules list in nuxt.config.ts; nuxt prepare then emits .nuxt/eslint.config.mjs with TS + Vue parsers already wired. Wrap your config with withNuxt(...):

import mess from "@amashukov/eslint-plugin-mess-detector";
import withNuxt from "./.nuxt/eslint.config.mjs";

export default withNuxt({
  files: ["**/*.{js,mjs,cjs,ts,vue}"],
  plugins: { "mess-detector": mess },
  rules: mess.configs.recommended.rules,
});

If yarn lint reports Parsing error from any rule, the fix is a missing parser, not a plugin bug.


Usage (flat config, ESLint v9)

Plain (no type information)

// eslint.config.js
import mess from "@amashukov/eslint-plugin-mess-detector";

export default [
  mess.configs.recommended,
];

This enables the 11 non-type-aware rules.

With type information

// eslint.config.js
import mess from "@amashukov/eslint-plugin-mess-detector";
import tseslint from "typescript-eslint";

export default [
  ...tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        project: "./tsconfig.json",
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  mess.configs["recommended-typed"],
];

This adds the two type-aware rules on top.


Rules

| # | Rule | Type-aware | Catches | |---|---|---|---| | 1 | no-todo | no | TODO / FIXME / XXX / HACK markers (owned or not) | | 2 | no-suppression-comments | no | // eslint-disable*, // @ts-ignore, // @ts-expect-error, // @ts-nocheck | | 3 | no-inline-narration | no | comments inside function bodies | | 4 | no-process-env-outside-config | no | process.env.X outside config/ and *.config.* files | | 5 | no-env-branch | no | runtime branching on "prod" / "dev" / "test" strings | | 6 | no-direct-date-now | no | Date.now(), new Date(), performance.now() outside clock/ | | 7 | no-redundant-bool-return | no | if (c) return true; return false; | | 8 | no-banal-error-wrap | no | throw new Error("failed to X: " + err.message) | | 9 | no-catch-rethrow-banal | no | catch (e) { throw new Error(e.message); } | | 10 | no-type-only-assertion | no | expect(x).toBeDefined() / toBeInstanceOf(...) etc. | | 11 | no-tautological-jsdoc | no | JSDoc that restates the function name | | 12 | no-silent-fallback | no | ??, ??=, and \|\| with a literal default — silent fallbacks for missing values | | 13 | no-dead-nullish-guard | yes | x === null on a type that admits neither null nor undefined | | 14 | no-redundant-optional-chain | yes | ?. on a type that admits neither null nor undefined |


Configs

Two flat-config presets:

  • mess.configs.recommended — rules 1–12. Works on plain ESLint without parserOptions.project.
  • mess.configs["recommended-typed"]recommended plus the two type-aware rules. Requires @typescript-eslint/parser with parserOptions.project.

Overlap with widely-used plugins

Several of these patterns are partly covered elsewhere. This plugin keeps all 13 anyway because the value is "single hard gate, no plugin sprawl":

| Concern | Overlapping plugin / rule | This plugin | |---|---|---| | TODO / FIXME markers | core ESLint no-warning-comments (off by default, configurable) | no-todo (strict, no carve-outs) | | // eslint-disable* directives | eslint-plugin-eslint-comments/no-use | no-suppression-comments (strict, also @ts-*) | | Inline comments | core no-inline-comments (only same-line) | no-inline-narration (whole function body) | | process.env | core no-process-env | no-process-env-outside-config (glob carve-out) | | if (cond) return true | eslint-plugin-sonarjs/prefer-single-boolean-return | no-redundant-bool-return | | new Error(...) content | eslint-plugin-unicorn/error-message | no-banal-error-wrap (banal-verb regex) | | // @ts-ignore | @typescript-eslint/ban-ts-comment | rolled into no-suppression-comments | | ?. on non-nullable | @typescript-eslint/no-unnecessary-condition | no-redundant-optional-chain | | catch { throw ... } | @typescript-eslint/no-useless-catch | no-catch-rethrow-banal (also flags message-only wrap) |


Comparison with sibling repos

| Concern | go-lint | rector-php-rules | eslint-plugin-mess-detector | |---|---|---|---| | Inline narration | noinlinecomment | NoCommentsOutsideInterfaceMethodDocBlockRector | no-inline-narration | | Tautological doc | norobotgodoc | — | no-tautological-jsdoc | | Suppression directives | nolintdirective | NoPhpstanIgnoreRector | no-suppression-comments | | Env access outside config | nogetenv | NoSuperglobalAccessRector | no-process-env-outside-config | | Env branching in src | noenvbranch | NoEnvironmentCheckInSrcRector | no-env-branch | | Real-clock injection | notimenow | RequirePsrClockInterfaceRector | no-direct-date-now | | Type-only test assertions | notypeonlyassert | NoTypeOnlyAssertionsInTestsRector | no-type-only-assertion | | Banal error wrapping | noerrorwrapbanality | — | no-banal-error-wrap + no-catch-rethrow-banal | | TODO / FIXME markers | notodo | NoTodoCommentRector | no-todo | | if cond return true | noredundantif | — | no-redundant-bool-return | | Nullish guard on non-nullable | nodeadguard | — | no-dead-nullish-guard | | Redundant optional chain | — | — | no-redundant-optional-chain |


Design notes

  • No configuration file beyond ESLint's own. Each rule is either on or off via the flat-config block. Policy lives in eslint.config.js, not a side-car YAML.
  • No autofixers. Most findings need restructuring, not a regex. The point is to make the human re-think the code.
  • No per-line waiver. If a rule is wrong for your project, drop it from the config. Per-line // eslint-disable waivers turn into silent debt — and no-suppression-comments flags them anyway.
  • Self-hosted. The plugin's own source is linted by its own recommended-typed config with zero findings.

Development

make install   # docker-driven npm install
make build     # tsup → dist/index.{js,cjs,d.ts}
make test      # vitest
make lint      # self-host gate (eslint on src/)
make typecheck # tsc --noEmit

All targets run inside node:22-alpine via docker — no host Node required.


License

MIT — see LICENSE.