-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SONARJS-862 Rule: Cognitive Complexity of functions should not be too high #449
Conversation
1282a32 to
c095cf6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena Good work! Kindly check my comments, Don't forget to handle the "(a && b) && c" and "a && (b && c)" cases!
| } | ||
| } | ||
|
|
||
| private class CognitiveComplexity extends DoubleDispatchVisitor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena This class is a valuable asset of the analyzer. It is likely to be reused in the future, e.g., for computing the complexity of a class or a file, or to display the complexity, etc. Therefore I would like to see this class as a top-level one, in project javascript-fronted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco I would prefer to extract it as soon as it will be required somewhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena @pynicolas @inverno Actually we had a long discussion last Friday (unfortunately you weren't present) about this point. Carlo's view point and mine was that whenever a strong business notion is found, it immediately deserves a separate class; waiting for a second usage of the notion is not a good criterion for creating the separate class. Pierre-Yves was of opposite opinion (yours). I really prefer the former.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco This will require time and efforts now, which are investments in the future, hypothetical future. In fact I've not heard that we will do CG metric, moreover I think CG of class or file contradicts to its notion.
|
|
||
| for (i = 0; i < length; i++) { // +1 (nesting level +1) | ||
| //S ^^^ LOOPS {{+1}} | ||
| if (condition) { // +2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena How about testing a "for" loop within a "for" loop? In my experience it is a common case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco done
|
|
||
| function nested_conditional_expression() { // Noncompliant [[effortToFix=6]] | ||
| x = condition1 ? (condition2 ? trueValue2 : falseValue2) : falseValue1 ; // +3 | ||
| x = condition1 ? trueValue1 : (condition2 ? trueValue2 : falseValue2) ; // +3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena OK. I just suggest that you replace this line with:
x = condition1 ? (condition2 ? trueValue2 : falseValue2) : (condition3 ? trueValue3 : falseValue3); // +5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco I've added it
| //S ^^ TRY_CATCH | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena I suggest you also add a case of nested try-catch, e.g,:
function nested_try_catch() { // Noncompliant [[effortToFix=3;id=NESTED_TRY_CATCH]]
try {
if (condition) { // +1 (nesting level +1)
//S ^^ NESTED_TRY_CATCH
try {}
catch (someError) { // +2
//S ^^^^^ NESTED_TRY_CATCH
}
}
} finally {
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco FYI in github comments you can wrap one line code in single backticks and multiline code in triple backticks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco thanks for the case. Added!
|
|
||
| for (var i = a && b; a && b; a && b) { // +1(for) +1(&&) +1(&&) +1(&&) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena I may be wrong, but I see nowhere in your nesting tests that complexity may increase more than once at a same nesting level. It is definitely worth it, because I know it has been a matter of debate: do we increase complexity once per level or possibly several times (the latter was retained). For example:
function nesting() { // Noncompliant [[effortToFix=5]]
if (condition) { // +1 (+1 for nesting)
if (condition) {} // +2
if (condition) {} // +2 (this one was debated - personally I disagree but this is irrelevant)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| /* When complexity of nesting function is considered for this check, we ignore all nested in it functions */ | ||
| private Set<Tree> ignoredNestedFunctions = new HashSet<>(); | ||
| private final CognitiveComplexity cognitiveComplexity = new CognitiveComplexity(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena I am not sure that this implementation is fault-tolerant. Class CognitiveComplexity is stateful. If an exception is raised in CognitiveComplexity#visitFunction(tree), the clean up in method CognitiveComplexity#leaveFunction(tree) will not be performed, so the CognitiveComplexity of the first function of the next file may be polluted. How about moving this initialization to line 85?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco Agree about the fact that CognitiveComplexity#leaveFunction(tree) might not be done in case of some exception. But in fact if some exception is raised the entire analysis is stopped so we don't care.
But we might change this behaviour at any moment so I will force initialization of CognitiveComplexity for each file.
Thanks!
|
|
||
| function and_or() { // Noncompliant [[effortToFix=7;id=LOGICAL_OPS]] | ||
| foo(1 && 2 && 3 && 4); // +1 | ||
| //S ^^ LOGICAL_OPS {{+1}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena Kindly see Ann's email about
var x = (a && b && c); // +1
var x = ((a && b) && c); // +1, not +2
Thanks!
| } | ||
| } | ||
|
|
||
| private void checkComplexity() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena This method could be public, named "getComplexity", and not raising issue. I would delegate the threshold and the issue raising to the enclosing Check class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco done in 21c4bd5
| return "+1"; | ||
|
|
||
| } else{ | ||
| return String.format("+%s (incl %s for nesting)", complexity, complexity - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena How about adding a dot at the end of "incl", that is "incl." ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // ^^ | ||
| var functionExpression = function(a, b) { return a && b; } // Noncompliant [[effortToFix=1]] | ||
| // ^^^^^^^^ | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena Maybe an example with an IIFE would be nice. How about a nasty little case like this?
(function(a) {
if (cond) {}
return a;
})(function(b) {return b + 1})(0);
I don't know what it is supposed to display... Too tired for today...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivandalbosco It's nasty, but not much interesting: one issue on first line (complexity = 1). But ok, I will add it
| boolean leftChildOfSameKind = leftChild.is(javaScriptTree.getKind()); | ||
| boolean rightChildOfSameKind = rightChild.is(javaScriptTree.getKind()); | ||
|
|
||
| // For expressions with same-kind operators like "a && (b && c)" we want to have secondary location on leftmost operator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7cbfe22 to
21c4bd5
Compare
| @@ -207,10 +207,13 @@ function with_complexity_after_nested_function() { // Noncompliant [[effortToFix | |||
| if (condition) {} // +1 | |||
| } | |||
|
|
|||
| function and_or() { // Noncompliant [[effortToFix=7;id=LOGICAL_OPS]] | |||
| function and_or() { // Noncompliant [[effortToFix=8;id=LOGICAL_OPS]] | |||
| foo(1 && 2 && 3 && 4); // +1 | |||
| //S ^^ LOGICAL_OPS {{+1}} | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena Great! I can see that
foo(1 && 2 && (3 && 4))
is +1, whereas
foo(1 && 2 && !(3 && 4))
is +2. Can you please add one or more test with the "not" operator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@inverno done
| public void setThreshold(int threshold) { | ||
| this.threshold = threshold; | ||
| } | ||
|
|
||
| private static class ComplexityData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vilchik-elena Good design, this class was indeed a must. However, you are in the middle of the river: methods complexity() and aggregatedNestedFunctions() are public (probably for future usage), whereas the class itself is private. So Eclipse correctly tells me that those two methods are "never used locally". I still recommend you move classes CognitiveComplexity and ComplexityData to package org.sonar.javascript.metrics. But this is up to you, I won't argue if you disagree...
714ba83 to
b4f3d50
Compare


No description provided.