Skip to content

Fix NPath computation#866

Merged
rultor merged 13 commits into
cqfn:masterfrom
Error10556:todo852
Jul 20, 2025
Merged

Fix NPath computation#866
rultor merged 13 commits into
cqfn:masterfrom
Error10556:todo852

Conversation

@Error10556

@Error10556 Error10556 commented Jul 15, 2025

Copy link
Copy Markdown
Contributor

I fixed the NPath complexity computation in /test/metrics/npath/Complicated.java.

In the process, I also discovered that 3 other tests (test_switch_empty, test_switch_simple_without_default, and test_switch_with_fallthrough) asserted an incorrect NPath value, so I edited them and clarified why the new asserted value was correct. The original Checkstyle agrees with me.

Closes #862
Closes #863

Summary by CodeRabbit

  • Bug Fixes

    • Corrected NPath complexity calculations for conditionals, loops, and switch statements, leading to more accurate results.
    • Updated test cases to reflect the corrected expected NPath values.
    • Removed expected failure markers from tests that now pass with the improved logic.
  • Tests

    • Adjusted test assertions and comments to align with the revised complexity calculations.
    • Updated expected values for high-complexity scenarios based on verified outputs.

@coderabbitai

coderabbitai Bot commented Jul 15, 2025

Copy link
Copy Markdown

Walkthrough

The NPath complexity calculation logic in aibolit/metrics/npath/main.py was refactored, with new and updated methods for handling expressions, conditionals, loops, and switch statements. The test suite in test/metrics/npath/test_all_types.py was updated to match the revised complexity calculations, with previously failing tests now expected to pass and expected values adjusted.

Changes

File(s) Change Summary
aibolit/metrics/npath/main.py Refactored NPath complexity logic: added _composite_npath, unified expression handling under _expression_npath, rewrote switch, if, loop, and binary expression calculations; removed _condition_npath.
test/metrics/npath/test_all_types.py Updated expected NPath values in tests, removed xfail decorators, deleted obsolete comments, and adjusted assertions to match new logic.

Sequence Diagram(s)

sequenceDiagram
    participant TestSuite
    participant MvnFreeNPathMetric
    participant ASTNode

    TestSuite->>MvnFreeNPathMetric: compute_npath(ASTNode)
    MvnFreeNPathMetric->>ASTNode: traverse node type
    alt Expression Node
        MvnFreeNPathMetric->>MvnFreeNPathMetric: _expression_npath(ASTNode)
        alt Binary Operator
            MvnFreeNPathMetric->>MvnFreeNPathMetric: _binary_expression_npath(ASTNode)
        end
    else If/Switch/Loop Node
        MvnFreeNPathMetric->>MvnFreeNPathMetric: _if_npath/_switch_npath/_for_loop_npath/_while_loop_npath(ASTNode)
        MvnFreeNPathMetric->>MvnFreeNPathMetric: _expression_npath(condition/control)
        MvnFreeNPathMetric->>MvnFreeNPathMetric: _sequence_npath(children)
    else Other Node
        MvnFreeNPathMetric->>MvnFreeNPathMetric: _composite_npath(ASTNode)
    end
    MvnFreeNPathMetric-->>TestSuite: Return NPath value
Loading

Assessment against linked issues

