@@ -25,7 +25,24 @@ function KtorParser:extract_base_path(file_path, line_number)
2525end
2626
2727--- Extracts endpoint path from Ktor routing content
28- function KtorParser :extract_endpoint_path (content )
28+ function KtorParser :extract_endpoint_path (content , file_path , line_number )
29+ -- Use multiline extraction for better accuracy
30+ if file_path and line_number then
31+ local path , end_line = self :_extract_path_multiline (file_path , line_number , content )
32+ if path then
33+ -- Store end_line_number for highlighting
34+ self ._last_end_line_number = end_line
35+ return path
36+ end
37+ end
38+
39+ -- Fallback to single line extraction
40+ self ._last_end_line_number = nil
41+ return self :_extract_path_single_line (content )
42+ end
43+
44+ --- Extracts path from single line content
45+ function KtorParser :_extract_path_single_line (content )
2946 -- Handle multiline patterns by normalizing whitespace
3047 local normalized_content = content :gsub (" %s+" , " " ):gsub (" [\r\n ]+" , " " )
3148
@@ -56,6 +73,102 @@ function KtorParser:extract_endpoint_path(content)
5673 return nil
5774end
5875
76+ --- Extracts path handling multiline routing definitions
77+ function KtorParser :_extract_path_multiline (file_path , start_line , content )
78+ -- First try single line extraction
79+ local path = self :_extract_path_single_line (content )
80+ if path then
81+ return path , nil -- Single line, no end_line
82+ end
83+
84+ -- If it's a multiline routing definition, read the file to find the path
85+ if self :_is_multiline_routing (content ) then
86+ local file = io.open (file_path , " r" )
87+ if not file then
88+ return nil , nil
89+ end
90+
91+ local lines = {}
92+ for line in file :lines () do
93+ table.insert (lines , line )
94+ end
95+ file :close ()
96+
97+ -- Read the next few lines to find the path parameter
98+ local multiline_content = content
99+ for i = start_line + 1 , math.min (start_line + 5 , # lines ) do
100+ local next_line = lines [i ]
101+ if next_line then
102+ multiline_content = multiline_content .. " " .. next_line :gsub (" ^%s+" , " " ):gsub (" %s+$" , " " )
103+
104+ -- Try to extract path from accumulated content
105+ local extracted_path = self :_extract_path_single_line (multiline_content )
106+ if extracted_path then
107+ return extracted_path , i -- Return path and end line number
108+ end
109+
110+ -- If we hit closing parenthesis followed by opening brace, stop and return end line
111+ if next_line :match " %s*%)%s*{" then
112+ -- Try one more time to extract path before stopping
113+ local final_path = self :_extract_path_single_line (multiline_content )
114+ return final_path , i
115+ end
116+ end
117+ end
118+ end
119+
120+ return nil , nil
121+ end
122+
123+ --- Checks if routing definition spans multiple lines
124+ function KtorParser :_is_multiline_routing (content )
125+ -- Check if content has HTTP method followed by opening parenthesis but no closing parenthesis and path
126+ return content :match " ^%s*%w+%s*%(%s*$" or content :match " ^%s*%w+%s*%($"
127+ end
128+
129+ --- Override parse_content to add end_line_number for multiline endpoints
130+ function KtorParser :parse_content (content , file_path , line_number , column )
131+ if not self :is_content_valid_for_parsing (content ) then
132+ return nil
133+ end
134+
135+ -- Use the 4 core methods to create endpoint
136+ local base_path = self :extract_base_path (file_path , line_number )
137+ local endpoint_path = self :extract_endpoint_path (content , file_path , line_number )
138+ local method = self :extract_method (content )
139+
140+ if not endpoint_path or not method then
141+ return nil
142+ end
143+
144+ -- Combine paths
145+ local full_path = self :combine_paths (base_path , endpoint_path )
146+
147+ -- Create endpoint entry
148+ local endpoint = {
149+ method = method :upper (),
150+ endpoint_path = full_path ,
151+ file_path = file_path ,
152+ line_number = line_number ,
153+ column = column ,
154+ display_value = method :upper () .. " " .. full_path ,
155+ confidence = self :get_parsing_confidence (content ),
156+ tags = { " api" },
157+ metadata = self :create_metadata (" endpoint" , {
158+ base_path = base_path ,
159+ raw_endpoint_path = endpoint_path ,
160+ }, content ),
161+ }
162+
163+ -- Add end_line_number if multiline
164+ if self ._last_end_line_number then
165+ endpoint .end_line_number = self ._last_end_line_number
166+ self ._last_end_line_number = nil -- Clean up
167+ end
168+
169+ return endpoint
170+ end
171+
59172--- Extracts HTTP method from Ktor routing content
60173function KtorParser :extract_method (content )
61174 -- Handle multiline patterns by normalizing whitespace
@@ -151,6 +264,8 @@ function KtorParser:_extract_base_paths_from_file(file_path, target_line)
151264 -- Track nesting level and extract route paths
152265 local bracket_depth = 0
153266 local route_stack = {}
267+ local multiline_route_buffer = nil
268+ local multiline_route_start_depth = nil
154269
155270 for i = 1 , target_line - 1 do
156271 local line = lines [i ]
@@ -159,13 +274,36 @@ function KtorParser:_extract_base_paths_from_file(file_path, target_line)
159274 local _ , open_count = line :gsub (" {" , " " )
160275 local _ , close_count = line :gsub (" }" , " " )
161276
162- -- Check for route("path") declarations
163- local route_path = line :match ' route%s*%("([^"]+)"%)'
164- if not route_path then
165- route_path = line :match " route%s*%('([^']+)'%)"
166- end
167- if route_path then
168- table.insert (route_stack , { path = route_path , depth = bracket_depth })
277+ -- Handle multiline route detection
278+ if multiline_route_buffer then
279+ -- We're in a multiline route, look for the path
280+ multiline_route_buffer = multiline_route_buffer .. " " .. line :gsub (" ^%s+" , " " ):gsub (" %s+$" , " " )
281+ local route_path = multiline_route_buffer :match ' route%s*%(%s*"([^"]+)"%s*%)'
282+ if not route_path then
283+ route_path = multiline_route_buffer :match " route%s*%(%s*'([^']+)'%s*%)"
284+ end
285+ if route_path then
286+ table.insert (route_stack , { path = route_path , depth = multiline_route_start_depth })
287+ multiline_route_buffer = nil
288+ multiline_route_start_depth = nil
289+ elseif line :match " %)" then
290+ -- Found closing paren but no path, reset
291+ multiline_route_buffer = nil
292+ multiline_route_start_depth = nil
293+ end
294+ else
295+ -- Check for single line route("path") declarations
296+ local route_path = line :match ' route%s*%("([^"]+)"%)'
297+ if not route_path then
298+ route_path = line :match " route%s*%('([^']+)'%)"
299+ end
300+ if route_path then
301+ table.insert (route_stack , { path = route_path , depth = bracket_depth })
302+ elseif line :match " route%s*%(" then
303+ -- Found start of multiline route
304+ multiline_route_buffer = line
305+ multiline_route_start_depth = bracket_depth
306+ end
169307 end
170308
171309 bracket_depth = bracket_depth + open_count - close_count
0 commit comments