mirror of
https://github.com/swc-project/swc.git
synced 2024-12-25 06:36:08 +03:00
fix(css/parser): Fix a bug with scope in block (#6402)
This commit is contained in:
parent
fafc6257c8
commit
3d7545d89b
@ -565,7 +565,7 @@ where
|
||||
| js_word!("-o-keyframes")
|
||||
| js_word!("-ms-keyframes") => {
|
||||
let ctx = Ctx {
|
||||
block_contents_grammar: BlockContentsGrammar::DeclarationList,
|
||||
block_contents_grammar: BlockContentsGrammar::RuleList,
|
||||
is_top_level: false,
|
||||
in_keyframes_at_rule: true,
|
||||
..self.ctx
|
||||
|
@ -134,24 +134,18 @@ where
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let (normalized_at_rule_name, name) = if at_keyword_name.0.starts_with("--") {
|
||||
(
|
||||
at_keyword_name.0.to_ascii_lowercase(),
|
||||
let name = if at_keyword_name.0.starts_with("--") {
|
||||
AtRuleName::DashedIdent(DashedIdent {
|
||||
span: Span::new(span.lo + BytePos(1), span.hi, Default::default()),
|
||||
value: at_keyword_name.0,
|
||||
raw: Some(at_keyword_name.1),
|
||||
}),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
(
|
||||
at_keyword_name.0.to_ascii_lowercase(),
|
||||
AtRuleName::Ident(Ident {
|
||||
span: Span::new(span.lo + BytePos(1), span.hi, Default::default()),
|
||||
value: at_keyword_name.0,
|
||||
raw: Some(at_keyword_name.1),
|
||||
}),
|
||||
)
|
||||
})
|
||||
};
|
||||
let mut prelude = vec![];
|
||||
let mut at_rule = AtRule {
|
||||
@ -181,103 +175,31 @@ where
|
||||
tok!(";") => {
|
||||
self.input.bump();
|
||||
|
||||
let list_of_component_values = self.create_locv(prelude);
|
||||
|
||||
at_rule.prelude = match self
|
||||
.parse_according_to_grammar(&list_of_component_values, |parser| {
|
||||
parser.parse_at_rule_prelude(&normalized_at_rule_name)
|
||||
}) {
|
||||
Ok(at_rule_prelude) => match at_rule_prelude {
|
||||
None if normalized_at_rule_name == js_word!("layer") => {
|
||||
self.errors.push(Error::new(
|
||||
span,
|
||||
ErrorKind::Expected("at least one name"),
|
||||
));
|
||||
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
}
|
||||
_ => at_rule_prelude.map(Box::new),
|
||||
},
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
if !list_of_component_values.children.is_empty() {
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
at_rule.prelude = Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
self.create_locv(prelude),
|
||||
)));
|
||||
at_rule.span = span!(self, span.lo);
|
||||
|
||||
// Canonicalization against a grammar
|
||||
at_rule = self.canonicalize_at_rule_prelude(at_rule)?;
|
||||
|
||||
return Ok(at_rule);
|
||||
}
|
||||
// <{-token>
|
||||
// Consume a simple block and assign it to the at-rule’s block. Return the at-rule.
|
||||
tok!("{") => {
|
||||
let mut block = self.parse_as::<SimpleBlock>()?;
|
||||
let block = self.parse_as::<SimpleBlock>()?;
|
||||
|
||||
let list_of_component_values = self.create_locv(prelude);
|
||||
|
||||
at_rule.prelude = match self
|
||||
.parse_according_to_grammar(&list_of_component_values, |parser| {
|
||||
parser.parse_at_rule_prelude(&normalized_at_rule_name)
|
||||
}) {
|
||||
Ok(at_rule_prelude) => match at_rule_prelude {
|
||||
Some(AtRulePrelude::LayerPrelude(LayerPrelude::NameList(
|
||||
name_list,
|
||||
))) if name_list.name_list.len() > 1 => {
|
||||
self.errors.push(Error::new(
|
||||
name_list.span,
|
||||
ErrorKind::Expected("only one name"),
|
||||
));
|
||||
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
}
|
||||
_ => at_rule_prelude.map(Box::new),
|
||||
},
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
if !list_of_component_values.children.is_empty() {
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
at_rule.block = match self.parse_according_to_grammar(
|
||||
&self.create_locv(block.value.clone()),
|
||||
|parser| parser.parse_at_rule_block(&normalized_at_rule_name),
|
||||
) {
|
||||
Ok(block_contents) => {
|
||||
block.value = block_contents;
|
||||
|
||||
Some(block)
|
||||
}
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
Some(block)
|
||||
}
|
||||
};
|
||||
at_rule.prelude = Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
self.create_locv(prelude),
|
||||
)));
|
||||
at_rule.block = Some(block);
|
||||
at_rule.span = span!(self, span.lo);
|
||||
|
||||
// Canonicalization against a grammar
|
||||
at_rule = self.canonicalize_at_rule_prelude(at_rule)?;
|
||||
at_rule = self.canonicalize_at_rule_block(at_rule)?;
|
||||
|
||||
return Ok(at_rule);
|
||||
}
|
||||
// anything else
|
||||
@ -299,50 +221,10 @@ where
|
||||
{
|
||||
fn parse(&mut self) -> PResult<QualifiedRule> {
|
||||
// To consume a qualified rule:
|
||||
let create_prelude =
|
||||
|p: &mut Parser<I>, list: Vec<ComponentValue>| -> PResult<QualifiedRulePrelude> {
|
||||
let list_of_component_values = p.create_locv(list);
|
||||
|
||||
if p.ctx.in_keyframes_at_rule {
|
||||
Ok(QualifiedRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
))
|
||||
} else if p.ctx.mixed_with_declarations {
|
||||
match p.parse_according_to_grammar::<RelativeSelectorList>(
|
||||
&list_of_component_values,
|
||||
|parser| parser.parse(),
|
||||
) {
|
||||
Ok(relative_selector_list) => Ok(
|
||||
QualifiedRulePrelude::RelativeSelectorList(relative_selector_list),
|
||||
),
|
||||
Err(err) => {
|
||||
p.errors.push(err);
|
||||
|
||||
Ok(QualifiedRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match p.parse_according_to_grammar::<SelectorList>(
|
||||
&list_of_component_values,
|
||||
|parser| parser.parse(),
|
||||
) {
|
||||
Ok(selector_list) => Ok(QualifiedRulePrelude::SelectorList(selector_list)),
|
||||
Err(err) => {
|
||||
p.errors.push(err);
|
||||
|
||||
Ok(QualifiedRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let span = self.input.cur_span();
|
||||
// Create a new qualified rule with its prelude initially set to an empty list,
|
||||
// and its value initially set to nothing.
|
||||
let span = self.input.cur_span();
|
||||
let mut prelude = vec![];
|
||||
|
||||
// Repeatedly consume the next input token:
|
||||
@ -375,30 +257,20 @@ where
|
||||
// Consume a simple block and assign it to the qualified rule’s block. Return the
|
||||
// qualified rule.
|
||||
tok!("{") => {
|
||||
let mut block = self.parse_as::<SimpleBlock>()?;
|
||||
|
||||
block.value = match self.ctx.block_contents_grammar {
|
||||
BlockContentsGrammar::DeclarationList => self
|
||||
.parse_according_to_grammar(&self.create_locv(block.value), |parser| {
|
||||
parser.parse_as::<Vec<DeclarationOrAtRule>>()
|
||||
})?
|
||||
.into_iter()
|
||||
.map(ComponentValue::DeclarationOrAtRule)
|
||||
.collect(),
|
||||
_ => self
|
||||
.parse_according_to_grammar(&self.create_locv(block.value), |parser| {
|
||||
parser.parse_as::<Vec<StyleBlock>>()
|
||||
})?
|
||||
.into_iter()
|
||||
.map(ComponentValue::StyleBlock)
|
||||
.collect(),
|
||||
let block = self.parse_as::<SimpleBlock>()?;
|
||||
let mut qualified_rule = QualifiedRule {
|
||||
span: span!(self, span.lo),
|
||||
prelude: QualifiedRulePrelude::ListOfComponentValues(
|
||||
self.create_locv(prelude),
|
||||
),
|
||||
block,
|
||||
};
|
||||
|
||||
return Ok(QualifiedRule {
|
||||
span: span!(self, span.lo),
|
||||
prelude: create_prelude(self, prelude)?,
|
||||
block,
|
||||
});
|
||||
// Canonicalization against a grammar
|
||||
qualified_rule = self.canonicalize_qualified_rule_prelude(qualified_rule)?;
|
||||
qualified_rule = self.canonicalize_qualified_rule_block(qualified_rule)?;
|
||||
|
||||
return Ok(qualified_rule);
|
||||
}
|
||||
// Reconsume the current input token. Consume a component value. Append the returned
|
||||
// value to the qualified rule’s prelude.
|
||||
@ -458,12 +330,7 @@ where
|
||||
// Reconsume the current input token. Consume an at-rule, and append the result to
|
||||
// rules.
|
||||
tok!("@") => {
|
||||
let at_rule = self
|
||||
.with_ctx(Ctx {
|
||||
block_contents_grammar: BlockContentsGrammar::StyleBlock,
|
||||
..self.ctx
|
||||
})
|
||||
.parse_as::<AtRule>()?;
|
||||
let at_rule = self.parse()?;
|
||||
|
||||
rules.push(StyleBlock::AtRule(Box::new(at_rule)));
|
||||
}
|
||||
@ -880,32 +747,8 @@ where
|
||||
return Ok(declaration);
|
||||
}
|
||||
|
||||
// Grammar parsing
|
||||
let list_of_component_values = self.create_locv(declaration.value);
|
||||
|
||||
declaration.value =
|
||||
match self.parse_according_to_grammar(&list_of_component_values, |parser| {
|
||||
let mut values = vec![];
|
||||
|
||||
loop {
|
||||
if is!(parser, EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
values.push(parser.parse_generic_value()?);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}) {
|
||||
Ok(values) => values,
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
list_of_component_values.children
|
||||
}
|
||||
};
|
||||
// Canonicalization against a grammar
|
||||
declaration = self.canonicalize_declaration_value(declaration)?;
|
||||
|
||||
// 8. Return the declaration.
|
||||
Ok(declaration)
|
||||
@ -1055,9 +898,8 @@ where
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let function_name = &*ident.0.to_ascii_lowercase();
|
||||
let name = Ident {
|
||||
span: swc_common::Span::new(span.lo, span.hi - BytePos(1), Default::default()),
|
||||
span: Span::new(span.lo, span.hi - BytePos(1), Default::default()),
|
||||
value: ident.0,
|
||||
raw: Some(ident.1),
|
||||
};
|
||||
@ -1101,26 +943,8 @@ where
|
||||
|
||||
function.span = span!(self, span.lo);
|
||||
|
||||
// Grammar parsing
|
||||
match self.ctx.block_contents_grammar {
|
||||
BlockContentsGrammar::DeclarationList => {}
|
||||
_ => {
|
||||
let locv = self.create_locv(function.value);
|
||||
|
||||
function.value = match self.parse_according_to_grammar(&locv, |parser| {
|
||||
parser.parse_function_values(function_name)
|
||||
}) {
|
||||
Ok(values) => values,
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
locv.children
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Canonicalization against a grammar
|
||||
function = self.canonicalize_function_value(function)?;
|
||||
|
||||
return Ok(function);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::{Span, Spanned, SyntaxContext, DUMMY_SP};
|
||||
use swc_css_ast::*;
|
||||
|
||||
@ -7,7 +8,7 @@ use super::{
|
||||
input::{Input, InputType, ParserInput},
|
||||
Ctx, Error, PResult, Parse, Parser,
|
||||
};
|
||||
use crate::parser::BlockContentsGrammar;
|
||||
use crate::{error::ErrorKind, parser::BlockContentsGrammar};
|
||||
|
||||
impl<I> Parser<I>
|
||||
where
|
||||
@ -74,6 +75,255 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
pub(super) fn canonicalize_at_rule_prelude(&mut self, mut at_rule: AtRule) -> PResult<AtRule> {
|
||||
let normalized_at_rule_name = match &at_rule.name {
|
||||
AtRuleName::Ident(Ident { value, .. }) => value.to_ascii_lowercase(),
|
||||
AtRuleName::DashedIdent(_) => return Ok(at_rule),
|
||||
};
|
||||
|
||||
let list_of_component_values = match at_rule.prelude {
|
||||
Some(at_rule_prelude) => match *at_rule_prelude {
|
||||
AtRulePrelude::ListOfComponentValues(list_of_component_values) => {
|
||||
list_of_component_values
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
at_rule.prelude = match self
|
||||
.parse_according_to_grammar(&list_of_component_values, |parser| {
|
||||
parser.parse_at_rule_prelude(&normalized_at_rule_name)
|
||||
}) {
|
||||
Ok(at_rule_prelude) => match at_rule_prelude {
|
||||
Some(AtRulePrelude::LayerPrelude(LayerPrelude::NameList(name_list)))
|
||||
if name_list.name_list.len() > 1 && at_rule.block.is_some() =>
|
||||
{
|
||||
self.errors.push(Error::new(
|
||||
name_list.span,
|
||||
ErrorKind::Expected("only one name"),
|
||||
));
|
||||
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
}
|
||||
None if *normalized_at_rule_name == js_word!("layer")
|
||||
&& at_rule.block.is_none() =>
|
||||
{
|
||||
self.errors.push(Error::new(
|
||||
at_rule.span,
|
||||
ErrorKind::Expected("at least one name"),
|
||||
));
|
||||
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
}
|
||||
_ => at_rule_prelude.map(Box::new),
|
||||
},
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
if !list_of_component_values.children.is_empty() {
|
||||
Some(Box::new(AtRulePrelude::ListOfComponentValues(
|
||||
list_of_component_values,
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(at_rule)
|
||||
}
|
||||
|
||||
pub(super) fn canonicalize_at_rule_block(&mut self, mut at_rule: AtRule) -> PResult<AtRule> {
|
||||
let normalized_at_rule_name = match &at_rule.name {
|
||||
AtRuleName::Ident(Ident { value, .. }) => value.to_ascii_lowercase(),
|
||||
AtRuleName::DashedIdent(_) => return Ok(at_rule),
|
||||
};
|
||||
|
||||
let mut block = match at_rule.block {
|
||||
Some(simple_block) => simple_block,
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
let list_of_component_values = self.create_locv(block.value);
|
||||
|
||||
block.value = match self.parse_according_to_grammar(&list_of_component_values, |parser| {
|
||||
parser.parse_at_rule_block(&normalized_at_rule_name)
|
||||
}) {
|
||||
Ok(block_contents) => block_contents,
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
list_of_component_values.children
|
||||
}
|
||||
};
|
||||
|
||||
at_rule.block = Some(block);
|
||||
|
||||
Ok(at_rule)
|
||||
}
|
||||
|
||||
pub(super) fn canonicalize_qualified_rule_prelude(
|
||||
&mut self,
|
||||
mut qualified_rule: QualifiedRule,
|
||||
) -> PResult<QualifiedRule> {
|
||||
let list_of_component_values = match qualified_rule.prelude {
|
||||
QualifiedRulePrelude::ListOfComponentValues(list_of_component_values) => {
|
||||
list_of_component_values
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
qualified_rule.prelude = if self.ctx.in_keyframes_at_rule {
|
||||
QualifiedRulePrelude::ListOfComponentValues(list_of_component_values)
|
||||
} else if self.ctx.mixed_with_declarations {
|
||||
match self.parse_according_to_grammar::<RelativeSelectorList>(
|
||||
&list_of_component_values,
|
||||
|parser| parser.parse(),
|
||||
) {
|
||||
Ok(relative_selector_list) => {
|
||||
QualifiedRulePrelude::RelativeSelectorList(relative_selector_list)
|
||||
}
|
||||
Err(err) => {
|
||||
self.errors.push(err);
|
||||
|
||||
QualifiedRulePrelude::ListOfComponentValues(list_of_component_values)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self
|
||||
.parse_according_to_grammar::<SelectorList>(&list_of_component_values, |parser| {
|
||||
parser.parse()
|
||||
}) {
|
||||
Ok(selector_list) => QualifiedRulePrelude::SelectorList(selector_list),
|
||||
Err(err) => {
|
||||
self.errors.push(err);
|
||||
|
||||
QualifiedRulePrelude::ListOfComponentValues(list_of_component_values)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(qualified_rule)
|
||||
}
|
||||
|
||||
pub(super) fn canonicalize_qualified_rule_block(
|
||||
&mut self,
|
||||
mut qualified_rule: QualifiedRule,
|
||||
) -> PResult<QualifiedRule> {
|
||||
qualified_rule.block.value = match self.ctx.block_contents_grammar {
|
||||
BlockContentsGrammar::RuleList if self.ctx.in_keyframes_at_rule => self
|
||||
.parse_according_to_grammar(
|
||||
&self.create_locv(qualified_rule.block.value),
|
||||
|parser| {
|
||||
parser
|
||||
.with_ctx(Ctx {
|
||||
block_contents_grammar: BlockContentsGrammar::DeclarationList,
|
||||
..parser.ctx
|
||||
})
|
||||
.parse_as::<Vec<DeclarationOrAtRule>>()
|
||||
},
|
||||
)?
|
||||
.into_iter()
|
||||
.map(ComponentValue::DeclarationOrAtRule)
|
||||
.collect(),
|
||||
_ => self
|
||||
.parse_according_to_grammar(
|
||||
&self.create_locv(qualified_rule.block.value),
|
||||
|parser| {
|
||||
parser
|
||||
.with_ctx(Ctx {
|
||||
block_contents_grammar: BlockContentsGrammar::StyleBlock,
|
||||
..parser.ctx
|
||||
})
|
||||
.parse_as::<Vec<StyleBlock>>()
|
||||
},
|
||||
)?
|
||||
.into_iter()
|
||||
.map(ComponentValue::StyleBlock)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(qualified_rule)
|
||||
}
|
||||
|
||||
pub(super) fn canonicalize_function_value(
|
||||
&mut self,
|
||||
mut function: Function,
|
||||
) -> PResult<Function> {
|
||||
match self.ctx.block_contents_grammar {
|
||||
BlockContentsGrammar::DeclarationList => {}
|
||||
_ => {
|
||||
let function_name = function.name.value.to_ascii_lowercase();
|
||||
|
||||
let locv = self.create_locv(function.value);
|
||||
|
||||
function.value = match self.parse_according_to_grammar(&locv, |parser| {
|
||||
parser.parse_function_values(&function_name)
|
||||
}) {
|
||||
Ok(values) => values,
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
locv.children
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(function)
|
||||
}
|
||||
|
||||
pub(super) fn canonicalize_declaration_value(
|
||||
&mut self,
|
||||
mut declaration: Declaration,
|
||||
) -> PResult<Declaration> {
|
||||
let locv = self.create_locv(declaration.value);
|
||||
|
||||
declaration.value = match self.parse_according_to_grammar(&locv, |parser| {
|
||||
let mut values = vec![];
|
||||
|
||||
loop {
|
||||
if is!(parser, EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
values.push(parser.parse_generic_value()?);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}) {
|
||||
Ok(values) => values,
|
||||
Err(err) => {
|
||||
if *err.kind() != ErrorKind::Ignore {
|
||||
self.errors.push(err);
|
||||
}
|
||||
|
||||
locv.children
|
||||
}
|
||||
};
|
||||
|
||||
Ok(declaration)
|
||||
}
|
||||
|
||||
pub(super) fn try_to_parse_legacy_nesting(&mut self) -> Option<QualifiedRule> {
|
||||
let state = self.input.state();
|
||||
let qualified_rule = self
|
||||
|
@ -266,6 +266,7 @@ where
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
// TODO use `JsWord`
|
||||
pub fn parse_function_values(&mut self, function_name: &str) -> PResult<Vec<ComponentValue>> {
|
||||
let mut values = vec![];
|
||||
|
||||
|
@ -2,5 +2,5 @@
|
||||
x Expected at least one name
|
||||
,-[$DIR/tests/recovery/at-rule/layer/empty/input.css:1:1]
|
||||
1 | @layer ;
|
||||
: ^^^^^^
|
||||
: ^^^^^^^^
|
||||
`----
|
||||
|
Loading…
Reference in New Issue
Block a user