Skip to content

Conversation

@Ollec
Copy link
Contributor

@Ollec Ollec commented Nov 3, 2025

This took a lot more than I was expecting. I really like the extension model that Lizard has.

When testing on actual projects, I encountered issues with both Python and PL/SQL, so I am confident we will find other kinks in other languages.

Add Cognitive Complexity (CogC) Metric Support

Overview

This PR implements Cognitive Complexity as a built-in complexity metric alongside Cyclomatic Complexity (CCN) in Lizard. Cognitive Complexity is a metric designed to measure how difficult code is to understand, addressing several limitations of Cyclomatic Complexity by focusing on human intuition about code readability rather than pure mathematical path counting.

Implementation follows the Cognitive Complexity specification v1.2 by SonarSource (April 2017).

What is Cognitive Complexity?

Unlike Cyclomatic Complexity which counts all decision points equally, Cognitive Complexity:

  • Counts switch statements as +1 total (vs +1 per case in CCN)
  • Adds nesting penalties - Deeply nested code gets higher scores
  • Groups binary logical operators - Sequences of && or || count as +1, not per operator
  • Ignores readability aids - Null-coalescing operators (?., ??) don't add complexity
  • Treats else-if specially - Hybrid increments without nesting penalty (mental cost already paid)
  • Excludes try blocks from nesting - Exception handling structure doesn't increase cognitive load

Result: Cognitive Complexity scores better correlate with perceived code difficulty and maintainability.

Implementation Architecture

Extension-Based Design

Cognitive Complexity is implemented as a Lizard extension (lizard_ext/lizardcogc.py) that:

  1. Integrates into the token processing pipeline as a generator
  2. Monkey-patches FileInfoBuilder and FunctionInfo to add CogC tracking methods
  3. Uses hooks for language-agnostic nesting tracking (works with both braced and indentation-based languages)
  4. Supports dual nesting tracking:
    • Bracket-based (current_nesting_level) for C-like languages
    • Control-flow based (cogc_nesting_level) for all languages, especially Python/Ruby

Key Features Implemented

