Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>TScannerNew to Visual Studio Code? Get it now.
TScanner

TScanner

lucasvtiradentes

|
22 installs
| (0) | Free
Code quality scanner for the AI-generated code era
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

tscanner logo
TScanner - VS Code Extension

Overview • Features • Motivation • Workflow • Quick Start • Usage
Configuration • Rules • Registry • Inspirations • Contributing • License

🎺 Overview

See code quality issues the moment you type, not after you ship. TScanner shows violations you customize in a sidebar panel with one-click navigation to each problem. Scan your whole project, just the files you changed in your current branch or only your current uncommited changes.

VS Code Extension Demo
issues detected in real time in the code editor

Other ways to use TScanner
Package Description
CLI

npm
Fast terminal scanning with pre-commit hook integration
GitHub Action

GitHub Marketplace
CICD integration with analysis summary attached to PR comments

⭐ Features

  • Your Rules, Enforced - 38 built-in checks + define your own with regex, scripts, or AI
  • Community Rules - Install pre-built rules from registry or share your own with the world
  • See Issues Instantly - Real-time feedback in code editor as you type, no manual scan needed
  • Copy for AI - Export issues to clipboard, paste into chat for bulk fixes
  • Multiple Scan Modes - Whole codebase, branch changes, uncommitted changes, or staged changes
  • Sub-second Scans - Rust engine processes hundreds of files in <1s, with smart caching
  • Not a Blocker - Issues are warnings by default; set as errors to fail CI/lint-staged

❓ Motivation

AI generates code fast, but it doesn't know your project's conventions, preferred patterns, or forbidden shortcuts. You end up reviewing the same issues over and over.

TScanner lets you define those rules once. Every AI-generated file, every PR, every save: automatically checked against your standards.

🔀 Workflow

Vision: Go fast with AI and know exactly what to fix before shipping. Detect bad patterns while reviewing code? Ask AI to create regex, script, or AI rules to catch it forever. Use the VSCode extension's "Copy Issues" button to get a ready-to-paste prompt and let your favorite AI tool fix everything. Before merging, see all issues at a glance in a PR comment from your CI/CD: nothing blocks by default, you decide what matters.

TScanner and the coding workflow
How TScanner fits into the average coding workflow

How does TScanner prevent issues from reaching production?
  • Code Editor: See issues in real-time while coding. Add to lint-staged to prevent committing errors.
  • Before PR: Check all issues in your branch compared to origin/main and fix them before opening a PR.
  • CI/CD: Every push to a PR is checked automatically. Get a single comment with clickable links to the exact lines.
Why does this matter?
  • Go fast with confidence: Know exactly what issues to fix before committing or merging.
  • Zero rejected PRs: Over time, eliminate PR rejections due to styling or poor code quality patterns.
  • AI-powered quality: Use AI rules to detect patterns that traditional linters miss, and let AI help fix AI-generated code.
  • Your job: Observe code patterns to enforce/avoid and add TScanner rules for that.
How TScanner maintains its own codebase?

We use TScanner to maintain this very codebase. Here's our setup:

Built-in rules (34 enabled): Standard code quality checks like no-explicit-any, prefer-const, no-console, etc.

Regex rules (3):

  • no-rust-deprecated: Block #[allow(deprecated)] in Rust code
  • no-rust-dead-code: Block #[allow(dead_code)] - remove unused code instead
  • no-process-env: Prevent direct process.env access

Script rules (8):

  • types-parity-match: Ensure TypeScript and Rust shared types are in sync
  • config-schema-match: Keep Rust config and TypeScript schema aligned
  • cli-builder-match: CLI builder must cover all CLI check flags
  • action-zod-match: GitHub Action inputs must match Zod validation
  • readme-toc-match: README table of contents must match all headings
  • rust-entry-simple: lib.rs and mod.rs should only contain module declarations
  • no-long-files: Files cannot exceed 300 lines
  • no-default-node-imports: Use named imports for Node.js modules

AI rules (2):

  • no-dead-code: Detect dead code patterns in Rust executors
  • find-enum-candidates: Find type unions that could be enums

TIP: Check the .tscanner/ folder to see the full config and script implementations.

