| 116 | * `maxLines` caps a single chunk; larger declarations are line-split. |
| 117 | */ |
| 118 | export async function astChunkFile(rel: string, content: string, maxLines = 80): Promise<Chunk[]> { |
| 119 | const lang = langForFile(rel); |
| 120 | if (!lang) return lineChunk(rel, content); |
| 121 | const boundary = BOUNDARY_TYPES[lang]; |
| 122 | if (!boundary) return lineChunk(rel, content); |
| 123 | |
| 124 | let parser; |
| 125 | try { |
| 126 | parser = await getParser(lang); |
| 127 | } catch { |
| 128 | return lineChunk(rel, content); |
| 129 | } |
| 130 | if (!parser) return lineChunk(rel, content); |
| 131 | |
| 132 | let tree; |
| 133 | try { |
| 134 | tree = parser.parser.parse(content); |
| 135 | } catch { |
| 136 | return lineChunk(rel, content); |
| 137 | } |
| 138 | |
| 139 | const lines = content.split('\n'); |
| 140 | const root = tree.rootNode; |
| 141 | const chunks: Chunk[] = []; |
| 142 | const boundaries: Array<{ startLine: number; endLine: number; symbol?: string; qualifiedSymbol?: string }> = []; |
| 143 | |
| 144 | // Container node types whose children we descend into to chunk members |
| 145 | // (methods, nested functions, nested classes) as their own units. |
| 146 | // - JS/TS: class_declaration, class_body |
| 147 | // - Python: class_definition (+ block body) |
| 148 | // - PHP: class_declaration, trait/interface declaration |
| 149 | // - Rust: impl_item, trait_item, mod_item (bodies are declaration_list) |
| 150 | // - Go: a struct is type_declaration but holds NO methods (methods are |
| 151 | // top-level funcs with receivers), so Go intentionally has no container |
| 152 | // to descend — its functions are already top-level boundaries. |
| 153 | const CONTAINER_RE = /^(class_declaration|abstract_class_declaration|class_definition|class_specifier|class_body|class|interface_declaration|trait_declaration|trait_item|impl_item|mod_item|module|namespace_definition|namespace|object|enum_declaration|enum_item|declaration_list)$/; |
| 154 | // Body node types that hold a container's members. |
| 155 | const BODY_RE = /^(body|class_body|declaration_list|field_declaration_list|enum_body|block|impl_item|trait_item)$/; |
| 156 | |
| 157 | /** |
| 158 | * Recursively collect chunk boundaries. |
| 159 | * |
| 160 | * Strategy (depth-unbounded, but cost-bounded by maxLines): |
| 161 | * - A boundary node that fits in maxLines becomes ONE chunk, with its full |
| 162 | * text (decorators included — see decorator handling below). |
| 163 | * - A boundary node that is a CONTAINER and is LARGER than maxLines is split: |
| 164 | * we don't emit the whole container, we descend into its body and chunk each |
| 165 | * member (method / nested function / nested class) separately, qualifying |
| 166 | * each member's symbol with the container name (e.g. "UserService.login"). |
| 167 | * This is what fixes deeply-nested React/Django/FastAPI code: a 400-line |
| 168 | * class no longer becomes one giant chunk or gets cut mid-method. |
| 169 | * - Decorators (Python @decorator, TS @Component) are part of the |
| 170 | * decorated_definition / are preceding siblings; tree-sitter's |
| 171 | * decorated_definition node already spans them, and for TS the decorators |
| 172 | * are children of the class/method node, so the node's start..end already |
| 173 | * includes them. We therefore chunk on the OUTERMOST node so the decorator |
| 174 | * travels with the thing it decorates. |
| 175 | */ |