Lizard Cyclomatic Complexity Analyzer

repository·master·Indexed 25 days ago

https://github.com/terryyin/lizard

An extensible static code analysis tool that supports multiple programming languages. Lizard calculates Cyclomatic Complexity Number (CCN), non-comment lines of code (NLOC), token counts, and parameter counts, and provides code duplicate detection. The documentation covers the CCN calculation logic, the Reader API, and guides for implementing custom LanguageReaders and extensions to modify complexity analysis.

Tokens
6.9K
Snippets
18
Records
26
Agent score
82%

What's inside Lizard

  1. How to ignore code using Whitelists or Comments

    master

    Using a Whitelist

    To ignore specific functions, create a file named whitelizard.txt in the current folder (or specify it with -W). The file format allows specifying function names or file-specific functions:

    #whitelizard.txt
    function_name1, function_name2
    file/path/name:function1, function2

    Using 'GENERATED CODE' comments

    Lizard will completely ignore any code in a source file that follows a comment containing GENERATED CODE. This code will not generate data, though the file will still be counted in the total file count.

    #whitelizard.txt
    function_name1, function_name2
    file/path/name:function1, function2
  2. How Lizard handles CCN for nested functions and classes

    master

    Lizard's approach to Cyclomatic Complexity for nested constructs (like functions, closures, or structs) depends on the language:

    • Python: Nested complexity is considered separately.
    • C++ and C++-like languages: The complexity of nested constructs is ignored by default to focus on the root function's complexity.

    There are theoretical alternatives (adding inner complexity to the outer construct or ignoring it entirely), but Lizard follows these language-specific defaults.

  3. Suppress warnings using forgiveness comments

    master

    You can use special comments within your source code to suppress Lizard's complexity warnings. There are two ways to apply forgiveness:

    1. Function Forgiveness

    To suppress warnings for a specific function, place a forgiveness comment inside or immediately before the function.

    • Full Function Forgiveness: Use #lizard forgives to suppress all metric warnings for that function.
    • Selective Forgiveness: Use #lizard forgives(metric1, metric2) to suppress only specific metrics. Supported metrics include:
      • length
      • cyclomatic_complexity
      • parameter_count
      • nloc
      • token_count

    2. Global Code Forgiveness

    To suppress warnings for code that exists outside of function definitions (e.g., global variables or top-level logic), place #lizard forgive global before the code block.

    int foo() {
        // #lizard forgives(length)  // Forgive only length violations
        ...
    }
    
    // #lizard forgive global
    int global_var = 0;
    if (condition) {
        // This complexity won't be counted
    }
    
    int bar() { 
        // Functions are still counted normally
    }
  4. Define condition categories for Cyclomatic Complexity

    master

    Lizard calculates Cyclomatic Complexity (CCN) by starting at a base of 1 and adding +1 for every token found in the following four categories. You must define these as sets in your CodeReader subclass:

    1. _control_flow_keywords: Keywords that create new execution paths or decision points (e.g., if, for, while, catch, match).
    2. _logical_operators: Operators that combine boolean conditions and add decision points (e.g., &&, ||, and, or).
    3. _case_keywords: Keywords used for individual branches in switch/case expressions (e.g., case, when).
    4. _ternary_operators: Operators for inline conditional expressions (e.g., ?, ??, ?:).

    Note: Use set() for categories your language does not use to avoid errors.

  5. Understand Nested Control Structures metric

    master

    The Nested Control Structures metric measures the depth of the deepest nested control structures (like if, while, for, and switch) within a function block. This is distinct from CCN, which measures the total number of control paths.

    Lizard's default maximum depth for nested structures is 3, a threshold inspired by the Linux kernel coding style. High nesting depth often indicates code that should be refactored through function extraction.

    int foo()
    {
            while (expr1) {
    
                    for (; expr2;) {
    
                            for (; expr3;) {
    
                                    if (expr4)
                                        return 42;
    
                            }
    
                    }
            }
            return -42;
    }
  6. Ignore CCN within Assertions

    master

    In defensive programming, assertions (e.g., assert(expression)) are used to check invariants and are not intended to be part of the actual functional flow. Including them in CCN calculations can artificially inflate a function's complexity.

    Lizard provides an IgnoreAssert extension for C-like languages to discount the CCN added by assertions. This allows you to calculate the 'true' inherent complexity of a function.

    Note: Lizard only discounts the CCN added by simple operators within assertions; it does not ignore arbitrary complexity if complex logic is placed inside an assertion.

  7. How Lizard calculates Cyclomatic Complexity Number (CCN)

    master

    Lizard calculates Cyclomatic Complexity Number (CCN) by identifying specific code constructs that create decision points. The total CCN is the base complexity (1) plus the count of all tokens found in the reader.conditions set.

    Lizard categorizes these tokens into four distinct groups to allow for fine-grained control and language-specific accuracy:

    1. Control Flow Keywords: Keywords that create decision points or control structures (e.g., if, for, while, try, catch).
    2. Logical Operators: Operators that combine boolean conditions (e.g., &&, ||, and, or).
    3. Case Keywords: Keywords used for switch/case branch labels (e.g., case, when).
    4. Ternary Operators: Conditional expression operators (e.g., ?, ??, ?:).

    Each token identified in these categories adds exactly +1 to the CCN.

    int func(int x, int y) {
        if (x > 0 && y > 0) {  // +1 (if) +1 (&&)
            switch(x) {         // +0 (switch itself)
                case 1:         // +1
                case 2:         // +1
                    break;
            }
        }
        return x > 0 ? 1 : -1;  // +1 (?)
    }
    // Total CCN: 1 (base) + 1 + 1 + 1 + 1 + 1 = 6
  8. How CCN is calculated in Lizard

    master

    Cyclomatic Complexity Number (CCN) is calculated by iterating through tokens and checking if they exist in the reader.conditions set. Every token found in this set increments the complexity count by +1 via reader.context.add_condition().

    To influence the CCN, an extension can modify the reader.conditions set or the individual category sets (like reader.logical_operators) which are used to build the combined set.

    def condition_counter(tokens, reader):
        conditions = reader.conditions  # Combined set of all categories
        for token in tokens:
            if token in conditions:
                reader.context.add_condition()  # Adds +1 to CCN
            yield token
  9. Understand Lizard's parsing limitations

    master

    Lizard uses partial parsers for various languages to ensure it always terminates and avoids hard crashes. However, this design introduces certain limitations and potential "soft failures" (omissions, misinterpretations, or improper tallies) when encountering complex syntax.

    Key Limitations

    • Syntax Requirement: Lizard requires syntactically correct code. Incorrect or unknown syntax may lead to incomplete analysis.
    • C/C++ Specifics:
      • Digraphs/Trigraphs: C/C++ digraphs and trigraphs are not recognized.
      • Macros: Preprocessing or macro expansion is not performed. Using macros instead of standard syntax (like parentheses) can confuse Lizard's bracket stacks.
      • Templates: Complex C++ templates may cause issues with matching angle brackets (<, >) when processing operators inside template arguments.

    Lizard guarantees it will not enter infinite loops or hang, even when encountering problematic code.

  10. Implement a custom LanguageReader

    master

    To support a new language, create a class that inherits from CodeReader (located in lizard_languages/code_reader.py) and overrides the four condition category class attributes. The base class automatically combines these into a single self.conditions set used for CCN calculation.

    Categories to define:

    • _control_flow_keywords: Core decision points (e.g., if, while).
    • _logical_operators: Compound condition operators (e.g., &&, ||).
    • _case_keywords: Switch/case branch keywords.
    • _ternary_operators: Inline conditional operators (e.g., ?).
    class LanguageReader(CodeReader):
        _control_flow_keywords = {'if', 'for', 'while'}
        _logical_operators = {'&&', '||'}
        _case_keywords = {'case'}
        _ternary_operators = {'?'}
  11. How to implement a new language in Lizard

    master

    To add support for a new programming language, you must implement a language reader that follows the Lizard architecture. This involves creating a reader class, defining condition categories for cyclomatic complexity calculation, implementing token generation, and defining a state machine for parsing.

    Core Components:

    1. Reader Class: Inherits from CodeReader. It defines file extensions (ext), command line names (language_names), and condition categories.
    2. Condition Categories: Four specific sets of keywords/operators that increment Cyclomatic Complexity (CCN).
    3. Token Generation: Customizing how the language's tokens are identified.
    4. State Machine: A CodeStateMachine to handle the structural parsing (e.g., identifying function boundaries).

    Integration Steps:

    1. Create a new file in lizard_languages/ (e.g., mylang.py).
    2. Register the reader in lizard_languages/__init__.py.
    3. Add tests in test/test_languages/testMyLang.py.
    4. Run tests using: nix develop -c python -m pytest test/test_languages/testMyLang.py.
    from .code_reader import CodeReader, CodeStateMachine
    from .clike import CCppCommentsMixin
    
    class MyLanguageReader(CodeReader, CCppCommentsMixin):
        ext = ['mylang']
        language_names = ['mylanguage', 'mylang']
        
        _control_flow_keywords = {'if', 'for', 'while', 'catch'}
        _logical_operators = {'&&', '||'}
        _case_keywords = {'case'}
        _ternary_operators = {'?'}
        
        def __init__(self, context):
            super(MyLanguageReader, self).__init__(context)
            self.parallel_states = [MyLanguageStates(context)]
  12. Install Lizard

    master

    You can use Lizard as a standalone Python script without installation, or perform a proper installation to access all functionalities. Lizard requires Python 3.8 or above.

    To install via pip:

    [sudo] pip install lizard

    To install from source:

    [sudo] python setup.py install --prefix=/path/to/installation/directory/
    [sudo] pip install lizard