Skip to main content

omena_query/style/
parser_facade.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use omena_parser::{
4    LexResult, ParseResult, ParsedAnimationFactKind, ParsedCssModuleComposesEdgeKind,
5    ParsedCssModuleComposesFactKind, ParsedCssModuleValueFactKind, ParsedIcssFactKind,
6    ParsedSassModuleEdgeFactKind, ParsedSassSymbolFactKind, ParsedSelectorFactKind,
7    ParsedStyleFacts, ParsedVariableFactKind, collect_icss_export_values_from_cst,
8    collect_style_facts, facts_from_cst, lex, parse, parse_with_reuse_cache,
9};
10
11use crate::*;
12
13pub(super) fn collect_omena_query_omena_parser_style_facts_raw(
14    style_source: &str,
15    dialect: OmenaParserStyleDialect,
16) -> ParsedStyleFacts {
17    #[cfg(test)]
18    style_facts_collect_probe::record();
19    collect_style_facts(style_source, dialect)
20}
21
22pub fn summarize_omena_query_sass_module_source_edges(
23    style_source: &str,
24    dialect: OmenaParserStyleDialect,
25) -> Vec<OmenaQuerySassModuleSourceEdgeV0> {
26    collect_omena_query_omena_parser_style_facts_raw(style_source, dialect)
27        .sass_module_edges
28        .into_iter()
29        .map(|edge| OmenaQuerySassModuleSourceEdgeV0 {
30            kind: omena_query_sass_module_edge_fact_kind_label(edge.kind),
31            source: edge.source,
32            byte_span: ParserByteSpanV0 {
33                start: u32::from(edge.range.start()) as usize,
34                end: u32::from(edge.range.end()) as usize,
35            },
36            namespace_kind: edge.namespace_kind,
37            namespace: edge.namespace,
38            forward_prefix: edge.forward_prefix,
39            visibility_filter_kind: edge.visibility_filter_kind,
40            visibility_filter_names: edge.visibility_filter_names,
41            media_qualified: edge.media_qualified,
42        })
43        .collect()
44}
45
46pub(super) fn collect_omena_query_style_facts_with_icss_values_raw(
47    style_source: &str,
48    dialect: OmenaParserStyleDialect,
49) -> (ParsedStyleFacts, BTreeMap<String, String>) {
50    let parsed = parse(style_source, dialect);
51    collect_omena_query_style_facts_with_icss_values_from_parse(style_source, &parsed)
52}
53
54pub(super) fn collect_omena_query_style_facts_with_icss_values_from_parse(
55    style_source: &str,
56    parsed: &ParseResult,
57) -> (ParsedStyleFacts, BTreeMap<String, String>) {
58    #[cfg(test)]
59    style_facts_collect_probe::record();
60    let values = collect_icss_export_values_from_cst(style_source, parsed)
61        .into_iter()
62        .collect();
63    (facts_from_cst(style_source, parsed), values)
64}
65
66pub(super) fn parse_omena_query_omena_parser_style_source(
67    style_source: &str,
68    dialect: OmenaParserStyleDialect,
69) -> ParseResult {
70    parse(style_source, dialect)
71}
72
73pub(super) fn lex_omena_query_omena_parser_style_source(
74    style_source: &str,
75    dialect: OmenaParserStyleDialect,
76) -> LexResult {
77    lex(style_source, dialect)
78}
79
80#[derive(Default)]
81pub struct OmenaQueryStyleFrameRefreshParseCacheV0 {
82    reuse_cache: omena_parser::ParseReuseCache,
83}
84
85pub struct OmenaQueryStyleFrameRefreshFactsV0 {
86    pub token_count: usize,
87    pub error_count: usize,
88    pub dependency_ids: Vec<String>,
89}
90
91pub fn summarize_omena_query_style_frame_refresh_facts_with_reuse(
92    style_source: &str,
93    dialect: OmenaParserStyleDialect,
94    cache: &mut OmenaQueryStyleFrameRefreshParseCacheV0,
95) -> OmenaQueryStyleFrameRefreshFactsV0 {
96    let parsed = parse_with_reuse_cache(style_source, dialect, &mut cache.reuse_cache);
97    let facts = facts_from_cst(style_source, &parsed);
98    OmenaQueryStyleFrameRefreshFactsV0 {
99        token_count: parsed.token_count(),
100        error_count: parsed.errors().len(),
101        dependency_ids: frame_refresh_dependency_ids(&facts),
102    }
103}
104
105fn frame_refresh_dependency_ids(facts: &ParsedStyleFacts) -> Vec<String> {
106    let mut dependency_ids = BTreeSet::new();
107    for edge in &facts.sass_module_edges {
108        dependency_ids.insert(edge.source.clone());
109    }
110    for edge in &facts.css_module_value_import_edges {
111        dependency_ids.insert(edge.import_source.clone());
112    }
113    for edge in &facts.css_module_composes_edges {
114        if let Some(import_source) = &edge.import_source {
115            dependency_ids.insert(import_source.clone());
116        }
117    }
118    for edge in &facts.icss_import_edges {
119        dependency_ids.insert(edge.import_source.clone());
120    }
121    dependency_ids.into_iter().collect()
122}
123
124pub fn summarize_omena_query_style_document(
125    style_path: &str,
126    style_source: &str,
127) -> Option<OmenaQueryStyleDocumentSummaryV0> {
128    let dialect = omena_parser_dialect_for_style_path(style_path);
129    let facts = collect_omena_query_omena_parser_style_facts_raw(style_source, dialect);
130    let mut selector_names = Vec::new();
131    let mut custom_property_decl_names = Vec::new();
132    let mut custom_property_ref_names = Vec::new();
133    let mut sass_module_use_sources = BTreeSet::new();
134    let mut sass_module_forward_sources = BTreeSet::new();
135
136    for selector in facts.selectors {
137        if selector.kind == ParsedSelectorFactKind::Class {
138            selector_names.push(selector.name);
139        }
140    }
141
142    for variable in facts.variables {
143        match variable.kind {
144            ParsedVariableFactKind::CustomPropertyDeclaration => {
145                if let Some(name) = variable.name.as_custom_property() {
146                    custom_property_decl_names.push(name.clone());
147                }
148            }
149            ParsedVariableFactKind::CustomPropertyReference => {
150                if let Some(name) = variable.name.as_custom_property() {
151                    custom_property_ref_names.push(name.clone());
152                }
153            }
154            _ => {}
155        }
156    }
157
158    for edge in facts.sass_module_edges {
159        match edge.kind {
160            ParsedSassModuleEdgeFactKind::Use => {
161                sass_module_use_sources.insert(edge.source);
162            }
163            ParsedSassModuleEdgeFactKind::Forward => {
164                sass_module_forward_sources.insert(edge.source);
165            }
166            ParsedSassModuleEdgeFactKind::Import => {
167                sass_module_use_sources.insert(edge.source);
168            }
169        }
170    }
171
172    Some(OmenaQueryStyleDocumentSummaryV0 {
173        schema_version: "0",
174        product: "omena-query.style-document-summary",
175        language: omena_parser_style_dialect_label(dialect),
176        selector_names,
177        custom_property_decl_names,
178        custom_property_ref_names,
179        sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
180        sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
181        diagnostic_count: facts.error_count,
182    })
183}
184
185pub fn summarize_omena_query_omena_parser_style_facts(
186    style_source: &str,
187    dialect: OmenaParserStyleDialect,
188) -> OmenaQueryOmenaParserStyleFactsV0 {
189    let facts = collect_omena_query_omena_parser_style_facts_raw(style_source, dialect);
190    summarize_omena_query_omena_parser_style_facts_from_facts(facts, dialect)
191}
192
193pub(super) fn summarize_omena_query_omena_parser_style_facts_from_facts(
194    facts: ParsedStyleFacts,
195    dialect: OmenaParserStyleDialect,
196) -> OmenaQueryOmenaParserStyleFactsV0 {
197    let sass_symbol_resolution =
198        summarize_omena_query_sass_symbol_resolution(facts.sass_symbols.as_slice());
199    let mut class_selector_names = Vec::new();
200    let mut id_selector_names = Vec::new();
201    let mut placeholder_selector_names = Vec::new();
202    let mut keyframe_names = Vec::new();
203    let mut animation_reference_names = Vec::new();
204    let mut css_module_value_definition_names = BTreeSet::new();
205    let mut css_module_value_reference_names = BTreeSet::new();
206    let mut css_module_value_import_sources = BTreeSet::new();
207    let mut css_module_composes_target_names = BTreeSet::new();
208    let mut css_module_composes_import_sources = BTreeSet::new();
209    let mut icss_export_names = BTreeSet::new();
210    let mut icss_import_local_names = BTreeSet::new();
211    let mut icss_import_remote_names = BTreeSet::new();
212    let mut icss_import_sources = BTreeSet::new();
213    let mut variable_names = BTreeSet::new();
214    let mut sass_symbol_declaration_names = BTreeSet::new();
215    let mut sass_symbol_reference_names = BTreeSet::new();
216    let mut sass_module_use_sources = BTreeSet::new();
217    let mut sass_module_forward_sources = BTreeSet::new();
218    let mut sass_module_import_sources = BTreeSet::new();
219    let mut custom_property_names = BTreeMap::new();
220    let mut custom_property_decl_names = BTreeMap::new();
221    let mut custom_property_ref_names = BTreeMap::new();
222
223    for selector in facts.selectors {
224        match selector.kind {
225            ParsedSelectorFactKind::Class => class_selector_names.push(selector.name),
226            ParsedSelectorFactKind::Id => id_selector_names.push(selector.name),
227            ParsedSelectorFactKind::Placeholder => placeholder_selector_names.push(selector.name),
228        }
229    }
230
231    for variable in facts.variables {
232        match variable.kind {
233            ParsedVariableFactKind::ScssDeclaration
234            | ParsedVariableFactKind::ScssReference
235            | ParsedVariableFactKind::LessDeclaration
236            | ParsedVariableFactKind::LessReference => {
237                if let Some(name) = variable.name.as_non_property() {
238                    variable_names.insert(name.to_string());
239                }
240            }
241            ParsedVariableFactKind::CustomPropertyDeclaration
242            | ParsedVariableFactKind::CustomPropertyReference => {
243                let Some(property_key) = variable.property_key else {
244                    continue;
245                };
246                let Some(property_name) = variable.name.as_custom_property().cloned() else {
247                    continue;
248                };
249                custom_property_names
250                    .entry(property_key.clone())
251                    .or_insert_with(|| property_name.clone());
252                match variable.kind {
253                    ParsedVariableFactKind::CustomPropertyDeclaration => {
254                        custom_property_decl_names
255                            .entry(property_key)
256                            .or_insert(property_name);
257                    }
258                    ParsedVariableFactKind::CustomPropertyReference => {
259                        custom_property_ref_names
260                            .entry(property_key)
261                            .or_insert(property_name);
262                    }
263                    _ => {}
264                }
265            }
266        }
267    }
268
269    for symbol in &facts.sass_symbols {
270        match symbol.role {
271            "declaration" => {
272                sass_symbol_declaration_names.insert(symbol.name.clone());
273            }
274            _ => {
275                sass_symbol_reference_names.insert(symbol.name.clone());
276            }
277        }
278    }
279
280    for edge in &facts.sass_module_edges {
281        match edge.kind {
282            ParsedSassModuleEdgeFactKind::Use => {
283                sass_module_use_sources.insert(edge.source.clone());
284            }
285            ParsedSassModuleEdgeFactKind::Forward => {
286                sass_module_forward_sources.insert(edge.source.clone());
287            }
288            ParsedSassModuleEdgeFactKind::Import => {
289                sass_module_import_sources.insert(edge.source.clone());
290            }
291        }
292    }
293
294    for animation in facts.animations {
295        match animation.kind {
296            ParsedAnimationFactKind::KeyframesDeclaration => keyframe_names.push(animation.name),
297            ParsedAnimationFactKind::AnimationNameReference => {
298                animation_reference_names.push(animation.name);
299            }
300        }
301    }
302
303    for value in facts.css_module_values {
304        match value.kind {
305            ParsedCssModuleValueFactKind::Definition => {
306                css_module_value_definition_names.insert(value.name);
307            }
308            ParsedCssModuleValueFactKind::Reference => {
309                css_module_value_reference_names.insert(value.name);
310            }
311            ParsedCssModuleValueFactKind::ImportSource => {
312                css_module_value_import_sources.insert(value.name);
313            }
314        }
315    }
316
317    for composes in facts.css_module_composes {
318        match composes.kind {
319            ParsedCssModuleComposesFactKind::Target => {
320                css_module_composes_target_names.insert(composes.name);
321            }
322            ParsedCssModuleComposesFactKind::ImportSource => {
323                css_module_composes_import_sources.insert(composes.name);
324            }
325        }
326    }
327
328    for icss in facts.icss {
329        match icss.kind {
330            ParsedIcssFactKind::ExportName => {
331                icss_export_names.insert(icss.name);
332            }
333            ParsedIcssFactKind::ImportLocalName => {
334                icss_import_local_names.insert(icss.name);
335            }
336            ParsedIcssFactKind::ImportRemoteName => {
337                icss_import_remote_names.insert(icss.name);
338            }
339            ParsedIcssFactKind::ImportSource => {
340                icss_import_sources.insert(icss.name);
341            }
342        }
343    }
344
345    OmenaQueryOmenaParserStyleFactsV0 {
346        schema_version: "0",
347        product: "omena-query.omena-parser-style-facts",
348        dialect: omena_parser_style_dialect_label(dialect),
349        class_selector_names,
350        id_selector_names,
351        placeholder_selector_names,
352        keyframe_names,
353        animation_reference_names,
354        css_module_value_definition_names: css_module_value_definition_names.into_iter().collect(),
355        css_module_value_reference_names: css_module_value_reference_names.into_iter().collect(),
356        css_module_value_import_sources: css_module_value_import_sources.into_iter().collect(),
357        css_module_value_import_edges: facts
358            .css_module_value_import_edges
359            .into_iter()
360            .map(|edge| OmenaQueryCssModuleValueImportEdgeFactV0 {
361                remote_name: edge.remote_name,
362                local_name: edge.local_name,
363                import_source: edge.import_source,
364            })
365            .collect(),
366        css_module_value_definition_edges: facts
367            .css_module_value_definition_edges
368            .into_iter()
369            .map(|edge| OmenaQueryCssModuleValueDefinitionEdgeFactV0 {
370                definition_name: edge.definition_name,
371                reference_names: edge.reference_names,
372            })
373            .collect(),
374        css_module_composes_target_names: css_module_composes_target_names.into_iter().collect(),
375        css_module_composes_import_sources: css_module_composes_import_sources
376            .into_iter()
377            .collect(),
378        css_module_composes_edges: facts
379            .css_module_composes_edges
380            .into_iter()
381            .map(|edge| OmenaQueryCssModuleComposesEdgeFactV0 {
382                kind: omena_query_css_module_composes_edge_kind_label(edge.kind),
383                owner_selector_names: edge.owner_selector_names,
384                target_names: edge.target_names,
385                import_source: edge.import_source,
386            })
387            .collect(),
388        icss_export_names: icss_export_names.into_iter().collect(),
389        icss_import_local_names: icss_import_local_names.into_iter().collect(),
390        icss_import_remote_names: icss_import_remote_names.into_iter().collect(),
391        icss_import_sources: icss_import_sources.into_iter().collect(),
392        icss_import_edges: facts
393            .icss_import_edges
394            .into_iter()
395            .map(|edge| OmenaQueryIcssImportEdgeFactV0 {
396                local_name: edge.local_name,
397                remote_name: edge.remote_name,
398                import_source: edge.import_source,
399            })
400            .collect(),
401        icss_export_edges: facts
402            .icss_export_edges
403            .into_iter()
404            .map(|edge| OmenaQueryIcssExportEdgeFactV0 {
405                export_name: edge.export_name,
406                reference_names: edge.reference_names,
407            })
408            .collect(),
409        variable_names: variable_names.into_iter().collect(),
410        sass_symbol_declaration_names: sass_symbol_declaration_names.into_iter().collect(),
411        sass_symbol_reference_names: sass_symbol_reference_names.into_iter().collect(),
412        sass_symbol_facts: facts
413            .sass_symbols
414            .into_iter()
415            .map(|symbol| OmenaQuerySassSymbolFactV0 {
416                kind: omena_query_sass_symbol_fact_kind_label(symbol.kind),
417                symbol_kind: symbol.symbol_kind,
418                name: symbol.name,
419                role: symbol.role,
420                namespace: symbol.namespace,
421            })
422            .collect(),
423        sass_symbol_resolution,
424        sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
425        sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
426        sass_module_import_sources: sass_module_import_sources.into_iter().collect(),
427        sass_module_edges: facts
428            .sass_module_edges
429            .into_iter()
430            .map(|edge| OmenaQuerySassModuleEdgeFactV0 {
431                kind: omena_query_sass_module_edge_fact_kind_label(edge.kind),
432                source: edge.source,
433                namespace_kind: edge.namespace_kind,
434                namespace: edge.namespace,
435                forward_prefix: edge.forward_prefix,
436                visibility_filter_kind: edge.visibility_filter_kind,
437                visibility_filter_names: edge.visibility_filter_names,
438            })
439            .collect(),
440        custom_property_names: custom_property_names.into_values().collect(),
441        custom_property_decl_names: custom_property_decl_names.into_values().collect(),
442        custom_property_ref_names: custom_property_ref_names.into_values().collect(),
443        at_rule_names: facts
444            .at_rules
445            .into_iter()
446            .map(|at_rule| at_rule.name)
447            .collect(),
448        parser_error_count: facts.error_count,
449    }
450}
451
452pub fn summarize_omena_query_omena_parser_css_modules_intermediate(
453    style_source: &str,
454    dialect: OmenaParserStyleDialect,
455) -> omena_parser::ParserIndexSummaryV0 {
456    omena_parser::summarize_css_modules_intermediate(style_source, dialect)
457}
458
459pub fn summarize_omena_query_omena_parser_lex(
460    style_source: &str,
461    dialect: OmenaParserStyleDialect,
462) -> omena_parser::OmenaParserLexSummaryV0 {
463    omena_parser::summarize_omena_parser_lex(style_source, dialect)
464}
465
466pub(super) fn omena_parser_dialect_for_style_path(style_path: &str) -> OmenaParserStyleDialect {
467    if style_path.ends_with(".sass") {
468        OmenaParserStyleDialect::Sass
469    } else if style_path.ends_with(".scss") {
470        OmenaParserStyleDialect::Scss
471    } else if style_path.ends_with(".less") {
472        OmenaParserStyleDialect::Less
473    } else {
474        OmenaParserStyleDialect::Css
475    }
476}
477
478pub(super) fn omena_parser_style_dialect_label(dialect: OmenaParserStyleDialect) -> &'static str {
479    match dialect {
480        OmenaParserStyleDialect::Css => "css",
481        OmenaParserStyleDialect::Scss => "scss",
482        OmenaParserStyleDialect::Sass => "sass",
483        OmenaParserStyleDialect::Less => "less",
484    }
485}
486
487pub(super) fn omena_query_css_module_composes_edge_kind_label(
488    kind: ParsedCssModuleComposesEdgeKind,
489) -> &'static str {
490    match kind {
491        ParsedCssModuleComposesEdgeKind::Local => "local",
492        ParsedCssModuleComposesEdgeKind::Global => "global",
493        ParsedCssModuleComposesEdgeKind::External => "external",
494    }
495}
496
497pub(super) fn omena_query_sass_symbol_fact_kind_label(
498    kind: ParsedSassSymbolFactKind,
499) -> &'static str {
500    match kind {
501        ParsedSassSymbolFactKind::VariableDeclaration => "sassVariableDeclaration",
502        ParsedSassSymbolFactKind::VariableReference => "sassVariableReference",
503        ParsedSassSymbolFactKind::MixinDeclaration => "sassMixinDeclaration",
504        ParsedSassSymbolFactKind::MixinInclude => "sassMixinInclude",
505        ParsedSassSymbolFactKind::FunctionDeclaration => "sassFunctionDeclaration",
506        ParsedSassSymbolFactKind::FunctionCall => "sassFunctionCall",
507    }
508}
509
510pub(super) fn omena_query_sass_module_edge_fact_kind_label(
511    kind: ParsedSassModuleEdgeFactKind,
512) -> &'static str {
513    match kind {
514        ParsedSassModuleEdgeFactKind::Use => "sassUse",
515        ParsedSassModuleEdgeFactKind::Forward => "sassForward",
516        ParsedSassModuleEdgeFactKind::Import => "sassImport",
517    }
518}
519
520fn summarize_omena_query_sass_symbol_resolution(
521    symbols: &[omena_parser::ParsedSassSymbolFact],
522) -> OmenaQuerySassSymbolResolutionV0 {
523    let mut declaration_by_symbol: BTreeMap<
524        (&'static str, Option<String>, String),
525        (usize, &'static str),
526    > = BTreeMap::new();
527    let mut declaration_count = 0usize;
528    let mut reference_count = 0usize;
529    let mut edges = Vec::new();
530
531    for (source_order, symbol) in symbols.iter().enumerate() {
532        let kind = omena_query_sass_symbol_fact_kind_label(symbol.kind);
533        if omena_query_sass_symbol_fact_kind_is_declaration(symbol.kind) {
534            declaration_count += 1;
535            declaration_by_symbol.insert(
536                (
537                    symbol.symbol_kind,
538                    symbol.namespace.clone(),
539                    symbol.name.clone(),
540                ),
541                (source_order, kind),
542            );
543            continue;
544        }
545        if !omena_query_sass_symbol_fact_kind_is_reference(symbol.kind) {
546            continue;
547        }
548
549        reference_count += 1;
550        let declaration = declaration_by_symbol.get(&(
551            symbol.symbol_kind,
552            symbol.namespace.clone(),
553            symbol.name.clone(),
554        ));
555        edges.push(OmenaQuerySassSymbolResolutionEdgeV0 {
556            symbol_kind: symbol.symbol_kind,
557            name: symbol.name.clone(),
558            namespace: symbol.namespace.clone(),
559            reference_kind: kind,
560            reference_role: symbol.role,
561            reference_source_order: source_order,
562            declaration_kind: declaration.map(|(_, declaration_kind)| *declaration_kind),
563            declaration_source_order: declaration.map(|(declaration_order, _)| *declaration_order),
564            status: if declaration.is_some() {
565                "resolved"
566            } else {
567                "unresolved"
568            },
569        });
570    }
571
572    let resolved_reference_count = edges
573        .iter()
574        .filter(|edge| edge.status == "resolved")
575        .count();
576    OmenaQuerySassSymbolResolutionV0 {
577        schema_version: "0",
578        product: "omena-query.sass-symbol-same-file-resolution",
579        resolution_scope: "same-file",
580        declaration_count,
581        reference_count,
582        resolved_reference_count,
583        unresolved_reference_count: reference_count.saturating_sub(resolved_reference_count),
584        edges,
585        capabilities: OmenaQuerySassSymbolResolutionCapabilitiesV0 {
586            same_file_lexical_resolution_ready: true,
587            declaration_before_reference_ready: true,
588            unresolved_reference_reporting_ready: true,
589            cross_file_module_resolution_ready: false,
590        },
591    }
592}
593
594pub(super) fn omena_query_sass_symbol_fact_kind_is_declaration(
595    kind: ParsedSassSymbolFactKind,
596) -> bool {
597    matches!(
598        kind,
599        ParsedSassSymbolFactKind::VariableDeclaration
600            | ParsedSassSymbolFactKind::MixinDeclaration
601            | ParsedSassSymbolFactKind::FunctionDeclaration
602    )
603}
604
605pub(super) fn omena_query_sass_symbol_fact_kind_is_reference(
606    kind: ParsedSassSymbolFactKind,
607) -> bool {
608    matches!(
609        kind,
610        ParsedSassSymbolFactKind::VariableReference
611            | ParsedSassSymbolFactKind::MixinInclude
612            | ParsedSassSymbolFactKind::FunctionCall
613    )
614}
615
616/// Test-only per-thread counter of raw style-fact collections, so dedup contracts
617/// ("one parse per (path, content)") are assertable instead of assumed.
618#[cfg(test)]
619pub(crate) mod style_facts_collect_probe {
620    use std::cell::Cell;
621
622    thread_local! {
623        static COLLECT_CALLS: Cell<usize> = const { Cell::new(0) };
624    }
625
626    pub(crate) fn reset() {
627        COLLECT_CALLS.with(|calls| calls.set(0));
628    }
629
630    pub(crate) fn count() -> usize {
631        COLLECT_CALLS.with(Cell::get)
632    }
633
634    pub(super) fn record() {
635        COLLECT_CALLS.with(|calls| calls.set(calls.get() + 1));
636    }
637}