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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sylphx/synth-metrics

v0.2.3

Published

Code metrics and complexity analysis for Synth AST - works across all languages

Downloads

749

Readme

@sylphx/synth-metrics

Code metrics and complexity analysis for Synth AST - works across all languages.

Features

  • Universal Metrics - Calculate metrics for any language
  • 📊 Comprehensive Analysis - Basic, complexity, Halstead, and maintainability metrics
  • 🎯 Function-Level - Per-function complexity and metrics
  • 🚀 Language-Agnostic - Works on Synth's universal AST
  • 📈 Industry Standard - Cyclomatic complexity, Halstead volume, maintainability index
  • Fast - Leverages Synth's performance-optimized AST

Installation

npm install @sylphx/synth-metrics

Usage

Quick Start

import { analyze } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'

const tree = parse(`
function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}
`)

const metrics = analyze(tree)

console.log(metrics.basic.loc)              // Lines of code
console.log(metrics.complexity.cyclomatic)  // Cyclomatic complexity
console.log(metrics.maintainability.index)  // Maintainability index

Full Report

import { report } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'

const tree = parse(sourceCode)
const metricsReport = report(tree, 'example.js')

console.log(metricsReport)
// {
//   file: 'example.js',
//   language: 'javascript',
//   metrics: { basic, complexity, halstead, maintainability },
//   functions: [{ name, loc, complexity, params, ... }],
//   timestamp: 1234567890
// }

Metrics Categories

Basic Metrics

interface BasicMetrics {
  loc: number          // Total lines of code
  sloc: number         // Source lines (non-comment, non-blank)
  cloc: number         // Comment lines
  blank: number        // Blank lines
  nodes: number        // Total AST nodes
  maxDepth: number     // Maximum nesting depth
  avgDepth: number     // Average node depth
}

Example:

const metrics = analyze(tree)

console.log(`Total lines: ${metrics.basic.loc}`)
console.log(`Source lines: ${metrics.basic.sloc}`)
console.log(`Comments: ${metrics.basic.cloc}`)
console.log(`Blank lines: ${metrics.basic.blank}`)
console.log(`Max nesting: ${metrics.basic.maxDepth}`)

Complexity Metrics

interface ComplexityMetrics {
  cyclomatic: number      // Cyclomatic complexity
  decisionPoints: number  // Number of decision points
  cognitive: number       // Cognitive complexity (weighted)
}

Cyclomatic Complexity - Measures number of linearly independent paths:

  • Formula: E - N + 2P or simply decision points + 1
  • Decision points: if, else, for, while, case, catch, &&, ||, ?

Cognitive Complexity - Measures how difficult code is to understand:

  • Weighted by nesting level
  • Nested conditions add more complexity

Example:

const metrics = analyze(tree)

console.log(`Cyclomatic complexity: ${metrics.complexity.cyclomatic}`)
console.log(`Decision points: ${metrics.complexity.decisionPoints}`)
console.log(`Cognitive complexity: ${metrics.complexity.cognitive}`)

if (metrics.complexity.cyclomatic > 10) {
  console.warn('High complexity - consider refactoring')
}

Halstead Complexity Metrics

interface HalsteadMetrics {
  n1: number              // Distinct operators
  n2: number              // Distinct operands
  N1: number              // Total operators
  N2: number              // Total operands
  vocabulary: number      // n1 + n2
  length: number          // N1 + N2
  calculatedLength: number
  volume: number          // Length * log2(vocabulary)
  difficulty: number      // (n1/2) * (N2/n2)
  effort: number          // Volume * difficulty
  time: number            // Time to program (seconds)
  bugs: number            // Estimated bugs
}

Example:

const metrics = analyze(tree)

console.log(`Program volume: ${metrics.halstead.volume.toFixed(2)}`)
console.log(`Difficulty: ${metrics.halstead.difficulty.toFixed(2)}`)
console.log(`Est. bugs: ${metrics.halstead.bugs.toFixed(3)}`)
console.log(`Est. time: ${(metrics.halstead.time / 60).toFixed(1)} minutes`)

Maintainability Metrics

interface MaintainabilityMetrics {
  index: number        // 0-100, higher is better
  level: 'low' | 'moderate' | 'high' | 'very high'
  maintainable: boolean  // index >= 20
}

Maintainability Index - Microsoft's metric:

  • Formula: 171 - 5.2 * ln(V) - 0.23 * G - 16.2 * ln(LOC)
  • Range: 0-100 (higher is better)
  • Levels:
    • 85-100: Low difficulty (excellent)
    • 65-84: Moderate difficulty (good)
    • 20-64: High difficulty (needs attention)
    • 0-19: Very high difficulty (critical)

Example:

const metrics = analyze(tree)

console.log(`Maintainability: ${metrics.maintainability.index.toFixed(2)}`)
console.log(`Level: ${metrics.maintainability.level}`)

if (!metrics.maintainability.maintainable) {
  console.error('Code is not maintainable!')
}

Function-Level Metrics

interface FunctionMetrics {
  name: string
  location: { start, end }
  loc: number          // Lines in function
  complexity: number   // Function complexity
  params: number       // Parameter count
  maxDepth: number     // Max nesting in function
}

Example:

const metricsReport = report(tree)

for (const fn of metricsReport.functions) {
  console.log(`Function: ${fn.name}`)
  console.log(`  LOC: ${fn.loc}`)
  console.log(`  Complexity: ${fn.complexity}`)
  console.log(`  Parameters: ${fn.params}`)

  if (fn.complexity > 10) {
    console.warn(`  ⚠️ High complexity!`)
  }
  if (fn.params > 5) {
    console.warn(`  ⚠️ Too many parameters!`)
  }
}

API

analyze(tree)

Analyze a tree and return all metrics.