How am I using this to improve my code at work?

I basically observe code patterns to enforce/avoid and add custom rules, here are my current rules:

regex rules:

"regex": {
  "no-nestjs-logger": {
    "pattern": "import\\s*\\{[^}]*Logger[^}]*\\}\\s*from\\s*['\"]@nestjs/common['\"]",
    "message": "Do not use NestJS Logger. Import from custom logger instead"
  },
  "no-typeorm-for-feature": {
    "pattern": "TypeOrmModule\\.forFeature\\(",
    "message": "Use api/src/way-type-orm.module.ts instead"
  },
  "avoid-typeorm-raw-queries": {
    "pattern": "await this\\.([^.]+)\\.manager\\.query\\(",
    "message": "Avoid using RawQueryBuilder. Use the repository instead",
    "severity": "error"
  },
  "no-static-zod-schema": {
    "pattern": "static\\s+zodSchema\\s*=",
    "message": "Remove 'static zodSchema' from class. The schema is already passed to createZodDto() and this property is redundant"
  }
}

script rules:

"script": {
  "entity-registered-in-typeorm-module": {
    "command": "npx tsx script-rules/entity-registered-in-typeorm-module.ts",
    "message": "Entity must be registered in way-type-orm.module.ts",
    "severity": "error",
    "include": ["api/src/**/*.entity.ts", "api/src/way-type-orm.module.ts"]
  },
  "entity-registered-in-setup-nest": {
    "command": "npx tsx script-rules/entity-registered-in-setup-nest.ts",
    "message": "Entity must be registered in setup-nest.ts for tests",
    "severity": "error",
    "include": ["api/src/**/*.entity.ts", "api/test/helpers/setup-nest.ts"]
  },
  "no-long-files": {
    "command": "npx tsx script-rules/no-long-files.ts",
    "message": "File exceeds 600 lines limit",
    "include": ["**/*.ts"]
  }
}

ai rules:

soon!

Note: my rules at work are not commited to the codebase, so I basically installed tscanner globally and move the .tscanner folder into the .gitignore file

🚀 Quick Start

  1. Install locally
npm install -D tscanner
  1. Initialize configuration
npx tscanner init

TIP: Use npx tscanner init --full for a complete config with example regex, script, and AI rules.

After that you can already install the extension:

  1. Install the extension:
Search "TScanner" in Extensions Install from marketplace
TScanner installation
VS Code
Open VSX
  1. Click TScanner icon in activity bar
  2. Issues appear automatically in the sidebar (if any)
  3. Click any issue to jump to its location

📖 Usage

View Modes

Organize results with 4 combinations:

By rule - flat list By rule - tree By file - flat list By file - tree
VS Code
VS Code
VS Code
VS Code

Copy issues by rule/file

Copy all issues to clipboard in a structured format, ready to paste into an AI agent for automatic fixes.

VS Code

Example output - by rule
# TScanner Issue Report

This is a report from TScanner, a CLI tool that detects code quality issues in TypeScript/JavaScript projects. Your task is to fix the issues listed below.

## Report Details

Filter: rule "no-console" | Mode: codebase mode | Issues: 3
CLI: tscanner check --rule no-console --group-by rule

Results:

Rules triggered:

  ● no-console: Unexpected call to console.error

Issues grouped by rule:

● no-console (3 issues, 2 files)

  assets/configs/example-no-long-files.ts (2 issues)
    ⚠ 46:3 → console.log(JSON.stringify({ issues }));
    ⚠ 50:3 → console.error(err);

  packages/cli/src/main.ts (1 issues)
    ⚠ 33:5 → console.error(err.message);

Scope:

  Rules: 1
  Files: 2 (0 cached, 2 scanned)


Results:

  Issues: 3 (⚠ 3)
  Triggered rules: 1 (● 1)
  Files with issues: 2
  Duration: 0ms

## Instructions

- Some fixes require changes in multiple files, not just where the issue is reported
- Consider fixing one issue at a time and verifying each fix
- The rule description explains what's wrong - understand it before fixing
- Line numbers are 1-indexed
Example output - by file
# TScanner Issue Report

