Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(css/ast): Support @scope at-rule #7837

Merged
merged 11 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/swc_atoms/words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2117,6 +2117,7 @@ scaleZ
scalex
scaley
scalez
scope
script
scroll
scroll-behavior
Expand Down
17 changes: 15 additions & 2 deletions crates/swc_css_ast/src/at_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use swc_atoms::{Atom, JsWord};
use swc_common::{ast_node, util::take::Take, EqIgnoreSpan, Span};

use crate::{
CustomIdent, CustomPropertyName, DashedIdent, Declaration, Dimension, FamilyName, Function,
Ident, ListOfComponentValues, Number, Percentage, Ratio, SelectorList, SimpleBlock, Str, Url,
CustomIdent, CustomPropertyName, DashedIdent, Declaration, Dimension, FamilyName,
ForgivingSelectorList, Function, Ident, ListOfComponentValues, Number, Percentage, Ratio,
SelectorList, SimpleBlock, Str, Url,
};

#[ast_node("AtRule")]
Expand Down Expand Up @@ -84,6 +85,18 @@ pub enum AtRulePrelude {
ContainerPrelude(ContainerCondition),
#[tag("CustomMedia")]
CustomMediaPrelude(CustomMediaQuery),
#[tag("ScopeRange")]
ScopePrelude(ScopeRange),
}

#[ast_node("ScopeRange")]
#[derive(Eq, Hash, EqIgnoreSpan)]
pub struct ScopeRange {
pub span: Span,
/// https://drafts.csswg.org/css-cascade-6/#typedef-scope-start
pub scope_start: Option<ForgivingSelectorList>,
/// https://drafts.csswg.org/css-cascade-6/#typedef-scope-end
pub scope_end: Option<ForgivingSelectorList>,
}

#[ast_node]
Expand Down
20 changes: 20 additions & 0 deletions crates/swc_css_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ where
n
)
}
AtRulePrelude::ScopePrelude(n) => {
emit!(self, n);
}
}
}

Expand Down Expand Up @@ -2459,6 +2462,23 @@ where
}
}

#[emitter]
fn emit_scope_range(&mut self, n: &ScopeRange) -> Result {
if let Some(start) = &n.scope_start {
formatting_space!(self);
write_raw!(self, "(");
emit!(self, start);
write_raw!(self, ")");
}
if let Some(end) = &n.scope_end {
write_raw!(self, " to");
space!(self);
write_raw!(self, "(");
emit!(self, end);
write_raw!(self, ")");
}
}

fn emit_list_pseudo_element_selector_children(
&mut self,
nodes: &[PseudoElementSelectorChildren],
Expand Down
35 changes: 35 additions & 0 deletions crates/swc_css_codegen/tests/fixture/at-rules/scope/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@scope {

/* Only match links inside a light-scheme */
a {
color: darkmagenta;
}
}

@scope (.light-scheme) {

/* Only match links inside a light-scheme */
a {
color: darkmagenta;
}
}

@scope to (.content > *) {
img {
border-radius: 50%;
}

.content {
padding: 1em;
}
}

@scope (.media-object) to (.content > *) {
img {
border-radius: 50%;
}

.content {
padding: 1em;
}
}
26 changes: 26 additions & 0 deletions crates/swc_css_codegen/tests/fixture/at-rules/scope/output.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@scope {
a {
color: darkmagenta;
}
}
@scope (.light-scheme) {
a {
color: darkmagenta;
}
}
@scope to (.content > *) {
img {
border-radius: 50%;
}
.content {
padding: 1em;
}
}
@scope (.media-object) to (.content > *) {
img {
border-radius: 50%;
}
.content {
padding: 1em;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@scope{a{color:darkmagenta}}@scope(.light-scheme){a{color:darkmagenta}}@scope to (.content>*){img{border-radius:50%}.content{padding:1em}}@scope(.media-object) to (.content>*){img{border-radius:50%}.content{padding:1em}}
2 changes: 2 additions & 0 deletions crates/swc_css_parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl Error {
ErrorKind::InvalidKeyframesName(s) => {
format!("{} is not valid name for keyframes", s).into()
}
ErrorKind::InvalidScopeAtRule => "Invalid @scope at-rule".into(),
}
}

Expand Down Expand Up @@ -115,6 +116,7 @@ pub enum ErrorKind {
InvalidAnPlusBMicrosyntax,
InvalidCustomIdent(JsWord),
InvalidKeyframesName(&'static str),
InvalidScopeAtRule,

UnknownAtRuleNotTerminated,
}
4 changes: 4 additions & 0 deletions crates/swc_css_parser/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,8 @@ macro_rules! tok {
(">") => {
swc_css_ast::Token::Delim { value: '>' }
};

("to") => {
swc_css_ast::Token::Ident { value: js_word!("to"), .. }
};
}
80 changes: 80 additions & 0 deletions crates/swc_css_parser/src/parser/at_rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,15 @@ where

None
}
js_word!("scope") => {
self.input.skip_ws();

let prelude = AtRulePrelude::ScopePrelude(self.parse()?);

self.input.skip_ws();

Some(prelude)
}
_ => {
return Err(Error::new(Default::default(), ErrorKind::Ignore));
}
Expand Down Expand Up @@ -756,6 +765,13 @@ where

rule_list
}
js_word!("scope") => {
let rule_list = self.parse_as::<Vec<Rule>>()?;
let rule_list: Vec<ComponentValue> =
rule_list.into_iter().map(ComponentValue::from).collect();

rule_list
}
_ => {
return Err(Error::new(Default::default(), ErrorKind::Ignore));
}
Expand Down Expand Up @@ -2625,3 +2641,67 @@ where
})
}
}

impl<I> Parse<ScopeRange> for Parser<I>
where
I: ParserInput,
{
fn parse(&mut self) -> PResult<ScopeRange> {
let span = self.input.cur_span();

if is!(self, EOF) {
return Ok(ScopeRange {
span: span!(self, span.lo),
scope_start: None,
scope_end: None,
});
}

match cur!(self) {
tok!("(") => {
bump!(self);
let start = self.parse()?;
expect!(self, ")");
self.input.skip_ws();

let end = if is!(self, EOF) {
None
} else if is_case_insensitive_ident!(self, "to") {
bump!(self);
self.input.skip_ws();
expect!(self, "(");
let result = self.parse()?;
expect!(self, ")");
Some(result)
} else {
None
};

Ok(ScopeRange {
span: span!(self, span.lo),
scope_start: Some(start),
scope_end: end,
})
}
_ => {
if is_case_insensitive_ident!(self, "to") {
bump!(self);

self.input.skip_ws();

expect!(self, "(");
let end = self.parse()?;
expect!(self, ")");

return Ok(ScopeRange {
span: span!(self, span.lo),
scope_start: None,
scope_end: Some(end),
});
}

return Err(Error::new(span, ErrorKind::InvalidScopeAtRule));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@scope {

/* Only match links inside a light-scheme */
a {
color: darkmagenta;
}
}

@scope (.light-scheme) {

/* Only match links inside a light-scheme */
a {
color: darkmagenta;
}
}

@scope to (.content > *) {
img {
border-radius: 50%;
}

.content {
padding: 1em;
}
}

@scope (.media-object) to (.content > *) {
img {
border-radius: 50%;
}

.content {
padding: 1em;
}
}

@scope TO (.content > *) {
img {
border-radius: 50%;
}

.content {
padding: 1em;
}
}