| name | comby |
| description | Structural code search and refactoring using comby, a tool for matching and rewriting code based on syntax structure rather than regex. Use when performing code refactoring, renaming patterns across files, finding/replacing function calls with specific argument patterns, migrating APIs, transforming function signatures, or any task requiring syntax-aware code transformation. Triggers include requests to refactor code, rename functions/variables across codebase, find and replace code patterns, migrate deprecated APIs, update naming conventions, remove debug statements, or modernize code patterns. |
Comby - Structural Code Search and Rewrite
Comby matches and rewrites code using structural templates instead of regex. It understands balanced delimiters, strings, and comments, making it ideal for safe code refactoring.
Installation Check
Verify comby is installed:
which comby || echo "Install: brew install comby (macOS) or bash <(curl -sL get-comby.netlify.app) (Linux)"
Quick Start
Basic Pattern
comby 'oldFunc(:[args])' 'newFunc(:[args])' .go
comby 'oldFunc(:[args])' 'newFunc(:[args])' .go -i
comby 'oldFunc(:[args])' 'newFunc(:[args])' -d src/ .go -i
comby 'oldFunc(:[args])' 'newFunc(:[args])' .go -review
Common Workflow
- Test pattern - Run without
-i to preview
- Review diff - Add
-diff flag for patch output
- Apply carefully - Use
-i to modify files in-place
- Verify - Run tests after refactoring
Core Concepts
Stdin/Stdout Usage
When using -stdin for input, add -stdout for plain rewritten output:
echo 'hello' | comby ':[x]' ':[x].Capitalize' -stdin -stdout
echo 'hello' | comby ':[x]' ':[x].Capitalize' -stdin
Match Holes
Holes capture varying parts of code:
| Syntax | Matches | Example |
|---|
:[hole] | Everything (lazy, balanced) | :[args] in func(a, b) captures a, b |
:[[hole]] | Identifiers only (\w+) | :[[name]] matches myFunc not my-func |
:[hole~\d+] | Regex pattern | Matches only digits |
... | Anonymous (don't care) | func(...) matches any args |
Rewrite with Properties
Transform captured values:
comby ':[[func]]' ':[func].lowerCamelCase' .py -i
comby ':[[var]]' ':[var].UPPER_SNAKE_CASE' .c -i
comby 'log(:[msg])' 'log(":[msg].file.name: " + :[msg])' .js -i
Common properties: .Capitalize, .lowercase, .UPPERCASE, .lowerCamelCase, .UpperCamelCase, .lower_snake_case, .UPPER_SNAKE_CASE, .length, .file
See references/syntax.md for complete syntax reference.
Rules for Constraints
Add conditions with -rule:
comby 'if (:[x] == :[y])' '' -rule 'where :[x] == :[y]' .c -match-only
comby 'log(":[level]", ":[msg]")' 'logger(":[level]", ":[msg]")' \
-rule 'where :[level] == "ERROR"' .py -i
comby 'func(":[arg]")' 'newfunc(":[arg]")' \
-rule 'where match :[arg] { | "error" -> true }' .go -i
See references/rules.md for complete rule system.
Essential Flags
| Flag | Purpose |
|---|
-i | Apply changes in-place |
-d dir | Target directory |
-f .ext | Filter by extension |
-match-only | Show matches without rewriting |
-diff | Generate unified diff |
-review | Interactive approval |
-rule 'where ...' | Add constraints |
-matcher .lang | Force language parser |
-stdin / -stdout | Pipe input/output |
-json-lines | JSON output for scripting |
Common Patterns
Rename Function/Method
comby 'oldFunc(:[args])' 'newFunc(:[args])' .go -i
comby ':[obj].oldMethod(:[args])' ':[obj].newMethod(:[args])' .js -i
Update Function Signature
comby 'fetch(:[url])' 'fetch(:[url], { timeout: 5000 })' .js -i
comby 'func(:[a], :[b])' 'func(:[b], :[a])' .py -i
API Migration
comby 'oldAPI.call(:[args])' 'newAPI.execute(:[args])' .js -i
comby ':[obj].old(:[x]).chain(:[y])' ':[obj].new(:[x], :[y])' .rs -i
Remove Debug Code
comby 'console.log(:[args])' '' .js -i
comby 'dbg!(:[expr])' ':[expr]' .rs -i
comby 'print(:[args])' '' .py -i
Update Naming Conventions
comby ':[[var]]' ':[var].lowerCamelCase' .js -i
comby ':[[var]]' ':[var].lower_snake_case' .py -i
Field/Property Rename
comby ':[obj].oldField' ':[obj].newField' .ts -i
comby ':[var]["old_key"]' ':[var]["new_key"]' .py -i
See references/examples.md for comprehensive refactoring examples.
Configuration Files
Create comby.toml for multiple patterns:
[rename-function]
match = "oldFunc(:[args])"
rewrite = "newFunc(:[args])"
[remove-debug]
match = "console.log(:[args])"
rewrite = ""
[update-import]
match = "from old_module import :[name]"
rewrite = "from new_module import :[name]"
rule = "where :[name] != 'excluded'"
Run: comby -config comby.toml -f .js -d directory/ -i
Language Support
Auto-detects by extension:
- C-family: C, C++, C#, Java, JavaScript, TypeScript, Go, Rust
- Dynamic: Python, Ruby, PHP, Perl
- Functional: OCaml, Haskell, Elixir, Erlang
- Markup: HTML, XML, JSON, YAML
- Other: SQL, Bash, LaTeX
Force language: -matcher .rs (use correct names from comby -list) or use -matcher .generic for unsupported languages.
Best Practices
- Always test first: Run without
-i to preview changes
- Use version control: Commit before large refactorings
- Start narrow: Test on one file, then expand to directory
- Review diffs: Use
-diff or -review for verification
- Combine with git:
git grep -l 'pattern' | xargs comby ... -i
- Escape properly: Use
\\d for regex digit class in shell
- Run tests: Validate after each refactoring step
Structural Matching Details
Comby understands:
- Balanced delimiters:
(), [], {}, <> (language-aware)
- String literals: Respects quotes and escape sequences
- Comments: Skips language-specific comment syntax
Example: func(:[args]) correctly matches func(foo(x), bar(y)) capturing nested calls.
Playground
Test patterns interactively: https://comby.live
Resources
Troubleshooting
Pattern doesn't match?
- Check whitespace (usually flexible but test variations)
- Try
:[[hole]] for identifiers instead of :[hole]
- Use
-matcher .generic if language detection fails
- Test at https://comby.live
Too many matches?
- Make holes more specific: use
:[[id]] instead of :[any]
- Add
-rule constraints
- Use regex holes:
:[num~\d+]
Changes not applying?
- Verify
-i flag is present
- Check file permissions
- Use
-diff to see what would change