invar-tools 1.10.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.
@@ -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
- return Failure(f"File not found: {file_path}")
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 (LX-06)."""
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("No Python files found")
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 (LX-06).
257
+ """Run map for TypeScript projects.
194
258
 
195
- MVP: Lists symbols without reference counting (Phase 2 can add references).
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("No TypeScript symbols found (files may be empty or contain no exportable symbols)")
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)