loctree 0.8.16

Structural code intelligence for AI agents. Scan once, query everything.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
//! Core types for loctree analysis.
//!
//! This module defines the fundamental data structures used throughout loctree:
//! - [`FileAnalysis`] - Per-file analysis result (imports, exports, commands)
//! - [`ImportEntry`] / [`ExportSymbol`] - Import/export representations
//! - [`CommandRef`] / [`EventRef`] - Tauri command and event tracking
//! - [`Mode`] - CLI operation modes
//! - [`Options`] - Analysis configuration

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

/// Default LOC threshold for "large file" warnings.
pub const DEFAULT_LOC_THRESHOLD: usize = 1000;

/// ANSI escape code for red text.
pub const COLOR_RED: &str = "\u{001b}[31m";

/// ANSI escape code to reset text color.
pub const COLOR_RESET: &str = "\u{001b}[0m";

/// Terminal color mode.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum ColorMode {
    /// Detect TTY and colorize if interactive.
    #[default]
    Auto,
    /// Always use ANSI colors.
    Always,
    /// Never use colors (for piping/CI).
    Never,
}

/// Output format for analysis results.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum OutputMode {
    /// Human-readable text with colors and formatting.
    Human,
    /// Pretty-printed JSON object.
    Json,
    /// Newline-delimited JSON (one object per line).
    Jsonl,
}

/// CLI operation mode - determines what loctree does.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Mode {
    /// Display directory tree with LOC counts (default without -A).
    Tree,
    /// Full import/export analysis (-A flag).
    AnalyzeImports,
    /// Initialize/update snapshot (scan once).
    Init,
    /// Holographic Slice - extract file + deps + consumers for AI context.
    Slice,
    /// Trace a handler - show full investigation path and WHY it's unused/missing.
    Trace,
    /// AI-optimized JSON output with quick wins and slice references.
    ForAi,
    /// Output the canonical findings artifact to stdout.
    Findings,
    /// Output findings summary only to stdout.
    Summary,
    /// Git awareness - temporal knowledge from repository history.
    Git(GitSubcommand),
    /// Unified search - returns symbol matches, semantic matches, dead status.
    Search,
}

/// Git subcommands for temporal awareness - semantic analysis only (no passthrough)
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum GitSubcommand {
    /// Semantic diff between two commits (snapshot comparison)
    /// Shows: files changed, graph delta, exports delta, dead code delta, impact analysis
    Compare {
        /// Starting commit (e.g., "HEAD~1", "abc123")
        from: String,
        /// Ending commit, defaults to current working tree if None
        to: Option<String>,
    },
    /// Symbol-level blame: which commit introduced each symbol/import
    Blame {
        /// File to analyze
        file: String,
    },
    /// Track evolution of a symbol or file's structure over time
    History {
        /// Symbol name to track (e.g., "processUser")
        symbol: Option<String>,
        /// File path to track
        file: Option<String>,
        /// Maximum number of commits to show
        limit: usize,
    },
    /// Find when a pattern was introduced (circular import, dead code, etc.)
    WhenIntroduced {
        /// Circular import pattern (e.g., "src/a.rs <-> src/b.rs")
        circular: Option<String>,
        /// Dead code symbol (e.g., "src/utils.rs::unused_fn")
        dead: Option<String>,
        /// Import source (e.g., "lodash")
        import: Option<String>,
    },
}