This is a report from TScanner, a CLI tool that detects code quality issues in TypeScript/JavaScript projects. Your task is to fix the issues listed below.

## Report Details

Filter: file "packages/github-action/src/core/input-validator.ts" | Mode: codebase mode | Issues: 6
CLI: tscanner check --glob packages/github-action/src/core/input-validator.ts --group-by file

Results:

Rules triggered:

  ● prefer-nullish-coalescing: Use nullish coalescing (??) instead of logical OR (||). The || operator treats 0, "", and false as falsy, while ?? only checks for null/undefined.

Issues grouped by file:

packages/github-action/src/core/input-validator.ts - 6 issues - 1 rules

  ● prefer-nullish-coalescing (6 issues)
    ⚠ 44:20 → const timezone = githubHelper.getInput('timezone') || DEFAULT_INPUTS.timezone;
    ⚠ 45:22 → const configPath = githubHelper.getInput('config-path') || DEFAULT_INPUTS.configPath;
    ⚠ 46:27 → const tscannerVersion = githubHelper.getInput('tscanner-version') || DEFAULT_INPUTS.tscannerVersion;
    ⚠ 48:24 → const groupByInput = githubHelper.getInput('group-by') || DEFAULT_INPUTS.groupBy;
    ⚠ 54:23 → const aiModeInput = githubHelper.getInput('ai-mode') || AiExecutionMode.Ignore;
    ⚠ 78:53 → ...(mode === ScanMode.Branch && { targetBranch: targetBranch || DEFAULT_INPUTS.targetBranch }),

Scope:

  Rules: 1
  Files: 1 (0 cached, 1 scanned)


Results:

  Issues: 6 (⚠ 6)
  Triggered rules: 1 (● 1)
  Files with issues: 1
  Duration: 0ms

## Instructions

- Some fixes require changes in multiple files, not just where the issue is reported
- Consider fixing one issue at a time and verifying each fix
- The rule description explains what's wrong - understand it before fixing
- Line numbers are 1-indexed

Scan Modes

You have four scanning options, switchable via status bar click:

Codebase Uncommitted Branch
Analyze all files Scan staged + unstaged changes Compare to target branch

Status bar

VS Code

VS Code

Commands

Access via Command Palette (Ctrl/Cmd + Shift + P):

Command Keybinding
tscanner: Clear Scan Caches -
tscanner: Go to Next Issue -
tscanner: Go to Previous Issue -
tscanner: Refresh AI Issues (no cache) -
tscanner: Refresh Issues (no cache) -
tscanner: Show Logs -

Updating

The VS Code extension and CLI binary must be compatible to work correctly. If you see a version mismatch warning:

How to update?:

  1. update the npm package

Local installations:

npm install tscanner@latest
pnpm update tscanner --latest
yarn upgrade tscanner --latest

Global installations:

npm install -g tscanner@latest
pnpm update -g tscanner --latest
yarn global upgrade tscanner --latest
  1. update the version in the .tscanner/config.jsonc to match the new version or just use latest if you are going to regularly update it
"$schema": "https://unpkg.com/tscanner@0.0.0/schema.json", // -> tscanner@0.0.1 OR tscanner@latest
  1. restart vscode

Why update?

  • New extension features may require updated CLI binary
  • CLI updates may include bug fixes and performance improvements
  • Version mismatches can cause unexpected behavior

⚙️ Configuration

To scan your code, you need to set up the rules in the TScanner config folder.

