use std::{
marker::PhantomData,
ops::{Index, IndexMut},
};
use lightningcss::{
media_query::MediaFeatureValue,
properties::{
custom::{Token, TokenList, TokenOrValue},
Property,
},
rules::{CssRule, CssRuleList},
stylesheet::ParserOptions,
traits::ParseWithOptions,
values::{
ident::Ident,
length::{Length, LengthValue},
string::CowArcStr,
},
visitor::{Visit, VisitTypes, Visitor},
};
use lightningcss::{stylesheet::StyleSheet, traits::IntoOwned};
use napi::{Env, JsFunction, JsObject, JsUnknown, Ref, ValueType};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::{at_rule_parser::AtRule, utils::get_named_property};
pub struct JsVisitor {
env: Env,
visit_stylesheet: VisitorsRef,
visit_rule: VisitorsRef,
rule_map: VisitorsRef,
property_map: VisitorsRef,
visit_declaration: VisitorsRef,
visit_length: Option<Ref<()>>,
visit_angle: Option<Ref<()>>,
visit_ratio: Option<Ref<()>>,
visit_resolution: Option<Ref<()>>,
visit_time: Option<Ref<()>>,
visit_color: Option<Ref<()>>,
visit_image: VisitorsRef,
visit_url: Option<Ref<()>>,
visit_media_query: VisitorsRef,
visit_supports_condition: VisitorsRef,
visit_custom_ident: Option<Ref<()>>,
visit_dashed_ident: Option<Ref<()>>,
visit_selector: Option<Ref<()>>,
visit_token: VisitorsRef,
token_map: VisitorsRef,
visit_function: VisitorsRef,
function_map: VisitorsRef,
visit_variable: VisitorsRef,
visit_env: VisitorsRef,
env_map: VisitorsRef,
types: VisitTypes,
}
unsafe impl Send for JsVisitor {}
#[derive(PartialEq, Eq, Clone, Copy)]
enum VisitStage {
Enter,
Exit,
}
type VisitorsRef = Visitors<Ref<()>>;
struct Visitors<T> {
enter: Option<T>,
exit: Option<T>,
}
impl<T> Visitors<T> {
fn new(enter: Option<T>, exit: Option<T>) -> Self {
Self { enter, exit }
}
fn for_stage(&self, stage: VisitStage) -> Option<&T> {
match stage {
VisitStage::Enter => self.enter.as_ref(),
VisitStage::Exit => self.exit.as_ref(),
}
}
}
impl Visitors<Ref<()>> {
fn get<U: napi::NapiValue>(&self, env: &Env) -> Visitors<U> {
Visitors {
enter: self.enter.as_ref().and_then(|p| env.get_reference_value_unchecked(p).ok()),
exit: self.exit.as_ref().and_then(|p| env.get_reference_value_unchecked(p).ok()),
}
}
}
impl Visitors<JsObject> {
fn named(&self, stage: VisitStage, name: &str) -> Option<JsFunction> {
self
.for_stage(stage)
.and_then(|m| get_named_property::<JsFunction>(m, name).ok())
}
fn custom(&self, stage: VisitStage, obj: &str, name: &str) -> Option<JsFunction> {
self
.for_stage(stage)
.and_then(|m| m.get_named_property::<JsUnknown>(obj).ok())
.and_then(|v| {
match v.get_type() {
Ok(ValueType::Function) => return v.try_into().ok(),
Ok(ValueType::Object) => {
let o: napi::Result<JsObject> = v.try_into();
if let Ok(o) = o {
return get_named_property::<JsFunction>(&o, name).ok();
}
}
_ => {}
}
None
})
}
}
impl Drop for JsVisitor {
fn drop(&mut self) {
macro_rules! drop {
($id: ident) => {
if let Some(v) = &mut self.$id {
drop(v.unref(self.env));
}
};
}
macro_rules! drop_tuple {
($id: ident) => {
if let Some(v) = &mut self.$id.enter {
drop(v.unref(self.env));
}
if let Some(v) = &mut self.$id.exit {
drop(v.unref(self.env));
}
};
}
drop_tuple!(visit_stylesheet);
drop_tuple!(visit_rule);
drop_tuple!(rule_map);
drop_tuple!(visit_declaration);
drop_tuple!(property_map);
drop!(visit_length);
drop!(visit_angle);
drop!(visit_ratio);
drop!(visit_resolution);
drop!(visit_time);
drop!(visit_color);
drop_tuple!(visit_image);
drop!(visit_url);
drop_tuple!(visit_media_query);
drop_tuple!(visit_supports_condition);
drop_tuple!(visit_variable);
drop_tuple!(visit_env);
drop_tuple!(env_map);
drop!(visit_custom_ident);
drop!(visit_dashed_ident);
drop_tuple!(visit_function);
drop_tuple!(function_map);
drop!(visit_selector);
drop_tuple!(visit_token);
drop_tuple!(token_map);
}
}
impl JsVisitor {
pub fn new(env: Env, visitor: JsObject) -> Self {
let mut types = VisitTypes::empty();
macro_rules! get {
($name: literal, $( $t: ident )|+) => {{
let res: Option<JsFunction> = get_named_property(&visitor, $name).ok();
if res.is_some() {
types |= $( VisitTypes::$t )|+;
}
res.and_then(|res| env.create_reference(res).ok())
}};
}
macro_rules! map {
($name: literal, $( $t: ident )|+) => {{
let obj: Option<JsObject> = get_named_property(&visitor, $name).ok();
if obj.is_some() {
types |= $( VisitTypes::$t )|+;
}
obj.and_then(|obj| env.create_reference(obj).ok())
}};
}
Self {
env,
visit_stylesheet: VisitorsRef::new(get!("StyleSheet", RULES), get!("StyleSheetExit", RULES)),
visit_rule: VisitorsRef::new(get!("Rule", RULES), get!("RuleExit", RULES)),
rule_map: VisitorsRef::new(map!("Rule", RULES), get!("RuleExit", RULES)),
visit_declaration: VisitorsRef::new(get!("Declaration", PROPERTIES), get!("DeclarationExit", PROPERTIES)),
property_map: VisitorsRef::new(map!("Declaration", PROPERTIES), map!("DeclarationExit", PROPERTIES)),
visit_length: get!("Length", LENGTHS),
visit_angle: get!("Angle", ANGLES),
visit_ratio: get!("Ratio", RATIOS),
visit_resolution: get!("Resolution", RESOLUTIONS),
visit_time: get!("Time", TIMES),
visit_color: get!("Color", COLORS),
visit_image: VisitorsRef::new(get!("Image", IMAGES), get!("ImageExit", IMAGES)),
visit_url: get!("Url", URLS),
visit_media_query: VisitorsRef::new(
get!("MediaQuery", MEDIA_QUERIES),
get!("MediaQueryExit", MEDIA_QUERIES),
),
visit_supports_condition: VisitorsRef::new(
get!("SupportsCondition", SUPPORTS_CONDITIONS),
get!("SupportsConditionExit", SUPPORTS_CONDITIONS),
),
visit_variable: VisitorsRef::new(get!("Variable", TOKENS), get!("VariableExit", TOKENS)),
visit_env: VisitorsRef::new(
get!("EnvironmentVariable", TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES),
get!(
"EnvironmentVariableExit",
TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES
),
),
env_map: VisitorsRef::new(
map!("EnvironmentVariable", TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES),
map!(
"EnvironmentVariableExit",
TOKENS | MEDIA_QUERIES | ENVIRONMENT_VARIABLES
),
),
visit_custom_ident: get!("CustomIdent", CUSTOM_IDENTS),
visit_dashed_ident: get!("DashedIdent", DASHED_IDENTS),
visit_function: VisitorsRef::new(get!("Function", TOKENS), get!("FunctionExit", TOKENS)),
function_map: VisitorsRef::new(map!("Function", TOKENS), map!("FunctionExit", TOKENS)),
visit_selector: get!("Selector", SELECTORS),
visit_token: VisitorsRef::new(get!("Token", TOKENS), None),
token_map: VisitorsRef::new(map!("Token", TOKENS), None),
types,
}
}
}
impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {
type Error = napi::Error;
fn visit_types(&self) -> VisitTypes {
self.types
}
fn visit_stylesheet<'o>(&mut self, stylesheet: &mut StyleSheet<'i, 'o, AtRule<'i>>) -> Result<(), Self::Error> {
if self.types.contains(VisitTypes::RULES) {
let env = self.env;
let visit_stylesheet = self.visit_stylesheet.get::<JsFunction>(&env);
if let Some(visit) = visit_stylesheet.for_stage(VisitStage::Enter) {
call_visitor(&env, stylesheet, visit)?
}
stylesheet.visit_children(self)?;
if let Some(visit) = visit_stylesheet.for_stage(VisitStage::Exit) {
call_visitor(&env, stylesheet, visit)?
}
Ok(())
} else {
stylesheet.visit_children(self)
}
}
fn visit_rule_list(
&mut self,
rules: &mut lightningcss::rules::CssRuleList<'i, AtRule<'i>>,
) -> Result<(), Self::Error> {
if self.types.contains(VisitTypes::RULES) {
let env = self.env;
let rule_map = self.rule_map.get::<JsObject>(&env);
let visit_rule = self.visit_rule.get::<JsFunction>(&env);
visit_list(
rules,
|value, stage| {
let name = match value {
CssRule::Media(..) => "media",
CssRule::Import(..) => "import",
CssRule::Style(..) => "style",
CssRule::Keyframes(..) => "keyframes",
CssRule::FontFace(..) => "font-face",
CssRule::FontPaletteValues(..) => "font-palette-values",
CssRule::FontFeatureValues(..) => "font-feature-values",
CssRule::Page(..) => "page",
CssRule::Supports(..) => "supports",
CssRule::CounterStyle(..) => "counter-style",
CssRule::Namespace(..) => "namespace",
CssRule::CustomMedia(..) => "custom-media",
CssRule::LayerBlock(..) => "layer-block",
CssRule::LayerStatement(..) => "layer-statement",
CssRule::Property(..) => "property",
CssRule::Container(..) => "container",
CssRule::Scope(..) => "scope",
CssRule::MozDocument(..) => "moz-document",
CssRule::Nesting(..) => "nesting",
CssRule::Viewport(..) => "viewport",
CssRule::StartingStyle(..) => "starting-style",
CssRule::ViewTransition(..) => "view-transition",
CssRule::Unknown(v) => {
let name = v.name.as_ref();
if let Some(visit) = rule_map.custom(stage, "unknown", name) {
let js_value = env.to_js_value(v)?;
let res = visit.call(None, &[js_value])?;
return env.from_js_value(res).map(serde_detach::detach);
} else {
"unknown"
}
}
CssRule::Custom(c) => {
let name = c.name.as_ref();
if let Some(visit) = rule_map.custom(stage, "custom", name) {
let js_value = env.to_js_value(c)?;
let res = visit.call(None, &[js_value])?;
return env.from_js_value(res).map(serde_detach::detach);
} else {
"custom"
}
}
CssRule::Ignored => return Ok(None),
};
if let Some(visit) = rule_map.named(stage, name).as_ref().or(visit_rule.for_stage(stage)) {
let js_value = env.to_js_value(value)?;
let res = visit.call(None, &[js_value])?;
env.from_js_value(res).map(serde_detach::detach)
} else {
Ok(None)
}
},
|rule| rule.visit_children(self),
)?;
Ok(())
} else {
rules.visit_children(self)
}
}
fn visit_declaration_block(
&mut self,
decls: &mut lightningcss::declaration::DeclarationBlock<'i>,
) -> Result<(), Self::Error> {
if self.types.contains(VisitTypes::PROPERTIES) {
let env = self.env;
let property_map = self.property_map.get::<JsObject>(&env);
let visit_declaration = self.visit_declaration.get::<JsFunction>(&env);
visit_declaration_list(
&env,
&mut decls.important_declarations,
&visit_declaration,
&property_map,
|property| property.visit_children(self),
)?;
visit_declaration_list(
&env,
&mut decls.declarations,
&visit_declaration,
&property_map,
|property| property.visit_children(self),
)?;
Ok(())
} else {
decls.visit_children(self)
}
}
fn visit_length(&mut self, length: &mut LengthValue) -> Result<(), Self::Error> {
visit(&self.env, length, &self.visit_length)
}
fn visit_angle(&mut self, angle: &mut lightningcss::values::angle::Angle) -> Result<(), Self::Error> {
visit(&self.env, angle, &self.visit_angle)
}
fn visit_ratio(&mut self, ratio: &mut lightningcss::values::ratio::Ratio) -> Result<(), Self::Error> {
visit(&self.env, ratio, &self.visit_ratio)
}
fn visit_resolution(
&mut self,
resolution: &mut lightningcss::values::resolution::Resolution,
) -> Result<(), Self::Error> {
visit(&self.env, resolution, &self.visit_resolution)
}
fn visit_time(&mut self, time: &mut lightningcss::values::time::Time) -> Result<(), Self::Error> {
visit(&self.env, time, &self.visit_time)
}
fn visit_color(&mut self, color: &mut lightningcss::values::color::CssColor) -> Result<(), Self::Error> {
visit(&self.env, color, &self.visit_color)
}
fn visit_image(&mut self, image: &mut lightningcss::values::image::Image<'i>) -> Result<(), Self::Error> {
visit(&self.env, image, &self.visit_image.enter)?;
image.visit_children(self)?;
visit(&self.env, image, &self.visit_image.exit)
}
fn visit_url(&mut self, url: &mut lightningcss::values::url::Url<'i>) -> Result<(), Self::Error> {
visit(&self.env, url, &self.visit_url)
}
fn visit_media_list(&mut self, media: &mut lightningcss::media_query::MediaList<'i>) -> Result<(), Self::Error> {
if self.types.contains(VisitTypes::MEDIA_QUERIES) {
let env = self.env;
let visit_media_query = self.visit_media_query.get::<JsFunction>(&env);
visit_list(
&mut media.media_queries,
|value, stage| {
if let Some(visit) = visit_media_query.for_stage(stage) {
let js_value = env.to_js_value(value)?;
let res = visit.call(None, &[js_value])?;
env.from_js_value(res).map(serde_detach::detach)
} else {
Ok(None)
}
},
|q| q.visit_children(self),
)?;
Ok(())
} else {
media.visit_children(self)
}
}
fn visit_media_feature_value(&mut self, value: &mut MediaFeatureValue<'i>) -> Result<(), Self::Error> {
if self.types.contains(VisitTypes::ENVIRONMENT_VARIABLES) && matches!(value, MediaFeatureValue::Env(_)) {
let env_map = self.env_map.get::<JsObject>(&self.env);
let visit_env = self.visit_env.get::<JsFunction>(&self.env);
let call = |stage: VisitStage, value: &mut MediaFeatureValue, env: &Env| -> napi::Result<()> {
let env_var = if let MediaFeatureValue::Env(env) = value {
env
} else {
return Ok(());
};
let visit_type = env_map.named(stage, env_var.name.name());
let visit = visit_env.for_stage(stage);
let new_value: Option<TokenOrValue> = if let Some(visit) = visit_type.as_ref().or(visit) {
let js_value = env.to_js_value(env_var)?;
let res = visit.call(None, &[js_value])?;
env.from_js_value(res).map(serde_detach::detach)?
} else {
None
};
match new_value {
None => return Ok(()),
Some(TokenOrValue::Length(l)) => *value = MediaFeatureValue::Length(Length::Value(l)),
Some(TokenOrValue::Resolution(r)) => *value = MediaFeatureValue::Resolution(r),
Some(TokenOrValue::Token(Token::Number { value: n, .. })) => *value = MediaFeatureValue::Number(n),
Some(TokenOrValue::Token(Token::Ident(ident))) => *value = MediaFeatureValue::Ident(Ident(ident)),
_ => {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("invalid environment value in media query: {:?}", new_value),
))
}
}
Ok(())
};
call(VisitStage::Enter, value, &self.env)?;
value.visit_children(self)?;
call(VisitStage::Exit, value, &self.env)?;
return Ok(());
}
value.visit_children(self)
}
fn visit_supports_condition(
&mut self,
condition: &mut lightningcss::rules::supports::SupportsCondition<'i>,
) -> Result<(), Self::Error> {
visit(&self.env, condition, &self.visit_supports_condition.enter)?;
condition.visit_children(self)?;
visit(&self.env, condition, &self.visit_supports_condition.exit)
}
fn visit_custom_ident(
&mut self,
ident: &mut lightningcss::values::ident::CustomIdent,
) -> Result<(), Self::Error> {
visit(&self.env, ident, &self.visit_custom_ident)
}
fn visit_dashed_ident(
&mut self,
ident: &mut lightningcss::values::ident::DashedIdent,
) -> Result<(), Self::Error> {
visit(&self.env, ident, &self.visit_dashed_ident)
}
fn visit_selector_list(
&mut self,
selectors: &mut lightningcss::selector::SelectorList<'i>,
) -> Result<(), Self::Error> {
if let Some(visit) = self
.visit_selector
.as_ref()
.and_then(|v| self.env.get_reference_value_unchecked::<JsFunction>(v).ok())
{
map::<_, _, _, true>(&mut selectors.0, |value| {
let js_value = self.env.to_js_value(value)?;
let res = visit.call(None, &[js_value])?;
self.env.from_js_value(res).map(serde_detach::detach)
})?;
}
Ok(())
}
fn visit_token_list(
&mut self,
tokens: &mut lightningcss::properties::custom::TokenList<'i>,
) -> Result<(), Self::Error> {
if self.types.contains(VisitTypes::TOKENS) {
let env = self.env;
let visit_token = self.visit_token.get::<JsFunction>(&env);
let token_map = self.token_map.get::<JsObject>(&env);
let visit_function = self.visit_function.get::<JsFunction>(&env);
let function_map = self.function_map.get::<JsObject>(&env);
let visit_variable = self.visit_variable.get::<JsFunction>(&env);
let visit_env = self.visit_env.get::<JsFunction>(&env);
let env_map = self.env_map.get::<JsObject>(&env);
visit_list(
&mut tokens.0,
|value, stage| {
let (visit_type, visit) = match value {
TokenOrValue::Function(f) => (
function_map.named(stage, f.name.0.as_ref()),
visit_function.for_stage(stage),
),
TokenOrValue::Var(_) => (None, visit_variable.for_stage(stage)),
TokenOrValue::Env(e) => (env_map.named(stage, e.name.name()), visit_env.for_stage(stage)),
TokenOrValue::Token(t) => {
let name = match t {
Token::Ident(_) => Some("ident"),
Token::AtKeyword(_) => Some("at-keyword"),
Token::Hash(_) => Some("hash"),
Token::IDHash(_) => Some("id-hash"),
Token::String(_) => Some("string"),
Token::Number { .. } => Some("number"),
Token::Percentage { .. } => Some("percentage"),
Token::Dimension { .. } => Some("dimension"),
_ => None,
};
let visit = if let Some(name) = name {
token_map.named(stage, name)
} else {
None
};
(visit, visit_token.for_stage(stage))
}
_ => return Ok(None),
};
if let Some(visit) = visit_type.as_ref().or(visit) {
let js_value = match value {
TokenOrValue::Function(f) => env.to_js_value(f)?,
TokenOrValue::Var(v) => env.to_js_value(v)?,
TokenOrValue::Env(v) => env.to_js_value(v)?,
TokenOrValue::Token(t) => env.to_js_value(t)?,
_ => unreachable!(),
};
let res = visit.call(None, &[js_value])?;
let res: Option<TokensOrRaw> = env.from_js_value(res).map(serde_detach::detach)?;
Ok(res.map(|r| r.0))
} else {
Ok(None)
}
},
|value| value.visit_children(self),
)?;
Ok(())
} else {
tokens.visit_children(self)
}
}
}
fn visit<V: Serialize + Deserialize<'static>>(
env: &Env,
value: &mut V,
visit: &Option<Ref<()>>,
) -> napi::Result<()> {
if let Some(visit) = visit
.as_ref()
.and_then(|v| env.get_reference_value_unchecked::<JsFunction>(v).ok())
{
call_visitor(env, value, &visit)?;
}
Ok(())
}
fn call_visitor<V: Serialize + Deserialize<'static>>(
env: &Env,
value: &mut V,
visit: &JsFunction,
) -> napi::Result<()> {
let js_value = env.to_js_value(value)?;
let res = visit.call(None, &[js_value])?;
let new_value: Option<V> = env.from_js_value(res).map(serde_detach::detach)?;
match new_value {
Some(new_value) => *value = new_value,
None => {}
}
Ok(())
}
fn visit_declaration_list<'i, C: FnMut(&mut Property<'i>) -> napi::Result<()>>(
env: &Env,
list: &mut Vec<Property<'i>>,
visit_declaration: &Visitors<JsFunction>,
property_map: &Visitors<JsObject>,
visit_children: C,
) -> napi::Result<()> {
visit_list(
list,
|value, stage| {
let visit = match value {
Property::Custom(v) => {
if let Some(visit) = property_map.custom(stage, "custom", v.name.as_ref()) {
let js_value = env.to_js_value(v)?;
let res = visit.call(None, &[js_value])?;
return env.from_js_value(res).map(serde_detach::detach);
} else {
None
}
}
_ => property_map.named(stage, value.property_id().name()),
};
if let Some(visit) = visit.as_ref().or(visit_declaration.for_stage(stage)) {
let js_value = env.to_js_value(value)?;
let res = visit.call(None, &[js_value])?;
env.from_js_value(res).map(serde_detach::detach)
} else {
Ok(None)
}
},
visit_children,
)
}
fn visit_list<
V,
L: List<V>,
F: Fn(&mut V, VisitStage) -> napi::Result<Option<ValueOrVec<V>>>,
C: FnMut(&mut V) -> napi::Result<()>,
>(
list: &mut L,
visit: F,
mut visit_children: C,
) -> napi::Result<()> {
map(list, |value| {
let mut new_value: Option<ValueOrVec<V>> = visit(value, VisitStage::Enter)?;
match &mut new_value {
Some(ValueOrVec::Value(v)) => {
visit_children(v)?;
if let Some(val) = visit(v, VisitStage::Exit)? {
new_value = Some(val);
}
}
Some(ValueOrVec::Vec(v)) => {
map(v, |value| {
visit_children(value)?;
visit(value, VisitStage::Exit)
})?;
}
None => {
visit_children(value)?;
if let Some(val) = visit(value, VisitStage::Exit)? {
new_value = Some(val);
}
}
}
Ok(new_value)
})
}
fn map<V, L: List<V>, F: FnMut(&mut V) -> napi::Result<Option<ValueOrVec<V, IS_VEC>>>, const IS_VEC: bool>(
list: &mut L,
mut f: F,
) -> napi::Result<()> {
let mut i = 0;
while i < list.len() {
let value = &mut list[i];
let new_value = f(value)?;
match new_value {
Some(ValueOrVec::Value(v)) => {
list[i] = v;
i += 1;
}
Some(ValueOrVec::Vec(vec)) => {
if vec.is_empty() {
list.remove(i);
} else {
let len = vec.len();
list.replace(i, vec);
i += len;
}
}
None => {
i += 1;
}
}
}
Ok(())
}
#[derive(serde::Serialize)]
#[serde(untagged)]
enum ValueOrVec<V, const IS_VEC: bool = false> {
Value(V),
Vec(Vec<V>),
}
impl<'de, V: serde::Deserialize<'de>, const IS_VEC: bool> serde::Deserialize<'de> for ValueOrVec<V, IS_VEC> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserializer;
let content = serde::__private::de::Content::deserialize(deserializer)?;
let de: serde::__private::de::ContentRefDeserializer<D::Error> =
serde::__private::de::ContentRefDeserializer::new(&content);
let mut was_seq = false;
let res = de.deserialize_seq(SeqVisitor {
was_seq: &mut was_seq,
phantom: PhantomData,
});
if was_seq {
if res.is_ok() || !IS_VEC {
return res.map(ValueOrVec::Vec);
}
}
let de = serde::__private::de::ContentRefDeserializer::new(&content);
return V::deserialize(de).map(ValueOrVec::Value);
struct SeqVisitor<'a, V> {
was_seq: &'a mut bool,
phantom: PhantomData<V>,
}
impl<'a, 'de, V: serde::Deserialize<'de>> serde::de::Visitor<'de> for SeqVisitor<'a, V> {
type Value = Vec<V>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
*self.was_seq = true;
let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(1));
while let Some(v) = seq.next_element()? {
vec.push(v);
}
Ok(vec)
}
}
}
}
struct TokensOrRaw<'i>(ValueOrVec<TokenOrValue<'i>>);
impl<'i, 'de: 'i> serde::Deserialize<'de> for TokensOrRaw<'i> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::__private::de::ContentRefDeserializer;
#[derive(serde::Deserialize)]
struct Raw<'i> {
#[serde(borrow)]
raw: CowArcStr<'i>,
}
let content = serde::__private::de::Content::deserialize(deserializer)?;
let de: ContentRefDeserializer<D::Error> = ContentRefDeserializer::new(&content);
if let Ok(res) = Raw::deserialize(de) {
let res = TokenList::parse_string_with_options(res.raw.as_ref(), ParserOptions::default())
.map_err(|_| serde::de::Error::custom("Could not parse value"))?;
return Ok(TokensOrRaw(ValueOrVec::Vec(res.into_owned().0)));
}
let de = ContentRefDeserializer::new(&content);
Ok(TokensOrRaw(ValueOrVec::deserialize(de)?))
}
}
trait List<V>: Index<usize, Output = V> + IndexMut<usize, Output = V> {
fn len(&self) -> usize;
fn remove(&mut self, i: usize);
fn replace(&mut self, i: usize, items: Vec<V>);
}
impl<V> List<V> for Vec<V> {
fn len(&self) -> usize {
Vec::len(self)
}
fn remove(&mut self, i: usize) {
Vec::remove(self, i);
}
fn replace(&mut self, i: usize, items: Vec<V>) {
self.splice(i..i + 1, items);
}
}
impl<V, T: smallvec::Array<Item = V>> List<V> for SmallVec<T> {
fn len(&self) -> usize {
SmallVec::len(self)
}
fn remove(&mut self, i: usize) {
SmallVec::remove(self, i);
}
fn replace(&mut self, i: usize, items: Vec<V>) {
let len = items.len();
let mut iter = items.into_iter();
self[i] = iter.next().unwrap();
if len > 1 {
self.insert_many(i + 1, iter);
}
}
}
impl<'i, R> List<CssRule<'i, R>> for CssRuleList<'i, R> {
fn len(&self) -> usize {
self.0.len()
}
fn remove(&mut self, i: usize) {
self[i] = CssRule::Ignored;
}
fn replace(&mut self, i: usize, items: Vec<CssRule<'i, R>>) {
self.0.replace(i, items)
}
}