Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Conversation

@KachanovYev
Copy link
Contributor

@KachanovYev KachanovYev commented Jun 30, 2025

#742

Implement bidirect index pattern
tests is working
add docs for code

Summary by CodeRabbit

  • New Features

    • Added detection of bidirectional index variables in Java source files, identifying variables both incremented and decremented within the same method or block, excluding increments/decrements inside for-loops with local variables of the same name.
    • Extended AST node types to include augmented assignments, identifiers, and unary operations.
  • Documentation

    • Added detailed explanations describing the bidirectional index detection concept and usage.
  • Tests

    • Removed unused imports and decorators from bidirectional index pattern tests for cleaner test code.

 tests is working
 add docs for code
too-many-statements issue
@coderabbitai
Copy link

coderabbitai bot commented Jun 30, 2025

Walkthrough

The BidirectIndex class was fully implemented to analyze Java source code lines for variables used as bidirectional indices, including methods to find methods, analyze blocks, detect for-loops, and count increments/decrements. The test for BidirectIndex was enabled by removing the skip decorator and the unused unittest import. Three new AST node types were added to the ASTNodeType enum.

Changes

File(s) Change Summary
aibolit/patterns/bidirect_index/bidirect_index.py Fully implemented BidirectIndex with methods to find methods, analyze blocks, detect for-loops, count increments/decrements, and detailed docstrings.
test/patterns/bidirect_index/test_bidirect_index.py Removed unittest import and skip decorator, enabled tests without other modifications.
aibolit/ast_framework/ast_node_type.py Added enum members AUGMENTED_ASSIGNMENT, IDENTIFIER, and UNARY_OPERATION to ASTNodeType.

Poem

In code we search, both up and down,
For indices that spin around.
With increments here, decrements there,
Bidirectional bunnies sniff everywhere!
Now tests can hop and run with glee—
A clever change, as you can see!
🐇✨

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 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 ff28b9a and 1872c25.

📒 Files selected for processing (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py
⏰ 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
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 generate unit tests to generate unit tests for 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
aibolit/patterns/bidirect_index/bidirect_index.py (4)

42-42: Fix quote style for consistency.

The codebase prefers single quotes based on the pipeline failures.

-        with open(filename, encoding="utf-8") as f:
+        with open(filename, encoding='utf-8') as f:

59-62: Simplify nested if statements.

Combine the conditions to improve readability.

-            if re.search(r"\bvoid\b\s+\w+\s*\(.*\)\s*{", line):
-                if mstart is None:
-                    mstart = idx
-                    brace = 0
+            if re.search(r'\bvoid\b\s+\w+\s*\(.*\)\s*{', line) and mstart is None:
+                mstart = idx
+                brace = 0

63-64: Fix quote style for consistency.

-            brace += line.count("{")
-            brace -= line.count("}")
+            brace += line.count('{')
+            brace -= line.count('}')

99-100: Fix quote style for consistency.

-                    brace += lines[i].count("{")
-                    brace -= lines[i].count("}")
+                    brace += lines[i].count('{')
+                    brace -= lines[i].count('}')
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 70d86b2 and 519d2dd.

📒 Files selected for processing (2)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
  • test/patterns/bidirect_index/test_bidirect_index.py (0 hunks)
💤 Files with no reviewable changes (1)
  • test/patterns/bidirect_index/test_bidirect_index.py
🧰 Additional context used
🪛 Ruff (0.11.9)
aibolit/patterns/bidirect_index/bidirect_index.py

59-60: Use a single if statement instead of nested if statements

(SIM102)


86-86: Ambiguous variable name: l

(E741)


116-116: Ambiguous variable name: l

(E741)


141-141: Ambiguous variable name: l

(E741)

🪛 Flake8 (7.2.0)
aibolit/patterns/bidirect_index/bidirect_index.py

[error] 86-86: ambiguous variable name 'l'

(E741)


[error] 116-116: ambiguous variable name 'l'

(E741)


[error] 141-141: ambiguous variable name 'l'

(E741)

🪛 GitHub Actions: flake8
aibolit/patterns/bidirect_index/bidirect_index.py

[error] 86-86: flake8: ambiguous variable name 'l' (E741)


[error] 116-116: flake8: ambiguous variable name 'l' (E741)


[error] 141-141: flake8: ambiguous variable name 'l' (E741)

🪛 GitHub Actions: ruff
aibolit/patterns/bidirect_index/bidirect_index.py

[error] 42-42: Ruff Q000: Double quotes found but single quotes preferred.


[error] 59-59: Ruff Q000: Double quotes found but single quotes preferred.


[error] 63-63: Ruff Q000: Double quotes found but single quotes preferred.


[error] 64-64: Ruff Q000: Double quotes found but single quotes preferred.


[error] 86-86: Ruff E741: Ambiguous variable name: l.


[error] 99-99: Ruff Q000: Double quotes found but single quotes preferred.


[error] 100-100: Ruff Q000: Double quotes found but single quotes preferred.


[error] 116-116: Ruff E741: Ambiguous variable name: l.


[error] 141-141: Ruff E741: Ambiguous variable name: l.

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: latexmk
  • GitHub Check: make
  • GitHub Check: e2e-test
🔇 Additional comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

8-25: Excellent documentation!

The docstring provides a clear and comprehensive explanation of the bidirectional index pattern, including the definition, use cases, and usage example.


1-154: Well-implemented bidirectional index pattern detection!

The implementation successfully achieves the PR objectives with comprehensive pattern matching and proper scope handling. The logic correctly identifies bidirectional indices while excluding increments/decrements within for-loops that declare local variables.

The only issues are style-related (variable naming and quote consistency) which should be addressed to pass the CI checks.

aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
flake8
ruff
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

116-119: Replace variable name 'line_' with a more descriptive name.

For consistency and clarity, use a more descriptive variable name.

Apply this diff:

         k = start
         while k < end:
-            line_ = lines[k]
-            for_decl = re.match(r'\s*for\s*\(\s*int\s+' + re.escape(var) + r'\s*=', line_)
+            current_line = lines[k]
+            for_decl = re.match(r'\s*for\s*\(\s*int\s+' + re.escape(var) + r'\s*=', current_line)
             if for_decl:
-                brace = line_.count('{') - line_.count('}')
+                brace = current_line.count('{') - current_line.count('}')

141-151: Replace variable name 'line_' with a more descriptive name.

For consistency with the rest of the codebase, use a more descriptive variable name.

Apply this diff:

             if not in_for:
-                line_ = lines[k]
+                current_line = lines[k]
                 if re.search(
                         r'(\+\+' + re.escape(var) + r'|' + re.escape(var) + r'\+\+|' +
                         re.escape(var) + r'\s*\+=\s*1\b|' +
-                        re.escape(var) + r'\s*=\s*' + re.escape(var) + r'\s*\+\s*1\b)', line_):
+                        re.escape(var) + r'\s*=\s*' + re.escape(var) + r'\s*\+\s*1\b)', current_line):
                     inc_outside += 1
                 if re.search(
                         r'(--' + re.escape(var) + r'|' + re.escape(var) + r'--|' +
                         re.escape(var) + r'\s*-=\s*1\b|' +
-                        re.escape(var) + r'\s*=\s*' + re.escape(var) + r'\s*-\s*1\b)', line_):
+                        re.escape(var) + r'\s*=\s*' + re.escape(var) + r'\s*-\s*1\b)', current_line):
                     dec_outside += 1
