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

lintcn

v0.7.0

Published

The shadcn for type-aware TypeScript lint rules. Browse, pick, and copy rules into your project.

Readme

lintcn

The shadcn for type-aware TypeScript lint rules. Powered by tsgolint.

Add rules by URL, own the source, customize freely. Rules are Go files that use the TypeScript type checker for deep analysis — things ESLint can't do.

Install

npm install -D lintcn

Usage

# Add a rule folder from tsgolint
npx lintcn add https://github.com/oxc-project/tsgolint/tree/main/internal/rules/no_floating_promises

# Add by file URL (auto-fetches the whole folder)
npx lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go

# Lint your project
npx lintcn lint

# Lint with a specific tsconfig
npx lintcn lint --tsconfig tsconfig.build.json

# List installed rules
npx lintcn list

# Remove a rule
npx lintcn remove no-floating-promises

# Clean cached tsgolint source + binaries
npx lintcn clean

Browse all 50+ available built-in rules in the tsgolint rules directory.

How it works

Each rule lives in its own subfolder under .lintcn/. You own the source — edit, customize, delete.

my-project/
├── .lintcn/
│   ├── .gitignore                          ← ignores generated Go files
│   ├── no_floating_promises/
│   │   ├── no_floating_promises.go         ← rule source (committed)
│   │   ├── no_floating_promises_test.go    ← tests (committed)
│   │   └── options.go                      ← rule options struct
│   ├── await_thenable/
│   │   ├── await_thenable.go
│   │   └── await_thenable_test.go
│   └── my_custom_rule/
│       └── my_custom_rule.go
├── src/
│   └── ...
├── tsconfig.json
└── package.json

When you run npx lintcn lint, the CLI:

  1. Scans .lintcn/*/ subfolders for rule definitions
  2. Generates a Go workspace with your custom rules
  3. Compiles a custom binary (cached — rebuilds only when rules change)
  4. Runs the binary against your project

You can run lintcn lint from any subdirectory — it walks up to find .lintcn/ and lints the cwd project.

Writing custom rules

To help AI agents write and modify rules, install the lintcn skill:

npx skills add remorses/lintcn

This gives your AI agent the full tsgolint rule API reference — AST visitors, type checker, reporting, fixes, and testing patterns.

Every rule lives in a subfolder under .lintcn/ with the package name matching the folder:

// .lintcn/no_unhandled_error/no_unhandled_error.go

// lintcn:name no-unhandled-error
// lintcn:description Disallow discarding Error-typed return values

package no_unhandled_error

import (
    "github.com/microsoft/typescript-go/shim/ast"
    "github.com/microsoft/typescript-go/shim/checker"
    "github.com/typescript-eslint/tsgolint/internal/rule"
    "github.com/typescript-eslint/tsgolint/internal/utils"
)

var NoUnhandledErrorRule = rule.Rule{
    Name: "no-unhandled-error",
    Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
        return rule.RuleListeners{
            ast.KindExpressionStatement: func(node *ast.Node) {
                expression := ast.SkipParentheses(node.AsExpressionStatement().Expression)

                if ast.IsVoidExpression(expression) {
                    return // void = intentional discard
                }

                innerExpr := expression
                if ast.IsAwaitExpression(innerExpr) {
                    innerExpr = ast.SkipParentheses(innerExpr.Expression())
                }
                if !ast.IsCallExpression(innerExpr) {
                    return
                }

                t := ctx.TypeChecker.GetTypeAtLocation(expression)

                if utils.IsTypeFlagSet(t, checker.TypeFlagsVoid|checker.TypeFlagsUndefined|checker.TypeFlagsNever) {
                    return
                }

                for _, part := range utils.UnionTypeParts(t) {
                    if utils.IsErrorLike(ctx.Program, ctx.TypeChecker, part) {
                        ctx.ReportNode(node, rule.RuleMessage{
                            Id:          "noUnhandledError",
                            Description: "Error-typed return value is not handled.",
                        })
                        return
                    }
                }
            },
        }
    },
}

This catches code like:

// error — result discarded, Error not handled
getUser("id"); // returns Error | User
await fetchData("/api"); // returns Promise<Error | Data>

// ok — result is checked
const user = getUser("id");
if (user instanceof Error) return user;

// ok — explicitly discarded
void getUser("id");

Warning severity

Rules can be configured as warnings instead of errors:

  • Don't fail CI — warnings produce exit code 0
  • Only shown for git-changed files — warnings for unchanged files are silently skipped

This lets you adopt new rules gradually. In a large codebase, enabling a rule as an error means hundreds of violations at once. As a warning, you only see violations in files you're actively changing — fixing issues in new code without blocking the build.

Configuring a rule as a warning

Add // lintcn:severity warn to the rule's Go file:

// lintcn:name no-unhandled-error
// lintcn:severity warn
// lintcn:description Disallow discarding Error-typed return values

Rules without // lintcn:severity default to error.

When warnings are shown

By default, lintcn lint runs git diff to find changed and untracked files. Warnings are only printed for files in that list:

# Warnings only for files in git diff (default)
npx lintcn lint

# Warnings for ALL files, ignoring git diff
npx lintcn lint --all-warnings

| Scenario | Warnings shown? | | ---------------------------------- | ----------------- | | File is in git diff or untracked | Yes | | File is committed and unchanged | No | | --all-warnings flag is passed | Yes, all files | | Git is not installed or not a repo | No warnings shown | | Clean git tree (no changes) | No warnings shown |

Workflow

  1. Add a new rule with lintcn add
  2. Set it to // lintcn:severity warn in the Go source
  3. Run lintcn lint — only see warnings in files you're currently editing
  4. Fix warnings as you touch files naturally
  5. Once the codebase is clean, change to // lintcn:severity error (or remove the directive) to enforce it

Version pinning

Pin lintcn in your package.json — do not use ^ or ~:

{
  "devDependencies": {
    "lintcn": "0.5.0"
  }
}

Each lintcn release bundles a specific tsgolint version. Updating lintcn can change the underlying tsgolint API, which may cause your rules to no longer compile. Always update consciously:

  1. Check the changelog for tsgolint version changes
  2. Run npx lintcn build after updating to verify your rules still compile
  3. Fix any compilation errors before committing

CI Setup

The first lintcn lint compiles a custom Go binary (~30s). Subsequent runs use the cached binary (<1s). Cache ~/.cache/lintcn/ and Go's build cache to keep CI fast.

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

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

      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Cache lintcn binary + Go build cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/lintcn
            ~/go/pkg
          key: lintcn-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.lintcn/**/*.go') }}
          restore-keys: |
            lintcn-${{ runner.os }}-${{ runner.arch }}-

      - run: npm ci
      - run: npx lintcn lint

The cache key includes a hash of your rule files — when rules change, the binary is recompiled. The restore-keys fallback ensures Go's build cache is still used even when rules change, so recompilation takes ~1s instead of 30s.

Prerequisites

  • Node.js — for the CLI
  • Go — for compiling rules (go.dev/dl)

Go is only needed for lintcn lint / lintcn build. Adding and listing rules works without Go.

License

MIT