lynx_eye 0.0.2

A code complexity analyzer for JavaScript, TypeScript, and Rust using tree-sitter. Calculates NLOC, CCN, token count, and complexity scores.
Documentation

Lynx Eye

License: MIT Rust

Lynx Eye is a code analysis tool similar to Lizard, built with Rust and tree-sitter. It analyzes code metrics for JavaScript and TypeScript, focusing on function-level static analysis to help identify complex or oversized functions that may need refactoring.

Features

  • Multi-language Support: Analyzes JavaScript and TypeScript code
  • Function-level Analysis: Extracts detailed metrics for each function
  • Code Complexity Metrics:
    • NLOC (Non-commenting Lines of Code)
    • CCN (Cyclomatic Complexity Number)
    • Token count
    • Parameter count
  • Configurable Thresholds: Set custom limits for complexity warnings
  • Fast Performance: Built with Rust for efficient processing

Installation

Prerequisites

  • Rust (edition 2024 or later)
  • Cargo

Build from Source

git clone https://github.com/yzzting/LynxEye.git
cd LynxEye
cargo build --release

The release binary will be available at target/release/lynx_eye.

Usage

Command Line Options

# Show help
lynx_eye --help

# Show version
lynx_eye --version

Input Options

# Analyze a single file
lynx_eye demo/test.js

# Analyze multiple files
lynx_eye demo/test.js demo/test.ts src/analysis.rs

# Analyze an entire directory (non-recursive)
lynx_eye demo/

# Analyze directory recursively (includes subdirectories)
lynx_eye --recursive demo/
lynx_eye -r src/

Output Formats

Table Format (Default)

lynx_eye demo/test.js

Output:

+------+-----+-------+-------+-------+----------------+------+---------+
| NLOC | CCN | Token | Param | Score | Function       | Line | File    |
+======================================================================+
| 7    | 2   | 30    | 1     | 24.4  | calculateTotal | 1    | test.js |
|------+-----+-------+-------+-------+----------------+------+---------+
| 3    | 1   | 13    | 2     | 17.2  | anonymous      | 9    | test.js |
|------+-----+-------+-------+-------+----------------+------+---------+
| 3    | 1   | 13    | 2     | 17.2  | add            | 14   | test.js |
+------+-----+-------+-------+-------+----------------+------+---------+

JSON Format

lynx_eye --format json demo/test.js

Output:

[
  {
    "ccn": 2,
    "complexity_score": 24.4,
    "file": "test.js",
    "name": "calculateTotal",
    "nloc": 7,
    "parameter_count": 1,
    "start_line": 1,
    "token_count": 30
  }
]

CSV Format

lynx_eye --format csv demo/test.js

Output:

NLOC,CCN,Token,Param,Score,Function,Line,File
7,2,30,1,24.4,calculateTotal,1,test.js
3,1,13,2,17.2,anonymous,9,test.js

Filtering Functions

Filter functions based on complexity metrics:

# Show only functions with complexity score >= 40
lynx_eye --min-score 40 demo/

# Show only highly complex functions (CCN >= 10)
lynx_eye --min-ccn 10 demo/

# Show only large functions (NLOC >= 20)
lynx-eye --min-nloc 20 demo/

# Show functions with many parameters (>= 5)
lynx_eye --min-params 5 demo/

# Combine multiple filters
lynx_eye --min-score 50 --min-ccn 5 --min-nloc 20 demo/

Output to File

Save analysis results to a file:

# Save JSON output
lynx_eye --format json --output analysis.json demo/

# Save CSV output
lynx_eye --format csv --output analysis.csv demo/

# Save table output
lynx_eye --output analysis.txt demo/

File Type Support

Lynx Eye currently supports:

JavaScript (.js files) ✅ TypeScript (.ts files)

Other file types (.py, .html, .css, etc.) are automatically ignored.

Metrics Explained

NLOC (Non-commenting Lines of Code)

What it measures: The number of lines containing actual code, excluding empty lines and comments.

