invar-tools 1.11.0__py3-none-any.whl → 1.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invar/mcp/handlers.py +28 -0
- invar/mcp/server.py +64 -14
- invar/node_tools/ts-query.js +396 -0
- invar/shell/commands/guard.py +24 -0
- invar/shell/commands/perception.py +302 -6
- invar/shell/py_refs.py +156 -0
- invar/shell/ts_compiler.py +238 -0
- invar/templates/examples/typescript/patterns.md +193 -0
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/METADATA +25 -7
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/RECORD +15 -11
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.11.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -7,6 +7,7 @@ Shell module: handles file I/O for map and sig commands.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
|
+
from dataclasses import dataclass
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import TYPE_CHECKING
|
|
12
13
|
|
|
@@ -70,7 +71,11 @@ def run_sig(target: str, json_output: bool) -> Result[None, str]:
|
|
|
70
71
|
|
|
71
72
|
file_path = Path(file_path_str)
|
|
72
73
|
if not file_path.exists():
|
|
73
|
-
|
|
74
|
+
# DX-78 Phase B: Suggest alternative tools
|
|
75
|
+
return Failure(
|
|
76
|
+
f"File not found: {file_path}\n\n"
|
|
77
|
+
"💡 Try using Grep to search for the symbol across the codebase."
|
|
78
|
+
)
|
|
74
79
|
|
|
75
80
|
# Read file content
|
|
76
81
|
try:
|
|
@@ -122,7 +127,59 @@ def _run_sig_python(
|
|
|
122
127
|
def _run_sig_typescript(
|
|
123
128
|
content: str, file_path: Path, symbol_name: str | None, json_output: bool
|
|
124
129
|
) -> Result[None, str]:
|
|
125
|
-
"""Run sig for TypeScript files
|
|
130
|
+
"""Run sig for TypeScript files.
|
|
131
|
+
|
|
132
|
+
DX-78: Uses TS Compiler API when available, falls back to regex parser.
|
|
133
|
+
"""
|
|
134
|
+
from invar.shell.ts_compiler import is_typescript_available, run_sig_typescript
|
|
135
|
+
|
|
136
|
+
# Try TS Compiler API first (DX-78)
|
|
137
|
+
if is_typescript_available():
|
|
138
|
+
sig_result = run_sig_typescript(file_path)
|
|
139
|
+
if isinstance(sig_result, Success):
|
|
140
|
+
symbols = sig_result.unwrap()
|
|
141
|
+
|
|
142
|
+
# Filter by symbol name if specified
|
|
143
|
+
if symbol_name:
|
|
144
|
+
symbols = [s for s in symbols if s.name == symbol_name]
|
|
145
|
+
if not symbols:
|
|
146
|
+
return Failure(f"Symbol '{symbol_name}' not found in {file_path}")
|
|
147
|
+
|
|
148
|
+
# Output using TS Compiler API format
|
|
149
|
+
if json_output:
|
|
150
|
+
output = {
|
|
151
|
+
"file": str(file_path),
|
|
152
|
+
"symbols": [
|
|
153
|
+
{
|
|
154
|
+
"name": s.name,
|
|
155
|
+
"kind": s.kind,
|
|
156
|
+
"signature": s.signature,
|
|
157
|
+
"line": s.line,
|
|
158
|
+
"contracts": s.contracts,
|
|
159
|
+
"members": s.members,
|
|
160
|
+
}
|
|
161
|
+
for s in symbols
|
|
162
|
+
],
|
|
163
|
+
}
|
|
164
|
+
console.print(json.dumps(output, indent=2))
|
|
165
|
+
else:
|
|
166
|
+
console.print(f"[bold]{file_path}[/bold]")
|
|
167
|
+
for s in symbols:
|
|
168
|
+
console.print(f" [{s.kind}] {s.name}")
|
|
169
|
+
console.print(f" {s.signature}")
|
|
170
|
+
if s.contracts:
|
|
171
|
+
for pre in s.contracts.get("pre", []):
|
|
172
|
+
console.print(f" @pre {pre}")
|
|
173
|
+
for post in s.contracts.get("post", []):
|
|
174
|
+
console.print(f" @post {post}")
|
|
175
|
+
if s.members:
|
|
176
|
+
for m in s.members:
|
|
177
|
+
console.print(f" [{m['kind']}] {m['name']}: {m.get('signature', '')}")
|
|
178
|
+
console.print()
|
|
179
|
+
|
|
180
|
+
return Success(None)
|
|
181
|
+
|
|
182
|
+
# Fallback to regex parser (LX-06 legacy)
|
|
126
183
|
from invar.core.ts_sig_parser import (
|
|
127
184
|
extract_ts_signatures,
|
|
128
185
|
format_ts_signatures_json,
|
|
@@ -172,7 +229,14 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
|
|
|
172
229
|
continue
|
|
173
230
|
|
|
174
231
|
if not file_infos:
|
|
175
|
-
return Failure(
|
|
232
|
+
return Failure(
|
|
233
|
+
"No Python symbols found.\n\n"
|
|
234
|
+
"Available tools:\n"
|
|
235
|
+
"- invar sig <file.py> — Extract signatures\n"
|
|
236
|
+
"- invar refs <file.py>::Symbol — Find references\n"
|
|
237
|
+
"- invar_doc_* — Document navigation\n"
|
|
238
|
+
"- invar_guard — Static verification"
|
|
239
|
+
)
|
|
176
240
|
|
|
177
241
|
# Build perception map
|
|
178
242
|
perception_map = build_perception_map(file_infos, sources, str(path.absolute()))
|
|
@@ -190,10 +254,47 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
|
|
|
190
254
|
|
|
191
255
|
# @shell_complexity: TypeScript map with file discovery and symbol extraction
|
|
192
256
|
def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[None, str]:
|
|
193
|
-
"""Run map for TypeScript projects
|
|
257
|
+
"""Run map for TypeScript projects.
|
|
194
258
|
|
|
195
|
-
|
|
259
|
+
DX-78: Uses TS Compiler API when available, falls back to regex parser.
|
|
196
260
|
"""
|
|
261
|
+
from invar.shell.ts_compiler import is_typescript_available, run_map_typescript
|
|
262
|
+
|
|
263
|
+
# Try TS Compiler API first (DX-78)
|
|
264
|
+
if is_typescript_available():
|
|
265
|
+
map_result = run_map_typescript(path, top_n)
|
|
266
|
+
if isinstance(map_result, Success):
|
|
267
|
+
data = map_result.unwrap()
|
|
268
|
+
|
|
269
|
+
if not data.get("symbols"):
|
|
270
|
+
return Failure(
|
|
271
|
+
"No TypeScript symbols found.\n\n"
|
|
272
|
+
"Available tools:\n"
|
|
273
|
+
"- invar sig <file.ts> — Extract signatures\n"
|
|
274
|
+
"- invar refs <file.ts>::Symbol — Find references\n"
|
|
275
|
+
"- invar_doc_* — Document navigation\n"
|
|
276
|
+
"- invar_guard — Static verification"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Output using TS Compiler API format
|
|
280
|
+
if json_output:
|
|
281
|
+
output = {
|
|
282
|
+
"language": "typescript",
|
|
283
|
+
"total_symbols": data.get("total", len(data["symbols"])),
|
|
284
|
+
"symbols": data["symbols"],
|
|
285
|
+
}
|
|
286
|
+
console.print(json.dumps(output, indent=2))
|
|
287
|
+
else:
|
|
288
|
+
console.print("[bold]TypeScript Symbol Map[/bold]")
|
|
289
|
+
console.print(f"Total symbols: {data.get('total', len(data['symbols']))}\n")
|
|
290
|
+
for sym in data["symbols"]:
|
|
291
|
+
console.print(f"[{sym['kind']}] {sym['name']}")
|
|
292
|
+
console.print(f" {sym['file']}:{sym['line']}")
|
|
293
|
+
console.print()
|
|
294
|
+
|
|
295
|
+
return Success(None)
|
|
296
|
+
|
|
297
|
+
# Fallback to regex parser (LX-06 legacy)
|
|
197
298
|
from invar.core.ts_sig_parser import TSSymbol, extract_ts_signatures
|
|
198
299
|
from invar.shell.fs import discover_typescript_files
|
|
199
300
|
|
|
@@ -213,7 +314,14 @@ def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[Non
|
|
|
213
314
|
continue
|
|
214
315
|
|
|
215
316
|
if not all_symbols:
|
|
216
|
-
return Failure(
|
|
317
|
+
return Failure(
|
|
318
|
+
"No TypeScript symbols found.\n\n"
|
|
319
|
+
"Available tools:\n"
|
|
320
|
+
"- invar sig <file.ts> — Extract signatures\n"
|
|
321
|
+
"- invar refs <file.ts>::Symbol — Find references\n"
|
|
322
|
+
"- invar_doc_* — Document navigation\n"
|
|
323
|
+
"- invar_guard — Static verification"
|
|
324
|
+
)
|
|
217
325
|
|
|
218
326
|
# Sort by kind priority (function/class first), then by name
|
|
219
327
|
kind_order = {"function": 0, "class": 1, "interface": 2, "type": 3, "const": 4, "method": 5}
|
|
@@ -249,3 +357,191 @@ def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[Non
|
|
|
249
357
|
console.print()
|
|
250
358
|
|
|
251
359
|
return Success(None)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# @shell_complexity: Reference finding with multi-language support and output formatting
|
|
363
|
+
def run_refs(target: str, json_output: bool) -> Result[None, str]:
|
|
364
|
+
"""Find all references to a symbol.
|
|
365
|
+
|
|
366
|
+
Target format: "path/to/file.py::symbol_name" or "path/to/file.ts::symbol_name"
|
|
367
|
+
DX-78: Supports both Python (via jedi) and TypeScript (via TS Compiler API).
|
|
368
|
+
"""
|
|
369
|
+
# Parse target
|
|
370
|
+
if "::" not in target:
|
|
371
|
+
return Failure(
|
|
372
|
+
"Invalid target format.\n\n"
|
|
373
|
+
"Expected: path/to/file.py::symbol_name\n"
|
|
374
|
+
"Example: src/auth.py::validate_token"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
file_part, symbol_name = target.rsplit("::", 1)
|
|
378
|
+
file_path = Path(file_part)
|
|
379
|
+
|
|
380
|
+
if not file_path.exists():
|
|
381
|
+
return Failure(f"File not found: {file_path}")
|
|
382
|
+
|
|
383
|
+
suffix = file_path.suffix.lower()
|
|
384
|
+
|
|
385
|
+
# Route to language-specific implementation
|
|
386
|
+
if suffix in (".ts", ".tsx"):
|
|
387
|
+
return _run_refs_typescript(file_path, symbol_name, json_output)
|
|
388
|
+
elif suffix in (".py", ".pyi"):
|
|
389
|
+
return _run_refs_python(file_path, symbol_name, json_output)
|
|
390
|
+
else:
|
|
391
|
+
return Failure(
|
|
392
|
+
f"Unsupported file type: {suffix}\n\n"
|
|
393
|
+
"Supported: .py, .pyi, .ts, .tsx"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# @shell_complexity: Reference finding with output formatting and error handling
|
|
398
|
+
def _run_refs_python(
|
|
399
|
+
file_path: Path, symbol_name: str, json_output: bool
|
|
400
|
+
) -> Result[None, str]:
|
|
401
|
+
"""Find references in Python using jedi."""
|
|
402
|
+
from invar.shell.py_refs import find_all_references_to_symbol
|
|
403
|
+
|
|
404
|
+
# Find project root
|
|
405
|
+
project_root = file_path.parent
|
|
406
|
+
for parent in file_path.parents:
|
|
407
|
+
if (parent / "pyproject.toml").exists() or (parent / "setup.py").exists():
|
|
408
|
+
project_root = parent
|
|
409
|
+
break
|
|
410
|
+
|
|
411
|
+
refs = find_all_references_to_symbol(file_path, symbol_name, project_root)
|
|
412
|
+
|
|
413
|
+
if not refs:
|
|
414
|
+
return Failure(f"Symbol '{symbol_name}' not found in {file_path}")
|
|
415
|
+
|
|
416
|
+
# Output
|
|
417
|
+
if json_output:
|
|
418
|
+
output = {
|
|
419
|
+
"target": str(file_path) + "::" + symbol_name,
|
|
420
|
+
"total": len(refs),
|
|
421
|
+
"references": [
|
|
422
|
+
{
|
|
423
|
+
"file": str(ref.file.relative_to(project_root))
|
|
424
|
+
if ref.file.is_relative_to(project_root)
|
|
425
|
+
else str(ref.file),
|
|
426
|
+
"line": ref.line,
|
|
427
|
+
"column": ref.column,
|
|
428
|
+
"context": ref.context,
|
|
429
|
+
"is_definition": ref.is_definition,
|
|
430
|
+
}
|
|
431
|
+
for ref in refs
|
|
432
|
+
],
|
|
433
|
+
}
|
|
434
|
+
console.print(json.dumps(output, indent=2))
|
|
435
|
+
else:
|
|
436
|
+
console.print(f"[bold]References to {symbol_name}[/bold]")
|
|
437
|
+
console.print(f"Found {len(refs)} reference(s)\n")
|
|
438
|
+
|
|
439
|
+
for ref in refs:
|
|
440
|
+
rel_path = (
|
|
441
|
+
ref.file.relative_to(project_root)
|
|
442
|
+
if ref.file.is_relative_to(project_root)
|
|
443
|
+
else ref.file
|
|
444
|
+
)
|
|
445
|
+
marker = " [definition]" if ref.is_definition else ""
|
|
446
|
+
console.print(f"{rel_path}:{ref.line}{marker}")
|
|
447
|
+
if ref.context:
|
|
448
|
+
console.print(f" {ref.context}")
|
|
449
|
+
console.print()
|
|
450
|
+
|
|
451
|
+
return Success(None)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@dataclass
|
|
455
|
+
class _SymbolPosition:
|
|
456
|
+
"""Temporary holder for symbol position during refs lookup."""
|
|
457
|
+
line: int
|
|
458
|
+
column: int
|
|
459
|
+
name: str
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
# @shell_complexity: TypeScript refs with symbol lookup and output formatting
|
|
463
|
+
def _run_refs_typescript(
|
|
464
|
+
file_path: Path, symbol_name: str, json_output: bool
|
|
465
|
+
) -> Result[None, str]:
|
|
466
|
+
"""Find references in TypeScript using TS Compiler API."""
|
|
467
|
+
from invar.shell.ts_compiler import is_typescript_available, run_refs_typescript
|
|
468
|
+
|
|
469
|
+
if not is_typescript_available():
|
|
470
|
+
return Failure(
|
|
471
|
+
"TypeScript tools not available.\n\n"
|
|
472
|
+
"Requirements:\n"
|
|
473
|
+
"- Node.js installed\n"
|
|
474
|
+
"- tsconfig.json in project root"
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# First, find the symbol's position using sig command
|
|
478
|
+
from invar.shell.ts_compiler import run_sig_typescript
|
|
479
|
+
|
|
480
|
+
sig_result = run_sig_typescript(file_path)
|
|
481
|
+
if isinstance(sig_result, Failure):
|
|
482
|
+
return sig_result
|
|
483
|
+
|
|
484
|
+
symbols = sig_result.unwrap()
|
|
485
|
+
symbol = next((s for s in symbols if s.name == symbol_name), None)
|
|
486
|
+
|
|
487
|
+
if symbol is None:
|
|
488
|
+
# Check class members
|
|
489
|
+
for s in symbols:
|
|
490
|
+
if s.members:
|
|
491
|
+
for member in s.members:
|
|
492
|
+
if member.get("name") == symbol_name:
|
|
493
|
+
# Extract column if available, default to 0
|
|
494
|
+
column = member.get("column", 0)
|
|
495
|
+
symbol = _SymbolPosition(
|
|
496
|
+
line=member["line"],
|
|
497
|
+
column=column,
|
|
498
|
+
name=symbol_name
|
|
499
|
+
)
|
|
500
|
+
break
|
|
501
|
+
if symbol:
|
|
502
|
+
break
|
|
503
|
+
|
|
504
|
+
if symbol is None:
|
|
505
|
+
return Failure(f"Symbol '{symbol_name}' not found in {file_path}")
|
|
506
|
+
|
|
507
|
+
# Find references using position
|
|
508
|
+
# Use symbol.column if available (from member dict), defaults to 0
|
|
509
|
+
column = getattr(symbol, "column", 0)
|
|
510
|
+
refs_result = run_refs_typescript(file_path, symbol.line, column)
|
|
511
|
+
if isinstance(refs_result, Failure):
|
|
512
|
+
return refs_result
|
|
513
|
+
|
|
514
|
+
refs = refs_result.unwrap()
|
|
515
|
+
|
|
516
|
+
if not refs:
|
|
517
|
+
return Failure(f"No references found for '{symbol_name}'")
|
|
518
|
+
|
|
519
|
+
# Output (refs already have relative paths from ts-query.js)
|
|
520
|
+
if json_output:
|
|
521
|
+
output = {
|
|
522
|
+
"target": str(file_path) + "::" + symbol_name,
|
|
523
|
+
"total": len(refs),
|
|
524
|
+
"references": [
|
|
525
|
+
{
|
|
526
|
+
"file": ref.file,
|
|
527
|
+
"line": ref.line,
|
|
528
|
+
"column": ref.column,
|
|
529
|
+
"context": ref.context,
|
|
530
|
+
"is_definition": ref.is_definition,
|
|
531
|
+
}
|
|
532
|
+
for ref in refs
|
|
533
|
+
],
|
|
534
|
+
}
|
|
535
|
+
console.print(json.dumps(output, indent=2))
|
|
536
|
+
else:
|
|
537
|
+
console.print(f"[bold]References to {symbol_name}[/bold]")
|
|
538
|
+
console.print(f"Found {len(refs)} reference(s)\n")
|
|
539
|
+
|
|
540
|
+
for ref in refs:
|
|
541
|
+
marker = " [definition]" if ref.is_definition else ""
|
|
542
|
+
console.print(f"{ref.file}:{ref.line}{marker}")
|
|
543
|
+
if ref.context:
|
|
544
|
+
console.print(f" {ref.context}")
|
|
545
|
+
console.print()
|
|
546
|
+
|
|
547
|
+
return Success(None)
|
invar/shell/py_refs.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Python reference finding using jedi.
|
|
2
|
+
|
|
3
|
+
DX-78: Provides cross-file reference finding for Python symbols.
|
|
4
|
+
Shell module: Uses jedi library for I/O-based symbol analysis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import jedi
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Reference:
|
|
17
|
+
"""A reference to a Python symbol."""
|
|
18
|
+
|
|
19
|
+
file: Path
|
|
20
|
+
line: int
|
|
21
|
+
column: int
|
|
22
|
+
context: str
|
|
23
|
+
is_definition: bool = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# @shell_complexity: Reference finding with jedi library and error handling
|
|
27
|
+
def find_references(
|
|
28
|
+
file_path: Path,
|
|
29
|
+
line: int,
|
|
30
|
+
column: int,
|
|
31
|
+
project_root: Path | None = None,
|
|
32
|
+
) -> list[Reference]:
|
|
33
|
+
"""Find all references to symbol at position using jedi.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
file_path: File containing the symbol
|
|
37
|
+
line: 1-based line number
|
|
38
|
+
column: 0-based column number
|
|
39
|
+
project_root: Project root for cross-file resolution
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List of references found
|
|
43
|
+
|
|
44
|
+
>>> from pathlib import Path
|
|
45
|
+
>>> import tempfile, os
|
|
46
|
+
>>> # Test with a simple Python file
|
|
47
|
+
>>> with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
48
|
+
... _ = f.write('def hello():\\n pass\\n')
|
|
49
|
+
... temp_file = Path(f.name)
|
|
50
|
+
>>> # Find references returns a list (may be empty if jedi not configured)
|
|
51
|
+
>>> refs = find_references(temp_file, 1, 4)
|
|
52
|
+
>>> isinstance(refs, list)
|
|
53
|
+
True
|
|
54
|
+
>>> os.unlink(temp_file)
|
|
55
|
+
"""
|
|
56
|
+
source = file_path.read_text(encoding="utf-8")
|
|
57
|
+
|
|
58
|
+
project = None
|
|
59
|
+
if project_root:
|
|
60
|
+
project = jedi.Project(path=str(project_root))
|
|
61
|
+
|
|
62
|
+
script = jedi.Script(source, path=str(file_path), project=project)
|
|
63
|
+
refs = script.get_references(line, column)
|
|
64
|
+
|
|
65
|
+
results: list[Reference] = []
|
|
66
|
+
for ref in refs:
|
|
67
|
+
# Skip builtins (no module_path)
|
|
68
|
+
if not ref.module_path:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
line_code = ref.get_line_code()
|
|
72
|
+
context = line_code.strip() if line_code else ""
|
|
73
|
+
|
|
74
|
+
results.append(
|
|
75
|
+
Reference(
|
|
76
|
+
file=Path(ref.module_path),
|
|
77
|
+
line=ref.line,
|
|
78
|
+
column=ref.column,
|
|
79
|
+
context=context,
|
|
80
|
+
is_definition=ref.is_definition(),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return results
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# @shell_complexity: Symbol search using jedi library
|
|
88
|
+
def find_symbol_position(file_path: Path, symbol_name: str) -> tuple[int, int] | None:
|
|
89
|
+
"""Find the position of a symbol definition in a file.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
file_path: File to search
|
|
93
|
+
symbol_name: Name of the symbol to find
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Tuple of (line, column) or None if not found
|
|
97
|
+
|
|
98
|
+
>>> from pathlib import Path
|
|
99
|
+
>>> import tempfile, os
|
|
100
|
+
>>> # Test finding a function definition
|
|
101
|
+
>>> with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
102
|
+
... _ = f.write('def test_func():\\n return 42\\n')
|
|
103
|
+
... temp_file = Path(f.name)
|
|
104
|
+
>>> pos = find_symbol_position(temp_file, "test_func")
|
|
105
|
+
>>> isinstance(pos, tuple) or pos is None # Returns tuple or None
|
|
106
|
+
True
|
|
107
|
+
>>> os.unlink(temp_file)
|
|
108
|
+
"""
|
|
109
|
+
source = file_path.read_text(encoding="utf-8")
|
|
110
|
+
script = jedi.Script(source, path=str(file_path))
|
|
111
|
+
|
|
112
|
+
# Get all names defined in the file
|
|
113
|
+
names = script.get_names(all_scopes=True)
|
|
114
|
+
|
|
115
|
+
for name in names:
|
|
116
|
+
if name.name == symbol_name and name.is_definition():
|
|
117
|
+
return (name.line, name.column)
|
|
118
|
+
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# @shell_complexity: Combines symbol position lookup and reference finding
|
|
123
|
+
def find_all_references_to_symbol(
|
|
124
|
+
file_path: Path,
|
|
125
|
+
symbol_name: str,
|
|
126
|
+
project_root: Path | None = None,
|
|
127
|
+
) -> list[Reference]:
|
|
128
|
+
"""Find all references to a named symbol.
|
|
129
|
+
|
|
130
|
+
Convenience function that combines find_symbol_position and find_references.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
file_path: File containing the symbol definition
|
|
134
|
+
symbol_name: Name of the symbol
|
|
135
|
+
project_root: Project root for cross-file resolution
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of references found
|
|
139
|
+
|
|
140
|
+
>>> from pathlib import Path
|
|
141
|
+
>>> import tempfile, os
|
|
142
|
+
>>> # Test finding all references to a symbol
|
|
143
|
+
>>> with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
144
|
+
... _ = f.write('def greet():\\n pass\\ngreet()\\n')
|
|
145
|
+
... temp_file = Path(f.name)
|
|
146
|
+
>>> refs = find_all_references_to_symbol(temp_file, "greet")
|
|
147
|
+
>>> isinstance(refs, list) # Returns list of references
|
|
148
|
+
True
|
|
149
|
+
>>> os.unlink(temp_file)
|
|
150
|
+
"""
|
|
151
|
+
position = find_symbol_position(file_path, symbol_name)
|
|
152
|
+
if position is None:
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
line, column = position
|
|
156
|
+
return find_references(file_path, line, column, project_root)
|