🧹 Nitpick comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

59-62: Simplify nested if statements.

The nested if statements can be combined into a single condition for better readability.

Apply this diff to simplify:

-            if re.search(r'\bvoid\b\s+\w+\s*\(.*\)\s*{', line):
-                if mstart is None:
-                    mstart = idx
-                    brace = 0
+            if re.search(r'\bvoid\b\s+\w+\s*\(.*\)\s*{', line) and mstart is None:
+                mstart = idx
+                brace = 0

86-90: Use consistent variable naming.

The variable name line_ is used here while line is used elsewhere. Consider using a more descriptive name like next_line or current_line for clarity and consistency.

Apply this diff:

                 j = i + 1
                 while j <= end:
-                    line_ = lines[j]
-                    if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
+                    next_line = lines[j]
+                    if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', next_line):
                         break
                     j += 1
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 519d2dd and 2fac967.

📒 Files selected for processing (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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.
🪛 Ruff (0.11.9)
aibolit/patterns/bidirect_index/bidirect_index.py

59-60: Use a single if statement instead of nested if statements

(SIM102)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: latexmk
  • GitHub Check: make
  • GitHub Check: e2e-test

aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
update doc;
update pattern for searching;
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

42-46: Improve exception chaining for better error context.

The error handling is good, but consider using exception chaining to preserve the original exception context.

-        except (FileNotFoundError, IOError) as e:
-            raise ValueError(f'Failed to read file {filename}: {e}')
+        except (FileNotFoundError, IOError) as e:
+            raise ValueError(f'Failed to read file {filename}: {e}') from e

68-73: Simplify nested if statements for better readability.

The regex pattern effectively captures Java method signatures with various modifiers and return types. However, the nested if statement can be simplified.

-            if (re.search(
-                    r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
-                    line)):
-                if mstart is None:
-                    mstart = idx
-                    brace = 0
+            if (re.search(
+                    r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
+                    line) and mstart is None):
+                mstart = idx
+                brace = 0
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2fac967 and 9f93a1e.

📒 Files selected for processing (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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 (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)
aibolit/ast_framework/ast_node.py (1)
  • line (41-69)
🪛 Ruff (0.11.9)
aibolit/patterns/bidirect_index/bidirect_index.py

46-46: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


68-71: Use a single if statement instead of nested if statements

(SIM102)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: make
  • GitHub Check: e2e-test
  • GitHub Check: latexmk
🔇 Additional comments (4)
aibolit/patterns/bidirect_index/bidirect_index.py (4)

7-25: Excellent documentation and clear pattern definition.

The class docstring provides a comprehensive explanation of the bidirectional index pattern with clear usage examples. The distinction about excluding for-loop local variables is particularly important for avoiding false positives.


81-117: Well-implemented block analysis with proper variable scope handling.

The method correctly handles both typed and untyped variable declarations, properly determines variable scope, and recursively analyzes nested blocks. The logic for identifying bidirectional indices by checking for both increments and decrements outside for-loop blocks is sound.


119-139: Correct implementation for detecting for-loop local variables.

The method properly identifies for-loop blocks that declare local variables with the target variable name. This is essential for excluding increments/decrements that occur within for-loops, preventing false positives in the bidirectional index detection.


141-164: Comprehensive increment/decrement pattern matching.

The method correctly identifies various increment and decrement patterns including pre/post operators, compound assignments, and explicit assignments. The exclusion of operations within for-loop blocks is properly implemented to avoid false positives.

Copy link
Contributor

@ivanovmg ivanovmg left a comment

Choose a reason for hiding this comment

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

@KachanovYev thank you for the PR! It looks like you spent quite a lot of time figuring out this pattern while brushing up your regex knowledge!

Please have a look at my comments. Bottom line is:

  • Prefer instance methods or external functions (if more reasonable) over static methods
  • Extract helper methods describing what regular expressions mean
  • Use verbose mode for regex, with comments what each section of the pattern does
  • Add a puzzle to refactor this solution in future towards using ast_framework.AST over filename as an input parameter, related to #813

Comment on lines 14 to 15
- increments/decrements inside a for-loop with a local variable of the same name
are ignored (to avoid "fake" cases).
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe indent it to match the indentation of the corresponding item

Suggested change
- increments/decrements inside a for-loop with a local variable of the same name
are ignored (to avoid "fake" cases).
- increments/decrements inside a for-loop with a local variable of the same name
are ignored (to avoid "fake" cases).

Comment on lines 30 to 31
@staticmethod
def value(filename: str | os.PathLike):
Copy link
Contributor

Choose a reason for hiding this comment

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

Please follow the interface described here: #814 (related to #813).

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see, in this implementation you totally relied on the literate source code of the file, rather than the abstract syntax tree.
Although, your solution covers all test cases, it goes against the direction on the unification of the Pattern interface.

I suppose that if this is a working solution, then having resolved the comments, it may be merged with a puzzle to refactor it into using an ast_framework.AST rather than a filename as an input parameter.
@AntonProkopyev can you suggest the way forward here?

Comment on lines 49 to 50
for m_start, m_end in BidirectIndex.find_methods(lines):
BidirectIndex.analyze_block(lines, m_start + 1, m_end, result)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we maybe use instance methods, rather than static methods?

Suggested change
for m_start, m_end in BidirectIndex.find_methods(lines):
BidirectIndex.analyze_block(lines, m_start + 1, m_end, result)
for m_start, m_end in self.find_methods(lines):
self.analyze_block(lines, m_start + 1, m_end, result)

Or, if reasonable, we can extract functions, that detect the pattern, but make BidirectIndex.value simply call this function.

Comment on lines 30 to 31
@staticmethod
def value(filename: str | os.PathLike):
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest against use of a static method, at least in this particular case, where it is a public method.

Suggested change
@staticmethod
def value(filename: str | os.PathLike):
def value(self, filename: str | os.PathLike):

Comment on lines 68 to 70
if (re.search(
r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
line)):
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose this deserves a separate function/method.
Maybe extract a function:

# or whatever the name should be
# I can't tell from the regex what it actually does :)
def _is_index_definition(line: str) -> bool:
  ...

Copy link
Contributor

Choose a reason for hiding this comment

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

These long regexes are really hard to read. But the good news is that they can be made very readable using re.VERBOSE flag.
Please have a look at the examples here: https://pycon2017.regex.training/re-module#verbose-flag

Example in action:
image

Copy link
Contributor

Choose a reason for hiding this comment

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

@KachanovYev can you please follow up on this? These regexes are still too difficult to digest unless properly commented like described here.

Copy link
Contributor

@ivanovmg ivanovmg Jul 21, 2025

Choose a reason for hiding this comment

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

@KachanovYev I strongly encourage you to address this issue. Reading complex regexes in the future is difficult and reduces the level of maintainability.

See #842 (comment)

return for_blocks

@staticmethod
def count_inc_dec(lines, var, for_blocks, start, end):
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest that all methods apart from value are made internal by adding a leading underscore. Or, if you gravitate towards static methods, then better extract functions, outside the class.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)

13-14: Fix indentation to match the parent item.

The indentation is inconsistent with the parent bullet point.

-        - increments/decrements inside a for-loop with a local variable of the same name
-    are ignored (to avoid "fake" cases).
+        - increments/decrements inside a for-loop with a local variable of the same name
+          are ignored (to avoid "fake" cases).
🧹 Nitpick comments (5)
aibolit/patterns/bidirect_index/bidirect_index.py (4)

61-66: Simplify nested if statements for better readability.

The nested if statement can be combined into a single condition as suggested by the static analysis tool.

-            if (re.search(
-                    r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
-                    line)):
-                if mstart is None:
-                    mstart = idx
-                    brace = 0
+            if (re.search(
+                    r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
+                    line) and mstart is None):
+                mstart = idx
+                brace = 0

82-86: Consider extracting variable declaration detection logic.

The variable declaration detection logic is complex and could benefit from extraction into a separate method for better readability and testability.

+    @staticmethod
+    def _extract_declared_variable(line):
+        """Extract variable name from declaration line if present."""
+        typed_decl = re.match(r'\s*(int|long|byte|short)\s+(\w+)\s*=', line)
+        if typed_decl:
+            return typed_decl.group(2)
+        
+        untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line)
+        if untyped_decl:
+            return untyped_decl.group(1)
+        
+        return None

