88from collections import defaultdict
99from functools import cache
1010from pathlib import Path
11- from typing import TYPE_CHECKING , Any , Optional
11+ from typing import TYPE_CHECKING , Any , Optional , Tuple
1212
1313import git
1414import libcst as cst
@@ -201,7 +201,7 @@ def get_functions_to_optimize(
201201 elif file is not None :
202202 logger .info ("!lsp|Finding all functions in the file '%s'…" , file )
203203 console .rule ()
204- functions = find_all_functions_in_file (file )
204+ functions : dict [ Path , list [ FunctionToOptimize ]] = find_all_functions_in_file (file )
205205 if only_get_this_function is not None :
206206 split_function = only_get_this_function .split ("." )
207207 if len (split_function ) > 2 :
@@ -224,8 +224,16 @@ def get_functions_to_optimize(
224224 if found_function is None :
225225 if is_lsp :
226226 return functions , 0 , None
227+ found = closest_matching_file_function_name (only_get_this_function , functions )
228+ if found is not None :
229+ file , found_function = found
230+ exit_with_message (
231+ f"Function { only_get_this_function } not found in file { file } \n or the function does not have a 'return' statement or is a property.\n "
232+ f"Did you mean { found_function .qualified_name } instead?"
233+ )
234+
227235 exit_with_message (
228- f"Function { only_function_name } not found in file { file } \n or the function does not have a 'return' statement or is a property"
236+ f"Function { only_get_this_function } not found in file { file } \n or the function does not have a 'return' statement or is a property"
229237 )
230238 functions [file ] = [found_function ]
231239 else :
@@ -259,6 +267,55 @@ def get_functions_within_git_diff(uncommitted_changes: bool) -> dict[str, list[F
259267 return get_functions_within_lines (modified_lines )
260268
261269
270+ def closest_matching_file_function_name (
271+ qualified_fn_to_find : str , found_fns : dict [Path , list [FunctionToOptimize ]]
272+ ) -> Tuple [Path , FunctionToOptimize ] | None :
273+ """Find closest matching function name using Levenshtein distance.
274+
275+ Args:
276+ qualified_fn_to_find: Function name to find in format "Class.function" or "function"
277+ found_fns: Dictionary of file paths to list of functions
278+
279+ Returns:
280+ Tuple of (file_path, function) for closest match, or None if no matches found
281+ """
282+ min_distance = 4
283+ closest_match = None
284+ closest_file = None
285+
286+ qualified_fn_to_find = qualified_fn_to_find .lower ()
287+
288+ for file_path , functions in found_fns .items ():
289+ for function in functions :
290+ # Compare either full qualified name or just function name
291+ fn_name = function .qualified_name .lower ()
292+ dist = levenshtein_distance (qualified_fn_to_find , fn_name )
293+
294+ if dist < min_distance :
295+ min_distance = dist
296+ closest_match = function
297+ closest_file = file_path
298+
299+ if closest_match is not None :
300+ return closest_file , closest_match
301+ return None
302+
303+
304+ def levenshtein_distance (s1 : str , s2 : str ):
305+ if len (s1 ) > len (s2 ):
306+ s1 , s2 = s2 , s1
307+ distances = range (len (s1 ) + 1 )
308+ for index2 , char2 in enumerate (s2 ):
309+ newDistances = [index2 + 1 ]
310+ for index1 , char1 in enumerate (s1 ):
311+ if char1 == char2 :
312+ newDistances .append (distances [index1 ])
313+ else :
314+ newDistances .append (1 + min ((distances [index1 ], distances [index1 + 1 ], newDistances [- 1 ])))
315+ distances = newDistances
316+ return distances [- 1 ]
317+
318+
262319def get_functions_inside_a_commit (commit_hash : str ) -> dict [str , list [FunctionToOptimize ]]:
263320 modified_lines : dict [str , list [int ]] = get_git_diff (only_this_commit = commit_hash )
264321 return get_functions_within_lines (modified_lines )
0 commit comments