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
56 .parse_media_feature_plain(ident)
57 .map(|f| MediaFeature::Plain(input.alloc(f))),
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();
67 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
68 name: MediaFeatureName::Ident(ident),
69 span,
70 }))
71 }
72 },
73 ComponentValue::SassVariable(variable) => {
74 let span = variable.span;
75 Ok(MediaFeature::Boolean(MediaFeatureBoolean {
76 name: MediaFeatureName::SassVariable(variable),
77 span,
78 }))
79 }
80 ComponentValue::PostcssSimpleVar(variable) => {
81 let span = variable.span;
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;
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;
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;
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();
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 = {
277 let peek = input.cursor.peek()?;
278 if peek.is_ident_name_eq_ignore_ascii_case(input.source, "not")
279 || peek.is_ident_name_eq_ignore_ascii_case(input.source, "only")
280 {
281 Some(input.parse::<Ident>()?)
282 } else {
283 None
284 }
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,
297 });
298 }
299 let condition =
300 if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "and") {
301 input.parse::<MediaConditionAfterMediaType>().map(Some)?
302 } else {
303 None
304 };
305
306 let mut span = *media_type.span();
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 let relax_bare_ident = {
325 let peek = self.cursor.peek()?;
326 self.syntax == Syntax::Css
327 && peek.ident(self.source).is_some()
328 && !peek.is_ident_name_eq_ignore_ascii_case(self.source, "not")
329 && self.source.as_bytes().get(peek.span.end) != Some(&b'(')
330 };
331 if relax_bare_ident {
332 let token = self.cursor.bump()?;
333 let span = token.span;
334 return Ok(MediaInParens {
335 kind: MediaInParensKind::GeneralEnclosed(TokenSeq {
336 tokens: self.vec1(token),
337 span,
338 }),
339 span,
340 });
341 }
342 self.parse()
343 }
344
345 fn parse_media_condition(
348 &mut self,
349 allow_or: bool,
350 after_logic_keyword: bool,
351 ) -> PResult<MediaCondition<'a>> {
352 if self.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(self.source, "not") {
353 let media_not = self.parse::<MediaNot>()?;
354 let span = media_not.span;
355 Ok(MediaCondition { conditions: self.vec1(MediaConditionKind::Not(media_not)), span })
356 } else {
357 let first = if after_logic_keyword {
358 self.parse_media_in_parens_after_logic()?
359 } else {
360 self.parse::<MediaInParens>()?
361 };
362 let mut span = first.span;
363 let mut conditions = self.vec1(MediaConditionKind::MediaInParens(first));
364 let peek = self.cursor.peek()?;
365 if peek.ident(self.source).is_some() {
366 if peek.is_ident_name_eq_ignore_ascii_case(self.source, "and") {
367 loop {
368 conditions.push(MediaConditionKind::And(self.parse()?));
369 if !self
370 .cursor
371 .peek()?
372 .is_ident_name_eq_ignore_ascii_case(self.source, "and")
373 {
374 break;
375 }
376 }
377 } else if allow_or && peek.is_ident_name_eq_ignore_ascii_case(self.source, "or") {
378 loop {
379 conditions.push(MediaConditionKind::Or(self.parse()?));
380 if !self
381 .cursor
382 .peek()?
383 .is_ident_name_eq_ignore_ascii_case(self.source, "or")
384 {
385 break;
386 }
387 }
388 }
389 }
390
391 if let Some(last) = conditions.last() {
392 span.end = last.span().end;
393 }
394 Ok(MediaCondition { conditions, span })
395 }
396 }
397
398 fn parse_media_feature_plain(
400 &mut self,
401 ident: InterpolableIdent<'a>,
402 ) -> PResult<MediaFeaturePlain<'a>> {
403 let (_, colon_span) = self.cursor.expect_colon()?;
404 let value = self.parse_media_feature_value()?;
405 let span = Span { start: ident.span().start, end: value.span().end };
406 Ok(MediaFeaturePlain { name: MediaFeatureName::Ident(ident), colon_span, value, span })
407 }
408
409 fn parse_media_feature_range_or_range_interval(
413 &mut self,
414 left: ComponentValue<'a>,
415 ) -> PResult<MediaFeature<'a>> {
416 let comparison = self.parse()?;
417 let name_or_right = self.parse_media_feature_value()?;
418 if let ComponentValue::InterpolableIdent(ident) = name_or_right {
419 match &self.cursor.peek()?.token {
420 Token::LessThan(..)
421 | Token::LessThanEqual(..)
422 | Token::GreaterThan(..)
423 | Token::GreaterThanEqual(..)
424 | Token::Equal(..) => {
425 let right_comparison = self.parse()?;
426 let right = self.parse_media_feature_value()?;
427 let span = Span { start: left.span().start, end: right.span().end };
428 Ok(MediaFeature::RangeInterval(self.alloc(MediaFeatureRangeInterval {
429 left,
430 left_comparison: comparison,
431 name: MediaFeatureName::Ident(ident),
432 right_comparison,
433 right,
434 span,
435 })))
436 }
437 _ => {
438 let span = Span { start: left.span().start, end: ident.span().end };
439 Ok(MediaFeature::Range(self.alloc(MediaFeatureRange {
440 left,
441 comparison,
442 right: ComponentValue::InterpolableIdent(ident),
443 span,
444 })))
445 }
446 }
447 } else {
448 if !matches!(left, ComponentValue::InterpolableIdent(..))
449 && !matches!(name_or_right, ComponentValue::InterpolableIdent(..))
450 {
451 self.recoverable_errors.push(Error {
452 kind: ErrorKind::ExpectMediaFeatureName,
453 span: *name_or_right.span(),
454 });
455 }
456 let span = Span { start: left.span().start, end: name_or_right.span().end };
457 Ok(MediaFeature::Range(self.alloc(MediaFeatureRange {
458 left,
459 comparison,
460 right: name_or_right,
461 span,
462 })))
463 }
464 }
465
466 fn parse_media_feature_value(&mut self) -> PResult<ComponentValue<'a>> {
468 let value = match self.syntax {
469 Syntax::Css => self.parse_component_value_atom()?,
470 Syntax::Scss | Syntax::Sass => {
471 self.parse_sass_bin_expr(false)?
472 }
473 Syntax::Less => self.parse_less_operation(true)?,
474 };
475 match value {
476 ComponentValue::Number(number)
477 if number.value >= 0.0
478 && matches!(self.cursor.peek()?.token, Token::Solidus(..)) =>
479 {
480 self.parse_ratio(number).map(ComponentValue::Ratio)
481 }
482 value => Ok(value),
483 }
484 }
485
486 fn parse_media_query_with_type_or_function(&mut self) -> PResult<MediaQuery<'a>> {
489 let media_query_with_type = self.parse::<MediaQueryWithType>()?;
490 match (media_query_with_type, self.cursor.peek()?) {
491 (
492 MediaQueryWithType {
493 modifier: None,
494 media_type: name,
495 condition: None,
496 span: mq_span,
497 },
498 TokenWithSpan { token: crate::token::Token::LParen(..), span: lparen_span },
499 ) if mq_span.end == lparen_span.start => {
500 self.cursor.bump()?;
501 let args = self.parse_function_args()?;
502 let (_, Span { end, .. }) = self.cursor.expect_r_paren()?;
503 Ok(MediaQuery::Function(Function {
504 name: FunctionName::Ident(name),
505 args,
506 span: Span { start: mq_span.start, end },
507 }))
508 }
509 (media_query_with_type, _) => {
510 Ok(MediaQuery::WithType(self.alloc(media_query_with_type)))
511 }
512 }
513 }
514}