Then use it in the main method:

-            typed_decl = re.match(r'\s*(int|long|byte|short)\s+(\w+)\s*=', line)
-            untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line) \
-                if not typed_decl else None
-            var = typed_decl.group(2) if typed_decl else (untyped_decl.group(1)
-                                                          if untyped_decl else None)
+            var = BidirectIndex._extract_declared_variable(line)

146-155: Consider using verbose regex for better readability.

The complex regex patterns for increment/decrement detection are hard to read and maintain. Consider using the re.VERBOSE flag as suggested in past comments.

+    @staticmethod
+    def _create_increment_pattern(var):
+        """Create verbose regex pattern for increment detection."""
+        return re.compile(rf"""
+            (?:
+                \+\+{re.escape(var)}          |  # Pre-increment: ++var
+                {re.escape(var)}\+\+          |  # Post-increment: var++
+                {re.escape(var)}\s*\+=\s*1\b  |  # Addition assignment: var += 1
+                {re.escape(var)}\s*=\s*{re.escape(var)}\s*\+\s*1\b  # Assignment: var = var + 1
+            )
+        """, re.VERBOSE)
+    
+    @staticmethod
+    def _create_decrement_pattern(var):
+        """Create verbose regex pattern for decrement detection."""
+        return re.compile(rf"""
+            (?:
+                --{re.escape(var)}            |  # Pre-decrement: --var
+                {re.escape(var)}--            |  # Post-decrement: var--
+                {re.escape(var)}\s*-=\s*1\b   |  # Subtraction assignment: var -= 1
+                {re.escape(var)}\s*=\s*{re.escape(var)}\s*-\s*1\b  # Assignment: var = var - 1
+            )
+        """, re.VERBOSE)

