Giter VIP home page Giter VIP logo

cognitive_complexity's Introduction

congnitive-complexity

Build Status Maintainability Test Coverage PyPI version PyPI - Python Version

Library to calculate Python functions cognitive complexity via code.

Installation

pip install cognitive_complexity

Usage

>>> import ast

>>> funcdef = ast.parse("""
... def f(a):
...     return a * f(a - 1)  # +1 for recursion
... """).body[0]

>>> from cognitive_complexity.api import get_cognitive_complexity
>>> get_cognitive_complexity(funcdef)
1

Flake8-Cognitive-Complexity Extension

Perhaps the most common way to use this library (especially if you are already using the Flake8 linter) is to use the flake8-cognitive-complexity extension. If you run Flake8 with this extension installed, Flake8 will let you know if your code is too complex. For more details and documentation, visit the flake8-cognitive-complexity extension repository.

What is cognitive complexity

Here are some readings about cognitive complexity:

Realization details

This is not precise realization of original algorithm proposed by G. Ann Campbell, but it gives rather similar results. The algorithm gives complexity points for breaking control flow, nesting, recursion, stacks logic operation etc.

Contributing

We would love you to contribute to our project. It's simple:

  • Create an issue with bug you found or proposal you have. Wait for approve from maintainer.
  • Create a pull request. Make sure all checks are green.
  • Fix review comments if any.
  • Be awesome.

Here are useful tips:

  • You can run all checks and tests with make check. Please do it before TravisCI does.
  • We use BestDoctor python styleguide. Sorry, styleguide is available only in Russian for now.
  • We respect Django CoC. Make soft, not bullshit.

cognitive_complexity's People

Contributors

fhightower avatar melevir avatar pkolbus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cognitive_complexity's Issues

Incorrect counting for sequences of binary logical operators

According to the Cognitive Complexity specification, sequences of binary logical operators receive a fundamental increment (B1) but not a nesting increment (B3). This is further supported by the overriddenSymbolFrom() example in appendix C.

The existing test_real_function() should be calculated as follows; note the +4 for the multiline if condition should be +2.

assert get_code_snippet_compexity("""
def process_raw_constant(constant, min_word_length):
    processed_words = []
    raw_camelcase_words = []
    for raw_word in re.findall(r'[a-z]+', constant):  # +1
        word = raw_word.strip()
        if (  # +2
            len(word) >= min_word_length
            and not (word.startswith('-') or word.endswith('-'))  # +2 (2 bool operator sequences)
        ):
            if is_camel_case_word(word):  # +2
                raw_camelcase_words.append(word)
            else:  # +1
                processed_words.append(word.lower())
    return processed_words, raw_camelcase_words
""") == 9  # not 11

Add test on big function

Should test on complex function with nested nodes etc.

This is required to check that recursive get_cognitive_complexity_for_node calls passes all params correctly.

Not increment complexity for try block

Original paper says:

Note that a catch only adds one point to the Cognitive Complexity score, no matter how many exception types are caught. try and finally blocks are ignored altogether.

Now try block is incremented by one, it should not

Incorrect counting for break and continue

According to the Cognitive Complexity specification, a fundamental increment is assessed for "goto, and break or continue to a label." (page 7 and section B1; emphasis mine). Simple break and continue do not receive an increment, as illuminated by the example for addVersion() in Appendix C, and by the justification that early exit tends to improve readability (page 7). (Further, the break in linear flow is already accounted for in the if, as break and continue only make sense within a conditional.)

The implementation assesses a structural increment for these constructs, causing complexity scores to be inflated. For example, test_break_and_continue() should be:

assert get_code_snippet_compexity("""
def f(a):
    for a in range(10):  # +1
        if a % 2:  # +2
            continue  # no increment
        if a == 8:  # +2
            break  # no increment
""") == 5

Try-blocks counts every line

From https://www.sonarsource.com/docs/CognitiveComplexity.pdf: instance, in the following example, there is no nesting increment for the method itself or for the try because neither structure results in either a structural or a hybrid increment:

also:

However, the if, for, while, and catch structures are all subject to both structural and
nesting increments.

In [1]: get_code_snippet_compexity('''
    ...: def my_method():
    ...:   try:
    ...:     print('hello')
    ...:     print('hello')
    ...:   except Exception as e:    # +1 nesting = 0
    ...:       return 0
    ...:         ''')
Out[1]: 2

In [2]: get_code_snippet_compexity('''
    ...: def my_method():
    ...:   try:
    ...:     print('hello')
    ...:     print('hello')
    ...:     print('hello')
    ...:   except Exception as e:    # +1 nesting = 0
    ...:       return 0
    ...:         ''')
Out[2]: 3

All these should be 1...?

Not increment nesting for decorators

Python’s decorator idiom allows additional behavior to be added to an existing function
without modifying the function itself. This addition is accomplished with the use of nested
functions in the decorator providing the additional behavior. In order not to penalize Python
coders for the use of a common feature of their language, an exception has been added.
However, an attempt has been made to define the exception narrowly. Specifically, to be
eligible for the exception, a function may contain only a nested function and a return
statement.

This is valid after #3 is done.

Document try complexity computation

Hi, thanks for this great tool 😄 I was scratching my head when adding/removing any lines from try block was influencing cognitive complexity and searched in your documentation and then code why is that and found this line in code:

# add +1 for all try nodes except body

It means that any line added inside try block will increment complexity (code below gives complexity 4)

def a(b, c):
    try:
        print(1)
        print(2)
        print(3)
        print(4)
    except Exception:
        raise

This behavior is unexpected as it was not mentioned in SonarSource links provided, can we document it here or in flake8 extension? Thanks

Incorrect result for else/elif

Thanks for the great package and flake8 plugin.

I've noticed that else (and nested elif) seem to be counted incorrectly. Based on my reading of the whitepaper (section B3, the discussion of "hybrid" increments, overriddenSymbolFrom() and other examples in section C, and the whitepaper's change log), else and elif are not subject to the nesting increment.

Sample test cases:

# passes
def test_nested_if_condition_complexity():
    assert get_code_snippet_compexity("""
    def f(a, b):
        if a == b:  # +1
            if (a):  # +2 (nesting=1)
                return 1
        return 0
    """) == 3

# fails (actual=1, expected=2)
def test_simple_else_condition_complexity():
    assert get_code_snippet_compexity("""
    def f(a):
        if (a):  # +1
            return 1
        else:  # +1
            return 3
    """) == 2

# fails (actual=3, expected=4)
def test_nested_else_condition_complexity():
    assert get_code_snippet_compexity("""
    def f(a, b):
        if a == b:  # +1
            if (a):  # +2 (nesting=1)
                return 1
            else:  # +1
                return 3
        return 0
    """) == 4

# fails  (actual=6, expected=5)
def test_nested_elif_condition_complexity():
    assert get_code_snippet_compexity("""
    def f(a, b):
        if a == b:  # +1
            if (a):  # +2 (nesting=1)
                return 1
            elif (b):  # +1
                return 2
            else:  # +1
                return 3
        return 0
    """) == 5

I'm not terribly familiar with Python AST parsing. But if you like, I can try working on a PR if you want (and agree it's an actual bug).

Increment nesting, but not increment complexity for lambdas and nested methods

Additionally, while top-level methods are ignored, and there is no structural increment for
lambdas, nested methods, and similar features, such methods do increment the nesting
level when nested inside other method-like structures

Current algorithms skips lambdas and increments complexity for nested methods.

Add motivation docs

Add article on comparing cognitive complexity to cyclomatic, show code samples, explain reasoning, show use cases.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.