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