143-156: Extract range checking logic for better readability.

The range checking logic for determining if a line is within a for-block could be extracted for clarity.

+    @staticmethod
+    def _is_in_for_block(line_idx, for_blocks):
+        """Check if line index is within any for-block range."""
+        return any(bstart <= line_idx <= bend for bstart, bend in for_blocks)

Then use it in the main method:

-            in_for = any(bstart <= k <= bend for (bstart, bend) in for_blocks)
+            in_for = BidirectIndex._is_in_for_block(k, for_blocks)
test/patterns/bidirect_index/test_bidirect_index.py (1)

11-13: Consider adding error handling to the helper method.

The _lines_from_file method should handle potential file reading errors gracefully to provide better test failure messages.

     def _lines_from_file(self, filename):
-        with open(filename, encoding='utf-8') as f:
-            return f.readlines()
+        try:
+            with open(filename, encoding='utf-8') as f:
+                return f.readlines()
+        except FileNotFoundError:
+            self.fail(f"Test file not found: {filename}")
+        except IOError as e:
+            self.fail(f"Error reading test file {filename}: {e}")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f93a1e and cb2a04c.

📒 Files selected for processing (3)
  • aibolit/ast_framework/ast_node_type.py (3 hunks)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
  • test/patterns/bidirect_index/test_bidirect_index.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • aibolit/ast_framework/ast_node_type.py
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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.
🪛 Ruff (0.11.9)
aibolit/patterns/bidirect_index/bidirect_index.py

61-64: Use a single if statement instead of nested if statements

(SIM102)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: make
  • GitHub Check: e2e-test
  • GitHub Check: latexmk
🔇 Additional comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (1)

88-93: Potential infinite loop if end condition is not met.

The while loop searches for the next declaration of the same variable but doesn't handle the case where j exceeds end. This could cause incorrect behavior.

-                j = i + 1
-                while j <= end:
-                    line_ = lines[j]
-                    if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
-                        break
-                    j += 1
+                j = i + 1
+                while j <= end:
+                    line_ = lines[j]
+                    if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
+                        break
+                    j += 1
+                # If we didn't find another declaration, analyze until end
+                if j > end:
+                    j = end + 1

Likely an incorrect or invalid review comment.

test/patterns/bidirect_index/test_bidirect_index.py (1)

16-21: LGTM! Test method correctly adapted to new interface.

The test method has been properly updated to use the new lines parameter instead of filename, and the expected behavior is preserved.

@KachanovYev KachanovYev requested a review from ivanovmg July 6, 2025 10:31
Copy link
Contributor

@ivanovmg ivanovmg left a comment

Choose a reason for hiding this comment

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

@KachanovYev please look through my comments. I suppose you haven't addressed them yet. Let me know if you have any questions.

Comment on lines 46 to 47
@staticmethod
def find_methods(lines):
Copy link
Contributor

Choose a reason for hiding this comment

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

