use std::path::Path;
use oxc_allocator::Allocator;
use oxc_ast::ast::{
Argument, BindingPattern, Expression, IdentifierReference, ImportDeclarationSpecifier,
NumericLiteral, ObjectExpression, ObjectPropertyKind, Program, PropertyKey, Statement,
UnaryOperator, VariableDeclaration, VariableDeclarationKind,
};
use oxc_ast_visit::{Visit, walk};
use oxc_parser::Parser;
use oxc_semantic::{ReferenceId, Scoping, SemanticBuilder};
use oxc_span::{GetSpan, SourceType};
use rustc_hash::FxHashMap;
use super::shared::{WRAPPER, count_newlines};
const UNITLESS_PROPERTIES: &[&str] = &[
"animationIterationCount",
"aspectRatio",
"borderImageOutset",
"borderImageSlice",
"borderImageWidth",
"boxFlex",
"boxFlexGroup",
"boxOrdinalGroup",
"columnCount",
"columns",
"flex",
"flexGrow",
"flexPositive",
"flexShrink",
"flexNegative",
"flexOrder",
"gridArea",
"gridRow",
"gridRowEnd",
"gridRowSpan",
"gridRowStart",
"gridColumn",
"gridColumnEnd",
"gridColumnSpan",
"gridColumnStart",
"fontWeight",
"lineClamp",
"lineHeight",
"opacity",
"order",
"orphans",
"scale",
"tabSize",
"widows",
"zIndex",
"zoom",
"fillOpacity",
"floodOpacity",
"stopOpacity",
"strokeDasharray",
"strokeDashoffset",
"strokeMiterlimit",
"strokeOpacity",
"strokeWidth",
];
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum Lib {
VanillaExtract,
Emotion,
EmotionStyled,
StyleX,
Panda,
}
impl Lib {
const fn is_atomic(self) -> bool {
matches!(self, Self::StyleX | Self::Panda)
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct CssInJsObjectSheets {
pub structural: Option<String>,
pub structural_partial: Option<String>,
pub atomic: Option<String>,
}
impl CssInJsObjectSheets {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.structural.is_none() && self.structural_partial.is_none() && self.atomic.is_none()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Stream {
Structural,
StructuralPartial,
Atomic,
}
struct Bucket {
offset: u32,
rule: String,
stream: Stream,
}
#[must_use]
pub fn css_in_js_object_sheets(source: &str, path: &Path) -> CssInJsObjectSheets {
let source_type = SourceType::from_path(path).unwrap_or_default();
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source, source_type).parse();
if !has_recognized_value_import(&ret.program) {
return CssInJsObjectSheets::default();
}
let semantic_ret = SemanticBuilder::new().build(&ret.program);
let scoping = semantic_ret.semantic.scoping();
let mut collector = ObjectStyleCollector::new(source);
collector.build_import_map(&ret.program, scoping);
if collector.imports.is_empty() {
return CssInJsObjectSheets::default();
}
if collector
.imports
.values()
.any(|(library, _)| *library == Lib::StyleX)
{
collector.build_const_string_map(&ret.program, scoping);
}
collector.visit_program(&ret.program);
collector.finish()
}
fn has_recognized_value_import(program: &Program<'_>) -> bool {
program.body.iter().any(|statement| {
let Statement::ImportDeclaration(declaration) = statement else {
return false;
};
!declaration.import_kind.is_type()
&& module_library(declaration.source.value.as_str()).is_some()
&& declaration.specifiers.as_ref().is_some_and(|specifiers| {
specifiers.iter().any(|specifier| {
!matches!(
specifier,
ImportDeclarationSpecifier::ImportSpecifier(specifier)
if specifier.import_kind.is_type()
)
})
})
})
}
struct ObjectStyleCollector<'a> {
source: &'a str,
imports: FxHashMap<ReferenceId, (Lib, &'a str)>,
const_strings: FxHashMap<ReferenceId, (u32, &'a str)>,
buckets: Vec<Bucket>,
}
impl<'a> ObjectStyleCollector<'a> {
fn new(source: &'a str) -> Self {
Self {
source,
imports: FxHashMap::default(),
const_strings: FxHashMap::default(),
buckets: Vec::new(),
}
}
fn build_import_map(&mut self, program: &Program<'a>, scoping: &Scoping) {
for stmt in &program.body {
let Statement::ImportDeclaration(decl) = stmt else {
continue;
};
if decl.import_kind.is_type() {
continue;
}
let Some(lib) = module_library(decl.source.value.as_str()) else {
continue;
};
let Some(specifiers) = &decl.specifiers else {
continue;
};
for specifier in specifiers {
let (binding, role) = match specifier {
ImportDeclarationSpecifier::ImportSpecifier(s) if !s.import_kind.is_type() => {
(&s.local, s.imported.name().as_str())
}
ImportDeclarationSpecifier::ImportSpecifier(_) => continue,
ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
let role = if lib == Lib::Emotion {
"css"
} else {
s.local.name.as_str()
};
(&s.local, role)
}
ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
(&s.local, s.local.name.as_str())
}
};
let Some(symbol_id) = binding.symbol_id.get() else {
continue;
};
self.imports.extend(
scoping
.get_resolved_reference_ids(symbol_id)
.iter()
.copied()
.map(|reference_id| (reference_id, (lib, role))),
);
}
}
}
fn build_const_string_map(&mut self, program: &Program<'a>, scoping: &Scoping) {
let mut collector = ConstStringCollector {
scoping,
values: &mut self.const_strings,
};
collector.visit_program(program);
}
fn imported_binding(&self, id: &IdentifierReference<'_>) -> Option<(Lib, &'a str)> {
let reference_id = id.reference_id.get()?;
self.imports.get(&reference_id).copied()
}
fn finish(self) -> CssInJsObjectSheets {
let source = self.source;
let mut buckets = self.buckets;
buckets.sort_by_key(|b| b.offset);
CssInJsObjectSheets {
structural: render(source, &buckets, Stream::Structural),
structural_partial: render(source, &buckets, Stream::StructuralPartial),
atomic: render(source, &buckets, Stream::Atomic),
}
}
fn recognize(&self, callee: &Expression<'a>) -> Option<(Lib, CallKind)> {
match callee {
Expression::Identifier(id) => {
let (lib, role) = self.imported_binding(id)?;
let kind = match (lib, role) {
(Lib::VanillaExtract, "style") | (Lib::Emotion | Lib::Panda, "css") => {
CallKind::SingleObject
}
(Lib::VanillaExtract, "styleVariants") | (Lib::StyleX, "create") => {
CallKind::ObjectOfObjects
}
(Lib::VanillaExtract, "globalStyle") => CallKind::GlobalStyle,
(Lib::VanillaExtract, "recipe") | (Lib::Panda, "cva") => CallKind::RecipeBase,
_ => return None,
};
Some((lib, kind))
}
Expression::StaticMemberExpression(member) => {
let Expression::Identifier(obj) = &member.object else {
return None;
};
let (lib, _) = self.imported_binding(obj)?;
let kind = match (lib, member.property.name.as_str()) {
(Lib::EmotionStyled, _) => CallKind::SingleObject,
(Lib::StyleX, "create") => CallKind::ObjectOfObjects,
_ => return None,
};
Some((lib, kind))
}
Expression::CallExpression(inner) => {
let Expression::Identifier(id) = &inner.callee else {
return None;
};
matches!(self.imported_binding(id), Some((Lib::EmotionStyled, _)))
.then_some((Lib::EmotionStyled, CallKind::SingleObject))
}
_ => None,
}
}
fn collect_call(&mut self, callee: &Expression<'a>, args: &[Argument<'a>]) {
let Some((lib, kind)) = self.recognize(callee) else {
return;
};
match kind {
CallKind::SingleObject => {
if let Some(obj) = object_arg(args, 0) {
self.push_bucket(obj, WRAPPER, lib, obj.span().start);
}
}
CallKind::ObjectOfObjects => {
if args.len() != 1 {
return;
}
let Some(obj) = object_arg(args, 0) else {
return;
};
for prop in &obj.properties {
if let ObjectPropertyKind::ObjectProperty(p) = prop
&& let Some(inner) = object_expression(&p.value)
{
self.push_bucket(inner, WRAPPER, lib, p.key.span().start);
}
}
}
CallKind::RecipeBase => {
let Some(obj) = object_arg(args, 0) else {
return;
};
for prop in &obj.properties {
if let ObjectPropertyKind::ObjectProperty(p) = prop
&& static_key(&p.key).as_deref() == Some("base")
&& let Some(inner) = object_expression(&p.value)
{
self.push_bucket(inner, WRAPPER, lib, p.key.span().start);
}
}
}
CallKind::GlobalStyle => {
let (Some(selector), Some(obj)) = (string_arg(args, 0), object_arg(args, 1)) else {
return;
};
let selector = sanitize_selector(&selector);
if !selector.is_empty() {
self.push_bucket(obj, &selector, lib, obj.span().start);
}
}
}
}
fn push_bucket(&mut self, obj: &ObjectExpression<'a>, selector: &str, lib: Lib, offset: u32) {
let mut body = String::new();
let mut dropped = false;
let context = ObjectSerializationContext {
stylex: lib == Lib::StyleX,
const_strings: &self.const_strings,
before: offset,
};
serialize_object_body(obj, &mut body, &mut dropped, &context);
if body.is_empty() {
return;
}
let stream = if lib.is_atomic() {
Stream::Atomic
} else if dropped {
Stream::StructuralPartial
} else {
Stream::Structural
};
self.buckets.push(Bucket {
offset,
rule: format!("{selector}{{{body}}}"),
stream,
});
}
}
impl<'a> Visit<'a> for ObjectStyleCollector<'a> {
fn visit_call_expression(&mut self, call: &oxc_ast::ast::CallExpression<'a>) {
self.collect_call(&call.callee, &call.arguments);
walk::walk_call_expression(self, call);
}
}
struct ConstStringCollector<'a, 's, 'm> {
scoping: &'s Scoping,
values: &'m mut FxHashMap<ReferenceId, (u32, &'a str)>,
}
impl<'a> Visit<'a> for ConstStringCollector<'a, '_, '_> {
fn visit_variable_declaration(&mut self, declaration: &VariableDeclaration<'a>) {
if declaration.kind == VariableDeclarationKind::Const {
for declarator in &declaration.declarations {
let BindingPattern::BindingIdentifier(binding) = &declarator.id else {
continue;
};
let Some(Expression::StringLiteral(value)) = declarator
.init
.as_ref()
.map(Expression::get_inner_expression)
else {
continue;
};
let Some(symbol_id) = binding.symbol_id.get() else {
continue;
};
self.values.extend(
self.scoping
.get_resolved_reference_ids(symbol_id)
.iter()
.copied()
.map(|reference_id| {
(reference_id, (declarator.span.start, value.value.as_str()))
}),
);
}
}
walk::walk_variable_declaration(self, declaration);
}
}
fn render(source: &str, buckets: &[Bucket], stream: Stream) -> Option<String> {
let mut out = String::new();
let mut current_line: usize = 1;
let mut found = false;
for bucket in buckets.iter().filter(|b| b.stream == stream) {
let block_line = 1 + count_newlines(&source[..bucket.offset as usize]);
while current_line < block_line {
out.push('\n');
current_line += 1;
}
out.push_str(&bucket.rule);
current_line += count_newlines(&bucket.rule);
found = true;
}
found.then_some(out)
}
enum CallKind {
SingleObject,
ObjectOfObjects,
RecipeBase,
GlobalStyle,
}
pub(super) fn module_library(specifier: &str) -> Option<Lib> {
match specifier {
"@pandacss/dev" => Some(Lib::Panda),
"@vanilla-extract/css" | "@vanilla-extract/recipes" => Some(Lib::VanillaExtract),
"@emotion/react" | "@emotion/css" => Some(Lib::Emotion),
"@emotion/styled" => Some(Lib::EmotionStyled),
"@stylexjs/stylex" | "stylex" => Some(Lib::StyleX),
_ if specifier
.split(['/', '\\'])
.any(|segment| segment == "styled-system") =>
{
Some(Lib::Panda)
}
_ => None,
}
}
fn object_arg<'a: 'b, 'b>(
args: &'b [Argument<'a>],
index: usize,
) -> Option<&'b ObjectExpression<'a>> {
object_expression(args.get(index)?.as_expression()?)
}
fn object_expression<'a: 'b, 'b>(
expression: &'b Expression<'a>,
) -> Option<&'b ObjectExpression<'a>> {
match expression.get_inner_expression() {
Expression::ObjectExpression(object) => Some(object),
_ => None,
}
}
fn string_arg(args: &[Argument<'_>], index: usize) -> Option<String> {
match args.get(index)?.as_expression()?.get_inner_expression() {
Expression::StringLiteral(lit) => Some(lit.value.to_string()),
_ => None,
}
}
struct ObjectSerializationContext<'maps, 'ast> {
stylex: bool,
const_strings: &'maps FxHashMap<ReferenceId, (u32, &'ast str)>,
before: u32,
}
fn serialize_object_body(
obj: &ObjectExpression<'_>,
out: &mut String,
dropped: &mut bool,
context: &ObjectSerializationContext<'_, '_>,
) {
for prop in &obj.properties {
let ObjectPropertyKind::ObjectProperty(prop) = prop else {
*dropped = true;
continue;
};
let Some(key) = static_key(&prop.key) else {
*dropped = true;
continue;
};
let value = prop.value.get_inner_expression();
match value {
Expression::ObjectExpression(nested) if is_selector_key(&key) => {
serialize_nested(&key, nested, out, dropped, context);
}
Expression::ObjectExpression(nested) if context.stylex => {
let mut conditional_body = String::new();
if serialize_stylex_conditional_values(&key, nested, &mut conditional_body, context)
{
out.push_str(&conditional_body);
} else {
*dropped = true;
}
}
Expression::ObjectExpression(_) => {
*dropped = true;
}
value => {
if let Some(rendered) = serialize_value(&key, value) {
out.push_str(&rendered);
} else {
*dropped = true;
}
}
}
}
}
fn serialize_nested(
key: &str,
nested: &ObjectExpression<'_>,
out: &mut String,
dropped: &mut bool,
context: &ObjectSerializationContext<'_, '_>,
) {
if key == "selectors" {
for prop in &nested.properties {
match prop {
ObjectPropertyKind::ObjectProperty(p) => {
if let (Some(inner_key), Some(inner)) =
(static_key(&p.key), object_expression(&p.value))
{
serialize_nested(&inner_key, inner, out, dropped, context);
} else {
*dropped = true;
}
}
ObjectPropertyKind::SpreadProperty(_) => *dropped = true,
}
}
return;
}
let mut body = String::new();
serialize_object_body(nested, &mut body, dropped, context);
if body.is_empty() {
return;
}
out.push_str(&nested_selector(key));
out.push('{');
out.push_str(&body);
out.push('}');
}
fn serialize_stylex_conditional_values(
property: &str,
conditional: &ObjectExpression<'_>,
out: &mut String,
context: &ObjectSerializationContext<'_, '_>,
) -> bool {
for entry in &conditional.properties {
let ObjectPropertyKind::ObjectProperty(entry) = entry else {
return false;
};
if !is_static_stylex_condition(entry, context) {
return false;
}
match entry.value.get_inner_expression() {
Expression::ObjectExpression(nested) => {
if !serialize_stylex_conditional_values(property, nested, out, context) {
return false;
}
}
value => {
if let Some(rendered) = serialize_value(property, value) {
out.push_str(&rendered);
} else {
return false;
}
}
}
}
true
}
fn is_static_stylex_condition(
property: &oxc_ast::ast::ObjectProperty<'_>,
context: &ObjectSerializationContext<'_, '_>,
) -> bool {
if !property.computed {
return property
.key
.static_name()
.is_some_and(|key| is_stylex_condition(&key));
}
let Some(expression) = property.key.as_expression() else {
return false;
};
match expression.get_inner_expression() {
Expression::StringLiteral(value) => is_stylex_condition(value.value.as_str()),
Expression::Identifier(id) => id
.reference_id
.get()
.and_then(|reference_id| context.const_strings.get(&reference_id))
.is_some_and(|(declaration_start, condition)| {
*declaration_start < context.before && is_stylex_condition(condition)
}),
_ => false,
}
}
fn is_stylex_condition(value: &str) -> bool {
value == "default" || value.starts_with(':') || value.starts_with('@') || value.starts_with('[')
}
fn is_selector_key(key: &str) -> bool {
if key == "selectors" {
return true;
}
matches!(
key.trim_start().chars().next(),
Some(':' | '&' | '@' | '>' | '+' | '~' | '.' | '#' | '[' | '*')
) || key.starts_with(' ')
}
fn nested_selector(key: &str) -> String {
let trimmed = key.trim();
if trimmed.starts_with('@') || trimmed.starts_with('&') {
return trimmed.to_string();
}
format!("&{trimmed}")
}
fn serialize_value(key: &str, value: &Expression<'_>) -> Option<String> {
let rendered = static_value(key, value)?;
Some(format!("{}:{rendered};", kebab_case(key)))
}
fn static_value(key: &str, value: &Expression<'_>) -> Option<String> {
match value.get_inner_expression() {
Expression::StringLiteral(lit) => {
let text = lit.value.as_str().trim();
(!text.is_empty()).then(|| text.to_string())
}
Expression::NumericLiteral(num) => Some(render_number(key, num)),
Expression::UnaryExpression(unary) if unary.operator == UnaryOperator::UnaryNegation => {
if let Expression::NumericLiteral(num) = &unary.argument {
Some(format!("-{}", render_number(key, num)))
} else {
None
}
}
_ => None,
}
}
fn render_number(key: &str, num: &NumericLiteral<'_>) -> String {
let value = format_f64(num.value);
if is_unitless(key) || key.starts_with("--") || num.value == 0.0 {
value
} else {
format!("{value}px")
}
}
fn format_f64(value: f64) -> String {
if value.fract() == 0.0 {
format!("{value:.0}")
} else {
value.to_string()
}
}
fn is_unitless(key: &str) -> bool {
UNITLESS_PROPERTIES.contains(&key)
}
fn static_key(key: &PropertyKey<'_>) -> Option<String> {
key.static_name().map(|name| name.to_string())
}
fn kebab_case(name: &str) -> String {
if name.starts_with("--") || name.contains('-') {
return name.to_string();
}
let mut out = String::with_capacity(name.len() + 2);
if let Some(rest) = name.strip_prefix("ms")
&& rest.chars().next().is_some_and(|c| c.is_ascii_uppercase())
{
out.push('-');
}
for ch in name.chars() {
if ch.is_ascii_uppercase() {
out.push('-');
out.push(ch.to_ascii_lowercase());
} else {
out.push(ch);
}
}
out
}
fn sanitize_selector(selector: &str) -> String {
selector
.chars()
.filter(|&c| c != '{' && c != '}' && c != ';')
.collect::<String>()
.trim()
.to_string()
}
#[cfg(all(test, not(miri)))]
mod tests {
use super::*;
use crate::compute_css_analytics;
fn sheets(source: &str) -> CssInJsObjectSheets {
css_in_js_object_sheets(source, Path::new("styles.ts"))
}
#[test]
fn vanilla_extract_style_lifts_to_parseable_css() {
let src = "import { style } from '@vanilla-extract/css';\n\
export const box = style({\n\
backgroundColor: 'red',\n\
padding: 8,\n\
});\n";
let s = sheets(src);
let css = s.structural.expect("vanilla-extract style is structural");
assert!(css.contains("background-color:red;"), "css={css:?}");
assert!(css.contains("padding:8px;"), "px default: css={css:?}");
let a = compute_css_analytics(&css).expect("lifted CSS parses");
assert!(a.total_declarations >= 2, "declarations counted: {a:?}");
assert!(s.atomic.is_none(), "vanilla-extract is not atomic");
}
#[test]
fn unitless_properties_keep_bare_number() {
let src = "import { style } from '@vanilla-extract/css';\n\
const x = style({ lineHeight: 1.5, zIndex: 10, fontWeight: 700, padding: 4 });\n";
let css = sheets(src).structural.expect("structural");
assert!(css.contains("line-height:1.5;"), "css={css:?}");
assert!(css.contains("z-index:10;"), "css={css:?}");
assert!(css.contains("font-weight:700;"), "css={css:?}");
assert!(css.contains("padding:4px;"), "css={css:?}");
}
#[test]
fn one_level_nesting_via_relative_selector() {
let src = "import { style } from '@vanilla-extract/css';\n\
const x = style({ color: 'red', ':hover': { color: 'blue' } });\n";
let css = sheets(src).structural.expect("structural");
assert!(
css.contains("&:hover{color:blue;}"),
"nested rule: css={css:?}"
);
let a = compute_css_analytics(&css).expect("nested parses");
assert!(a.rule_count >= 2, "nested rule counted: {a:?}");
}
#[test]
fn vanilla_extract_selectors_wrapper_unwrapped() {
let src = "import { style } from '@vanilla-extract/css';\n\
const x = style({ color: 'red', selectors: { '&:hover': { color: 'blue' } } });\n";
let css = sheets(src).structural.expect("structural");
assert!(
css.contains("&:hover{color:blue;}"),
"selectors wrapper unwrapped: css={css:?}"
);
assert!(
!css.contains("selectors{"),
"no literal selectors rule: css={css:?}"
);
}
#[test]
fn global_style_keeps_real_selector() {
let src = "import { globalStyle } from '@vanilla-extract/css';\n\
globalStyle('html, body', { margin: 0 });\n";
let css = sheets(src).structural.expect("structural");
assert!(
css.contains("html, body{margin:0;}"),
"real selector: css={css:?}"
);
let a = compute_css_analytics(&css).expect("parses");
assert_eq!(a.rule_count, 1);
}
#[test]
fn stylex_create_is_atomic_one_bucket_per_key() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
export const styles = stylex.create({\n\
root: { color: 'red', padding: 16 },\n\
card: { color: 'blue' },\n\
});\n";
let s = sheets(src);
assert!(s.structural.is_none(), "stylex is atomic, not structural");
let css = s.atomic.expect("stylex.create is atomic");
assert!(css.contains("color:red;"), "css={css:?}");
assert!(css.contains("padding:16px;"), "css={css:?}");
assert!(css.contains("color:blue;"), "second bucket: css={css:?}");
let a = compute_css_analytics(&css).expect("parses");
assert!(a.rule_count >= 2, "two buckets: {a:?}");
}
#[test]
fn stylex_named_create_and_conditional_values_feed_atomic_vocabulary() {
let src = "import { create as makeStyles } from 'stylex';\n\
const DARK = '@media (prefers-color-scheme: dark)';\n\
export const styles = makeStyles({\n\
root: { color: { default: '#111', [DARK]: '#eee' } },\n\
});\n";
let sheets = sheets(src);
assert!(sheets.structural.is_none());
let css = sheets.atomic.expect("StyleX named create is atomic");
assert!(css.contains("color:#111;"), "default value: {css:?}");
assert!(css.contains("color:#eee;"), "conditional value: {css:?}");
let analytics = compute_css_analytics(&css).expect("conditional sheet parses");
assert!(analytics.colors.iter().any(|color| color == "#111"));
assert!(analytics.colors.iter().any(|color| color == "#eee"));
}
#[test]
fn stylex_dynamic_conditional_key_does_not_feed_atomic_vocabulary() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
export const styles = stylex.create({\n\
root: { color: { default: '#111', [getCondition()]: '#eee' } },\n\
});\n";
assert!(sheets(src).is_empty());
}
#[test]
fn stylex_static_computed_conditional_keys_feed_atomic_vocabulary() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
export const styles = stylex.create({\n\
root: { color: { ['default']: '#111', ['@media (prefers-color-scheme: dark)']: '#eee' } },\n\
});\n";
let css = sheets(src)
.atomic
.expect("static computed StyleX conditions are recovered");
assert!(css.contains("color:#111;"), "default value: {css:?}");
assert!(css.contains("color:#eee;"), "media value: {css:?}");
}
#[test]
fn stylex_computed_condition_declared_after_create_abstains() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
export const styles = stylex.create({\n\
root: { color: { default: '#111', [DARK]: '#eee' } },\n\
});\n\
const DARK = '@media (prefers-color-scheme: dark)';\n";
assert!(sheets(src).is_empty());
}
#[test]
fn stylex_pseudo_and_selector_conditions_feed_atomic_vocabulary() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
export const styles = stylex.create({ root: { color: {\n\
default: 'red', ':hover': 'blue', '[data-active]': 'green',\n\
'@media (width > 10px)': { ':active': 'black' },\n\
} } });\n";
let css = sheets(src)
.atomic
.expect("static StyleX pseudo conditions are recovered");
for value in ["red", "blue", "green", "black"] {
assert!(css.contains(&format!("color:{value};")), "value: {css:?}");
}
}
#[test]
fn stylex_shadowed_namespace_abstains_in_every_lexical_scope() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
const valid = stylex.create({ root: { color: 'red' } });\n\
function parameter(stylex) { stylex.create({ root: { color: 'blue' } }); }\n\
{ stylex.create({ root: { color: 'green' } }); const stylex = local; }\n\
for (const stylex of libraries) { stylex.create({ root: { color: 'pink' } }); }\n\
try {} catch (stylex) { stylex.create({ root: { color: 'orange' } }); }\n";
let css = sheets(src).atomic.expect("unshadowed StyleX call survives");
assert!(css.contains("color:red;"), "module import call: {css:?}");
for shadowed in ["blue", "green", "pink", "orange"] {
assert!(
!css.contains(shadowed),
"shadowed namespace must abstain for {shadowed}: {css:?}"
);
}
}
#[test]
fn stylex_shadowed_named_create_abstains_in_every_lexical_scope() {
let src = "import { create as makeStyles } from '@stylexjs/stylex';\n\
const valid = makeStyles({ root: { color: 'red' } });\n\
function parameter(makeStyles) { makeStyles({ root: { color: 'blue' } }); }\n\
{ makeStyles({ root: { color: 'green' } }); const makeStyles = local; }\n\
for (const makeStyles of factories) { makeStyles({ root: { color: 'pink' } }); }\n\
try {} catch (makeStyles) { makeStyles({ root: { color: 'orange' } }); }\n";
let css = sheets(src)
.atomic
.expect("unshadowed named create call survives");
assert!(css.contains("color:red;"), "module import call: {css:?}");
for shadowed in ["blue", "green", "pink", "orange"] {
assert!(
!css.contains(shadowed),
"shadowed create alias must abstain for {shadowed}: {css:?}"
);
}
}
#[test]
fn stylex_transparent_typescript_wrappers_preserve_static_objects() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
export const styles = stylex.create((({\n\
root: ({ color: ('red' as const), padding: (8 satisfies number) } satisfies Record<string, unknown>),\n\
} as const) satisfies Record<string, unknown>));\n";
let css = sheets(src)
.atomic
.expect("wrapped static StyleX object is recovered");
assert!(css.contains("color:red;"), "wrapped string: {css:?}");
assert!(css.contains("padding:8px;"), "wrapped number: {css:?}");
}
#[test]
fn stylex_shadowed_computed_condition_binding_abstains() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
const CONDITION = '@media (width > 10px)';\n\
function styles() {\n\
const CONDITION = getCondition();\n\
return stylex.create({ root: { color: { default: 'red', [CONDITION]: 'blue' } } });\n\
}\n";
assert!(sheets(src).is_empty());
}
#[test]
fn stylex_type_only_named_create_does_not_open_gate() {
let src = "import { type create } from '@stylexjs/stylex';\n\
const styles = create({ root: { color: 'red' } });\n";
assert!(sheets(src).is_empty());
}
#[test]
fn panda_css_from_styled_system_is_atomic() {
let src = "import { css } from '../styled-system/css';\n\
const c = css({ display: 'flex', gap: 8 });\n";
let s = sheets(src);
let css = s.atomic.expect("panda css is atomic");
assert!(css.contains("display:flex;"), "css={css:?}");
assert!(css.contains("gap:8px;"), "css={css:?}");
}
#[test]
fn emotion_css_and_styled_are_structural() {
let src = "import { css } from '@emotion/react';\n\
import styled from '@emotion/styled';\n\
const a = css({ color: 'red' });\n\
const B = styled.div({ fontWeight: 700 });\n";
let css = sheets(src).structural.expect("emotion is structural");
assert!(css.contains("color:red;"), "css={css:?}");
assert!(css.contains("font-weight:700;"), "styled.div: css={css:?}");
}
#[test]
fn styled_call_form_is_lifted() {
let src = "import styled from '@emotion/styled';\n\
const Primary = styled(Button)({ fontWeight: 700 });\n";
let css = sheets(src)
.structural
.expect("styled(Component)({}) lifted");
assert!(css.contains("font-weight:700;"), "css={css:?}");
}
#[test]
fn dynamic_value_is_dropped_to_structural_partial() {
let src = "import { style } from '@vanilla-extract/css';\n\
import { theme } from './theme';\n\
const x = style({ color: theme.primary, padding: 8, margin: 4, top: 1, left: 2 });\n";
let s = sheets(src);
assert!(s.structural.is_none(), "bucket had a drop: {s:?}");
let css = s.structural_partial.expect("partial");
assert!(
!css.contains("fallowinterp"),
"no placeholder, value dropped: {css:?}"
);
assert!(
!css.contains("primary"),
"dynamic member not serialized: {css:?}"
);
assert!(css.contains("padding:8px;"), "static survives: {css:?}");
let a = compute_css_analytics(&css).expect("must parse, not None");
assert_eq!(a.important_declarations, 0, "no invented !important: {a:?}");
}
#[test]
fn spread_and_computed_key_dropped() {
let src = "import { style } from '@vanilla-extract/css';\n\
const base = {};\n\
const k = 'color';\n\
const x = style({ ...base, [k]: 'red', padding: 8, margin: 4, top: 1 });\n";
let s = sheets(src);
let css = s.structural_partial.expect("partial");
assert!(css.contains("padding:8px;"), "static survives: {css:?}");
}
#[test]
fn cva_variants_map_is_not_serialized_as_css() {
let cva = "import { cva } from 'class-variance-authority';\n\
const button = cva('base', { variants: { size: { sm: 'text-sm' } } });\n";
assert!(
sheets(cva).is_empty(),
"unrelated cva must not fire: {:?}",
sheets(cva)
);
let panda = "import { cva } from '../styled-system/css';\n\
const button = cva({ base: { color: 'red', padding: 8, margin: 4, top: 1 }, variants: { size: { sm: { fontSize: 12 } } } });\n";
let s = sheets(panda);
let css = s.atomic.expect("panda cva base is atomic");
assert!(css.contains("color:red;"), "base serialized: {css:?}");
assert!(
!css.contains("size"),
"variants config not serialized: {css:?}"
);
let a = compute_css_analytics(&css).expect("parses cleanly");
assert!(
a.notable_rules.is_empty(),
"no garbled structural finding: {a:?}"
);
}
#[test]
fn panda_cva_and_class_variance_authority_cva_coexist() {
let src = "import { cva } from '../styled-system/css';\n\
import { cva as cn } from 'class-variance-authority';\n\
const a = cva({ base: { color: 'red' } });\n\
const b = cn('base', { variants: { size: { sm: 'text-sm' } } });\n";
let css = sheets(src).atomic.expect("panda cva base is atomic");
assert!(css.contains("color:red;"), "panda base lifted: {css:?}");
assert!(!css.contains("text-sm"), "cva-lib not serialized: {css:?}");
}
#[test]
fn local_helper_with_recognized_name_does_not_fire() {
let src = "const css = (o) => o;\n\
const x = css({ color: 'red', padding: 8 });\n";
assert!(
sheets(src).is_empty(),
"local css helper must not fire: {:?}",
sheets(src)
);
}
#[test]
fn type_only_import_does_not_open_the_gate() {
let src = "import type { style } from '@vanilla-extract/css';\n\
const x = style({ color: 'red' });\n";
assert!(
sheets(src).is_empty(),
"type-only import must not open provenance: {:?}",
sheets(src)
);
}
#[test]
fn all_dynamic_bucket_emits_no_empty_rule() {
let src = "import { style } from '@vanilla-extract/css';\n\
import { v } from './v';\n\
const x = style({ color: v.a, background: v.b });\n";
let s = sheets(src);
assert!(s.is_empty(), "all-dynamic bucket dropped entirely: {s:?}");
}
#[test]
fn aliased_named_import_still_recognized() {
let src = "import { style as s, globalStyle as gs } from '@vanilla-extract/css';\n\
export const a = s({ color: 'red' });\n\
gs('html', { margin: 0 });\n";
let s = sheets(src);
let css = s.structural.expect("aliased style/globalStyle recognized");
assert!(css.contains("color:red;"), "aliased style fired: {css:?}");
assert!(
css.contains("html{margin:0;}"),
"aliased globalStyle fired: {css:?}"
);
}
#[test]
fn emotion_css_default_import_recognized() {
let src = "import css from '@emotion/css';\n\
const a = css({ color: 'red' });\n";
let css = sheets(src)
.structural
.expect("default css import recognized");
assert!(css.contains("color:red;"), "css={css:?}");
}
#[test]
fn emotion_css_default_import_aliased_recognized() {
let src = "import emo from '@emotion/css';\n\
const a = emo({ color: 'red' });\n";
let css = sheets(src)
.structural
.expect("aliased default css import recognized");
assert!(css.contains("color:red;"), "css={css:?}");
}
#[test]
fn non_decimal_numeric_literals_become_valid_css() {
let src = "import { style } from '@vanilla-extract/css';\n\
const x = style({ padding: 0xFF, zIndex: 1e3 });\n";
let css = sheets(src).structural.expect("structural");
assert!(
css.contains("padding:255px;"),
"hex -> decimal px: css={css:?}"
);
assert!(
css.contains("z-index:1000;"),
"scientific -> decimal: css={css:?}"
);
assert!(compute_css_analytics(&css).is_some(), "valid CSS");
}
#[test]
fn custom_property_numeric_value_keeps_no_unit() {
let src = "import { css } from '@emotion/react';\n\
const g = css({ ':root': { '--space': 8, '--ratio': 1.5 }, padding: 8 });\n";
let sheet = sheets(src)
.structural
.or_else(|| sheets(src).structural_partial)
.expect("structural output");
assert!(
sheet.contains("--space:8;"),
"custom prop keeps no unit: {sheet:?}"
);
assert!(
sheet.contains("--ratio:1.5;"),
"custom prop float unchanged: {sheet:?}"
);
assert!(
sheet.contains("padding:8px;"),
"normal prop still px: {sheet:?}"
);
}
#[test]
fn ms_vendor_prefix_kebabs_with_leading_dash() {
assert_eq!(kebab_case("msFlexAlign"), "-ms-flex-align");
assert_eq!(kebab_case("WebkitBoxShadow"), "-webkit-box-shadow");
assert_eq!(kebab_case("backgroundColor"), "background-color");
assert_eq!(kebab_case("msgType"), "msg-type");
}
#[test]
fn negative_numbers_handled() {
let src = "import { style } from '@vanilla-extract/css';\n\
const x = style({ marginTop: -8, zIndex: -1 });\n";
let css = sheets(src).structural.expect("structural");
assert!(css.contains("margin-top:-8px;"), "css={css:?}");
assert!(
css.contains("z-index:-1;"),
"unitless negative: css={css:?}"
);
}
#[test]
fn none_without_any_object_css_in_js() {
assert!(sheets("const x = 1; function f() {}").is_empty());
assert!(sheets("import React from 'react'; const x = <div/>;").is_empty());
}
#[test]
fn line_numbers_map_back_to_source() {
let src = "import { style } from '@vanilla-extract/css';\n\
\n\
const a = style({\n\
color: 'red',\n\
});\n";
let css = sheets(src).structural.expect("structural");
let pos = css.find("color").expect("color present");
let css_line = 1 + css[..pos].bytes().filter(|&b| b == b'\n').count();
assert_eq!(
css_line, 3,
"bucket maps to the style() object line: css={css:?}"
);
}
#[test]
fn multibyte_content_value_preserved() {
let src = "import { style } from '@vanilla-extract/css';\n\
const x = style({ content: '\"café 日本 €\"', fontFamily: '\"Ñoño\"' });\n";
let css = sheets(src).structural.expect("structural");
assert!(
css.contains("café 日本 €"),
"multibyte preserved: css={css:?}"
);
assert!(
compute_css_analytics(&css).is_some(),
"valid UTF-8 / parses"
);
}
#[test]
fn distinct_colors_fall_out_of_object_styles() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
const s = stylex.create({ a: { color: 'red' }, b: { color: 'blue' }, c: { color: 'red' } });\n";
let css = sheets(src).atomic.expect("atomic");
let a = compute_css_analytics(&css).expect("parses");
assert_eq!(a.colors.len(), 2, "distinct colors counted: {:?}", a.colors);
}
#[test]
fn multi_bucket_padding_uses_key_line() {
let src = "import * as stylex from '@stylexjs/stylex';\n\
const s = stylex.create({\n\
root: { color: 'red' },\n\
card: { color: 'blue' },\n\
});\n";
let css = sheets(src).atomic.expect("atomic");
let red = css.find("color:red").expect("root present");
let blue = css.find("color:blue").expect("card present");
let red_line = 1 + css[..red].bytes().filter(|&b| b == b'\n').count();
let blue_line = 1 + css[..blue].bytes().filter(|&b| b == b'\n').count();
assert_eq!(red_line, 3, "root on its key line: css={css:?}");
assert_eq!(blue_line, 4, "card on its own key line: css={css:?}");
}
}