tree-hugger-js
Version:
A friendly tree-sitter wrapper for JavaScript and TypeScript
134 lines (109 loc) • 3.95 kB
text/typescript
import { parse } from '../src';
const reactCode = `
import React, { useState, useEffect, useCallback } from 'react';
import { Button } from './components/Button';
import axios from 'axios';
export const UserProfile = ({ userId, onUpdate }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser();
}, [userId]);
const fetchUser = useCallback(async () => {
try {
const response = await axios.get(\`/api/users/\${userId}\`);
setUser(response.data);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
<Button onClick={() => onUpdate(user)}>
Update Profile
</Button>
</div>
);
};
export default UserProfile;
`;
console.log('=== Advanced Pattern Matching ===\n');
const tree = parse(reactCode, { language: 'tsx' });
// CSS-like selectors
console.log('1. Find all async arrow functions:');
const asyncArrows = tree.findAll('arrow_function[async]');
asyncArrows.forEach(fn => {
console.log(`- Line ${fn.line}: ${fn.text.slice(0, 50)}...`);
});
console.log('\n2. Find JSX elements with className:');
const elementsWithClass = tree.findAll('jsx_element:has(jsx_attribute[name="className"])');
elementsWithClass.forEach(el => {
const tag = el.find('jsx_opening_element > identifier')?.text;
console.log(`- <${tag}> at line ${el.line}`);
});
console.log('\n3. Find useState calls:');
const useStates = tree.findAll('call_expression > identifier[text="useState"]');
console.log(`Found ${useStates.length} useState calls`);
console.log('\n=== Visitor Pattern ===\n');
// Count node types
const typeCounts = new Map<string, number>();
tree.visit((node) => {
const count = typeCounts.get(node.type) || 0;
typeCounts.set(node.type, count + 1);
});
console.log('Top 5 most common node types:');
Array.from(typeCounts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.forEach(([type, count]) => {
console.log(`- ${type}: ${count}`);
});
console.log('\n=== Scope Analysis ===\n');
const scopes = tree.analyzeScopes();
tree.visit((node) => {
if (node.type === 'identifier' && node.text === 'userId') {
const scope = scopes.getScope(node);
if (scope) {
console.log(`userId at line ${node.line} is in scope: ${scope.node.type}`);
}
}
});
console.log('\n=== JSX Analysis ===\n');
console.log('React hooks used:');
const hooks = tree.hooks();
hooks.forEach(hook => {
const name = hook.node.childForFieldName('function')?.text;
console.log(`- ${name} at line ${hook.line}`);
});
console.log('\nJSX Components:');
const components = tree.jsxComponents();
components.forEach(comp => {
const name = comp.find('identifier')?.text || comp.find('member_expression')?.text;
console.log(`- <${name}> at line ${comp.line}`);
});
console.log('\nButton props:');
const buttonProps = tree.jsxProps('Button');
buttonProps.forEach(prop => {
const name = prop.node.childForFieldName('name')?.text;
console.log(`- ${name} at line ${prop.line}`);
});
console.log('\n=== Export Analysis ===\n');
const exportNodes = tree.exports();
exportNodes.forEach(exp => {
const isDefault = exp.text.includes('default');
const exported = exp.find('identifier')?.text || exp.find('function')?.name;
console.log(`- ${isDefault ? 'default' : 'named'} export: ${exported}`);
});
console.log('\n=== Position-based Queries ===\n');
// Find node at specific position
const nodeAtLine15 = tree.nodeAt(15, 10);
if (nodeAtLine15) {
console.log(`Node at line 15, col 10: ${nodeAtLine15.type} "${nodeAtLine15.text.slice(0, 20)}..."`);
console.log('Path to root:', nodeAtLine15.getPath().map(n => n.type).join(' > '));
}