Please consider removing staticmethod everywhere, or extracting a related functionality into separate functions (https://github.com/cqfn/aibolit/pull/842/files#r2176314046).

Suggested change
@staticmethod
def find_methods(lines):
def _find_methods(self, lines):

Comment on lines 134 to 135
@staticmethod
def count_inc_dec(lines, var, for_blocks, start, end):
Copy link
Contributor

Choose a reason for hiding this comment

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

https://github.com/cqfn/aibolit/pull/842/files#r2176330476

Suggested change
@staticmethod
def count_inc_dec(lines, var, for_blocks, start, end):
def _count_inc_dec(self, lines, var, for_blocks, start, end):

Comment on lines 11 to 30
def value(self, filename: str | os.PathLike):
def value(self, lines):
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that adding a third kind of interface for value is a good idea.

This is what we already have:

def value(self, ast: AST) -> list[LineNumber]: ...  # preferable
def value(self, filename: str | os.PathLike) -> list[LineNumber]: ...  # soon to be deprecated in favor of the preferable option above

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
aibolit/patterns/bidirect_index/bidirect_index.py (4)

41-43: Use re.VERBOSE flag for complex regex patterns to improve readability.

The regex patterns are difficult to understand. Consider using the re.VERBOSE flag with comments:

typed_decl = re.match(
    r'''
    \s*                     # Optional whitespace
    (int|long|byte|short)   # Java primitive types
    \s+                     # Required whitespace
    (\w+)                   # Variable name (captured)
    \s*                     # Optional whitespace
    =                       # Assignment operator
    ''', 
    line, 
    re.VERBOSE
)

127-127: Fix indentation to match the list item structure.

-        - increments/decrements inside a for-loop with a local variable of the same name
-    are ignored (to avoid "fake" cases).
+        - increments/decrements inside a for-loop with a local variable of the same name
+          are ignored (to avoid "fake" cases).

143-157: Consider following the unified Pattern interface using AST.

Based on the PR objectives and past comments, the Pattern interface should accept an AST object rather than source lines. The current implementation using source lines goes against the unified interface direction.

Consider refactoring to:

def value(self, ast: AST) -> list[LineNumber]:
    """Analyze the AST and return line numbers with bidirectional indices."""
    # Implementation using AST traversal

This would align with the codebase's direction towards AST-based analysis rather than text-based parsing.


105-114: Refactor complex regex patterns using re.VERBOSE for better readability.

These regex patterns are extremely difficult to parse. Use re.VERBOSE with comments:

inc_pattern = re.compile(
    rf'''
    (
        \+\+{re.escape(var)}              # Pre-increment: ++var
        |{re.escape(var)}\+\+             # Post-increment: var++
        |{re.escape(var)}\s*\+=\s*1\b     # Compound assignment: var += 1
        |{re.escape(var)}\s*=\s*          # Assignment: var = var + 1
         {re.escape(var)}\s*\+\s*1\b
    )
    ''',
    re.VERBOSE
)
if inc_pattern.search(line_):
    inc_outside += 1
🧹 Nitpick comments (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)

6-31: Consider making this a private class method or keeping functions consistently outside the class.

The function is defined outside the class but used inside. For consistency, either make all helper functions class methods (prefixed with _ for internal use) or keep them all as standalone functions.

-def find_methods(lines):
+class BidirectIndex:
+    # ... existing code ...
+    
+    def _find_methods(self, lines):

Also update the call in line 155:

-        for m_start, m_end in find_methods(lines):
+        for m_start, m_end in self._find_methods(lines):
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 73dbaa5 and 3be30f2.

📒 Files selected for processing (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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.
⏰ 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). (5)
  • GitHub Check: pylint
  • GitHub Check: pdd
  • GitHub Check: make
  • GitHub Check: e2e-test
  • GitHub Check: latexmk

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (6)
aibolit/patterns/bidirect_index/bidirect_index.py (6)

9-34: Convert standalone function to instance method.

Based on past review feedback, standalone functions should be converted to instance methods of the BidirectIndex class for better encapsulation and consistency with the codebase design patterns.

-def find_methods(lines):
+class BidirectIndex:
+    def _find_methods(self, lines):

23-25: Improve regex readability using re.VERBOSE flag.

The complex regex pattern is difficult to read and maintain. Based on past review feedback, use the re.VERBOSE flag to make it more readable with comments and proper formatting.

-        if (re.search(
-                r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
-                line)):
+        method_pattern = re.compile(r'''
+            (public|private|protected|static|\s)*  # Access modifiers
+            ([\w<>\[\]]+)                          # Return type
+            \s+\w+                                 # Method name
+            \s*\([^)]*\)                          # Parameters
+            \s*{                                   # Opening brace
+        ''', re.VERBOSE)
+        if method_pattern.search(line):

37-73: Convert to instance method and improve structure.

This function should be an instance method for consistency with the class design pattern. Also consider breaking down the complex logic into smaller, more focused methods.

-def analyze_block(lines, start, end, result):
+    def _analyze_block(self, lines, start, end, result):

75-95: Convert to instance method.

This function should be an instance method for consistency with the class design pattern.

-def find_for_blocks(lines, var, start, end):
+    def _find_for_blocks(self, lines, var, start, end):

97-120: Convert to instance method and improve regex readability.

This function should be an instance method, and the complex regex patterns should use the re.VERBOSE flag for better readability.

-def count_inc_dec(lines, var, for_blocks, start, end):
+    def _count_inc_dec(self, lines, var, for_blocks, start, end):

For the regex patterns, consider using re.VERBOSE:

-            if re.search(
-                    r'(\+\+' + re.escape(var) + r'|' + re.escape(var) + r'\+\+|' +
-                    re.escape(var) + r'\s*\+=\s*1\b|' +
-                    re.escape(var) + r'\s*=\s*' + re.escape(var) + r'\s*\+\s*1\b)', line_):
+            increment_pattern = re.compile(rf'''
+                (\+\+{re.escape(var)}          |    # Pre-increment
+                 {re.escape(var)}\+\+          |    # Post-increment
+                 {re.escape(var)}\s*\+=\s*1\b  |    # += 1
+                 {re.escape(var)}\s*=\s*{re.escape(var)}\s*\+\s*1\b)  # = var + 1
+            ''', re.VERBOSE)
+            if increment_pattern.search(line_):

139-157: Address interface consistency and add error handling.

Based on past review feedback, the interface should be updated to match the established pattern in the codebase. Also, file operations need proper error handling.

-    def value(self, filename: str | os.PathLike) -> list[LineNumber]:
+    def value(self, lines: list[str]) -> list[LineNumber]:
         """
-        Analyze the given Java file and return a sorted list of line numbers where a variable
+        Analyze the given Java source lines and return a sorted list of line numbers where a variable
         is used as a bidirectional index.

         Args:
-            filename (str | os.PathLike): Path to the Java source file.
+            lines (list[str]): Lines of Java source code to analyze.

         Returns:
             list[LineNumber]: Sorted list of line numbers where bidirectional indices are found.
         """
-
-        with open(filename, encoding='utf-8') as f:
-            lines = f.readlines()
         result: list[int] = []
-        for m_start, m_end in find_methods(lines):
-            analyze_block(lines, m_start + 1, m_end, result)
+        for m_start, m_end in self._find_methods(lines):
+            self._analyze_block(lines, m_start + 1, m_end, result)
         return [LineNumber(n) for n in sorted(result)]
🧹 Nitpick comments (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)

44-48: Simplify variable declaration detection logic.

The current logic for detecting typed and untyped declarations is complex and could be simplified for better readability.

-        typed_decl = re.match(r'\s*(int|long|byte|short)\s+(\w+)\s*=', line)
-        untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line) \
-            if not typed_decl else None
-        var = typed_decl.group(2) if typed_decl else (untyped_decl.group(1)
-                                                      if untyped_decl else None)
+        var = None
+        typed_decl = re.match(r'\s*(int|long|byte|short)\s+(\w+)\s*=', line)
+        if typed_decl:
+            var = typed_decl.group(2)
+        else:
+            untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line)
+            if untyped_decl:
+                var = untyped_decl.group(1)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3be30f2 and 4a02d3c.

