Prevent crate, Self and super to be used as identifiers

This commit is contained in:
Guillaume Gomez 2025-05-19 21:49:19 +02:00
parent 6ac39d24d9
commit 244980e0e9
3 changed files with 49 additions and 2 deletions

View File

@ -39,6 +39,14 @@ fn check_expr<'a>(
allow_underscore: bool, allow_underscore: bool,
) -> Result<(), ParseErr<'a>> { ) -> Result<(), ParseErr<'a>> {
match &expr.inner { match &expr.inner {
// List can be found in rust compiler "can_be_raw" function (although in our case, it's also
// used in cases like `match`, so `self` is allowed in this case).
Expr::Var(name @ ("crate" | "super" | "Self")) => {
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!("`{name}` cannot be used as an identifier"),
expr.span,
)))
}
Expr::Var("_") if !allow_underscore => Err(winnow::error::ErrMode::Cut(ErrorContext::new( Expr::Var("_") if !allow_underscore => Err(winnow::error::ErrMode::Cut(ErrorContext::new(
"reserved keyword `_` cannot be used here", "reserved keyword `_` cannot be used here",
expr.span, expr.span,
@ -53,11 +61,21 @@ fn check_expr<'a>(
Ok(()) Ok(())
} }
} }
Expr::Path(path) => {
if let [name] = path.as_slice() {
if !crate::can_be_variable_name(name) {
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!("`{name}` cannot be used as an identifier"),
expr.span,
)));
}
}
Ok(())
}
Expr::BoolLit(_) Expr::BoolLit(_)
| Expr::NumLit(_, _) | Expr::NumLit(_, _)
| Expr::StrLit(_) | Expr::StrLit(_)
| Expr::CharLit(_) | Expr::CharLit(_)
| Expr::Path(_)
| Expr::Attr(_, _) | Expr::Attr(_, _)
| Expr::Filter(_) | Expr::Filter(_)
| Expr::NamedArgument(_, _) | Expr::NamedArgument(_, _)
@ -702,7 +720,17 @@ impl<'a> Suffix<'a> {
preceded( preceded(
ws(('.', not('.'))), ws(('.', not('.'))),
cut_err(( cut_err((
alt((digit1, identifier)), |i: &mut _| {
let name = alt((digit1, identifier)).parse_next(i)?;
if !crate::can_be_variable_name(name) {
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!("`{name}` cannot be used as an identifier"),
*i,
)))
} else {
Ok(name)
}
},
opt(|i: &mut _| call_generics(i, level)), opt(|i: &mut _| call_generics(i, level)),
)), )),
) )

View File

@ -1077,6 +1077,11 @@ pub fn strip_common(base: &Path, path: &Path) -> String {
} }
} }
#[inline]
pub(crate) fn can_be_variable_name(name: &str) -> bool {
!matches!(name, "self" | "Self" | "super" | "crate")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntKind { pub enum IntKind {
I8, I8,

View File

@ -108,6 +108,15 @@ impl<'a> Target<'a> {
return Ok(Self::Struct(path, targets)); return Ok(Self::Struct(path, targets));
} }
// If the path only contains one item, we need to check the name.
if let [name] = path.as_slice() {
if !crate::can_be_variable_name(name) {
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!("`{name}` cannot be used as an identifier"),
i_before_matching_with,
)));
}
}
*i = i_before_matching_with; *i = i_before_matching_with;
return Ok(Self::Path(path)); return Ok(Self::Path(path));
} }
@ -204,6 +213,11 @@ fn verify_name<'a>(
format!("cannot use `{name}` as a name: it is a rust keyword"), format!("cannot use `{name}` as a name: it is a rust keyword"),
input, input,
))) )))
} else if !crate::can_be_variable_name(name) {
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!("`{name}` cannot be used as an identifier"),
input,
)))
} else if name.starts_with("__askama") { } else if name.starts_with("__askama") {
Err(winnow::error::ErrMode::Cut(ErrorContext::new( Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!("cannot use `{name}` as a name: it is reserved for `askama`"), format!("cannot use `{name}` as a name: it is reserved for `askama`"),