How Understand-Anything Language Extractors Parse TypeScript, Python, Rust, and Go
Understand-Anything normalizes multi-language code analysis through a single LanguageExtractor interface, where each implementation traverses tree-sitter ASTs to generate standardized structural data and call graphs.
Understand-Anything is an open-source code comprehension framework that unifies parsing across heterogeneous languages. Its language extractors convert language-specific syntax into two consistent outputs: a StructuralAnalysis object containing functions, classes, and imports, and a CallGraphEntry array mapping caller-callee relationships. According to the Understand-Anything source code, each extractor implements an identical interface while handling the unique AST node types of its target language.
The LanguageExtractor Interface and Core Architecture
All extractors inherit from the shared LanguageExtractor interface defined in [types.ts](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/extractors/types.ts). The contract requires two primary methods: extractStructure() and extractCallGraph(), both accepting a tree-sitter rootNode and returning normalized data.
Every extractor follows a four-phase pipeline:
- Walk top-level AST nodes via
rootNode.children. - Dispatch each node to specialized helpers like
processTopLevelNode()based onnode.type. - Collect parameters, return types, imports, and exports using utility functions that understand language-specific AST shapes.
- Build call graphs by recursively traversing the tree, maintaining a stack of current function names, and recording call expressions.
TypeScript and JavaScript Parsing
The TypeScript extractor ([typescript-extractor.ts](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/extractors/typescript-extractor.ts)) handles both JavaScript and TypeScript source files by targeting the tree-sitter TypeScript grammar.
Parameter and Return Type Extraction
The extractParams() method walks formal_parameters nodes, processing required_parameter, optional_parameter, and rest patterns (...args). It extracts identifiers from simple bindings and typed parameters. Return types are captured via extractReturnType(), which reads the optional type_annotation field attached to function declarations.
Import and Export Processing
extractImportSpecifiers() parses three import variants: named_imports (destructured imports), namespace_import (* as ns), and default identifiers. The processTopLevelNode() router directs function_declaration, class_declaration, lexical_declaration (const/let), import_statement, and export_statement nodes to appropriate handlers, building the exports list from top-level declarations.
Call Graph Construction
extractCallGraph() identifies function-like nodes—including function_declaration, method_definition, arrow_function, and function_expression—to maintain a scope stack. It records every call_expression where the callee contains a function field, associating the call with the current function context and line number.
Python Parsing
The Python implementation ([python-extractor.ts](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/extractors/python-extractor.ts)) adapts to Python's dynamic nature and explicit self-references.
Dynamic Parameter Handling
extractParams() supports plain identifiers, typed parameters, default values, and splat patterns (*args, **kwargs). It explicitly skips the implicit first parameter when it equals self or cls, avoiding noise in method signatures. Return type annotations are retrieved via extractReturnType(), which reads the return_type field on function_definition nodes.
Import Statements and Implicit Exports
Python uses two extraction helpers: extractImport() handles import statements by collecting dotted_name and aliased_import nodes, while extractFromImport() processes from ... import ... forms, including wildcard imports. Unlike TypeScript, Python has no explicit export syntax; the private addExport() method automatically registers top-level functions and classes as exports.
Call Graph Generation
The extractor records call nodes where the callee is an identifier or attribute (method access). It maintains a function name stack by tracking function_definition and class_definition entries, creating CallGraphEntry objects linking the current scope to the callee with line numbers.
Rust Parsing
The Rust extractor ([rust-extractor.ts](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/extractors/rust-extractor.ts)) manages Rust's complex type system and visibility modifiers.
Visibility Controls and Export Detection
The isPublic() helper checks for visibility_modifier nodes starting with pub, determining which items are exported. Only public functions, structs, and traits are added to the exports list. The extractParams() method walks parameters nodes while ignoring self_parameter receivers, and extractReturnType() reads the return_type named field of function_item nodes.
Type System Extraction
The extractor maps Rust's type system to the classes array through specialized methods: extractStruct(), extractEnum(), and extractTrait(). These populate properties (struct fields or enum variants) and methods (including trait signatures). extractImpl() processes implementation blocks, attaching methods to types via a methodsByType map and treating pub methods as exports.
Call Graph and Path Extraction
extractUseDeclaration() handles Rust's use statement variations—single, scoped, list, and wildcard—extracting source paths via extractScopedPath(). For call graphs, extractCallGraph() captures call_expression nodes and resolves callee names using extractCalleeName(), which handles simple identifiers, field expressions (obj.method), and scoped identifiers (Vec::new).
Go Parsing
The Go extractor ([go-extractor.ts](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/extractors/go-extractor.ts)) implements Go-specific conventions including exported identifiers and receiver methods.
Export Conventions and Method Receivers
isExported() checks if a name begins with an uppercase letter, following Go's capitalization-based export rules. extractParams() iterates over parameter_declaration nodes, collecting multiple identifiers from single declarations (e.g., a, b int). extractReceiverType() reads receiver declarations to associate methods with their underlying struct types.
Struct and Interface Analysis
Both structs and interfaces are stored in the classes array. extractStruct() gathers field identifiers from struct literals, while extractInterface() records method names from method_elem nodes within interface definitions.
Import Handling and Call Graphs
extractImportDeclaration() parses grouped and single imports, unquoting path strings and determining specifiers from aliases or the last path component. The call graph walker records call_expression nodes, pushing function and method names onto a scope stack before associating calls with their containing functions.
Using the Extractors Programmatically
You can invoke any extractor directly by loading the appropriate tree-sitter WASM grammar and calling the standardized interface:
import { LanguageExtractor } from './packages/core/src/plugins/extractors/types.js';
import { TypeScriptExtractor } from './packages/core/src/plugins/extractors/typescript-extractor.js';
import { parse } from 'web-tree-sitter';
async function analyzeSource(source: string) {
await parse.init();
const parser = new parse.Parser();
const Lang = await parse.Language.load(
'node_modules/tree-sitter-typescript/tsx.wasm'
);
parser.setLanguage(Lang);
const tree = parser.parse(source);
const extractor: LanguageExtractor = new TypeScriptExtractor();
const structure = extractor.extractStructure(tree.rootNode);
const callGraph = extractor.extractCallGraph(tree.rootNode);
return { structure, callGraph };
}
Replace TypeScriptExtractor with PythonExtractor, RustExtractor, or GoExtractor, and load the corresponding WASM file (tree-sitter-python, tree-sitter-rust, or tree-sitter-go). The public API remains identical across all languages.
Summary
- Unified Interface: All extractors implement
LanguageExtractorfrom [types.ts](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/extractors/types.ts), providingextractStructure()andextractCallGraph()methods. - Tree-sitter Foundation: Every extractor operates on tree-sitter ASTs, using language-specific node types like
function_definition(Python) orfunction_item(Rust). - Normalization: Language quirks—such as Go's uppercase exports, Rust's visibility modifiers, or Python's implicit exports—are normalized into consistent
StructuralAnalysisandCallGraphEntryformats. - Extensibility: New languages can be supported by implementing the interface and walking the appropriate AST nodes, following the patterns established in the four reference implementations.
Frequently Asked Questions
What is the LanguageExtractor interface in Understand-Anything?
The LanguageExtractor interface is a TypeScript contract defined in the core types module that standardizes how the framework interacts with language-specific parsers. It requires implementations to provide two methods: extractStructure(), which returns function signatures, classes, and imports, and extractCallGraph(), which returns caller-callee relationships. This abstraction allows Understand-Anything to treat TypeScript, Python, Rust, and Go uniformly despite their syntactic differences.
How does Understand-Anything handle language-specific AST differences?
Each extractor contains private helper methods—such as extractParams(), extractReturnType(), and processTopLevelNode()—that understand the specific AST node types of their target language. For example, the Rust extractor handles visibility_modifier nodes to determine exports, while the Go extractor checks for uppercase identifiers. These helpers normalize the diverse AST structures into the common StructuralAnalysis format before returning data to the caller.
Can I add support for additional languages to Understand-Anything?
Yes, you can extend the system by creating a new class that implements the LanguageExtractor interface and writing tree-sitter traversal logic for your target language. You must implement logic to extract functions, classes or structs, imports, and exports into the standardized output format, plus build a call graph by tracking function scopes and call expressions. Follow the file structure in packages/core/src/plugins/extractors/ and reference existing extractors for implementation patterns.
How does the call graph extraction work across different languages?
Call graph extraction follows a consistent pattern across all extractors: the walker maintains a stack of function names as it descends the AST, and when it encounters a call node—such as call_expression in TypeScript/Rust/Go or call in Python—it records the relationship between the current function scope and the callee. Language-specific logic in helpers like extractCalleeName() handles how the callee identifier is extracted, whether through simple identifiers, attribute access (Python), or scoped paths (Rust).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →