A friendly tree-sitter wrapper for JavaScript and TypeScript that eliminates boilerplate and makes AST parsing a breeze. Write patterns using natural terms like function
, class
, and string
instead of tree-sitter's verbose node types.
import { parse } from 'tree-hugger-js';
// Parse a file
const tree = parse('app.js');
// Or parse code directly
const tree = parse(`
function hello() {
console.log('Hello, World!');
}
`);
// Find all functions (any type - declaration, expression, arrow)
const functions = tree.findAll('function');
// Find async functions with our intuitive syntax
const asyncFuncs = tree.findAll('function[async]');
// Find classes and their methods
const firstClass = tree.find('class');
const methods = firstClass?.findAll('method');
- 🚀 Zero Configuration - Auto-detects JS, TS, JSX, and TSX
- 🎯 Intuitive Patterns - Use
function
instead offunction_declaration
,class
instead ofclass_declaration
- 🔍 Smart Queries - Pattern matching with CSS-like selectors and helpful error messages
- 🏷️ Built-in Queries - Common patterns like
functions()
,classes()
,imports()
- 🧭 Easy Navigation - Parent, sibling, and ancestor traversal
- ✨ Context-Aware Transforms - Operations understand context (e.g., removing a variable removes the declaration)
- 💪 TypeScript First - Full type safety and IntelliSense support
- 📦 Lightweight - Only includes JavaScript/TypeScript parsers
npm install tree-hugger-js
Tree Hugger JS is specifically designed for the JavaScript/TypeScript ecosystem:
- ✅ JavaScript (.js, .mjs, .cjs)
- ✅ TypeScript (.ts)
- ✅ JSX (.jsx)
- ✅ TSX (.tsx)
For other languages, consider using tree-sitter directly or look for language-specific wrappers.
const todos = tree.comments()
.filter(c => c.text.includes('TODO'))
.map(c => ({ text: c.text, line: c.line }));
const imports = tree.imports().map(imp => imp.name);
const asyncFuncs = tree.functions()
.filter(fn => fn.text.includes('async'));
const method = tree.find('method');
const parentClass = method?.getParent('class');
Tree Hugger JS supports intuitive patterns with CSS-like selectors:
Instead of tree-sitter's verbose names, use natural terms:
function
- matches all function types (declaration, expression, arrow, method)class
- matches class declarations and expressionsstring
- matches string and template literalsloop
- matches for, while, do-while loopsimport
/export
- matches import/export statementsjsx
- matches JSX elements and fragmentscall
- matches function calls- And many more!
- Type selectors:
function
,class
,string
- Attribute selectors:
[name="foo"]
,[async]
,[text*="test"]
- Descendant selectors:
class method
,function call
- Child selectors:
function > return
- Pseudo-selectors:
:has()
,:not()
parse(filenameOrCode, options?)
- Parse a file or code stringfind(pattern)
- Find first matching nodefindAll(pattern)
- Find all matching nodesvisit(visitor)
- Visit nodes with enter/exit callbacksnodeAt(line, col)
- Find node at positionanalyzeScopes()
- Analyze variable scopes
functions()
- All function declarations/expressionsclasses()
- All class declarationsimports()
- All import statementsexports()
- All export statementsvariables()
- All variable declarationscomments()
- All commentsjsxComponents()
- All JSX elementsjsxProps(name?)
- JSX attributes/propshooks()
- React hooks usage
getParent(type?)
- Get parent nodesiblings()
- Get sibling nodesancestors()
- Get all ancestorsdescendants(type?)
- Get all descendants
transform()
- Start a transformation chainrename(oldName, newName)
- Intelligently rename identifiers (skips strings, comments)renameIdentifier(old, new)
- Simple identifier replacementreplaceIn(nodeType, pattern, replacement)
- Replace in specific nodesremove(pattern)
- Remove nodes (understands context, e.g.,remove('console.log')
works!)removeUnusedImports()
- Clean up importsinsertBefore(pattern, text)
- Insert before nodesinsertAfter(pattern, text)
- Insert after nodes (smart context awareness)
tree.visit({
enter(node) {
console.log('Entering:', node.type);
},
exit(node) {
console.log('Exiting:', node.type);
}
});
const scopes = tree.analyzeScopes();
const binding = scopes.findBinding(node, 'variableName');
// Find async functions (works with any function type)
tree.findAll('function[async]');
// Find JSX elements with className prop
tree.findAll('jsx:has(jsx-attribute[name="className"])');
// Find async methods in classes
tree.findAll('class method[async]');
// Find functions that call console.log
tree.findAll('function:has(call[text*="console.log"])');
// Find all loops
tree.findAll('loop');
// Find all string literals containing "TODO"
tree.findAll('string[text*="TODO"]');
const transformed = tree.transform()
.rename('oldFunction', 'newFunction')
.rename('oldVar', 'newVar')
.toString();
const cleaned = tree.transform()
.removeUnusedImports()
.toString();
const refactored = tree.transform()
.rename('getData', 'fetchData')
.replaceIn('string', /localhost/g, 'api.example.com')
.remove('console.log') // Removes all console.log calls
.removeUnusedImports()
.toString();
// Insert after a const declaration (not just the keyword!)
const withLogs = tree.transform()
.insertAfter('const', ' console.log(x);')
.toString();
// Result: const x = 42; console.log(x);
// Remove a variable (removes the whole declaration if it's the only one)
const cleaned = tree.transform()
.remove('variable[name="tempVar"]')
.toString();
Tree-sitter is powerful but requires learning its specific node types and APIs. Tree Hugger JS bridges this gap by:
- Intuitive Patterns: Write
function
instead of memorizingfunction_declaration
vsfunction_expression
- Smart Operations: Transformations understand context and do what you expect
- Better Errors: Get helpful suggestions when patterns don't match
- Type Safety: Full TypeScript support with great IntelliSense
- Focused Scope: Optimized specifically for JavaScript/TypeScript workflows
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build the project
npm run build
# Watch mode for development
npm run dev
This project uses GitHub Actions for continuous integration:
- CI Pipeline: Runs tests on Node.js 18.x, 20.x, and 22.x for every PR and push
- Cross-Platform Tests: Ensures compatibility across Ubuntu, Windows, and macOS
- Automated Publishing: Publishes to NPM when releases are created
- Code Coverage: Uploads coverage reports to Codecov
All PRs must pass the full test suite before merging.
MIT