Skip to main content

fallow_extract/css_in_js/
tokens.rs

1//! CSS-in-JS design-token DEFINITION walker for the design-token blast-radius
2//! (CSS program Phase 3d).
3//!
4//! The zero-runtime CSS-in-JS libraries declare design tokens as a JS OBJECT
5//! passed to a library call, binding the token surface to an exported identifier
6//! that consumers read via member access (`import { vars } from './tokens';
7//! vars.color.primary`). This module is the DEFINITION half of the token
8//! blast-radius: it parses JS/TS with oxc, gates recognition on import-binding
9//! provenance (reusing the sibling `object::module_library`), and for each
10//! recognized token-definition call emits the access BINDING plus the flattened
11//! dotted LEAF token paths (with each leaf's source line). The CONSUMER half (who
12//! reads `vars.color.primary` across modules) is resolved in the analyze layer
13//! against the module graph; this walker only produces the defined-token side.
14//!
15//! Health-time-only, like the 3b/3c CSS-in-JS lifters: it runs over file SOURCE
16//! and persists nothing to the extraction cache (no `CACHE_VERSION` bump).
17//!
18//! # Recognized definition shapes
19//!
20//! Recognition is gated on the callee binding being imported from a recognized
21//! token library in THIS file (a local `defineVars` helper or an unrelated
22//! `createTheme` never fires):
23//!
24//! - StyleX `stylex.defineVars({...})` (namespace member call) or
25//!   `defineVars({...})` (named import). Binding = the assigned identifier and
26//!   every top-level key is one token, including conditional object values.
27//! - StyleX `unstable_defineVarsNested({...})`: binding = the assigned identifier;
28//!   namespace objects recurse while conditional objects and calls remain leaves.
29//! - vanilla-extract `createThemeContract({...})`: binding = the assigned
30//!   identifier (the contract IS the vars surface consumers read).
31//! - vanilla-extract `createTheme({...})` (1-arg): returns `[themeClass, vars]`;
32//!   binding = the SECOND array-destructure element (`vars`); `themeClass` is a
33//!   class string, not a token surface.
34//! - vanilla-extract `createGlobalTheme(selector, {...})` (2-arg): returns the
35//!   vars object; binding = the assigned identifier.
36//! - PandaCSS `defineTokens({...})`: binding = the assigned identifier; token
37//!   objects with a `value` field collapse to the token path (`colors.brand`),
38//!   matching `token('colors.brand')` consumers.
39//! - PandaCSS `defineConfig({ theme: { tokens, semanticTokens } })`: binding =
40//!   `pandaConfig`; only static token object literals are read.
41//!
42//! The two CONTRACT-IMPLEMENTATION forms are deliberately NOT definition sites
43//! here, because the contract they fill was already declared by
44//! `createThemeContract` (captured above) and that is the binding consumers read:
45//! - `createTheme(contract, {...})` (2-arg) returns a class string; tokens fill
46//!   the existing `contract`.
47//! - `createGlobalTheme(selector, contract, {...})` (3-arg) returns void.
48//!
49use std::path::Path;
50
51use oxc_allocator::Allocator;
52use oxc_ast::{
53    AstKind,
54    ast::{
55        Argument, ArrowFunctionExpression, AssignmentExpression, AssignmentTarget,
56        AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern, BlockStatement,
57        CallExpression, ComputedMemberExpression, Declaration, Expression, Function,
58        IdentifierReference, ImportDeclarationSpecifier, NumericLiteral, ObjectExpression,
59        ObjectPropertyKind, Program, SimpleAssignmentTarget, Statement, StaticMemberExpression,
60        UnaryExpression, UnaryOperator, UpdateExpression, VariableDeclarationKind,
61        VariableDeclarator,
62    },
63};
64use oxc_ast_visit::{Visit, walk};
65use oxc_parser::Parser;
66use oxc_semantic::{ReferenceId, ScopeFlags, Scoping, SemanticBuilder, SymbolId};
67use oxc_span::{GetSpan, SourceType, Span};
68use rustc_hash::{FxHashMap, FxHashSet};
69
70use super::object::{Lib, module_library};
71
72const PANDA_CONFIG_BINDING: &str = "pandaConfig";
73
74/// A single defined design token: its dotted LEAF path relative to the access
75/// binding (`color.primary`, or flat `primaryColor` for StyleX), the 1-based
76/// source line of its key, and the static value when the literal is recoverable.
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct CssInJsToken {
79    /// Dotted leaf path relative to the binding (e.g. `color.primary`).
80    pub path: String,
81    /// 1-based line of the token's key in the defining source.
82    pub def_line: u32,
83    /// Static token value for literal definitions. Dynamic expressions and
84    /// contract-only leaves have no value.
85    pub value: Option<String>,
86}
87
88/// A CSS-in-JS token-definition site: the exported access binding consumers read
89/// through (e.g. `vars`) and the flattened leaf tokens it defines.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct CssInJsTokenDef {
92    /// The identifier the token surface is bound to (`vars`), the receiver of
93    /// cross-module member access (`vars.color.primary`).
94    pub binding: String,
95    /// Which CSS-in-JS family defined the tokens.
96    pub origin: CssInJsTokenOrigin,
97    /// The flattened leaf tokens defined on `binding`.
98    pub tokens: Vec<CssInJsToken>,
99}
100
101/// The CSS-in-JS token system that produced a token definition.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum CssInJsTokenOrigin {
104    /// StyleX `defineVars`.
105    StyleX,
106    /// vanilla-extract `createTheme` family definitions.
107    VanillaExtract,
108    /// PandaCSS `defineTokens`.
109    Panda,
110    /// styled-components / Emotion theme object definitions.
111    Theme,
112}
113
114/// Walk a JS/TS source for CSS-in-JS design-token DEFINITIONS, returning each
115/// access binding and its flattened leaf token paths. Empty when the source has
116/// no recognized token-library import (provenance gate closed).
117#[must_use]
118pub fn css_in_js_token_defs(source: &str, path: &Path) -> Vec<CssInJsTokenDef> {
119    let source_type = SourceType::from_path(path).unwrap_or_default();
120    let allocator = Allocator::default();
121    let ret = Parser::new(&allocator, source, source_type).parse();
122
123    let mut collector = TokenDefCollector::new(source);
124    collector.build_import_map(&ret.program);
125    if collector.imports.is_empty() {
126        return Vec::new();
127    }
128    let semantic_return = SemanticBuilder::new().build(&ret.program);
129    collector.build_const_object_map(&ret.program, semantic_return.semantic.scoping());
130    collector.collecting_mutations = true;
131    collector.visit_program(&ret.program);
132    collector.collecting_mutations = false;
133    collector.visit_program(&ret.program);
134    collector.defs
135}
136
137/// One located consumer of a CSS-in-JS token: the defined LEAF token path it
138/// reads (relative to the binding, e.g. `color.primary`) and the 1-based line of
139/// the member-access site.
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct TokenConsumerHit {
142    /// The defined leaf token path consumed (`color.primary`), relative to the
143    /// access binding (the leading binding segment stripped).
144    pub token_path: String,
145    /// 1-based line of the member-access site in the consuming source.
146    pub line: u32,
147}
148
149/// Walk a JS/TS source for statically-authored theme object definitions used by
150/// styled-components and Emotion. A `theme` or `*Theme` variable with an object
151/// literal initializer becomes a token surface, with nested scalar leaves exposed
152/// as dotted paths.
153#[must_use]
154pub fn css_in_js_theme_token_defs(source: &str, path: &Path) -> Vec<CssInJsTokenDef> {
155    let source_type = SourceType::from_path(path).unwrap_or_default();
156    let allocator = Allocator::default();
157    let ret = Parser::new(&allocator, source, source_type).parse();
158
159    let mut collector = ThemeDefCollector {
160        lines: LineCounter::new(source),
161        defs: Vec::new(),
162    };
163    collector.visit_program(&ret.program);
164    collector.defs
165}
166
167/// One attribution query to run against a single parsed consumer source. A scan
168/// runs any mix of queries against ONE parse of the source.
169pub enum ConsumerQuery<'a> {
170    /// Member-access reads `<alias>.a.b` of an imported token binding. A read is
171    /// a hit only when `a.b` is a defined leaf path, not an intermediate group.
172    MemberBinding {
173        /// The local identifier the token binding was imported under.
174        alias: &'a str,
175        /// The defined leaf token paths (`color.primary`).
176        leaf_paths: &'a FxHashSet<String>,
177    },
178    /// StyleX `createTheme(contract, ...)` applies the complete resolved
179    /// variable group, including partial and empty reset themes.
180    StyleXThemeGroup {
181        /// Local identifier of the resolved StyleX variable contract.
182        contract_alias: &'a str,
183        /// Every defined leaf in that contract group.
184        leaf_paths: &'a FxHashSet<String>,
185    },
186    /// PandaCSS `token('a.b')` calls through the given alias.
187    PandaTokenCall {
188        /// The local alias imported from Panda's generated token module.
189        alias: &'a str,
190        /// The defined leaf token paths (`colors.brand`).
191        leaf_paths: &'a FxHashSet<String>,
192    },
193    /// PandaCSS style-call object values naming token paths.
194    PandaStyleValues {
195        /// The local aliases for Panda style calls (`css`, `cva`).
196        aliases: &'a FxHashSet<String>,
197        /// The defined leaf token paths (`colors.brand`).
198        leaf_paths: &'a FxHashSet<String>,
199    },
200    /// styled-components / Emotion theme reads (`theme.colors.x`), including
201    /// `props.theme.colors.x`.
202    ThemeReads {
203        /// The defined leaf token paths (`colors.brand`).
204        leaf_paths: &'a FxHashSet<String>,
205    },
206}
207
208impl ConsumerQuery<'_> {
209    /// `true` for the queries that `BatchedBindingCollector` runs in one walk.
210    const fn is_batched(&self) -> bool {
211        matches!(
212            self,
213            Self::MemberBinding { .. } | Self::StyleXThemeGroup { .. }
214        )
215    }
216}
217
218/// Parse `source` once and run every query against the same AST, returning
219/// `(query_index, hit)` pairs so the caller can attribute each hit back to the
220/// definer that produced its query. A query with an empty alias or an empty
221/// leaf set contributes no hits and does not suppress the other queries.
222#[must_use]
223pub fn css_in_js_consumer_scan(
224    source: &str,
225    path: &Path,
226    queries: &[ConsumerQuery<'_>],
227) -> Vec<(usize, TokenConsumerHit)> {
228    if queries.is_empty() {
229        return Vec::new();
230    }
231    let source_type = SourceType::from_path(path).unwrap_or_default();
232    let allocator = Allocator::default();
233    let ret = Parser::new(&allocator, source, source_type).parse();
234    let mut out = Vec::new();
235    if queries.iter().any(ConsumerQuery::is_batched) {
236        let mut collector = BatchedBindingCollector {
237            lines: LineCounter::new(source),
238            queries,
239            namespaces: FxHashSet::default(),
240            theme_functions: FxHashSet::default(),
241            stylex_imports: FxHashMap::default(),
242            member_queries: FxHashMap::default(),
243            theme_queries: FxHashMap::default(),
244            root_reference_spans: FxHashSet::default(),
245            static_values: FxHashMap::default(),
246            static_alias_neighbors: FxHashMap::default(),
247            collecting_mutations: false,
248            hits: Vec::new(),
249        };
250        collector.build_import_map(&ret.program);
251        collector.build_query_indexes();
252        collector.build_static_value_map(&ret.program);
253        collector.build_root_reference_spans(&ret.program);
254        collector.collecting_mutations = true;
255        collector.visit_program(&ret.program);
256        collector.collecting_mutations = false;
257        collector.visit_program(&ret.program);
258        out.extend(collector.hits);
259    }
260    for (idx, query) in queries.iter().enumerate() {
261        if !query.is_batched() {
262            run_consumer_query(query, source, &ret.program, idx, &mut out);
263        }
264    }
265    out
266}
267
268struct BatchedBindingCollector<'a, 'q, 'v> {
269    lines: LineCounter<'a>,
270    queries: &'q [ConsumerQuery<'v>],
271    namespaces: FxHashSet<&'a str>,
272    theme_functions: FxHashSet<&'a str>,
273    stylex_imports: FxHashMap<&'a str, (Lib, &'a str)>,
274    member_queries: FxHashMap<&'v str, Vec<(usize, &'v FxHashSet<String>)>>,
275    theme_queries: FxHashMap<&'v str, Vec<(usize, &'v FxHashSet<String>)>>,
276    root_reference_spans: FxHashSet<Span>,
277    static_values: FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
278    static_alias_neighbors: FxHashMap<&'a str, FxHashSet<&'a str>>,
279    collecting_mutations: bool,
280    hits: Vec<(usize, TokenConsumerHit)>,
281}
282
283impl<'a> BatchedBindingCollector<'a, '_, '_> {
284    fn build_import_map(&mut self, program: &'a Program<'a>) {
285        for stmt in &program.body {
286            let Statement::ImportDeclaration(decl) = stmt else {
287                continue;
288            };
289            if decl.import_kind.is_type()
290                || module_library(decl.source.value.as_str()) != Some(Lib::StyleX)
291            {
292                continue;
293            }
294            let Some(specifiers) = &decl.specifiers else {
295                continue;
296            };
297            for specifier in specifiers {
298                match specifier {
299                    ImportDeclarationSpecifier::ImportSpecifier(specifier)
300                        if !specifier.import_kind.is_type() =>
301                    {
302                        let local = specifier.local.name.as_str();
303                        let role = specifier.imported.name().as_str();
304                        self.stylex_imports.insert(local, (Lib::StyleX, role));
305                        if matches!(role, "createTheme" | "unstable_createThemeNested") {
306                            self.theme_functions.insert(local);
307                        }
308                    }
309                    ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => {
310                        let local = specifier.local.name.as_str();
311                        self.namespaces.insert(local);
312                        self.stylex_imports.insert(local, (Lib::StyleX, local));
313                    }
314                    ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => {
315                        let local = specifier.local.name.as_str();
316                        self.namespaces.insert(local);
317                        self.stylex_imports.insert(local, (Lib::StyleX, local));
318                    }
319                    ImportDeclarationSpecifier::ImportSpecifier(_) => {}
320                }
321            }
322        }
323    }
324
325    fn build_query_indexes(&mut self) {
326        for (idx, query) in self.queries.iter().enumerate() {
327            match query {
328                ConsumerQuery::MemberBinding { alias, leaf_paths } if !alias.is_empty() => {
329                    self.member_queries
330                        .entry(alias)
331                        .or_default()
332                        .push((idx, leaf_paths));
333                }
334                ConsumerQuery::StyleXThemeGroup {
335                    contract_alias,
336                    leaf_paths,
337                } if !contract_alias.is_empty() => {
338                    self.theme_queries
339                        .entry(contract_alias)
340                        .or_default()
341                        .push((idx, leaf_paths));
342                }
343                _ => {}
344            }
345        }
346    }
347
348    fn build_root_reference_spans(&mut self, program: &'a Program<'a>) {
349        let mut names: FxHashSet<&str> = self.member_queries.keys().copied().collect();
350        names.extend(self.theme_queries.keys().copied());
351        names.extend(self.namespaces.iter().copied());
352        names.extend(self.theme_functions.iter().copied());
353        names.extend(self.stylex_imports.keys().copied());
354        names.extend(self.static_values.keys().copied());
355
356        let semantic_return = SemanticBuilder::new().with_build_nodes(true).build(program);
357        let semantic = semantic_return.semantic;
358        let scoping = semantic.scoping();
359        let root_scope = scoping.root_scope_id();
360        for name in names {
361            if let Some(symbol_id) = scoping.get_binding(root_scope, oxc_str::Ident::from(name)) {
362                for reference in scoping.get_resolved_references(symbol_id) {
363                    if let AstKind::IdentifierReference(identifier) =
364                        semantic.nodes().kind(reference.node_id())
365                    {
366                        self.root_reference_spans.insert(identifier.span);
367                    }
368                }
369            }
370            if let Some(reference_ids) = scoping.root_unresolved_references().get(name) {
371                for reference_id in reference_ids {
372                    let reference = scoping.get_reference(*reference_id);
373                    if let AstKind::IdentifierReference(identifier) =
374                        semantic.nodes().kind(reference.node_id())
375                    {
376                        self.root_reference_spans.insert(identifier.span);
377                    }
378                }
379            }
380        }
381        for name in ["String", "Number", "Math", "Object", "Array"] {
382            if let Some(reference_ids) = scoping.root_unresolved_references().get(name) {
383                for reference_id in reference_ids {
384                    let reference = scoping.get_reference(*reference_id);
385                    if let AstKind::IdentifierReference(identifier) =
386                        semantic.nodes().kind(reference.node_id())
387                    {
388                        self.root_reference_spans.insert(identifier.span);
389                    }
390                }
391            }
392        }
393    }
394
395    fn build_static_value_map(&mut self, program: &'a Program<'a>) {
396        for stmt in &program.body {
397            let declaration = match stmt {
398                Statement::VariableDeclaration(declaration) => Some(&**declaration),
399                Statement::ExportDeclaration(export) => match &export.declaration {
400                    Declaration::VariableDeclaration(declaration) => Some(&**declaration),
401                    _ => None,
402                },
403                _ => None,
404            };
405            let Some(declaration) = declaration else {
406                continue;
407            };
408            if declaration.kind != VariableDeclarationKind::Const {
409                continue;
410            }
411            for declarator in &declaration.declarations {
412                let BindingPattern::BindingIdentifier(binding) = &declarator.id else {
413                    continue;
414                };
415                if let Some(init) = &declarator.init {
416                    self.static_values
417                        .insert(binding.name.as_str(), (declarator.span.start, init));
418                    if let Some(alias) = expression_root_binding(init) {
419                        self.static_alias_neighbors
420                            .entry(binding.name.as_str())
421                            .or_default()
422                            .insert(alias.name);
423                        self.static_alias_neighbors
424                            .entry(alias.name)
425                            .or_default()
426                            .insert(binding.name.as_str());
427                    }
428                }
429            }
430        }
431    }
432
433    fn is_theme_callee(&self, callee: &Expression<'a>) -> bool {
434        match callee {
435            Expression::Identifier(id) => {
436                self.root_reference_spans.contains(&id.span)
437                    && self.theme_functions.contains(id.name.as_str())
438            }
439            Expression::StaticMemberExpression(member) => {
440                let Expression::Identifier(object) = unwrap_transparent_expression(&member.object)
441                else {
442                    return false;
443                };
444                self.root_reference_spans.contains(&object.span)
445                    && self.namespaces.contains(object.name.as_str())
446                    && matches!(
447                        member.property.name.as_str(),
448                        "createTheme" | "unstable_createThemeNested"
449                    )
450            }
451            _ => false,
452        }
453    }
454
455    fn record_member(&mut self, chain: Option<(&str, Span, Vec<String>)>, span_start: u32) {
456        let Some((base, base_span, segments)) = chain else {
457            return;
458        };
459        if segments.is_empty() || !self.root_reference_spans.contains(&base_span) {
460            return;
461        }
462        let token_path = segments.join(".");
463        if let Some(queries) = self.member_queries.get(base) {
464            for &(idx, leaf_paths) in queries {
465                if !leaf_paths.contains(&token_path) {
466                    continue;
467                }
468                let line = self.lines.line_at(span_start);
469                self.hits.push((
470                    idx,
471                    TokenConsumerHit {
472                        token_path: token_path.clone(),
473                        line,
474                    },
475                ));
476            }
477        }
478    }
479
480    fn invalidate_static_value(&mut self, binding: Option<RootBinding<'_>>) {
481        if let Some(binding) = binding
482            && self.root_reference_spans.contains(&binding.span)
483        {
484            let mut pending = vec![binding.name];
485            let mut invalidated = FxHashSet::default();
486            while let Some(name) = pending.pop() {
487                if !invalidated.insert(name) {
488                    continue;
489                }
490                self.static_values.remove(name);
491                if let Some(neighbors) = self.static_alias_neighbors.get(name) {
492                    pending.extend(neighbors.iter().copied());
493                }
494            }
495        }
496    }
497
498    fn mutation_root_binding(
499        &self,
500        expression: &'a Expression<'a>,
501        before: u32,
502        visiting: &mut FxHashSet<&'a str>,
503    ) -> Option<RootBinding<'a>> {
504        if let Some(binding) = expression_root_binding(expression) {
505            return Some(binding);
506        }
507        let Expression::CallExpression(call) = unwrap_transparent_expression(expression) else {
508            return None;
509        };
510        let Expression::Identifier(callee) = unwrap_transparent_expression(&call.callee) else {
511            return None;
512        };
513        if !self.root_reference_spans.contains(&callee.span) {
514            return None;
515        }
516        let name = callee.name.as_str();
517        let &(declaration_start, value) = self.static_values.get(name)?;
518        let Expression::ArrowFunctionExpression(arrow) = unwrap_transparent_expression(value)
519        else {
520            return None;
521        };
522        if declaration_start >= before
523            || arrow.r#async
524            || arrow.params.rest.is_some()
525            || arrow.params.items.len() != call.arguments.len()
526            || !visiting.insert(name)
527        {
528            return None;
529        }
530        let Some(body) = stylex_arrow_expression_body(arrow) else {
531            visiting.remove(name);
532            return None;
533        };
534        let resolved = expression_root_binding(body)
535            .and_then(|body_root| {
536                arrow.params.items.iter().position(|parameter| {
537                    matches!(
538                        &parameter.pattern,
539                        BindingPattern::BindingIdentifier(binding)
540                            if binding.name.as_str() == body_root.name
541                    )
542                })
543            })
544            .and_then(|index| call.arguments.get(index))
545            .and_then(Argument::as_expression)
546            .and_then(|argument| self.mutation_root_binding(argument, before, visiting))
547            .or_else(|| self.mutation_root_binding(body, declaration_start, visiting));
548        visiting.remove(name);
549        resolved
550    }
551
552    fn binding_is_definitely_primitive(&self, binding: RootBinding<'_>, before: u32) -> bool {
553        let Some(&(declaration_start, value)) = self.static_values.get(binding.name) else {
554            return false;
555        };
556        declaration_start < before
557            && is_definitely_static_primitive(
558                value,
559                declaration_start,
560                &self.static_values,
561                &self.root_reference_spans,
562                &mut FxHashSet::default(),
563            )
564    }
565
566    fn record_theme_call(&mut self, call: &CallExpression<'a>) {
567        if call.arguments.len() != 2 || !self.is_theme_callee(&call.callee) {
568            return;
569        }
570        let Some(contract_expression) = call.arguments.first().and_then(Argument::as_expression)
571        else {
572            return;
573        };
574        let Some(contract) = self.resolve_theme_contract_alias(
575            contract_expression,
576            call.span.start,
577            &mut FxHashSet::default(),
578        ) else {
579            return;
580        };
581        let Some(overrides) = call.arguments.get(1).and_then(Argument::as_expression) else {
582            return;
583        };
584        if !is_static_stylex_theme_override_object(
585            overrides,
586            call.span.start,
587            &self.static_values,
588            &self.stylex_imports,
589            &self.root_reference_spans,
590            &mut FxHashSet::default(),
591        ) {
592            return;
593        }
594        let Some(queries) = self.theme_queries.get(contract) else {
595            return;
596        };
597        let line = self.lines.line_at(call.span().start);
598        for &(idx, leaf_paths) in queries {
599            self.hits.extend(
600                leaf_paths
601                    .iter()
602                    .cloned()
603                    .map(|token_path| (idx, TokenConsumerHit { token_path, line })),
604            );
605        }
606    }
607
608    fn resolve_theme_contract_alias(
609        &self,
610        expression: &'a Expression<'a>,
611        before: u32,
612        visiting: &mut FxHashSet<&'a str>,
613    ) -> Option<&'a str> {
614        let Expression::Identifier(identifier) = unwrap_transparent_expression(expression) else {
615            return None;
616        };
617        if !self.root_reference_spans.contains(&identifier.span) {
618            return None;
619        }
620        let name = identifier.name.as_str();
621        if self.theme_queries.contains_key(name) {
622            return Some(name);
623        }
624        let &(declaration_start, value) = self.static_values.get(name)?;
625        if declaration_start >= before || !visiting.insert(name) {
626            return None;
627        }
628        let resolved = self.resolve_theme_contract_alias(value, declaration_start, visiting);
629        visiting.remove(name);
630        resolved
631    }
632}
633
634impl<'a> Visit<'a> for BatchedBindingCollector<'a, '_, '_> {
635    fn visit_static_member_expression(&mut self, member: &StaticMemberExpression<'a>) {
636        let mut chain = binding_access_object_chain(&member.object);
637        if let Some((_, _, segments)) = chain.as_mut() {
638            segments.push(member.property.name.to_string());
639        }
640        if !self.collecting_mutations {
641            self.record_member(chain, member.span().start);
642        }
643        walk::walk_static_member_expression(self, member);
644    }
645
646    fn visit_computed_member_expression(&mut self, member: &ComputedMemberExpression<'a>) {
647        let mut chain = binding_access_object_chain(&member.object);
648        if let (Some((_, _, segments)), Some(key)) =
649            (chain.as_mut(), static_computed_key(&member.expression))
650        {
651            segments.push(key);
652        } else {
653            chain = None;
654        }
655        if !self.collecting_mutations {
656            self.record_member(chain, member.span().start);
657        }
658        walk::walk_computed_member_expression(self, member);
659    }
660
661    fn visit_variable_declarator(&mut self, declaration: &VariableDeclarator<'a>) {
662        if !self.collecting_mutations
663            && let (BindingPattern::BindingIdentifier(_), Some(Expression::CallExpression(call))) =
664                (&declaration.id, declaration.init.as_ref())
665        {
666            self.record_theme_call(call);
667        }
668        walk::walk_variable_declarator(self, declaration);
669    }
670
671    fn visit_assignment_expression(&mut self, assignment: &AssignmentExpression<'a>) {
672        if self.collecting_mutations {
673            for binding in assignment_target_root_bindings(&assignment.left) {
674                self.invalidate_static_value(Some(binding));
675            }
676            if let Some(receiver) = assignment_target_receiver_expression(&assignment.left) {
677                let binding = self.mutation_root_binding(
678                    receiver,
679                    assignment.span.start,
680                    &mut FxHashSet::default(),
681                );
682                self.invalidate_static_value(binding);
683            }
684        }
685        walk::walk_assignment_expression(self, assignment);
686    }
687
688    fn visit_update_expression(&mut self, update: &UpdateExpression<'a>) {
689        if self.collecting_mutations {
690            self.invalidate_static_value(simple_assignment_target_root_binding(&update.argument));
691        }
692        walk::walk_update_expression(self, update);
693    }
694
695    fn visit_unary_expression(&mut self, expression: &UnaryExpression<'a>) {
696        if self.collecting_mutations && expression.operator.is_delete() {
697            self.invalidate_static_value(expression_root_binding(&expression.argument));
698        }
699        walk::walk_unary_expression(self, expression);
700    }
701
702    fn visit_call_expression(&mut self, call: &CallExpression<'a>) {
703        if !self.collecting_mutations {
704            walk::walk_call_expression(self, call);
705            return;
706        }
707        let mut visiting = FxHashSet::default();
708        let is_stylex_helper = call.arguments.len() == 1
709            && is_root_stylex_static_call(
710                &call.callee,
711                &self.stylex_imports,
712                &self.root_reference_spans,
713            )
714            && call
715                .arguments
716                .first()
717                .and_then(Argument::as_expression)
718                .is_some_and(|argument| {
719                    is_static_stylex_theme_override(
720                        argument,
721                        call.span.start,
722                        &self.static_values,
723                        &self.stylex_imports,
724                        &self.root_reference_spans,
725                        &mut visiting,
726                    )
727                });
728        let is_pure = is_stylex_helper
729            || is_static_stylex_pure_call(
730                call,
731                call.span.start,
732                &self.static_values,
733                &self.stylex_imports,
734                &self.root_reference_spans,
735                &mut visiting,
736            );
737        if !self.is_theme_callee(&call.callee) && !is_pure {
738            if let Some(receiver) = call_receiver_expression(&call.callee) {
739                let binding = self.mutation_root_binding(
740                    receiver,
741                    call.span.start,
742                    &mut FxHashSet::default(),
743                );
744                self.invalidate_static_value(binding);
745            }
746            let possibly_mutated: Vec<RootBinding<'_>> = call
747                .arguments
748                .iter()
749                .filter_map(Argument::as_expression)
750                .filter_map(|argument| {
751                    self.mutation_root_binding(argument, call.span.start, &mut FxHashSet::default())
752                })
753                .collect();
754            for binding in possibly_mutated {
755                if !self.binding_is_definitely_primitive(binding, call.span.start) {
756                    self.invalidate_static_value(Some(binding));
757                }
758            }
759        }
760        walk::walk_call_expression(self, call);
761    }
762}
763
764/// Run one non-batched [`ConsumerQuery`] against an already-parsed `program`,
765/// and tag each resulting hit with `idx`. A query with an empty alias or an
766/// empty leaf set gives no hits.
767fn run_consumer_query<'a>(
768    query: &ConsumerQuery<'_>,
769    source: &'a str,
770    program: &Program<'a>,
771    idx: usize,
772    out: &mut Vec<(usize, TokenConsumerHit)>,
773) {
774    match query {
775        // `css_in_js_consumer_scan` runs these queries in `BatchedBindingCollector`.
776        ConsumerQuery::MemberBinding { .. } | ConsumerQuery::StyleXThemeGroup { .. } => {}
777        ConsumerQuery::PandaTokenCall { alias, leaf_paths } => {
778            if alias.is_empty() || leaf_paths.is_empty() {
779                return;
780            }
781            let mut collector = PandaTokenCallCollector {
782                lines: LineCounter::new(source),
783                alias,
784                leaf_paths,
785                hits: Vec::new(),
786            };
787            collector.visit_program(program);
788            out.extend(collector.hits.into_iter().map(|hit| (idx, hit)));
789        }
790        ConsumerQuery::PandaStyleValues {
791            aliases,
792            leaf_paths,
793        } => {
794            if aliases.is_empty() || leaf_paths.is_empty() {
795                return;
796            }
797            let mut collector = PandaStyleValueCollector {
798                lines: LineCounter::new(source),
799                aliases,
800                leaf_paths,
801                hits: Vec::new(),
802            };
803            collector.visit_program(program);
804            out.extend(collector.hits.into_iter().map(|hit| (idx, hit)));
805        }
806        ConsumerQuery::ThemeReads { leaf_paths } => {
807            if leaf_paths.is_empty() {
808                return;
809            }
810            let mut collector = ThemeConsumerCollector {
811                lines: LineCounter::new(source),
812                leaf_paths,
813                hits: Vec::new(),
814            };
815            collector.visit_program(program);
816            out.extend(collector.hits.into_iter().map(|hit| (idx, hit)));
817        }
818    }
819}
820
821struct PandaTokenCallCollector<'a, 'b> {
822    lines: LineCounter<'a>,
823    alias: &'b str,
824    leaf_paths: &'b FxHashSet<String>,
825    hits: Vec<TokenConsumerHit>,
826}
827
828impl<'a> Visit<'a> for PandaTokenCallCollector<'a, '_> {
829    fn visit_call_expression(&mut self, call: &oxc_ast::ast::CallExpression<'a>) {
830        let Expression::Identifier(callee) = &call.callee else {
831            walk::walk_call_expression(self, call);
832            return;
833        };
834        if callee.name.as_str() == self.alias
835            && let Some(Argument::StringLiteral(lit)) = call.arguments.first()
836        {
837            let token_path = lit.value.as_str();
838            if self.leaf_paths.contains(token_path) {
839                let line = self.lines.line_at(call.span().start);
840                self.hits.push(TokenConsumerHit {
841                    token_path: token_path.to_owned(),
842                    line,
843                });
844            }
845        }
846        walk::walk_call_expression(self, call);
847    }
848}
849
850struct PandaStyleValueCollector<'a, 'b> {
851    lines: LineCounter<'a>,
852    aliases: &'b FxHashSet<String>,
853    leaf_paths: &'b FxHashSet<String>,
854    hits: Vec<TokenConsumerHit>,
855}
856
857impl<'a> PandaStyleValueCollector<'a, '_> {
858    fn record_object(&mut self, obj: &ObjectExpression<'a>) {
859        for prop in &obj.properties {
860            let ObjectPropertyKind::ObjectProperty(prop) = prop else {
861                continue;
862            };
863            self.record_expression(&prop.value);
864        }
865    }
866
867    fn record_expression(&mut self, expr: &Expression<'a>) {
868        match expr {
869            Expression::StringLiteral(lit) => {
870                let token_path = lit.value.as_str();
871                if self.leaf_paths.contains(token_path) {
872                    let line = self.lines.line_at(lit.span().start);
873                    self.hits.push(TokenConsumerHit {
874                        token_path: token_path.to_owned(),
875                        line,
876                    });
877                }
878            }
879            Expression::ObjectExpression(obj) => self.record_object(obj),
880            _ => {}
881        }
882    }
883}
884
885impl<'a> Visit<'a> for PandaStyleValueCollector<'a, '_> {
886    fn visit_call_expression(&mut self, call: &oxc_ast::ast::CallExpression<'a>) {
887        let Expression::Identifier(callee) = &call.callee else {
888            walk::walk_call_expression(self, call);
889            return;
890        };
891        if self.aliases.contains(callee.name.as_str()) {
892            for arg in &call.arguments {
893                if let Argument::ObjectExpression(obj) = arg {
894                    self.record_object(obj);
895                }
896            }
897        }
898        walk::walk_call_expression(self, call);
899    }
900}
901
902struct ThemeDefCollector<'a> {
903    lines: LineCounter<'a>,
904    defs: Vec<CssInJsTokenDef>,
905}
906
907impl<'a> ThemeDefCollector<'a> {
908    fn process_declarator(&mut self, decl: &VariableDeclarator<'a>) {
909        let BindingPattern::BindingIdentifier(binding) = &decl.id else {
910            return;
911        };
912        let binding_name = binding.name.as_str();
913        if !is_theme_binding_name(binding_name) {
914            return;
915        }
916        let Some(Expression::ObjectExpression(obj)) = &decl.init else {
917            return;
918        };
919        let mut tokens = Vec::new();
920        collect_token_leaves(
921            &mut self.lines,
922            obj,
923            "",
924            CssInJsTokenOrigin::Theme,
925            &mut tokens,
926        );
927        if tokens.is_empty() {
928            return;
929        }
930        self.defs.push(CssInJsTokenDef {
931            binding: binding_name.to_owned(),
932            origin: CssInJsTokenOrigin::Theme,
933            tokens,
934        });
935    }
936}
937
938impl<'a> Visit<'a> for ThemeDefCollector<'a> {
939    fn visit_variable_declarator(&mut self, decl: &VariableDeclarator<'a>) {
940        self.process_declarator(decl);
941        walk::walk_variable_declarator(self, decl);
942    }
943}
944
945struct ThemeConsumerCollector<'a, 'b> {
946    lines: LineCounter<'a>,
947    leaf_paths: &'b FxHashSet<String>,
948    hits: Vec<TokenConsumerHit>,
949}
950
951impl<'a> ThemeConsumerCollector<'a, '_> {
952    fn record(&mut self, chain: Option<(&'a str, Vec<String>)>, span_start: u32) {
953        let Some((base, segments)) = chain else {
954            return;
955        };
956        let token_segments: &[String] = match base {
957            "theme" => &segments,
958            "props" if segments.first().is_some_and(|segment| segment == "theme") => &segments[1..],
959            _ => return,
960        };
961        if token_segments.is_empty() {
962            return;
963        }
964        let token_path = token_segments.join(".");
965        if self.leaf_paths.contains(&token_path) {
966            let line = self.lines.line_at(span_start);
967            self.hits.push(TokenConsumerHit { token_path, line });
968        }
969    }
970}
971
972impl<'a> Visit<'a> for ThemeConsumerCollector<'a, '_> {
973    fn visit_static_member_expression(&mut self, member: &StaticMemberExpression<'a>) {
974        let mut chain = access_object_chain(&member.object);
975        if let Some((_, segments)) = chain.as_mut() {
976            segments.push(member.property.name.to_string());
977        }
978        self.record(chain, member.span().start);
979        walk::walk_static_member_expression(self, member);
980    }
981
982    fn visit_computed_member_expression(&mut self, member: &ComputedMemberExpression<'a>) {
983        let mut chain = access_object_chain(&member.object);
984        if let (Some((_, segments)), Some(key)) =
985            (chain.as_mut(), static_computed_key(&member.expression))
986        {
987            segments.push(key);
988        } else {
989            chain = None;
990        }
991        self.record(chain, member.span().start);
992        walk::walk_computed_member_expression(self, member);
993    }
994}
995
996/// Reconstruct the `(base identifier, [segments])` chain of a member-access OBJECT
997/// expression, threading through both static (`a.b`) and string-literal-computed
998/// (`a['b']`) member access. `vars.color` -> `("vars", ["color"])`. Returns `None`
999/// if the chain is not rooted at a plain identifier (a call result, `this`, a
1000/// non-literal computed key, etc.).
1001fn access_object_chain<'a>(expr: &Expression<'a>) -> Option<(&'a str, Vec<String>)> {
1002    match expr {
1003        Expression::Identifier(id) => Some((id.name.as_str(), Vec::new())),
1004        Expression::StaticMemberExpression(inner) => {
1005            let (base, mut segments) = access_object_chain(&inner.object)?;
1006            segments.push(inner.property.name.to_string());
1007            Some((base, segments))
1008        }
1009        Expression::ComputedMemberExpression(inner) => {
1010            let (base, mut segments) = access_object_chain(&inner.object)?;
1011            segments.push(static_computed_key(&inner.expression)?);
1012            Some((base, segments))
1013        }
1014        Expression::ParenthesizedExpression(expression) => {
1015            access_object_chain(&expression.expression)
1016        }
1017        Expression::TSAsExpression(expression) => access_object_chain(&expression.expression),
1018        Expression::TSSatisfiesExpression(expression) => {
1019            access_object_chain(&expression.expression)
1020        }
1021        Expression::TSNonNullExpression(expression) => access_object_chain(&expression.expression),
1022        Expression::TSTypeAssertion(expression) => access_object_chain(&expression.expression),
1023        _ => None,
1024    }
1025}
1026
1027fn binding_access_object_chain<'a>(expr: &Expression<'a>) -> Option<(&'a str, Span, Vec<String>)> {
1028    match expr {
1029        Expression::Identifier(id) => Some((id.name.as_str(), id.span, Vec::new())),
1030        Expression::StaticMemberExpression(inner) => {
1031            let (base, span, mut segments) = binding_access_object_chain(&inner.object)?;
1032            segments.push(inner.property.name.to_string());
1033            Some((base, span, segments))
1034        }
1035        Expression::ComputedMemberExpression(inner) => {
1036            let (base, span, mut segments) = binding_access_object_chain(&inner.object)?;
1037            segments.push(static_computed_key(&inner.expression)?);
1038            Some((base, span, segments))
1039        }
1040        Expression::ParenthesizedExpression(expression) => {
1041            binding_access_object_chain(&expression.expression)
1042        }
1043        Expression::TSAsExpression(expression) => {
1044            binding_access_object_chain(&expression.expression)
1045        }
1046        Expression::TSSatisfiesExpression(expression) => {
1047            binding_access_object_chain(&expression.expression)
1048        }
1049        Expression::TSNonNullExpression(expression) => {
1050            binding_access_object_chain(&expression.expression)
1051        }
1052        Expression::TSTypeAssertion(expression) => {
1053            binding_access_object_chain(&expression.expression)
1054        }
1055        _ => None,
1056    }
1057}
1058
1059fn unwrap_transparent_expression<'a, 'b: 'a>(mut expr: &'a Expression<'b>) -> &'a Expression<'b> {
1060    loop {
1061        expr = match expr {
1062            Expression::ParenthesizedExpression(expression) => &expression.expression,
1063            Expression::TSAsExpression(expression) => &expression.expression,
1064            Expression::TSSatisfiesExpression(expression) => &expression.expression,
1065            Expression::TSNonNullExpression(expression) => &expression.expression,
1066            Expression::TSTypeAssertion(expression) => &expression.expression,
1067            _ => return expr,
1068        };
1069    }
1070}
1071
1072fn call_receiver_root<'a, 'b: 'a>(callee: &'a Expression<'b>) -> Option<RootBinding<'a>> {
1073    match callee {
1074        Expression::StaticMemberExpression(member) => expression_root_binding(&member.object),
1075        Expression::ComputedMemberExpression(member) => expression_root_binding(&member.object),
1076        Expression::ParenthesizedExpression(expression) => {
1077            call_receiver_root(&expression.expression)
1078        }
1079        Expression::TSAsExpression(expression) => call_receiver_root(&expression.expression),
1080        Expression::TSSatisfiesExpression(expression) => call_receiver_root(&expression.expression),
1081        Expression::TSNonNullExpression(expression) => call_receiver_root(&expression.expression),
1082        Expression::TSTypeAssertion(expression) => call_receiver_root(&expression.expression),
1083        _ => None,
1084    }
1085}
1086
1087fn call_receiver_expression<'a, 'b: 'a>(callee: &'a Expression<'b>) -> Option<&'a Expression<'b>> {
1088    match callee {
1089        Expression::StaticMemberExpression(member) => Some(&member.object),
1090        Expression::ComputedMemberExpression(member) => Some(&member.object),
1091        Expression::ParenthesizedExpression(expression) => {
1092            call_receiver_expression(&expression.expression)
1093        }
1094        Expression::TSAsExpression(expression) => call_receiver_expression(&expression.expression),
1095        Expression::TSSatisfiesExpression(expression) => {
1096            call_receiver_expression(&expression.expression)
1097        }
1098        Expression::TSNonNullExpression(expression) => {
1099            call_receiver_expression(&expression.expression)
1100        }
1101        Expression::TSTypeAssertion(expression) => call_receiver_expression(&expression.expression),
1102        _ => None,
1103    }
1104}
1105
1106fn is_static_stylex_theme_override<'a>(
1107    expression: &'a Expression<'a>,
1108    before: u32,
1109    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1110    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
1111    root_reference_spans: &FxHashSet<Span>,
1112    visiting: &mut FxHashSet<&'a str>,
1113) -> bool {
1114    let expression = unwrap_transparent_expression(expression);
1115    if let Some(is_static) = is_static_stylex_composite_expression(
1116        expression,
1117        before,
1118        static_values,
1119        imports,
1120        root_reference_spans,
1121        visiting,
1122    ) {
1123        return is_static;
1124    }
1125    match expression {
1126        Expression::StringLiteral(_)
1127        | Expression::NumericLiteral(_)
1128        | Expression::BooleanLiteral(_)
1129        | Expression::NullLiteral(_)
1130        | Expression::BigIntLiteral(_) => true,
1131        Expression::ObjectExpression(object) => is_static_stylex_theme_object(
1132            object,
1133            before,
1134            static_values,
1135            imports,
1136            root_reference_spans,
1137            visiting,
1138        ),
1139        Expression::Identifier(identifier) => {
1140            if !root_reference_spans.contains(&identifier.span) {
1141                return false;
1142            }
1143            let name = identifier.name.as_str();
1144            let Some(&(declaration_start, value)) = static_values.get(name) else {
1145                return false;
1146            };
1147            if declaration_start >= before || !visiting.insert(name) {
1148                return false;
1149            }
1150            let is_static = is_static_stylex_theme_override(
1151                value,
1152                declaration_start,
1153                static_values,
1154                imports,
1155                root_reference_spans,
1156                visiting,
1157            );
1158            visiting.remove(name);
1159            is_static
1160        }
1161        Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_) => {
1162            let mut member_visiting = visiting.clone();
1163            let resolver = StyleXStaticResolver {
1164                before,
1165                static_values,
1166                root_reference_spans,
1167            };
1168            resolve_static_stylex_member(expression, &resolver, &mut member_visiting).is_some_and(
1169                |resolved| {
1170                    is_static_stylex_theme_override(
1171                        resolved.value,
1172                        resolved.before,
1173                        static_values,
1174                        imports,
1175                        root_reference_spans,
1176                        &mut member_visiting,
1177                    )
1178                },
1179            )
1180        }
1181        Expression::CallExpression(call)
1182            if call.arguments.len() == 1
1183                && is_root_stylex_static_call(&call.callee, imports, root_reference_spans) =>
1184        {
1185            call.arguments.first().is_some_and(|argument| {
1186                argument.as_expression().is_some_and(|argument| {
1187                    is_static_stylex_theme_override(
1188                        argument,
1189                        before,
1190                        static_values,
1191                        imports,
1192                        root_reference_spans,
1193                        visiting,
1194                    )
1195                })
1196            })
1197        }
1198        Expression::CallExpression(call) => is_static_stylex_pure_call(
1199            call,
1200            before,
1201            static_values,
1202            imports,
1203            root_reference_spans,
1204            visiting,
1205        ),
1206        _ => false,
1207    }
1208}
1209
1210fn is_static_stylex_composite_expression<'a>(
1211    expression: &'a Expression<'a>,
1212    before: u32,
1213    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1214    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
1215    root_reference_spans: &FxHashSet<Span>,
1216    visiting: &mut FxHashSet<&'a str>,
1217) -> Option<bool> {
1218    let mut check = |expression| {
1219        is_static_stylex_theme_override(
1220            expression,
1221            before,
1222            static_values,
1223            imports,
1224            root_reference_spans,
1225            visiting,
1226        )
1227    };
1228    match expression {
1229        Expression::TemplateLiteral(template) => Some(template.expressions.iter().all(check)),
1230        Expression::TaggedTemplateExpression(tagged) => Some(
1231            is_root_string_raw_tag(&tagged.tag, static_values, root_reference_spans)
1232                && tagged.quasi.expressions.iter().all(check),
1233        ),
1234        Expression::UnaryExpression(unary) => Some(
1235            !unary.operator.is_delete()
1236                && (unary.operator != UnaryOperator::UnaryPlus
1237                    || !is_static_stylex_bigint_value(
1238                        &unary.argument,
1239                        before,
1240                        static_values,
1241                        root_reference_spans,
1242                        &mut FxHashSet::default(),
1243                    ))
1244                && check(&unary.argument),
1245        ),
1246        Expression::ConditionalExpression(conditional) => Some(
1247            check(&conditional.test)
1248                && check(&conditional.consequent)
1249                && check(&conditional.alternate),
1250        ),
1251        Expression::LogicalExpression(logical) => {
1252            Some(check(&logical.left) && check(&logical.right))
1253        }
1254        Expression::SequenceExpression(sequence) => Some(sequence.expressions.iter().all(check)),
1255        Expression::BinaryExpression(binary) => Some(
1256            !binary.operator.is_relational()
1257                && (!binary.operator.is_numeric_or_string_binary_operator()
1258                    || (!is_static_stylex_bigint_value(
1259                        &binary.left,
1260                        before,
1261                        static_values,
1262                        root_reference_spans,
1263                        &mut FxHashSet::default(),
1264                    ) && !is_static_stylex_bigint_value(
1265                        &binary.right,
1266                        before,
1267                        static_values,
1268                        root_reference_spans,
1269                        &mut FxHashSet::default(),
1270                    )))
1271                && check(&binary.left)
1272                && check(&binary.right),
1273        ),
1274        _ => None,
1275    }
1276}
1277
1278fn is_static_stylex_theme_object<'a>(
1279    object: &'a ObjectExpression<'a>,
1280    before: u32,
1281    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1282    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
1283    root_reference_spans: &FxHashSet<Span>,
1284    visiting: &mut FxHashSet<&'a str>,
1285) -> bool {
1286    object.properties.iter().all(|property| match property {
1287        ObjectPropertyKind::ObjectProperty(property) => {
1288            if property.computed {
1289                let Some(key) = property.key.as_expression() else {
1290                    return false;
1291                };
1292                if !is_static_stylex_computed_key(
1293                    key,
1294                    before,
1295                    static_values,
1296                    imports,
1297                    root_reference_spans,
1298                    visiting,
1299                ) {
1300                    return false;
1301                }
1302            }
1303            is_static_stylex_theme_override(
1304                &property.value,
1305                before,
1306                static_values,
1307                imports,
1308                root_reference_spans,
1309                visiting,
1310            )
1311        }
1312        ObjectPropertyKind::SpreadProperty(spread) => is_static_stylex_theme_override_object(
1313            &spread.argument,
1314            before,
1315            static_values,
1316            imports,
1317            root_reference_spans,
1318            visiting,
1319        ),
1320    })
1321}
1322
1323fn is_static_stylex_computed_key<'a>(
1324    expression: &'a Expression<'a>,
1325    before: u32,
1326    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1327    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
1328    root_reference_spans: &FxHashSet<Span>,
1329    visiting: &mut FxHashSet<&'a str>,
1330) -> bool {
1331    match unwrap_transparent_expression(expression) {
1332        Expression::StringLiteral(_) | Expression::NumericLiteral(_) => true,
1333        Expression::TemplateLiteral(template) => template.expressions.iter().all(|expression| {
1334            is_static_stylex_theme_override(
1335                expression,
1336                before,
1337                static_values,
1338                imports,
1339                root_reference_spans,
1340                visiting,
1341            )
1342        }),
1343        Expression::BinaryExpression(binary)
1344            if binary.operator.is_numeric_or_string_binary_operator() =>
1345        {
1346            is_static_stylex_theme_override(
1347                expression,
1348                before,
1349                static_values,
1350                imports,
1351                root_reference_spans,
1352                visiting,
1353            )
1354        }
1355        Expression::Identifier(identifier) => {
1356            if !root_reference_spans.contains(&identifier.span) {
1357                return false;
1358            }
1359            let name = identifier.name.as_str();
1360            let Some(&(declaration_start, value)) = static_values.get(name) else {
1361                return false;
1362            };
1363            if declaration_start >= before || !visiting.insert(name) {
1364                return false;
1365            }
1366            let is_static = is_static_stylex_computed_key(
1367                value,
1368                declaration_start,
1369                static_values,
1370                imports,
1371                root_reference_spans,
1372                visiting,
1373            );
1374            visiting.remove(name);
1375            is_static
1376        }
1377        Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_) => {
1378            let mut member_visiting = visiting.clone();
1379            let resolver = StyleXStaticResolver {
1380                before,
1381                static_values,
1382                root_reference_spans,
1383            };
1384            resolve_static_stylex_member(expression, &resolver, &mut member_visiting).is_some_and(
1385                |resolved| {
1386                    is_static_stylex_computed_key(
1387                        resolved.value,
1388                        resolved.before,
1389                        static_values,
1390                        imports,
1391                        root_reference_spans,
1392                        &mut member_visiting,
1393                    )
1394                },
1395            )
1396        }
1397        _ => false,
1398    }
1399}
1400
1401struct StyleXStaticResolver<'maps, 'ast> {
1402    before: u32,
1403    static_values: &'maps FxHashMap<&'ast str, (u32, &'ast Expression<'ast>)>,
1404    root_reference_spans: &'maps FxHashSet<Span>,
1405}
1406
1407struct ResolvedStyleXStaticMember<'ast> {
1408    value: &'ast Expression<'ast>,
1409    before: u32,
1410}
1411
1412#[derive(Clone, Copy)]
1413struct ResolvedStyleXStaticObject<'ast> {
1414    object: &'ast ObjectExpression<'ast>,
1415    before: u32,
1416}
1417
1418fn resolve_static_stylex_member<'a>(
1419    expression: &'a Expression<'a>,
1420    resolver: &StyleXStaticResolver<'_, 'a>,
1421    visiting: &mut FxHashSet<&'a str>,
1422) -> Option<ResolvedStyleXStaticMember<'a>> {
1423    match unwrap_transparent_expression(expression) {
1424        Expression::StaticMemberExpression(member) => {
1425            let object = resolve_static_stylex_object(&member.object, resolver, visiting)?;
1426            resolve_static_stylex_object_property(
1427                object,
1428                member.property.name.as_str(),
1429                resolver,
1430                visiting,
1431            )
1432        }
1433        Expression::ComputedMemberExpression(member) => {
1434            let object = resolve_static_stylex_object(&member.object, resolver, visiting)?;
1435            let key = resolve_static_stylex_member_key(&member.expression, resolver, visiting)?;
1436            resolve_static_stylex_object_property(object, &key, resolver, visiting)
1437        }
1438        _ => None,
1439    }
1440}
1441
1442fn resolve_static_stylex_object<'a>(
1443    expression: &'a Expression<'a>,
1444    resolver: &StyleXStaticResolver<'_, 'a>,
1445    visiting: &mut FxHashSet<&'a str>,
1446) -> Option<ResolvedStyleXStaticObject<'a>> {
1447    match unwrap_transparent_expression(expression) {
1448        Expression::ObjectExpression(object) => Some(ResolvedStyleXStaticObject {
1449            object,
1450            before: resolver.before,
1451        }),
1452        Expression::Identifier(identifier) => {
1453            if !resolver.root_reference_spans.contains(&identifier.span) {
1454                return None;
1455            }
1456            let name = identifier.name.as_str();
1457            let &(declaration_start, value) = resolver.static_values.get(name)?;
1458            if declaration_start >= resolver.before || !visiting.insert(name) {
1459                return None;
1460            }
1461            let nested_resolver = StyleXStaticResolver {
1462                before: declaration_start,
1463                static_values: resolver.static_values,
1464                root_reference_spans: resolver.root_reference_spans,
1465            };
1466            resolve_static_stylex_object(value, &nested_resolver, visiting)
1467        }
1468        Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_) => {
1469            let resolved = resolve_static_stylex_member(expression, resolver, visiting)?;
1470            let nested_resolver = StyleXStaticResolver {
1471                before: resolved.before,
1472                static_values: resolver.static_values,
1473                root_reference_spans: resolver.root_reference_spans,
1474            };
1475            resolve_static_stylex_object(resolved.value, &nested_resolver, visiting)
1476        }
1477        _ => None,
1478    }
1479}
1480
1481fn resolve_static_stylex_object_property<'a>(
1482    resolved_object: ResolvedStyleXStaticObject<'a>,
1483    wanted: &str,
1484    resolver: &StyleXStaticResolver<'_, 'a>,
1485    visiting: &mut FxHashSet<&'a str>,
1486) -> Option<ResolvedStyleXStaticMember<'a>> {
1487    for property in resolved_object.object.properties.iter().rev() {
1488        match property {
1489            ObjectPropertyKind::ObjectProperty(property) => {
1490                let key = if property.computed {
1491                    resolve_static_stylex_member_key(
1492                        property.key.as_expression()?,
1493                        resolver,
1494                        visiting,
1495                    )?
1496                } else {
1497                    property.key.static_name()?.to_string()
1498                };
1499                if key == wanted {
1500                    return Some(ResolvedStyleXStaticMember {
1501                        value: &property.value,
1502                        before: resolved_object.before,
1503                    });
1504                }
1505            }
1506            ObjectPropertyKind::SpreadProperty(spread) => {
1507                let spread_resolver = StyleXStaticResolver {
1508                    before: resolved_object.before,
1509                    static_values: resolver.static_values,
1510                    root_reference_spans: resolver.root_reference_spans,
1511                };
1512                let spread_object =
1513                    resolve_static_stylex_object(&spread.argument, &spread_resolver, visiting)?;
1514                if let Some(value) =
1515                    resolve_static_stylex_object_property(spread_object, wanted, resolver, visiting)
1516                {
1517                    return Some(value);
1518                }
1519            }
1520        }
1521    }
1522    None
1523}
1524
1525fn resolve_static_stylex_member_key<'a>(
1526    expression: &'a Expression<'a>,
1527    resolver: &StyleXStaticResolver<'_, 'a>,
1528    visiting: &mut FxHashSet<&'a str>,
1529) -> Option<String> {
1530    if let Some(key) = static_computed_key(expression) {
1531        return Some(key);
1532    }
1533    let Expression::Identifier(identifier) = unwrap_transparent_expression(expression) else {
1534        return None;
1535    };
1536    if !resolver.root_reference_spans.contains(&identifier.span) {
1537        return None;
1538    }
1539    let name = identifier.name.as_str();
1540    let &(declaration_start, value) = resolver.static_values.get(name)?;
1541    if declaration_start >= resolver.before || !visiting.insert(name) {
1542        return None;
1543    }
1544    let nested_resolver = StyleXStaticResolver {
1545        before: declaration_start,
1546        static_values: resolver.static_values,
1547        root_reference_spans: resolver.root_reference_spans,
1548    };
1549    let key = resolve_static_stylex_member_key(value, &nested_resolver, visiting);
1550    visiting.remove(name);
1551    key
1552}
1553
1554fn is_static_stylex_theme_override_object<'a>(
1555    expression: &'a Expression<'a>,
1556    before: u32,
1557    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1558    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
1559    root_reference_spans: &FxHashSet<Span>,
1560    visiting: &mut FxHashSet<&'a str>,
1561) -> bool {
1562    match unwrap_transparent_expression(expression) {
1563        Expression::ObjectExpression(_) => is_static_stylex_theme_override(
1564            expression,
1565            before,
1566            static_values,
1567            imports,
1568            root_reference_spans,
1569            visiting,
1570        ),
1571        Expression::Identifier(identifier) => {
1572            if !root_reference_spans.contains(&identifier.span) {
1573                return false;
1574            }
1575            let name = identifier.name.as_str();
1576            let Some(&(declaration_start, value)) = static_values.get(name) else {
1577                return false;
1578            };
1579            if declaration_start >= before || !visiting.insert(name) {
1580                return false;
1581            }
1582            let is_static = is_static_stylex_theme_override_object(
1583                value,
1584                declaration_start,
1585                static_values,
1586                imports,
1587                root_reference_spans,
1588                visiting,
1589            );
1590            visiting.remove(name);
1591            is_static
1592        }
1593        Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_) => {
1594            let mut member_visiting = visiting.clone();
1595            let resolver = StyleXStaticResolver {
1596                before,
1597                static_values,
1598                root_reference_spans,
1599            };
1600            resolve_static_stylex_member(expression, &resolver, &mut member_visiting).is_some_and(
1601                |resolved| {
1602                    is_static_stylex_theme_override_object(
1603                        resolved.value,
1604                        resolved.before,
1605                        static_values,
1606                        imports,
1607                        root_reference_spans,
1608                        &mut member_visiting,
1609                    )
1610                },
1611            )
1612        }
1613        _ => false,
1614    }
1615}
1616
1617fn is_root_string_raw_tag(
1618    tag: &Expression<'_>,
1619    static_values: &FxHashMap<&str, (u32, &Expression<'_>)>,
1620    root_reference_spans: &FxHashSet<Span>,
1621) -> bool {
1622    let Expression::StaticMemberExpression(member) = unwrap_transparent_expression(tag) else {
1623        return false;
1624    };
1625    let Expression::Identifier(object) = unwrap_transparent_expression(&member.object) else {
1626        return false;
1627    };
1628    object.name == "String"
1629        && member.property.name == "raw"
1630        && root_reference_spans.contains(&object.span)
1631        && !static_values.contains_key("String")
1632}
1633
1634fn is_static_stylex_pure_call<'a>(
1635    call: &'a CallExpression<'a>,
1636    before: u32,
1637    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1638    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
1639    root_reference_spans: &FxHashSet<Span>,
1640    visiting: &mut FxHashSet<&'a str>,
1641) -> bool {
1642    if is_static_stylex_local_arrow_call(
1643        call,
1644        before,
1645        static_values,
1646        imports,
1647        root_reference_spans,
1648        visiting,
1649    ) {
1650        return true;
1651    }
1652    let allow_array_arguments =
1653        is_root_object_from_entries_call(&call.callee, static_values, root_reference_spans);
1654    if allow_array_arguments {
1655        return is_static_stylex_entries_call(
1656            call,
1657            before,
1658            static_values,
1659            imports,
1660            root_reference_spans,
1661            visiting,
1662        );
1663    }
1664    if !is_root_scalar_pure_call(
1665        &call.callee,
1666        before,
1667        static_values,
1668        imports,
1669        root_reference_spans,
1670        visiting,
1671    ) || !is_static_stylex_scalar_call_shape(call, before, static_values, root_reference_spans)
1672    {
1673        return false;
1674    }
1675    if is_root_math_call(&call.callee, static_values, root_reference_spans)
1676        && call.arguments.iter().any(|argument| {
1677            argument.as_expression().is_none_or(|argument| {
1678                is_static_stylex_bigint_value(
1679                    argument,
1680                    before,
1681                    static_values,
1682                    root_reference_spans,
1683                    &mut FxHashSet::default(),
1684                )
1685            })
1686        })
1687    {
1688        return false;
1689    }
1690    call.arguments.iter().all(|argument| {
1691        argument.as_expression().is_some_and(|argument| {
1692            is_definitely_static_primitive(
1693                argument,
1694                before,
1695                static_values,
1696                root_reference_spans,
1697                visiting,
1698            ) && is_static_stylex_theme_override(
1699                argument,
1700                before,
1701                static_values,
1702                imports,
1703                root_reference_spans,
1704                visiting,
1705            )
1706        })
1707    })
1708}
1709
1710fn is_root_math_call(
1711    callee: &Expression<'_>,
1712    static_values: &FxHashMap<&str, (u32, &Expression<'_>)>,
1713    root_reference_spans: &FxHashSet<Span>,
1714) -> bool {
1715    let Expression::StaticMemberExpression(member) = unwrap_transparent_expression(callee) else {
1716        return false;
1717    };
1718    let Expression::Identifier(object) = unwrap_transparent_expression(&member.object) else {
1719        return false;
1720    };
1721    object.name == "Math"
1722        && root_reference_spans.contains(&object.span)
1723        && !static_values.contains_key("Math")
1724}
1725
1726fn is_definitely_static_primitive<'a>(
1727    expression: &'a Expression<'a>,
1728    before: u32,
1729    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1730    root_reference_spans: &FxHashSet<Span>,
1731    visiting: &mut FxHashSet<&'a str>,
1732) -> bool {
1733    match unwrap_transparent_expression(expression) {
1734        Expression::StringLiteral(_)
1735        | Expression::NumericLiteral(_)
1736        | Expression::BooleanLiteral(_)
1737        | Expression::NullLiteral(_)
1738        | Expression::BigIntLiteral(_) => true,
1739        Expression::TemplateLiteral(template) => template.expressions.iter().all(|expression| {
1740            is_definitely_static_primitive(
1741                expression,
1742                before,
1743                static_values,
1744                root_reference_spans,
1745                visiting,
1746            )
1747        }),
1748        Expression::UnaryExpression(unary) => {
1749            !unary.operator.is_delete()
1750                && is_definitely_static_primitive(
1751                    &unary.argument,
1752                    before,
1753                    static_values,
1754                    root_reference_spans,
1755                    visiting,
1756                )
1757        }
1758        Expression::BinaryExpression(binary) => is_static_stylex_primitive_pair(
1759            &binary.left,
1760            &binary.right,
1761            before,
1762            static_values,
1763            root_reference_spans,
1764            visiting,
1765        ),
1766        Expression::LogicalExpression(logical) => is_static_stylex_primitive_pair(
1767            &logical.left,
1768            &logical.right,
1769            before,
1770            static_values,
1771            root_reference_spans,
1772            visiting,
1773        ),
1774        Expression::ConditionalExpression(conditional) => is_static_stylex_primitive_pair(
1775            &conditional.consequent,
1776            &conditional.alternate,
1777            before,
1778            static_values,
1779            root_reference_spans,
1780            visiting,
1781        ),
1782        Expression::SequenceExpression(sequence) => {
1783            sequence.expressions.last().is_some_and(|expression| {
1784                is_definitely_static_primitive(
1785                    expression,
1786                    before,
1787                    static_values,
1788                    root_reference_spans,
1789                    visiting,
1790                )
1791            })
1792        }
1793        Expression::Identifier(identifier) => {
1794            if !root_reference_spans.contains(&identifier.span) {
1795                return false;
1796            }
1797            let name = identifier.name.as_str();
1798            let Some(&(declaration_start, value)) = static_values.get(name) else {
1799                return false;
1800            };
1801            if declaration_start >= before || !visiting.insert(name) {
1802                return false;
1803            }
1804            let is_primitive = is_definitely_static_primitive(
1805                value,
1806                declaration_start,
1807                static_values,
1808                root_reference_spans,
1809                visiting,
1810            );
1811            visiting.remove(name);
1812            is_primitive
1813        }
1814        _ => false,
1815    }
1816}
1817
1818fn is_static_stylex_primitive_pair<'a>(
1819    left: &'a Expression<'a>,
1820    right: &'a Expression<'a>,
1821    before: u32,
1822    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1823    root_reference_spans: &FxHashSet<Span>,
1824    visiting: &mut FxHashSet<&'a str>,
1825) -> bool {
1826    is_definitely_static_primitive(left, before, static_values, root_reference_spans, visiting)
1827        && is_definitely_static_primitive(
1828            right,
1829            before,
1830            static_values,
1831            root_reference_spans,
1832            visiting,
1833        )
1834}
1835
1836fn is_static_stylex_bigint_value<'a>(
1837    expression: &'a Expression<'a>,
1838    before: u32,
1839    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
1840    root_reference_spans: &FxHashSet<Span>,
1841    visiting: &mut FxHashSet<&'a str>,
1842) -> bool {
1843    let parameters = FxHashMap::default();
1844    let context = StyleXBigIntContext {
1845        before,
1846        static_values,
1847        root_reference_spans,
1848        parameters: &parameters,
1849    };
1850    is_static_stylex_bigint_value_with_context(expression, &context, visiting)
1851}
1852
1853#[derive(Clone, Copy)]
1854struct StyleXBigIntContext<'maps, 'ast> {
1855    before: u32,
1856    static_values: &'maps FxHashMap<&'ast str, (u32, &'ast Expression<'ast>)>,
1857    root_reference_spans: &'maps FxHashSet<Span>,
1858    parameters: &'maps FxHashMap<&'ast str, &'ast Expression<'ast>>,
1859}
1860
1861fn is_static_stylex_bigint_value_with_context<'a>(
1862    expression: &'a Expression<'a>,
1863    context: &StyleXBigIntContext<'_, 'a>,
1864    visiting: &mut FxHashSet<&'a str>,
1865) -> bool {
1866    match unwrap_transparent_expression(expression) {
1867        Expression::BigIntLiteral(_) => true,
1868        Expression::Identifier(identifier) => {
1869            let name = identifier.name.as_str();
1870            if let Some(value) = context.parameters.get(name) {
1871                return is_static_stylex_bigint_value_with_context(value, context, visiting);
1872            }
1873            if !context.root_reference_spans.contains(&identifier.span) {
1874                return false;
1875            }
1876            let Some(&(declaration_start, value)) = context.static_values.get(name) else {
1877                return false;
1878            };
1879            if declaration_start >= context.before || !visiting.insert(name) {
1880                return false;
1881            }
1882            let nested_context = StyleXBigIntContext {
1883                before: declaration_start,
1884                ..*context
1885            };
1886            let is_bigint =
1887                is_static_stylex_bigint_value_with_context(value, &nested_context, visiting);
1888            visiting.remove(name);
1889            is_bigint
1890        }
1891        Expression::UnaryExpression(unary)
1892            if matches!(
1893                unary.operator,
1894                UnaryOperator::UnaryNegation | UnaryOperator::BitwiseNot
1895            ) =>
1896        {
1897            is_static_stylex_bigint_value_with_context(&unary.argument, context, visiting)
1898        }
1899        Expression::BinaryExpression(binary)
1900            if binary.operator.is_numeric_or_string_binary_operator() =>
1901        {
1902            is_static_stylex_bigint_pair(&binary.left, &binary.right, context, visiting)
1903        }
1904        Expression::ConditionalExpression(conditional) => is_static_stylex_bigint_pair(
1905            &conditional.consequent,
1906            &conditional.alternate,
1907            context,
1908            visiting,
1909        ),
1910        Expression::LogicalExpression(logical) => {
1911            is_static_stylex_bigint_pair(&logical.left, &logical.right, context, visiting)
1912        }
1913        Expression::SequenceExpression(sequence) => {
1914            sequence.expressions.last().is_some_and(|expression| {
1915                is_static_stylex_bigint_value_with_context(expression, context, visiting)
1916            })
1917        }
1918        Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_) => {
1919            let mut member_visiting = visiting.clone();
1920            let resolver = StyleXStaticResolver {
1921                before: context.before,
1922                static_values: context.static_values,
1923                root_reference_spans: context.root_reference_spans,
1924            };
1925            resolve_static_stylex_member(expression, &resolver, &mut member_visiting).is_some_and(
1926                |resolved| {
1927                    let nested_context = StyleXBigIntContext {
1928                        before: resolved.before,
1929                        ..*context
1930                    };
1931                    is_static_stylex_bigint_value_with_context(
1932                        resolved.value,
1933                        &nested_context,
1934                        &mut member_visiting,
1935                    )
1936                },
1937            )
1938        }
1939        Expression::CallExpression(call) => {
1940            is_static_stylex_bigint_arrow_call(call, context, visiting)
1941        }
1942        _ => false,
1943    }
1944}
1945
1946fn is_static_stylex_bigint_pair<'a>(
1947    left: &'a Expression<'a>,
1948    right: &'a Expression<'a>,
1949    context: &StyleXBigIntContext<'_, 'a>,
1950    visiting: &mut FxHashSet<&'a str>,
1951) -> bool {
1952    is_static_stylex_bigint_value_with_context(left, context, visiting)
1953        || is_static_stylex_bigint_value_with_context(right, context, visiting)
1954}
1955
1956fn is_static_stylex_bigint_arrow_call<'a>(
1957    call: &'a CallExpression<'a>,
1958    context: &StyleXBigIntContext<'_, 'a>,
1959    visiting: &mut FxHashSet<&'a str>,
1960) -> bool {
1961    let Expression::Identifier(callee) = unwrap_transparent_expression(&call.callee) else {
1962        return false;
1963    };
1964    if !context.root_reference_spans.contains(&callee.span) {
1965        return false;
1966    }
1967    let name = callee.name.as_str();
1968    let Some(&(declaration_start, value)) = context.static_values.get(name) else {
1969        return false;
1970    };
1971    let Expression::ArrowFunctionExpression(arrow) = unwrap_transparent_expression(value) else {
1972        return false;
1973    };
1974    if declaration_start >= context.before
1975        || arrow.r#async
1976        || arrow.params.rest.is_some()
1977        || arrow.params.items.len() != call.arguments.len()
1978        || !visiting.insert(name)
1979    {
1980        return false;
1981    }
1982    let Some(body) = stylex_arrow_expression_body(arrow) else {
1983        visiting.remove(name);
1984        return false;
1985    };
1986    let mut arrow_parameters = context.parameters.clone();
1987    for (parameter, argument) in arrow.params.items.iter().zip(&call.arguments) {
1988        let (BindingPattern::BindingIdentifier(binding), Some(argument)) =
1989            (&parameter.pattern, argument.as_expression())
1990        else {
1991            visiting.remove(name);
1992            return false;
1993        };
1994        arrow_parameters.insert(binding.name.as_str(), argument);
1995    }
1996    let nested_context = StyleXBigIntContext {
1997        before: context.before,
1998        parameters: &arrow_parameters,
1999        ..*context
2000    };
2001    let is_bigint = is_static_stylex_bigint_value_with_context(body, &nested_context, visiting);
2002    visiting.remove(name);
2003    is_bigint
2004}
2005
2006fn is_root_object_from_entries_call(
2007    callee: &Expression<'_>,
2008    static_values: &FxHashMap<&str, (u32, &Expression<'_>)>,
2009    root_reference_spans: &FxHashSet<Span>,
2010) -> bool {
2011    let Expression::StaticMemberExpression(member) = unwrap_transparent_expression(callee) else {
2012        return false;
2013    };
2014    let Expression::Identifier(object) = unwrap_transparent_expression(&member.object) else {
2015        return false;
2016    };
2017    object.name == "Object"
2018        && member.property.name == "fromEntries"
2019        && root_reference_spans.contains(&object.span)
2020        && !static_values.contains_key("Object")
2021}
2022
2023fn is_root_scalar_pure_call<'a>(
2024    callee: &'a Expression<'a>,
2025    before: u32,
2026    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2027    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
2028    root_reference_spans: &FxHashSet<Span>,
2029    visiting: &mut FxHashSet<&'a str>,
2030) -> bool {
2031    match unwrap_transparent_expression(callee) {
2032        Expression::Identifier(identifier) => {
2033            matches!(identifier.name.as_str(), "String" | "Number")
2034                && root_reference_spans.contains(&identifier.span)
2035                && !static_values.contains_key(identifier.name.as_str())
2036        }
2037        Expression::StaticMemberExpression(member) => {
2038            let method = member.property.name.as_str();
2039            match unwrap_transparent_expression(&member.object) {
2040                Expression::Identifier(object) if object.name == "Math" => {
2041                    root_reference_spans.contains(&object.span)
2042                        && !static_values.contains_key("Math")
2043                        && is_static_math_method(method)
2044                }
2045                object => match static_stylex_scalar_kind(
2046                    object,
2047                    before,
2048                    static_values,
2049                    imports,
2050                    root_reference_spans,
2051                    visiting,
2052                ) {
2053                    Some(StyleXScalarKind::String) => is_static_string_method(method),
2054                    Some(StyleXScalarKind::Number) => is_static_number_method(method),
2055                    Some(StyleXScalarKind::Other) | None => false,
2056                },
2057            }
2058        }
2059        _ => false,
2060    }
2061}
2062
2063#[derive(Clone, Copy)]
2064enum StyleXScalarKind {
2065    String,
2066    Number,
2067    Other,
2068}
2069
2070fn static_stylex_scalar_kind<'a>(
2071    expression: &'a Expression<'a>,
2072    before: u32,
2073    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2074    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
2075    root_reference_spans: &FxHashSet<Span>,
2076    visiting: &mut FxHashSet<&'a str>,
2077) -> Option<StyleXScalarKind> {
2078    match unwrap_transparent_expression(expression) {
2079        Expression::StringLiteral(_) => Some(StyleXScalarKind::String),
2080        Expression::NumericLiteral(_) => Some(StyleXScalarKind::Number),
2081        Expression::Identifier(identifier) => {
2082            if !root_reference_spans.contains(&identifier.span) {
2083                return None;
2084            }
2085            let name = identifier.name.as_str();
2086            let &(declaration_start, value) = static_values.get(name)?;
2087            if declaration_start >= before || !visiting.insert(name) {
2088                return None;
2089            }
2090            let kind = static_stylex_scalar_kind(
2091                value,
2092                declaration_start,
2093                static_values,
2094                imports,
2095                root_reference_spans,
2096                visiting,
2097            );
2098            visiting.remove(name);
2099            kind
2100        }
2101        Expression::CallExpression(call)
2102            if is_static_stylex_pure_call(
2103                call,
2104                before,
2105                static_values,
2106                imports,
2107                root_reference_spans,
2108                visiting,
2109            ) =>
2110        {
2111            static_stylex_scalar_call_result(&call.callee)
2112        }
2113        _ => None,
2114    }
2115}
2116
2117fn static_stylex_scalar_call_result(callee: &Expression<'_>) -> Option<StyleXScalarKind> {
2118    match unwrap_transparent_expression(callee) {
2119        Expression::Identifier(identifier) => match identifier.name.as_str() {
2120            "String" => Some(StyleXScalarKind::String),
2121            "Number" => Some(StyleXScalarKind::Number),
2122            _ => None,
2123        },
2124        Expression::StaticMemberExpression(member) => match member.property.name.as_str() {
2125            "at" | "charAt" | "concat" | "padEnd" | "padStart" | "replace" | "replaceAll"
2126            | "slice" | "substring" | "toExponential" | "toFixed" | "toLowerCase"
2127            | "toPrecision" | "toString" | "toUpperCase" | "trim" | "trimEnd" | "trimStart" => {
2128                Some(StyleXScalarKind::String)
2129            }
2130            "valueOf" => match unwrap_transparent_expression(&member.object) {
2131                Expression::StringLiteral(_) => Some(StyleXScalarKind::String),
2132                Expression::NumericLiteral(_) => Some(StyleXScalarKind::Number),
2133                _ => Some(StyleXScalarKind::Other),
2134            },
2135            method if is_static_math_method(method) => Some(StyleXScalarKind::Number),
2136            _ => Some(StyleXScalarKind::Other),
2137        },
2138        _ => None,
2139    }
2140}
2141
2142fn is_static_string_method(method: &str) -> bool {
2143    matches!(
2144        method,
2145        "at" | "charAt"
2146            | "charCodeAt"
2147            | "codePointAt"
2148            | "concat"
2149            | "endsWith"
2150            | "includes"
2151            | "indexOf"
2152            | "lastIndexOf"
2153            | "padEnd"
2154            | "padStart"
2155            | "replace"
2156            | "replaceAll"
2157            | "search"
2158            | "slice"
2159            | "startsWith"
2160            | "substring"
2161            | "toLowerCase"
2162            | "toString"
2163            | "toUpperCase"
2164            | "trim"
2165            | "trimEnd"
2166            | "trimStart"
2167            | "valueOf"
2168    )
2169}
2170
2171fn is_static_math_method(method: &str) -> bool {
2172    matches!(
2173        method,
2174        "abs"
2175            | "acos"
2176            | "acosh"
2177            | "asin"
2178            | "asinh"
2179            | "atan"
2180            | "atan2"
2181            | "atanh"
2182            | "cbrt"
2183            | "ceil"
2184            | "clz32"
2185            | "cos"
2186            | "cosh"
2187            | "exp"
2188            | "expm1"
2189            | "floor"
2190            | "fround"
2191            | "hypot"
2192            | "imul"
2193            | "log"
2194            | "log10"
2195            | "log1p"
2196            | "log2"
2197            | "max"
2198            | "min"
2199            | "pow"
2200            | "round"
2201            | "sign"
2202            | "sin"
2203            | "sinh"
2204            | "sqrt"
2205            | "tan"
2206            | "tanh"
2207            | "trunc"
2208    )
2209}
2210
2211fn is_static_stylex_entries_call<'a>(
2212    call: &'a CallExpression<'a>,
2213    before: u32,
2214    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2215    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
2216    root_reference_spans: &FxHashSet<Span>,
2217    visiting: &mut FxHashSet<&'a str>,
2218) -> bool {
2219    let Some(Expression::ArrayExpression(entries)) =
2220        call.arguments.first().and_then(Argument::as_expression)
2221    else {
2222        return false;
2223    };
2224    call.arguments.len() == 1
2225        && entries.elements.iter().all(|entry| {
2226            let Some(Expression::ArrayExpression(pair)) = entry.as_expression() else {
2227                return false;
2228            };
2229            let [key, value] = pair.elements.as_slice() else {
2230                return false;
2231            };
2232            let (Some(key), Some(value)) = (key.as_expression(), value.as_expression()) else {
2233                return false;
2234            };
2235            is_static_stylex_computed_key(
2236                key,
2237                before,
2238                static_values,
2239                imports,
2240                root_reference_spans,
2241                visiting,
2242            ) && is_static_stylex_theme_override(
2243                value,
2244                before,
2245                static_values,
2246                imports,
2247                root_reference_spans,
2248                visiting,
2249            )
2250        })
2251}
2252
2253fn is_static_number_method(method: &str) -> bool {
2254    matches!(
2255        method,
2256        "toExponential" | "toFixed" | "toPrecision" | "toString" | "valueOf"
2257    )
2258}
2259
2260fn is_static_stylex_scalar_call_shape<'a>(
2261    call: &'a CallExpression<'a>,
2262    before: u32,
2263    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2264    root_reference_spans: &FxHashSet<Span>,
2265) -> bool {
2266    let Expression::StaticMemberExpression(member) = unwrap_transparent_expression(&call.callee)
2267    else {
2268        return true;
2269    };
2270    match member.property.name.as_str() {
2271        "padStart" | "padEnd" => {
2272            static_padding_arguments_are_safe(call, before, static_values, root_reference_spans)
2273        }
2274        "at" | "charAt" | "charCodeAt" | "codePointAt" | "endsWith" | "includes" | "indexOf"
2275        | "lastIndexOf" | "slice" | "startsWith" | "substring" => call
2276            .arguments
2277            .iter()
2278            .filter_map(Argument::as_expression)
2279            .all(|argument| {
2280                !is_static_stylex_bigint_value(
2281                    argument,
2282                    before,
2283                    static_values,
2284                    root_reference_spans,
2285                    &mut FxHashSet::default(),
2286                )
2287            }),
2288        "toFixed" | "toExponential" => static_numeric_method_argument(
2289            call,
2290            0.0,
2291            100.0,
2292            before,
2293            static_values,
2294            root_reference_spans,
2295        ),
2296        "toPrecision" => static_numeric_method_argument(
2297            call,
2298            1.0,
2299            100.0,
2300            before,
2301            static_values,
2302            root_reference_spans,
2303        ),
2304        "toString"
2305            if static_stylex_scalar_call_result(&call.callee)
2306                .is_some_and(|kind| matches!(kind, StyleXScalarKind::String)) =>
2307        {
2308            static_numeric_method_argument(
2309                call,
2310                2.0,
2311                36.0,
2312                before,
2313                static_values,
2314                root_reference_spans,
2315            )
2316        }
2317        "valueOf" => call.arguments.is_empty(),
2318        _ => true,
2319    }
2320}
2321
2322fn static_numeric_method_argument<'a>(
2323    call: &'a CallExpression<'a>,
2324    min: f64,
2325    max: f64,
2326    before: u32,
2327    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2328    root_reference_spans: &FxHashSet<Span>,
2329) -> bool {
2330    let Some(argument) = call.arguments.first() else {
2331        return true;
2332    };
2333    if call.arguments.len() != 1 {
2334        return false;
2335    }
2336    let Some(argument) = argument.as_expression() else {
2337        return false;
2338    };
2339    static_stylex_numeric_value(
2340        argument,
2341        before,
2342        static_values,
2343        root_reference_spans,
2344        &mut FxHashSet::default(),
2345    )
2346    .is_some_and(|value| value.fract() == 0.0 && (min..=max).contains(&value))
2347}
2348
2349fn static_padding_arguments_are_safe<'a>(
2350    call: &'a CallExpression<'a>,
2351    before: u32,
2352    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2353    root_reference_spans: &FxHashSet<Span>,
2354) -> bool {
2355    const MAX_STATIC_PADDING: f64 = 1_000_000.0;
2356    let Some(target) = call.arguments.first() else {
2357        return true;
2358    };
2359    if call.arguments.len() > 2 {
2360        return false;
2361    }
2362    let Some(target) = target.as_expression() else {
2363        return false;
2364    };
2365    static_stylex_numeric_value(
2366        target,
2367        before,
2368        static_values,
2369        root_reference_spans,
2370        &mut FxHashSet::default(),
2371    )
2372    .is_some_and(|value| value.is_finite() && (0.0..=MAX_STATIC_PADDING).contains(&value))
2373}
2374
2375fn static_stylex_numeric_value<'a>(
2376    expression: &'a Expression<'a>,
2377    before: u32,
2378    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2379    root_reference_spans: &FxHashSet<Span>,
2380    visiting: &mut FxHashSet<&'a str>,
2381) -> Option<f64> {
2382    match unwrap_transparent_expression(expression) {
2383        Expression::NumericLiteral(value) => Some(value.value),
2384        Expression::Identifier(identifier) => {
2385            if !root_reference_spans.contains(&identifier.span) {
2386                return None;
2387            }
2388            let name = identifier.name.as_str();
2389            let &(declaration_start, value) = static_values.get(name)?;
2390            if declaration_start >= before || !visiting.insert(name) {
2391                return None;
2392            }
2393            let number = static_stylex_numeric_value(
2394                value,
2395                declaration_start,
2396                static_values,
2397                root_reference_spans,
2398                visiting,
2399            );
2400            visiting.remove(name);
2401            number
2402        }
2403        _ => None,
2404    }
2405}
2406
2407fn is_static_stylex_local_arrow_call<'a>(
2408    call: &'a CallExpression<'a>,
2409    before: u32,
2410    static_values: &FxHashMap<&'a str, (u32, &'a Expression<'a>)>,
2411    imports: &FxHashMap<&'a str, (Lib, &'a str)>,
2412    root_reference_spans: &FxHashSet<Span>,
2413    visiting: &mut FxHashSet<&'a str>,
2414) -> bool {
2415    let Expression::Identifier(callee) = unwrap_transparent_expression(&call.callee) else {
2416        return false;
2417    };
2418    if !root_reference_spans.contains(&callee.span) {
2419        return false;
2420    }
2421    let name = callee.name.as_str();
2422    let Some(&(declaration_start, value)) = static_values.get(name) else {
2423        return false;
2424    };
2425    let Expression::ArrowFunctionExpression(arrow) = unwrap_transparent_expression(value) else {
2426        return false;
2427    };
2428    if declaration_start >= before
2429        || arrow.r#async
2430        || arrow.params.rest.is_some()
2431        || arrow.params.items.len() != call.arguments.len()
2432        || !visiting.insert(name)
2433    {
2434        return false;
2435    }
2436    let mut parameters = FxHashMap::default();
2437    for (parameter, argument) in arrow.params.items.iter().zip(&call.arguments) {
2438        let (BindingPattern::BindingIdentifier(binding), Some(argument)) =
2439            (&parameter.pattern, argument.as_expression())
2440        else {
2441            visiting.remove(name);
2442            return false;
2443        };
2444        parameters.insert(binding.name.as_str(), argument);
2445    }
2446    let arguments_static = call.arguments.iter().all(|argument| {
2447        argument.as_expression().is_some_and(|argument| {
2448            is_static_stylex_theme_override(
2449                argument,
2450                before,
2451                static_values,
2452                imports,
2453                root_reference_spans,
2454                visiting,
2455            )
2456        })
2457    });
2458    let context = StyleXArrowStaticContext {
2459        before,
2460        static_values,
2461        imports,
2462        root_reference_spans,
2463        parameters,
2464    };
2465    let body_static = arguments_static
2466        && stylex_arrow_expression_body(arrow)
2467            .is_some_and(|body| is_static_stylex_arrow_body(body, &context, visiting));
2468    visiting.remove(name);
2469    body_static
2470}
2471
2472struct StyleXArrowStaticContext<'maps, 'ast> {
2473    before: u32,
2474    static_values: &'maps FxHashMap<&'ast str, (u32, &'ast Expression<'ast>)>,
2475    imports: &'maps FxHashMap<&'ast str, (Lib, &'ast str)>,
2476    root_reference_spans: &'maps FxHashSet<Span>,
2477    parameters: FxHashMap<&'ast str, &'ast Expression<'ast>>,
2478}
2479
2480fn stylex_arrow_expression_body<'a>(
2481    arrow: &'a ArrowFunctionExpression<'a>,
2482) -> Option<&'a Expression<'a>> {
2483    arrow.body.as_expression()
2484}
2485
2486fn resolve_stylex_arrow_parameter_member<'a>(
2487    expression: &'a Expression<'a>,
2488    context: &StyleXArrowStaticContext<'_, 'a>,
2489    visiting: &mut FxHashSet<&'a str>,
2490) -> Option<ResolvedStyleXStaticMember<'a>> {
2491    let (base, _, segments) = binding_access_object_chain(expression)?;
2492    let argument = context.parameters.get(base)?;
2493    let mut resolver = StyleXStaticResolver {
2494        before: context.before,
2495        static_values: context.static_values,
2496        root_reference_spans: context.root_reference_spans,
2497    };
2498    let mut object = resolve_static_stylex_object(argument, &resolver, visiting)?;
2499    let mut segments = segments.into_iter().peekable();
2500    while let Some(segment) = segments.next() {
2501        let member = resolve_static_stylex_object_property(object, &segment, &resolver, visiting)?;
2502        if segments.peek().is_none() {
2503            return Some(member);
2504        }
2505        resolver = StyleXStaticResolver {
2506            before: member.before,
2507            ..resolver
2508        };
2509        object = resolve_static_stylex_object(member.value, &resolver, visiting)?;
2510    }
2511    None
2512}
2513
2514fn is_static_stylex_arrow_body<'a>(
2515    expression: &'a Expression<'a>,
2516    context: &StyleXArrowStaticContext<'_, 'a>,
2517    visiting: &mut FxHashSet<&'a str>,
2518) -> bool {
2519    let expression = unwrap_transparent_expression(expression);
2520    match expression {
2521        Expression::Identifier(identifier)
2522            if context.parameters.contains_key(identifier.name.as_str()) =>
2523        {
2524            true
2525        }
2526        Expression::TemplateLiteral(template) => template
2527            .expressions
2528            .iter()
2529            .all(|expression| is_static_stylex_arrow_body(expression, context, visiting)),
2530        Expression::UnaryExpression(unary) => {
2531            let bigint_context = StyleXBigIntContext {
2532                before: context.before,
2533                static_values: context.static_values,
2534                root_reference_spans: context.root_reference_spans,
2535                parameters: &context.parameters,
2536            };
2537            !unary.operator.is_delete()
2538                && (unary.operator != UnaryOperator::UnaryPlus
2539                    || !is_static_stylex_bigint_value_with_context(
2540                        &unary.argument,
2541                        &bigint_context,
2542                        &mut visiting.clone(),
2543                    ))
2544                && is_static_stylex_arrow_body(&unary.argument, context, visiting)
2545        }
2546        Expression::BinaryExpression(binary) => {
2547            let bigint_context = StyleXBigIntContext {
2548                before: context.before,
2549                static_values: context.static_values,
2550                root_reference_spans: context.root_reference_spans,
2551                parameters: &context.parameters,
2552            };
2553            !binary.operator.is_relational()
2554                && (!binary.operator.is_numeric_or_string_binary_operator()
2555                    || !is_static_stylex_bigint_pair(
2556                        &binary.left,
2557                        &binary.right,
2558                        &bigint_context,
2559                        &mut visiting.clone(),
2560                    ))
2561                && is_static_stylex_arrow_body(&binary.left, context, visiting)
2562                && is_static_stylex_arrow_body(&binary.right, context, visiting)
2563        }
2564        Expression::LogicalExpression(logical) => {
2565            is_static_stylex_arrow_body(&logical.left, context, visiting)
2566                && is_static_stylex_arrow_body(&logical.right, context, visiting)
2567        }
2568        Expression::ConditionalExpression(conditional) => [
2569            &conditional.test,
2570            &conditional.consequent,
2571            &conditional.alternate,
2572        ]
2573        .into_iter()
2574        .all(|expression| is_static_stylex_arrow_body(expression, context, visiting)),
2575        Expression::SequenceExpression(sequence) => sequence
2576            .expressions
2577            .iter()
2578            .all(|expression| is_static_stylex_arrow_body(expression, context, visiting)),
2579        Expression::StaticMemberExpression(_) | Expression::ComputedMemberExpression(_) => {
2580            if let Some(resolved) =
2581                resolve_stylex_arrow_parameter_member(expression, context, visiting)
2582            {
2583                return is_static_stylex_theme_override(
2584                    resolved.value,
2585                    resolved.before,
2586                    context.static_values,
2587                    context.imports,
2588                    context.root_reference_spans,
2589                    visiting,
2590                );
2591            }
2592            is_static_stylex_theme_override(
2593                expression,
2594                context.before,
2595                context.static_values,
2596                context.imports,
2597                context.root_reference_spans,
2598                visiting,
2599            )
2600        }
2601        _ => is_static_stylex_theme_override(
2602            expression,
2603            context.before,
2604            context.static_values,
2605            context.imports,
2606            context.root_reference_spans,
2607            visiting,
2608        ),
2609    }
2610}
2611
2612fn is_root_stylex_static_call(
2613    callee: &Expression<'_>,
2614    imports: &FxHashMap<&str, (Lib, &str)>,
2615    root_reference_spans: &FxHashSet<Span>,
2616) -> bool {
2617    match unwrap_transparent_expression(callee) {
2618        Expression::Identifier(identifier) => {
2619            root_reference_spans.contains(&identifier.span)
2620                && imports
2621                    .get(identifier.name.as_str())
2622                    .is_some_and(|(lib, role)| {
2623                        *lib == Lib::StyleX
2624                            && matches!(*role, "unstable_conditional" | "keyframes" | "positionTry")
2625                    })
2626        }
2627        Expression::StaticMemberExpression(member) => match &member.object {
2628            Expression::Identifier(object) => {
2629                root_reference_spans.contains(&object.span)
2630                    && imports
2631                        .get(object.name.as_str())
2632                        .is_some_and(|(lib, role)| {
2633                            *lib == Lib::StyleX
2634                                && (if *role == "types" {
2635                                    is_stylex_type_helper(member.property.name.as_str())
2636                                } else {
2637                                    matches!(
2638                                        member.property.name.as_str(),
2639                                        "unstable_conditional" | "keyframes" | "positionTry"
2640                                    )
2641                                })
2642                        })
2643            }
2644            Expression::StaticMemberExpression(namespace) => {
2645                let Expression::Identifier(object) = &namespace.object else {
2646                    return false;
2647                };
2648                root_reference_spans.contains(&object.span)
2649                    && imports
2650                        .get(object.name.as_str())
2651                        .is_some_and(|(lib, _)| *lib == Lib::StyleX)
2652                    && namespace.property.name.as_str() == "types"
2653                    && is_stylex_type_helper(member.property.name.as_str())
2654            }
2655            _ => false,
2656        },
2657        _ => false,
2658    }
2659}
2660
2661fn is_stylex_type_helper(name: &str) -> bool {
2662    matches!(
2663        name,
2664        "angle"
2665            | "color"
2666            | "image"
2667            | "integer"
2668            | "length"
2669            | "lengthPercentage"
2670            | "number"
2671            | "percentage"
2672            | "resolution"
2673            | "time"
2674            | "transformFunction"
2675            | "transformList"
2676            | "url"
2677    )
2678}
2679
2680/// The value of a static string or numeric computed-member key, or `None` for a
2681/// dynamic key that cannot be resolved without executing code.
2682fn static_computed_key(expr: &Expression<'_>) -> Option<String> {
2683    match unwrap_transparent_expression(expr) {
2684        Expression::StringLiteral(lit) => Some(lit.value.to_string()),
2685        Expression::NumericLiteral(lit) => Some(format_numeric_token(lit)),
2686        Expression::TemplateLiteral(template) if template.expressions.is_empty() => template
2687            .quasis
2688            .first()
2689            .map(|quasi| quasi.value.raw.to_string()),
2690        _ => None,
2691    }
2692}
2693
2694#[derive(Clone, Copy)]
2695struct RootBinding<'a> {
2696    name: &'a str,
2697    span: Span,
2698    reference_id: Option<ReferenceId>,
2699}
2700
2701fn root_binding<'a>(identifier: &'a IdentifierReference<'_>) -> RootBinding<'a> {
2702    RootBinding {
2703        name: identifier.name.as_str(),
2704        span: identifier.span,
2705        reference_id: identifier.reference_id.get(),
2706    }
2707}
2708
2709fn expression_root_binding<'a, 'b: 'a>(expr: &'a Expression<'b>) -> Option<RootBinding<'a>> {
2710    match expr {
2711        Expression::Identifier(id) => Some(root_binding(id)),
2712        Expression::StaticMemberExpression(member) => expression_root_binding(&member.object),
2713        Expression::ComputedMemberExpression(member) => expression_root_binding(&member.object),
2714        Expression::ParenthesizedExpression(expression) => {
2715            expression_root_binding(&expression.expression)
2716        }
2717        Expression::TSAsExpression(expression) => expression_root_binding(&expression.expression),
2718        Expression::TSSatisfiesExpression(expression) => {
2719            expression_root_binding(&expression.expression)
2720        }
2721        Expression::TSNonNullExpression(expression) => {
2722            expression_root_binding(&expression.expression)
2723        }
2724        Expression::TSTypeAssertion(expression) => expression_root_binding(&expression.expression),
2725        _ => None,
2726    }
2727}
2728
2729fn assignment_target_root_bindings<'a, 'b: 'a>(
2730    target: &'a AssignmentTarget<'b>,
2731) -> Vec<RootBinding<'a>> {
2732    let mut bindings = Vec::new();
2733    collect_assignment_target_root_bindings(target, &mut bindings);
2734    bindings
2735}
2736
2737fn assignment_target_receiver_expression<'a, 'b: 'a>(
2738    target: &'a AssignmentTarget<'b>,
2739) -> Option<&'a Expression<'b>> {
2740    match target {
2741        AssignmentTarget::StaticMemberExpression(member) => Some(&member.object),
2742        AssignmentTarget::ComputedMemberExpression(member) => Some(&member.object),
2743        AssignmentTarget::TSAsExpression(expression) => {
2744            call_receiver_expression(&expression.expression)
2745        }
2746        AssignmentTarget::TSSatisfiesExpression(expression) => {
2747            call_receiver_expression(&expression.expression)
2748        }
2749        AssignmentTarget::TSNonNullExpression(expression) => {
2750            call_receiver_expression(&expression.expression)
2751        }
2752        AssignmentTarget::TSTypeAssertion(expression) => {
2753            call_receiver_expression(&expression.expression)
2754        }
2755        _ => None,
2756    }
2757}
2758
2759fn collect_assignment_target_root_bindings<'a, 'b: 'a>(
2760    target: &'a AssignmentTarget<'b>,
2761    bindings: &mut Vec<RootBinding<'a>>,
2762) {
2763    match target {
2764        AssignmentTarget::AssignmentTargetIdentifier(id) => bindings.push(root_binding(id)),
2765        AssignmentTarget::StaticMemberExpression(member) => {
2766            bindings.extend(expression_root_binding(&member.object));
2767        }
2768        AssignmentTarget::ComputedMemberExpression(member) => {
2769            bindings.extend(expression_root_binding(&member.object));
2770        }
2771        AssignmentTarget::TSAsExpression(expression) => {
2772            bindings.extend(expression_root_binding(&expression.expression));
2773        }
2774        AssignmentTarget::TSSatisfiesExpression(expression) => {
2775            bindings.extend(expression_root_binding(&expression.expression));
2776        }
2777        AssignmentTarget::TSNonNullExpression(expression) => {
2778            bindings.extend(expression_root_binding(&expression.expression));
2779        }
2780        AssignmentTarget::TSTypeAssertion(expression) => {
2781            bindings.extend(expression_root_binding(&expression.expression));
2782        }
2783        AssignmentTarget::ArrayAssignmentTarget(array) => {
2784            for element in array.elements.iter().flatten() {
2785                collect_assignment_target_maybe_default_root_bindings(element, bindings);
2786            }
2787            if let Some(rest) = &array.rest {
2788                collect_assignment_target_root_bindings(&rest.target, bindings);
2789            }
2790        }
2791        AssignmentTarget::ObjectAssignmentTarget(object) => {
2792            for property in &object.properties {
2793                match property {
2794                    AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(property) => {
2795                        bindings.push(root_binding(&property.binding));
2796                    }
2797                    AssignmentTargetProperty::AssignmentTargetPropertyProperty(property) => {
2798                        collect_assignment_target_maybe_default_root_bindings(
2799                            &property.binding,
2800                            bindings,
2801                        );
2802                    }
2803                }
2804            }
2805            if let Some(rest) = &object.rest {
2806                collect_assignment_target_root_bindings(&rest.target, bindings);
2807            }
2808        }
2809        AssignmentTarget::PrivateFieldExpression(_) => {}
2810    }
2811}
2812
2813fn collect_assignment_target_maybe_default_root_bindings<'a, 'b: 'a>(
2814    target: &'a AssignmentTargetMaybeDefault<'b>,
2815    bindings: &mut Vec<RootBinding<'a>>,
2816) {
2817    if let AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(default) = target {
2818        collect_assignment_target_root_bindings(&default.binding, bindings);
2819    } else if let Some(target) = target.as_assignment_target() {
2820        collect_assignment_target_root_bindings(target, bindings);
2821    }
2822}
2823
2824fn simple_assignment_target_root_binding<'a, 'b: 'a>(
2825    target: &'a SimpleAssignmentTarget<'b>,
2826) -> Option<RootBinding<'a>> {
2827    match target {
2828        SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => Some(root_binding(id)),
2829        SimpleAssignmentTarget::StaticMemberExpression(member) => {
2830            expression_root_binding(&member.object)
2831        }
2832        SimpleAssignmentTarget::ComputedMemberExpression(member) => {
2833            expression_root_binding(&member.object)
2834        }
2835        SimpleAssignmentTarget::TSAsExpression(expression) => {
2836            expression_root_binding(&expression.expression)
2837        }
2838        SimpleAssignmentTarget::TSSatisfiesExpression(expression) => {
2839            expression_root_binding(&expression.expression)
2840        }
2841        SimpleAssignmentTarget::TSNonNullExpression(expression) => {
2842            expression_root_binding(&expression.expression)
2843        }
2844        SimpleAssignmentTarget::TSTypeAssertion(expression) => {
2845            expression_root_binding(&expression.expression)
2846        }
2847        SimpleAssignmentTarget::PrivateFieldExpression(_) => None,
2848    }
2849}
2850
2851/// Where the access binding comes from for a recognized token-definition call.
2852#[derive(Clone, Copy)]
2853enum BindingSource {
2854    /// The assigned identifier (`const vars = ...`).
2855    LhsIdent,
2856    /// An element of an array-destructure (`const [_, vars] = ...`).
2857    TupleElement(usize),
2858}
2859
2860/// A recognized token-definition call: where the binding comes from and which
2861/// argument carries the token object.
2862#[derive(Clone, Copy)]
2863struct Recognized {
2864    binding_source: BindingSource,
2865    tokens_arg: usize,
2866    origin: CssInJsTokenOrigin,
2867    stylex_shape: Option<StyleXTokenShape>,
2868}
2869
2870#[derive(Clone, Copy)]
2871enum StyleXTokenShape {
2872    Flat,
2873    Nested,
2874}
2875
2876/// Collects token-definition sites, gated on import provenance.
2877struct TokenDefCollector<'a> {
2878    lines: LineCounter<'a>,
2879    /// local-binding name -> (library, canonical role). Mirrors the
2880    /// `css_in_js_object` provenance map but for token-definition roles.
2881    imports: FxHashMap<&'a str, (Lib, &'a str)>,
2882    /// Top-level immutable object literals available to macro calls in source
2883    /// order. Mutable or non-object bindings are deliberately absent.
2884    const_objects: FxHashMap<SymbolId, (u32, &'a ObjectExpression<'a>)>,
2885    /// Every resolved reference to a top-level immutable object literal.
2886    const_object_references: FxHashMap<ReferenceId, SymbolId>,
2887    /// Top-level constant condition names used by computed StyleX condition keys.
2888    const_strings: FxHashMap<ReferenceId, (u32, &'a str)>,
2889    nested_depth: u32,
2890    collecting_mutations: bool,
2891    defs: Vec<CssInJsTokenDef>,
2892}
2893
2894impl<'a> TokenDefCollector<'a> {
2895    fn new(source: &'a str) -> Self {
2896        Self {
2897            lines: LineCounter::new(source),
2898            imports: FxHashMap::default(),
2899            const_objects: FxHashMap::default(),
2900            const_object_references: FxHashMap::default(),
2901            const_strings: FxHashMap::default(),
2902            nested_depth: 0,
2903            collecting_mutations: false,
2904            defs: Vec::new(),
2905        }
2906    }
2907
2908    /// Map each import binding from a recognized token library to its library +
2909    /// canonical role. Named imports dispatch on the imported (canonical) name so
2910    /// `import { createTheme as ct }` still fires; default / namespace bindings
2911    /// (`import * as stylex`) carry the local name for member-call recognition.
2912    fn build_import_map(&mut self, program: &Program<'a>) {
2913        for stmt in &program.body {
2914            let Statement::ImportDeclaration(decl) = stmt else {
2915                continue;
2916            };
2917            if decl.import_kind.is_type() {
2918                continue;
2919            }
2920            let Some(lib) = module_library(decl.source.value.as_str()) else {
2921                continue;
2922            };
2923            let Some(specifiers) = &decl.specifiers else {
2924                continue;
2925            };
2926            for specifier in specifiers {
2927                let (local, role) = match specifier {
2928                    ImportDeclarationSpecifier::ImportSpecifier(s) if !s.import_kind.is_type() => {
2929                        (s.local.name.as_str(), s.imported.name().as_str())
2930                    }
2931                    ImportDeclarationSpecifier::ImportSpecifier(_) => continue,
2932                    ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
2933                        (s.local.name.as_str(), s.local.name.as_str())
2934                    }
2935                    ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
2936                        (s.local.name.as_str(), s.local.name.as_str())
2937                    }
2938                };
2939                self.imports.insert(local, (lib, role));
2940            }
2941        }
2942    }
2943
2944    fn build_const_object_map(&mut self, program: &'a Program<'a>, scoping: &Scoping) {
2945        for stmt in &program.body {
2946            let declaration = match stmt {
2947                Statement::VariableDeclaration(declaration) => Some(&**declaration),
2948                Statement::ExportDeclaration(export) => match &export.declaration {
2949                    Declaration::VariableDeclaration(declaration) => Some(&**declaration),
2950                    _ => None,
2951                },
2952                _ => None,
2953            };
2954            let Some(declaration) = declaration else {
2955                continue;
2956            };
2957            if declaration.kind != VariableDeclarationKind::Const {
2958                continue;
2959            }
2960            for declarator in &declaration.declarations {
2961                let BindingPattern::BindingIdentifier(binding) = &declarator.id else {
2962                    continue;
2963                };
2964                let Some(symbol_id) = binding.symbol_id.get() else {
2965                    continue;
2966                };
2967                let declaration_start = declarator.span.start;
2968                match declarator.init.as_ref().map(unwrap_transparent_expression) {
2969                    Some(Expression::ObjectExpression(obj)) => {
2970                        self.const_objects
2971                            .insert(symbol_id, (declaration_start, obj));
2972                        self.const_object_references.extend(
2973                            scoping
2974                                .get_resolved_reference_ids(symbol_id)
2975                                .iter()
2976                                .copied()
2977                                .map(|reference_id| (reference_id, symbol_id)),
2978                        );
2979                    }
2980                    Some(Expression::StringLiteral(value)) => {
2981                        self.const_strings.extend(
2982                            scoping
2983                                .get_resolved_reference_ids(symbol_id)
2984                                .iter()
2985                                .copied()
2986                                .map(|reference_id| {
2987                                    (reference_id, (declaration_start, value.value.as_str()))
2988                                }),
2989                        );
2990                    }
2991                    _ => {}
2992                }
2993            }
2994        }
2995    }
2996
2997    /// Resolve a call's callee to `(library, role)` if its binding is a recognized
2998    /// token-library import. Handles both a named/aliased import callee
2999    /// (`defineVars(...)`) and a namespace member call (`stylex.defineVars(...)`).
3000    fn callee_role(&self, callee: &Expression<'a>) -> Option<(Lib, &'a str)> {
3001        match callee {
3002            Expression::Identifier(id) => self.imports.get(id.name.as_str()).copied(),
3003            Expression::StaticMemberExpression(member) => {
3004                let Expression::Identifier(obj) = &member.object else {
3005                    return None;
3006                };
3007                let (lib, _) = *self.imports.get(obj.name.as_str())?;
3008                // Member-call role is the accessed property (`stylex.defineVars`).
3009                Some((lib, member.property.name.as_str()))
3010            }
3011            _ => None,
3012        }
3013    }
3014
3015    /// Dispatch `(library, role, arg_count)` to a recognized token-definition
3016    /// form, or `None` (unrecognized, or a contract-implementation form whose
3017    /// contract is the canonical definition).
3018    fn recognize(lib: Lib, role: &str, arg_count: usize) -> Option<Recognized> {
3019        let single = |tokens_arg, origin, stylex_shape| {
3020            Some(Recognized {
3021                binding_source: BindingSource::LhsIdent,
3022                tokens_arg,
3023                origin,
3024                stylex_shape,
3025            })
3026        };
3027        match (lib, role) {
3028            // `defineVars(obj)` / `createThemeContract(obj)`: binding = the assigned
3029            // identifier, token object = arg 0.
3030            (Lib::StyleX, "defineVars") if arg_count >= 1 => {
3031                single(0, CssInJsTokenOrigin::StyleX, Some(StyleXTokenShape::Flat))
3032            }
3033            (Lib::StyleX, "unstable_defineVarsNested") if arg_count >= 1 => single(
3034                0,
3035                CssInJsTokenOrigin::StyleX,
3036                Some(StyleXTokenShape::Nested),
3037            ),
3038            (Lib::VanillaExtract, "createThemeContract") if arg_count >= 1 => {
3039                single(0, CssInJsTokenOrigin::VanillaExtract, None)
3040            }
3041            // 1-arg createTheme returns [themeClass, vars]; tokens on the second
3042            // destructure element. The 2-arg (contract, tokens) form fills an
3043            // existing contract and is skipped (createThemeContract is canonical).
3044            (Lib::VanillaExtract, "createTheme") if arg_count == 1 => Some(Recognized {
3045                binding_source: BindingSource::TupleElement(1),
3046                tokens_arg: 0,
3047                origin: CssInJsTokenOrigin::VanillaExtract,
3048                stylex_shape: None,
3049            }),
3050            // 2-arg createGlobalTheme(selector, tokens) returns the vars object;
3051            // the 3-arg (selector, contract, tokens) form returns void (contract
3052            // canonical), so only the 2-arg form is a definition site here.
3053            (Lib::VanillaExtract, "createGlobalTheme") if arg_count == 2 => {
3054                single(1, CssInJsTokenOrigin::VanillaExtract, None)
3055            }
3056            (Lib::Panda, "defineTokens") if arg_count >= 1 => {
3057                single(0, CssInJsTokenOrigin::Panda, None)
3058            }
3059            _ => None,
3060        }
3061    }
3062
3063    /// Extract the access binding name from a declarator's binding pattern for the
3064    /// recognized binding source.
3065    fn binding_name(decl: &VariableDeclarator<'a>, source: BindingSource) -> Option<&'a str> {
3066        match source {
3067            BindingSource::LhsIdent => match &decl.id {
3068                BindingPattern::BindingIdentifier(id) => Some(id.name.as_str()),
3069                _ => None,
3070            },
3071            BindingSource::TupleElement(index) => {
3072                let BindingPattern::ArrayPattern(arr) = &decl.id else {
3073                    return None;
3074                };
3075                let element = arr.elements.get(index)?.as_ref()?;
3076                match element {
3077                    BindingPattern::BindingIdentifier(id) => Some(id.name.as_str()),
3078                    _ => None,
3079                }
3080            }
3081        }
3082    }
3083
3084    fn process_declarator(&mut self, decl: &VariableDeclarator<'a>) {
3085        let Some(Expression::CallExpression(call)) = &decl.init else {
3086            return;
3087        };
3088        if self.process_panda_config_call(call) {
3089            return;
3090        }
3091        let Some((lib, role)) = self.callee_role(&call.callee) else {
3092            return;
3093        };
3094        let Some(recognized) = Self::recognize(lib, role, call.arguments.len()) else {
3095            return;
3096        };
3097        let Some(binding) = Self::binding_name(decl, recognized.binding_source) else {
3098            return;
3099        };
3100        let Some(obj) = self.resolve_object_argument(call, recognized.tokens_arg) else {
3101            return;
3102        };
3103        let mut tokens = Vec::new();
3104        let complete = if let Some(shape) = recognized.stylex_shape {
3105            let context = StyleXTokenContext {
3106                imports: &self.imports,
3107                const_strings: &self.const_strings,
3108                before: call.span.start,
3109            };
3110            collect_stylex_token_object(&mut self.lines, obj, "", shape, &context, &mut tokens)
3111        } else {
3112            collect_token_leaves(&mut self.lines, obj, "", recognized.origin, &mut tokens);
3113            true
3114        };
3115        if !complete {
3116            return;
3117        }
3118        if tokens.is_empty() {
3119            return;
3120        }
3121        self.defs.push(CssInJsTokenDef {
3122            binding: binding.to_owned(),
3123            origin: recognized.origin,
3124            tokens,
3125        });
3126    }
3127
3128    fn resolve_object_argument(
3129        &self,
3130        call: &'a oxc_ast::ast::CallExpression<'a>,
3131        index: usize,
3132    ) -> Option<&'a ObjectExpression<'a>> {
3133        let expr = call.arguments.get(index)?.as_expression()?;
3134        match unwrap_transparent_expression(expr) {
3135            Expression::ObjectExpression(obj) => Some(obj),
3136            Expression::Identifier(id) => {
3137                let reference_id = id.reference_id.get()?;
3138                let symbol_id = self.const_object_references.get(&reference_id)?;
3139                let (declaration_start, object) = self.const_objects.get(symbol_id)?;
3140                (*declaration_start < call.span.start).then_some(*object)
3141            }
3142            _ => None,
3143        }
3144    }
3145
3146    fn invalidate_const_object(&mut self, binding: Option<RootBinding<'_>>) {
3147        let Some(reference_id) = binding.and_then(|binding| binding.reference_id) else {
3148            return;
3149        };
3150        let Some(symbol_id) = self.const_object_references.get(&reference_id) else {
3151            return;
3152        };
3153        self.const_objects.remove(symbol_id);
3154    }
3155
3156    fn process_panda_config_call(&mut self, call: &oxc_ast::ast::CallExpression<'a>) -> bool {
3157        let Some((Lib::Panda, "defineConfig")) = self.callee_role(&call.callee) else {
3158            return false;
3159        };
3160        let Some(Argument::ObjectExpression(obj)) = call.arguments.first() else {
3161            return true;
3162        };
3163        let mut tokens = Vec::new();
3164        collect_panda_config_token_leaves(&mut self.lines, obj, &mut tokens);
3165        if !tokens.is_empty() {
3166            self.defs.push(CssInJsTokenDef {
3167                binding: PANDA_CONFIG_BINDING.to_string(),
3168                origin: CssInJsTokenOrigin::Panda,
3169                tokens,
3170            });
3171        }
3172        true
3173    }
3174}
3175
3176struct StyleXTokenContext<'maps, 'ast> {
3177    imports: &'maps FxHashMap<&'ast str, (Lib, &'ast str)>,
3178    const_strings: &'maps FxHashMap<ReferenceId, (u32, &'ast str)>,
3179    before: u32,
3180}
3181
3182fn collect_stylex_token_object(
3183    lines: &mut LineCounter<'_>,
3184    obj: &ObjectExpression<'_>,
3185    prefix: &str,
3186    shape: StyleXTokenShape,
3187    context: &StyleXTokenContext<'_, '_>,
3188    out: &mut Vec<CssInJsToken>,
3189) -> bool {
3190    for prop in &obj.properties {
3191        let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3192            return false;
3193        };
3194        let Some(key) = prop.key.static_name() else {
3195            return false;
3196        };
3197        let path = if prefix.is_empty() {
3198            key.to_string()
3199        } else {
3200            format!("{prefix}.{key}")
3201        };
3202        let line = lines.line_at(prop.key.span().start);
3203        match shape {
3204            StyleXTokenShape::Flat => out.push(CssInJsToken {
3205                path,
3206                def_line: line,
3207                value: stylex_static_token_value(&prop.value, context.imports),
3208            }),
3209            StyleXTokenShape::Nested => match unwrap_transparent_expression(&prop.value) {
3210                Expression::ObjectExpression(nested)
3211                    if !is_stylex_conditional_object(
3212                        nested,
3213                        context.const_strings,
3214                        context.before,
3215                    ) =>
3216                {
3217                    if !collect_stylex_token_object(lines, nested, &path, shape, context, out) {
3218                        return false;
3219                    }
3220                }
3221                value => out.push(CssInJsToken {
3222                    path,
3223                    def_line: line,
3224                    value: stylex_static_token_value(value, context.imports),
3225                }),
3226            },
3227        }
3228    }
3229    true
3230}
3231
3232fn is_stylex_conditional_object(
3233    obj: &ObjectExpression<'_>,
3234    const_strings: &FxHashMap<ReferenceId, (u32, &str)>,
3235    before: u32,
3236) -> bool {
3237    let mut has_default = false;
3238    for prop in &obj.properties {
3239        let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3240            return false;
3241        };
3242        let key = if prop.computed {
3243            match prop.key.as_expression() {
3244                Some(Expression::Identifier(id)) => {
3245                    let Some(reference_id) = id.reference_id.get() else {
3246                        return false;
3247                    };
3248                    let Some((declaration_start, condition)) = const_strings.get(&reference_id)
3249                    else {
3250                        return false;
3251                    };
3252                    if *declaration_start >= before {
3253                        return false;
3254                    }
3255                    (*condition).to_string()
3256                }
3257                Some(expression) => {
3258                    let Some(condition) = static_computed_key(expression) else {
3259                        return false;
3260                    };
3261                    condition
3262                }
3263                None => return false,
3264            }
3265        } else {
3266            let Some(key) = prop.key.static_name() else {
3267                return false;
3268            };
3269            key.to_string()
3270        };
3271        if key == "default" {
3272            has_default = true;
3273        } else if !key.starts_with('@') {
3274            return false;
3275        }
3276    }
3277    has_default
3278}
3279
3280fn stylex_static_token_value(
3281    value: &Expression<'_>,
3282    imports: &FxHashMap<&str, (Lib, &str)>,
3283) -> Option<String> {
3284    match unwrap_transparent_expression(value) {
3285        Expression::ObjectExpression(obj) => obj.properties.iter().find_map(|prop| {
3286            let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3287                return None;
3288            };
3289            (prop.key.static_name().as_deref() == Some("default"))
3290                .then(|| stylex_static_token_value(&prop.value, imports))
3291                .flatten()
3292        }),
3293        Expression::CallExpression(call) if is_stylex_static_value_call(&call.callee, imports) => {
3294            call.arguments
3295                .first()
3296                .and_then(Argument::as_expression)
3297                .and_then(|value| stylex_static_token_value(value, imports))
3298        }
3299        _ => static_token_value(value),
3300    }
3301}
3302
3303fn is_stylex_static_value_call(
3304    callee: &Expression<'_>,
3305    imports: &FxHashMap<&str, (Lib, &str)>,
3306) -> bool {
3307    match callee {
3308        Expression::Identifier(id) => imports
3309            .get(id.name.as_str())
3310            .is_some_and(|(lib, role)| *lib == Lib::StyleX && *role == "unstable_conditional"),
3311        Expression::StaticMemberExpression(member) => match &member.object {
3312            Expression::Identifier(object) => {
3313                imports
3314                    .get(object.name.as_str())
3315                    .is_some_and(|(lib, role)| {
3316                        *lib == Lib::StyleX
3317                            && (*role == "types"
3318                                || member.property.name.as_str() == "unstable_conditional")
3319                    })
3320            }
3321            Expression::StaticMemberExpression(namespace) => {
3322                let Expression::Identifier(object) = &namespace.object else {
3323                    return false;
3324                };
3325                imports.get(object.name.as_str()).is_some_and(|(lib, _)| {
3326                    *lib == Lib::StyleX && namespace.property.name.as_str() == "types"
3327                })
3328            }
3329            _ => false,
3330        },
3331        _ => false,
3332    }
3333}
3334
3335/// Flatten an object literal into dotted LEAF paths. An inline-object value
3336/// recurses (an intermediate token GROUP, not a token); a value-producing
3337/// expression (string / number / `null` contract leaf / call like
3338/// `px(2 * grid)` / template / member access like `colors.red['500']`) is a LEAF
3339/// token. A BARE IDENTIFIER value (`palette: tailwindPalette`) is SKIPPED: it
3340/// references something whose structure is invisible here, most often an imported
3341/// token GROUP (recording it as a leaf would invent a phantom token and wrongly
3342/// credit every `vars.palette.<x>` access to it). Spreads and computed keys are
3343/// skipped because they cannot be resolved statically.
3344fn collect_token_leaves(
3345    lines: &mut LineCounter<'_>,
3346    obj: &ObjectExpression<'_>,
3347    prefix: &str,
3348    origin: CssInJsTokenOrigin,
3349    out: &mut Vec<CssInJsToken>,
3350) {
3351    for prop in &obj.properties {
3352        let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3353            continue;
3354        };
3355        let Some(key) = prop.key.static_name() else {
3356            continue;
3357        };
3358        let path = if prefix.is_empty() {
3359            key.to_string()
3360        } else {
3361            format!("{prefix}.{key}")
3362        };
3363        match &prop.value {
3364            Expression::ObjectExpression(nested)
3365                if origin == CssInJsTokenOrigin::Panda
3366                    && !prefix.is_empty()
3367                    && object_has_static_key(nested, "value") =>
3368            {
3369                out.push(CssInJsToken {
3370                    path,
3371                    def_line: lines.line_at(prop.key.span().start),
3372                    value: object_static_property_value(nested, "value"),
3373                });
3374            }
3375            Expression::ObjectExpression(nested) => {
3376                collect_token_leaves(lines, nested, &path, origin, out);
3377            }
3378            // A bare identifier is an unresolvable reference, usually an imported
3379            // token group; do not record it as a leaf.
3380            Expression::Identifier(_) => {}
3381            _ => out.push(CssInJsToken {
3382                value: static_token_value(&prop.value),
3383                path,
3384                def_line: lines.line_at(prop.key.span().start),
3385            }),
3386        }
3387    }
3388}
3389
3390fn object_static_property_value(obj: &ObjectExpression<'_>, wanted: &str) -> Option<String> {
3391    obj.properties.iter().find_map(|prop| {
3392        let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3393            return None;
3394        };
3395        (prop.key.static_name().as_deref() == Some(wanted))
3396            .then(|| static_token_value(&prop.value))
3397            .flatten()
3398    })
3399}
3400
3401fn static_token_value(value: &Expression<'_>) -> Option<String> {
3402    match value {
3403        Expression::StringLiteral(lit) => {
3404            let text = lit.value.as_str().trim();
3405            (!text.is_empty()).then(|| text.to_string())
3406        }
3407        Expression::NumericLiteral(num) => Some(format_numeric_token(num)),
3408        Expression::UnaryExpression(unary) if unary.operator == UnaryOperator::UnaryNegation => {
3409            if let Expression::NumericLiteral(num) = &unary.argument {
3410                Some(format!("-{}", format_numeric_token(num)))
3411            } else {
3412                None
3413            }
3414        }
3415        _ => None,
3416    }
3417}
3418
3419fn format_numeric_token(num: &NumericLiteral<'_>) -> String {
3420    if num.value.fract() == 0.0 {
3421        format!("{:.0}", num.value)
3422    } else {
3423        num.value.to_string()
3424    }
3425}
3426
3427fn is_theme_binding_name(name: &str) -> bool {
3428    let lower = name.to_ascii_lowercase();
3429    lower == "theme" || lower.ends_with("theme")
3430}
3431
3432fn object_has_static_key(obj: &ObjectExpression<'_>, wanted: &str) -> bool {
3433    obj.properties.iter().any(|prop| {
3434        let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3435            return false;
3436        };
3437        prop.key.static_name().is_some_and(|key| key == wanted)
3438    })
3439}
3440
3441fn object_static_property_object<'a>(
3442    obj: &'a ObjectExpression<'a>,
3443    wanted: &str,
3444) -> Option<&'a ObjectExpression<'a>> {
3445    obj.properties.iter().find_map(|prop| {
3446        let ObjectPropertyKind::ObjectProperty(prop) = prop else {
3447            return None;
3448        };
3449        if prop.key.static_name().as_deref() == Some(wanted)
3450            && let Expression::ObjectExpression(value) = &prop.value
3451        {
3452            Some(&**value)
3453        } else {
3454            None
3455        }
3456    })
3457}
3458
3459fn collect_panda_config_token_leaves(
3460    lines: &mut LineCounter<'_>,
3461    obj: &ObjectExpression<'_>,
3462    out: &mut Vec<CssInJsToken>,
3463) {
3464    let Some(theme) = object_static_property_object(obj, "theme") else {
3465        return;
3466    };
3467    for key in ["tokens", "semanticTokens"] {
3468        if let Some(tokens) = object_static_property_object(theme, key) {
3469            collect_token_leaves(lines, tokens, "", CssInJsTokenOrigin::Panda, out);
3470        }
3471    }
3472}
3473
3474impl<'a> Visit<'a> for TokenDefCollector<'a> {
3475    fn visit_variable_declarator(&mut self, decl: &VariableDeclarator<'a>) {
3476        if !self.collecting_mutations && self.nested_depth == 0 {
3477            self.process_declarator(decl);
3478        }
3479        walk::walk_variable_declarator(self, decl);
3480    }
3481
3482    fn visit_function(&mut self, function: &Function<'a>, flags: ScopeFlags) {
3483        self.nested_depth = self.nested_depth.saturating_add(1);
3484        walk::walk_function(self, function, flags);
3485        self.nested_depth = self.nested_depth.saturating_sub(1);
3486    }
3487
3488    fn visit_arrow_function_expression(&mut self, function: &ArrowFunctionExpression<'a>) {
3489        self.nested_depth = self.nested_depth.saturating_add(1);
3490        walk::walk_arrow_function_expression(self, function);
3491        self.nested_depth = self.nested_depth.saturating_sub(1);
3492    }
3493
3494    fn visit_block_statement(&mut self, block: &BlockStatement<'a>) {
3495        self.nested_depth = self.nested_depth.saturating_add(1);
3496        walk::walk_block_statement(self, block);
3497        self.nested_depth = self.nested_depth.saturating_sub(1);
3498    }
3499
3500    fn visit_assignment_expression(&mut self, assignment: &AssignmentExpression<'a>) {
3501        if self.collecting_mutations {
3502            for binding in assignment_target_root_bindings(&assignment.left) {
3503                self.invalidate_const_object(Some(binding));
3504            }
3505        }
3506        walk::walk_assignment_expression(self, assignment);
3507    }
3508
3509    fn visit_update_expression(&mut self, update: &UpdateExpression<'a>) {
3510        if self.collecting_mutations {
3511            self.invalidate_const_object(simple_assignment_target_root_binding(&update.argument));
3512        }
3513        walk::walk_update_expression(self, update);
3514    }
3515
3516    fn visit_unary_expression(&mut self, expression: &UnaryExpression<'a>) {
3517        if self.collecting_mutations && expression.operator.is_delete() {
3518            self.invalidate_const_object(expression_root_binding(&expression.argument));
3519        }
3520        walk::walk_unary_expression(self, expression);
3521    }
3522
3523    fn visit_call_expression(&mut self, call: &CallExpression<'a>) {
3524        if self.collecting_mutations && self.callee_role(&call.callee).is_none() {
3525            if let Some(receiver) = call_receiver_root(&call.callee) {
3526                self.invalidate_const_object(Some(receiver));
3527            }
3528            let possibly_mutated: Vec<RootBinding<'_>> = call
3529                .arguments
3530                .iter()
3531                .filter_map(Argument::as_expression)
3532                .filter_map(expression_root_binding)
3533                .collect();
3534            for binding in possibly_mutated {
3535                self.invalidate_const_object(Some(binding));
3536            }
3537        }
3538        walk::walk_call_expression(self, call);
3539    }
3540
3541    fn visit_export_default_declaration(
3542        &mut self,
3543        decl: &oxc_ast::ast::ExportDefaultDeclaration<'a>,
3544    ) {
3545        if !self.collecting_mutations
3546            && let Some(Expression::CallExpression(call)) = decl.declaration.as_expression()
3547        {
3548            self.process_panda_config_call(call);
3549        }
3550        walk::walk_export_default_declaration(self, decl);
3551    }
3552}
3553
3554/// Count `\n` bytes in `s` as a saturating `u32`.
3555fn count_newlines_u32(s: &str) -> u32 {
3556    u32::try_from(s.bytes().filter(|&b| b == b'\n').count()).unwrap_or(u32::MAX)
3557}
3558
3559/// Incremental 1-based line-number counter over a fixed `source` (issue #1843
3560/// follow-up). The old free `line_at` counted the newlines in `source[..offset]`
3561/// from the start on every call, so a token file with M definitions cost
3562/// O(M * source-len). Definitions and consumer hits are visited in source order,
3563/// so this advances a cursor by only the newline delta since the previous query
3564/// (`source[last_offset..offset]`), making a whole walk O(source-len). A
3565/// non-monotonic query rewinds by the reverse delta, and an out-of-range or
3566/// non-char-boundary offset clamps to line 1 exactly as the previous `line_at`
3567/// did (matching `css::line_at_offset`), so the result is byte-identical to a
3568/// from-scratch count regardless of query order. Deliberately a plain cursor,
3569/// mirroring the `MAX_BINDING_PATH_DEPTH` bounded-work companions.
3570struct LineCounter<'a> {
3571    source: &'a str,
3572    /// Byte offset of the last query whose line was computed. Always a valid
3573    /// char boundary (only ever assigned a boundary-checked `end`).
3574    last_offset: usize,
3575    /// `1 + count_newlines(&source[..last_offset])`, the invariant maintained
3576    /// across queries.
3577    last_line: u32,
3578}
3579
3580impl<'a> LineCounter<'a> {
3581    fn new(source: &'a str) -> Self {
3582        Self {
3583            source,
3584            last_offset: 0,
3585            last_line: 1,
3586        }
3587    }
3588
3589    /// 1-based line number of `offset`, byte-identical to the previous
3590    /// `line_at(source, offset)`.
3591    fn line_at(&mut self, offset: u32) -> u32 {
3592        let end = (offset as usize).min(self.source.len());
3593        // Preserve the previous `.get(..end)` contract: a non-char-boundary
3594        // offset clamps to line 1 rather than panicking on the slice below.
3595        if !self.source.is_char_boundary(end) {
3596            return 1;
3597        }
3598        if end >= self.last_offset {
3599            let delta = count_newlines_u32(&self.source[self.last_offset..end]);
3600            self.last_line = self.last_line.saturating_add(delta);
3601        } else {
3602            let delta = count_newlines_u32(&self.source[end..self.last_offset]);
3603            self.last_line = self.last_line.saturating_sub(delta);
3604        }
3605        self.last_offset = end;
3606        self.last_line
3607    }
3608}
3609
3610#[cfg(all(test, not(miri)))]
3611mod tests {
3612    use super::*;
3613
3614    fn defs(source: &str) -> Vec<CssInJsTokenDef> {
3615        css_in_js_token_defs(source, Path::new("tokens.ts"))
3616    }
3617
3618    fn paths(defs: &[CssInJsTokenDef], binding: &str) -> Vec<String> {
3619        defs.iter()
3620            .find(|d| d.binding == binding)
3621            .map(|d| d.tokens.iter().map(|t| t.path.clone()).collect())
3622            .unwrap_or_default()
3623    }
3624
3625    fn token_values(defs: &[CssInJsTokenDef], binding: &str) -> Vec<(String, Option<String>)> {
3626        defs.iter()
3627            .find(|d| d.binding == binding)
3628            .map(|d| {
3629                d.tokens
3630                    .iter()
3631                    .map(|t| (t.path.clone(), t.value.clone()))
3632                    .collect()
3633            })
3634            .unwrap_or_default()
3635    }
3636
3637    fn theme_defs(source: &str) -> Vec<CssInJsTokenDef> {
3638        css_in_js_theme_token_defs(source, Path::new("theme.ts"))
3639    }
3640
3641    #[test]
3642    fn incremental_def_lines_match_source_order() {
3643        // Issue #1843 follow-up (FIX B): the incremental LineCounter must yield
3644        // the same def_line as a from-scratch newline count for every token,
3645        // across MULTIPLE definitions on multiple lines (the source-order cursor
3646        // must advance correctly between separate `defineVars` calls, not just
3647        // within one).
3648        let src = "import { defineVars } from '@stylexjs/stylex';\n\
3649export const colors = defineVars({\n\
3650primary: '#000',\n\
3651secondary: '#fff',\n\
3652});\n\
3653export const space = defineVars({\n\
3654sm: '4px',\n\
3655lg: '16px',\n\
3656});\n";
3657        let d = defs(src);
3658        let line_of = |binding: &str, path: &str| {
3659            d.iter()
3660                .find(|def| def.binding == binding)
3661                .and_then(|def| def.tokens.iter().find(|t| t.path == path))
3662                .unwrap_or_else(|| panic!("token {binding}.{path} present"))
3663                .def_line
3664        };
3665        assert_eq!(line_of("colors", "primary"), 3);
3666        assert_eq!(line_of("colors", "secondary"), 4);
3667        assert_eq!(line_of("space", "sm"), 7);
3668        assert_eq!(line_of("space", "lg"), 8);
3669    }
3670
3671    #[test]
3672    fn stylex_define_vars_flat_namespace_call() {
3673        let d = defs(
3674            r"
3675import * as stylex from '@stylexjs/stylex';
3676export const vars = stylex.defineVars({ primaryColor: '#3b82f6', spacingSm: '4px' });
3677",
3678        );
3679        assert_eq!(paths(&d, "vars"), vec!["primaryColor", "spacingSm"]);
3680        assert_eq!(
3681            token_values(&d, "vars"),
3682            vec![
3683                ("primaryColor".to_string(), Some("#3b82f6".to_string())),
3684                ("spacingSm".to_string(), Some("4px".to_string())),
3685            ]
3686        );
3687    }
3688
3689    #[test]
3690    fn stylex_define_vars_conditional_value_is_one_flat_token() {
3691        let d = defs(
3692            r"
3693import { defineVars } from '@stylexjs/stylex';
3694const DARK = '@media (prefers-color-scheme: dark)';
3695export const vars = defineVars({ color: { default: '#000', [DARK]: '#fff' } });
3696",
3697        );
3698        assert_eq!(paths(&d, "vars"), vec!["color"]);
3699        assert_eq!(
3700            token_values(&d, "vars"),
3701            vec![("color".to_string(), Some("#000".to_string()))]
3702        );
3703    }
3704
3705    #[test]
3706    fn stylex_nested_vars_recurse_and_stop_at_conditional_leaves() {
3707        let d = defs(
3708            r"
3709import * as stylex from 'stylex';
3710import { unstable_conditional as cond } from 'stylex';
3711const DARK = '@media (prefers-color-scheme: dark)';
3712export const vars = stylex.unstable_defineVarsNested({
3713  surface: {
3714    bg: { default: '#fff', [DARK]: '#111' },
3715    text: cond({ default: '#000', [DARK]: '#eee' }),
3716  },
3717  typed: stylex.types.color('red'),
3718});
3719",
3720        );
3721        assert_eq!(
3722            paths(&d, "vars"),
3723            vec!["surface.bg", "surface.text", "typed"]
3724        );
3725        assert_eq!(
3726            token_values(&d, "vars"),
3727            vec![
3728                ("surface.bg".to_string(), Some("#fff".to_string())),
3729                ("surface.text".to_string(), Some("#000".to_string())),
3730                ("typed".to_string(), Some("red".to_string())),
3731            ]
3732        );
3733    }
3734
3735    #[test]
3736    fn stylex_nested_default_token_namespace_is_not_a_conditional_leaf() {
3737        let d = defs(
3738            r"
3739import * as stylex from '@stylexjs/stylex';
3740export const vars = stylex.unstable_defineVarsNested({
3741  button: {
3742    primary: {
3743      background: {
3744        default: stylex.unstable_conditional({ default: 'blue' }),
3745        hovered: stylex.unstable_conditional({ default: 'navy' }),
3746      },
3747    },
3748  },
3749});
3750",
3751        );
3752        assert_eq!(
3753            paths(&d, "vars"),
3754            vec![
3755                "button.primary.background.default",
3756                "button.primary.background.hovered",
3757            ]
3758        );
3759    }
3760
3761    #[test]
3762    fn stylex_nested_static_computed_conditions_remain_one_leaf() {
3763        let d = defs(
3764            r"
3765import * as stylex from '@stylexjs/stylex';
3766export const vars = stylex.unstable_defineVarsNested({
3767  surface: {
3768    color: {
3769      ['default']: '#fff',
3770      [`@media (prefers-color-scheme: dark)`]: '#111',
3771    },
3772  },
3773});
3774",
3775        );
3776        assert_eq!(paths(&d, "vars"), vec!["surface.color"]);
3777    }
3778
3779    #[test]
3780    fn stylex_define_vars_resolves_local_const_object() {
3781        let d = defs(
3782            r"
3783import { defineVars as define } from '@stylexjs/stylex';
3784const values = { foreground: '#111', background: '#fff' };
3785export const vars = define(values);
3786",
3787        );
3788        assert_eq!(paths(&d, "vars"), vec!["foreground", "background"]);
3789    }
3790
3791    #[test]
3792    fn stylex_define_vars_resolves_transparent_typescript_wrappers() {
3793        let d = defs(
3794            r"
3795import { defineVars } from '@stylexjs/stylex';
3796const values = { foreground: '#111' } as const;
3797export const vars = defineVars(values);
3798export const more = defineVars(
3799  { background: '#fff' } satisfies Record<string, string>,
3800);
3801",
3802        );
3803        assert_eq!(paths(&d, "vars"), vec!["foreground"]);
3804        assert_eq!(paths(&d, "more"), vec!["background"]);
3805    }
3806
3807    #[test]
3808    fn stylex_define_vars_abstains_after_local_const_object_mutation() {
3809        let d = defs(
3810            r"
3811import { defineVars } from '@stylexjs/stylex';
3812const values = { foreground: '#111' };
3813values.foreground = getColor();
3814export const vars = defineVars(values);
3815",
3816        );
3817        assert!(d.is_empty(), "mutated token objects must abstain: {d:?}");
3818    }
3819
3820    #[test]
3821    fn stylex_define_vars_abstains_when_local_object_is_mutated_after_definition() {
3822        let d = defs(
3823            r"
3824import { defineVars } from '@stylexjs/stylex';
3825const values = { foreground: '#111' };
3826export const vars = defineVars(values);
3827values.foreground = getColor();
3828",
3829        );
3830        assert!(
3831            d.is_empty(),
3832            "later mutation must invalidate the definition: {d:?}"
3833        );
3834    }
3835
3836    #[test]
3837    fn stylex_define_vars_abstains_after_delete_or_receiver_call() {
3838        for mutation in ["delete values.foreground;", "values.mutate();"] {
3839            let source = format!(
3840                "import {{ defineVars }} from '@stylexjs/stylex';\nconst values = {{ foreground: '#111' }};\n{mutation}\nexport const vars = defineVars(values);"
3841            );
3842            let d = defs(&source);
3843            assert!(d.is_empty(), "mutated token objects must abstain: {source}");
3844        }
3845    }
3846
3847    #[test]
3848    fn stylex_define_vars_requires_declared_unmutated_root_object() {
3849        for source in [
3850            r"
3851import { defineVars } from '@stylexjs/stylex';
3852export const vars = defineVars(values);
3853const values = { foreground: '#111' };
3854",
3855            r"
3856import { defineVars } from '@stylexjs/stylex';
3857const values = { foreground: '#111' };
3858({ foreground: values.foreground } = next);
3859export const vars = defineVars(values);
3860",
3861            r"
3862import { defineVars } from '@stylexjs/stylex';
3863const values = { foreground: '#111' };
3864[values.foreground] = next;
3865export const vars = defineVars(values);
3866",
3867        ] {
3868            let d = defs(source);
3869            assert!(d.is_empty(), "invalid static object must abstain: {source}");
3870        }
3871    }
3872
3873    #[test]
3874    fn stylex_define_vars_ignores_shadowed_mutation() {
3875        let d = defs(
3876            r"
3877import { defineVars } from '@stylexjs/stylex';
3878const values = { foreground: '#111' };
3879function mutate(values) { values.foreground = '#fff'; }
3880export const vars = defineVars(values);
3881",
3882        );
3883        assert_eq!(paths(&d, "vars"), vec!["foreground"]);
3884    }
3885
3886    #[test]
3887    fn stylex_nested_condition_must_be_declared_before_use() {
3888        let d = defs(
3889            r"
3890import { unstable_defineVarsNested } from '@stylexjs/stylex';
3891export const vars = unstable_defineVarsNested({
3892  surface: { color: { default: '#fff', [DARK]: '#111' } },
3893});
3894const DARK = '@media (prefers-color-scheme: dark)';
3895",
3896        );
3897        assert!(d.is_empty(), "TDZ condition must abstain: {d:?}");
3898    }
3899
3900    #[test]
3901    fn stylex_nested_scope_shadow_does_not_define_tokens() {
3902        let d = defs(
3903            r"
3904import * as stylex from '@stylexjs/stylex';
3905export const makeVars = (stylex) => {
3906  const vars = stylex.defineVars({ foreground: '#111' });
3907  return vars;
3908};
3909",
3910        );
3911        assert!(d.is_empty(), "nested shadowed calls must abstain: {d:?}");
3912    }
3913
3914    #[test]
3915    fn stylex_arbitrary_value_call_does_not_invent_comparable_value() {
3916        let d = defs(
3917            r"
3918import { defineVars } from '@stylexjs/stylex';
3919const dynamic = value => value;
3920export const vars = defineVars({ color: dynamic({ default: '#111' }) });
3921",
3922        );
3923        assert_eq!(paths(&d, "vars"), vec!["color"]);
3924        assert_eq!(token_values(&d, "vars"), vec![("color".to_string(), None)]);
3925    }
3926
3927    #[test]
3928    fn panda_define_tokens_collapses_value_objects() {
3929        let d = defs(
3930            r"
3931import { defineTokens } from '@pandacss/dev';
3932export const tokens = defineTokens({
3933  colors: {
3934    brand: { value: '#f05a28' },
3935    accent: { value: '{colors.brand}' },
3936  },
3937  spacing: { card: { value: '1rem' } },
3938});
3939",
3940        );
3941        assert_eq!(
3942            paths(&d, "tokens"),
3943            vec!["colors.brand", "colors.accent", "spacing.card"]
3944        );
3945        assert_eq!(
3946            token_values(&d, "tokens"),
3947            vec![
3948                ("colors.brand".to_string(), Some("#f05a28".to_string())),
3949                (
3950                    "colors.accent".to_string(),
3951                    Some("{colors.brand}".to_string())
3952                ),
3953                ("spacing.card".to_string(), Some("1rem".to_string())),
3954            ]
3955        );
3956        assert_eq!(
3957            d.iter().find(|d| d.binding == "tokens").unwrap().origin,
3958            CssInJsTokenOrigin::Panda
3959        );
3960    }
3961
3962    #[test]
3963    fn panda_define_config_extracts_tokens_and_semantic_tokens() {
3964        let d = defs(
3965            r"
3966import { defineConfig } from '@pandacss/dev';
3967
3968export default defineConfig({
3969  theme: {
3970    tokens: {
3971      colors: {
3972        brand: { value: '#f05a28' },
3973      },
3974    },
3975    semanticTokens: {
3976      colors: {
3977        surface: { value: { base: '{colors.brand}', _dark: '#111111' } },
3978      },
3979    },
3980    recipes: {
3981      card: { base: { color: 'colors.brand' } },
3982    },
3983  },
3984});
3985",
3986        );
3987        assert_eq!(
3988            paths(&d, "pandaConfig"),
3989            vec!["colors.brand", "colors.surface"]
3990        );
3991        assert_eq!(
3992            token_values(&d, "pandaConfig"),
3993            vec![
3994                ("colors.brand".to_string(), Some("#f05a28".to_string())),
3995                ("colors.surface".to_string(), None),
3996            ]
3997        );
3998        assert_eq!(
3999            d.iter()
4000                .find(|d| d.binding == "pandaConfig")
4001                .unwrap()
4002                .origin,
4003            CssInJsTokenOrigin::Panda
4004        );
4005    }
4006
4007    #[test]
4008    fn theme_object_definitions_flatten_static_leaves() {
4009        let d = theme_defs(
4010            r"
4011export const appTheme = {
4012  colors: { brand: '#f05a28', accent: '#111' },
4013  space: { card: '1rem' },
4014  dynamic: palette,
4015};
4016",
4017        );
4018        assert_eq!(
4019            paths(&d, "appTheme"),
4020            vec!["colors.brand", "colors.accent", "space.card"]
4021        );
4022        assert_eq!(
4023            token_values(&d, "appTheme"),
4024            vec![
4025                ("colors.brand".to_string(), Some("#f05a28".to_string())),
4026                ("colors.accent".to_string(), Some("#111".to_string())),
4027                ("space.card".to_string(), Some("1rem".to_string())),
4028            ]
4029        );
4030        assert_eq!(
4031            d.iter().find(|d| d.binding == "appTheme").unwrap().origin,
4032            CssInJsTokenOrigin::Theme
4033        );
4034    }
4035
4036    #[test]
4037    fn theme_consumers_credit_props_and_destructured_theme_reads() {
4038        let leaves = ["colors.brand", "space.card"]
4039            .into_iter()
4040            .map(str::to_owned)
4041            .collect();
4042        let hits = scan_one(
4043            r"
4044import styled from 'styled-components';
4045export const Card = styled.div`
4046  color: ${({ theme }) => theme.colors.brand};
4047  margin: ${props => props.theme.space.card};
4048`;
4049",
4050            Path::new("card.tsx"),
4051            ConsumerQuery::ThemeReads {
4052                leaf_paths: &leaves,
4053            },
4054        );
4055        let mut token_paths: Vec<String> = hits.into_iter().map(|hit| hit.token_path).collect();
4056        token_paths.sort();
4057        assert_eq!(token_paths, vec!["colors.brand", "space.card"]);
4058    }
4059
4060    #[test]
4061    fn ve_create_theme_tuple_destructure_binds_element_one() {
4062        let d = defs(
4063            r"
4064import { createTheme } from '@vanilla-extract/css';
4065export const [themeClass, vars] = createTheme({
4066  color: { brand: 'red', accent: 'blue' },
4067  space: { small: '4px' },
4068});
4069",
4070        );
4071        // Token paths bind to `vars` (element 1), NOT `themeClass`.
4072        assert_eq!(
4073            paths(&d, "vars"),
4074            vec!["color.brand", "color.accent", "space.small"]
4075        );
4076        assert!(paths(&d, "themeClass").is_empty());
4077    }
4078
4079    #[test]
4080    fn ve_create_theme_contract_null_leaves() {
4081        let d = defs(
4082            r"
4083import { createThemeContract } from '@vanilla-extract/css';
4084export const vars = createThemeContract({ color: { brand: null, accent: null } });
4085",
4086        );
4087        // `null` contract leaves are tokens (the contract declares the shape).
4088        assert_eq!(paths(&d, "vars"), vec!["color.brand", "color.accent"]);
4089    }
4090
4091    #[test]
4092    fn ve_create_global_theme_two_arg_binds_lhs_tokens_in_second_arg() {
4093        let d = defs(
4094            r"
4095import { createGlobalTheme } from '@vanilla-extract/css';
4096export const vars = createGlobalTheme(':root', { color: { brand: 'red' } });
4097",
4098        );
4099        assert_eq!(paths(&d, "vars"), vec!["color.brand"]);
4100    }
4101
4102    #[test]
4103    fn ve_create_theme_two_arg_contract_impl_is_not_a_definition_site() {
4104        // The 2-arg form fills an existing contract (declared by
4105        // createThemeContract elsewhere); it must NOT introduce a binding.
4106        let d = defs(
4107            r"
4108import { createTheme } from '@vanilla-extract/css';
4109export const themeClass = createTheme(vars, { color: { brand: 'red' } });
4110",
4111        );
4112        assert!(
4113            d.is_empty(),
4114            "2-arg createTheme must not define tokens, got {d:?}"
4115        );
4116    }
4117
4118    #[test]
4119    fn ve_create_global_theme_three_arg_contract_impl_is_not_a_definition_site() {
4120        let d = defs(
4121            r"
4122import { createGlobalTheme } from '@vanilla-extract/css';
4123createGlobalTheme(':root', vars, { color: { brand: 'red' } });
4124",
4125        );
4126        assert!(
4127            d.is_empty(),
4128            "3-arg createGlobalTheme must not define tokens, got {d:?}"
4129        );
4130    }
4131
4132    #[test]
4133    fn aliased_named_import_still_fires() {
4134        let d = defs(
4135            r"
4136import { createThemeContract as ct } from '@vanilla-extract/css';
4137export const vars = ct({ color: { brand: null } });
4138",
4139        );
4140        assert_eq!(paths(&d, "vars"), vec!["color.brand"]);
4141    }
4142
4143    #[test]
4144    fn local_helper_not_from_library_does_not_fire() {
4145        // A local `defineVars` shadowing the StyleX name must not be recognized.
4146        let d = defs(
4147            r"
4148function defineVars(o) { return o; }
4149export const vars = defineVars({ color: { primary: '#000' } });
4150",
4151        );
4152        assert!(d.is_empty(), "local defineVars must not fire, got {d:?}");
4153    }
4154
4155    #[test]
4156    fn unrelated_create_theme_import_does_not_fire() {
4157        let d = defs(
4158            r"
4159import { createTheme } from '@mui/material/styles';
4160export const theme = createTheme({ palette: { primary: { main: '#000' } } });
4161",
4162        );
4163        assert!(d.is_empty(), "non-VE createTheme must not fire, got {d:?}");
4164    }
4165
4166    #[test]
4167    fn type_only_import_does_not_fire() {
4168        let d = defs(
4169            r"
4170import type { defineVars } from '@stylexjs/stylex';
4171export const vars = defineVars({ color: { primary: '#000' } });
4172",
4173        );
4174        assert!(
4175            d.is_empty(),
4176            "type-only import must not gate recognition, got {d:?}"
4177        );
4178    }
4179
4180    #[test]
4181    fn token_def_lines_are_per_leaf() {
4182        let src = "import { unstable_defineVarsNested } from '@stylexjs/stylex';\nexport const vars = unstable_defineVarsNested({\n  color: {\n    primary: '#000',\n    secondary: '#fff',\n  },\n});\n";
4183        let d = defs(src);
4184        let def = d.iter().find(|d| d.binding == "vars").unwrap();
4185        let primary = def
4186            .tokens
4187            .iter()
4188            .find(|t| t.path == "color.primary")
4189            .unwrap();
4190        let secondary = def
4191            .tokens
4192            .iter()
4193            .find(|t| t.path == "color.secondary")
4194            .unwrap();
4195        assert_eq!(primary.def_line, 4);
4196        assert_eq!(secondary.def_line, 5);
4197    }
4198
4199    #[test]
4200    fn stylex_spread_or_dynamic_key_abstains_for_whole_definition() {
4201        let d = defs(
4202            r"
4203import { defineVars } from '@stylexjs/stylex';
4204const base = { a: '1' };
4205export const vars = defineVars({ ...base, ['x' + 'y']: '2', real: '#000' });
4206",
4207        );
4208        assert!(d.is_empty(), "partial StyleX shapes must abstain: {d:?}");
4209    }
4210
4211    #[test]
4212    fn stylex_nested_dynamic_condition_key_abstains_for_whole_definition() {
4213        let d = defs(
4214            r"
4215import * as stylex from '@stylexjs/stylex';
4216export const vars = stylex.unstable_defineVarsNested({
4217  surface: { default: '#fff', [getCondition()]: '#111' },
4218});
4219",
4220        );
4221        assert!(
4222            d.is_empty(),
4223            "dynamic StyleX conditions must abstain: {d:?}"
4224        );
4225    }
4226
4227    #[test]
4228    fn identifier_valued_key_is_not_a_leaf_but_call_and_member_values_are() {
4229        // `palette: tailwindPalette` (bare identifier, an imported group) must NOT
4230        // become a phantom `palette` leaf; `radius: px(2)` (call) and
4231        // `red: colors.red['500']` (member access) are real scalar leaves.
4232        let d = defs(
4233            r"
4234import { createGlobalTheme } from '@vanilla-extract/css';
4235export const vars = createGlobalTheme(':root', {
4236  palette: tailwindPalette,
4237  radius: px(2),
4238  red: colors.red['500'],
4239});
4240",
4241        );
4242        let p = paths(&d, "vars");
4243        assert!(
4244            !p.contains(&"palette".to_string()),
4245            "identifier-valued key must not be a leaf: {p:?}"
4246        );
4247        assert!(
4248            p.contains(&"radius".to_string()),
4249            "call-valued key is a leaf: {p:?}"
4250        );
4251        assert!(
4252            p.contains(&"red".to_string()),
4253            "member-valued key is a leaf: {p:?}"
4254        );
4255    }
4256
4257    #[test]
4258    fn no_css_in_js_import_returns_empty() {
4259        let d = defs("export const vars = { color: { primary: '#000' } };");
4260        assert!(d.is_empty());
4261    }
4262
4263    fn leaves(paths: &[&str]) -> FxHashSet<String> {
4264        paths.iter().map(|s| (*s).to_string()).collect()
4265    }
4266
4267    /// Run one query and drop the query index from each hit.
4268    fn scan_one(source: &str, path: &Path, query: ConsumerQuery<'_>) -> Vec<TokenConsumerHit> {
4269        css_in_js_consumer_scan(source, path, &[query])
4270            .into_iter()
4271            .map(|(_, hit)| hit)
4272            .collect()
4273    }
4274
4275    fn consumers(source: &str, alias: &str, paths: &[&str]) -> Vec<TokenConsumerHit> {
4276        let leaf_paths = leaves(paths);
4277        scan_one(
4278            source,
4279            Path::new("card.ts"),
4280            ConsumerQuery::MemberBinding {
4281                alias,
4282                leaf_paths: &leaf_paths,
4283            },
4284        )
4285    }
4286
4287    fn panda_consumers(source: &str, alias: &str, paths: &[&str]) -> Vec<TokenConsumerHit> {
4288        let leaf_paths = leaves(paths);
4289        scan_one(
4290            source,
4291            Path::new("card.ts"),
4292            ConsumerQuery::PandaTokenCall {
4293                alias,
4294                leaf_paths: &leaf_paths,
4295            },
4296        )
4297    }
4298
4299    fn panda_style_consumers(
4300        source: &str,
4301        aliases: &[&str],
4302        paths: &[&str],
4303    ) -> Vec<TokenConsumerHit> {
4304        let aliases = aliases.iter().map(|s| (*s).to_string()).collect();
4305        let leaf_paths = leaves(paths);
4306        scan_one(
4307            source,
4308            Path::new("card.ts"),
4309            ConsumerQuery::PandaStyleValues {
4310                aliases: &aliases,
4311                leaf_paths: &leaf_paths,
4312            },
4313        )
4314    }
4315
4316    #[test]
4317    fn consumer_matches_deepest_leaf_not_intermediate_group() {
4318        // `vars.color.primary` is the leaf; `vars.color` (an intermediate group)
4319        // must NOT be counted, so exactly one hit per access site.
4320        let hits = consumers(
4321            "const a = vars.color.primary;",
4322            "vars",
4323            &["color.primary", "color.secondary"],
4324        );
4325        assert_eq!(hits.len(), 1);
4326        assert_eq!(hits[0].token_path, "color.primary");
4327        assert_eq!(hits[0].line, 1);
4328    }
4329
4330    #[test]
4331    fn consumer_aliased_receiver() {
4332        // The caller passes the local alias; member access on it is matched.
4333        let hits = consumers("const a = v.color.primary;", "v", &["color.primary"]);
4334        assert_eq!(hits.len(), 1);
4335        assert_eq!(hits[0].token_path, "color.primary");
4336    }
4337
4338    #[test]
4339    fn consumer_multiple_sites_distinct_lines() {
4340        let src = "const a = vars.color.primary;\nconst b = vars.space.sm;\nconst c = vars.color.primary;";
4341        let hits = consumers(src, "vars", &["color.primary", "space.sm"]);
4342        assert_eq!(hits.len(), 3);
4343        let lines: Vec<u32> = hits.iter().map(|h| h.line).collect();
4344        assert_eq!(lines, vec![1, 2, 3]);
4345    }
4346
4347    #[test]
4348    fn consumer_in_style_object_value_position() {
4349        // The dominant real shape: a token read inside a style-call object value.
4350        let hits = consumers(
4351            "export const s = stylex.create({ root: { color: vars.color.primary } });",
4352            "vars",
4353            &["color.primary"],
4354        );
4355        assert_eq!(hits.len(), 1);
4356        assert_eq!(hits[0].token_path, "color.primary");
4357    }
4358
4359    #[test]
4360    fn panda_token_call_consumer_matches_string_literal() {
4361        let hits = panda_consumers(
4362            "export const c = css({ color: token('colors.brand') });",
4363            "token",
4364            &["colors.brand", "colors.accent"],
4365        );
4366        assert_eq!(hits.len(), 1);
4367        assert_eq!(hits[0].token_path, "colors.brand");
4368    }
4369
4370    #[test]
4371    fn panda_style_value_consumer_matches_known_token_string() {
4372        let hits = panda_style_consumers(
4373            "export const c = css({ color: 'colors.brand', _hover: { bg: 'colors.accent' } });",
4374            &["css"],
4375            &["colors.brand", "colors.accent", "colors.unused"],
4376        );
4377        let paths: Vec<_> = hits.iter().map(|hit| hit.token_path.as_str()).collect();
4378        assert_eq!(paths, vec!["colors.brand", "colors.accent"]);
4379    }
4380
4381    #[test]
4382    fn panda_style_value_consumer_ignores_unimported_alias() {
4383        let hits = panda_style_consumers(
4384            "export const c = notPanda({ color: 'colors.brand' });",
4385            &["css"],
4386            &["colors.brand"],
4387        );
4388        assert!(hits.is_empty());
4389    }
4390
4391    #[test]
4392    fn consumer_flat_stylex_depth_one() {
4393        let hits = consumers("const a = vars.primaryColor;", "vars", &["primaryColor"]);
4394        assert_eq!(hits.len(), 1);
4395        assert_eq!(hits[0].token_path, "primaryColor");
4396    }
4397
4398    #[test]
4399    fn stylex_theme_call_consumes_complete_group_once() {
4400        let leaf_paths = leaves(&["surface.bg", "surface.text"]);
4401        let queries = [ConsumerQuery::StyleXThemeGroup {
4402            contract_alias: "tokens",
4403            leaf_paths: &leaf_paths,
4404        }];
4405        let hits = css_in_js_consumer_scan(
4406            "import { createTheme as theme } from 'stylex';\nconst reset = theme(tokens, {});",
4407            Path::new("theme.ts"),
4408            &queries,
4409        );
4410        let mut paths: Vec<_> = hits
4411            .into_iter()
4412            .map(|(_, hit)| (hit.token_path, hit.line))
4413            .collect();
4414        paths.sort();
4415        assert_eq!(
4416            paths,
4417            vec![
4418                ("surface.bg".to_string(), 2),
4419                ("surface.text".to_string(), 2),
4420            ]
4421        );
4422    }
4423
4424    #[test]
4425    fn stylex_theme_call_abstains_for_type_only_or_unknown_contract() {
4426        let leaf_paths = leaves(&["surface.bg"]);
4427        let queries = [ConsumerQuery::StyleXThemeGroup {
4428            contract_alias: "tokens",
4429            leaf_paths: &leaf_paths,
4430        }];
4431        for source in [
4432            "import { type createTheme } from '@stylexjs/stylex'; createTheme(tokens, {});",
4433            "import * as stylex from '@stylexjs/stylex'; stylex.createTheme(other, {});",
4434            "const createTheme = (a, b) => b; createTheme(tokens, {});",
4435            "import { createTheme } from 'stylex'; const run = (createTheme) => createTheme(tokens, {});",
4436            "import * as stylex from 'stylex'; const run = (stylex) => stylex.createTheme(tokens, {});",
4437            "import { createTheme } from 'stylex'; const run = (tokens) => createTheme(tokens, {});",
4438        ] {
4439            assert!(
4440                css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries).is_empty(),
4441                "must abstain: {source}"
4442            );
4443        }
4444    }
4445
4446    #[test]
4447    fn stylex_theme_call_requires_bound_exact_static_shape() {
4448        let leaf_paths = leaves(&["surface.bg"]);
4449        let queries = [ConsumerQuery::StyleXThemeGroup {
4450            contract_alias: "tokens",
4451            leaf_paths: &leaf_paths,
4452        }];
4453        for source in [
4454            "import { createTheme } from 'stylex'; createTheme(tokens, {});",
4455            "import { createTheme } from 'stylex'; const theme = createTheme(tokens);",
4456            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, {}, {});",
4457            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, '#fff');",
4458            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, getOverrides());",
4459            "import { createTheme } from 'stylex'; const { theme } = createTheme(tokens, {});",
4460        ] {
4461            assert!(
4462                css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries).is_empty(),
4463                "must abstain: {source}"
4464            );
4465        }
4466    }
4467
4468    #[test]
4469    fn stylex_theme_call_accepts_unmutated_local_static_override_and_wrapped_contract() {
4470        let leaf_paths = leaves(&["surface.bg"]);
4471        let queries = [ConsumerQuery::StyleXThemeGroup {
4472            contract_alias: "tokens",
4473            leaf_paths: &leaf_paths,
4474        }];
4475        let source = r"
4476import { createTheme, unstable_conditional } from 'stylex';
4477const overrides = {
4478  surface: unstable_conditional({ default: '#fff', '@media (prefers-color-scheme: dark)': '#111' }),
4479} as const;
4480const theme = createTheme(tokens as typeof tokens, overrides);
4481";
4482        let hits = css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries);
4483        assert_eq!(hits.len(), 1);
4484        assert_eq!(hits[0].1.token_path, "surface.bg");
4485    }
4486
4487    #[test]
4488    fn stylex_theme_call_accepts_official_static_expression_shapes() {
4489        let leaf_paths = leaves(&["surface.bg"]);
4490        let queries = [ConsumerQuery::StyleXThemeGroup {
4491            contract_alias: "tokens",
4492            leaf_paths: &leaf_paths,
4493        }];
4494        let source = r"
4495import { createTheme, types } from 'stylex';
4496const DARK = '@media (prefers-color-scheme: dark)';
4497const name = 'light';
4498const RADIUS = 4;
4499const theme = createTheme(tokens, {
4500  [DARK]: `${name}green`,
4501  radius: RADIUS * 2,
4502  typed: types.length({ default: RADIUS * 2 }),
4503});
4504";
4505        let hits = css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries);
4506        assert_eq!(hits.len(), 1);
4507        assert_eq!(hits[0].1.token_path, "surface.bg");
4508    }
4509
4510    #[test]
4511    fn stylex_theme_call_accepts_generic_evaluator_shapes() {
4512        let leaf_paths = leaves(&["surface.bg"]);
4513        let queries = [ConsumerQuery::StyleXThemeGroup {
4514            contract_alias: "tokens",
4515            leaf_paths: &leaf_paths,
4516        }];
4517        let source = r"
4518import { createTheme } from 'stylex';
4519const palette = { green: 'green' };
4520const base = { color: 'red' };
4521const alias = base;
4522const FLAG = true;
4523const RADIUS = '4';
4524const choose = value => value ? 'red' : 'blue';
4525const pick = value => value.color;
4526consume(RADIUS);
4527const theme = createTheme(tokens, {
4528  ...alias,
4529  member: palette.green,
4530  conditional: FLAG ? 'red' : 'blue',
4531  logical: FLAG && 'red',
4532  sequence: (0, 'red'),
4533  raw: String.raw`red-${2}`,
4534  math: Math.max(1, 2),
4535  stringMethod: 'red'.toUpperCase(),
4536  entries: Object.fromEntries([['color', 'red']]),
4537  arrow: choose(FLAG),
4538  arrowMember: pick({ color: 'red' }),
4539  coercedRadius: +RADIUS,
4540  bigintEquality: (1n === 1n) ? 8 : 4,
4541});
4542";
4543        let hits = css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries);
4544        assert_eq!(hits.len(), 1);
4545        assert_eq!(hits[0].1.token_path, "surface.bg");
4546    }
4547
4548    #[test]
4549    fn stylex_theme_call_resolves_static_contract_aliases_and_scalar_receivers() {
4550        let leaf_paths = leaves(&["surface.bg"]);
4551        let queries = [ConsumerQuery::StyleXThemeGroup {
4552            contract_alias: "tokens",
4553            leaf_paths: &leaf_paths,
4554        }];
4555        let source = r"
4556import { createTheme } from 'stylex';
4557const alias = tokens;
4558const secondAlias = alias;
4559const color = 'red';
4560const digits = 2;
4561const theme = createTheme(secondAlias, {
4562  color: color.toUpperCase().toLowerCase(),
4563  radius: (1).toFixed(digits),
4564});
4565color.toUpperCase();
4566const second = createTheme(alias, { color });
4567";
4568        let hits = css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries);
4569        assert_eq!(hits.len(), 2);
4570    }
4571
4572    #[test]
4573    fn stylex_pure_call_arguments_remain_static_across_theme_calls() {
4574        let leaf_paths = leaves(&["surface.bg"]);
4575        let queries = [ConsumerQuery::StyleXThemeGroup {
4576            contract_alias: "tokens",
4577            leaf_paths: &leaf_paths,
4578        }];
4579        let source = r"
4580import { createTheme } from 'stylex';
4581const RADIUS = 4;
4582const first = createTheme(tokens, { radius: Math.max(RADIUS, 2) });
4583const second = createTheme(tokens, { radius: RADIUS * 2 });
4584";
4585        let hits = css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries);
4586        assert_eq!(hits.len(), 2);
4587    }
4588
4589    #[test]
4590    fn stylex_theme_call_rejects_transitive_tdz_cycles_and_dynamic_helpers() {
4591        let leaf_paths = leaves(&["surface.bg"]);
4592        let queries = [ConsumerQuery::StyleXThemeGroup {
4593            contract_alias: "tokens",
4594            leaf_paths: &leaf_paths,
4595        }];
4596        for source in [
4597            "import { createTheme } from 'stylex'; const palette = { green: later }; const later = 'red'; const theme = createTheme(tokens, { color: palette.green });",
4598            "import { createTheme } from 'stylex'; const palette = { green: palette.green }; const theme = createTheme(tokens, { color: palette.green });",
4599            "import { createTheme } from 'stylex'; const choose = value => getColor(value); const theme = createTheme(tokens, { color: choose(true) });",
4600            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: Object.assign({}, { color: 'red' }) });",
4601            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: Object.fromEntries() });",
4602            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: Object.fromEntries([['color', ['red']]]) });",
4603            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: Math.notAFunction(1) });",
4604            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'x'.repeat(-1) });",
4605            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: (1).toFixed(1000) });",
4606            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { radius: 1n + 1 });",
4607            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { radius: +1n });",
4608            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { radius: Math.max(1n, 2n) });",
4609            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'x' in 1 });",
4610            "import { createTheme } from 'stylex'; const base = { color: 'red' }; const alias = base; alias.color = getColor(); const theme = createTheme(tokens, base);",
4611            "import { createTheme } from 'stylex'; const big = () => 1n; const theme = createTheme(tokens, { radius: Math.max(big(), 2) });",
4612            "import { createTheme } from 'stylex'; const big = () => LATER; const LATER = 1n; const theme = createTheme(tokens, { radius: Math.max(big(), 2) });",
4613            "import { createTheme } from 'stylex'; const base = { color: 'red' }; const get = () => base; get().color = getColor(); const theme = createTheme(tokens, base);",
4614            "import { createTheme } from 'stylex'; const base = { color: 'red' }; const get = () => base; mutate(get()); const theme = createTheme(tokens, base);",
4615            "import { createTheme } from 'stylex'; const base = { nested: { color: 'red' } }; const member = value => value.nested; member(base).color = getColor(); const theme = createTheme(tokens, base);",
4616            "import { createTheme } from 'stylex'; const base = { nested: { color: 'red' } }; const member = value => value.nested; mutate(member(base)); const theme = createTheme(tokens, base);",
4617            "import { createTheme } from 'stylex'; const base = { color: 'red' }; const theme = createTheme(tokens, base); base.color = getColor();",
4618            "import { createTheme } from 'stylex'; const choose = value => { return value; }; const theme = createTheme(tokens, { color: choose('red') });",
4619            "import { createTheme } from 'stylex'; const choose = value => { return value; mutate(); }; const theme = createTheme(tokens, { color: choose('red') });",
4620            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'a'.localeCompare('b', 'not_a_locale') });",
4621            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'x'.padStart(1 / 0, 'a') });",
4622            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'abc'.charAt(1n) });",
4623            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'abc'.includes('a', 1n) });",
4624            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: String(getFlag() ? 'a' : 'b') });",
4625            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: String((mutate(), 'red')) });",
4626            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: String(+1n) });",
4627            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: 'x'.concat(1n + 1n) });",
4628            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: String('x' in 1) });",
4629            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { color: Math.max(+1n, 2) });",
4630            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { [1n + 1]: 'red' });",
4631            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { [1n / 0n]: 'red' });",
4632            "import { createTheme } from 'stylex'; const alias = tokens; alias = other; const theme = createTheme(alias, { color: 'red' });",
4633        ] {
4634            assert!(
4635                css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries).is_empty(),
4636                "must abstain: {source}"
4637            );
4638        }
4639    }
4640
4641    #[test]
4642    fn stylex_theme_call_rejects_noncompiler_static_shapes() {
4643        let leaf_paths = leaves(&["surface.bg"]);
4644        let queries = [ConsumerQuery::StyleXThemeGroup {
4645            contract_alias: "tokens",
4646            leaf_paths: &leaf_paths,
4647        }];
4648        for source in [
4649            "import { createTheme } from 'stylex'; const theme = createTheme(tokens, { value: [] });",
4650            "import stylex, { createTheme } from 'stylex'; const theme = createTheme(tokens, { value: stylex.types.notAType({}) });",
4651            "import stylex, { createTheme } from 'stylex'; const theme = createTheme(tokens, { value: stylex.keyframes() });",
4652            "import { createTheme } from 'stylex'; const overrides = {}; const run = (overrides) => { const theme = createTheme(tokens, overrides); };",
4653            "import { createTheme } from 'stylex'; const overrides = {}; ({ value: overrides.value } = next); const theme = createTheme(tokens, overrides);",
4654        ] {
4655            assert!(
4656                css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries).is_empty(),
4657                "must abstain: {source}"
4658            );
4659        }
4660    }
4661
4662    #[test]
4663    fn consumer_other_binding_not_matched() {
4664        // A same-named member access on a DIFFERENT binding must not be a hit.
4665        let hits = consumers("const a = other.color.primary;", "vars", &["color.primary"]);
4666        assert!(hits.is_empty());
4667    }
4668
4669    #[test]
4670    fn consumer_shadowed_token_alias_is_not_matched() {
4671        let hits = consumers(
4672            "const read = (vars) => vars.color.primary;",
4673            "vars",
4674            &["color.primary"],
4675        );
4676        assert!(hits.is_empty());
4677    }
4678
4679    #[test]
4680    fn consumer_lexical_shadowing_respects_loops_catch_and_tdz() {
4681        let source = r"
4682import { vars } from './tokens.stylex';
4683for (const vars of groups) { use(vars.color.primary); }
4684try { work(); } catch (vars) { use(vars.color.primary); }
4685{ use(vars.color.primary); const vars = fallback; }
4686use((vars as typeof vars).color.primary);
4687";
4688        let hits = consumers(source, "vars", &["color.primary"]);
4689        assert_eq!(hits.len(), 1);
4690        assert_eq!(hits[0].line, 6);
4691    }
4692
4693    #[test]
4694    fn stylex_theme_callee_loop_shadow_does_not_escape_loop_scope() {
4695        let leaf_paths = leaves(&["surface.bg"]);
4696        let queries = [ConsumerQuery::StyleXThemeGroup {
4697            contract_alias: "tokens",
4698            leaf_paths: &leaf_paths,
4699        }];
4700        let source = r"
4701import { createTheme } from 'stylex';
4702for (const createTheme of factories) {
4703  const bad = createTheme(tokens, {});
4704}
4705const good = createTheme(tokens, {});
4706";
4707        let hits = css_in_js_consumer_scan(source, Path::new("theme.ts"), &queries);
4708        assert_eq!(hits.len(), 1);
4709        assert_eq!(hits[0].1.line, 6);
4710    }
4711
4712    #[test]
4713    fn consumer_deeper_access_past_leaf_matches_leaf_subexpression_once() {
4714        // `vars.color.primary.toString()` reads the leaf `color.primary`; the outer
4715        // `.toString` chain is not a leaf, the inner `vars.color.primary` is.
4716        let hits = consumers(
4717            "const a = vars.color.primary.toString();",
4718            "vars",
4719            &["color.primary"],
4720        );
4721        assert_eq!(hits.len(), 1);
4722        assert_eq!(hits[0].token_path, "color.primary");
4723    }
4724
4725    #[test]
4726    fn consumer_undefined_path_not_matched() {
4727        let hits = consumers("const a = vars.color.tertiary;", "vars", &["color.primary"]);
4728        assert!(hits.is_empty());
4729    }
4730
4731    #[test]
4732    fn consumer_bracket_notation_hyphenated_key() {
4733        // Hyphenated / digit-leading token keys are not valid JS identifiers, so
4734        // they are consumed via bracket notation; the leaf path keeps the raw key.
4735        let hits = consumers(
4736            "const a = vars.color['gray-100'];\nconst b = vars.borderRadius['0x'];",
4737            "vars",
4738            &["color.gray-100", "borderRadius.0x"],
4739        );
4740        let paths: Vec<&str> = hits.iter().map(|h| h.token_path.as_str()).collect();
4741        assert!(paths.contains(&"color.gray-100"));
4742        assert!(paths.contains(&"borderRadius.0x"));
4743        assert_eq!(hits.len(), 2);
4744    }
4745
4746    #[test]
4747    fn consumer_mixed_dot_and_bracket_chain() {
4748        // `vars['color'].primary` and `vars.color['primary']` both reconstruct the
4749        // same `color.primary` leaf.
4750        let hits = consumers(
4751            "const a = vars['color'].primary;\nconst b = vars.color['primary'];",
4752            "vars",
4753            &["color.primary"],
4754        );
4755        assert_eq!(hits.len(), 2);
4756        assert!(hits.iter().all(|h| h.token_path == "color.primary"));
4757    }
4758
4759    #[test]
4760    fn consumer_numeric_computed_key_is_matched() {
4761        let hits = consumers("const a = vars.color.gray[50];", "vars", &["color.gray.50"]);
4762        assert_eq!(hits.len(), 1);
4763        assert_eq!(hits[0].token_path, "color.gray.50");
4764    }
4765
4766    #[test]
4767    fn consumer_non_literal_computed_key_not_matched() {
4768        // A dynamic computed key cannot be resolved statically (lower-bound miss).
4769        let hits = consumers(
4770            "const k = 'primary'; const a = vars.color[k];",
4771            "vars",
4772            &["color.primary"],
4773        );
4774        assert!(hits.is_empty());
4775    }
4776
4777    #[test]
4778    fn consumer_empty_inputs_short_circuit() {
4779        assert!(consumers("const a = vars.color.primary;", "", &["color.primary"]).is_empty());
4780        assert!(consumers("const a = vars.color.primary;", "vars", &[]).is_empty());
4781    }
4782
4783    #[test]
4784    fn consumer_scan_matches_individual_calls() {
4785        // One source exercising all four query kinds; the scan must return exactly
4786        // the union of the four individual functions' hits, each tagged with the
4787        // index of the query that produced it.
4788        let source = "const a = vars.color.primary;\nconst b = css({ color: token('colors.brand'), background: 'colors.accent' });\nconst c = theme.space.card;";
4789        let path = Path::new("card.tsx");
4790
4791        let member_leaves = leaves(&["color.primary"]);
4792        let panda_call_leaves = leaves(&["colors.brand"]);
4793        let panda_style_aliases = leaves(&["css"]);
4794        let panda_style_leaves = leaves(&["colors.accent"]);
4795        let theme_leaves = leaves(&["space.card"]);
4796
4797        let queries = [
4798            ConsumerQuery::MemberBinding {
4799                alias: "vars",
4800                leaf_paths: &member_leaves,
4801            },
4802            ConsumerQuery::PandaTokenCall {
4803                alias: "token",
4804                leaf_paths: &panda_call_leaves,
4805            },
4806            ConsumerQuery::PandaStyleValues {
4807                aliases: &panda_style_aliases,
4808                leaf_paths: &panda_style_leaves,
4809            },
4810            ConsumerQuery::ThemeReads {
4811                leaf_paths: &theme_leaves,
4812            },
4813        ];
4814        let scanned = css_in_js_consumer_scan(source, path, &queries);
4815
4816        let individual: Vec<(usize, TokenConsumerHit)> = scan_one(
4817            source,
4818            path,
4819            ConsumerQuery::MemberBinding {
4820                alias: "vars",
4821                leaf_paths: &member_leaves,
4822            },
4823        )
4824        .into_iter()
4825        .map(|hit| (0, hit))
4826        .chain(
4827            scan_one(
4828                source,
4829                path,
4830                ConsumerQuery::PandaTokenCall {
4831                    alias: "token",
4832                    leaf_paths: &panda_call_leaves,
4833                },
4834            )
4835            .into_iter()
4836            .map(|hit| (1, hit)),
4837        )
4838        .chain(
4839            scan_one(
4840                source,
4841                path,
4842                ConsumerQuery::PandaStyleValues {
4843                    aliases: &panda_style_aliases,
4844                    leaf_paths: &panda_style_leaves,
4845                },
4846            )
4847            .into_iter()
4848            .map(|hit| (2, hit)),
4849        )
4850        .chain(
4851            scan_one(
4852                source,
4853                path,
4854                ConsumerQuery::ThemeReads {
4855                    leaf_paths: &theme_leaves,
4856                },
4857            )
4858            .into_iter()
4859            .map(|hit| (3, hit)),
4860        )
4861        .collect();
4862
4863        assert_eq!(scanned, individual);
4864        assert_eq!(scanned.len(), 4);
4865        assert_eq!(
4866            scanned[0],
4867            (
4868                0,
4869                TokenConsumerHit {
4870                    token_path: "color.primary".to_string(),
4871                    line: 1,
4872                }
4873            )
4874        );
4875        assert_eq!(
4876            scanned[3],
4877            (
4878                3,
4879                TokenConsumerHit {
4880                    token_path: "space.card".to_string(),
4881                    line: 3,
4882                }
4883            )
4884        );
4885    }
4886
4887    #[test]
4888    fn consumer_scan_empty_query_is_isolated() {
4889        // An empty-alias query short-circuits to no hits WITHOUT suppressing the
4890        // valid query that follows it.
4891        let source = "const a = vars.color.primary;";
4892        let path = Path::new("card.ts");
4893        let empty_leaves = leaves(&["color.primary"]);
4894        let valid_leaves = leaves(&["color.primary"]);
4895        let queries = [
4896            ConsumerQuery::MemberBinding {
4897                alias: "",
4898                leaf_paths: &empty_leaves,
4899            },
4900            ConsumerQuery::MemberBinding {
4901                alias: "vars",
4902                leaf_paths: &valid_leaves,
4903            },
4904        ];
4905        let scanned = css_in_js_consumer_scan(source, path, &queries);
4906        assert_eq!(scanned.len(), 1);
4907        assert_eq!(scanned[0].0, 1);
4908        assert_eq!(scanned[0].1.token_path, "color.primary");
4909    }
4910
4911    #[test]
4912    fn consumer_scan_two_member_queries_same_source() {
4913        // Two definers imported under different aliases with an overlapping leaf
4914        // path; each read attributes to the alias (query index) it used.
4915        let source = "const a = brand.color.primary;\nconst b = accent.color.primary;";
4916        let path = Path::new("card.ts");
4917        let brand_leaves = leaves(&["color.primary"]);
4918        let accent_leaves = leaves(&["color.primary"]);
4919        let queries = [
4920            ConsumerQuery::MemberBinding {
4921                alias: "brand",
4922                leaf_paths: &brand_leaves,
4923            },
4924            ConsumerQuery::MemberBinding {
4925                alias: "accent",
4926                leaf_paths: &accent_leaves,
4927            },
4928        ];
4929        let scanned = css_in_js_consumer_scan(source, path, &queries);
4930        assert_eq!(scanned.len(), 2);
4931        assert!(scanned.contains(&(
4932            0,
4933            TokenConsumerHit {
4934                token_path: "color.primary".to_string(),
4935                line: 1,
4936            }
4937        )));
4938        assert!(scanned.contains(&(
4939            1,
4940            TokenConsumerHit {
4941                token_path: "color.primary".to_string(),
4942                line: 2,
4943            }
4944        )));
4945    }
4946}