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 and: Ident = match input.cursor.bump()? {
29 TokenWithSpan { token: Token::Ident(ident), span }
30 if ident.name().eq_ignore_ascii_case("and") =>
31 {
32 input.ident(ident, span)
33 }
34 TokenWithSpan { span, .. } => {
35 return Err(Error { kind: ErrorKind::ExpectMediaAnd, span });
36 }
37 };
38
39 let condition = input.parse_media_condition(
40 false, true,
41 )?;
42
43 let span = Span { start: and.span.start, end: condition.span.end };
44 Ok(MediaConditionAfterMediaType { and, condition, span })
45 }
46}
47
48impl<'a> Parse<'a> for MediaFeature<'a> {
54 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
55 match input.parse_media_feature_value()? {
56 ComponentValue::InterpolableIdent(ident) => match &input.cursor.peek()?.token {
57 Token::Colon(..) => input.parse_media_feature_plain(ident).map(MediaFeature::Plain),
58 Token::LessThan(..)
59 | Token::LessThanEqual(..)
60 | Token::GreaterThan(..)
61 | Token::GreaterThanEqual(..)
62 | Token::Equal(..) => input.parse_media_feature_range_or_range_interval(
63 ComponentValue::InterpolableIdent(ident),
64 ),
65 _ => {
66 let span = ident.span().clone();
67 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
68 name: MediaFeatureName::Ident(ident),
69 span,
70 }))
71 }
72 },
73 ComponentValue::SassVariable(variable) => {
74 let span = variable.span.clone();
75 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
76 name: MediaFeatureName::SassVariable(variable),
77 span,
78 }))
79 }
80 ComponentValue::PostcssSimpleVar(variable) => {
81 let span = variable.span.clone();
82 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
83 name: MediaFeatureName::PostcssSimpleVar(variable),
84 span,
85 }))
86 }
87 value => input.parse_media_feature_range_or_range_interval(value),
88 }
89 }
90}
91
92impl<'a> Parse<'a> for MediaFeatureComparison {
94 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
95 match input.cursor.bump()? {
96 TokenWithSpan { token: Token::LessThan(..), span } => {
97 Ok(MediaFeatureComparison { kind: MediaFeatureComparisonKind::LessThan, span })
98 }
99 TokenWithSpan { token: Token::LessThanEqual(..), span } => Ok(MediaFeatureComparison {
100 kind: MediaFeatureComparisonKind::LessThanOrEqual,
101 span,
102 }),
103 TokenWithSpan { token: Token::GreaterThan(..), span } => {
104 Ok(MediaFeatureComparison { kind: MediaFeatureComparisonKind::GreaterThan, span })
105 }
106 TokenWithSpan { token: Token::GreaterThanEqual(..), span } => {
107 Ok(MediaFeatureComparison {
108 kind: MediaFeatureComparisonKind::GreaterThanOrEqual,
109 span,
110 })
111 }
112 TokenWithSpan { token: Token::Equal(..), span } => {
113 Ok(MediaFeatureComparison { kind: MediaFeatureComparisonKind::Equal, span })
114 }
115 TokenWithSpan { span, .. } => {
116 Err(Error { kind: ErrorKind::ExpectMediaFeatureComparison, span })
117 }
118 }
119 }
120}
121
122impl<'a> Parse<'a> for MediaInParens<'a> {
124 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
125 if matches!(input.syntax, Syntax::Scss | Syntax::Sass)
128 && matches!(&input.cursor.peek()?.token, Token::HashLBrace(..))
129 && let InterpolableIdent::SassInterpolated(interpolation) =
130 input.parse_sass_interpolated_ident()?
131 {
132 let span = interpolation.span.clone();
133 return Ok(MediaInParens {
134 kind: MediaInParensKind::SassInterpolation(interpolation),
135 span,
136 });
137 }
138 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
139 let kind = input.parse()?;
140 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
141 Ok(MediaInParens { kind, span: Span { start, end } })
142 }
143}
144
145impl<'a> Parse<'a> for MediaInParensKind<'a> {
147 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
148 if let Ok(media_condition) = input.try_parse(|parser| {
149 let media_condition = parser.parse_media_condition(
150 true, false,
151 )?;
152 if matches!(&parser.cursor.peek()?.token, Token::RParen(..)) {
157 Ok(media_condition)
158 } else {
159 let span = parser.cursor.peek()?.span.clone();
160 Err(Error { kind: ErrorKind::ExpectMediaFeatureName, span })
161 }
162 }) {
163 Ok(MediaInParensKind::MediaCondition(media_condition))
164 } else if let Ok(media_feature) = input.try_parse(|parser| {
165 let media_feature = parser.parse::<MediaFeature>()?;
166 if matches!(&parser.cursor.peek()?.token, Token::RParen(..)) {
167 Ok(media_feature)
168 } else {
169 let span = parser.cursor.peek()?.span.clone();
170 Err(Error { kind: ErrorKind::ExpectMediaFeatureName, span })
171 }
172 }) {
173 Ok(MediaInParensKind::MediaFeature(input.alloc(media_feature)))
174 } else {
175 let tokens = input.parse_tokens_in_parens()?;
177 Ok(MediaInParensKind::GeneralEnclosed(tokens))
178 }
179 }
180}
181
182impl<'a> Parse<'a> for MediaNot<'a> {
184 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
185 let keyword = input.parse::<Ident>()?;
186 if keyword.name.eq_ignore_ascii_case("not") {
187 let media_in_parens = input.parse::<MediaInParens>()?;
188 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
189 Ok(MediaNot { keyword, media_in_parens, span })
190 } else {
191 Err(Error { kind: ErrorKind::ExpectMediaNot, span: keyword.span })
192 }
193 }
194}
195
196impl<'a> Parse<'a> for MediaOr<'a> {
198 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
199 let keyword = input.parse::<Ident>()?;
200 if keyword.name.eq_ignore_ascii_case("or") {
201 let media_in_parens = input.parse_media_in_parens_after_logic()?;
202 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
203 Ok(MediaOr { keyword, media_in_parens, span })
204 } else {
205 Err(Error { kind: ErrorKind::ExpectMediaOr, span: keyword.span })
206 }
207 }
208}
209
210impl<'a> Parse<'a> for MediaQuery<'a> {
215 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
216 if let Ok(condition_only) = input.try_parse(|parser| {
217 parser.parse_media_condition(
218 true, false,
219 )
220 }) {
221 Ok(MediaQuery::ConditionOnly(condition_only))
222 } else if input.syntax == Syntax::Less {
223 match input.cursor.peek()?.token {
224 Token::AtKeyword(..) => {
225 input.parse_less_maybe_variable_or_with_lookups().map(|value| match value {
226 ComponentValue::LessVariable(variable) => {
227 MediaQuery::LessVariable(variable)
228 }
229 ComponentValue::LessNamespaceValue(namespace_value) => {
230 MediaQuery::LessNamespaceValue(namespace_value)
231 }
232 _ => unreachable!(),
233 })
234 }
235 Token::Dot(..) | Token::Hash(..) => {
236 let less_namespace_value = input.parse()?;
237 Ok(MediaQuery::LessNamespaceValue(input.alloc(less_namespace_value)))
238 }
239 _ => input.parse_media_query_with_type_or_function(),
240 }
241 } else {
242 input.parse_media_query_with_type_or_function()
243 }
244 }
245}
246
247impl<'a> Parse<'a> for MediaQueryList<'a> {
251 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
252 let first = input.parse::<MediaQuery>()?;
253 let mut span = first.span().clone();
254
255 let mut queries = input.vec1(first);
256 let mut comma_spans = input.vec();
257 while let Some((_, comma_span)) = input.cursor.eat_comma()? {
258 comma_spans.push(comma_span);
259 queries.push(input.parse()?);
260 }
261 debug_assert_eq!(comma_spans.len() + 1, queries.len());
262
263 span.end = unsafe {
265 let index = queries.len() - 1;
266 queries.get_unchecked(index).span().end
267 };
268 Ok(MediaQueryList { queries, comma_spans, span })
269 }
270}
271
272impl<'a> Parse<'a> for MediaQueryWithType<'a> {
275 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
276 let modifier = if let Token::Ident(ident) = &input.cursor.peek()?.token {
277 let name = ident.name();
278 if name.eq_ignore_ascii_case("not") || name.eq_ignore_ascii_case("only") {
279 Some(input.parse::<Ident>()?)
280 } else {
281 None
282 }
283 } else {
284 None
285 };
286 let media_type = input.parse::<InterpolableIdent>()?;
287 if let InterpolableIdent::Literal(Ident { name, span, .. }) = &media_type
288 && (name.eq_ignore_ascii_case("only")
289 || name.eq_ignore_ascii_case("not")
290 || name.eq_ignore_ascii_case("and")
291 || name.eq_ignore_ascii_case("or")
292 || name.eq_ignore_ascii_case("layer"))
293 {
294 input.recoverable_errors.push(Error {
295 kind: ErrorKind::MediaTypeKeywordDisallowed(name.to_string()),
296 span: span.clone(),
297 });
298 }
299 let condition = match &input.cursor.peek()?.token {
300 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
301 input.parse::<MediaConditionAfterMediaType>().map(Some)?
302 }
303 _ => None,
304 };
305
306 let mut span = media_type.span().clone();
307 if let Some(modifier) = &modifier {
308 span.start = modifier.span.start;
309 }
310 if let Some(condition) = &condition {
311 span.end = condition.span.end;
312 }
313 Ok(MediaQueryWithType { modifier, media_type, condition, span })
314 }
315}
316
317impl<'a> Parser<'a> {
318 fn parse_media_in_parens_after_logic(&mut self) -> PResult<MediaInParens<'a>> {
324 if self.syntax == Syntax::Css
325 && let TokenWithSpan { token: Token::Ident(ident), span } = self.cursor.peek()?
326 && !ident.name().eq_ignore_ascii_case("not")
327 && self.source.as_bytes().get(span.end) != Some(&b'(')
328 {
329 let token = self.cursor.bump()?;
330 let span = token.span.clone();
331 return Ok(MediaInParens {
332 kind: MediaInParensKind::GeneralEnclosed(TokenSeq {
333 tokens: self.vec1(token),
334 span: span.clone(),
335 }),
336 span,
337 });
338 }
339 self.parse()
340 }
341
342 fn parse_media_condition(
345 &mut self,
346 allow_or: bool,
347 after_logic_keyword: bool,
348 ) -> PResult<MediaCondition<'a>> {
349 match &self.cursor.peek()?.token {
350 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
351 let media_not = self.parse::<MediaNot>()?;
352 let span = media_not.span.clone();
353 Ok(MediaCondition {
354 conditions: self.vec1(MediaConditionKind::Not(media_not)),
355 span,
356 })
357 }
358 _ => {
359 let first = if after_logic_keyword {
360 self.parse_media_in_parens_after_logic()?
361 } else {
362 self.parse::<MediaInParens>()?
363 };
364 let mut span = first.span.clone();
365 let mut conditions = self.vec1(MediaConditionKind::MediaInParens(first));
366 if let Token::Ident(ident) = &self.cursor.peek()?.token {
367 let name = ident.name();
368 if name.eq_ignore_ascii_case("and") {
369 loop {
370 conditions.push(MediaConditionKind::And(self.parse()?));
371 match &self.cursor.peek()?.token {
372 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
373 }
374 _ => break,
375 }
376 }
377 } else if allow_or && name.eq_ignore_ascii_case("or") {
378 loop {
379 conditions.push(MediaConditionKind::Or(self.parse()?));
380 match &self.cursor.peek()?.token {
381 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {}
382 _ => break,
383 }
384 }
385 }
386 }
387
388 if let Some(last) = conditions.last() {
389 span.end = last.span().end;
390 }
391 Ok(MediaCondition { conditions, span })
392 }
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().clone(),
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}