📒 Files selected for processing (2)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
  • test/patterns/bidirect_index/test_bidirect_index.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • test/patterns/bidirect_index/test_bidirect_index.py
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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.
⏰ 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: latexmk
  • GitHub Check: make

aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
Update method calls to use instance methods.

The calls to standalone functions should be updated to use instance methods after converting them.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
aibolit/patterns/bidirect_index/bidirect_index.py (4)

16-16: Fix indentation to match the list format.

The continuation line should be properly indented to align with the list items above.

-      - increments/decrements inside a for-loop with a local variable of the same name are ignored.
+      - increments/decrements inside a for-loop with a local variable of the same name
+        are ignored (to avoid "fake" cases).

135-151: Consider refactoring to use AST interface for consistency.

As mentioned in previous reviews, this implementation uses filename as input while the unified Pattern interface expects AST. This goes against the project's direction for interface unification.

Consider creating a puzzle to track the refactoring of this implementation to use ast_framework.AST rather than filename as suggested in the past review comments.


122-131: Use re.VERBOSE flag to improve regex readability.

These complex regex patterns are difficult to read and maintain. Consider using the re.VERBOSE flag with comments as previously suggested.

# Define patterns with re.VERBOSE for better readability
INCREMENT_PATTERN = re.compile(rf'''
    \+\+{re.escape(var)}           |  # prefix increment
    {re.escape(var)}\+\+           |  # postfix increment  
    {re.escape(var)}\s*\+=\s*1\b   |  # compound assignment
    {re.escape(var)}\s*=\s*{re.escape(var)}\s*\+\s*1\b  # assignment
''', re.VERBOSE)

DECREMENT_PATTERN = re.compile(rf'''
    --{re.escape(var)}             |  # prefix decrement
    {re.escape(var)}--             |  # postfix decrement
    {re.escape(var)}\s*-=\s*1\b    |  # compound assignment
    {re.escape(var)}\s*=\s*{re.escape(var)}\s*-\s*1\b  # assignment
''', re.VERBOSE)

# Then use:
if INCREMENT_PATTERN.search(line_):
    inc_outside += 1
if DECREMENT_PATTERN.search(line_):
    dec_outside += 1

146-147: Add error handling for file operations.

The file operations should handle potential errors gracefully as previously suggested.

-        with open(filename, encoding='utf-8') as f:
-            lines = f.readlines()
+        try:
+            with open(filename, encoding='utf-8') as f:
+                lines = f.readlines()
+        except (FileNotFoundError, IOError) as e:
+            raise ValueError(f"Failed to read file {filename}: {e}")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4a02d3c and bbc8ced.