Objective Addressed Explanation
Fix NPath calculation logic in test_all_types.py so that tests pass and puzzles are removed (#862, #863)

Possibly related PRs

  • cqfn/aibolit#836: Adjusts if statement NPath formula, directly related to similar fixes in this PR.
  • cqfn/aibolit#861: Introduces and then supersedes initial while loop and _condition_npath logic, which this PR refactors and replaces.

Suggested reviewers

  • ivanovmg

Poem

In the warren of code, where logic winds deep,
The NPath now counts with a hop and a leap.
Switches and loops, expressions anew—
The tests all now pass, the puzzles are through!
🐇✨

Complexity tamed, with carrots in sight,
This rabbit declares: the code feels just right!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abdef57 and 71359fd.

📒 Files selected for processing (2)
  • aibolit/metrics/npath/main.py (1 hunks)
  • test/metrics/npath/test_all_types.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/metrics/npath/test_all_types.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Error10556
PR: cqfn/aibolit#866
File: test/metrics/npath/javacode/OneIfStatement.java:7-7
Timestamp: 2025-07-15T19:00:20.609Z
Learning: In the aibolit project, syntax errors in test files under `test/metrics/npath/javacode/` directory (such as using `bool` instead of `boolean`) are not relevant concerns since these are code snippets used for NPath complexity testing, not standalone compilable Java classes.
Learnt from: Error10556
PR: cqfn/aibolit#866
File: test/metrics/npath/javacode/IfWithIfElseInsideOuterElse.java:6-8
Timestamp: 2025-07-15T19:00:29.065Z
Learning: In the aibolit project, syntax correctness (such as using `boolean` instead of `bool`) is not a concern for test files under `test/metrics/npath/javacode/` directory since they are code snippets for NPath complexity testing, not standalone compilable Java classes.
Learnt from: Error10556
PR: cqfn/aibolit#866
File: test/metrics/npath/javacode/ComplexIfElseWithNPathComplexityOf5.java:6-6
Timestamp: 2025-07-15T19:00:48.837Z
Learning: For test files under `test/metrics/npath/javacode/` directory in the aibolit project, public class names not matching filenames are not relevant concerns since these are code snippets used for NPath complexity testing, not standalone compilable Java classes.
Learnt from: Error10556
PR: cqfn/aibolit#866
File: test/metrics/npath/javacode/WhileWithBreak.java:6-16
Timestamp: 2025-07-15T18:57:59.152Z
Learning: In the aibolit project, test files under `test/metrics/npath/javacode/` directory consistently use the class name `Test` regardless of the file name. These are code snippets used for NPath complexity testing, not standalone compilable Java classes, so Java naming conventions don't apply.
Learnt from: Error10556
PR: cqfn/aibolit#866
File: aibolit/metrics/npath/main.py:128-128
Timestamp: 2025-07-15T18:58:35.630Z
Learning: In NPath complexity calculations, the formula for loops (for, while) is additive: control_npath + body_npath + 1, not multiplicative. This follows the official NPath specification where nested statements have additive complexity, while only sequential statements have multiplicative complexity.
Learnt from: Error10556
PR: cqfn/aibolit#866
File: aibolit/metrics/npath/main.py:128-128
Timestamp: 2025-07-15T18:58:35.630Z
Learning: The NPath complexity specification by Nejmeh (1988) and Checkstyle defines that for while loops the formula is "NP(while-range) + NP(expr) + 1" which is additive, not multiplicative. The same pattern applies to for loops.
aibolit/metrics/npath/main.py (3)
Learnt from: Error10556
PR: cqfn/aibolit#866
File: aibolit/metrics/npath/main.py:128-128
Timestamp: 2025-07-15T18:58:35.630Z
Learning: In NPath complexity calculations, the formula for loops (for, while) is additive: control_npath + body_npath + 1, not multiplicative. This follows the official NPath specification where nested statements have additive complexity, while only sequential statements have multiplicative complexity.
Learnt from: Error10556
PR: cqfn/aibolit#866
File: aibolit/metrics/npath/main.py:128-128
Timestamp: 2025-07-15T18:58:35.630Z
Learning: The NPath complexity specification by Nejmeh (1988) and Checkstyle defines that for while loops the formula is "NP(while-range) + NP(expr) + 1" which is additive, not multiplicative. The same pattern applies to for loops.
Learnt from: SinbadTheSailor2005
PR: cqfn/aibolit#816
File: aibolit/patterns/loop_outsider/loop_outsider.py:0-0
Timestamp: 2025-06-08T11:50:22.673Z
Learning: In the aibolit codebase, ASTNodeType.ASSIGNMENT nodes have an attribute called `expressionl` (with 'l' suffix), not `expression`. This is confirmed in `aibolit/ast_framework/_auxiliary_data.py` and used consistently throughout existing patterns like mutable_index and many_primary_ctors.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: make
  • GitHub Check: latexmk
🔇 Additional comments (9)
aibolit/metrics/npath/main.py (9)

107-107: Good refactoring for modularity.

The extraction of composite node handling into a separate method improves code organization and readability.


115-115: Good addition of explicit expression handling.

Adding explicit handling for expression nodes ensures consistent processing through the dedicated _expression_npath method.


121-122: Well-designed method with clear responsibility.

The _composite_npath method cleanly separates the handling of composite nodes and follows the single responsibility principle.


125-125: Correct implementation of sequential complexity.

The use of max(1, math.prod(...)) properly implements the NPath specification for sequential statements (multiplicative) while preventing zero complexity results.


128-128: Correct implementation of if-statement complexity.

The changes properly implement the NPath specification:

  • Using _expression_npath for consistent condition evaluation
  • Using additive formula condition_npath + then_npath + else_npath which is correct for nested statements in NPath complexity

Also applies to: 135-135


138-148: Improved switch statement complexity calculation.

The rewritten implementation correctly:

  • Tracks presence of cases and defaults for accurate complexity assessment
  • Uses additive approach for nested case statements
  • Applies appropriate penalties for missing cases or defaults
  • Handles null statements between multiple case labels

151-151: Correct loop complexity implementation.

The changes properly implement the NPath specification for loops:

  • Using _expression_npath for consistent expression evaluation
  • Using additive formulas control_npath + body_npath + 1 which is correct per the NPath specification for nested statements in loops

Also applies to: 153-153, 156-156, 158-158


160-163: Well-designed expression complexity handler.

The _expression_npath method provides centralized and consistent handling of expression complexity:

  • Properly delegates binary operations to specialized handler
  • Recursively processes child expressions
  • Uses appropriate summation for expression children

166-168: Improved binary expression complexity calculation.

The changes provide consistent and correct binary expression handling:

  • Using _expression_npath for both operands ensures consistent evaluation
  • Tuple usage for operator check follows best practices
  • Additive approach with logical operator penalty is appropriate for expression complexity
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Error10556 Error10556 changed the title Todo852 Fix NPath computation Jul 15, 2025
@Error10556 Error10556 marked this pull request as draft July 15, 2025 18:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🔭 Outside diff range comments (2)
test/metrics/npath/javacode/EvenMoreComplicated.java (1)

14-20: Local variable a is read before initialization – compilation will fail

boolean a, b = true; declares a without an initializer, yet the subsequent if (a || b) uses it.
Javac rejects reads of un-initialized local variables, which will break any build steps that attempt real compilation.

-boolean a, b = true;
+boolean a = false, b = true; // value doesn’t matter for metric, just must be initialised
test/metrics/npath/javacode/OtherClass.java (1)

13-23: Undeclared identifiers i, j, MyClass – code will not compile

The body references variables and a class that aren’t declared anywhere in the file.
If the test harness ever switches to a compiler-backed parser this file will break.

Fast fix:

-    MyClass myObj = new MyClass();
-    int result;
-    if(i % 2 == 0) {
+    int i = 0, j = 0;
+    int result;
+    if (i % 2 == 0) {
         result = i + j;

…and either remove MyClass or stub it.

♻️ Duplicate comments (3)
test/metrics/npath/javacode/ForWithAndCondition.java (1)

6-9: Same class-name collision as above.

Rename class Test or introduce a package to prevent clashes during bulk compilation.

test/metrics/npath/javacode/IfWithIfElseInsideOuterElse.java (1)

6-6: Potential class-name duplication / default package issue.

Same remark as for other files: unique class name or explicit package is preferable.

test/metrics/npath/javacode/NestedWhileLoops.java (1)

6-9: Class-name duplication in default package.

NestedWhileLoops.java also declares a Test class. See earlier comments for mitigation.

🧹 Nitpick comments (20)
test/metrics/npath/javacode/EvenMoreComplicated.java (1)

30-30: Nit: clarify magic “288” comment or use an annotation block

A bare // 288 gives no context. Consider something self-describing (e.g. // NPATH: 288) so future readers immediately know what the number represents.

test/metrics/npath/javacode/OtherClass.java (2)

3-8: Duplicate SPDX header – keep only one copy

The same copyright / licence banner appears twice.
Redundant headers add noise and hinder quick scans.


26-26: Same comment as above: make the trailing “3” self-describing

// NPATH: 3 would be clearer.

test/metrics/npath/javacode/EmptyWhileLoop.java (1)

11-11: Trailing magic number: prefer explicit tag, e.g. // NPATH: 2

test/metrics/npath/javacode/Complicated.java (2)

7-8: Comment mixes cyclomatic and NPath metrics – may confuse readers

Line 7 says “cyclomatic complexity 12”, while the new footer says // 12 (implied NPath).
Align the wording to avoid ambiguity, e.g.:

-  // This method has a cyclomatic complexity of 12
+  // Expected NPath complexity: 12

30-30: Same note on magic numbers: make it explicit (// NPATH: 12).

test/metrics/npath/javacode/SwitchSimpleWithoutDefault.java (1)

8-11: Add an explicit default case (even if empty) for completeness.

Leaving out default can trigger style-check warnings and may weaken the example’s clarity compared with other switch test files that do include a default branch. A stub keeps behaviour unchanged while maintaining consistency:

  case 2: System.out.println("2"); break;
+ default: /* no-op */
+     break;
test/metrics/npath/javacode/IfWithOrCondition.java (2)

7-14: Add explicit access modifiers for test clarity
Package-private classes/methods are perfectly valid, but making the class and its method public avoids compiler warnings when the file is compiled in isolation and keeps consistency with the rest of the test corpus.

-class Test {
-    void validate(int x, int y) {
+public class Test {
+    public void validate(int x, int y) {
         if (x == 0 || y == 0) {
             System.out.println("Zero detected");
         } else {
             System.out.println("No zeros");
         }
     }
 }

15-15: Keep metric annotations on a dedicated line comment
Some IDEs flag a trailing numeric literal (// 3) as a magic number. Prefixing with a small descriptor (e.g. // NPATH: 3) makes the intent explicit and silences such warnings without affecting metric logic.

test/metrics/npath/javacode/TwoClassesDefinition.java (1)

6-8: File-name mismatch is fine but add a short comment
TwoClassesDefinition.java hosts two package-private classes. That’s legal, yet adding a quick note (// package-private by design) saves future confusion for newcomers skimming the code.

test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1)

15-15: Use an explicit tag for the metric value
Same remark as before – consider // NPATH: 3 rather than a bare // 3.

test/metrics/npath/javacode/EmptyInfiniteForLoop.java (2)

7-9: Remove the unused parameter to silence warnings
n is never referenced, which triggers an “unused parameter” warning in most static-analysis tools.

-    void empty(int n) {
-        for (;;);
+    void empty() {
+        for (;;);
     }

This change does not alter the intended NPath (still 2).


11-11: Annotate metric value explicitly

-// 2
+// NPATH: 2
test/metrics/npath/javacode/ForWithSwitch.java (2)

9-14: Document intentional fall-through
Cases 1-3 deliberately fall through. Add // fall through to avoid false-positive warnings from Checkstyle / SpotBugs.

-                case 1: System.out.println("1");
-                case 2: System.out.println("2");
-                case 3: System.out.println("3");
+                case 1: System.out.println("1"); // fall through
+                case 2: System.out.println("2"); // fall through
+                case 3: System.out.println("3"); // fall through

18-18: Prefix metric literal

-// 5
+// NPATH: 5
test/metrics/npath/javacode/IfWithInnerIfElse.java (1)

6-6: Consider giving the class a unique name or package.

Several test files declare a top-level class called Test or similarly generic names in the default package. If the test harness ever compiles the whole folder, these collide.
Either add a lightweight package test.npath; declaration or ensure file-level unique class names.

test/metrics/npath/javacode/WhileWithAndCondition.java (1)

6-9: Duplicate Test class in default package.

WhileWithAndCondition.java, ForWithAndCondition.java, NestedWhileLoops.java, … all define class Test in the unnamed package. This compiles only when each file is compiled in isolation.
Adding a small package declaration or renaming avoids accidental clashes.

-package (default)
+package test.metrics.npath.snippets;
test/metrics/npath/javacode/IfWithAndCondition.java (1)

15-15: Clarify the inline complexity marker

The lone comment // 3 is cryptic. Consider an explicit tag such as // NPATH: 3 to aid automated harvesters.

test/metrics/npath/javacode/ComplexIfElseWithNPathComplexityOf5.java (1)

25-25: Same remark as above: replace // 5 with something descriptive like // NPATH: 5.

aibolit/metrics/npath/main.py (1)

135-145: Consider adding comments to explain the switch complexity calculation.

The new implementation handles edge cases well, but the logic for null_statements and the adjustments for missing cases/defaults could benefit from explanatory comments.

 def _switch_npath(self, node: ASTNode) -> int:
     npath = self._expression_npath(node.expression)
     has_case = False
     has_default = False
     for case_group in node.cases:
         if len(case_group.case):
             has_case = True
         else:
             has_default = True
+        # Account for fall-through cases (multiple case labels without break)
         null_statements = max(0, len(case_group.case) - 1)
         npath += self._node_npath(case_group.statements) + null_statements
+    # Add 1 if no cases exist, and 1 if no default exists
     return npath + int(not has_case) + int(not has_default)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5d1b0a and 7883f83.

📒 Files selected for processing (36)
  • aibolit/metrics/npath/main.py (1 hunks)
  • test/metrics/npath/javacode/ClassDefinition.java (1 hunks)
  • test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1 hunks)
  • test/metrics/npath/javacode/ComplexIfElseWithNPathComplexityOf5.java (1 hunks)
  • test/metrics/npath/javacode/ComplexWithIfElseInsideIfElseBlocks.java (1 hunks)
  • test/metrics/npath/javacode/Complicated.java (1 hunks)
  • test/metrics/npath/javacode/EmptyInfiniteForLoop.java (1 hunks)
  • test/metrics/npath/javacode/EmptyWhileLoop.java (1 hunks)
  • test/metrics/npath/javacode/EvenMoreComplicated.java (2 hunks)
  • test/metrics/npath/javacode/ForWithAndCondition.java (1 hunks)
  • test/metrics/npath/javacode/ForWithBreakContinue.java (1 hunks)
  • test/metrics/npath/javacode/ForWithIfInside.java (1 hunks)
  • test/metrics/npath/javacode/ForWithOrCondition.java (1 hunks)
  • test/metrics/npath/javacode/ForWithSwitch.java (1 hunks)
  • test/metrics/npath/javacode/IfWithAndCondition.java (1 hunks)
  • test/metrics/npath/javacode/IfWithIfElseInsideOuterElse.java (1 hunks)
  • test/metrics/npath/javacode/IfWithInnerIfElse.java (1 hunks)
  • test/metrics/npath/javacode/IfWithOrCondition.java (1 hunks)
  • test/metrics/npath/javacode/NestedForLoops.java (1 hunks)
  • test/metrics/npath/javacode/NestedSwitchStatements.java (1 hunks)
  • test/metrics/npath/javacode/NestedWhileLoops.java (1 hunks)
  • test/metrics/npath/javacode/OneIfElseStatement.java (1 hunks)
  • test/metrics/npath/javacode/OneIfStatement.java (1 hunks)
  • test/metrics/npath/javacode/OtherClass.java (2 hunks)
  • test/metrics/npath/javacode/SimpleForLoop.java (1 hunks)
  • test/metrics/npath/javacode/SimpleWhileLoops.java (1 hunks)
  • test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1 hunks)
  • test/metrics/npath/javacode/SwitchSimpleWithoutDefault.java (1 hunks)
  • test/metrics/npath/javacode/SwitchWithFallthrough.java (1 hunks)
  • test/metrics/npath/javacode/TestSwitchEmpty.java (1 hunks)
  • test/metrics/npath/javacode/TwoClassesDefinition.java (1 hunks)
  • test/metrics/npath/javacode/WhileWithAndCondition.java (1 hunks)
  • test/metrics/npath/javacode/WhileWithBreak.java (1 hunks)
  • test/metrics/npath/javacode/WhileWithIf.java (1 hunks)
  • test/metrics/npath/javacode/WhileWithOrCondition.java (1 hunks)
  • test/metrics/npath/test_all_types.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
aibolit/metrics/npath/main.py (1)
Learnt from: SinbadTheSailor2005
PR: cqfn/aibolit#816
File: aibolit/patterns/loop_outsider/loop_outsider.py:0-0
Timestamp: 2025-06-08T11:50:22.673Z
Learning: In the aibolit codebase, ASTNodeType.ASSIGNMENT nodes have an attribute called `expressionl` (with 'l' suffix), not `expression`. This is confirmed in `aibolit/ast_framework/_auxiliary_data.py` and used consistently throughout existing patterns like mutable_index and many_primary_ctors.
🧬 Code Graph Analysis (11)
test/metrics/npath/javacode/OneIfStatement.java (1)
test/metrics/npath/javacode/OneIfElseStatement.java (1)
  • WithOneIf (6-14)
test/metrics/npath/javacode/ForWithAndCondition.java (4)
test/metrics/npath/javacode/ForWithOrCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithAndCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/IfWithOrCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SimpleForLoop.java (1)
  • Test (6-12)
test/metrics/npath/javacode/SimpleWhileLoops.java (16)
test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1)
  • Test (6-19)
test/metrics/npath/javacode/EmptyInfiniteForLoop.java (1)
  • Test (6-10)
test/metrics/npath/javacode/EmptyWhileLoop.java (1)
  • Test (6-10)
test/metrics/npath/javacode/ForWithAndCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/ForWithBreakContinue.java (1)
  • Test (6-18)
test/metrics/npath/javacode/ForWithIfInside.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithOrCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithAndCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithSwitch.java (1)
  • Test (6-17)
test/metrics/npath/javacode/IfWithOrCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedForLoops.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedWhileLoops.java (1)
  • Test (6-17)
test/metrics/npath/javacode/SimpleForLoop.java (1)
  • Test (6-12)
test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SwitchSimpleWithoutDefault.java (1)
  • Test (6-13)
test/metrics/npath/javacode/SwitchWithFallthrough.java (1)
  • Test (6-16)
test/metrics/npath/javacode/OneIfElseStatement.java (1)
test/metrics/npath/javacode/OneIfStatement.java (1)
  • WithOneIf (6-12)
test/metrics/npath/javacode/ForWithIfInside.java (14)
test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1)
  • Test (6-19)
test/metrics/npath/javacode/EmptyInfiniteForLoop.java (1)
  • Test (6-10)
test/metrics/npath/javacode/ForWithAndCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/ForWithBreakContinue.java (1)
  • Test (6-18)
test/metrics/npath/javacode/ForWithOrCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithAndCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithSwitch.java (1)
  • Test (6-17)
test/metrics/npath/javacode/IfWithOrCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedForLoops.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedWhileLoops.java (1)
  • Test (6-17)
test/metrics/npath/javacode/SimpleForLoop.java (1)
  • Test (6-12)
test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SwitchSimpleWithoutDefault.java (1)
  • Test (6-13)
test/metrics/npath/javacode/SwitchWithFallthrough.java (1)
  • Test (6-16)
test/metrics/npath/javacode/WhileWithBreak.java (16)
test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1)
  • Test (6-19)
test/metrics/npath/javacode/EmptyInfiniteForLoop.java (1)
  • Test (6-10)
test/metrics/npath/javacode/EmptyWhileLoop.java (1)
  • Test (6-10)
test/metrics/npath/javacode/ForWithAndCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/ForWithBreakContinue.java (1)
  • Test (6-18)
test/metrics/npath/javacode/ForWithIfInside.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithOrCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithAndCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithSwitch.java (1)
  • Test (6-17)
test/metrics/npath/javacode/IfWithOrCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedForLoops.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedWhileLoops.java (1)
  • Test (6-17)
test/metrics/npath/javacode/SimpleForLoop.java (1)
  • Test (6-12)
test/metrics/npath/javacode/SimpleWhileLoops.java (1)
  • Test (6-13)
test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SwitchSimpleWithoutDefault.java (1)
  • Test (6-13)
test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1)
test/metrics/npath/javacode/ForWithSwitch.java (1)
  • Test (6-17)
test/metrics/npath/javacode/NestedForLoops.java (14)
test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1)
  • Test (6-19)
test/metrics/npath/javacode/EmptyInfiniteForLoop.java (1)
  • Test (6-10)
test/metrics/npath/javacode/ForWithAndCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/ForWithBreakContinue.java (1)
  • Test (6-18)
test/metrics/npath/javacode/ForWithIfInside.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithOrCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithAndCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/ForWithSwitch.java (1)
  • Test (6-17)
test/metrics/npath/javacode/IfWithOrCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/NestedWhileLoops.java (1)
  • Test (6-17)
test/metrics/npath/javacode/SimpleForLoop.java (1)
  • Test (6-12)
test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SwitchSimpleWithoutDefault.java (1)
  • Test (6-13)
test/metrics/npath/javacode/SwitchWithFallthrough.java (1)
  • Test (6-16)
test/metrics/npath/javacode/WhileWithAndCondition.java (3)
test/metrics/npath/javacode/ForWithAndCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithAndCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SimpleWhileLoops.java (1)
  • Test (6-13)
test/metrics/npath/javacode/WhileWithOrCondition.java (3)
test/metrics/npath/javacode/ForWithOrCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/IfWithOrCondition.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SimpleWhileLoops.java (1)
  • Test (6-13)
test/metrics/npath/javacode/ForWithOrCondition.java (3)
test/metrics/npath/javacode/ForWithAndCondition.java (1)
  • Test (6-12)
test/metrics/npath/javacode/ForWithIfInside.java (1)
  • Test (6-14)
test/metrics/npath/javacode/SimpleForLoop.java (1)
  • Test (6-12)
🔇 Additional comments (24)
test/metrics/npath/javacode/ClassDefinition.java (1)

6-7: LGTM – minimal valid Java class, no issues found

test/metrics/npath/javacode/TestSwitchEmpty.java (1)

1-12: LGTM! Well-structured test case for empty switch statement.

The test case correctly implements an empty switch statement, which is a valid Java construct. The expected NPath complexity value of 2 is reasonable for this control flow structure.

test/metrics/npath/javacode/SimpleForLoop.java (1)

1-14: LGTM! Well-structured test case for simple for loop.

The test case correctly implements a simple for loop with proper Java syntax. The expected NPath complexity value of 2 is reasonable for this control flow structure.

test/metrics/npath/javacode/SimpleWhileLoops.java (1)

1-15: LGTM! Well-structured test case for simple while loop.

The test case correctly implements a simple while loop with proper Java syntax. The expected NPath complexity value of 2 is reasonable for this control flow structure.

test/metrics/npath/javacode/SwitchWithFallthrough.java (1)

8-14: Looks good – fall-through is clearly documented.

Code is minimal, well-commented, and serves the intended test purpose without introducing ambiguity.

test/metrics/npath/javacode/NestedForLoops.java (1)

8-12: Nested-loop sample is fine.

The snippet is concise and demonstrates the intended nesting scenario for NPath tests without unnecessary extras.

test/metrics/npath/javacode/ForWithBreakContinue.java (1)

8-16: LGTM – covers break/continue paths cleanly.

No functional or style issues spotted; the construct is clear for metric testing.

test/metrics/npath/javacode/ForWithIfInside.java (1)

8-12: Example is clear and self-contained.

Implements the minimal pattern needed for the metric check with readable intent.

test/metrics/npath/javacode/SwitchSimpleWithDefault.java (1)

9-12: Nice concise switch block
The switch is minimal and correctly terminates each case. No issues spotted.

test/metrics/npath/javacode/ForWithOrCondition.java (1)

1-14: LGTM! Well-structured test case for OR condition in loops.

The implementation correctly demonstrates a for loop with an OR condition in the loop guard, which is appropriate for testing NPath complexity calculations for logical OR operators in loop conditions.

test/metrics/npath/javacode/ComplexWithIfElseInsideIfElseBlocks.java (1)

1-24: LGTM! Excellent test case for nested if-else complexity.

The nested if-else structure correctly creates 4 distinct execution paths, making it an ideal test case for validating NPath complexity calculations for nested conditional constructs.

test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java (1)

6-21: LGTM! Good test case for multiple control flow constructs.

The implementation correctly combines a for-each loop with conditional logic and a switch statement, providing appropriate coverage for testing NPath complexity calculations with multiple nested constructs.

test/metrics/npath/javacode/NestedSwitchStatements.java (1)

1-23: LGTM! Excellent test case for nested switch statements.

The nested switch structure correctly demonstrates complex switch statement scenarios with proper break statements to prevent fall-through. The inline comment helpfully explains the path calculation, and the expected complexity of 5 is accurate.

test/metrics/npath/test_all_types.py (4)

162-177: LGTM! Well-designed helper methods for external file testing.

The new helper methods _filepath and _value_from_filepath provide a clean abstraction for loading and testing external Java files, improving code maintainability and reducing duplication.


32-34: Inconsistency detected between AI summary and code.

The AI summary mentions that the expected complexity value was updated from 200 to 288, but the code still shows 200. This needs clarification.

Please verify whether the expected complexity value for EvenMoreComplicated.java should be 200 or 288, and ensure the test assertion matches the actual expected value.

Likely an incorrect or invalid review comment.


123-125: Test method references the typo filename.

The test method references ComlpexForWithMultipleConstructs.java which contains a typo. If the filename is corrected as suggested earlier, this reference should also be updated.

Update the file reference once the Java file is renamed to fix the typo.


16-50: File path consistency confirmed; verify complexity assertions.

All referenced .java files under test/metrics/npath/javacode/ exist (the only missing ooo1.java is intentionally used in testNonExistedFile). Please double-check the expected NPath complexity values in these tests:

  • testLowScore (OtherClass.java): res['data'][0]['complexity'] == 3
  • testMediumScore (Complicated.java): res['data'][0]['complexity'] == 12
  • testHighScore (EvenMoreComplicated.java): res['data'][0]['complexity'] == 200
aibolit/metrics/npath/main.py (7)

112-112: LGTM! Good refactoring of the dispatcher.

Adding ASTNodeType.EXPRESSION handling and extracting the default handler to _composite_npath improves code organization and readability.

Also applies to: 115-115


118-120: LGTM! Clean extraction of default complexity calculation.

The method provides a clear default behavior for nodes without specific handlers.


122-122: LGTM! Better use of built-in functions.

Using math.prod is more readable and efficient than manual multiplication.


148-148: Verify the additive complexity calculation for loops.

Similar to the if-statement, the change from multiplication to addition deviates from traditional NPath complexity calculations.

Also applies to: 151-151


154-156: Consistent with other loop complexity calculations.

The additive formula is consistently applied across all loop types.


158-161: LGTM! Clean expression handling implementation.

The method provides a unified approach to handle different expression types with proper delegation to specialized handlers.


164-166: LGTM! Simplified binary expression complexity calculation.

The implementation correctly adds extra complexity only for logical operators (&&, ||) which create additional execution paths.

Comment thread test/metrics/npath/javacode/EmptyWhileLoop.java Outdated
Comment thread test/metrics/npath/javacode/OneIfStatement.java Outdated
Comment thread test/metrics/npath/javacode/OneIfElseStatement.java Outdated
Comment thread test/metrics/npath/javacode/IfWithInnerIfElse.java Outdated
Comment thread test/metrics/npath/javacode/IfWithIfElseInsideOuterElse.java Outdated
Comment thread test/metrics/npath/javacode/WhileWithIf.java Outdated
Comment thread test/metrics/npath/javacode/WhileWithBreak.java Outdated
Comment thread test/metrics/npath/javacode/ComlpexForWithMultipleConstructs.java Outdated
Comment thread aibolit/metrics/npath/main.py Outdated
Comment thread aibolit/metrics/npath/main.py
@Error10556 Error10556 marked this pull request as ready for review July 15, 2025 18:54
@Error10556

Copy link
Copy Markdown
Contributor Author

@yegor256 Hello! Could you take a look when you have the time?

@AntonProkopyev

Copy link
Copy Markdown
Contributor

@Error10556 Hello! Thank you for fixing the NPath complexity computation. Could you please move out test/* changes to separate PR to minimize diff and make PR more simple for review.

Comment thread aibolit/metrics/npath/main.py Outdated
Comment thread aibolit/metrics/npath/main.py Outdated
@AntonProkopyev

Copy link
Copy Markdown
Contributor

@ivanovmg JFYI

@Error10556

Copy link
Copy Markdown
Contributor Author

@AntonProkopyev Thank you for the suggestions, I applied them.

I will create a separate PR for the java test files later.

@yegor256

Copy link
Copy Markdown
Member

@AntonProkopyev what's up with this one? good to go?

@ivanovmg ivanovmg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Error10556 thank you very much for fixing it! I spent quite some time without success, but here comes the power of the open source and pdd!
It looks good to me. I have some optional comments though. Please have a look.

@yegor256 FYI

else 1
)
return condition_npath * then_npath + else_npath
return condition_npath + then_npath + else_npath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing it!

has_default = True
null_statements = max(0, len(case_group.case) - 1)
npath += self._node_npath(case_group.statements) + null_statements
return npath + int(not has_case) + int(not has_default)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, now switch statements seem to work right! However, from the readability standpoint, this statement is not that clear to me. As far as I understand, we must always add 1 for default case in npath metric, even if it is implicit. Nevertheless, here we seem to add 1 only when default is explicit. I wonder if you'd please clarify.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! So, in this statement, we ensure that we always add at least 1 for the default and at least 1 for the cases.

The idea is simple. First, we account for all the present cases and defaults. Then, we look back and retroactively "add" an empty case statement if there were none (+ int(not has_case)) and an empty default statement if there were none (+ int(not has_default)).

).strip()
assert self._value(content) == 2
# NPath = 3 = 2 (for 2 CASEs) + 1 (for an empty DEFAULT)
assert self._value(content) == 3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing those, considering that you double checked with checkstyle!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have double-checked!

# </module>
#
# $ java -jar checkstyle-10.26.1-all.jar -c config.xml test/metrics/npath/Foo.java
# [ERROR] ... NPath Complexity is 288 (max allowed is 1). [NPathComplexity]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great that you made these tests pass! However, I recommend that we keep a comment that the actual npath complexity is 288, rather than 200 as stated in the java file.

@yegor256

yegor256 commented Jul 20, 2025

Copy link
Copy Markdown
Member

@rultor merge

@rultor

rultor commented Jul 20, 2025

Copy link
Copy Markdown
Contributor

@rultor merge

@yegor256 OK, I'll try to merge now. You can check the progress of the merge here.

@rultor rultor merged commit c8a4347 into cqfn:master Jul 20, 2025
19 checks passed
@rultor

rultor commented Jul 20, 2025

Copy link
Copy Markdown
Contributor

@rultor merge

@yegor256 Done! FYI, the full log is here (took me 19min).

@yegor256

Copy link
Copy Markdown
Member

@Error10556 many thanks!

@0crat

0crat commented Jul 20, 2025

Copy link
Copy Markdown
Collaborator

@Error10556 Thank you for your contribution! Here's the breakdown of your points: +16 base, +5.15 for 103 hits-of-code, -16 for no code review, -9.8 for 57 comments during review (exceeding the 8-comment threshold). Your total comes to 4 points, which aligns with our minimum reward policy. We appreciate your effort, but please ensure future contributions include a code review to maximize your points. Your running balance is now +4. Keep up the good work and remember to balance speed and quality in your future submissions!

@0crat

0crat commented Jul 20, 2025

Copy link
Copy Markdown
Collaborator

@ivanovmg Hey there, code review superstar! 🌟 You've just racked up a sweet +20 points for your awesome review. That's +12 for showing up and an extra +8 for those 47 thoughtful comments you dropped. Nice work on going above and beyond - you definitely dodged that 10-point deduction for low engagement! Your balance is now sitting pretty at +77. Keep up the great work, and who knows? You might just hit that 24-point max bonus next time!

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.

MvnFreeNPathMetric has incorrect functioning in test_all_types.py MvnFreeNPathMetric doesn't work as expected in test_all_types.py

6 participants