oxc_css_parser/parser/at_rule/
media.rs1use super::Parser;
2use crate::{
3 Parse, Syntax, arena_box, arena_vec,
4 ast::*,
5 bump, eat,
6 error::{Error, ErrorKind, PResult},
7 expect, peek,
8 pos::{Span, Spanned},
9 tokenizer::{Token, TokenWithSpan},
10};
11
12impl<'a> Parse<'a> for MediaAnd<'a> {
13 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
14 let keyword = input.parse::<Ident>()?;
15 if keyword.name.eq_ignore_ascii_case("and") {
16 let media_in_parens = input.parse::<MediaInParens>()?;
17 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
18 Ok(MediaAnd { keyword, media_in_parens, span })
19 } else {
20 Err(Error { kind: ErrorKind::ExpectMediaAnd, span: keyword.span })
21 }
22 }
23}
24
25impl<'a> Parse<'a> for MediaConditionAfterMediaType<'a> {
26 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
27 let and: Ident = match bump!(input) {
28 TokenWithSpan { token: Token::Ident(ident), span }
29 if ident.name().eq_ignore_ascii_case("and") =>
30 {
31 input.ident(ident, span)
32 }
33 TokenWithSpan { span, .. } => {
34 return Err(Error { kind: ErrorKind::ExpectMediaAnd, span });
35 }
36 };
37
38 let condition = input.parse_media_condition(false)?;
39
40 let span = Span { start: and.span.start, end: condition.span.end };
41 Ok(MediaConditionAfterMediaType { and, condition, span })
42 }
43}
44
45impl<'a> Parse<'a> for MediaFeature<'a> {
46 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
47 match input.parse_media_feature_value()? {
48 ComponentValue::InterpolableIdent(ident) => match &peek!(input).token {
49 Token::Colon(..) => input.parse_media_feature_plain(ident).map(MediaFeature::Plain),
50 Token::LessThan(..)
51 | Token::LessThanEqual(..)
52 | Token::GreaterThan(..)
53 | Token::GreaterThanEqual(..)
54 | Token::Equal(..) => input.parse_media_feature_range_or_range_interval(
55 ComponentValue::InterpolableIdent(ident),
56 ),
57 _ => {
58 let span = ident.span().clone();
59 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
60 name: MediaFeatureName::Ident(ident),
61 span,
62 }))
63 }
64 },
65 ComponentValue::SassVariable(variable) => {
66 let span = variable.span.clone();
67 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
68 name: MediaFeatureName::SassVariable(variable),
69 span,
70 }))
71 }
72 ComponentValue::PostcssSimpleVar(variable) => {
73 let span = variable.span.clone();
74 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
75 name: MediaFeatureName::PostcssSimpleVar(variable),
76 span,
77 }))
78 }
79 value => input.parse_media_feature_range_or_range_interval(value),
80 }
81 }
82}
83
84impl<'a> Parse<'a> for MediaFeatureComparison {
85 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
86 match bump!(input) {
87 TokenWithSpan { token: Token::LessThan(..), span } => {
88 Ok(MediaFeatureComparison { kind: MediaFeatureComparisonKind::LessThan, span })
89 }
90 TokenWithSpan { token: Token::LessThanEqual(..), span } => Ok(MediaFeatureComparison {
91 kind: MediaFeatureComparisonKind::LessThanOrEqual,
92 span,
93 }),
94 TokenWithSpan { token: Token::GreaterThan(..), span } => {
95 Ok(MediaFeatureComparison { kind: MediaFeatureComparisonKind::GreaterThan, span })
96 }
97 TokenWithSpan { token: Token::GreaterThanEqual(..), span } => {
98 Ok(MediaFeatureComparison {
99 kind: MediaFeatureComparisonKind::GreaterThanOrEqual,
100 span,
101 })
102 }
103 TokenWithSpan { token: Token::Equal(..), span } => {
104 Ok(MediaFeatureComparison { kind: MediaFeatureComparisonKind::Equal, span })
105 }
106 TokenWithSpan { span, .. } => {
107 Err(Error { kind: ErrorKind::ExpectMediaFeatureComparison, span })
108 }
109 }
110 }
111}
112
113impl<'a> Parse<'a> for MediaInParens<'a> {
114 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
115 if matches!(input.syntax, Syntax::Scss | Syntax::Sass)
118 && matches!(&peek!(input).token, Token::HashLBrace(..))
119 && let InterpolableIdent::SassInterpolated(interpolation) =
120 input.parse_sass_interpolated_ident()?
121 {
122 let span = interpolation.span.clone();
123 return Ok(MediaInParens {
124 kind: MediaInParensKind::SassInterpolation(interpolation),
125 span,
126 });
127 }
128 let (_, Span { start, .. }) = expect!(input, LParen);
129 let kind = input.parse()?;
130 let (_, Span { end, .. }) = expect!(input, RParen);
131 Ok(MediaInParens { kind, span: Span { start, end } })
132 }
133}
134
135impl<'a> Parse<'a> for MediaInParensKind<'a> {
136 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
137 if let Ok(media_condition) = input.try_parse(|parser| {
138 let media_condition = parser.parse_media_condition(true)?;
139 if matches!(&peek!(parser).token, Token::RParen(..)) {
144 Ok(media_condition)
145 } else {
146 let span = peek!(parser).span.clone();
147 Err(Error { kind: ErrorKind::ExpectMediaFeatureName, span })
148 }
149 }) {
150 Ok(MediaInParensKind::MediaCondition(media_condition))
151 } else if let Ok(media_feature) = input.try_parse(|parser| {
152 let media_feature = parser.parse::<MediaFeature>()?;
153 if matches!(&peek!(parser).token, Token::RParen(..)) {
154 Ok(media_feature)
155 } else {
156 let span = peek!(parser).span.clone();
157 Err(Error { kind: ErrorKind::ExpectMediaFeatureName, span })
158 }
159 }) {
160 Ok(MediaInParensKind::MediaFeature(arena_box!(input, media_feature)))
161 } else {
162 let tokens = input.parse_tokens_in_parens()?;
164 Ok(MediaInParensKind::GeneralEnclosed(tokens))
165 }
166 }
167}
168
169impl<'a> Parse<'a> for MediaNot<'a> {
170 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
171 let keyword = input.parse::<Ident>()?;
172 if keyword.name.eq_ignore_ascii_case("not") {
173 let media_in_parens = input.parse::<MediaInParens>()?;
174 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
175 Ok(MediaNot { keyword, media_in_parens, span })
176 } else {
177 Err(Error { kind: ErrorKind::ExpectMediaNot, span: keyword.span })
178 }
179 }
180}
181
182impl<'a> Parse<'a> for MediaOr<'a> {
183 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
184 let keyword = input.parse::<Ident>()?;
185 if keyword.name.eq_ignore_ascii_case("or") {
186 let media_in_parens = input.parse::<MediaInParens>()?;
187 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
188 Ok(MediaOr { keyword, media_in_parens, span })
189 } else {
190 Err(Error { kind: ErrorKind::ExpectMediaOr, span: keyword.span })
191 }
192 }
193}
194
195impl<'a> Parse<'a> for MediaQuery<'a> {
196 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
197 if let Ok(condition_only) =
198 input.try_parse(|parser| parser.parse_media_condition(true))
199 {
200 Ok(MediaQuery::ConditionOnly(condition_only))
201 } else if input.syntax == Syntax::Less {
202 match peek!(input).token {
203 Token::AtKeyword(..) => {
204 input.parse_less_maybe_variable_or_with_lookups().map(|value| match value {
205 ComponentValue::LessVariable(variable) => {
206 MediaQuery::LessVariable(variable)
207 }
208 ComponentValue::LessNamespaceValue(namespace_value) => {
209 MediaQuery::LessNamespaceValue(namespace_value)
210 }
211 _ => unreachable!(),
212 })
213 }
214 Token::Dot(..) | Token::Hash(..) => {
215 let less_namespace_value = input.parse()?;
216 Ok(MediaQuery::LessNamespaceValue(arena_box!(input, less_namespace_value)))
217 }
218 _ => input.parse_media_query_with_type_or_function(),
219 }
220 } else {
221 input.parse_media_query_with_type_or_function()
222 }
223 }
224}
225
226impl<'a> Parse<'a> for MediaQueryList<'a> {
228 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
229 let first = input.parse::<MediaQuery>()?;
230 let mut span = first.span().clone();
231
232 let mut queries = arena_vec!(input; first);
233 let mut comma_spans = arena_vec!(input);
234 while let Some((_, comma_span)) = eat!(input, Comma) {
235 comma_spans.push(comma_span);
236 queries.push(input.parse()?);
237 }
238 debug_assert_eq!(comma_spans.len() + 1, queries.len());
239
240 span.end = unsafe {
242 let index = queries.len() - 1;
243 queries.get_unchecked(index).span().end
244 };
245 Ok(MediaQueryList { queries, comma_spans, span })
246 }
247}
248
249impl<'a> Parse<'a> for MediaQueryWithType<'a> {
250 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
251 let modifier = if let Token::Ident(ident) = &peek!(input).token {
252 let name = ident.name();
253 if name.eq_ignore_ascii_case("not") || name.eq_ignore_ascii_case("only") {
254 Some(input.parse::<Ident>()?)
255 } else {
256 None
257 }
258 } else {
259 None
260 };
261 let media_type = input.parse::<InterpolableIdent>()?;
262 if let InterpolableIdent::Literal(Ident { name, span, .. }) = &media_type
263 && (name.eq_ignore_ascii_case("only")
264 || name.eq_ignore_ascii_case("not")
265 || name.eq_ignore_ascii_case("and")
266 || name.eq_ignore_ascii_case("or")
267 || name.eq_ignore_ascii_case("layer"))
268 {
269 input.recoverable_errors.push(Error {
270 kind: ErrorKind::MediaTypeKeywordDisallowed(name.to_string()),
271 span: span.clone(),
272 });
273 }
274 let condition = match &peek!(input).token {
275 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
276 input.parse::<MediaConditionAfterMediaType>().map(Some)?
277 }
278 _ => None,
279 };
280
281 let mut span = media_type.span().clone();
282 if let Some(modifier) = &modifier {
283 span.start = modifier.span.start;
284 }
285 if let Some(condition) = &condition {
286 span.end = condition.span.end;
287 }
288 Ok(MediaQueryWithType { modifier, media_type, condition, span })
289 }
290}
291
292impl<'a> Parser<'a> {
293 fn parse_media_condition(&mut self, allow_or: bool) -> PResult<MediaCondition<'a>> {
294 match &peek!(self).token {
295 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
296 let media_not = self.parse::<MediaNot>()?;
297 let span = media_not.span.clone();
298 Ok(MediaCondition {
299 conditions: arena_vec!(self; MediaConditionKind::Not(media_not)),
300 span,
301 })
302 }
303 _ => {
304 let first = self.parse::<MediaInParens>()?;
305 let mut span = first.span.clone();
306 let mut conditions = arena_vec!(self; MediaConditionKind::MediaInParens(first));
307 if let Token::Ident(ident) = &peek!(self).token {
308 let name = ident.name();
309 if name.eq_ignore_ascii_case("and") {
310 loop {
311 conditions.push(MediaConditionKind::And(self.parse()?));
312 match &peek!(self).token {
313 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
314 }
315 _ => break,
316 }
317 }
318 } else if allow_or && name.eq_ignore_ascii_case("or") {
319 loop {
320 conditions.push(MediaConditionKind::Or(self.parse()?));
321 match &peek!(self).token {
322 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {}
323 _ => break,
324 }
325 }
326 }
327 }
328
329 if let Some(last) = conditions.last() {
330 span.end = last.span().end;
331 }
332 Ok(MediaCondition { conditions, span })
333 }
334 }
335 }
336
337 fn parse_media_feature_plain(
338 &mut self,
339 ident: InterpolableIdent<'a>,
340 ) -> PResult<MediaFeaturePlain<'a>> {
341 let (_, colon_span) = expect!(self, Colon);
342 let value = self.parse_media_feature_value()?;
343 let span = Span { start: ident.span().start, end: value.span().end };
344 Ok(MediaFeaturePlain { name: MediaFeatureName::Ident(ident), colon_span, value, span })
345 }
346
347 fn parse_media_feature_range_or_range_interval(
348 &mut self,
349 left: ComponentValue<'a>,
350 ) -> PResult<MediaFeature<'a>> {
351 let comparison = self.parse()?;
352 let name_or_right = self.parse_media_feature_value()?;
353 if let ComponentValue::InterpolableIdent(ident) = name_or_right {
354 match &peek!(self).token {
355 Token::LessThan(..)
356 | Token::LessThanEqual(..)
357 | Token::GreaterThan(..)
358 | Token::GreaterThanEqual(..)
359 | Token::Equal(..) => {
360 let right_comparison = self.parse()?;
361 let right = self.parse_media_feature_value()?;
362 let span = Span { start: left.span().start, end: right.span().end };
363 Ok(MediaFeature::RangeInterval(MediaFeatureRangeInterval {
364 left,
365 left_comparison: comparison,
366 name: MediaFeatureName::Ident(ident),
367 right_comparison,
368 right,
369 span,
370 }))
371 }
372 _ => {
373 let span = Span { start: left.span().start, end: ident.span().end };
374 Ok(MediaFeature::Range(MediaFeatureRange {
375 left,
376 comparison,
377 right: ComponentValue::InterpolableIdent(ident),
378 span,
379 }))
380 }
381 }
382 } else {
383 if !matches!(left, ComponentValue::InterpolableIdent(..))
384 && !matches!(name_or_right, ComponentValue::InterpolableIdent(..))
385 {
386 self.recoverable_errors.push(Error {
387 kind: ErrorKind::ExpectMediaFeatureName,
388 span: name_or_right.span().clone(),
389 });
390 }
391 let span = Span { start: left.span().start, end: name_or_right.span().end };
392 Ok(MediaFeature::Range(MediaFeatureRange {
393 left,
394 comparison,
395 right: name_or_right,
396 span,
397 }))
398 }
399 }
400
401 fn parse_media_feature_value(&mut self) -> PResult<ComponentValue<'a>> {
402 let value = match self.syntax {
403 Syntax::Css => self.parse_component_value_atom()?,
404 Syntax::Scss | Syntax::Sass => {
405 self.parse_sass_bin_expr(false)?
406 }
407 Syntax::Less => self.parse_less_operation(true)?,
408 };
409 match value {
410 ComponentValue::Number(number)
411 if number.value >= 0.0 && matches!(peek!(self).token, Token::Solidus(..)) =>
412 {
413 self.parse_ratio(number).map(ComponentValue::Ratio)
414 }
415 value => Ok(value),
416 }
417 }
418
419 fn parse_media_query_with_type_or_function(&mut self) -> PResult<MediaQuery<'a>> {
420 let media_query_with_type = self.parse::<MediaQueryWithType>()?;
421 match (media_query_with_type, peek!(self)) {
422 (
423 MediaQueryWithType {
424 modifier: None,
425 media_type: name,
426 condition: None,
427 span: mq_span,
428 },
429 TokenWithSpan { token: crate::token::Token::LParen(..), span: lparen_span },
430 ) if mq_span.end == lparen_span.start => {
431 bump!(self);
432 let args = self.parse_function_args()?;
433 let (_, Span { end, .. }) = expect!(self, RParen);
434 Ok(MediaQuery::Function(Function {
435 name: FunctionName::Ident(name),
436 args,
437 span: Span { start: mq_span.start, end },
438 }))
439 }
440 (media_query_with_type, _) => Ok(MediaQuery::WithType(media_query_with_type)),
441 }
442 }
443}