/// Analysis configuration options.
///
/// Controls file filtering, output format, and analysis behavior.
#[derive(Clone)]
pub struct Options {
    /// File extensions to include (None = all supported).
    pub extensions: Option<HashSet<String>>,
    /// Paths to exclude from analysis.
    pub ignore_paths: Vec<std::path::PathBuf>,
    /// Optional glob-based ignore rules (compiled from ignore patterns).
    ///
    /// This complements `ignore_paths`:
    /// - `ignore_paths` is fast prefix matching (best for literal directories)
    /// - `ignore_globs` enables patterns like `**/index.*` or `*.log`
    pub ignore_globs: Option<std::sync::Arc<globset::GlobSet>>,
    /// Respect .gitignore rules.
    pub use_gitignore: bool,
    /// Maximum directory depth for tree view.
    pub max_depth: Option<usize>,
    /// Terminal color mode.
    pub color: ColorMode,
    /// Output format (Human, Json, Jsonl).
    pub output: OutputMode,
    /// Show summary statistics.
    pub summary: bool,
    /// Max items in summary lists.
    pub summary_limit: usize,
    /// If true, only show summary/top entries (suppress full tree dump).
    pub summary_only: bool,
    /// Include dotfiles/directories.
    pub show_hidden: bool,
    /// Include gitignored files.
    pub show_ignored: bool,
    /// LOC threshold for "large file" warnings.
    pub loc_threshold: usize,
    /// Max files to analyze (0 = unlimited).
    pub analyze_limit: usize,
    /// Path for HTML report output.
    pub report_path: Option<std::path::PathBuf>,
    /// Start local server for HTML report.
    pub serve: bool,
    /// Editor command for click-to-open (e.g., "code -g").
    pub editor_cmd: Option<String>,
    /// Max nodes in dependency graph.
    pub max_graph_nodes: Option<usize>,
    /// Max edges in dependency graph.
    pub max_graph_edges: Option<usize>,
    /// Enable verbose logging.
    pub verbose: bool,
    /// Scan all files (ignore incremental cache).
    pub scan_all: bool,
    /// Symbol to search for (--symbol flag).
    pub symbol: Option<String>,
    /// File for impact analysis (--impact flag).
    pub impact: Option<String>,
    /// Detect build artifacts (node_modules, target, etc.).
    pub find_artifacts: bool,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            extensions: None,
            ignore_paths: Vec::new(),
            ignore_globs: None,
            use_gitignore: true,
            max_depth: None,
            color: ColorMode::Auto,
            output: OutputMode::Human,
            summary: false,
            summary_limit: 50,
            summary_only: false,
            show_hidden: false,
            show_ignored: false,
            loc_threshold: 500,
            analyze_limit: 100,
            report_path: None,
            serve: false,
            editor_cmd: None,
            max_graph_nodes: None,
            max_graph_edges: None,
            verbose: false,
            scan_all: false,
            symbol: None,
            impact: None,
            find_artifacts: false,
        }
    }
}

/// A single line in the tree output (file or directory).
pub struct LineEntry {
    /// Display label (filename with tree prefix).
    pub label: String,
    /// Lines of code (None for directories without aggregation).
    pub loc: Option<usize>,
    /// Path relative to scan root.
    pub relative_path: String,
    /// True if this is a directory.
    pub is_dir: bool,
    /// True if LOC exceeds threshold.
    pub is_large: bool,
}

/// A symbol match from search/grep operations.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SymbolMatch {
    /// 1-based line number.
    pub line: usize,
    /// Line content with match highlighted.
    pub context: String,
}

/// A locally-defined symbol (non-exported or imported).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalSymbol {
    /// Symbol name as defined in the file.
    pub name: String,
    /// Symbol kind (function, class, variable, type, import, etc.).
    pub kind: String,
    /// 1-based line number of definition (if known).
    #[serde(default)]
    pub line: Option<usize>,
    /// Source line context for the definition (trimmed).
    #[serde(default)]
    pub context: String,
    /// True if this symbol is exported.
    #[serde(default)]
    pub is_exported: bool,
}

/// A usage site for a symbol within a file.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SymbolUsage {
    /// Symbol name as referenced in code.
    pub name: String,
    /// 1-based line number of usage.
    pub line: usize,
    /// Source line context for the usage (trimmed).
    #[serde(default)]
    pub context: String,
}

/// A file exceeding the LOC threshold.
pub struct LargeEntry {
    /// Relative path to file.
    pub path: String,
    /// Lines of code.
    pub loc: usize,
}

