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
« 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.
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"""
8from __future__ import annotations
10import re
11from pathlib import Path
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]
59def resolve_anchor(file_path: str, line_number: int) -> str | None:
60 """Return the enclosing function, class, or section for the given file:line.
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.
66 Args:
67 file_path: Path to the source file (relative or absolute).
68 line_number: 1-based line number within the file.
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
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