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