@@ -94,30 +94,26 @@ function extractPathFromNode(node: Node): string {
9494 }
9595}
9696
97+ /**
98+ * Extracts from route decorators like @app.get("/path"), @router.post("/path"), etc.
99+ */
97100export function decoratorExtractor ( node : Node ) : RouteInfo | null {
98101 if ( node . type !== "decorated_definition" ) {
99102 return null
100103 }
101104
102105 const decoratorNode = node . firstNamedChild
103- const callNodes = decoratorNode ? findNodesByType ( decoratorNode , "call" ) : [ ]
104- const callNode = callNodes . length > 0 ? callNodes [ 0 ] : null
105-
106- if ( ! callNode ) {
107- return null
108- }
109-
110- const functionNameNode = callNode . childForFieldName ( "function" )
111- const argumentsNode = callNode . childForFieldName ( "arguments" )
112-
113- if ( ! functionNameNode || ! argumentsNode ) {
106+ if ( ! decoratorNode ) {
114107 return null
115108 }
116109
117- const objectNode = functionNameNode . childForFieldName ( "object" )
118- const methodNode = functionNameNode . childForFieldName ( "attribute" )
110+ const callNode = findNodesByType ( decoratorNode , "call" ) [ 0 ]
111+ const functionNode = callNode ?. childForFieldName ( "function" )
112+ const argumentsNode = callNode ?. childForFieldName ( "arguments" )
113+ const objectNode = functionNode ?. childForFieldName ( "object" )
114+ const methodNode = functionNode ?. childForFieldName ( "attribute" )
119115
120- if ( ! objectNode || ! methodNode ) {
116+ if ( ! objectNode || ! methodNode || ! argumentsNode ) {
121117 return null
122118 }
123119
@@ -172,65 +168,66 @@ export function decoratorExtractor(node: Node): RouteInfo | null {
172168 }
173169}
174170
171+ /** Extracts tags from a list node like ["users", "admin"] */
172+ function extractTags ( listNode : Node ) : string [ ] {
173+ const tags : string [ ] = [ ]
174+ for ( const elem of listNode . namedChildren ) {
175+ const tagValue = extractStringValue ( elem )
176+ if ( tagValue !== null ) {
177+ tags . push ( tagValue )
178+ }
179+ }
180+ return tags
181+ }
182+
175183export function routerExtractor ( node : Node ) : RouterInfo | null {
176184 if ( node . type !== "assignment" ) {
177185 return null
178186 }
179187
180188 const variableNameNode = node . childForFieldName ( "left" )
181189 const valueNode = node . childForFieldName ( "right" )
182-
183- if ( ! variableNameNode || ! valueNode ) {
190+ if ( ! variableNameNode || valueNode ?. type !== "call" ) {
184191 return null
185192 }
186193
187- const variableName = variableNameNode . text
194+ const funcName = valueNode . childForFieldName ( "function" ) ?. text
195+ const type : RouterType =
196+ funcName === "APIRouter" || funcName === "fastapi.APIRouter"
197+ ? "APIRouter"
198+ : funcName === "FastAPI" || funcName === "fastapi.FastAPI"
199+ ? "FastAPI"
200+ : "Unknown"
188201
189- let type : RouterType = "Unknown"
202+ if ( type === "Unknown" ) {
203+ return null
204+ }
190205
191- if ( valueNode . type === "call" ) {
192- const functionNameNode = valueNode . childForFieldName ( "function" )
193- const funcName = functionNameNode ?. text
194- if ( funcName === "APIRouter" || funcName === "fastapi.APIRouter" ) {
195- type = "APIRouter"
196- } else if ( funcName === "FastAPI" || funcName === "fastapi.FastAPI" ) {
197- type = "FastAPI"
206+ let prefix = ""
207+ let tags : string [ ] = [ ]
208+ const argumentsNode = valueNode . childForFieldName ( "arguments" )
209+ for ( const child of argumentsNode ?. namedChildren ?? [ ] ) {
210+ if ( child . type !== "keyword_argument" ) {
211+ continue
198212 }
213+ const argName = child . childForFieldName ( "name" ) ?. text
214+ const argValue = child . childForFieldName ( "value" )
199215
200- let prefix = ""
201- const tags : string [ ] = [ ]
202- const argumentsNode = valueNode . childForFieldName ( "arguments" )
203- if ( argumentsNode ) {
204- for ( const child of argumentsNode . namedChildren ) {
205- if ( child . type !== "keyword_argument" ) continue
206- const argName = child . childForFieldName ( "name" ) ?. text
207- const argValue = child . childForFieldName ( "value" )
208-
209- if ( argName === "prefix" && argValue ) {
210- prefix = extractPathFromNode ( argValue )
211- } else if ( argName === "tags" && argValue ?. type === "list" ) {
212- for ( const elem of argValue . namedChildren ) {
213- const tagValue = extractStringValue ( elem )
214- if ( tagValue !== null ) {
215- tags . push ( tagValue )
216- }
217- }
218- }
219- }
220- }
221- if ( type !== "Unknown" ) {
222- return {
223- variableName,
224- type,
225- prefix,
226- tags,
227- line : node . startPosition . row + 1 ,
228- column : node . startPosition . column ,
229- }
216+ if ( argName === "prefix" && argValue ) {
217+ prefix = extractPathFromNode ( argValue )
218+ } else if ( argName === "tags" && argValue ?. type === "list" ) {
219+ tags = extractTags ( argValue )
230220 }
231221 }
232222
233- return null
223+ return {
224+ variableName : variableNameNode . text ,
225+ type,
226+ prefix,
227+ tags,
228+ line : node . startPosition . row + 1 ,
229+ column : node . startPosition . column ,
230+ }
234231}
235232
236233export function importExtractor ( node : Node ) : ImportInfo | null {
@@ -308,98 +305,73 @@ export function importExtractor(node: Node): ImportInfo | null {
308305 return { modulePath, names, namedImports, isRelative, relativeDots }
309306}
310307
311- export function includeRouterExtractor ( node : Node ) : IncludeRouterInfo | null {
308+ /** Extracts method call info: object.method(args) */
309+ function extractMethodCall (
310+ node : Node ,
311+ methodName : string ,
312+ ) : { object : string ; args : Node [ ] } | null {
312313 if ( node . type !== "call" ) {
313314 return null
314315 }
315316
316- const functionNameNode = node . childForFieldName ( "function" )
317- if ( ! functionNameNode || functionNameNode . type !== "attribute" ) {
317+ const functionNode = node . childForFieldName ( "function" )
318+ if ( functionNode ? .type !== "attribute" ) {
318319 return null
319320 }
320321
321- const objectNode = functionNameNode . childForFieldName ( "object" )
322- const methodNode = functionNameNode . childForFieldName ( "attribute" )
323- if ( ! objectNode || ! methodNode || methodNode . text !== "include_router" ) {
322+ const objectNode = functionNode . childForFieldName ( "object" )
323+ const methodNode = functionNode . childForFieldName ( "attribute" )
324+ if ( ! objectNode || methodNode ? .text !== methodName ) {
324325 return null
325326 }
326327
327328 const argumentsNode = node . childForFieldName ( "arguments" )
328- if ( ! argumentsNode ) {
329+ const args =
330+ argumentsNode ?. namedChildren . filter ( ( c ) => c . type !== "comment" ) ?? [ ]
331+
332+ return { object : objectNode . text , args }
333+ }
334+
335+ export function includeRouterExtractor ( node : Node ) : IncludeRouterInfo | null {
336+ const call = extractMethodCall ( node , "include_router" )
337+ if ( ! call ) {
329338 return null
330339 }
331340
332- const routerArg = argumentsNode . namedChildren [ 0 ]
333- const router = routerArg ? routerArg . text : ""
334-
335341 let prefix = ""
336- const tags : string [ ] = [ ]
337- for ( const argNode of argumentsNode . namedChildren ) {
338- if ( argNode . type === "keyword_argument" ) {
339- const nameNode = argNode . childForFieldName ( "name" )
340- const valueNode = argNode . childForFieldName ( "value" )
341- if ( nameNode ?. text === "prefix" && valueNode ) {
342- prefix = extractPathFromNode ( valueNode )
343- } else if ( nameNode ?. text === "tags" && valueNode ?. type === "list" ) {
344- for ( const elem of valueNode . namedChildren ) {
345- const tagValue = extractStringValue ( elem )
346- if ( tagValue !== null ) {
347- tags . push ( tagValue )
348- }
349- }
350- }
342+ let tags : string [ ] = [ ]
343+ for ( const arg of call . args ) {
344+ if ( arg . type !== "keyword_argument" ) {
345+ continue
346+ }
347+ const name = arg . childForFieldName ( "name" ) ?. text
348+ const value = arg . childForFieldName ( "value" )
349+
350+ if ( name === "prefix" && value ) {
351+ prefix = extractPathFromNode ( value )
352+ } else if ( name === "tags" && value ?. type === "list" ) {
353+ tags = extractTags ( value )
351354 }
352355 }
353356
354357 return {
355- object : objectNode . text ,
356- router,
358+ object : call . object ,
359+ router : call . args [ 0 ] ?. text ?? "" ,
357360 prefix,
358361 tags,
359362 }
360363}
361364
362- /**
363- * Extracts mount() calls for subapps.
364- * Pattern: app.mount("/path", subapp)
365- */
365+ /** Extracts mount() calls for subapps: app.mount("/path", subapp) */
366366export function mountExtractor ( node : Node ) : MountInfo | null {
367- if ( node . type !== "call" ) {
368- return null
369- }
370-
371- const functionNameNode = node . childForFieldName ( "function" )
372- if ( ! functionNameNode || functionNameNode . type !== "attribute" ) {
373- return null
374- }
375-
376- const objectNode = functionNameNode . childForFieldName ( "object" )
377- const methodNode = functionNameNode . childForFieldName ( "attribute" )
378- if ( ! objectNode || ! methodNode || methodNode . text !== "mount" ) {
379- return null
380- }
381-
382- const argumentsNode = node . childForFieldName ( "arguments" )
383- if ( ! argumentsNode ) {
384- return null
385- }
386-
387- // Skip comment nodes to find actual arguments
388- const args = argumentsNode . namedChildren . filter (
389- ( child ) => child . type !== "comment" ,
390- )
391-
392- // First arg is path, second is app
393- const pathArg = args [ 0 ]
394- const appArg = args [ 1 ]
395-
396- if ( ! pathArg || ! appArg ) {
367+ const call = extractMethodCall ( node , "mount" )
368+ if ( ! call || call . args . length < 2 ) {
397369 return null
398370 }
399371
400372 return {
401- object : objectNode . text ,
402- path : extractPathFromNode ( pathArg ) ,
403- app : appArg . text ,
373+ object : call . object ,
374+ path : extractPathFromNode ( call . args [ 0 ] ) ,
375+ app : call . args [ 1 ] . text ,
404376 }
405377}
0 commit comments