Four Types of Increments (per specification):

  • Structural (if, for, while, foreach, repeat) - Add +1 + nesting level
  • Hybrid (else, elif, elseif) - Add +1 without nesting penalty, but increase nesting for children
  • Fundamental (binary operators, goto, labeled jumps) - Add +1 regardless of nesting
  • Preprocessor directives (#if, #ifdef, #elif) - Treated as structural increments

Special Handling:

  • Switch/match statements - Single increment for entire switch
  • Try blocks - Excluded from nesting calculations (cogc_excluded_nesting counter)
  • Catch clauses - Each clause adds +1
  • Ternary operators - Counted like if statements
  • Null-coalescing - Ignored (?., ??)
  • Do-while loops - Only do counted, not trailing while
  • Binary operator sequences - Smart tracking with cogc_last_operator normalization

Language-Specific Adaptations:

  • JavaScript/TypeScript - Outer function pattern detection (has_top_level_increment)
  • Python - Decorator pattern exception (narrow case for idiomatic code)
  • C++ - Lambda expressions aggregate to parent
  • Erlang - Multi-clause if/case support (partial due to parser limitations)
  • PL/SQL - Exception block handling with WHEN clauses
  • Brace-less languages - pending_structural_nesting flag with add_bare_nesting() hook

Language Support

26 languages with Cognitive Complexity support:
All Languages however
Erlang ⚠️ Partial (2/4 parser-limited tests pass)
Structured Text ⚠️ Partial (tests skipped)

Total:
190 language-specific CogC tests
+
30+ core/spec tests
= ~220 tests

Files Changed

New Files

  • lizard_ext/lizardcogc.py - Core CogC extension implementation
  • cognitive_complexity_theory.rst - Comprehensive documentation
  • test/test_extensions/test_cognitive_complexity.py - Core functionality tests
  • test/test_extensions/test_cognitive_complexity_cli.py - CLI integration tests

Modified Files

  • lizard.py - CLI argument for -G/--CogC threshold (default: 15)
  • lizard_ext/htmloutput.py - Added CogC column to HTML output
  • README.rst - Updated supported languages list, added CogC usage examples
  • CHANGELOG.md - Documented new feature
  • Language readers - Minor modifications for CogC tracking:
    • clike.py, erlang.py, plsql.py, python.py, st.py, typescript.py, zig.py
  • 26 test files - Added comprehensive CogC test cases for each language
  • Test coverage: ~220 new test cases

CLI Usage

Command-Line Options

# Set Cognitive Complexity threshold (default: 15)
lizard -Ecogc -G 20 mycode/

# Sort by Cognitive Complexity
lizard -Ecogc -s cognitive_complexity mycode/

# Use both CCN and CogC thresholds
lizard -Ecogc -C 15 -G 20 mycode/

# Warnings-only mode with CogC threshold
lizard -Ecogc -w -G 10 mycode/

Output Formats

CogC can appears in all output formats:

  • Tabular (default) - New CogC column
  • XML (cppncss-style) - <cognitive_complexity> element
  • HTML - Interactive DataTables with sortable CogC column
  • CSV - New cognitive_complexity field
  • Checkstyle XML - Included in violation messages

Example Output

==============================================================
  NLOC    CCN   CogC   token  param    function@line@file
--------------------------------------------------------------
    10      2      1     29      2    start_new_player@26@./html_game.c
    24      3      4     61      1    server_main@454@./httpd.c
--------------------------------------------------------------
2 file analyzed.
==============================================================

Implementation Details

Token Processing Pipeline

preprocessing → comment_counter → line_counter → token_counter →
condition_counter → cognitive_complexity_counter (extension)

State Variables Tracked

In FileInfoBuilder:

  • cogc_nesting_level - Control-flow nesting depth
  • cogc_last_operator - Last binary operator (normalized)
  • cogc_nesting_stack - Boolean stack for structural nesting
  • cogc_excluded_nesting - Try block nesting to exclude
  • pending_lambda_nesting - Lambda entry flag
  • lambda_depth - Nested lambda tracking
  • pending_structural_nesting - Brace-less language flag

In FunctionInfo:

  • cognitive_complexity - Running CogC total
  • initial_nesting_level - Bracket nesting at function start
  • initial_cogc_nesting_level - Control-flow nesting at start (preprocessor directives)

Nesting Calculation Formula

nesting_from_stack = max(0, current_nesting_level - initial_nesting_level - 1)
nesting_from_cogc = cogc_nesting_level + initial_cogc_nesting_level
nesting = max(nesting_from_stack, nesting_from_cogc) - cogc_excluded_nesting
cognitive_complexity += increment + max(0, nesting)

Documentation

Added Documentation Files

  1. cognitive_complexity_theory.rst - covering:

    • Introduction and comparison with CCN
    • Basic calculation rules (3 rules, 4 increment types)
    • Detailed explanation of each construct
    • Implementation architecture in Lizard
    • Language-specific adaptations
    • Edge cases and common pitfalls
    • Debugging guide
    • Implementation checklist for new languages
  2. Updated README.rst:

    • Simplified language list with inline notes
    • CogC usage examples
    • References to theory documentation
  3. Updated CHANGELOG.md:

    • Comprehensive feature description
    • Links to documentation

Testing Strategy

Test Categories

  1. Core functionality tests (test_cognitive_complexity.py)

    • Nesting calculations
    • Binary operator sequences
    • Try/catch exclusions
    • Lambda/nested function handling
  2. CLI integration tests (test_cognitive_complexity_cli.py)

    • Threshold warnings
    • Sorting by CogC
    • Output format validation
  3. Language-specific tests (26 test files)

    • Simple functions (baseline)
    • Single if statements
    • Nested loops with nesting penalties
    • Binary logical operators
    • Switch statements
    • Try/catch blocks
    • Language-specific constructs

Test Coverage Standards

All tests follow strict assertions:

  • Perferd to use assertEqual(expected, actual.cognitive_complexity)
  • Rarely used assertGreaterEqual, assertLess, etc.
  • Include inline CogC calculation comments: // +1, // +2 (nesting=1)

Breaking Changes

None. This is a purely additive feature:

  • New CLI option -G/--CogC (doesn't conflict with existing options)
  • New output column (doesn't break existing parsers)
  • Extension-based architecture (doesn't modify core behavior)
  • Backwards compatible with all existing functionality

Performance Impact

Minimal. The extension:

  • Processes tokens in a single pass (no additional parsing)
  • Maintains lightweight state (a few integers and flags)
  • Uses efficient algorithms (no recursive traversal)
  • Only active when extension is loaded (default for built-in extensions)

Future Work

Not implemented (per specification, but low priority):

  • Recursion detection (+1 for each function in recursion cycle)
  • Some Erlang edge cases (parser limitations with ; in if/case)
  • Structured Text test issue?

Specification Attribution

Source Specification

This implementation is based on the Cognitive Complexity specification version 1.2 by G. Ann Campbell, SonarSource SA (April 19, 2017).

Specification Details:

Attribution Locations

Proper attribution to SonarSource and the specification is provided in:

  1. lizard_ext/lizardcogc.py - Core implementation file
  2. cognitive_complexity_theory.rst - Documentation
  3. README.rst - User-facing documentation
  4. Language test files - Comments in transformed tests note specification inspiration

Test Examples Inspired by Specification

Test cases inspired by the specification have been transformed to avoid copyright concerns while maintaining specification compliance validation:

  • Structural equivalence - Tests preserve exact control flow patterns and complexity scores
  • Transformed content - Different function names, variable names, and domain contexts
  • Specification compliance - Ensures our implementation matches the spec's complexity calculations
  • Distributed across language tests - Located in language-specific test files (test/test_languages/)
  • Clear documentation - Tests include comments noting "same structure as spec example, transformed to avoid copyright"

Related Issues

Closes #432

Ollec and others added 9 commits November 1, 2025 23:13
… for multiple languages with tests for each

- Implemented cognitive complexity tests for Python, R, Ruby, Rust, Scala, Solidity, Structured Text, Swift, TTCN-3, TypeScript, Vue.js, Zig and more.
- Each language includes tests for simple functions, single if statements, nested loops, binary logical operators, and other control structures.
- Updated error messages in option parsing to include cognitive complexity as a valid sorting factor.
- Implemented a new extension for calculating Cognitive Complexity in functions, following the specification by SonarSource.
- Updated output formats to include Cognitive Complexity in headers and outputs for different formats (CSV, HTML).
- Adjusted error messages for sorting factors to reflect the new order of fields, ensuring clarity in available options.
…uages, and made it no longer default

- Removed cognitive complexity tests from Python, R, Ruby, Rust, Scala, Solidity, Structured Text, Swift, TTCN-3, TypeScript, Vue.js, and Zig test files.
- Updated test cases to ensure they accurately reflect the cognitive complexity calculations for various control structures.
- Adjusted the expected cognitive complexity values based on the latest implementation details.
- Modified the test options to reflect changes in the default and extension fields for cognitive complexity.
@Ollec Ollec marked this pull request as ready for review November 6, 2025 00:43
@Ollec
Copy link
Contributor Author

Ollec commented Nov 25, 2025

@terryyin are there any changes you would like to me to make, or scrap the whole PR?

@terryyin
Copy link
Owner

@Ollec sorry, I keep postponing this because too many things are changed. I will review as soon as I can.

@Ollec
Copy link
Contributor Author

Ollec commented Nov 25, 2025

@terryyin No worries, I understand. It ended up being a lot more of an undertaking than I had been planning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Cognitive Complexity

2 participants