Full configuration
{
  "$schema": "https://unpkg.com/tscanner@latest/schema.json",
  "rules": {
    "builtin": {
      "consistent-return": {},
      "max-function-length": {},
      "max-params": {},
      "no-absolute-imports": {},
      "no-alias-imports": {},
      "no-async-without-await": {},
      "no-console": {},
      "no-constant-condition": {},
      "no-default-export": {},
      "no-duplicate-imports": {},
      "no-dynamic-import": {},
      "no-else-return": {},
      "no-empty-class": {},
      "no-empty-function": {},
      "no-empty-interface": {},
      "no-explicit-any": {},
      "no-floating-promises": {},
      "no-forwarded-exports": {},
      "no-implicit-any": {},
      "no-inferrable-types": {},
      "no-nested-require": {},
      "no-nested-ternary": {},
      "no-non-null-assertion": {},
      "no-relative-imports": {},
      "no-return-await": {},
      "no-shadow": {},
      "no-single-or-array-union": {},
      "no-todo-comments": {},
      "no-unnecessary-type-assertion": {},
      "no-unreachable-code": {},
      "no-unused-vars": {},
      "no-useless-catch": {},
      "no-var": {},
      "prefer-const": {},
      "prefer-interface-over-type": {},
      "prefer-nullish-coalescing": {},
      "prefer-optional-chain": {},
      "prefer-type-over-interface": {}
    },
    "regex": {
      "example-no-console-log": {
        "pattern": "console\\.log",
        "message": "Remove console.log before committing"
      }
    },
    "script": {
      "example-no-long-files": {
        "command": "npx tsx script-rules/example-no-long-files.ts",
        "message": "File exceeds 300 lines limit",
        "include": ["packages/**/*.ts", "packages/**/*.rs"]
      }
    }
  },
  "aiRules": {
    "example-find-enum-candidates": {
      "prompt": "example-find-enum-candidates.md",
      "mode": "agentic",
      "message": "Type union could be replaced with an enum for better type safety",
      "severity": "warning",
      "include": ["**/*.ts"]
    }
  },
  "ai": {
    "provider": "claude"
  },
  "files": {
    "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
    "exclude": ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"]
  },
  "codeEditor": {
    "highlightErrors": true,
    "highlightWarnings": true,
    "highlightInfos": true,
    "highlightHints": true,
    "autoScanInterval": 0,
    "autoAiScanInterval": 0,
    "startupScan": "cached",
    "startupAiScan": "off"
  }
}
Minimal configuration
{
  "$schema": "https://unpkg.com/tscanner@latest/schema.json",
  "rules": {
    "builtin": {
      "no-explicit-any": {}
    },
    "regex": {},
    "script": {}
  },
  "aiRules": {},
  "files": {
    "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
    "exclude": ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"]
  }
}
Additional info

Per-rule file patterns: Each rule can have its own include/exclude patterns:

{
  "rules": {
    "builtin": {
      "no-console": { "exclude": ["src/logger.ts"] },
      "max-function-length": { "include": ["src/core/**/*.ts"] }
    }
  }
}

Inline disables:

// tscanner-ignore-next-line no-explicit-any
const data: any = fetchData();

// tscanner-ignore
// Entire file is skipped

📋 Rules

Rules are the core of TScanner. They define what to check, where to check, and how to report issues. Mix built-in rules with custom ones to enforce your team's standards.

Type Use Case Example
Built-in Common typescript anti-patterns no-explicit-any, prefer-const, no-console
Regex Simple text patterns for any file Match TODO comments, banned imports
Script Complex logic in any language (TS, Python, Rust, Go...) Enforce folder structure, type parity checks, enforce min/max lines per file
AI LLM-powered analysis for context-aware patterns (dead code, enum candidates, architectural violations) Detect potential enum candidates, check if a complex pattern was followed across multiple files

Built-in rules (38)

Type Safety (6)

Rule Description Options Also in
no-explicit-any

TypeScript only
Detects usage of TypeScript 'any' type (: any and as any). Using 'any' defeats the purpose of TypeScript's type system. ESLint Biome
no-implicit-any

TypeScript only
Detects function parameters without type annotations that implicitly have 'any' type.
no-inferrable-types

TypeScript only
Disallows explicit type annotations on variables initialized with literal values. TypeScript can infer these types automatically. ESLint Biome
no-non-null-assertion

TypeScript only
Disallows the non-null assertion operator (!). Use proper null checks or optional chaining instead. ESLint Biome
no-single-or-array-union

TypeScript only
Disallows union types that combine a type with its array form (e.g., string | string[], number | number[]). Prefer using a consistent type to avoid handling multiple cases in function implementations.
no-unnecessary-type-assertion

TypeScript only
Disallows type assertions on values that are already of the asserted type (e.g., "hello" as string, 123 as number). ESLint

