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