/// Aggregated scan statistics.
#[derive(Default)]
pub struct Stats {
    /// Total directories scanned.
    pub directories: usize,
    /// Total files scanned.
    pub files: usize,
    /// Files with countable LOC.
    pub files_with_loc: usize,
    /// Sum of all LOC.
    pub total_loc: usize,
}

/// Mutable collectors passed through tree traversal.
pub struct Collectors<'a> {
    /// Tree entries for display.
    pub entries: &'a mut Vec<LineEntry>,
    /// Files exceeding LOC threshold.
    pub large_entries: &'a mut Vec<LargeEntry>,
    /// Running statistics.
    pub stats: &'a mut Stats,
}

/// An import statement (JS/TS/Python).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ImportEntry {
    /// 1-based line number of the import declaration (if known).
    #[serde(default)]
    pub line: Option<usize>,
    /// Resolved/normalized source path.
    pub source: String,
    /// Original source as written in code.
    pub source_raw: String,
    /// Import type (static, side-effect, dynamic).
    pub kind: ImportKind,
    /// Absolute resolved path (if local file).
    pub resolved_path: Option<String>,
    /// True if bare specifier (npm package, not relative).
    pub is_bare: bool,
    /// Imported symbols (named, default, namespace).
    pub symbols: Vec<ImportSymbol>,
    /// Resolution result (local, stdlib, dynamic, unknown).
    pub resolution: ImportResolutionKind,
    /// True if inside TYPE_CHECKING block (Python).
    pub is_type_checking: bool,
    /// True if placed inside a function/method (lazy import to break cycles).
    #[serde(default)]
    pub is_lazy: bool,
    /// True if import starts with `crate::` (Rust only).
    #[serde(default)]
    pub is_crate_relative: bool,
    /// True if import starts with `super::` (Rust only).
    #[serde(default)]
    pub is_super_relative: bool,
    /// True if import starts with `self::` (Rust only).
    #[serde(default)]
    pub is_self_relative: bool,
    /// True if this is a Rust `mod foo;` declaration (not a true import).
    #[serde(default)]
    pub is_mod_declaration: bool,
    /// Original raw path before resolution (Rust only).
    #[serde(default)]
    pub raw_path: String,
}

/// Type of import statement.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ImportKind {
    /// `import X from 'y'` or `from x import y`
    Static,
    /// `import type { X } from 'y'` (TypeScript-only, still a real dependency)
    Type,
    /// `import 'styles.css'` (no bindings)
    SideEffect,
    /// `import('module')` or `React.lazy(() => import(...))`
    Dynamic,
}

/// How an import source was resolved.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ImportResolutionKind {
    /// Local file (relative or absolute path).
    Local,
    /// Standard library module.
    Stdlib,
    /// Dynamic import (path unknown at parse time).
    Dynamic,
    /// Could not resolve.
    Unknown,
}

/// A single symbol from an import statement.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ImportSymbol {
    /// Original name in source module.
    pub name: String,
    /// Local alias (e.g., `import { foo as bar }`).
    pub alias: Option<String>,
    /// True if default import (`import Foo from './bar'`).
    #[serde(default)]
    pub is_default: bool,
}

/// A re-export statement (`export { x } from './y'` or `export * from './z'`).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ReexportEntry {
    /// Source module path.
    pub source: String,
    /// Star or named re-export.
    pub kind: ReexportKind,
    /// Resolved absolute path (if local).
    pub resolved: Option<String>,
}

/// Type of re-export.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ReexportKind {
    /// `export * from './module'`
    Star,
    /// `export { a, b as c } from './module'`
    /// Each tuple is (original_name, exported_name) - same if no alias
    Named(Vec<(String, String)>),
}

/// Parameter information for function exports.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ParamInfo {
    /// Parameter name.
    pub name: String,
    /// Type annotation if present (e.g., "string", "int", "&str").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub type_annotation: Option<String>,
    /// Whether parameter has a default value.
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    pub has_default: bool,
}

