melevir / cognitive_complexity Goto Github PK
View Code? Open in Web Editor NEWLibrary to calculate Python functions cognitive complexity via code.
License: MIT License
Library to calculate Python functions cognitive complexity via code.
License: MIT License
Hi,
are you ok if I make PR to migrate this project to setup.cfg or pyproject?
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...?
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.
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.
From original paper:
break or continue to a label add fundamental increments to Cognitive
Complexity.
Current algorithm doesn't increment for that.
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
Trenary operator should add complexity because it is if
node analog.
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.
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
Most common usecase of using this library is via flake8-cognitive-complexity
. Readme has to say this explicitly.
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).
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:
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
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
An explicit LICENSE file is missing in repo and pypi release tarball - please add such information
Add article on comparing cognitive complexity to cyclomatic, show code samples, explain reasoning, show use cases.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.