Code Quality (13)

Rule Description Options Also in
max-function-length

Configurable
Enforces a maximum number of statements in functions. Long functions are harder to understand and maintain. maxLength: 50 ESLint
max-parameters

Configurable
Limits the number of parameters in a function. Functions with many parameters should use an options object instead. maxParams: 4 ESLint
no-async-without-await
Disallows async functions that don't use await. The async keyword is unnecessary if await is never used. ESLint Biome
no-console

Regex rule Configurable
Disallow the use of console methods. Console statements should be removed before committing to production. methods: [21 items] ESLint Biome
no-else-return
Disallows else blocks after return statements. The else is unnecessary since the function already returned. ESLint Biome
no-empty-class
Disallows empty classes without methods or properties.
no-empty-function
Disallows empty functions and methods. Empty functions are often leftovers from incomplete code. ESLint
no-empty-interface

TypeScript only
Disallows empty interface declarations. Empty interfaces are equivalent to {} and usually indicate incomplete code. ESLint Biome
no-nested-ternary
Disallows nested ternary expressions. Nested ternaries are hard to read and should be replaced with if-else statements. ESLint Biome
no-return-await
Disallows redundant 'return await' in async functions. The await is unnecessary since the function already returns a Promise. ESLint
no-todo-comments

Regex rule Configurable
Detects TODO comments (case insensitive). Configure 'keywords' option to detect additional markers like FIXME, HACK, etc. keywords: [1 items] ESLint
no-unused-variables
Detects variables that are declared but never used in the code. ESLint Biome
no-useless-catch
Disallows catch blocks that only rethrow the caught error. Remove the try-catch or add meaningful error handling. ESLint Biome

Bug Prevention (4)

Rule Description Options Also in
consistent-return
Requires consistent return behavior in functions. Either all code paths return a value or none do. ESLint
no-constant-condition
Disallows constant expressions in conditions (if/while/for/ternary). Likely a programming error. ESLint Biome
no-floating-promises

TypeScript only
Disallows floating promises (promises used as statements without await, .then(), or .catch()). Unhandled promises can lead to silent failures. ESLint Biome
no-unreachable-code
Detects code after return, throw, break, or continue statements. This code will never execute. ESLint Biome

Variables (3)

Rule Description Options Also in
no-shadow
Disallows variable declarations that shadow variables in outer scopes. Shadowing can lead to confusing code and subtle bugs. ESLint
no-var
Disallows the use of 'var' keyword. Use 'let' or 'const' instead for block-scoped variables. ESLint Biome
prefer-const
Suggests using 'const' instead of 'let' when variables are never reassigned. ESLint Biome

Imports (8)

Rule Description Options Also in
no-absolute-imports
Disallows absolute imports without alias. Prefer relative or aliased imports.
no-alias-imports
Disallows aliased imports (starting with @). Prefer relative imports.
no-default-export
Disallows default exports. Named exports are preferred for better refactoring support and explicit imports. Biome
no-duplicate-imports
Disallows multiple import statements from the same module. Merge them into a single import. ESLint Biome
no-dynamic-import
Disallows dynamic import() expressions. Dynamic imports make static analysis harder and can impact bundle optimization.
no-forwarded-exports
Disallows re-exporting from other modules. This includes direct re-exports (export { X } from 'module'), star re-exports (export * from 'module'), and re-exporting imported values. Biome
no-nested-require
Disallows require() calls inside functions, blocks, or conditionals. Require statements should be at the top level for static analysis. ESLint
no-relative-imports
Detects relative imports (starting with './' or '../'). Prefer absolute imports with @ prefix for better maintainability.

Style (4)

Rule Description Options Also in
prefer-interface-over-type

TypeScript only
Suggests using 'interface' keyword instead of 'type' for consistency. ESLint
prefer-nullish-coalescing
Suggests using nullish coalescing (??) instead of logical OR (||) for default values. The || operator treats 0, "", and false as falsy, which may not be intended. ESLint
prefer-optional-chain
Suggests using optional chaining (?.) instead of logical AND (&&) chains for null checks. ESLint Biome
prefer-type-over-interface