/// An exported symbol from a module.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExportSymbol {
    /// Exported name (may differ from internal name).
    pub name: String,
    /// Symbol kind: "function", "class", "const", "type", etc.
    pub kind: String,
    /// Export type: "named", "default", "reexport".
    pub export_type: String,
    /// 1-based line number of declaration.
    pub line: Option<usize>,
    /// Function parameters (empty for non-functions).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub params: Vec<ParamInfo>,
}

/// A Tauri command reference (handler or invocation).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommandRef {
    /// Command function name (Rust side).
    pub name: String,
    /// Exposed name if different (e.g., via `#[tauri::command(rename_all = ...)]`).
    pub exposed_name: Option<String>,
    /// 1-based line number.
    pub line: usize,
    /// Generic type parameter (e.g., `State<AppState>`).
    pub generic_type: Option<String>,
    /// Payload type/shape if detected.
    pub payload: Option<String>,
    /// Plugin name for Tauri plugin commands (e.g., "window" from `invoke('plugin:window|set_icon')`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub plugin_name: Option<String>,
}

/// Casing inconsistency in command payload keys.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommandPayloadCasing {
    /// Command name.
    pub command: String,
    /// Key with inconsistent casing.
    pub key: String,
    /// File path.
    pub path: String,
    /// 1-based line number.
    pub line: usize,
}

/// JS/TS string literal captured for dynamic/registry awareness
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct StringLiteral {
    pub value: String,
    pub line: usize,
}

/// Python/Backend route declaration (FastAPI/Flask/etc.)
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct RouteInfo {
    /// Framework label (e.g., "fastapi", "flask")
    pub framework: String,
    /// HTTP method or decorator kind (GET/POST/route/etc.)
    pub method: String,
    /// Route path if extracted from decorator
    #[serde(skip_serializing_if = "Option::is_none")]
    pub path: Option<String>,
    /// Handler name (set when attached to a def)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// 1-based line number of the decorator
    pub line: usize,
}

/// Python exec/eval/compile dynamic code generation pattern.
/// Tracks template strings (e.g., "get%s", "set%s") passed to exec() that generate symbols dynamically.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DynamicExecTemplate {
    /// Template pattern containing placeholders (%s, %d, {name}, etc.)
    pub template: String,
    /// Generated symbol names extracted from the template (e.g., ["get", "set"] from "get%s", "set%s")
    pub generated_prefixes: Vec<String>,
    /// 1-based line number where exec/eval/compile is called
    pub line: usize,
    /// Type of dynamic call: "exec", "eval", or "compile"
    pub call_type: String,
}

/// Python sys.modules monkey-patching pattern.
/// Detects when a module injects itself into sys.modules under a different name,
/// making all its exports accessible at runtime (should not be flagged as dead).
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SysModulesInjection {
    /// The module name being injected (e.g., "compat", "shim", or "__name__")
    pub module_name: String,
    /// 1-based line number where injection occurs
    pub line: usize,
    /// The value being assigned (variable name or expression)
    pub value: String,
}

/// A Tauri event reference (emit or listen).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventRef {
    /// Original event name as written (may be const reference).
    pub raw_name: Option<String>,
    /// Resolved event name.
    pub name: String,
    /// 1-based line number.
    pub line: usize,
    /// "emit" or "listen".
    pub kind: String,
    /// True if awaited (`await emit(...)`).
    pub awaited: bool,
    /// Payload type/shape if detected.
    pub payload: Option<String>,
    /// True if this event uses a dynamic pattern (format!/template literal).
    #[serde(default)]
    pub is_dynamic: bool,
}

/// Python concurrency race indicator
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PyRaceIndicator {
    /// Line number where the pattern was found
    pub line: usize,
    /// Type of concurrency pattern: "threading", "asyncio", "multiprocessing"
    pub concurrency_type: String,
    /// Specific pattern: "Thread", "Lock", "gather", "create_task", "Pool", etc.
    pub pattern: String,
    /// Risk level: "info", "warning", "high"
    pub risk: String,
    /// Description of the potential issue
    pub message: String,
}

