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> {
11 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
12 let keyword = input.parse::<Ident>()?;
13 if keyword.name.eq_ignore_ascii_case("and") {
14 let media_in_parens = input.parse_media_in_parens_after_logic()?;
15 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
16 Ok(MediaAnd { keyword, media_in_parens, span })
17 } else {
18 Err(Error { kind: ErrorKind::ExpectMediaAnd, span: keyword.span })
19 }
20 }
21}
22
23impl<'a> Parse<'a> for MediaConditionAfterMediaType<'a> {
24 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
25 let and: Ident = match input.cursor.bump()? {
26 TokenWithSpan { token: Token::Ident(ident), span }
27 if ident.name().eq_ignore_ascii_case("and") =>
28 {
29 input.ident(ident, span)
30 }
31 TokenWithSpan { span, .. } => {
32 return Err(Error { kind: ErrorKind::ExpectMediaAnd, span });
33 }
34 };
35
36 let condition = input.parse_media_condition(
37 false, true,
38 )?;
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 &input.cursor.peek()?.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 input.cursor.bump()? {
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!(&input.cursor.peek()?.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, .. }) = input.cursor.expect_l_paren()?;
129 let kind = input.parse()?;
130 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
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(
139 true, false,
140 )?;
141 if matches!(&parser.cursor.peek()?.token, Token::RParen(..)) {
146 Ok(media_condition)
147 } else {
148 let span = parser.cursor.peek()?.span.clone();
149 Err(Error { kind: ErrorKind::ExpectMediaFeatureName, span })
150 }
151 }) {
152 Ok(MediaInParensKind::MediaCondition(media_condition))
153 } else if let Ok(media_feature) = input.try_parse(|parser| {
154 let media_feature = parser.parse::<MediaFeature>()?;
155 if matches!(&parser.cursor.peek()?.token, Token::RParen(..)) {
156 Ok(media_feature)
157 } else {
158 let span = parser.cursor.peek()?.span.clone();
159 Err(Error { kind: ErrorKind::ExpectMediaFeatureName, span })
160 }
161 }) {
162 Ok(MediaInParensKind::MediaFeature(input.alloc(media_feature)))
163 } else {
164 let tokens = input.parse_tokens_in_parens()?;
166 Ok(MediaInParensKind::GeneralEnclosed(tokens))
167 }
168 }
169}
170
171impl<'a> Parse<'a> for MediaNot<'a> {
172 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
173 let keyword = input.parse::<Ident>()?;
174 if keyword.name.eq_ignore_ascii_case("not") {
175 let media_in_parens = input.parse::<MediaInParens>()?;
176 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
177 Ok(MediaNot { keyword, media_in_parens, span })
178 } else {
179 Err(Error { kind: ErrorKind::ExpectMediaNot, span: keyword.span })
180 }
181 }
182}
183
184impl<'a> Parse<'a> for MediaOr<'a> {
185 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
186 let keyword = input.parse::<Ident>()?;
187 if keyword.name.eq_ignore_ascii_case("or") {
188 let media_in_parens = input.parse_media_in_parens_after_logic()?;
189 let span = Span { start: keyword.span.start, end: media_in_parens.span.end };
190 Ok(MediaOr { keyword, media_in_parens, span })
191 } else {
192 Err(Error { kind: ErrorKind::ExpectMediaOr, span: keyword.span })
193 }
194 }
195}
196
197impl<'a> Parse<'a> for MediaQuery<'a> {
198 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
199 if let Ok(condition_only) = input.try_parse(|parser| {
200 parser.parse_media_condition(
201 true, false,
202 )
203 }) {
204 Ok(MediaQuery::ConditionOnly(condition_only))
205 } else if input.syntax == Syntax::Less {
206 match input.cursor.peek()?.token {
207 Token::AtKeyword(..) => {
208 input.parse_less_maybe_variable_or_with_lookups().map(|value| match value {
209 ComponentValue::LessVariable(variable) => {
210 MediaQuery::LessVariable(variable)
211 }
212 ComponentValue::LessNamespaceValue(namespace_value) => {
213 MediaQuery::LessNamespaceValue(namespace_value)
214 }
215 _ => unreachable!(),
216 })
217 }
218 Token::Dot(..) | Token::Hash(..) => {
219 let less_namespace_value = input.parse()?;
220 Ok(MediaQuery::LessNamespaceValue(input.alloc(less_namespace_value)))
221 }
222 _ => input.parse_media_query_with_type_or_function(),
223 }
224 } else {
225 input.parse_media_query_with_type_or_function()
226 }
227 }
228}
229
230impl<'a> Parse<'a> for MediaQueryList<'a> {
232 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
233 let first = input.parse::<MediaQuery>()?;
234 let mut span = first.span().clone();
235
236 let mut queries = input.vec1(first);
237 let mut comma_spans = input.vec();
238 while let Some((_, comma_span)) = input.cursor.eat_comma()? {
239 comma_spans.push(comma_span);
240 queries.push(input.parse()?);
241 }
242 debug_assert_eq!(comma_spans.len() + 1, queries.len());
243
244 span.end = unsafe {
246 let index = queries.len() - 1;
247 queries.get_unchecked(index).span().end
248 };
249 Ok(MediaQueryList { queries, comma_spans, span })
250 }
251}
252
253impl<'a> Parse<'a> for MediaQueryWithType<'a> {
254 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
255 let modifier = if let Token::Ident(ident) = &input.cursor.peek()?.token {
256 let name = ident.name();
257 if name.eq_ignore_ascii_case("not") || name.eq_ignore_ascii_case("only") {
258 Some(input.parse::<Ident>()?)
259 } else {
260 None
261 }
262 } else {
263 None
264 };
265 let media_type = input.parse::<InterpolableIdent>()?;
266 if let InterpolableIdent::Literal(Ident { name, span, .. }) = &media_type
267 && (name.eq_ignore_ascii_case("only")
268 || name.eq_ignore_ascii_case("not")
269 || name.eq_ignore_ascii_case("and")
270 || name.eq_ignore_ascii_case("or")
271 || name.eq_ignore_ascii_case("layer"))
272 {
273 input.recoverable_errors.push(Error {
274 kind: ErrorKind::MediaTypeKeywordDisallowed(name.to_string()),
275 span: span.clone(),
276 });
277 }
278 let condition = match &input.cursor.peek()?.token {
279 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
280 input.parse::<MediaConditionAfterMediaType>().map(Some)?
281 }
282 _ => None,
283 };
284
285 let mut span = media_type.span().clone();
286 if let Some(modifier) = &modifier {
287 span.start = modifier.span.start;
288 }
289 if let Some(condition) = &condition {
290 span.end = condition.span.end;
291 }
292 Ok(MediaQueryWithType { modifier, media_type, condition, span })
293 }
294}
295
296impl<'a> Parser<'a> {
297 fn parse_media_in_parens_after_logic(&mut self) -> PResult<MediaInParens<'a>> {
303 if self.syntax == Syntax::Css
304 && let TokenWithSpan { token: Token::Ident(ident), span } = self.cursor.peek()?
305 && !ident.name().eq_ignore_ascii_case("not")
306 && self.source.as_bytes().get(span.end) != Some(&b'(')
307 {
308 let token = self.cursor.bump()?;
309 let span = token.span.clone();
310 return Ok(MediaInParens {
311 kind: MediaInParensKind::GeneralEnclosed(TokenSeq {
312 tokens: self.vec1(token),
313 span: span.clone(),
314 }),
315 span,
316 });
317 }
318 self.parse()
319 }
320
321 fn parse_media_condition(
322 &mut self,
323 allow_or: bool,
324 after_logic_keyword: bool,
325 ) -> PResult<MediaCondition<'a>> {
326 match &self.cursor.peek()?.token {
327 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("not") => {
328 let media_not = self.parse::<MediaNot>()?;
329 let span = media_not.span.clone();
330 Ok(MediaCondition {
331 conditions: self.vec1(MediaConditionKind::Not(media_not)),
332 span,
333 })
334 }
335 _ => {
336 let first = if after_logic_keyword {
337 self.parse_media_in_parens_after_logic()?
338 } else {
339 self.parse::<MediaInParens>()?
340 };
341 let mut span = first.span.clone();
342 let mut conditions = self.vec1(MediaConditionKind::MediaInParens(first));
343 if let Token::Ident(ident) = &self.cursor.peek()?.token {
344 let name = ident.name();
345 if name.eq_ignore_ascii_case("and") {
346 loop {
347 conditions.push(MediaConditionKind::And(self.parse()?));
348 match &self.cursor.peek()?.token {
349 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("and") => {
350 }
351 _ => break,
352 }
353 }
354 } else if allow_or && name.eq_ignore_ascii_case("or") {
355 loop {
356 conditions.push(MediaConditionKind::Or(self.parse()?));
357 match &self.cursor.peek()?.token {
358 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("or") => {}
359 _ => break,
360 }
361 }
362 }
363 }
364
365 if let Some(last) = conditions.last() {
366 span.end = last.span().end;
367 }
368 Ok(MediaCondition { conditions, span })
369 }
370 }
371 }
372
373 fn parse_media_feature_plain(
374 &mut self,
375 ident: InterpolableIdent<'a>,
376 ) -> PResult<MediaFeaturePlain<'a>> {
377 let (_, colon_span) = self.cursor.expect_colon()?;
378 let value = self.parse_media_feature_value()?;
379 let span = Span { start: ident.span().start, end: value.span().end };
380 Ok(MediaFeaturePlain { name: MediaFeatureName::Ident(ident), colon_span, value, span })
381 }
382
383 fn parse_media_feature_range_or_range_interval(
384 &mut self,
385 left: ComponentValue<'a>,
386 ) -> PResult<MediaFeature<'a>> {
387 let comparison = self.parse()?;
388 let name_or_right = self.parse_media_feature_value()?;
389 if let ComponentValue::InterpolableIdent(ident) = name_or_right {
390 match &self.cursor.peek()?.token {
391 Token::LessThan(..)
392 | Token::LessThanEqual(..)
393 | Token::GreaterThan(..)
394 | Token::GreaterThanEqual(..)
395 | Token::Equal(..) => {
396 let right_comparison = self.parse()?;
397 let right = self.parse_media_feature_value()?;
398 let span = Span { start: left.span().start, end: right.span().end };
399 Ok(MediaFeature::RangeInterval(MediaFeatureRangeInterval {
400 left,
401 left_comparison: comparison,
402 name: MediaFeatureName::Ident(ident),
403 right_comparison,
404 right,
405 span,
406 }))
407 }
408 _ => {
409 let span = Span { start: left.span().start, end: ident.span().end };
410 Ok(MediaFeature::Range(MediaFeatureRange {
411 left,
412 comparison,
413 right: ComponentValue::InterpolableIdent(ident),
414 span,
415 }))
416 }
417 }
418 } else {
419 if !matches!(left, ComponentValue::InterpolableIdent(..))
420 && !matches!(name_or_right, ComponentValue::InterpolableIdent(..))
421 {
422 self.recoverable_errors.push(Error {
423 kind: ErrorKind::ExpectMediaFeatureName,
424 span: name_or_right.span().clone(),
425 });
426 }
427 let span = Span { start: left.span().start, end: name_or_right.span().end };
428 Ok(MediaFeature::Range(MediaFeatureRange {
429 left,
430 comparison,
431 right: name_or_right,
432 span,
433 }))
434 }
435 }
436
437 fn parse_media_feature_value(&mut self) -> PResult<ComponentValue<'a>> {
438 let value = match self.syntax {
439 Syntax::Css => self.parse_component_value_atom()?,
440 Syntax::Scss | Syntax::Sass => {
441 self.parse_sass_bin_expr(false)?
442 }
443 Syntax::Less => self.parse_less_operation(true)?,
444 };
445 match value {
446 ComponentValue::Number(number)
447 if number.value >= 0.0
448 && matches!(self.cursor.peek()?.token, Token::Solidus(..)) =>
449 {
450 self.parse_ratio(number).map(ComponentValue::Ratio)
451 }
452 value => Ok(value),
453 }
454 }
455
456 fn parse_media_query_with_type_or_function(&mut self) -> PResult<MediaQuery<'a>> {
457 let media_query_with_type = self.parse::<MediaQueryWithType>()?;
458 match (media_query_with_type, self.cursor.peek()?) {
459 (
460 MediaQueryWithType {
461 modifier: None,
462 media_type: name,
463 condition: None,
464 span: mq_span,
465 },
466 TokenWithSpan { token: crate::token::Token::LParen(..), span: lparen_span },
467 ) if mq_span.end == lparen_span.start => {
468 self.cursor.bump()?;
469 let args = self.parse_function_args()?;
470 let (_, Span { end, .. }) = self.cursor.expect_r_paren()?;
471 Ok(MediaQuery::Function(Function {
472 name: FunctionName::Ident(name),
473 args,
474 span: Span { start: mq_span.start, end },
475 }))
476 }
477 (media_query_with_type, _) => Ok(MediaQuery::WithType(media_query_with_type)),
478 }
479 }
480}