@@ -170,14 +170,9 @@ export function decoratorExtractor(node: Node): RouteInfo | null {
170170
171171/** Extracts tags from a list node like ["users", "admin"] */
172172function 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
173+ return listNode . namedChildren
174+ . map ( ( elem ) => extractStringValue ( elem ) )
175+ . filter ( ( v ) : v is string => v !== null )
181176}
182177
183178export function routerExtractor ( node : Node ) : RouterInfo | null {
@@ -192,14 +187,12 @@ export function routerExtractor(node: Node): RouterInfo | null {
192187 }
193188
194189 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"
201-
202- if ( type === "Unknown" ) {
190+ let type : RouterType
191+ if ( funcName === "APIRouter" || funcName === "fastapi.APIRouter" ) {
192+ type = "APIRouter"
193+ } else if ( funcName === "FastAPI" || funcName === "fastapi.FastAPI" ) {
194+ type = "FastAPI"
195+ } else {
203196 return null
204197 }
205198
@@ -230,6 +223,35 @@ export function routerExtractor(node: Node): RouterInfo | null {
230223 }
231224}
232225
226+ /** Checks if a node is inside an ancestor of a given type */
227+ function hasAncestor ( node : Node , ancestorType : string ) : boolean {
228+ let parent = node . parent
229+ while ( parent ) {
230+ if ( parent . type === ancestorType ) {
231+ return true
232+ }
233+ parent = parent . parent
234+ }
235+ return false
236+ }
237+
238+ /** Parses a module path, extracting relative dots if present */
239+ function parseModulePath ( rawPath : string ) : {
240+ modulePath : string
241+ isRelative : boolean
242+ relativeDots : number
243+ } {
244+ const matches = rawPath . match ( / ^ ( \. + ) ( .* ) / )
245+ if ( matches ) {
246+ return {
247+ modulePath : matches [ 2 ] ,
248+ isRelative : true ,
249+ relativeDots : matches [ 1 ] . length ,
250+ }
251+ }
252+ return { modulePath : rawPath , isRelative : false , relativeDots : 0 }
253+ }
254+
233255export function importExtractor ( node : Node ) : ImportInfo | null {
234256 if (
235257 node . type !== "import_statement" &&
@@ -238,67 +260,50 @@ export function importExtractor(node: Node): ImportInfo | null {
238260 return null
239261 }
240262
241- let modulePath = ""
242263 const names : string [ ] = [ ]
243264 const namedImports : ImportedName [ ] = [ ]
244- let isRelative = false
245- let relativeDots = 0
246265
247266 if ( node . type === "import_statement" ) {
248267 const nameNodes = findNodesByType ( node , "dotted_name" )
249268 for ( const nameNode of nameNodes ) {
250- modulePath = nameNode . text
251- const asNames = nameNode . text . split ( "." )
252- names . push ( asNames [ 0 ] )
253- namedImports . push ( { name : asNames [ 0 ] , alias : null } )
269+ const firstName = nameNode . text . split ( "." ) [ 0 ]
270+ names . push ( firstName )
271+ namedImports . push ( { name : firstName , alias : null } )
254272 }
255- } else if ( node . type === "import_from_statement" ) {
256- const moduleNode = node . childForFieldName ( "module_name" )
257- if ( moduleNode ) {
258- const rawPath = moduleNode . text
259- const matches = rawPath . match ( / ^ ( \. + ) ( .* ) / )
260- if ( matches ) {
261- isRelative = true
262- relativeDots = matches [ 1 ] . length
263- modulePath = matches [ 2 ]
264- } else {
265- modulePath = rawPath
266- }
273+ const modulePath = nameNodes [ 0 ] ?. text ?? ""
274+ return {
275+ modulePath,
276+ names,
277+ namedImports,
278+ isRelative : false ,
279+ relativeDots : 0 ,
267280 }
281+ }
268282
269- // Look for aliased_import nodes (e.g., "router as users_router")
270- const aliasedImports = findNodesByType ( node , "aliased_import" )
271- for ( const aliased of aliasedImports ) {
272- const nameNode = aliased . childForFieldName ( "name" )
273- const aliasNode = aliased . childForFieldName ( "alias" )
274- if ( nameNode ) {
275- const originalName = nameNode . text
276- const aliasName = aliasNode ?. text ?? null
277- names . push ( aliasName ?? originalName )
278- namedImports . push ( { name : originalName , alias : aliasName } )
279- }
283+ // import_from_statement
284+ const moduleNode = node . childForFieldName ( "module_name" )
285+ const { modulePath, isRelative, relativeDots } = parseModulePath (
286+ moduleNode ?. text ?? "" ,
287+ )
288+
289+ // Aliased imports (e.g., "router as users_router")
290+ for ( const aliased of findNodesByType ( node , "aliased_import" ) ) {
291+ const nameNode = aliased . childForFieldName ( "name" )
292+ const aliasNode = aliased . childForFieldName ( "alias" )
293+ if ( nameNode ) {
294+ const alias = aliasNode ?. text ?? null
295+ names . push ( alias ?? nameNode . text )
296+ namedImports . push ( { name : nameNode . text , alias } )
280297 }
281298 }
282299
283- // Get non-aliased imports (dotted_name nodes not inside aliased_import)
284- if ( node . type === "import_from_statement" ) {
285- const nameNodes = findNodesByType ( node , "dotted_name" )
286- for ( let i = 1 ; i < nameNodes . length ; i ++ ) {
287- const nameNode = nameNodes [ i ]
288- // Check if this node is inside an aliased_import
289- let parent = nameNode . parent
290- let isAliased = false
291- while ( parent ) {
292- if ( parent . type === "aliased_import" ) {
293- isAliased = true
294- break
295- }
296- parent = parent . parent
297- }
298- if ( ! isAliased ) {
299- names . push ( nameNode . text )
300- namedImports . push ( { name : nameNode . text , alias : null } )
301- }
300+ // Non-aliased imports (skip first dotted_name which is the module path)
301+ const nameNodes = findNodesByType ( node , "dotted_name" )
302+ for ( let i = 1 ; i < nameNodes . length ; i ++ ) {
303+ const nameNode = nameNodes [ i ]
304+ if ( ! hasAncestor ( nameNode , "aliased_import" ) ) {
305+ names . push ( nameNode . text )
306+ namedImports . push ( { name : nameNode . text , alias : null } )
302307 }
303308 }
304309
0 commit comments