diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index cc50ac861..c423c9d83 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -5,12 +5,11 @@ use askama_shared::heritage::{Context, Heritage}; use askama_shared::input::{Print, Source, TemplateInput}; use askama_shared::parser::{parse, Expr, Node}; -use askama_shared::{ - generator, get_template_source, read_config_file, CompileError, Config, Integrations, -}; +use askama_shared::{generator, get_template_source, read_config_file, Config, Integrations}; use proc_macro::TokenStream; use proc_macro2::Span; +use std::borrow::Cow; use std::collections::HashMap; use std::path::PathBuf; @@ -19,9 +18,7 @@ pub fn derive_template(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(input).unwrap(); match build_template(&ast) { Ok(source) => source.parse().unwrap(), - Err(e) => syn::Error::new(Span::call_site(), e) - .to_compile_error() - .into(), + Err(err) => err.to_compile_error().into(), } } @@ -32,26 +29,36 @@ pub fn derive_template(input: TokenStream) -> TokenStream { /// parsed, and the parse tree is fed to the code generator. Will print /// the parse tree and/or generated source according to the `print` key's /// value as passed to the `template()` attribute. -fn build_template(ast: &syn::DeriveInput) -> Result { - let config_toml = read_config_file()?; - let config = Config::new(&config_toml)?; +fn build_template(ast: &syn::DeriveInput) -> Result { + let config_toml = read_config_file().map_err(|msg| syn::Error::new(Span::call_site(), msg))?; + let config = + Config::new(&config_toml).map_err(|msg| syn::Error::new(Span::call_site(), msg))?; let input = TemplateInput::new(ast, &config)?; - let source: String = match input.source { - Source::Source(ref s) => s.clone(), - Source::Path(_) => get_template_source(&input.path)?, + let (source, span) = match &input.source { + Source::Source(s, span) => (s.clone(), *span), + Source::Path(_, span) => { + let s = get_template_source(&input.path).map_err(|msg| syn::Error::new(*span, msg))?; + (s, *span) + } }; let mut sources = HashMap::new(); - find_used_templates(&input, &mut sources, source)?; + find_used_templates(&input, &mut sources, source).map_err(|msg| syn::Error::new(span, msg))?; let mut parsed = HashMap::new(); for (path, src) in &sources { - parsed.insert(path.as_path(), parse(src, input.syntax)?); + parsed.insert( + path.as_path(), + parse(src, input.syntax).map_err(|msg| syn::Error::new(span, msg))?, + ); } let mut contexts = HashMap::new(); for (path, nodes) in &parsed { - contexts.insert(*path, Context::new(input.config, path, nodes)?); + contexts.insert( + *path, + Context::new(input.config, path, nodes).map_err(|msg| syn::Error::new(span, msg))?, + ); } let ctx = &contexts[input.path.as_path()]; @@ -65,7 +72,8 @@ fn build_template(ast: &syn::DeriveInput) -> Result { eprintln!("{:?}", parsed[input.path.as_path()]); } - let code = generator::generate(&input, &contexts, heritage.as_ref(), INTEGRATIONS)?; + let code = generator::generate(&input, &contexts, heritage.as_ref(), INTEGRATIONS) + .map_err(|msg| syn::Error::new(span, msg))?; if input.print == Print::Code || input.print == Print::All { eprintln!("{}", code); } @@ -76,7 +84,7 @@ fn find_used_templates( input: &TemplateInput<'_>, map: &mut HashMap, source: String, -) -> Result<(), CompileError> { +) -> Result<(), Cow<'static, str>> { let mut dependency_graph = Vec::new(); let mut check = vec![(input.path.clone(), source)]; while let Some((path, source)) = check.pop() { @@ -86,13 +94,14 @@ fn find_used_templates( let extends = input.config.find_template(extends, Some(&path))?; let dependency_path = (path.clone(), extends.clone()); if dependency_graph.contains(&dependency_path) { - return Err(CompileError::String(format!( + return Err(format!( "cyclic dependecy in graph {:#?}", dependency_graph .iter() .map(|e| format!("{:#?} --> {:#?}", e.0, e.1)) - .collect::>() - ))); + .collect::>(), + ) + .into()); } dependency_graph.push(dependency_path); let source = get_template_source(&extends)?; diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 53729c694..93343e352 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -1,4 +1,4 @@ -use super::{get_template_source, CompileError, Integrations}; +use super::{get_template_source, Integrations}; use crate::filters; use crate::heritage::{Context, Heritage}; use crate::input::{Source, TemplateInput}; @@ -8,6 +8,7 @@ use proc_macro2::Span; use quote::{quote, ToTokens}; +use std::borrow::Cow; use std::collections::HashMap; use std::path::Path; use std::{cmp, hash, mem, str}; @@ -17,7 +18,7 @@ pub fn generate( contexts: &HashMap<&Path, Context<'_>, S>, heritage: Option<&Heritage<'_>>, integrations: Integrations, -) -> Result { +) -> Result> { Generator::new(input, contexts, heritage, integrations, MapChain::new()) .build(&contexts[input.path.as_path()]) } @@ -82,7 +83,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Takes a Context and generates the relevant implementations. - fn build(mut self, ctx: &'a Context<'_>) -> Result { + fn build(mut self, ctx: &'a Context<'_>) -> Result> { let mut buf = Buffer::new(0); if !ctx.blocks.is_empty() { if let Some(parent) = self.input.parent { @@ -122,7 +123,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::askama::Template", None)?; buf.writeln( "fn render_into(&self, writer: &mut (impl ::std::fmt::Write + ?Sized)) -> \ @@ -133,8 +134,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { for path in self.contexts.keys() { // Skip the fake path of templates defined in rust source. let path_is_valid = match self.input.source { - Source::Path(_) => true, - Source::Source(_) => path != &self.input.path, + Source::Path(_, _) => true, + Source::Source(_, _) => path != &self.input.path, }; if path_is_valid { let path = path.to_str().unwrap(); @@ -178,7 +179,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, parent_type: &syn::Type, - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::std::ops::Deref", None)?; buf.writeln(&format!( "type Target = {};", @@ -192,7 +193,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement `Display` for the given context struct. - fn impl_display(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_display(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::std::fmt::Display", None)?; buf.writeln("#[inline]")?; buf.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {")?; @@ -202,7 +203,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement Actix-web's `Responder`. - fn impl_actix_web_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_actix_web_responder(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::actix_web::Responder", None)?; buf.writeln("type Body = ::actix_web::body::BoxBody;")?; buf.writeln("#[inline]")?; @@ -216,7 +217,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement Axum's `IntoResponse`. - fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::askama_axum::IntoResponse", None)?; buf.writeln("#[inline]")?; buf.writeln( @@ -230,7 +231,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement gotham's `IntoResponse`. - fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::askama_gotham::IntoResponse", None)?; buf.writeln("#[inline]")?; buf.writeln( @@ -244,7 +245,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement mendes' `Responder`. - fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { let param = syn::parse_str("A: ::mendes::Application").unwrap(); let mut generics = self.input.ast.generics.clone(); @@ -293,7 +294,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement Rocket's `Responder`. - fn impl_rocket_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_rocket_responder(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { let lifetime = syn::Lifetime::new("'askama", Span::call_site()); let param = syn::GenericParam::Lifetime(syn::LifetimeDef::new(lifetime)); self.write_header( @@ -315,7 +316,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Ok(()) } - fn impl_tide_integrations(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_tide_integrations(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { let ext = self.input.extension().unwrap_or("txt"); self.write_header( @@ -340,7 +341,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.writeln("}\n}") } - fn impl_warp_reply(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { + fn impl_warp_reply(&mut self, buf: &mut Buffer) -> Result<(), Cow<'static, str>> { self.write_header(buf, "::askama_warp::warp::reply::Reply", None)?; buf.writeln("#[inline]")?; buf.writeln("fn into_response(self) -> ::askama_warp::warp::reply::Response {")?; @@ -357,7 +358,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, target: &str, params: Option>, - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { let mut generics = self.input.ast.generics.clone(); if let Some(params) = params { for param in params { @@ -386,7 +387,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { nodes: &'a [Node<'_>], buf: &mut Buffer, level: AstLevel, - ) -> Result { + ) -> Result> { let mut size_hint = 0; for n in nodes { match *n { @@ -473,7 +474,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, conds: &'a [Cond<'_>], ws: Ws, - ) -> Result { + ) -> Result> { let mut flushed = 0; let mut arm_sizes = Vec::new(); let mut has_else = false; @@ -490,7 +491,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { if i == 0 { buf.write("if "); } else { - buf.dedent()?; + buf.dedent(); buf.write("} else if "); } @@ -514,7 +515,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.write(") as &bool)"); } } else { - buf.dedent()?; + buf.dedent(); buf.write("} else"); has_else = true; } @@ -545,7 +546,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { expr: &Expr<'_>, arms: &'a [When<'_>], ws2: Ws, - ) -> Result { + ) -> Result> { self.flush_ws(ws1); let flushed = self.write_buf_writable(buf)?; let mut arm_sizes = Vec::new(); @@ -588,7 +589,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { ctx: &'a Context<'_>, buf: &mut Buffer, loop_block: &'a Loop<'_>, - ) -> Result { + ) -> Result> { self.handle_ws(loop_block.ws1); self.locals.push(); @@ -660,21 +661,23 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { scope: Option<&str>, name: &str, args: &[Expr<'_>], - ) -> Result { + ) -> Result> { if name == "super" { return self.write_block(buf, None, ws); } let (def, own_ctx) = if let Some(s) = scope { - let path = ctx.imports.get(s).ok_or_else(|| { - CompileError::String(format!("no import found for scope '{}'", s)) - })?; - let mctx = self.contexts.get(path.as_path()).ok_or_else(|| { - CompileError::String(format!("context for '{:?}' not found", path)) - })?; + let path = ctx + .imports + .get(s) + .ok_or_else(|| Cow::Owned(format!("no import found for scope '{}'", s)))?; + let mctx = self + .contexts + .get(path.as_path()) + .ok_or_else(|| Cow::Owned(format!("context for '{:?}' not found", path)))?; ( mctx.macros.get(name).ok_or_else(|| { - CompileError::String(format!("macro '{}' not found in scope '{}'", name, s)) + Cow::Owned(format!("macro '{}' not found in scope '{}'", name, s)) })?, mctx, ) @@ -682,7 +685,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { ( ctx.macros .get(name) - .ok_or_else(|| CompileError::String(format!("macro '{}' not found", name)))?, + .ok_or_else(|| Cow::Owned(format!("macro '{}' not found", name)))?, ctx, ) }; @@ -697,9 +700,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { let mut values = Buffer::new(0); let mut is_first_variable = true; for (i, arg) in def.args.iter().enumerate() { - let expr = args.get(i).ok_or_else(|| { - CompileError::String(format!("macro '{}' takes more than {} arguments", name, i)) - })?; + let expr = args + .get(i) + .ok_or_else(|| format!("macro '{}' takes more than {} arguments", name, i))?; match expr { // If `expr` is already a form of variable then @@ -758,7 +761,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, ws: Ws, path: &str, - ) -> Result { + ) -> Result> { self.flush_ws(ws); self.write_buf_writable(buf)?; let path = self @@ -796,7 +799,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, ws: Ws, var: &'a Target<'_>, - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { self.handle_ws(ws); self.write_buf_writable(buf)?; buf.write("let "); @@ -804,26 +807,36 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.writeln(";") } - fn is_shadowing_variable(&self, var: &Target<'a>) -> bool { + fn is_shadowing_variable(&self, var: &Target<'a>) -> Result> { match var { Target::Name(name) => { let name = normalize_identifier(name); - match self.locals.get(&name) { + Ok(match self.locals.get(&name) { // declares a new variable None => false, // an initialized variable gets shadowed Some(meta) if meta.initialized => true, // initializes a variable that was introduced in a LetDecl before _ => false, + }) + } + Target::Tuple(_, targets) => { + for target in targets { + if self.is_shadowing_variable(target)? { + return Ok(true); + } } + Ok(false) } - Target::Tuple(_, targets) => targets - .iter() - .any(|target| self.is_shadowing_variable(target)), - Target::Struct(_, named_targets) => named_targets - .iter() - .any(|(_, target)| self.is_shadowing_variable(target)), - _ => panic!("Cannot have literals on the left-hand-side of an assignment."), + Target::Struct(_, named_targets) => { + for (_, target) in named_targets { + if self.is_shadowing_variable(target)? { + return Ok(true); + } + } + Ok(false) + } + _ => Err("Cannot have literals on the left-hand-side of an assignment.".into()), } } @@ -833,12 +846,12 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { ws: Ws, var: &'a Target<'_>, val: &Expr<'_>, - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { self.handle_ws(ws); let mut expr_buf = Buffer::new(0); self.visit_expr(&mut expr_buf, val)?; - let shadowed = self.is_shadowing_variable(var); + let shadowed = self.is_shadowing_variable(var)?; if shadowed { // Need to flush the buffer if the variable is being shadowed, // to ensure the old variable is used. @@ -863,7 +876,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, name: Option<&'a str>, outer: Ws, - ) -> Result { + ) -> Result> { // Flush preceding whitespace according to the outer WS spec self.flush_ws(outer); @@ -880,21 +893,24 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { // `super()` was called inside a block (None, Some((prev_name, gen))) => (prev_name, gen + 1), // `super()` is called from outside a block - (None, None) => return Err("cannot call 'super()' outside block".into()), + (None, None) => { + return Err("cannot call 'super()' outside block".into()); + } }; self.super_block = Some(cur); // Get the block definition from the heritage chain - let heritage = self + let (ctx, def) = self .heritage - .as_ref() - .ok_or(CompileError::Static("no block ancestors available"))?; - let (ctx, def) = heritage.blocks[cur.0].get(cur.1).ok_or_else(|| { - CompileError::from(match name { - None => format!("no super() block found for block '{}'", cur.0), - Some(name) => format!("no block found for name '{}'", name), - }) - })?; + .ok_or(Cow::Borrowed("no block ancestors available"))? + .blocks[cur.0] + .get(cur.1) + .ok_or_else(|| { + Cow::Owned(match name { + None => format!("no super() block found for block '{}'", cur.0), + Some(name) => format!("no block found for name '{}'", name), + }) + })?; // Get the nodes and whitespace suppression data from the block definition let (ws1, nodes, ws2) = if let Node::BlockDef(ws1, _, nodes, ws2) = def { @@ -929,7 +945,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Write expression buffer and empty - fn write_buf_writable(&mut self, buf: &mut Buffer) -> Result { + fn write_buf_writable(&mut self, buf: &mut Buffer) -> Result> { if self.buf_writable.is_empty() { return Ok(0); } @@ -999,7 +1015,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.writeln("writer,")?; buf.writeln(&format!("{:#?},", &buf_format.buf))?; buf.writeln(buf_expr.buf.trim())?; - buf.dedent()?; + buf.dedent(); buf.writeln(")?;")?; Ok(size_hint) } @@ -1032,7 +1048,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { /* Visitor methods for expression types */ - fn visit_expr_root(&mut self, expr: &Expr<'_>) -> Result { + fn visit_expr_root(&mut self, expr: &Expr<'_>) -> Result> { let mut buf = Buffer::new(0); self.visit_expr(&mut buf, expr)?; Ok(buf.buf) @@ -1042,7 +1058,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, expr: &Expr<'_>, - ) -> Result { + ) -> Result> { Ok(match *expr { Expr::BoolLit(s) => self.visit_bool_lit(buf, s), Expr::NumLit(s) => self.visit_num_lit(buf, s), @@ -1081,7 +1097,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, mut name: &str, args: &[Expr<'_>], - ) -> Result { + ) -> Result> { if matches!(name, "escape" | "e") { self._visit_escape_filter(buf, args)?; return Ok(DisplayWrap::Wrapped); @@ -1133,13 +1149,15 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, args: &[Expr<'_>], - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { if args.len() > 2 { return Err("only two arguments allowed to escape filter".into()); } let opt_escaper = match args.get(1) { Some(Expr::StrLit(name)) => Some(*name), - Some(_) => return Err("invalid escaper type for escape filter".into()), + Some(_) => { + return Err("invalid escaper type for escape filter".into()); + } None => None, }; let escaper = match opt_escaper { @@ -1149,7 +1167,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { .escapers .iter() .find_map(|(escapers, escaper)| escapers.contains(name).then(|| escaper)) - .ok_or(CompileError::Static("invalid escaper for escape filter"))?, + .ok_or(Cow::Borrowed("invalid escaper for escape filter"))?, None => self.input.escaper, }; buf.write("::askama::filters::escape("); @@ -1164,7 +1182,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, args: &[Expr<'_>], - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { buf.write("format!("); if let Some(Expr::StrLit(v)) = args.first() { self.visit_str_lit(buf, v); @@ -1183,7 +1201,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, args: &[Expr<'_>], - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { buf.write("format!("); if let Some(Expr::StrLit(v)) = args.get(1) { self.visit_str_lit(buf, v); @@ -1204,7 +1222,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, args: &[Expr<'_>], - ) -> Result<(), CompileError> { + ) -> Result<(), Cow<'static, str>> { buf.write("::askama::filters::join((&"); for (i, arg) in args.iter().enumerate() { if i > 0 { @@ -1219,7 +1237,11 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Ok(()) } - fn _visit_args(&mut self, buf: &mut Buffer, args: &[Expr<'_>]) -> Result<(), CompileError> { + fn _visit_args( + &mut self, + buf: &mut Buffer, + args: &[Expr<'_>], + ) -> Result<(), Cow<'static, str>> { if args.is_empty() { return Ok(()); } @@ -1262,7 +1284,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, obj: &Expr<'_>, attr: &str, - ) -> Result { + ) -> Result> { if let Expr::Var(name) = *obj { if name == "loop" { if attr == "index" { @@ -1292,7 +1314,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, obj: &Expr<'_>, key: &Expr<'_>, - ) -> Result { + ) -> Result> { buf.write("&"); self.visit_expr(buf, obj)?; buf.write("["); @@ -1307,7 +1329,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { obj: &Expr<'_>, method: &str, args: &[Expr<'_>], - ) -> Result { + ) -> Result> { if matches!(obj, Expr::Var("loop")) { match method { "cycle" => match args { @@ -1326,9 +1348,13 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.writeln("_cycle[_loop_item.index % _len]")?; buf.writeln("})")?; } - _ => return Err("loop.cycle(…) expects exactly one argument".into()), + _ => { + return Err("loop.cycle(…) expects exactly one argument".into()); + } }, - s => return Err(format!("unknown loop method: {:?}", s).into()), + s => { + return Err(format!("unknown loop method: {:?}", s).into()); + } } } else { if let Expr::Var("self") = obj { @@ -1348,7 +1374,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, op: &str, inner: &Expr<'_>, - ) -> Result { + ) -> Result> { buf.write(op); self.visit_expr(buf, inner)?; Ok(DisplayWrap::Unwrapped) @@ -1360,7 +1386,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { op: &str, left: &Option>>, right: &Option>>, - ) -> Result { + ) -> Result> { if let Some(left) = left { self.visit_expr(buf, left)?; } @@ -1377,7 +1403,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { op: &str, left: &Expr<'_>, right: &Expr<'_>, - ) -> Result { + ) -> Result> { self.visit_expr(buf, left)?; buf.write(&format!(" {} ", op)); self.visit_expr(buf, right)?; @@ -1388,7 +1414,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, inner: &Expr<'_>, - ) -> Result { + ) -> Result> { buf.write("("); self.visit_expr(buf, inner)?; buf.write(")"); @@ -1399,7 +1425,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, buf: &mut Buffer, elements: &[Expr<'_>], - ) -> Result { + ) -> Result> { buf.write("["); for (i, el) in elements.iter().enumerate() { if i > 0 { @@ -1426,7 +1452,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, path: &[&str], args: &[Expr<'_>], - ) -> Result { + ) -> Result> { for (i, part) in path.iter().enumerate() { if i > 0 { buf.write("::"); @@ -1454,7 +1480,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf: &mut Buffer, s: &str, args: &[Expr<'_>], - ) -> Result { + ) -> Result> { buf.write("("); let s = normalize_identifier(s); if !self.locals.contains(&s) && s != "self" { @@ -1604,9 +1630,9 @@ impl Buffer { } } - fn writeln(&mut self, s: &str) -> Result<(), CompileError> { + fn writeln(&mut self, s: &str) -> Result<(), Cow<'static, str>> { if s == "}" { - self.dedent()?; + self.dedent(); } if !s.is_empty() { self.write(s); @@ -1633,12 +1659,11 @@ impl Buffer { self.indent += 1; } - fn dedent(&mut self) -> Result<(), CompileError> { + fn dedent(&mut self) { if self.indent == 0 { - return Err("dedent() called while indentation == 0".into()); + panic!("dedent() called while indentation == 0"); } self.indent -= 1; - Ok(()) } } diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs index 8dd97e2e6..d17daff68 100644 --- a/askama_shared/src/heritage.rs +++ b/askama_shared/src/heritage.rs @@ -1,8 +1,9 @@ +use std::borrow::Cow; use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::parser::{Expr, Loop, Macro, Node}; -use crate::{CompileError, Config}; +use crate::Config; pub struct Heritage<'a> { pub root: &'a Context<'a>, @@ -46,7 +47,7 @@ impl Context<'_> { config: &Config<'_>, path: &Path, nodes: &'n [Node<'n>], - ) -> Result, CompileError> { + ) -> Result, Cow<'static, str>> { let mut extends = None; let mut blocks = Vec::new(); let mut macros = HashMap::new(); @@ -58,7 +59,9 @@ impl Context<'_> { for n in nodes { match n { Node::Extends(Expr::StrLit(extends_path)) if top => match extends { - Some(_) => return Err("multiple extend blocks found".into()), + Some(_) => { + return Err("multiple extend blocks found".into()); + } None => { extends = Some(config.find_template(extends_path, Some(path))?); } diff --git a/askama_shared/src/input.rs b/askama_shared/src/input.rs index 1d9823138..2eee9947c 100644 --- a/askama_shared/src/input.rs +++ b/askama_shared/src/input.rs @@ -1,10 +1,12 @@ -use crate::{CompileError, Config, Syntax}; +use crate::{Config, Syntax}; +use std::borrow::Cow; use std::path::{Path, PathBuf}; use std::str::FromStr; use mime::Mime; -use quote::ToTokens; +use proc_macro2::Span; +use syn::spanned::Spanned; pub struct TemplateInput<'a> { pub ast: &'a syn::DeriveInput, @@ -27,27 +29,48 @@ impl TemplateInput<'_> { pub fn new<'n>( ast: &'n syn::DeriveInput, config: &'n Config<'_>, - ) -> Result, CompileError> { + ) -> Result, syn::Error> { // Check that an attribute called `template()` exists once and that it is // the proper type (list). let mut template_args = None; for attr in &ast.attrs { - if attr.path.is_ident("template") { + let ident = match attr.path.get_ident() { + Some(ident) => ident, + None => continue, + }; + if ident == "template" { if template_args.is_some() { - return Err(CompileError::Static("duplicated 'template' attribute")); + return Err(syn::Error::new( + ident.span(), + "duplicated 'template' attribute", + )); } match attr.parse_meta() { Ok(syn::Meta::List(syn::MetaList { nested, .. })) => { - template_args = Some(nested); + template_args = Some((ident.span(), nested)); + } + Ok(meta) => { + return Err(syn::Error::new( + meta.span(), + "'template' attribute must be a list", + )); + } + Err(e) => { + return Err(syn::Error::new( + e.span(), + format!("unable to parse attribute: {}", e), + )); } - Ok(_) => return Err("'template' attribute must be a list".into()), - Err(e) => return Err(format!("unable to parse attribute: {}", e).into()), } } } - let template_args = - template_args.ok_or(CompileError::Static("no attribute 'template' found"))?; + let (template_span, template_args) = template_args.ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "no attribute 'template' found", + ) + })?; // Loop over the meta attributes and find everything that we // understand. Raise panics if something is not right. @@ -59,76 +82,112 @@ impl TemplateInput<'_> { let mut syntax = None; for item in template_args { let pair = match item { - syn::NestedMeta::Meta(syn::Meta::NameValue(ref pair)) => pair, - _ => { - return Err(format!( - "unsupported attribute argument {:?}", - item.to_token_stream() - ) - .into()) + syn::NestedMeta::Meta(syn::Meta::NameValue(pair)) => pair, + meta => { + return Err(syn::Error::new( + meta.span(), + "unsupported attribute argument", + )); } }; - if pair.path.is_ident("path") { + let ident = match pair.path.get_ident() { + Some(ident) => ident, + None => unreachable!("impossible in Meta::NameValue(…)"), + }; + + if ident == "path" { if let syn::Lit::Str(ref s) = pair.lit { if source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); + return Err(syn::Error::new( + pair.lit.span(), + "must specify 'source' or 'path', not both", + )); } - source = Some(Source::Path(s.value())); + source = Some(Source::Path(s.value(), s.span())); } else { - return Err("template path must be string literal".into()); + return Err(syn::Error::new( + pair.lit.span(), + "template path must be string literal", + )); } - } else if pair.path.is_ident("source") { + } else if ident == "source" { if let syn::Lit::Str(ref s) = pair.lit { if source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); + return Err(syn::Error::new( + pair.lit.span(), + "must specify 'source' or 'path', not both", + )); } - source = Some(Source::Source(s.value())); + source = Some(Source::Source(s.value(), s.span())); } else { - return Err("template source must be string literal".into()); + return Err(syn::Error::new( + pair.lit.span(), + "template source must be string literal", + )); } - } else if pair.path.is_ident("print") { + } else if ident == "print" { if let syn::Lit::Str(ref s) = pair.lit { - print = s.value().parse()?; + print = s + .value() + .parse() + .map_err(|msg| syn::Error::new(pair.lit.span(), msg))?; } else { - return Err("print value must be string literal".into()); + return Err(syn::Error::new( + pair.lit.span(), + "print value must be string literal", + )); } - } else if pair.path.is_ident("escape") { + } else if ident == "escape" { if let syn::Lit::Str(ref s) = pair.lit { escaping = Some(s.value()); } else { - return Err("escape value must be string literal".into()); + return Err(syn::Error::new( + pair.lit.span(), + "escape value must be string literal", + )); } - } else if pair.path.is_ident("ext") { - if let syn::Lit::Str(ref s) = pair.lit { - ext = Some(s.value()); + } else if ident == "ext" { + if let syn::Lit::Str(s) = pair.lit { + ext = Some(s); } else { - return Err("ext value must be string literal".into()); + return Err(syn::Error::new( + pair.lit.span(), + "ext value must be string literal", + )); } - } else if pair.path.is_ident("syntax") { - if let syn::Lit::Str(ref s) = pair.lit { - syntax = Some(s.value()) + } else if ident == "syntax" { + if let syn::Lit::Str(s) = pair.lit { + syntax = Some(s); } else { - return Err("syntax value must be string literal".into()); + return Err(syn::Error::new( + pair.lit.span(), + "syntax value must be string literal", + )); } } else { - return Err(format!( - "unsupported attribute key '{}' found", - pair.path.to_token_stream() - ) - .into()); + return Err(syn::Error::new( + ident.span(), + "unsupported attribute key found", + )); } } // Validate the `source` and `ext` value together, since they are // related. In case `source` was used instead of `path`, the value // of `ext` is merged into a synthetic `path` value here. + let ext_value = ext.as_ref().map(|ext| ext.value()); let source = source.expect("template path or source not found in attributes"); - let path = match (&source, &ext) { - (&Source::Path(ref path), _) => config.find_template(path, None)?, - (&Source::Source(_), Some(ext)) => PathBuf::from(format!("{}.{}", ast.ident, ext)), - (&Source::Source(_), None) => { - return Err("must include 'ext' attribute when using 'source' attribute".into()) + let path = match (&source, &ext_value) { + (&Source::Path(ref path, span), _) => config + .find_template(path, None) + .map_err(|msg| syn::Error::new(span, msg))?, + (&Source::Source(_, _), Some(ext)) => PathBuf::from(format!("{}.{}", ast.ident, ext)), + (&Source::Source(_, _), None) => { + return Err(syn::Error::new( + template_span, + "must include 'ext' attribute when using 'source' attribute", + )); } }; @@ -154,14 +213,21 @@ impl TemplateInput<'_> { } // Validate syntax - let syntax = syntax.map_or_else( - || Ok(config.syntaxes.get(config.default_syntax).unwrap()), - |s| { - config.syntaxes.get(&s).ok_or_else(|| { - CompileError::String(format!("attribute syntax {} not exist", s)) - }) - }, - )?; + let syntax = match syntax { + Some(lit) => { + let s = lit.value(); + match config.syntaxes.get(&s) { + Some(syntax) => syntax, + None => { + return Err(syn::Error::new( + lit.span(), + format!("attribute syntax {} not exist", s), + )); + } + } + } + None => config.syntaxes.get(config.default_syntax).unwrap(), + }; // Match extension against defined output formats @@ -181,12 +247,21 @@ impl TemplateInput<'_> { } let escaper = escaper.ok_or_else(|| { - CompileError::String(format!("no escaper defined for extension '{}'", escaping,)) + let span = match (&ext, &source) { + (Some(ext), _) => ext.span(), + (None, Source::Path(_, span)) => *span, + _ => template_span, + }; + syn::Error::new( + span, + format!("no escaper defined for extension '{}'", escaping), + ) })?; - let mime_type = - extension_to_mime_type(ext_default_to_path(ext.as_deref(), &path).unwrap_or("txt")) - .to_string(); + let mime_type = extension_to_mime_type( + ext_default_to_path(ext_value.as_deref(), &path).unwrap_or("txt"), + ) + .to_string(); Ok(TemplateInput { ast, @@ -195,7 +270,7 @@ impl TemplateInput<'_> { source, print, escaper, - ext, + ext: ext_value, mime_type, parent, path, @@ -228,8 +303,8 @@ fn extension(path: &Path) -> Option<&str> { } pub enum Source { - Path(String), - Source(String), + Path(String, Span), + Source(String, Span), } #[derive(PartialEq)] @@ -241,7 +316,7 @@ pub enum Print { } impl FromStr for Print { - type Err = CompileError; + type Err = Cow<'static, str>; fn from_str(s: &str) -> Result { use self::Print::*; @@ -250,7 +325,9 @@ impl FromStr for Print { "ast" => Ast, "code" => Code, "none" => None, - v => return Err(format!("invalid value for print option: {}", v,).into()), + v => { + return Err(format!("invalid value for print option: {}", v).into()); + } }) } } diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index 911367a0e..abf9abafb 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -3,10 +3,11 @@ #![deny(elided_lifetimes_in_paths)] #![deny(unreachable_pub)] +use std::borrow::Cow; use std::collections::{BTreeMap, HashSet}; use std::convert::TryFrom; use std::path::{Path, PathBuf}; -use std::{env, fmt, fs}; +use std::{env, fs}; #[cfg(feature = "serde")] use serde::Deserialize; @@ -36,7 +37,7 @@ pub struct Config<'a> { } impl Config<'_> { - pub fn new(s: &str) -> std::result::Result, CompileError> { + pub fn new(s: &str) -> std::result::Result, Cow<'static, str>> { let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let default_dirs = vec![root.join("templates")]; @@ -108,7 +109,7 @@ impl Config<'_> { &self, path: &str, start_at: Option<&Path>, - ) -> std::result::Result { + ) -> std::result::Result> { if let Some(root) = start_at { let relative = root.with_file_name(path); if relative.exists() { @@ -155,7 +156,7 @@ impl Default for Syntax<'_> { } impl<'a> TryFrom> for Syntax<'a> { - type Error = CompileError; + type Error = Cow<'static, str>; fn try_from(raw: RawSyntax<'a>) -> std::result::Result { let default = Self::default(); @@ -203,14 +204,12 @@ struct RawConfig<'d> { impl RawConfig<'_> { #[cfg(feature = "config")] - fn from_toml_str(s: &str) -> std::result::Result, CompileError> { - toml::from_str(s).map_err(|e| { - CompileError::String(format!("invalid TOML in {}: {}", CONFIG_FILE_NAME, e)) - }) + fn from_toml_str(s: &str) -> std::result::Result, Cow<'static, str>> { + toml::from_str(s).map_err(|e| format!("invalid TOML in {}: {}", CONFIG_FILE_NAME, e).into()) } #[cfg(not(feature = "config"))] - fn from_toml_str(_: &str) -> std::result::Result, CompileError> { + fn from_toml_str(_: &str) -> std::result::Result, Cow<'static, str>> { Err("TOML support not available".into()) } } @@ -239,13 +238,12 @@ struct RawEscaper<'a> { extensions: Vec<&'a str>, } -pub fn read_config_file() -> std::result::Result { +pub fn read_config_file() -> std::result::Result> { let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let filename = root.join(CONFIG_FILE_NAME); if filename.exists() { - fs::read_to_string(&filename).map_err(|_| { - CompileError::String(format!("unable to read {}", filename.to_str().unwrap())) - }) + fs::read_to_string(&filename) + .map_err(|_| format!("unable to read {}", filename.to_str().unwrap()).into()) } else { Ok("".to_string()) } @@ -259,12 +257,13 @@ where } #[allow(clippy::match_wild_err_arm)] -pub fn get_template_source(tpl_path: &Path) -> std::result::Result { +pub fn get_template_source(tpl_path: &Path) -> std::result::Result> { match fs::read_to_string(tpl_path) { - Err(_) => Err(CompileError::String(format!( + Err(_) => Err(format!( "unable to open template file '{}'", tpl_path.to_str().unwrap() - ))), + ) + .into()), Ok(mut source) => { if source.ends_with('\n') { let _ = source.pop(); @@ -293,33 +292,6 @@ static DEFAULT_ESCAPERS: &[(&[&str], &str)] = &[ (&["j2", "jinja", "jinja2"], "::askama::Html"), ]; -#[derive(Debug)] -pub enum CompileError { - Static(&'static str), - String(String), -} - -impl fmt::Display for CompileError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CompileError::Static(s) => write!(fmt, "{}", s), - CompileError::String(s) => write!(fmt, "{}", s), - } - } -} - -impl From<&'static str> for CompileError { - fn from(s: &'static str) -> Self { - CompileError::Static(s) - } -} - -impl From for CompileError { - fn from(s: String) -> Self { - CompileError::String(s) - } -} - #[cfg(test)] #[allow(clippy::blacklisted_name)] mod tests { diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index f5685b9fd..a269ead4d 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cell::Cell; use std::str; @@ -10,7 +11,7 @@ use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::{self, error_position, AsChar, IResult, InputTakeAtPosition}; -use crate::{CompileError, Syntax}; +use crate::Syntax; #[derive(Debug, PartialEq)] pub enum Node<'a> { @@ -1173,7 +1174,7 @@ fn tag_expr_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { tag(s.syntax.expr_end)(i) } -pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result>, CompileError> { +pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result>, Cow<'static, str>> { let state = State { syntax, loop_depth: Cell::new(0), @@ -1200,16 +1201,16 @@ pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result>, let (row, last_line) = source_before.lines().enumerate().last().unwrap(); let column = last_line.chars().count(); - let msg = format!( + Err(format!( "problems parsing template source at row {}, column {} near:\n{}", row + 1, column, source_after, - ); - Err(msg.into()) + ) + .into()) } - Err(nom::Err::Incomplete(_)) => Err("parsing incomplete".into()), + Err(nom::Err::Incomplete(_)) => panic!("parsing incomplete"), } } diff --git a/testing/tests/ui.rs b/testing/tests/ui.rs index 33848b2ae..fcd29bc0f 100644 --- a/testing/tests/ui.rs +++ b/testing/tests/ui.rs @@ -14,6 +14,10 @@ fn ui() { t.compile_fail("tests/ui/before_1.58/*.rs"); } + if rustc::is_min_version("1.54").unwrap() { + t.compile_fail("tests/ui/since_1.54/*.rs"); + } + if rustc::is_min_version("1.54").unwrap() && rustc::is_max_version("1.57").unwrap() { t.compile_fail("tests/ui/1.54_to_1.57/*.rs"); } diff --git a/testing/tests/ui/1.54_to_1.57/incorrect_path.stderr b/testing/tests/ui/1.54_to_1.57/incorrect_path.stderr index e3d7ca4e3..7162ead3b 100644 --- a/testing/tests/ui/1.54_to_1.57/incorrect_path.stderr +++ b/testing/tests/ui/1.54_to_1.57/incorrect_path.stderr @@ -1,7 +1,5 @@ error: template "thisdoesnotexist.html" not found in directories ["$WORKSPACE/target/tests/askama_testing/templates"] - --> $DIR/incorrect_path.rs:3:10 + --> tests/ui/1.54_to_1.57/incorrect_path.rs:4:19 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +4 | #[template(path = "thisdoesnotexist.html")] + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/before_1.54/incorrect_path.stderr b/testing/tests/ui/before_1.54/incorrect_path.stderr index ea971493c..1f6fcd5c4 100644 --- a/testing/tests/ui/before_1.54/incorrect_path.stderr +++ b/testing/tests/ui/before_1.54/incorrect_path.stderr @@ -1,7 +1,5 @@ error: template "thisdoesnotexist.html" not found in directories ["$WORKSPACE/target/tests/askama_testing/templates"] - --> $DIR/incorrect_path.rs:3:10 + --> tests/ui/before_1.54/incorrect_path.rs:4:19 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) +4 | #[template(path = "thisdoesnotexist.html")] + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/before_1.54/no_template_attribute.rs b/testing/tests/ui/before_1.54/no_template_attribute.rs new file mode 120000 index 000000000..5838a477e --- /dev/null +++ b/testing/tests/ui/before_1.54/no_template_attribute.rs @@ -0,0 +1 @@ +../since_1.54/no_template_attribute.rs \ No newline at end of file diff --git a/testing/tests/ui/before_1.54/no_template_attribute.stderr b/testing/tests/ui/before_1.54/no_template_attribute.stderr new file mode 100644 index 000000000..6cbee1ec1 --- /dev/null +++ b/testing/tests/ui/before_1.54/no_template_attribute.stderr @@ -0,0 +1,7 @@ +error: no attribute 'template' found + --> tests/ui/before_1.54/no_template_attribute.rs:3:10 + | +3 | #[derive(Template)] + | ^^^^^^^^ + | + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/testing/tests/ui/before_1.58/break_outside_of_loop.stderr b/testing/tests/ui/before_1.58/break_outside_of_loop.stderr index 99d1d25ca..c8bd73d62 100644 --- a/testing/tests/ui/before_1.58/break_outside_of_loop.stderr +++ b/testing/tests/ui/before_1.58/break_outside_of_loop.stderr @@ -1,8 +1,6 @@ error: problems parsing template source at row 1, column 9 near: "break%}, have a parsing error!" - --> $DIR/break_outside_of_loop.rs:3:10 + --> tests/ui/before_1.58/break_outside_of_loop.rs:5:14 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | source = "Have a {%break%}, have a parsing error!", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/before_1.58/typo_in_keyword.stderr b/testing/tests/ui/before_1.58/typo_in_keyword.stderr index 545476edd..8ab51ef18 100644 --- a/testing/tests/ui/before_1.58/typo_in_keyword.stderr +++ b/testing/tests/ui/before_1.58/typo_in_keyword.stderr @@ -1,8 +1,6 @@ error: problems parsing template source at row 1, column 26 near: "endfo%}\n12345678901234567890123456789012"... - --> $DIR/typo_in_keyword.rs:3:10 + --> tests/ui/before_1.58/typo_in_keyword.rs:5:14 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | source = "{%for i in 1..=10%}{{i}}{%endfo%}\n1234567890123456789012345678901234567890", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/duplicated_template_attribute.stderr b/testing/tests/ui/duplicated_template_attribute.stderr index 4e6828c7a..c7362973a 100644 --- a/testing/tests/ui/duplicated_template_attribute.stderr +++ b/testing/tests/ui/duplicated_template_attribute.stderr @@ -1,7 +1,5 @@ error: duplicated 'template' attribute - --> tests/ui/duplicated_template_attribute.rs:3:10 + --> tests/ui/duplicated_template_attribute.rs:8:3 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +8 | #[template( + | ^^^^^^^^ diff --git a/testing/tests/ui/lit_on_assignment_lhs.stderr b/testing/tests/ui/lit_on_assignment_lhs.stderr index fa488cbb1..e6532857e 100644 --- a/testing/tests/ui/lit_on_assignment_lhs.stderr +++ b/testing/tests/ui/lit_on_assignment_lhs.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/lit_on_assignment_lhs.rs:3:10 +error: Cannot have literals on the left-hand-side of an assignment. + --> tests/ui/lit_on_assignment_lhs.rs:5:14 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = help: message: Cannot have literals on the left-hand-side of an assignment. +5 | source = "{%let 7=x%}", + | ^^^^^^^^^^^^^ diff --git a/testing/tests/ui/loop_cycle_wrong_argument_count.stderr b/testing/tests/ui/loop_cycle_wrong_argument_count.stderr index acf3bb185..74465d348 100644 --- a/testing/tests/ui/loop_cycle_wrong_argument_count.stderr +++ b/testing/tests/ui/loop_cycle_wrong_argument_count.stderr @@ -1,7 +1,5 @@ error: loop.cycle(…) expects exactly one argument - --> $DIR/loop_cycle_wrong_argument_count.rs:3:10 + --> tests/ui/loop_cycle_wrong_argument_count.rs:5:14 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | source = r#"{% for v in values %}{{ loop.cycle("r", "g", "b") }}{{ v }},{% endfor %}"#, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/missing_ext.rs b/testing/tests/ui/missing_ext.rs new file mode 100644 index 000000000..473da81b1 --- /dev/null +++ b/testing/tests/ui/missing_ext.rs @@ -0,0 +1,8 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = "🙂")] +struct MissingExt; + +fn main() { +} diff --git a/testing/tests/ui/missing_ext.stderr b/testing/tests/ui/missing_ext.stderr new file mode 100644 index 000000000..3161860e6 --- /dev/null +++ b/testing/tests/ui/missing_ext.stderr @@ -0,0 +1,5 @@ +error: must include 'ext' attribute when using 'source' attribute + --> tests/ui/missing_ext.rs:4:3 + | +4 | #[template(source = "🙂")] + | ^^^^^^^^ diff --git a/testing/tests/ui/no_template_attribute.rs b/testing/tests/ui/since_1.54/no_template_attribute.rs similarity index 100% rename from testing/tests/ui/no_template_attribute.rs rename to testing/tests/ui/since_1.54/no_template_attribute.rs diff --git a/testing/tests/ui/no_template_attribute.stderr b/testing/tests/ui/since_1.54/no_template_attribute.stderr similarity index 79% rename from testing/tests/ui/no_template_attribute.stderr rename to testing/tests/ui/since_1.54/no_template_attribute.stderr index d45d7ceb7..2cb73721b 100644 --- a/testing/tests/ui/no_template_attribute.stderr +++ b/testing/tests/ui/since_1.54/no_template_attribute.stderr @@ -1,5 +1,5 @@ error: no attribute 'template' found - --> tests/ui/no_template_attribute.rs:3:10 + --> tests/ui/since_1.54/no_template_attribute.rs:3:10 | 3 | #[derive(Template)] | ^^^^^^^^ diff --git a/testing/tests/ui/since_1.58/break_outside_of_loop.stderr b/testing/tests/ui/since_1.58/break_outside_of_loop.stderr index 8f6ba68ca..16832947f 100644 --- a/testing/tests/ui/since_1.58/break_outside_of_loop.stderr +++ b/testing/tests/ui/since_1.58/break_outside_of_loop.stderr @@ -1,8 +1,6 @@ error: problems parsing template source at row 1, column 9 near: "break%}, have a parsing error!" - --> tests/ui/since_1.58/break_outside_of_loop.rs:3:10 + --> tests/ui/since_1.58/break_outside_of_loop.rs:5:14 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | source = "Have a {%break%}, have a parsing error!", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/since_1.58/incorrect_path.stderr b/testing/tests/ui/since_1.58/incorrect_path.stderr index 7581a9193..353e9689c 100644 --- a/testing/tests/ui/since_1.58/incorrect_path.stderr +++ b/testing/tests/ui/since_1.58/incorrect_path.stderr @@ -1,7 +1,5 @@ error: template "thisdoesnotexist.html" not found in directories ["$WORKSPACE/target/tests/askama_testing/templates"] - --> tests/ui/since_1.58/incorrect_path.rs:3:10 + --> tests/ui/since_1.58/incorrect_path.rs:4:19 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +4 | #[template(path = "thisdoesnotexist.html")] + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/since_1.58/typo_in_keyword.stderr b/testing/tests/ui/since_1.58/typo_in_keyword.stderr index 92c638df3..bcf5e7c38 100644 --- a/testing/tests/ui/since_1.58/typo_in_keyword.stderr +++ b/testing/tests/ui/since_1.58/typo_in_keyword.stderr @@ -1,8 +1,6 @@ error: problems parsing template source at row 1, column 26 near: "endfo%}\n12345678901234567890123456789012"... - --> tests/ui/since_1.58/typo_in_keyword.rs:3:10 + --> tests/ui/since_1.58/typo_in_keyword.rs:5:14 | -3 | #[derive(Template)] - | ^^^^^^^^ - | - = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | source = "{%for i in 1..=10%}{{i}}{%endfo%}\n1234567890123456789012345678901234567890", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/unknown_template_argument.rs b/testing/tests/ui/unknown_template_argument.rs new file mode 100644 index 000000000..104a77b99 --- /dev/null +++ b/testing/tests/ui/unknown_template_argument.rs @@ -0,0 +1,8 @@ +use askama::Template; + +#[derive(Template)] +#[template(no_such_argument = "fail")] +struct UnknownTemplateArgument; + +fn main() { +} diff --git a/testing/tests/ui/unknown_template_argument.stderr b/testing/tests/ui/unknown_template_argument.stderr new file mode 100644 index 000000000..45e553269 --- /dev/null +++ b/testing/tests/ui/unknown_template_argument.stderr @@ -0,0 +1,5 @@ +error: unsupported attribute key found + --> tests/ui/unknown_template_argument.rs:4:12 + | +4 | #[template(no_such_argument = "fail")] + | ^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/unnamed_template_argument.rs b/testing/tests/ui/unnamed_template_argument.rs new file mode 100644 index 000000000..28151bf61 --- /dev/null +++ b/testing/tests/ui/unnamed_template_argument.rs @@ -0,0 +1,8 @@ +use askama::Template; + +#[derive(Template)] +#[template(ext = "txt", "unnamed")] +struct UnnamedTemplateArgument; + +fn main() { +} diff --git a/testing/tests/ui/unnamed_template_argument.stderr b/testing/tests/ui/unnamed_template_argument.stderr new file mode 100644 index 000000000..502efc119 --- /dev/null +++ b/testing/tests/ui/unnamed_template_argument.stderr @@ -0,0 +1,5 @@ +error: unsupported attribute argument + --> tests/ui/unnamed_template_argument.rs:4:25 + | +4 | #[template(ext = "txt", "unnamed")] + | ^^^^^^^^^