/// Per-file analysis result.
///
/// Contains all extracted information from a single source file:
/// imports, exports, Tauri commands/events, and metadata.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct FileAnalysis {
    /// Relative path from project root.
    #[serde(default)]
    pub path: String,
    /// Lines of code (excluding blanks/comments).
    #[serde(default)]
    pub loc: usize,
    /// Detected language: "typescript", "javascript", "python", "rust", "css".
    #[serde(default)]
    pub language: String,
    /// File kind: "code", "test", "config", "style".
    #[serde(default)]
    pub kind: String,
    /// True if test file (based on path/name patterns).
    #[serde(default)]
    pub is_test: bool,
    /// True if generated file (has generation marker).
    #[serde(default)]
    pub is_generated: bool,
    /// True if file uses Flow type annotations (@flow).
    #[serde(default)]
    pub is_flow_file: bool,
    /// Import statements found in file.
    #[serde(default)]
    pub imports: Vec<ImportEntry>,
    /// Re-export statements (`export { x } from './y'`).
    #[serde(default)]
    pub reexports: Vec<ReexportEntry>,
    /// Dynamic import paths (resolved where possible).
    #[serde(default)]
    pub dynamic_imports: Vec<String>,
    /// Exported symbols (functions, classes, consts, types).
    #[serde(default)]
    pub exports: Vec<ExportSymbol>,
    /// Locally-defined symbols (non-exported or imported).
    #[serde(default)]
    pub local_symbols: Vec<LocalSymbol>,
    /// Local usage sites for symbols in this file.
    #[serde(default)]
    pub symbol_usages: Vec<SymbolUsage>,
    /// Tauri command invocations (frontend `invoke()`).
    #[serde(default)]
    pub command_calls: Vec<CommandRef>,
    /// Tauri command handlers (backend `#[tauri::command]`).
    #[serde(default)]
    pub command_handlers: Vec<CommandRef>,
    /// Detected casing inconsistencies in command payloads.
    #[serde(default)]
    pub command_payload_casing: Vec<CommandPayloadCasing>,
    /// String literals for dynamic/registry awareness.
    #[serde(default)]
    pub string_literals: Vec<StringLiteral>,
    /// Tauri event emissions.
    #[serde(default)]
    pub event_emits: Vec<EventRef>,
    /// Tauri event listeners.
    #[serde(default)]
    pub event_listens: Vec<EventRef>,
    /// Event name constants (`const EVENT_X = "event-x"`).
    #[serde(default)]
    pub event_consts: HashMap<String, String>,
    /// Symbol search matches.
    #[serde(default)]
    pub matches: Vec<SymbolMatch>,
    /// Detected entry points (main, index, App).
    #[serde(default)]
    pub entry_points: Vec<String>,
    /// Rust handlers registered via `tauri::generate_handler![...]`.
    #[serde(default)]
    pub tauri_registered_handlers: Vec<String>,
    /// File mtime (Unix timestamp) for incremental scanning.
    #[serde(default)]
    pub mtime: u64,
    /// File size in bytes for incremental cache validation.
    #[serde(default)]
    pub size: u64,
    /// Python concurrency race indicators.
    #[serde(default)]
    pub py_race_indicators: Vec<PyRaceIndicator>,
    /// Python: True if package has py.typed marker (PEP 561).
    #[serde(default)]
    pub is_typed_package: bool,
    /// Python: True if namespace package (PEP 420).
    #[serde(default)]
    pub is_namespace_package: bool,
    /// Locally-referenced symbols (for dead-code suppression).
    #[serde(default)]
    pub local_uses: Vec<String>,
    /// Type usages that appear in function signatures (parameters/returns).
    #[serde(default)]
    pub signature_uses: Vec<SignatureUse>,

    /// Web route handlers detected in Python/other backends
    #[serde(default)]
    pub routes: Vec<RouteInfo>,

    /// Pytest fixtures defined in this file
    #[serde(default)]
    pub pytest_fixtures: Vec<String>,

    /// True if file uses WeakMap or WeakSet (global registry pattern in React/libs)
    #[serde(default)]
    pub has_weak_collections: bool,

    /// Python exec/eval/compile dynamic code generation templates.
    #[serde(default)]
    pub dynamic_exec_templates: Vec<DynamicExecTemplate>,

    /// Python sys.modules monkey-patching injections.
    /// Files with these injections have all exports accessible at runtime.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub sys_modules_injections: Vec<SysModulesInjection>,
}

