ast-grep Guide for Home Repository
Overview
ast-grep is a fast, structural code search and rewrite tool that operates on Abstract Syntax Trees (AST). Unlike text-based tools like grep or ripgrep, ast-grep understands the syntax of programming languages, making it ideal for pattern-based code analysis and refactoring.
This guide documents the integration and usage of ast-grep in the home repository.
Why ast-grep?
Problems It Solves
-
Polyglot Codebase: Our repository contains Nix, Bash, Go, Python, YAML, and more. ast-grep works across all these languages with a consistent interface.
-
Structural vs. Text Search: Finding
password = "..."in Nix configuration is different from findingPASSWORD="..."in Bash. ast-grep understands these structural differences. -
Safe Refactoring: When refactoring code patterns, text-based replacement can match unintended locations. ast-grep only matches syntactically valid structures.
-
Custom Linting: We have repository-specific patterns and conventions that generic linters don’t check for.
Advantages
- Fast: Scans 400+ files in ~0.02 seconds
- Accurate: AST-based matching eliminates false positives
- Interactive: Review and apply changes one by one
- Flexible: From simple CLI searches to complex YAML rules
- Polyglot: One tool for multiple languages
Installation
ast-grep is already available in the repository via Nix:
# Already in your shell environment
ast-grep --version # 0.40.5
# Or use via nix-shell
nix-shell -p ast-grep
Quick Start
Basic Search
Find all mkHost calls in flake.nix:
ast-grep -p 'mkHost' -l nix flake.nix
Output:
flake.nix:72: kyushu = libx.mkHost {
flake.nix:78: aomi = libx.mkHost {
flake.nix:81: sakhalin = libx.mkHost {
...
Search with Metavariables
Find all bash functions:
ast-grep -p '$NAME() { $$$ }' -l bash tools/
Metavariables:
$VAR- matches single AST node$$$ARGS- matches zero or more nodes
Interactive Rewrite
Replace a pattern across files:
ast-grep -p 'oldPattern' --rewrite 'newPattern' --interactive
Configuration
The repository includes pre-configured rules:
~/src/home/
├── sgconfig.yml # Main configuration (repo root)
└── .ast-grep/
└── rules/ # Custom rules
├── nix-prefer-inherit.yml
├── nix-explicit-option-types.yml
├── bash-require-strict-mode.yml
├── bash-unsafe-rm-rf.yml
├── security-unsafe-curl-pipe-sh.yml
└── ...
Note: sgconfig.yml must be in the repository root for ast-grep scan to auto-detect it.
Running Linter
Important: Run from repository root (~/src/home)
Scan the entire repository:
cd ~/src/home
ast-grep scan
Scan specific directory:
cd ~/src/home
ast-grep scan systems/
Get JSON output:
cd ~/src/home
ast-grep scan --json
Or specify config path explicitly:
ast-grep scan --config /home/vincent/src/home/.ast-grep/sgconfig.yml
Current Rules
Nix Rules
nix-prefer-inherit (info)
Problem: Redundant attribute assignment when name matches variable.
Bad:
{ pkgs = pkgs; lib = lib; }
Good:
{ inherit pkgs lib; }
nix-explicit-option-types (warning)
Problem: mkOption without explicit type annotation.
Bad:
myOption = mkOption {
default = "value";
description = "My option";
};
Good:
myOption = mkOption {
type = types.str;
default = "value";
description = "My option";
};
Current findings: 137 instances across custom modules (see Scan Results)
nix-prefer-optional (info)
Problem: Verbose conditional list construction.
Bad:
if condition then [ item ] else []
Good:
lib.optional condition item
nix-boolean-comparison (warning)
Problem: Comparing booleans to true/false directly.
Bad:
if myBool == true then ...
Good:
if myBool then ...
Bash Rules
bash-require-strict-mode (error)
Problem: Bash scripts without error handling.
Every #!/usr/bin/env bash script should start with:
#!/usr/bin/env bash
set -euo pipefail
Where:
set -e: Exit on any command failureset -u: Exit on undefined variable usageset -o pipefail: Exit if any command in a pipeline fails
Current findings: 10 scripts missing strict mode
Files affected:
install.shkeyboards/eyelash_corne/go.shimperative/nagoya/apply.sh- And 7 more…
bash-unsafe-rm-rf (error)
Problem: Dangerous rm -rf usage without variable checks.
Bad:
rm -rf $SOME_DIR
Good:
[[ -n "$SOME_DIR" ]] && rm -rf "$SOME_DIR"
Or better:
if [[ -n "$SOME_DIR" && -d "$SOME_DIR" ]]; then
rm -rf "$SOME_DIR"
fi
Current findings: 5 instances needing review
bash-use-command-over-which (warning)
Problem: Using non-POSIX which command.
Bad:
which git
Good:
command -v git
bash-prefer-dollar-parens (info)
Problem: Using backticks for command substitution.
Bad:
OUTPUT=`date`
Good:
OUTPUT=$(date)
Security Rules
security-unsafe-curl-pipe-sh (error)
Problem: Executing downloaded scripts without review.
Dangerous patterns:
curl https://example.com/install.sh | sh
wget -O- https://example.com/install.sh | bash
This is unsafe because:
- No integrity verification (no checksum)
- No review of what’s being executed
- Vulnerable to MITM attacks
- Can’t debug if something goes wrong
Better approach:
# Download
curl -o install.sh https://example.com/install.sh
# Verify checksum
echo "expected_sha256 install.sh" | sha256sum -c
# Review
less install.sh
# Execute
bash install.sh
Current findings: 1 instance in test/example code
Scan Results
Full repository scan (as of 2026-02-09):
Files scanned: 417 (*.nix, *.sh, *.go, *.py, *.yaml, *.json)
Scan time: 0.022 seconds
Issues found: 154
Breakdown by severity:
- Errors: 16
- Warnings: 137
- Info: 1
Breakdown by rule:
- nix-explicit-option-types (warning): 137
- bash-require-strict-mode (error): 10
- bash-unsafe-rm-rf (error): 5
- security-unsafe-curl-pipe-sh (error): 1
- nix-prefer-optional (info): 1
Performance
| Operation | Time | Files/sec |
|---|---|---|
| Full scan (417 files) | 0.022s | ~19,000 |
| Nix files only (~350) | 0.018s | ~19,400 |
| Bash files only (~50) | 0.008s | ~6,250 |
Comparison with other tools:
- ripgrep (text search): ~0.005s (faster but less accurate)
- semgrep (semantic analysis): ~15s (slower but more powerful)
- ast-grep: Sweet spot between speed and accuracy
Real-World Examples
Example 1: Finding Module Options Without Types
Goal: Find all mkOption calls in custom modules that don’t specify a type.
Command:
ast-grep -p 'mkOption { $$$OPTS }' -l nix modules/ | grep -v "type ="
Results: Found 137 instances across modules:
modules/jellyfin-auto-collections/: 13 optionsmodules/nixpkgs-consolidate/: 8 optionsmodules/govanityurl/: 4 optionsmodules/microshift/: 4 options- And more…
Action: These should be fixed by adding explicit types for better documentation and type checking.
Example 2: Bash Script Safety Audit
Goal: Identify bash scripts missing error handling.
Command:
ast-grep scan --json | jq -r '.[] | select(.ruleId == "bash-require-strict-mode") | .file'
Results:
install.sh
keyboards/eyelash_corne/go.sh
imperative/nagoya/apply.sh
imperative/wakasu/apply.sh
tools/tmp/create-vm.sh
tools/tmp/install.sh
...
Fix: Add set -euo pipefail after shebang in each file.
Impact: Prevents silent failures and undefined variable bugs.
Example 3: Finding Unsafe rm -rf Usage
Goal: Audit potentially dangerous rm -rf commands.
Command:
ast-grep scan --json | jq -r '.[] | select(.ruleId == "bash-unsafe-rm-rf") | {file, line: .range.start.line, code: .text}'
Results: 5 instances found, example:
# tools/nix-flake-update/nix-flake-update.sh:103
rm -rf "$WORKTREE_DIR" || true
Analysis: In this case, it’s actually safe because:
- Variable is defined at script start with timestamp
- Cleanup is in trap handler
- Has
|| trueto prevent trap failure
Learning: Not all matches are bugs - ast-grep helps you find patterns, you still need to review them.
Example 4: Searching Across Languages
Goal: Find all password-related configurations across Nix and Bash.
Nix search:
ast-grep -p 'passwordFile = $PATH' -l nix systems/
Bash search:
ast-grep -p 'PASSWORD=$VALUE' -l bash tools/
Benefit: Single tool, consistent interface, structural matching.
Advanced Usage
Interactive Refactoring
Suppose we want to standardize error logging in bash scripts:
ast-grep -p 'echo "ERROR: $MSG" >&2' \
--rewrite 'log "ERROR: $MSG"' \
--interactive \
-l bash tools/
This will:
- Find all matching patterns
- Show you each match with context
- Ask if you want to apply the replacement
- Keep track of what you’ve changed
Writing Custom Rules
Create a new rule in .ast-grep/rules/my-rule.yml:
id: my-custom-rule
message: Your custom message here
severity: warning # error, warning, info, hint
language: Nix
rule:
pattern: $PATTERN_HERE
fix: $FIXED_PATTERN # optional
note: |
Detailed explanation of why this rule exists
and how to fix violations.
Test your rule:
ast-grep scan --rule .ast-grep/rules/my-rule.yml
JSON Output for Automation
Get structured data for processing:
ast-grep scan --json | jq -r '.[] |
select(.severity == "error") |
{file, line: .range.start.line, rule: .ruleId}'
Use in CI/CD:
# Fail build if errors found
if ast-grep scan --json | jq -e '.[] | select(.severity == "error")' >/dev/null; then
echo "ast-grep found errors!"
exit 1
fi
Integration with Workflow
Pre-commit Hook
Add to .git/hooks/pre-commit:
#!/usr/bin/env bash
echo "Running ast-grep linting..."
if ! ast-grep scan --error-only; then
echo "ast-grep found errors. Commit aborted."
echo "Run 'ast-grep scan' to see details."
exit 1
fi
Makefile Integration
Already integrated via make lint:
.PHONY: lint-ast-grep
lint-ast-grep:
@echo "Running ast-grep linting..."
@ast-grep scan
Editor Integration
VSCode: Install ast-grep.ast-grep-vscode extension
- Real-time diagnostics
- Quick fixes
- Hover documentation
Emacs: Use LSP mode with ast-grep server
ast-grep lsp
When to Use ast-grep
Good Use Cases
✅ Structural code search: Finding function calls, specific syntax patterns ✅ Cross-language patterns: Same tool for Nix, Bash, Go, Python ✅ Quick refactoring: Renaming, updating patterns across files ✅ Custom linting: Repository-specific conventions ✅ Code audits: Finding security issues, deprecated patterns ✅ Learning codebase: Discovering usage patterns
When NOT to Use
❌ Deep semantic analysis: Use language-specific tools (e.g., hnix for Nix, gopls for Go)
❌ Type checking: Use proper type checkers
❌ Complex data flow: Use static analyzers like semgrep
❌ Simple text search: Use ripgrep for speed
Complementary Tools
Use ast-grep alongside:
- statix: Nix-specific linter (semantic analysis)
- deadnix: Unused Nix code detection
- shellcheck: Bash linting (deeper analysis)
- golangci-lint: Go linting suite
- semgrep: Advanced security scanning
ast-grep fills the gap between simple text search and full semantic analysis.
Common Patterns
Find All Functions
Bash:
ast-grep -p '$NAME() { $$$ }' -l bash
Go:
ast-grep -p 'func $NAME($$$ARGS) { $$$ }' -l go
Python:
ast-grep -p 'def $NAME($$$ARGS): $$$' -l python
Find Conditional Patterns
Nix attribute sets with specific keys:
ast-grep -p '{ enable = $VAL; $$$ }' -l nix
Bash error handling:
ast-grep -p 'if [[ $? -ne 0 ]]; then $$$ fi' -l bash
Extract Structured Data
Get all module option descriptions:
ast-grep -p 'description = "$DESC"' -l nix modules/ --json | \
jq -r '.[].text' | \
sort -u
Roadmap
Completed ✅
- Install ast-grep in environment
- Create sgconfig.yml configuration
- Write Nix linting rules
- Write Bash safety rules
- Write security rules
- Document usage and examples
- Performance benchmarking
- Real-world examples from repository
Planned 🔄
- Add pre-commit hook integration
- Create more Nix best-practice rules
- Add Python linting rules
- Add Go error-handling rules
- Integrate with CI/CD pipeline
- Create Makefile targets for common tasks
- LSP integration for real-time feedback
- Shared rule repository for NixOS community
Future 🔮
- Tekton-specific rules (for work projects)
- Custom rules for home-manager patterns
- Auto-fix automation for safe rules
- Monthly scan reports
- Rule effectiveness tracking
Resources
- Official Documentation: https://ast-grep.github.io/
- Pattern Playground: https://ast-grep.github.io/playground.html
- Rule Catalog: https://ast-grep.github.io/catalog/
- GitHub: https://github.com/ast-grep/ast-grep
- Discord: https://discord.com/invite/4YZjf6htSQ
Contributing
Adding New Rules
- Identify a pattern that should be checked
- Create rule file:
.ast-grep/rules/category-rule-name.yml - Test:
ast-grep scan --rule .ast-grep/rules/category-rule-name.yml - Document in this guide
- Commit with message:
feat(ast-grep): add rule for [pattern]
Rule Template
id: category-descriptive-name
message: Clear, actionable message
severity: error # or warning, info, hint
language: Nix # or Bash, Go, Python, etc.
note: |
Detailed explanation:
- Why this rule exists
- How to fix violations
- Links to documentation
rule:
pattern: $PATTERN_HERE
fix: $FIXED_PATTERN # optional, only if mechanical fix exists
FAQ
Q: Is ast-grep production-ready? A: Yes, version 0.40+ is stable. Used by many projects including Stripe, Uber, and others.
Q: How does it compare to semgrep? A: ast-grep is faster and simpler, semgrep has more advanced features. Use ast-grep for structural patterns, semgrep for deep security analysis.
Q: Can I use it in CI/CD? A: Yes! It’s designed for this. Fast enough to run on every commit.
Q: What about false positives? A: AST-based matching has very few false positives compared to regex. Review findings, adjust rules as needed.
Q: Can I disable rules?
A: Yes, use severity: off in the rule file, or use --rule flag to run specific rules.
Q: How do I debug patterns? A: Use the playground to test patterns interactively.
Last updated: 2026-02-09
ast-grep version: 0.40.5
Repository: ~/src/home