-
Notifications
You must be signed in to change notification settings - Fork 115
Support Ruby procedural modifications #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 35 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
3a41a8c
Add 20 Ruby procedural bug modifiers with property analysis
cosgroveb 447d254
Add 22 Ruby repo profiles with RSpec and Minitest log parsers
cosgroveb 55dc8b3
Add tests for Ruby modifiers, property analysis, and profiles
cosgroveb 3ebac51
Fix operator precedence in test-unit parser and nested node removal
cosgroveb 676a6eb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 7a1fd92
Add 26 tests covering flip-failure, no-candidate, and edge-case paths
cosgroveb 5a09425
Fix malformed JSON test to actually hit JSONDecodeError path
cosgroveb 26811ce
Assert all property tags for parse_query and _normalize_params
cosgroveb 49b465d
Assert both directions of if/else swap in invert test
cosgroveb f805798
Pad no-else test method to meet min_complexity threshold
cosgroveb fad3a37
Add unless/else invert test for ControlIfElseInvertModifier
cosgroveb 9713734
Add test for OrDefaultRemovalModifier with Ruby `or` keyword
cosgroveb 458d0f1
Remove find_sole_by! from BANG_METHODS — no bang version exists
cosgroveb 2e7b9ba
Add parametrized guard removal tests for unless and raise keywords
cosgroveb 744f43d
Add missing self.flip() to ControlShuffleLinesModifier
cosgroveb 80b32f4
Add shuffle test for singleton_method (def self.foo) nodes
cosgroveb 48eceef
Simplify safe navigation check to just child.type
cosgroveb 255ab80
Add ** to ARITHMETIC_OPS, remove redundant ALL_BINARY_OPS entry
cosgroveb c8cc206
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 4e12735
Add OperationChangeModifier tests for comparison, logical, and keywor…
cosgroveb ea0c9c7
Add regexp operators =~ and !~ to FLIPPED_OPERATORS and REGEX_OPS
cosgroveb 9d70cb7
Add range operator flipping between inclusive .. and exclusive ...
cosgroveb 467eb5c
Update ChangeConstants docstring to reflect float handling
cosgroveb 8e73e2c
Unroll parametrized remove test into standalone functions
cosgroveb e4050e1
Add remove tests for until and for loop types
cosgroveb ec28bf3
Add remove test for unless conditional
cosgroveb ee6b43e
Add ensure block to rescue removal test
cosgroveb 0b872ae
Extract regex pattern to _RUBY_IDENTIFIER_PATTERN constant
cosgroveb 6e4ab58
Filter then keyword from then_stmts to fix if/then/else inversion
cosgroveb 8f15d2a
Replace find! with take! in BANG_METHODS — find already raises
cosgroveb fc85b17
Fix nested binary corruption in OperationSwapOperandsModifier
cosgroveb a525ee9
Skip keyword leaf nodes in _remove_matching_nodes
cosgroveb dc69a94
Add regression test for rescue keyword-only deletion
cosgroveb 8598c92
Exclude conditional contexts from OrDefaultRemovalModifier
cosgroveb 7805146
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 6e6dc9a
Check immediate parent only in OrDefaultRemovalModifier conditional f…
cosgroveb d8d1bbf
Remove phantom error status from RSpec JSON parser
cosgroveb 642ba09
Map E status to TestStatus.ERROR in Minitest and test-unit parsers
cosgroveb dd91f0c
Move SWE-bench_Multilingual comment above all multilingual repos
cosgroveb 07292f2
Walk condition field to detect || inside conditionals
cosgroveb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| from swesmith.bug_gen.procedural.base import ProceduralModifier | ||
| from swesmith.bug_gen.procedural.ruby.control_flow import ( | ||
| ControlIfElseInvertModifier, | ||
| ControlShuffleLinesModifier, | ||
| GuardClauseInvertModifier, | ||
| ) | ||
| from swesmith.bug_gen.procedural.ruby.nil_introduction import ( | ||
| BangMethodStripModifier, | ||
| NilGuardRemovalModifier, | ||
| OrDefaultRemovalModifier, | ||
| OrEqualsRemovalModifier, | ||
| PresenceStripModifier, | ||
| SafeNavigationRemovalModifier, | ||
| ) | ||
| from swesmith.bug_gen.procedural.ruby.operations import ( | ||
| OperationBreakChainsModifier, | ||
| OperationChangeConstantsModifier, | ||
| OperationChangeModifier, | ||
| OperationFlipOperatorModifier, | ||
| OperationSwapOperandsModifier, | ||
| ) | ||
| from swesmith.bug_gen.procedural.ruby.remove import ( | ||
| RemoveAssignModifier, | ||
| RemoveConditionalModifier, | ||
| RemoveLoopModifier, | ||
| RemoveRescueEnsureModifier, | ||
| ) | ||
| from swesmith.bug_gen.procedural.ruby.ruby_specific import ( | ||
| BlockMutationModifier, | ||
| SymbolStringSwapModifier, | ||
| ) | ||
|
|
||
| MODIFIERS_RUBY: list[ProceduralModifier] = [ | ||
| # Standard modifiers (CommonPMs) | ||
| ControlIfElseInvertModifier(likelihood=0.75), | ||
| ControlShuffleLinesModifier(likelihood=0.75), | ||
| OperationChangeModifier(likelihood=0.4), | ||
| OperationFlipOperatorModifier(likelihood=0.4), | ||
| OperationSwapOperandsModifier(likelihood=0.4), | ||
| OperationBreakChainsModifier(likelihood=0.3), | ||
| OperationChangeConstantsModifier(likelihood=0.4), | ||
| RemoveAssignModifier(likelihood=0.25), | ||
| RemoveConditionalModifier(likelihood=0.25), | ||
| RemoveLoopModifier(likelihood=0.25), | ||
| # Ruby-specific | ||
| GuardClauseInvertModifier(likelihood=0.6), | ||
| RemoveRescueEnsureModifier(likelihood=0.4), | ||
| BlockMutationModifier(likelihood=0.4), | ||
| SymbolStringSwapModifier(likelihood=0.5), | ||
| # Nil introduction | ||
| SafeNavigationRemovalModifier(likelihood=0.5), | ||
| OrDefaultRemovalModifier(likelihood=0.4), | ||
| PresenceStripModifier(likelihood=0.5), | ||
| BangMethodStripModifier(likelihood=0.4), | ||
| OrEqualsRemovalModifier(likelihood=0.4), | ||
| NilGuardRemovalModifier(likelihood=0.5), | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| from abc import ABC | ||
|
|
||
| import tree_sitter_ruby as tsruby | ||
| from tree_sitter import Language, Parser | ||
|
|
||
| from swesmith.bug_gen.procedural.base import ProceduralModifier | ||
| from swesmith.constants import BugRewrite, CodeEntity | ||
|
|
||
| RUBY_LANGUAGE = Language(tsruby.language()) | ||
|
|
||
|
|
||
| class RubyProceduralModifier(ProceduralModifier, ABC): | ||
| """Base class for Ruby-specific procedural modifications.""" | ||
|
|
||
| @staticmethod | ||
| def validate_syntax(original: str, modified: str) -> bool | None: | ||
| """Return True if valid, False if errors, None if unchanged.""" | ||
| if original == modified: | ||
| return None | ||
| parser = Parser(RUBY_LANGUAGE) | ||
| tree = parser.parse(bytes(modified, "utf8")) | ||
|
|
||
| def has_errors(node): | ||
| if node.type in ("ERROR", "MISSING"): | ||
| return True | ||
| return any(has_errors(c) for c in node.children) | ||
|
|
||
| return not has_errors(tree.root_node) | ||
|
|
||
| @staticmethod | ||
| def find_nodes(node, *types) -> list: | ||
| """Recursively find all AST nodes matching any of the given types. | ||
|
|
||
| Note: tree-sitter Ruby reuses the type name for both compound | ||
| statement nodes and their keyword tokens (e.g. ``while`` appears | ||
| as both the loop node and its keyword child). Callers searching | ||
| for compound statements like ``while``, ``if``, ``rescue`` etc. | ||
| should filter out leaf nodes (``n.children == 0``) to avoid | ||
| matching bare keywords. | ||
| """ | ||
| results = [] | ||
|
|
||
| def walk(n): | ||
| if n.type in types: | ||
| results.append(n) | ||
| for child in n.children: | ||
| walk(child) | ||
|
|
||
| walk(node) | ||
| return results | ||
|
|
||
| @staticmethod | ||
| def replace_node(code: str, node, replacement: str) -> str: | ||
| """Replace a tree-sitter node's text via byte offsets.""" | ||
| code_bytes = code.encode("utf8") | ||
| new_bytes = ( | ||
| code_bytes[: node.start_byte] | ||
| + replacement.encode("utf8") | ||
| + code_bytes[node.end_byte :] | ||
| ) | ||
| return new_bytes.decode("utf8") | ||
|
|
||
| def _remove_matching_nodes( | ||
| self, code_entity: CodeEntity, *node_types: str, validate: bool = False | ||
| ) -> BugRewrite | None: | ||
| """Remove AST nodes matching the given types from the source code.""" | ||
| if not self.flip(): | ||
| return None | ||
|
|
||
| parser = Parser(RUBY_LANGUAGE) | ||
| tree = parser.parse(bytes(code_entity.src_code, "utf8")) | ||
|
|
||
| removals = [] | ||
|
|
||
| def collect(n): | ||
| # Keyword tokens (e.g. `while` inside while_modifier) are leaf | ||
| # nodes in tree-sitter; compound statements always have children. | ||
| if n.type in node_types and n.children and self.flip(): | ||
| removals.append(n) | ||
| return # skip children to avoid stale byte offsets on nested removals | ||
| for child in n.children: | ||
| collect(child) | ||
|
|
||
| collect(tree.root_node) | ||
|
|
||
| if not removals: | ||
| return None | ||
|
|
||
| source_bytes = code_entity.src_code.encode("utf8") | ||
| for node in sorted(removals, key=lambda x: x.start_byte, reverse=True): | ||
| source_bytes = ( | ||
| source_bytes[: node.start_byte] + source_bytes[node.end_byte :] | ||
| ) | ||
|
|
||
| modified_code = source_bytes.decode("utf8") | ||
|
|
||
| if validate: | ||
| valid = self.validate_syntax(code_entity.src_code, modified_code) | ||
| if not valid: | ||
| return None | ||
| elif modified_code == code_entity.src_code: | ||
| return None | ||
|
|
||
| return BugRewrite( | ||
| rewrite=modified_code, | ||
| explanation=self.explanation, | ||
| strategy=self.name, | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.