📒 Files selected for processing (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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 (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)
aibolit/ast_framework/ast_node.py (1)
  • line (41-69)
⏰ 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: latexmk
  • GitHub Check: make

aibolit/patterns/bidirect_index/bidirect_index.py Outdated Show resolved Hide resolved
Comment on lines 61 to 64
untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line) \
if not typed_decl else None
var = typed_decl.group(2) if typed_decl else (untyped_decl.group(1)
if untyped_decl else None)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve untyped variable detection to avoid false positives.

The current regex for untyped declarations could match invalid patterns. In Java, variable reassignments must be preceded by a declaration. Consider checking if the variable was previously declared.

-            untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line) \
-                if not typed_decl else None
+            # Only match simple reassignments, not method calls or field access
+            untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^=;]+;', line) \
+                if not typed_decl else None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^;]+;', line) \
if not typed_decl else None
var = typed_decl.group(2) if typed_decl else (untyped_decl.group(1)
if untyped_decl else None)
# Only match simple reassignments, not method calls or field access
untyped_decl = re.match(r'^\s*(\w+)\s*=\s*[^=;]+;', line) \
if not typed_decl else None
var = typed_decl.group(2) if typed_decl else (untyped_decl.group(1)
if untyped_decl else None)
🤖 Prompt for AI Agents
In aibolit/patterns/bidirect_index/bidirect_index.py around lines 61 to 64, the
regex used to detect untyped variable declarations may produce false positives
by matching invalid patterns. To fix this, enhance the logic to verify that the
variable identified by the regex was previously declared before treating it as a
valid untyped declaration. This can be done by maintaining a record of declared
variables and checking against it before assigning the variable from the
untyped_decl match.

Comment on lines 69 to 71
if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
break
j += 1
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check for both typed and untyped redeclarations when finding variable scope.

The current implementation only checks for typed redeclarations, missing cases where the variable is reassigned without a type.

-                    if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
+                    # Check for both typed declarations and simple reassignments
+                    if (re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_) or
+                        re.match(r'^\s*' + re.escape(var) + r'\s*=\s*[^=;]+;', line_)):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
break
j += 1
# Check for both typed declarations and simple reassignments
if (re.match(r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_) or
re.match(r'^\s*' + re.escape(var) + r'\s*=\s*[^=;]+;', line_)):
break
j += 1
🤖 Prompt for AI Agents
In aibolit/patterns/bidirect_index/bidirect_index.py around lines 69 to 71, the
code only checks for typed redeclarations of variables but misses untyped
reassignments. Update the condition to also detect lines where the variable is
reassigned without a type, such as simple assignments, by adding a check for
untyped redeclarations alongside the existing typed check.

@KachanovYev KachanovYev requested a review from ivanovmg July 15, 2025 15:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)

149-150: Add error handling for file operations.

The method should handle potential file reading errors gracefully.

-        with open(filename, encoding='utf-8') as f:
-            lines = f.readlines()
+        try:
+            with open(filename, encoding='utf-8') as f:
+                lines = f.readlines()
+        except (FileNotFoundError, IOError) as e:
+            raise ValueError(f"Failed to read file {filename}: {e}")
🧹 Nitpick comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

40-43: Consider handling methods with opening braces on separate lines.