How it's calculated:

  • Counts lines within function boundaries
  • Excludes empty lines (line.trim().is_empty())
  • Excludes single-line comments starting with //
  • Excludes multi-line comment blocks starting with /*

Example:

function example(param) {    // NLOC: 0 (comment line)
    let result = param;      // NLOC: 1 ✅
    // This is a comment    // NLOC: 0 (comment line)
    return result;           // NLOC: 1 ✅
}                           // NLOC: 1 ✅
// Total NLOC: 3

Why it matters: High NLOC indicates large functions that may be harder to understand and maintain. Functions with NLOC > 20 often need refactoring.

CCN (Cyclomatic Complexity Number)

What it measures: The number of independent paths through the code, representing decision complexity.

How it's calculated:

  • Base complexity: 1
  • +1 for each decision point (branch nodes):
    • if statements
    • for, while, do loops
    • switch cases
    • Ternary expressions (condition ? a : b)
    • Logical operators (&&, ||)
    • catch clauses

Example:

function example(value) {        // Base CCN: 1
    if (value > 10) {            // +1 = 2
        return value * 2;
    } else if (value < 5) {      // +1 = 3
        return value / 2;
    }
    return value + 1;           // Total CCN: 3
}

Why it matters: CCN > 10 indicates complex code that's hard to test and maintain. Each additional branch increases testing requirements exponentially.

Token Count

What it measures: The number of smallest syntactic units in the code.

How it's calculated: Counts all leaf nodes in the tree-sitter syntax tree.

Example breakdown:

function add(a, b) { return a + b; }
// Tokens: function, add, (, a, ,, b, ), {, return, a, +, b, ;, }
// Total: 13 tokens

Why it matters: High token density (Token/NLOC ratio) can indicate complex expressions and potentially hard-to-read code.

Parameter Count

What it measures: The number of parameters a function accepts.

How it's calculated: Counts function parameters, supporting:

  • Regular parameters: function(a, b)
  • Optional parameters: function(a?: number)
  • Rest parameters: function(...args)
  • Default parameters: function(a = 1)

Example:

function example(required, optional = 5, ...rest) { }
// Parameter count: 3 (required, optional, rest)

Why it matters: Functions with many parameters (>3-4) are often doing too much and may benefit from parameter objects or refactoring.

Line Number

What it measures: The starting line number of the function in the source file.

Why it matters: Helps locate functions in the codebase for review and debugging.

Function Name

What it measures: The identifier of the function. Shows as "anonymous" for unnamed functions.

Why it matters: Identifies specific functions that may need attention or refactoring.

Complexity Score

What it measures: A composite score (0-100) that combines NLOC, CCN, and code density into a single complexity metric.

How it's calculated:

Score = 0.5·CCN_norm + 0.3·NLOC_norm + 0.2·Density_norm

Where:

  • CCN_norm = min(CCN / 15, 1.0) - Normalized cyclomatic complexity (15 = threshold)
  • NLOC_norm = min(NLOC / 30, 1.0) - Normalized lines of code (30 = threshold)
  • Density_norm = min((Token/NLOC) / 8, 1.0) - Normalized token density (8 = high density threshold)

Example breakdown:

function simple(x) {           // CCN=1, NLOC=2, Token=8, Density=4.0
    return x + 1;             // Score: 15.3 (Low complexity)
}

function complex(data, options) {  // CCN=4, NLOC=15, Token=75, Density=5.0
    if (!data) return null;        // Score: 48.1 (Moderate complexity)

    for (let i = 0; i < data.length; i++) {
        if (options.validate && !isValid(data[i])) {
            throw new Error(`Invalid item at ${i}`);
        }
        data[i] = transform(data[i], options.config);
    }
    return data;
}

Why it matters:

  • Score < 30: Simple functions, easy to understand and maintain
  • Score 30-60: Moderate complexity, may need review
  • Score > 60: Highly complex functions, strong candidates for refactoring

The score prioritizes cyclomatic complexity (50% weight) as the most important factor, followed by code size (30%) and code density (20%). This provides a balanced view that identifies genuinely complex code rather than just large functions.

Example

$ cargo run demo/test.js
+------+-----+-------+-------+-------+----------------+------+---------+
| NLOC | CCN | Token | Param | Score | Function       | Line | File    |
+======================================================================+
| 7    | 2   | 30    | 1     | 24.4  | calculateTotal | 1    | test.js |
|------+-----+-------+-------+-------+----------------+------+---------+
| 3    | 1   | 13    | 2     | 17.2  | anonymous      | 9    | test.js |
|------+-----+-------+-------+-------+----------------+------+---------+
| 3    | 1   | 13    | 2     | 17.2  | add            | 14   | test.js |
|------+-----+-------+-------+-------+----------------+------+---------+
| 3    | 1   | 13    | 2     | 17.2  | subtract       | 18   | test.js |
+------+-----+-------+-------+-------+----------------+------+---------+

This output shows:

  • calculateTotal (lines 1-7): NLOC=7, CCN=2 (contains a for loop), 30 tokens, 1 parameter, Score=24.4 (low complexity)
  • calculateTax (lines 9-11): NLOC=3, CCN=1 (simple function), 13 tokens, 2 parameters, Score=17.2 (very low complexity)
  • add and subtract (lines 14-21): NLOC=3, CCN=1 (simple methods), 13 tokens, 2 parameters each, Score=17.2 (very low complexity)

Development

Build and Run

# Build the project
cargo build

# Run in debug mode
cargo run

# Build optimized release version
cargo build --release

Testing

# Run all tests
cargo test

# Run a specific test
cargo test test_function_name

Development Tools

# Quick check without building
cargo check

# Run linter
cargo clippy

# Format code
cargo fmt

# Clean build artifacts
cargo clean

Architecture

Lynx Eye uses tree-sitter to parse source code into a Concrete Syntax Tree (CST), then:

  1. Tree Traversal: Identifies function nodes (function declarations, arrow functions, method definitions, etc.)
  2. Metadata Extraction: Captures function name, start/end lines, and parameters
  3. NLOC Calculation: Counts non-empty, non-comment lines within function boundaries
  4. CCN Calculation: Counts branch nodes (if statements, loops, ternary expressions, etc.)

Supported Function Types

  • function_declaration - function foo() {}
  • function_expression - const foo = function() {}
  • arrow_function - const foo = () => {}
  • method_definition - Class methods, getters, setters
  • generator_function_declaration - function* foo() {}

Complexity Nodes

The tool counts the following branch nodes for CCN calculation:

  • if_statement, for_statement, for_in_statement
  • while_statement, do_statement
  • switch_case (each case)
  • ternary_expression / conditional_expression
  • catch_clause
  • && and || binary expressions

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Compatibility Requirements

Please ensure changes maintain compatibility with Rust 1.70.0+. See COMPATIBILITY.md for detailed compatibility notes and upgrade guidelines.

Acknowledgments

  • tree-sitter - Parser generator tool
  • Lizard - Inspiration for this project
  • The Rust community for excellent tooling and libraries