TypeScript only
Suggests using 'type' keyword instead of 'interface' for consistency. Type aliases are more flexible and composable. ESLint
Regex rules examples

Define patterns to match in your code using regular expressions:

Config (.tscanner/config.jsonc):

{
  "rules": {
    "regex": {
      "no-rust-deprecated": {
        "pattern": "allow\\(deprecated\\)",
        "message": "No deprecated methods",
        "include": ["packages/rust-core/**/*.rs"]
      },
      "no-process-env": {
        "pattern": "process\\.env",
        "message": "No process env"
      },
      "no-debug-logs": {
        "pattern": "console\\.(log|debug|info)",
        "message": "Remove debug statements",
        "exclude": ["**/*.test.ts"]
      }
    }
  }
}
Script rules examples

Run custom scripts in any language (TypeScript, Python, Rust, Go, etc.) that reads JSON from stdin and outputs JSON to stdout.

Input contract (received via stdin):

{
  "files": [
    {
      "path": "src/utils.ts",
      "content": "export function add(a: number, b: number)...",
      "lines": ["export function add(a: number, b: number)", "..."]
    }
  ],
  "options": { "maxLines": 300 },
  "workspaceRoot": "/path/to/project"
}

Output contract (expected via stdout):

{
  "issues": [
    { "file": "src/utils.ts", "line": 10, "message": "Issue description" }
  ]
}

Config (.tscanner/config.jsonc):

{
  "rules": {
    "script": {
      "no-long-files": {
        "command": "npx tsx script-rules/no-long-files.ts",
        "message": "File exceeds 300 lines limit",
        "include": ["**/*.ts", "**/*.rs", "**/*.py", "**/*.go"]
      }
    }
  }
}
TypeScript example
#!/usr/bin/env npx tsx
import { stdin } from 'node:process';

async function main() {
  let data = '';
  for await (const chunk of stdin) data += chunk;

  const input = JSON.parse(data);
  const issues = [];

  for (const file of input.files) {
    if (file.lines.length > 300) {
      issues.push({ file: file.path, line: 301, message: `File exceeds 300 lines` });
    }
  }

  console.log(JSON.stringify({ issues }));
}
main().catch((err) => {
  console.error(err);
  process.exit(1);
});
Python example
#!/usr/bin/env python3
import json, sys

def main():
    input_data = json.loads(sys.stdin.read())
    issues = []

    for file in input_data["files"]:
        if len(file["lines"]) > 300:
            issues.append({"file": file["path"], "line": 301, "message": "File exceeds 300 lines"})

    print(json.dumps({"issues": issues}))

if __name__ == "__main__":
    main()
Rust example
#!/usr/bin/env rust-script
use std::io::{self, Read};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct ScriptFile { path: String, lines: Vec<String> }

#[derive(Deserialize)]
struct ScriptInput { files: Vec<ScriptFile> }

#[derive(Serialize)]
struct ScriptIssue { file: String, line: usize, message: String }

#[derive(Serialize)]
struct ScriptOutput { issues: Vec<ScriptIssue> }

fn main() -> io::Result<()> {
    let mut data = String::new();
    io::stdin().read_to_string(&mut data)?;
    let input: ScriptInput = serde_json::from_str(&data).unwrap();
    let mut issues = Vec::new();

    for file in input.files {
        if file.lines.len() > 300 {
            issues.push(ScriptIssue { file: file.path, line: 301, message: "File exceeds 300 lines".into() });
        }
    }

    println!("{}", serde_json::to_string(&ScriptOutput { issues }).unwrap());
    Ok(())
}

💡 See real examples in the .tscanner/script-rules/ and registry/script-rules/ folders.

AI rules examples

Use AI prompts (markdown files) to perform semantic code analysis. Works with any AI provider (Claude, OpenAI, Ollama, etc.).

Modes - How files are passed to the AI: | Mode | Description | Best for | |------|-------------|----------| | paths | Only file paths (AI reads files via tools) | Large codebases, many files | | content | Full file content in prompt | Small files, quick analysis | | agentic | Paths + AI can explore freely | Cross-file analysis, complex patterns |