The current regex assumes the opening brace { is on the same line as the method signature. In Java, it's valid to have:

public void myMethod()
{
    // code
}

Consider updating the logic to handle this case:

-            if (re.search(
-                    r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
-                    line)):
+            # First check for method signature
+            method_sig = re.search(
+                r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*$',
+                line)
+            # Then check if it has brace on same line or next line
+            if method_sig or (re.search(
+                    r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
+                    line)):

125-134: Consider improving regex readability.

The increment/decrement detection regexes are comprehensive but difficult to read.

Consider using the re.VERBOSE flag for better readability:

inc_pattern = re.compile(rf'''
    \+\+{re.escape(var)}       |  # pre-increment
    {re.escape(var)}\+\+       |  # post-increment
    {re.escape(var)}\s*\+=\s*1\b |  # compound assignment
    {re.escape(var)}\s*=\s*{re.escape(var)}\s*\+\s*1\b  # reassignment
''', re.VERBOSE)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bbc8ced and cdce2a0.

📒 Files selected for processing (1)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.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.
🔇 Additional comments (1)
aibolit/patterns/bidirect_index/bidirect_index.py (1)

91-112: LGTM!

The method correctly identifies for-loop blocks with proper handling of all numeric types and brace counting.

@KachanovYev
Copy link
Contributor Author

@ivanovmg
Hi PR is updated by your request.

Copy link
Contributor

@ivanovmg ivanovmg left a comment

Choose a reason for hiding this comment

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

@KachanovYev please have a look at my review.

  1. Could you move value to the top of the class, so that it is the public method, that is visible first to the reader?
  2. Keep only essential changes in the test files, while moving those desirable into another PR.
  3. Comment regexes, because it is extremely difficult to understand them. I suggest using re.VERBOSE flag

While issues 1 and 2 are not that critical, issue 3 is pretty concerning. Could you address it?

Comment on lines 17 to 16
BidirectIndex().value(Path(self.dir_path, 'BidirectIndexIncreaseDecrease.java')),
BidirectIndex().value(file_path),
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest that we leave the changes in the test files outside of the present PR (apparently, except making the test non-skipped). It is OK to do it in another one, dedicated to the refactoring if necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

k += 1
return inc_outside, dec_outside

def value(self, filename: str | os.PathLike) -> list[LineNumber]:
Copy link
Contributor

Choose a reason for hiding this comment

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

It is better to keep the public method on top, while having the remaining helper methods defined below. This way we will read the code like the newspaper - header first, then details, then more details, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resolved

Comment on lines 68 to 70
if (re.search(
r'(public|private|protected|static|\s)*([\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*{',
line)):
Copy link
Contributor

@ivanovmg ivanovmg Jul 21, 2025

Choose a reason for hiding this comment

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

@KachanovYev I strongly encourage you to address this issue. Reading complex regexes in the future is difficult and reduces the level of maintainability.

See #842 (comment)

@KachanovYev
Copy link
Contributor Author

@KachanovYev please have a look at my review.

  1. Could you move value to the top of the class, so that it is the public method, that is visible first to the reader?
  2. Keep only essential changes in the test files, while moving those desirable into another PR.
  3. Comment regexes, because it is extremely difficult to understand them. I suggest using re.VERBOSE flag

While issues 1 and 2 are not that critical, issue 3 is pretty concerning. Could you address it?

resolved

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
aibolit/patterns/bidirect_index/bidirect_index.py (4)

16-16: Fix docstring indentation for consistency.

The list item indentation doesn't match the other items.

-      - increments/decrements inside a for-loop with a local variable of the same name are ignored.
+        - increments/decrements inside a for-loop with a local variable of the same name
+          are ignored (to avoid "fake" cases).

23-39: Consider migrating to AST-based interface.

Per previous discussions, the preferred interface should use ast_framework.AST rather than filename as input parameter to align with the unified Pattern interface direction.

-    def value(self, filename: str | os.PathLike) -> list[LineNumber]:
+    def value(self, ast: AST) -> list[LineNumber]:

Additionally, add error handling for file operations:

-        with open(filename, encoding='utf-8') as f:
-            lines = f.readlines()
+        try:
+            with open(filename, encoding='utf-8') as f:
+                lines = f.readlines()
+        except (FileNotFoundError, IOError) as e:
+            raise ValueError(f"Failed to read file {filename}: {e}")

87-90: Improve untyped variable detection to avoid false positives.

The current regex for untyped declarations could match invalid patterns like method calls. Consider a more restrictive pattern:

-            untyped_decl = re.match(
-                r'^\s*(\w+)\s*=\s*[^=;]+;', line) if not typed_decl else None
+            # Only match simple reassignments, not method calls or field access
+            untyped_decl = re.match(
+                r'^\s*(\w+)\s*=\s*[^=;]+;', line) if not typed_decl else None

95-99: Include untyped reassignments when finding variable scope.

The current implementation only checks for typed redeclarations, missing cases where variables are reassigned without type:

-                    # Check for re-declaration of variable with type
-                    if re.match(
-                            r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_):
+                    # Check for both typed declarations and simple reassignments
+                    if (re.match(
+                            r'\s*(int|long|byte|short)\s+' + re.escape(var) + r'\s*=', line_) or
+                        re.match(r'^\s*' + re.escape(var) + r'\s*=\s*[^=;]+;', line_)):
🧹 Nitpick comments (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

41-74: Well-structured method detection with readable regex.

Good use of re.VERBOSE for regex readability. Minor typo in comment:

-            ([\w<>\[\]]+)                          # # return type
+            ([\w<>\[\]]+)                          # return type

1-175: Consider future refactoring to AST-based implementation.

While this regex-based implementation is functional and covers all test cases, it would benefit from being refactored to use the ast_framework.AST for better maintainability and consistency with other patterns in the codebase. This could be tracked as a technical debt item.

Would you like me to create an issue to track the refactoring from regex-based to AST-based implementation?

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5fb34f9 and ff28b9a.

📒 Files selected for processing (2)
  • aibolit/patterns/bidirect_index/bidirect_index.py (1 hunks)
  • test/patterns/bidirect_index/test_bidirect_index.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • test/patterns/bidirect_index/test_bidirect_index.py
🧰 Additional context used
🧠 Learnings (1)
aibolit/patterns/bidirect_index/bidirect_index.py (3)

Learnt from: SinbadTheSailor2005
PR: #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.

Learnt from: Error10556
PR: #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: #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.

⏰ 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 (2)
aibolit/patterns/bidirect_index/bidirect_index.py (2)

117-142: Well-implemented for-loop detection.

Good use of re.VERBOSE for regex readability and proper handling of all numeric types.


144-174: Comprehensive increment/decrement detection.

Excellent use of re.VERBOSE with clear pattern documentation. The logic correctly excludes counts within for-loops that declare local variables.

@KachanovYev KachanovYev requested a review from ivanovmg July 26, 2025 09:19
@KachanovYev
Copy link
Contributor Author

@yegor256
Hi this is resolved issue.

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.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.