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

ts-twin

v0.1.0

Published

Find structurally similar JS/TS functions, even after renaming/refactoring

Downloads

256

Readme

ts-twin

Find structurally similar JavaScript and TypeScript functions.

ts-twin parses source files, extracts function bodies, and compares their AST structure using TF-IDF weighted shingles. It is designed to find near-duplicate functions even when names, formatting, and small implementation details differ.

Purpose

Most copy-paste detectors report duplicated text ranges. That is useful for finding repeated blocks, but it often misses a different refactoring signal: separate functions that have the same structure after identifiers and local details are normalized.

ts-twin focuses on that case. It reports groups of related functions, not raw line ranges, so the output is aimed at code review, refactoring, and automated maintenance workflows.

Not every match should become an abstraction. In those cases, extract only the shared logic when it improves readability, or suppress the function with // ts-twin-ignore: <reason>.

Comparison

Unlike text-based clone detectors such as jscpd, ts-twin focuses specifically on structurally similar JavaScript and TypeScript functions.

Features

  • Structural comparison based on AST shape rather than text.
  • Identifier normalization for parameters and local variables.
  • TF-IDF weighting so common boilerplate contributes less to the score.
  • Fast parsing through oxc-parser.
  • Inline suppression with // ts-twin-ignore: <reason>.

Installation

yarn add -D ts-twin
pnpm add -D ts-twin
npm install -D ts-twin

or run with npx without installing:

npx ts-twin

CLI

Description
  Find structurally similar JavaScript and TypeScript functions.

Usage
  $ ts-twin [root] [options]

Options
  --min-score             Similarity threshold (0-1). Practical range: 0.75-0.95.  (default 0.82)
  --include-tests         Include test, spec, and stories files.
  --fail-on-duplicates    Exit 1 when duplicate groups are found.
  --json                  Print JSON report.
  --preview               Print syntax-highlighted source previews.
  --preview-lines         Maximum lines per preview snippet.  (default 80)
  --preview-theme         Shiki theme for preview snippets.  (default vitesse-dark)
  -v, --version           Displays current version
  -h, --help              Displays this message

Examples
  $ ts-twin --min-score 0.9
  $ ts-twin --preview
  $ ts-twin apps/volvable --include-tests

Preview mode is intended for local terminal use. It keeps the normal match summary, then prints the matched function bodies below each location. When stdout is not a TTY, snippets are printed without ANSI colors.

Library API

import {
  findDuplicateFunctions,
  findDuplicateFunctionsInSources,
} from 'ts-twin';

const diskReport = await findDuplicateFunctions({
  root: './src',
  minScore: 0.85,
});

const sourceReport = findDuplicateFunctionsInSources([
  { file: 'a.ts', source: 'export function foo() { ... }' },
  { file: 'b.ts', source: 'export function bar() { ... }' },
]);

for (const group of diskReport.groups) {
  console.log(`Score: ${group.score.toFixed(2)}`);
  for (const fn of group.functions) {
    console.log(`  ${fn.file}:${fn.line} ${fn.name}`);
  }
}

Suppression

Add a ts-twin-ignore: comment above or inside a function to exclude it from the report.

// ts-twin-ignore: intentionally duplicated for performance
export function fastPath(value: string) {
  // ...
}

How It Works

  1. Source files are parsed with oxc-parser.
  2. Function bodies are encoded into canonical token streams.
  3. Parameters and local identifiers are normalized.
  4. Token streams are converted into overlapping 5-gram shingles.
  5. Shingles are weighted with TF-IDF.
  6. Candidate functions are compared with weighted Jaccard similarity.
  7. Similar functions are grouped with union-find.

Performance benchmarks

Recent synthetic benchmark results:

| Corpus | Functions | Median time | Throughput | |---|---:|---:|---:| | Templated | 500 | 294 ms | 1,703 functions/s | | Templated | 2,000 | 1.56 s | 1,282 functions/s | | Templated | 5,000 | 6.18 s | 809 functions/s | | Templated | 10,000 | 18.8 s | 532 functions/s |

The templated corpus is intentionally repetitive, so it stresses the duplicate comparison phase more than a typical mixed codebase.

License

MIT