Skip to main content

fallow_extract/cache/
types.rs

1//! Serialization types for the incremental parse cache.
2//!
3//! All types use bitcode `Encode`/`Decode` for fast binary serialization.
4
5use bitcode::{Decode, Encode};
6
7use crate::MemberKind;
8
9/// Cache version. Bump it when the serialized cache shape or the cached
10/// extraction semantics change, and give the reason in the commit message and
11/// the CHANGELOG. A stale version serves old extraction results from a warm
12/// cache. The `assert_cached_type_size!` guards below catch shape changes.
13pub(super) const CACHE_VERSION: u32 = 385;
14
15/// Duplication token cache version. Bump it when duplicate tokenization,
16/// normalization, or the on-disk token cache schema changes, and give the
17/// reason in the commit message and the CHANGELOG.
18pub const DUPES_CACHE_VERSION: u32 = 18;
19
20/// Default maximum cache size (256 MB). Overridable per-project via
21/// `cache.maxSizeMb` in the config file or `FALLOW_CACHE_MAX_SIZE` env var.
22/// Also used as the hard ceiling on load-time deserialization as a defence
23/// against pathological on-disk files.
24pub const DEFAULT_CACHE_MAX_SIZE: usize = 256 * 1024 * 1024;
25
26/// Trigger LRU eviction when the serialized cache exceeds 80% of the cap.
27/// Basis points (1/100 of a percent) for integer arithmetic without floats.
28pub(super) const EVICTION_TRIGGER_BPS: usize = 8000;
29
30/// Evict down to 60% of the cap so subsequent saves leave headroom.
31pub(super) const EVICTION_TARGET_BPS: usize = 6000;
32
33/// Promote the eviction log from `debug!` to `info!` when at least 25% of
34/// entries are removed in a single save. Default-noise concerns mean
35/// small-turnover saves should not be visible without `RUST_LOG=debug`.
36pub(super) const EVICTION_SIGNIFICANT_BPS: usize = 2500;
37
38/// Import kind discriminant for `CachedImport`:
39/// 0 = Named, 1 = Default, 2 = Namespace, 3 = `SideEffect`.
40pub(super) const IMPORT_KIND_NAMED: u8 = 0;
41pub(super) const IMPORT_KIND_DEFAULT: u8 = 1;
42pub(super) const IMPORT_KIND_NAMESPACE: u8 = 2;
43pub(super) const IMPORT_KIND_SIDE_EFFECT: u8 = 3;
44
45macro_rules! assert_cached_type_size {
46    ($ty:ty, $size:expr) => {
47        const _: () = assert!(
48            std::mem::size_of::<$ty>() == $size,
49            concat!(
50                stringify!($ty),
51                " size changed; bump CACHE_VERSION if the cached wire shape or extraction semantics changed, then update this assertion"
52            )
53        );
54    };
55}
56
57assert_cached_type_size!(CachedModule, 1504);
58assert_cached_type_size!(CachedNamespaceObjectAlias, 72);
59assert_cached_type_size!(CachedLocalTypeDeclaration, 32);
60assert_cached_type_size!(CachedPublicSignatureTypeReference, 64);
61assert_cached_type_size!(CachedSuppression, 88);
62assert_cached_type_size!(CachedUnknownSuppressionKind, 56);
63assert_cached_type_size!(CachedExport, 152);
64assert_cached_type_size!(CachedImport, 96);
65assert_cached_type_size!(CachedDynamicImport, 88);
66assert_cached_type_size!(CachedRequireCall, 96);
67assert_cached_type_size!(CachedReExport, 104);
68assert_cached_type_size!(CachedMember, 88);
69assert_cached_type_size!(CachedDynamicImportPattern, 88);
70assert_cached_type_size!(crate::MemberAccess, 48);
71assert_cached_type_size!(fallow_types::extract::SemanticFact, 80);
72assert_cached_type_size!(fallow_types::extract::CalleeUse, 32);
73assert_cached_type_size!(fallow_types::extract::ImportedCallSite, 80);
74assert_cached_type_size!(fallow_types::extract::ImportBindingReference, 64);
75assert_cached_type_size!(fallow_types::extract::MisplacedDirectiveSite, 8);
76assert_cached_type_size!(fallow_types::extract::SinkSite, 216);
77assert_cached_type_size!(fallow_types::extract::FunctionComplexity, 104);
78assert_cached_type_size!(fallow_types::extract::ComplexityContribution, 16);
79assert_cached_type_size!(fallow_types::extract::FlagUse, 80);
80assert_cached_type_size!(fallow_types::extract::ClassHeritageInfo, 168);
81assert_cached_type_size!(fallow_types::extract::FactoryReturnExport, 56);
82assert_cached_type_size!(fallow_types::extract::TypeMemberTypeEntry, 72);
83assert_cached_type_size!(fallow_types::extract::LoadReturnKey, 32);
84
85/// Cached data for a single module.
86#[derive(Debug, Clone, Encode, Decode)]
87pub struct CachedModule {
88    /// xxh3 hash of the file content.
89    pub content_hash: u64,
90    /// File modification time in nanoseconds for fast cache validation.
91    /// When mtime+ctime+size match the on-disk file, we skip reading file
92    /// content entirely.
93    pub mtime_ns: u64,
94    /// File inode change time in nanoseconds, or `0` on platforms that do not
95    /// report one. Stored beside `mtime_ns` because mtime is writer-controlled:
96    /// a same-length rewrite with a restored mtime is invisible to
97    /// `(mtime, size)` alone, and the metadata-only fast path would then hand
98    /// back analysis of the previous content.
99    pub ctime_ns: u64,
100    /// File size in bytes for fast cache validation.
101    pub file_size: u64,
102    /// Seconds-since-epoch of the last saved run whose discovery included this
103    /// file. `CacheStore::retain_paths` sets it for every discovered file, so
104    /// a cache hit refreshes it as well as a parse does. `CacheStore::save`
105    /// evicts the smallest values first: entries that only another scope
106    /// sees (test files that `--production` skips, a sibling workspace) go
107    /// before the files that every run reads. An earlier design stamped only
108    /// writes, so a stable file that every run read was the oldest entry and
109    /// was evicted first, and the next run parsed it again.
110    pub last_seen_secs: u64,
111    /// Exported symbols.
112    pub exports: Vec<CachedExport>,
113    /// Import specifiers.
114    pub imports: Vec<CachedImport>,
115    /// Re-export specifiers.
116    pub re_exports: Vec<CachedReExport>,
117    /// Dynamic import specifiers.
118    pub dynamic_imports: Vec<CachedDynamicImport>,
119    /// `require()` specifiers.
120    pub require_calls: Vec<CachedRequireCall>,
121    /// Package names statically referenced through package path resolution.
122    pub package_path_references: Box<[String]>,
123    /// Package names that a module augmentation (`declare module 'pkg'` in a
124    /// module file) names. They credit the package as a type-only use.
125    pub type_package_references: Box<[String]>,
126    /// Binary names found in `node_modules/.bin/<name>` paths in string
127    /// literals and template quasis. The analysis maps each name to the
128    /// package that declares the binary.
129    pub bin_path_references: Box<[String]>,
130    /// Package names from direct `require.resolve` calls, with call offsets.
131    pub package_resolve_sites: Box<[(String, u32)]>,
132    /// Static member accesses (e.g., `Status.Active`).
133    pub member_accesses: Vec<crate::MemberAccess>,
134    /// Typed semantic facts produced by extraction for cross-layer analysis.
135    /// `None` means no facts, which keeps the common warm-cache payload lean.
136    pub semantic_facts: Option<Box<[fallow_types::extract::SemanticFact]>>,
137    /// Identifiers used as whole objects (Object.values, for..in, spread, etc.).
138    pub whole_object_uses: Box<[String]>,
139    /// Dynamic import patterns with partial static resolution.
140    pub dynamic_import_patterns: Vec<CachedDynamicImportPattern>,
141    /// Number of parser diagnostics the parse of this file produced.
142    /// Round-trips so a warm load still reports `source-parse-degraded`
143    /// instead of silently inheriting a partial parse.
144    pub parse_error_count: u32,
145    /// Whether the parser abandoned this file instead of recovering.
146    pub parse_panicked: bool,
147    /// Whether the file nests too deep to parse, so it was not parsed.
148    pub parse_nesting_too_deep: bool,
149    /// Whether this module uses CJS exports.
150    pub has_cjs_exports: bool,
151    /// Whether this module declares at least one Angular `@Component({
152    /// templateUrl: ... })` decorator. Mirrors `ModuleInfo.has_angular_component_template_url`
153    /// so the CRAP-inherit walker's gate survives a warm-cache load.
154    pub has_angular_component_template_url: bool,
155    /// Local names of import bindings that are never referenced in this file.
156    pub unused_import_bindings: Vec<String>,
157    /// Local import bindings referenced from type positions.
158    pub type_referenced_import_bindings: Vec<String>,
159    /// Local import bindings referenced from value positions.
160    pub value_referenced_import_bindings: Vec<String>,
161    /// Inline suppression directives.
162    pub suppressions: Vec<CachedSuppression>,
163    /// Suppression tokens that did not parse to any known `IssueKind`. See #449.
164    pub unknown_suppression_kinds: Vec<CachedUnknownSuppressionKind>,
165    /// Pre-computed line-start byte offsets for O(log N) byte-to-line/col conversion.
166    pub line_offsets: Vec<u32>,
167    /// Per-function complexity metrics.
168    pub complexity: Vec<fallow_types::extract::FunctionComplexity>,
169    /// Whether the run that wrote this entry actually extracted complexity.
170    ///
171    /// An empty `complexity` vector is ambiguous on its own: a `dead-code` run
172    /// passes `need_complexity == false` and produces one, and so does a file
173    /// with no functions at all. Reading emptiness as "not cached" made every
174    /// later `health` run pay a full cold parse for a file that was already
175    /// analyzed. This flag is the honest answer, so a complexity consumer can
176    /// hit on a rich entry and a complexity-blind run can refuse to downgrade
177    /// one.
178    pub complexity_extracted: bool,
179    /// Feature flag use sites.
180    pub flag_uses: Vec<fallow_types::extract::FlagUse>,
181    /// Flag-key registries and registry reads.
182    pub flag_registry_facts: Option<Box<fallow_types::extract::FlagRegistryFacts>>,
183    /// Heritage metadata for exported classes.
184    pub class_heritage: Vec<fallow_types::extract::ClassHeritageInfo>,
185    /// Exported free-function factories that provably return one class instance
186    /// (`export function useApi() { return new RESTApi() }`). Compacted to `None`
187    /// when empty so the common no-factory module pays no payload. See #1441 Part A.
188    pub exported_factory_returns: Option<Box<[fallow_types::extract::FactoryReturnExport]>>,
189    /// Object-literal factory-return shapes (`export function createUi() {
190    /// return { orders: factory.ordersPage } }`). Compacted to `None` when empty
191    /// so the common no-object-factory module pays no payload. See issue #1858.
192    pub exported_factory_return_object_shapes:
193        Option<Box<[fallow_types::extract::FactoryReturnObjectShapeExport]>>,
194    /// Named-type property types declared by top-level interfaces and
195    /// type-literal aliases (`interface Opts { c: OptDep }`). Compacted to
196    /// `None` when empty so the common no-interface module pays no payload.
197    /// See issue #1785.
198    pub type_member_types: Option<Box<[fallow_types::extract::TypeMemberTypeEntry]>>,
199    /// Angular `InjectionToken<Interface>` `(token, interface)` pairs (#920).
200    pub injection_tokens: Vec<(String, String)>,
201    /// Local type-capable declarations.
202    pub local_type_declarations: Vec<CachedLocalTypeDeclaration>,
203    /// Type references from exported public signatures.
204    pub public_signature_type_references: Vec<CachedPublicSignatureTypeReference>,
205    /// Namespace-import aliases re-exported through an object literal
206    /// (`export const API = { foo }` where `foo` is `import * as foo from './bar'`).
207    pub namespace_object_aliases: Vec<CachedNamespaceObjectAlias>,
208    /// Iconify collection prefixes found in static icon props (issue #608).
209    pub iconify_prefixes: Vec<String>,
210    /// Nuxt UI icon class suffixes found in static script-side icon properties
211    /// (issue #955).
212    pub iconify_icon_names: Vec<String>,
213    /// Bare identifier names that are candidates for convention auto-import
214    /// resolution (issue #704). Content-local, so they round-trip through the
215    /// cache; resolution against the plugin table happens at graph-build time.
216    pub auto_import_candidates: Vec<String>,
217    /// File-level string directives (`"use client"`, `"use server"`). Content-local,
218    /// round-trips through the cache so the security `client-server-leak` detector
219    /// sees directives on warm-cache loads.
220    pub directives: Vec<String>,
221    /// Byte-offset starts of `next/dynamic(..., { ssr: false })` dynamic imports.
222    /// Content-local, round-trips so the security `client-server-leak` BFS sees
223    /// the ssr:false client-only escape hatch on warm-cache loads.
224    pub client_only_dynamic_import_spans: Vec<u32>,
225    /// Captured security sink sites (category-blind). Round-trips through the
226    /// cache so the catalogue-driven `tainted_sink` detector sees sinks on
227    /// warm-cache loads.
228    pub security_sinks: Vec<fallow_types::extract::SinkSite>,
229    /// Count of sink-shaped nodes whose callee could not be flattened to a
230    /// static path. Round-trips so the in-band blind-spot count is stable.
231    pub security_sinks_skipped: u32,
232    /// Span-level diagnostics for skipped security sink callees.
233    pub security_unresolved_callee_sites: Vec<fallow_types::extract::SkippedSecurityCalleeSite>,
234    /// Local bindings tied to the member-access path they were sourced from.
235    /// Round-trips so the security `tainted_sink` source-to-sink association
236    /// sees source-tainted bindings on warm-cache loads.
237    pub tainted_bindings: Vec<fallow_types::extract::TaintedBinding>,
238    /// Direct sink arguments recognized as sanitizer calls.
239    pub sanitized_sink_args: Vec<fallow_types::extract::SanitizedSinkArg>,
240    /// Defensive control call sites for security surface output.
241    pub security_control_sites: Vec<fallow_types::extract::SecurityControlSite>,
242    /// Deduped statically flattenable callee paths. Round-trips so the
243    /// `boundaries.calls.forbidden` detector sees call sites on warm-cache
244    /// loads.
245    pub callee_uses: Vec<fallow_types::extract::CalleeUse>,
246    /// Scope-resolved imported calls, preserving each occurrence and argument.
247    pub imported_call_sites: Vec<fallow_types::extract::ImportedCallSite>,
248    /// Import binding references for `fallow trace --dependency`. Round-trips
249    /// so the dependency usage trace sees them on warm-cache loads.
250    pub import_binding_references: Vec<fallow_types::extract::ImportBindingReference>,
251    /// Misplaced `"use client"` / `"use server"` directive sites.
252    /// Round-trips so the `misplaced-directive` detector sees them on
253    /// warm-cache loads.
254    pub misplaced_directives: Vec<fallow_types::extract::MisplacedDirectiveSite>,
255    /// Export local names of inline `"use server"` body Server Actions.
256    /// Round-trips so the `unused-server-action` reclassifier sees them on
257    /// warm-cache loads.
258    pub inline_server_action_exports: Vec<String>,
259    /// Vue `provide`/`inject` and Svelte `setContext`/`getContext` key sites.
260    /// Round-trips so the `unprovided-inject` detector sees them on warm-cache
261    /// loads.
262    pub di_key_sites: Vec<fallow_types::extract::DiKeySite>,
263    /// Whether the module had an unknowable-key provide. Round-trips so the
264    /// `unprovided-inject` project-wide abstain holds on warm-cache loads.
265    pub has_dynamic_provide: bool,
266    /// All-action `"use server"` module flag. Round-trips so the security
267    /// `client-server-leak` BFS sees the action boundary on warm-cache loads.
268    pub is_server_action_module: bool,
269    /// Global-scope declaration flag. Mirrors `ModuleInfo.has_global_declarations`.
270    pub has_global_declarations: bool,
271    /// `/// <reference path>` values. Mirrors `ModuleInfo.triple_slash_reference_paths`.
272    pub triple_slash_reference_paths: Box<[String]>,
273    /// Config JSX runtime flag. Mirrors `ModuleInfo.jsx_runtime_from_config`.
274    pub jsx_runtime_from_config: bool,
275    /// Vue `<script setup>` `defineProps` and Svelte 5 `$props()` declared props.
276    /// Round-trips so the `unused-component-prop` detector sees them on
277    /// warm-cache loads.
278    pub component_props: Vec<fallow_types::extract::ComponentProp>,
279    /// Framework-neutral input contracts and inspected caller evidence.
280    pub component_contracts: Option<Box<fallow_types::extract::ComponentContractFacts>>,
281    /// Effect layer, tagged error and `catchTag` facts.
282    pub effect_facts: Option<Box<fallow_types::extract::EffectFacts>>,
283    /// Whether the template spreads `$attrs`/`$props`/`props` or the
284    /// `defineProps` return is rest-destructured. Round-trips for the abstain.
285    pub has_props_attrs_fallthrough: bool,
286    /// Whether the SFC calls `defineExpose(...)`. Round-trips for the abstain.
287    pub has_define_expose: bool,
288    /// Whether the SFC calls `defineModel(...)`. Round-trips for the abstain.
289    pub has_define_model: bool,
290    /// Whether `defineProps` had an unharvestable type-reference argument.
291    /// Round-trips for the abstain.
292    pub has_unharvestable_props: bool,
293    /// Vue `<script setup>` `defineEmits` declared events. Round-trips so the
294    /// `unused-component-emit` detector sees them on warm-cache loads.
295    pub component_emits: Vec<fallow_types::extract::ComponentEmit>,
296    /// Angular component/directive inputs (`@Input()` decorators and signal
297    /// `input()` / `model()` initializers). Round-trips so the
298    /// `unused-component-input` detector sees them on warm-cache loads.
299    pub angular_inputs: Vec<fallow_types::extract::AngularInputMember>,
300    /// Angular component/directive outputs (`@Output()` decorators and signal
301    /// `output()` / `outputFromObservable()` initializers). Round-trips so the
302    /// `unused-component-output` detector sees them on warm-cache loads.
303    pub angular_outputs: Vec<fallow_types::extract::AngularOutputMember>,
304    /// Angular `@Component` declarations with their `selector` value(s).
305    /// Round-trips so the Angular `unrendered-component` arm sees them on
306    /// warm-cache loads.
307    pub angular_component_selectors: Vec<fallow_types::extract::AngularComponentSelector>,
308    /// Lit / web-component custom elements registered in this file. Round-trips so
309    /// the Lit `unrendered-component` arm sees them on warm-cache loads.
310    pub registered_custom_elements: Vec<fallow_types::extract::RegisteredCustomElement>,
311    /// Custom-element tag names used (rendered) in this file's `html` templates.
312    /// Round-trips for the Lit `unrendered-component` rendered-tag union.
313    pub used_custom_element_tags: Vec<String>,
314    /// Custom element selector tags referenced in this file's Angular templates.
315    /// Round-trips for the Angular `unrendered-component` used-selector union.
316    pub angular_used_selectors: Vec<String>,
317    /// Angular route / bootstrap component class references. Round-trips for the
318    /// Angular `unrendered-component` entry-point abstain.
319    pub angular_entry_component_refs: Vec<String>,
320    /// Whether this file dynamically renders a component (project-wide abstain
321    /// signal for the Angular `unrendered-component` detector). Round-trips.
322    pub has_dynamic_component_render: bool,
323    /// Whether `defineEmits` had an unharvestable argument. Round-trips for the
324    /// abstain.
325    pub has_unharvestable_emits: bool,
326    /// Whether an `emit(<nonLiteral>)` call was seen. Round-trips for the abstain.
327    pub has_dynamic_emit: bool,
328    /// Whether the emit binding was used as a whole value. Round-trips for the
329    /// abstain.
330    pub has_emit_whole_object_use: bool,
331    /// SvelteKit `load()` return-object keys. Round-trips so the
332    /// `unused-load-data-key` detector sees them on warm-cache loads.
333    pub load_return_keys: Vec<fallow_types::extract::LoadReturnKey>,
334    /// Whether this file's `load()` body could not be harvested safely.
335    /// Round-trips for the abstain.
336    pub has_unharvestable_load: bool,
337    /// Whether this file passes the whole `data` object opaquely. Round-trips
338    /// for the `unused-load-data-key` abstain.
339    pub has_load_data_whole_use: bool,
340    /// React/JSX component definitions. Round-trips so the React-health phases
341    /// see them on warm-cache loads.
342    pub component_functions: Vec<fallow_types::extract::ComponentFunction>,
343    /// React component props. Round-trips so the React `unused-component-prop`
344    /// arm sees them on warm-cache loads.
345    pub react_props: Vec<fallow_types::extract::ComponentProp>,
346    /// React hook call sites. Round-trips for the complexity-fold phase.
347    pub hook_uses: Vec<fallow_types::extract::HookUse>,
348    /// React render edges (child name captured; resolution deferred to graph
349    /// build). Round-trips so the render graph survives a warm cache.
350    pub render_edges: Vec<fallow_types::extract::RenderEdge>,
351    /// Svelte custom events dispatched via `dispatch('<name>')`. Round-trips so
352    /// the `unused-svelte-event` detector sees them on warm-cache loads.
353    pub svelte_dispatched_events: Vec<fallow_types::extract::DispatchedEvent>,
354    /// Svelte template `on:<name>` listener names on component tags. Round-trips
355    /// so the project-wide listened set is correct on warm-cache loads.
356    pub svelte_listened_events: Vec<String>,
357    /// Whether a `dispatch(<nonLiteral>)` call or whole-`dispatch`-value use was
358    /// seen. Round-trips for the `unused-svelte-event` abstain.
359    pub has_dynamic_dispatch: bool,
360}
361
362impl CachedModule {
363    /// Source metadata fingerprint stored with this cache entry.
364    ///
365    #[must_use]
366    pub fn source_fingerprint(&self) -> fallow_types::source_fingerprint::SourceFingerprint {
367        fallow_types::source_fingerprint::SourceFingerprint::with_ctime(
368            self.mtime_ns,
369            self.ctime_ns,
370            self.file_size,
371        )
372    }
373}
374
375/// Cached namespace-object alias.
376#[derive(Debug, Clone, Encode, Decode)]
377pub struct CachedNamespaceObjectAlias {
378    /// Canonical export name on this module.
379    pub via_export_name: String,
380    /// Dotted suffix of the property path relative to the export.
381    pub suffix: String,
382    /// Local name of the namespace import on this module.
383    pub namespace_local: String,
384}
385
386/// Cached local type declaration.
387#[derive(Debug, Clone, Encode, Decode)]
388pub struct CachedLocalTypeDeclaration {
389    /// Local declaration name.
390    pub name: String,
391    /// Byte offset of the declaration span start.
392    pub span_start: u32,
393    /// Byte offset of the declaration span end.
394    pub span_end: u32,
395}
396
397/// Cached public signature type reference.
398#[derive(Debug, Clone, Encode, Decode)]
399pub struct CachedPublicSignatureTypeReference {
400    /// Exported symbol whose signature contains the reference.
401    pub export_name: String,
402    /// Referenced type name.
403    pub type_name: String,
404    /// Byte offset of the reference span start.
405    pub span_start: u32,
406    /// Byte offset of the reference span end.
407    pub span_end: u32,
408    /// True when the reference comes from a `satisfies` clause.
409    pub from_satisfies: bool,
410}
411
412/// Cached suppression directive.
413#[derive(Debug, Clone, Encode, Decode)]
414pub struct CachedSuppression {
415    /// 1-based line this suppression applies to. 0 = file-wide.
416    pub(crate) line: u32,
417    /// 1-based line where the comment itself appears.
418    pub(crate) comment_line: u32,
419    /// 0 = suppress all, otherwise `IssueKind` discriminant.
420    pub(crate) kind: u8,
421    /// Rule-pack name for scoped policy suppressions. Empty for all other
422    /// suppression targets.
423    pub(crate) policy_pack: String,
424    /// Rule id for scoped policy suppressions. Empty for all other suppression
425    /// targets.
426    pub(crate) policy_rule_id: String,
427    /// Human-authored reason after `--`, when present.
428    pub(crate) reason: Option<String>,
429}
430
431/// Cached unknown suppression kind token (see #449).
432#[derive(Debug, Clone, Encode, Decode)]
433pub struct CachedUnknownSuppressionKind {
434    /// 1-based line where the comment itself appears.
435    pub comment_line: u32,
436    /// True when the marker was `fallow-ignore-file`.
437    pub is_file_level: bool,
438    /// The verbatim token that did not parse.
439    pub token: String,
440    /// Human-authored reason after `--`, when present.
441    pub reason: Option<String>,
442}
443
444/// Cached export data for a single export declaration.
445#[derive(Debug, Clone, Encode, Decode)]
446pub struct CachedExport {
447    /// Export name (or "default" for default exports).
448    pub(crate) name: String,
449    /// Whether this is a default export.
450    pub(crate) is_default: bool,
451    /// Whether this is a type-only export.
452    pub(crate) is_type_only: bool,
453    /// Whether this export is registered through a runtime side effect at
454    /// module load time (Lit `@customElement` decorator or
455    /// `customElements.define` call). Persisted so warm-cache runs continue
456    /// to skip unused-export reporting for these classes.
457    pub(crate) is_side_effect_used: bool,
458    /// Visibility tag discriminant (0=None, 1=Public, 2=Internal, 3=Beta, 4=Alpha).
459    pub(crate) visibility: u8,
460    /// Human-authored reason on `@expected-unused -- <reason>`, when present.
461    pub(crate) expected_unused_reason: Option<String>,
462    /// Whether the leading JSDoc carries a `@deprecated` tag.
463    pub(crate) deprecated: bool,
464    /// Plain-text `@deprecated` message, `None` for a bare tag.
465    pub(crate) deprecated_reason: Option<Box<str>>,
466    /// The local binding name, if different.
467    pub(crate) local_name: Option<String>,
468    /// Byte offset of the export span start.
469    pub(crate) span_start: u32,
470    /// Byte offset of the export span end.
471    pub(crate) span_end: u32,
472    /// Members of this export (for enums and classes).
473    pub(crate) members: Vec<CachedMember>,
474    /// The local name of the parent class from `extends` clause, if any.
475    pub(crate) super_class: Option<String>,
476}
477
478/// Cached import data for a single import declaration.
479#[derive(Debug, Clone, Encode, Decode)]
480pub struct CachedImport {
481    /// The import specifier.
482    pub(crate) source: String,
483    /// For Named imports, the imported symbol name. Empty for other kinds.
484    pub(crate) imported_name: String,
485    /// The local binding name.
486    pub(crate) local_name: String,
487    /// Whether this is a type-only import.
488    pub(crate) is_type_only: bool,
489    /// Whether this whole-module import forwards type meanings only
490    /// (`export type *` inside a `declare module '...'` body).
491    pub(crate) is_type_only_star: bool,
492    /// Whether this import originated from an SFC `<style>` block / `<style src>` (CSS context).
493    pub(crate) from_style: bool,
494    /// Import kind: 0=Named, 1=Default, 2=Namespace, 3=SideEffect.
495    pub(crate) kind: u8,
496    /// Byte offset of the import span start.
497    pub(crate) span_start: u32,
498    /// Byte offset of the import span end.
499    pub(crate) span_end: u32,
500    /// Byte offset of the source string literal span start.
501    pub(crate) source_span_start: u32,
502    /// Byte offset of the source string literal span end.
503    pub(crate) source_span_end: u32,
504}
505
506/// Cached dynamic import data.
507#[derive(Debug, Clone, Encode, Decode)]
508pub struct CachedDynamicImport {
509    /// The import specifier.
510    pub(crate) source: String,
511    /// Byte offset of the span start.
512    pub(crate) span_start: u32,
513    /// Byte offset of the span end.
514    pub(crate) span_end: u32,
515    /// Names destructured from the import result.
516    pub(crate) destructured_names: Vec<String>,
517    /// Local variable name for namespace imports.
518    pub(crate) local_name: Option<String>,
519    /// True when this dynamic import was synthesised by fallow (see
520    /// `DynamicImportInfo::is_speculative`).
521    pub(crate) is_speculative: bool,
522    /// See `DynamicImportInfo::value_escapes`.
523    pub(crate) value_escapes: bool,
524}
525
526/// Cached `require()` call data.
527#[derive(Debug, Clone, Encode, Decode)]
528pub struct CachedRequireCall {
529    /// The require specifier.
530    pub(crate) source: String,
531    /// Byte offset of the span start.
532    pub(crate) span_start: u32,
533    /// Byte offset of the span end.
534    pub(crate) span_end: u32,
535    /// Byte offset of the specifier string-literal span start.
536    pub(crate) source_span_start: u32,
537    /// Byte offset of the specifier string-literal span end.
538    pub(crate) source_span_end: u32,
539    /// Names destructured from the require result.
540    pub(crate) destructured_names: Vec<String>,
541    /// Local variable name for namespace requires.
542    pub(crate) local_name: Option<String>,
543    /// `true` for the type-erased `import type X = require('...')` spelling.
544    pub(crate) is_type_only: bool,
545}
546
547/// Cached re-export data.
548#[derive(Debug, Clone, Encode, Decode)]
549pub struct CachedReExport {
550    /// The module being re-exported from.
551    pub(crate) source: String,
552    /// Name imported from the source.
553    pub(crate) imported_name: String,
554    /// Name exported from this module.
555    pub(crate) exported_name: String,
556    /// Whether this is a type-only re-export.
557    pub(crate) is_type_only: bool,
558    /// Byte offset of the re-export span start (for line-number reporting).
559    pub(crate) span_start: u32,
560    /// Byte offset of the re-export span end.
561    pub(crate) span_end: u32,
562    /// Byte offset of the enclosing statement span start.
563    pub(crate) statement_span_start: u32,
564    /// Byte offset of the enclosing statement span end.
565    pub(crate) statement_span_end: u32,
566    /// Byte offset of the source string-literal span start.
567    pub(crate) source_span_start: u32,
568    /// Byte offset of the source string-literal span end.
569    pub(crate) source_span_end: u32,
570}
571
572/// Cached enum or class member data.
573#[derive(Debug, Clone, Encode, Decode)]
574pub struct CachedMember {
575    /// Member name.
576    pub(crate) name: String,
577    /// Member kind (enum, method, or property).
578    pub(crate) kind: MemberKind,
579    /// Byte offset of the span start.
580    pub(crate) span_start: u32,
581    /// Byte offset of the span end.
582    pub(crate) span_end: u32,
583    /// Whether this member has decorators.
584    pub(crate) has_decorator: bool,
585    /// Full dotted path of each decorator (e.g. `step`, `ns.foo`).
586    /// Empty for undecorated members and decorators with non-identifier
587    /// expressions.
588    pub(crate) decorator_names: Vec<String>,
589    /// True when this is a static method that returns a fresh instance of
590    /// the class: body returns `new this()` / `new <SameClassName>()`, or the
591    /// declared return type matches the class name. Treated as a factory.
592    /// See issues #346, #387.
593    pub(crate) is_instance_returning_static: bool,
594    /// True when this instance method's call result is an instance of the
595    /// same class (declared return type matches the class name, or body's
596    /// last statement is `return this`). Drives fluent-chain credit. See
597    /// issue #387.
598    pub(crate) is_self_returning: bool,
599    /// Root identifier of the class `extends` expression for an `override`
600    /// member, else `None`.
601    pub(crate) override_base: Option<String>,
602}
603
604/// Cached dynamic import pattern data (template literals, `import.meta.glob`).
605#[derive(Debug, Clone, Encode, Decode)]
606pub struct CachedDynamicImportPattern {
607    /// Static prefix of the import path.
608    pub(crate) prefix: String,
609    /// Static suffix, if any.
610    pub(crate) suffix: Option<String>,
611    /// Globs of the files that the pattern does not load.
612    pub(crate) excludes: Vec<String>,
613    /// Byte offset of the span start.
614    pub(crate) span_start: u32,
615    /// Byte offset of the span end.
616    pub(crate) span_end: u32,
617    /// Runtime mechanism used to load modules matching this pattern.
618    pub(crate) mechanism: crate::ModuleLoadMechanism,
619}