impl ImportEntry {
    pub fn new(source: String, kind: ImportKind) -> Self {
        let is_bare = !source.starts_with('.') && !source.starts_with('/');
        Self {
            line: None,
            source_raw: source.clone(),
            source,
            kind,
            resolved_path: None,
            is_bare,
            symbols: Vec::new(),
            resolution: ImportResolutionKind::Unknown,
            is_type_checking: false,
            is_lazy: false,
            is_crate_relative: false,
            is_super_relative: false,
            is_self_relative: false,
            raw_path: String::new(),
            is_mod_declaration: false,
        }
    }
}

impl ExportSymbol {
    /// Create export symbol without params (backwards compatible).
    pub fn new(name: String, kind: &str, export_type: &str, line: Option<usize>) -> Self {
        Self {
            name,
            kind: kind.to_string(),
            export_type: export_type.to_string(),
            line,
            params: Vec::new(),
        }
    }

    /// Create export symbol with params (for functions).
    pub fn with_params(
        name: String,
        kind: &str,
        export_type: &str,
        line: Option<usize>,
        params: Vec<ParamInfo>,
    ) -> Self {
        Self {
            name,
            kind: kind.to_string(),
            export_type: export_type.to_string(),
            line,
            params,
        }
    }
}

impl FileAnalysis {
    pub fn new(path: String) -> Self {
        Self {
            path,
            loc: 0,
            language: String::new(),
            kind: "code".to_string(),
            is_test: false,
            is_generated: false,
            is_flow_file: false,
            imports: Vec::new(),
            reexports: Vec::new(),
            dynamic_imports: Vec::new(),
            exports: Vec::new(),
            local_symbols: Vec::new(),
            symbol_usages: Vec::new(),
            command_calls: Vec::new(),
            command_handlers: Vec::new(),
            command_payload_casing: Vec::new(),
            string_literals: Vec::new(),
            event_emits: Vec::new(),
            event_listens: Vec::new(),
            event_consts: HashMap::new(),
            matches: Vec::new(),
            entry_points: Vec::new(),
            tauri_registered_handlers: Vec::new(),
            py_race_indicators: Vec::new(),
            mtime: 0,
            size: 0,
            is_typed_package: false,
            is_namespace_package: false,
            local_uses: Vec::new(),
            signature_uses: Vec::new(),
            routes: Vec::new(),
            pytest_fixtures: Vec::new(),
            has_weak_collections: false,
            dynamic_exec_templates: Vec::new(),
            sys_modules_injections: Vec::new(),
        }
    }
}

/// How a type is used in a function signature.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SignatureUseKind {
    Parameter,
    Return,
}

/// A single mention of a type in a function signature.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SignatureUse {
    /// Function or method name where the type appears.
    pub function: String,
    /// Kind of usage: parameter or return type.
    pub usage: SignatureUseKind,
    /// The referenced type name (as parsed).
    pub type_name: String,
    /// Line number for traceability.
    #[serde(default)]
    pub line: Option<usize>,
}

// Convenience type aliases reused across modules
pub type ExportIndex = HashMap<String, Vec<String>>;
pub type PayloadEntry = (String, usize, Option<String>);
pub type PayloadMap = HashMap<String, Vec<PayloadEntry>>;