import { analyze } from '@sylphx/synth-metrics'

const metrics = analyze(tree)

report(tree, file?)

Generate a full metrics report with timestamp and file info.

import { report } from '@sylphx/synth-metrics'

const metricsReport = report(tree, 'example.js')

MetricsAnalyzer

Advanced usage with analyzer instance.

import { MetricsAnalyzer } from '@sylphx/synth-metrics'

const analyzer = new MetricsAnalyzer()
const metrics = analyzer.analyze(tree)
const metricsReport = analyzer.report(tree, 'example.js')

Examples

Check Code Quality

import { analyze } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'

const tree = parse(sourceCode)
const metrics = analyze(tree)

// Check maintainability
if (metrics.maintainability.index < 20) {
  console.error('❌ Code is not maintainable')
  process.exit(1)
}

// Check complexity
if (metrics.complexity.cyclomatic > 15) {
  console.warn('⚠️ High cyclomatic complexity')
}

// Check size
if (metrics.basic.loc > 500) {
  console.warn('⚠️ File is too large')
}

CI/CD Integration

import { report } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'
import { readFileSync } from 'fs'

const source = readFileSync('src/index.js', 'utf-8')
const tree = parse(source)
const metricsReport = report(tree, 'src/index.js')

// Check quality gates
const passed =
  metricsReport.metrics.maintainability.index >= 65 &&
  metricsReport.metrics.complexity.cyclomatic <= 15

if (!passed) {
  console.error('Quality gates failed!')
  console.log(JSON.stringify(metricsReport, null, 2))
  process.exit(1)
}

console.log('✅ Quality gates passed')

Generate Report for All Files

import { report } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'
import { readdirSync, readFileSync, writeFileSync } from 'fs'

const files = readdirSync('src').filter(f => f.endsWith('.js'))
const reports = []

for (const file of files) {
  const source = readFileSync(`src/${file}`, 'utf-8')
  const tree = parse(source)
  const metricsReport = report(tree, file)
  reports.push(metricsReport)
}

// Save reports
writeFileSync('metrics.json', JSON.stringify(reports, null, 2))

// Summary
const avgComplexity = reports.reduce((sum, r) =>
  sum + r.metrics.complexity.cyclomatic, 0) / reports.length

console.log(`Average complexity: ${avgComplexity.toFixed(2)}`)

Compare Metrics Before/After

import { analyze } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'

const beforeTree = parse(beforeCode)
const afterTree = parse(afterCode)

const beforeMetrics = analyze(beforeTree)
const afterMetrics = analyze(afterTree)

console.log('Complexity:')
console.log(`  Before: ${beforeMetrics.complexity.cyclomatic}`)
console.log(`  After: ${afterMetrics.complexity.cyclomatic}`)
console.log(`  Change: ${afterMetrics.complexity.cyclomatic - beforeMetrics.complexity.cyclomatic}`)

console.log('Maintainability:')
console.log(`  Before: ${beforeMetrics.maintainability.index.toFixed(2)}`)
console.log(`  After: ${afterMetrics.maintainability.index.toFixed(2)}`)

Function Complexity Report

import { report } from '@sylphx/synth-metrics'
import { parse } from '@sylphx/synth-js'

const tree = parse(sourceCode)
const metricsReport = report(tree)

// Sort functions by complexity
const sorted = metricsReport.functions.sort((a, b) => b.complexity - a.complexity)

console.log('Top 10 most complex functions:')
for (const fn of sorted.slice(0, 10)) {
  console.log(`  ${fn.name}: complexity ${fn.complexity}`)
}

Language-Agnostic Analysis

import { analyze } from '@sylphx/synth-metrics'
import { parse as parseJS } from '@sylphx/synth-js'
import { parse as parsePy } from '@sylphx/synth-python'
import { parse as parseGo } from '@sylphx/synth-go'

// Analyze JavaScript
const jsTree = parseJS(jsCode)
const jsMetrics = analyze(jsTree)

// Analyze Python
const pyTree = parsePy(pyCode)
const pyMetrics = analyze(pyTree)

// Analyze Go
const goTree = parseGo(goCode)
const goMetrics = analyze(goTree)

// Compare across languages
console.log('Complexity comparison:')
console.log(`  JavaScript: ${jsMetrics.complexity.cyclomatic}`)
console.log(`  Python: ${pyMetrics.complexity.cyclomatic}`)
console.log(`  Go: ${goMetrics.complexity.cyclomatic}`)

Interpreting Metrics

Cyclomatic Complexity

  • 1-10: Simple, well-structured code
  • 11-20: Moderately complex, acceptable
  • 21-50: Complex, needs refactoring
  • 51+: Very complex, high risk

Maintainability Index

  • 85-100: Highly maintainable (green)
  • 65-84: Moderately maintainable (yellow)
  • 20-64: Difficult to maintain (orange)
  • 0-19: Unmaintainable (red)

Function Metrics

  • Parameters: Ideally ≤ 3, max 5
  • LOC: Ideally ≤ 50, max 100
  • Complexity: Ideally ≤ 5, max 10
  • Depth: Ideally ≤ 3, max 5

Use Cases

  • Code quality enforcement - Fail builds on poor metrics
  • Refactoring priorities - Identify complex functions to refactor
  • Code review - Objective metrics for discussions
  • Technical debt tracking - Monitor trends over time
  • CI/CD quality gates - Maintain code quality standards
  • Performance correlation - Complex code often performs poorly
  • Multi-language projects - Consistent metrics across languages

Performance

Leverages Synth's performance-optimized AST:

  • Fast traversal using arena-based storage
  • O(1) node access
  • Single-pass analysis
  • Minimal memory overhead

License

MIT


Note: This is a universal metrics analyzer. Works across all languages supported by Synth parsers.