Placeholders - Use in your prompt markdown: | Placeholder | Replaced with | |-------------|---------------| | {{FILES}} | List of files to analyze (required) | | {{OPTIONS}} | Custom options from config (optional) |

Output contract - AI must return JSON:

{
  "issues": [
    { "file": "src/utils.ts", "line": 10, "column": 1, "message": "Description" }
  ]
}

Config (.tscanner/config.jsonc):

{
  "aiRules": {
    "find-enum-candidates": {
      "prompt": "find-enum-candidates.md",
      "mode": "agentic",
      "message": "Type union could be replaced with an enum",
      "severity": "warning",
      "include": ["**/*.ts", "**/*.tsx", "**/*.rs"]
    },
    "no-dead-code": {
      "prompt": "no-dead-code.md",
      "mode": "content",
      "message": "Dead code detected",
      "severity": "error",
      "include": ["**/*.rs"],
      "options": { "allowTestFiles": true }
    }
  },
  "ai": {
    "provider": "claude"
  }
}
Prompt example (agentic mode)
# Enum Candidates Detector

Find type unions that could be replaced with enums.

## What to look for

1. String literal unions: \`type Status = 'pending' | 'active'\`
2. Repeated string literals across files
3. Type unions used as discriminators

## Exploration hints

- Check how the type is used across files
- Look for related constants

---

## Files

{{FILES}}
Prompt example (with options)
# Dead Code Detector

Detect dead code patterns.

## Rules

1. No \`#[allow(dead_code)]\` attributes
2. No unreachable code after return/break

## Options

{{OPTIONS}}

## Files

{{FILES}}

💡 See real examples in the .tscanner/ai-rules/ and registry/ai-rules/ folders.

📦 Registry

The registry is a collection of community rules ready to install with a single command.

npx tscanner registry                     # List all available rules (and you chose the ones you want to install)
npx tscanner registry no-long-files       # Install a specific rule
npx tscanner registry --kind script       # Filter by type (ai, script, regex)
npx tscanner registry --category security # Filter by category
npx tscanner registry --latest            # Use rules from main branch instead of current version

Available rules (5)

Rule Type Language Description
find-enum-candidates ai Markdown Find string literal unions that could be replaced with enums
no-long-files script TypeScript Enforce maximum lines per file limit
no-empty-files script Python Enforce minimum lines per file
no-fixme-comments script Rust Disallow FIXME/XXX comments in code
no-process-env regex - Disallow direct process.env access

Want to share your rule? Open a PR adding your rule to the registry/ folder. Once merged, everyone can install it with npx tscanner registry your-rule-name.

💡 Inspirations

  • Biome - High-performance Rust-based linter and formatter for web projects
  • ESLint - Find and fix problems in your JavaScript code
  • Vitest - Next generation testing framework powered by Vite
  • VSCode Bookmarks - Bookmarks Extension for Visual Studio Code
How each project was used?
  • Biome:
    • multi-crate Rust architecture (cli, core, server separation)
    • LSP server implementation for real-time IDE diagnostics
    • parallel file processing with Rayon
    • SWC parser integration for JavaScript/TypeScript AST
    • visitor pattern for AST node traversal
    • file-level result caching strategy
  • ESLint:
    • inline suppression system (disable-next-line, disable-file patterns)
    • precursor on javascript linting concepts
    • inspiration for rule ideas and detection patterns
  • Vitest:
    • glob pattern matching techniques for file discovery
  • VSCode Bookmarks:
    • sidebar icon badge displaying issue count
Notes about the huge impact Biome has on this project
This project only makes sense because it is fast, and it can only be fast because we applied the same techniques from the amazing Biome project. Once you experience a project powered by Biome and compare it to the traditional ESLint + Prettier setup, it feels like we were being fooled our entire careers. The speed difference is so dramatic that going back to the old tools feels almost unbearable. I am deeply grateful to the Biome team for open-sourcing such an incredible project and paving the way for high-performance JavaScript tooling.

🤝 Contributing

Contributions are welcome! See CONTRIBUTING.md for setup instructions and development workflow.

📜 License

MIT License - see LICENSE file for details.


LinkedIn Gmail X Github
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft