Skip to content

Commit 138190d

Browse files
Clean up extractors
1 parent d16a812 commit 138190d

2 files changed

Lines changed: 96 additions & 124 deletions

File tree

src/core/extractors.ts

Lines changed: 92 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
*/
97100
export 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+
175183
export 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

236233
export 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) */
366366
export 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
}

src/core/transformer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ function findParentRouter(
9191
function buildPrefixHierarchy(
9292
flatRouters: RouterDefinition[],
9393
): RouterDefinition[] {
94-
// Sort by prefix length (shortest first) to process parents before children
94+
// Sort by segment count (fewest first) to process parents before children
9595
const sorted = [...flatRouters].sort((a, b) => {
96-
const prefixA = stripLeadingDynamicSegments(a.prefix)
97-
const prefixB = stripLeadingDynamicSegments(b.prefix)
98-
return prefixA.length - prefixB.length
96+
const segmentsA = countSegments(stripLeadingDynamicSegments(a.prefix))
97+
const segmentsB = countSegments(stripLeadingDynamicSegments(b.prefix))
98+
return segmentsA - segmentsB
9999
})
100100

101101
// Build a map of prefix -> router for grouping

0 commit comments

Comments
 (0)