Coverage for little_loops / issues / anchors.py: 100%

19 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-05-22 16:19 -0500

1"""Anchor resolver for file:line references — ENH-1300. 

2 

3Resolves a file:line reference to its enclosing function, class, or section 

4using a language-agnostic backwards regex scan (no AST). Covers Python, 

5TypeScript, JavaScript, Go, Rust, Ruby, Java, C#, and Markdown. 

6""" 

7 

8from __future__ import annotations 

9 

10import re 

11from pathlib import Path 

12 

13# Each entry: (compiled pattern, kind) 

14# kind is "function", "class", or "section" 

15_ANCHOR_PATTERNS: list[tuple[re.Pattern[str], str]] = [ 

16 # Python / Ruby — def and async def 

17 (re.compile(r"^[ \t]*(?:async\s+)?def\s+(\w+)\s*\("), "function"), 

18 # JS / TS — function declaration or named function expression 

19 ( 

20 re.compile(r"^[ \t]*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+(\w+)\s*\("), 

21 "function", 

22 ), 

23 # JS / TS — const/let/var arrow or assigned function 

24 ( 

25 re.compile( 

26 r"^[ \t]*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\(|function\b)" 

27 ), 

28 "function", 

29 ), 

30 # Go — top-level func and methods (optional receiver before name) 

31 (re.compile(r"^func\s+(?:\([^)]+\)\s+)?(\w+)\s*[(\[]"), "function"), 

32 # Rust — fn (optionally pub, async, unsafe) 

33 ( 

34 re.compile(r"^[ \t]*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*[<(]"), 

35 "function", 

36 ), 

37 # Java / C# heuristic — access-modifier(s) + return-type + name( 

38 ( 

39 re.compile( 

40 r"^[ \t]*(?:(?:public|private|protected|static|final|override|virtual|abstract|async|synchronized)\s+)" 

41 r"{1,4}\w[\w<>\[\]?*]*\s+(\w+)\s*\(" 

42 ), 

43 "function", 

44 ), 

45 # Universal — class / struct / interface / trait / impl / enum 

46 ( 

47 re.compile( 

48 r"^[ \t]*" 

49 r"(?:(?:pub(?:\([^)]*\))?\s+|(?:public|private|protected|abstract|final|sealed|static|export|default)\s+))*" 

50 r"(?:class|struct|interface|trait|impl|enum)\s+(\w+)" 

51 ), 

52 "class", 

53 ), 

54 # Markdown heading (any level, strip trailing hashes) 

55 (re.compile(r"^#{1,6}\s+(.+?)(?:\s+#+)?$"), "section"), 

56] 

57 

58 

59def resolve_anchor(file_path: str, line_number: int) -> str | None: 

60 """Return the enclosing function, class, or section for the given file:line. 

61 

62 Scans backwards from line_number to find the nearest enclosing definition 

63 using language-agnostic regexes. Works for .py, .ts, .js, .go, .rs, .rb, 

64 .java, .cs, .md and any language with recognizable definition syntax. 

65 

66 Args: 

67 file_path: Path to the source file (relative or absolute). 

68 line_number: 1-based line number within the file. 

69 

70 Returns: 

71 A human-readable anchor string such as: 

72 "near function foo" 

73 "near class Bar" 

74 'under section "Title"' 

75 or None if the file cannot be read or no anchor can be resolved. 

76 """ 

77 try: 

78 lines = Path(file_path).read_text(encoding="utf-8", errors="replace").splitlines() 

79 except OSError: 

80 return None 

81 

82 scan_end = min(line_number, len(lines)) 

83 for i in range(scan_end - 1, -1, -1): 

84 for pattern, kind in _ANCHOR_PATTERNS: 

85 m = pattern.match(lines[i]) 

86 if m: 

87 name = m.group(1).strip() 

88 if kind == "section": 

89 return f'under section "{name}"' 

90 return f"near {kind} {name}" 

91 return None