11import { Effect } from 'effect'
22import type { RebuiltText } from '../chunking/rebuild'
3+ import { findScopeAtOffset , getAncestorChain } from '../scope/tree'
34import type {
45 ByteRange ,
56 Chunk ,
67 ChunkContext ,
78 ChunkOptions ,
9+ EntityInfo ,
10+ ExtractedEntity ,
11+ ImportInfo ,
812 ScopeTree ,
913} from '../types'
14+ import { getSiblings , type SiblingOptions } from './siblings'
1015
1116/**
1217 * Error when attaching context fails
@@ -19,88 +24,90 @@ export class ContextError {
1924 ) { }
2025}
2126
22- /**
23- * Attach context information to a chunk
24- *
25- * @param text - The rebuilt text info for the chunk
26- * @param scopeTree - The scope tree for the file
27- * @param options - Chunking options
28- * @param index - The chunk index
29- * @param totalChunks - Total number of chunks
30- * @returns Effect yielding the complete chunk with context
31- *
32- * TODO: Implement context attachment
33- */
34- export const attachContext = (
35- text : RebuiltText ,
36- scopeTree : ScopeTree ,
37- options : ChunkOptions ,
38- index : number ,
39- totalChunks : number ,
40- ) : Effect . Effect < Chunk , ContextError > => {
41- // TODO: Implement context attachment
42- // 1. Find scope for this chunk's byte range
43- // 2. Get entities within the chunk
44- // 3. Get siblings based on options
45- // 4. Get relevant imports
46- const context : ChunkContext = {
47- scope : [ ] ,
48- entities : [ ] ,
49- siblings : [ ] ,
50- imports : [ ] ,
51- }
52-
53- void scopeTree
54- void options
55-
56- return Effect . succeed ( {
57- text : text . text ,
58- byteRange : text . byteRange ,
59- lineRange : text . lineRange ,
60- context,
61- index,
62- totalChunks,
63- } )
64- }
65-
6627/**
6728 * Get scope information for a byte range
6829 *
30+ * Finds the scope containing this range and builds an array of EntityInfo
31+ * from the scope and its ancestors.
32+ *
6933 * @param byteRange - The byte range to get scope for
7034 * @param scopeTree - The scope tree
71- * @returns Scope entity info array
72- *
73- * TODO: Implement scope lookup
35+ * @returns Scope entity info array representing the scope chain
7436 */
7537export const getScopeForRange = (
7638 byteRange : ByteRange ,
7739 scopeTree : ScopeTree ,
7840) : ChunkContext [ 'scope' ] => {
79- // TODO: Implement scope lookup
80- // Find containing scopes and return as EntityInfo[]
81- void byteRange
82- void scopeTree
83- return [ ]
41+ // Find the scope at the start of the range
42+ const scope = findScopeAtOffset ( scopeTree , byteRange . start )
43+
44+ if ( ! scope ) {
45+ return [ ]
46+ }
47+
48+ // Build scope chain: current scope + ancestors
49+ const scopeChain : EntityInfo [ ] = [ ]
50+
51+ // Add current scope
52+ scopeChain . push ( {
53+ name : scope . entity . name ,
54+ type : scope . entity . type ,
55+ signature : scope . entity . signature ,
56+ } )
57+
58+ // Add ancestors (from immediate parent to root)
59+ const ancestors = getAncestorChain ( scope )
60+ for ( const ancestor of ancestors ) {
61+ scopeChain . push ( {
62+ name : ancestor . entity . name ,
63+ type : ancestor . entity . type ,
64+ signature : ancestor . entity . signature ,
65+ } )
66+ }
67+
68+ return scopeChain
8469}
8570
8671/**
8772 * Get entities within a byte range
8873 *
74+ * Finds entities whose byte ranges overlap with the given range.
75+ * Overlap condition: entity.start < range.end && entity.end > range.start
76+ *
8977 * @param byteRange - The byte range to search
9078 * @param scopeTree - The scope tree
9179 * @returns Entity info array for entities in range
92- *
93- * TODO: Implement entity lookup
9480 */
9581export const getEntitiesInRange = (
9682 byteRange : ByteRange ,
9783 scopeTree : ScopeTree ,
9884) : ChunkContext [ 'entities' ] => {
99- // TODO: Implement entity lookup
100- // Find entities whose ranges overlap with byteRange
101- void byteRange
102- void scopeTree
103- return [ ]
85+ const overlappingEntities = scopeTree . allEntities . filter ( ( entity ) => {
86+ // Overlap check: entity.start < range.end && entity.end > range.start
87+ return (
88+ entity . byteRange . start < byteRange . end &&
89+ entity . byteRange . end > byteRange . start
90+ )
91+ } )
92+
93+ // Map to EntityInfo
94+ return overlappingEntities . map ( ( entity ) => ( {
95+ name : entity . name ,
96+ type : entity . type ,
97+ signature : entity . signature ,
98+ } ) )
99+ }
100+
101+ /**
102+ * Get import source from an import entity
103+ *
104+ * Uses the pre-extracted source from AST parsing (works for all languages).
105+ *
106+ * @param entity - The import entity
107+ * @returns The import source or empty string if not found
108+ */
109+ const getImportSource = ( entity : ExtractedEntity ) : string => {
110+ return entity . source ?? ''
104111}
105112
106113/**
@@ -110,18 +117,133 @@ export const getEntitiesInRange = (
110117 * @param scopeTree - The scope tree
111118 * @param filterImports - Whether to filter to only used imports
112119 * @returns Import info array
113- *
114- * TODO: Implement import filtering
115120 */
116121export const getRelevantImports = (
117122 entities : ChunkContext [ 'entities' ] ,
118123 scopeTree : ScopeTree ,
119124 filterImports : boolean ,
120125) : ChunkContext [ 'imports' ] => {
121- // TODO: Implement import filtering
122- // If filterImports, only include imports used by chunk entities
123- void entities
124- void scopeTree
125- void filterImports
126- return [ ]
126+ const imports = scopeTree . imports
127+
128+ if ( imports . length === 0 ) {
129+ return [ ]
130+ }
131+
132+ // Map import entity to ImportInfo
133+ const mapToImportInfo = ( entity : ExtractedEntity ) : ImportInfo => ( {
134+ name : entity . name ,
135+ source : getImportSource ( entity ) ,
136+ } )
137+
138+ // If not filtering, return all imports
139+ if ( ! filterImports ) {
140+ return imports . map ( mapToImportInfo )
141+ }
142+
143+ // Filter to only imports that are used by entities in the chunk
144+ // Build a set of names that appear in entity signatures and names
145+ const usedNames = new Set < string > ( )
146+ for ( const entity of entities ) {
147+ // Add the entity name
148+ usedNames . add ( entity . name )
149+
150+ // Extract identifiers from signature if available
151+ if ( entity . signature ) {
152+ // Match word characters that could be identifiers
153+ const identifiers = entity . signature . match (
154+ / \b [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * \b / g,
155+ )
156+ if ( identifiers ) {
157+ for ( const id of identifiers ) {
158+ usedNames . add ( id )
159+ }
160+ }
161+ }
162+ }
163+
164+ // Filter imports to those whose names appear in the chunk
165+ const filteredImports = imports . filter ( ( importEntity ) => {
166+ return usedNames . has ( importEntity . name )
167+ } )
168+
169+ return filteredImports . map ( mapToImportInfo )
170+ }
171+
172+ /**
173+ * Attach context information to a chunk
174+ *
175+ * @param text - The rebuilt text info for the chunk
176+ * @param scopeTree - The scope tree for the file
177+ * @param options - Chunking options
178+ * @param index - The chunk index
179+ * @param totalChunks - Total number of chunks
180+ * @returns Effect yielding the complete chunk with context
181+ */
182+ export const attachContext = (
183+ text : RebuiltText ,
184+ scopeTree : ScopeTree ,
185+ options : ChunkOptions ,
186+ index : number ,
187+ totalChunks : number ,
188+ ) : Effect . Effect < Chunk , ContextError > => {
189+ return Effect . try ( {
190+ try : ( ) => {
191+ // Determine context mode
192+ const contextMode = options . contextMode ?? 'full'
193+
194+ // For 'none' mode, return minimal context
195+ if ( contextMode === 'none' ) {
196+ const context : ChunkContext = {
197+ scope : [ ] ,
198+ entities : [ ] ,
199+ siblings : [ ] ,
200+ imports : [ ] ,
201+ }
202+ return {
203+ text : text . text ,
204+ byteRange : text . byteRange ,
205+ lineRange : text . lineRange ,
206+ context,
207+ index,
208+ totalChunks,
209+ }
210+ }
211+
212+ // Get scope for this chunk's byte range
213+ const scope = getScopeForRange ( text . byteRange , scopeTree )
214+
215+ // Get entities within the chunk
216+ const entities = getEntitiesInRange ( text . byteRange , scopeTree )
217+
218+ // Get siblings based on options
219+ const siblingDetail = options . siblingDetail ?? 'signatures'
220+ const siblingOptions : SiblingOptions = {
221+ detail : siblingDetail ,
222+ maxSiblings : contextMode === 'minimal' ? 2 : undefined ,
223+ }
224+ const siblings = getSiblings ( text . byteRange , scopeTree , siblingOptions )
225+
226+ // Get relevant imports
227+ const filterImports = options . filterImports ?? false
228+ const imports = getRelevantImports ( entities , scopeTree , filterImports )
229+
230+ const context : ChunkContext = {
231+ scope,
232+ entities,
233+ siblings,
234+ imports,
235+ }
236+
237+ return {
238+ text : text . text ,
239+ byteRange : text . byteRange ,
240+ lineRange : text . lineRange ,
241+ context,
242+ index,
243+ totalChunks,
244+ }
245+ } ,
246+ catch : ( error : unknown ) =>
247+ new ContextError ( 'Failed to attach context' , error ) ,
248+ } )
127249}
0 commit comments