1use super::*;
2
3#[derive(Debug, PartialEq)]
4pub struct CompileError<'src> {
5 pub token: Token<'src>,
6 pub kind: Box<CompileErrorKind<'src>>,
7}
8
9impl<'src> CompileError<'src> {
10 pub fn context(&self) -> Token<'src> {
11 self.token
12 }
13
14 pub fn new(token: Token<'src>, kind: CompileErrorKind<'src>) -> CompileError<'src> {
15 Self {
16 token,
17 kind: kind.into(),
18 }
19 }
20}
21
22fn capitalize(s: &str) -> String {
23 let mut chars = s.chars();
24 match chars.next() {
25 None => String::new(),
26 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
27 }
28}
29
30impl Display for CompileError<'_> {
31 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
32 use CompileErrorKind::*;
33
34 match &*self.kind {
35 AttributeArgumentCountMismatch {
36 attribute,
37 found,
38 min,
39 max,
40 } => {
41 write!(
42 f,
43 "Attribute `{attribute}` got {found} {} but takes ",
44 Count("argument", *found),
45 )?;
46
47 if min == max {
48 let expected = min;
49 write!(f, "{expected} {}", Count("argument", *expected))
50 } else if found < min {
51 write!(f, "at least {min} {}", Count("argument", *min))
52 } else {
53 write!(f, "at most {max} {}", Count("argument", *max))
54 }
55 }
56 BacktickShebang => write!(f, "Backticks may not start with `#!`"),
57 CircularRecipeDependency { recipe, ref circle } => {
58 if circle.len() == 2 {
59 write!(f, "Recipe `{recipe}` depends on itself")
60 } else {
61 write!(
62 f,
63 "Recipe `{recipe}` has circular dependency `{}`",
64 circle.join(" -> ")
65 )
66 }
67 }
68 CircularVariableDependency {
69 variable,
70 ref circle,
71 } => {
72 if circle.len() == 2 {
73 write!(f, "Variable `{variable}` is defined in terms of itself")
74 } else {
75 write!(
76 f,
77 "Variable `{variable}` depends on its own value: `{}`",
78 circle.join(" -> "),
79 )
80 }
81 }
82 DependencyArgumentCountMismatch {
83 dependency,
84 found,
85 min,
86 max,
87 } => {
88 write!(
89 f,
90 "Dependency `{dependency}` got {found} {} but takes ",
91 Count("argument", *found),
92 )?;
93
94 if min == max {
95 let expected = min;
96 write!(f, "{expected} {}", Count("argument", *expected))
97 } else if found < min {
98 write!(f, "at least {min} {}", Count("argument", *min))
99 } else {
100 write!(f, "at most {max} {}", Count("argument", *max))
101 }
102 }
103 DuplicateAttribute { attribute, first } => write!(
104 f,
105 "Recipe attribute `{attribute}` first used on line {} is duplicated on line {}",
106 first.ordinal(),
107 self.token.line.ordinal(),
108 ),
109 DuplicateParameter { recipe, parameter } => {
110 write!(f, "Recipe `{recipe}` has duplicate parameter `{parameter}`")
111 }
112 DuplicateSet { setting, first } => write!(
113 f,
114 "Setting `{setting}` first set on line {} is redefined on line {}",
115 first.ordinal(),
116 self.token.line.ordinal(),
117 ),
118 DuplicateVariable { variable } => {
119 write!(f, "Variable `{variable}` has multiple definitions")
120 }
121 DuplicateUnexport { variable } => {
122 write!(f, "Variable `{variable}` is unexported multiple times")
123 }
124 ExpectedKeyword { expected, found } => {
125 let expected = List::or_ticked(expected);
126 if found.kind == TokenKind::Identifier {
127 write!(
128 f,
129 "Expected keyword {expected} but found identifier `{}`",
130 found.lexeme()
131 )
132 } else {
133 write!(f, "Expected keyword {expected} but found `{}`", found.kind)
134 }
135 }
136 ExportUnexported { variable } => {
137 write!(f, "Variable {variable} is both exported and unexported")
138 }
139 ExtraLeadingWhitespace => write!(f, "Recipe line has extra leading whitespace"),
140 ExtraneousAttributes { count } => {
141 write!(f, "Extraneous {}", Count("attribute", *count))
142 }
143 FunctionArgumentCountMismatch {
144 function,
145 found,
146 expected,
147 } => write!(
148 f,
149 "Function `{function}` called with {found} {} but takes {}",
150 Count("argument", *found),
151 expected.display(),
152 ),
153 Include => write!(
154 f,
155 "The `!include` directive has been stabilized as `import`"
156 ),
157 InconsistentLeadingWhitespace { expected, found } => write!(
158 f,
159 "Recipe line has inconsistent leading whitespace. Recipe started with `{}` but found \
160 line with `{}`",
161 ShowWhitespace(expected),
162 ShowWhitespace(found)
163 ),
164 Internal { ref message } => write!(
165 f,
166 "Internal error, this may indicate a bug in just: {message}\n\
167 consider filing an issue: https://github.com/casey/just/issues/new"
168 ),
169 InvalidAttribute {
170 item_name,
171 item_kind,
172 attribute,
173 } => write!(
174 f,
175 "{item_kind} `{item_name}` has invalid attribute `{}`",
176 attribute.name(),
177 ),
178 InvalidEscapeSequence { character } => write!(
179 f,
180 "`\\{}` is not a valid escape sequence",
181 match character {
182 '`' => r"\`".to_owned(),
183 '\\' => r"\".to_owned(),
184 '\'' => r"'".to_owned(),
185 '"' => r#"""#.to_owned(),
186 _ => character.escape_default().collect(),
187 }
188 ),
189 MismatchedClosingDelimiter {
190 open,
191 open_line,
192 close,
193 } => write!(
194 f,
195 "Mismatched closing delimiter `{}`. (Did you mean to close the `{}` on line {}?)",
196 close.close(),
197 open.open(),
198 open_line.ordinal(),
199 ),
200 MixedLeadingWhitespace { whitespace } => write!(
201 f,
202 "Found a mix of tabs and spaces in leading whitespace: `{}`\nLeading whitespace may \
203 consist of tabs or spaces, but not both",
204 ShowWhitespace(whitespace)
205 ),
206 ParameterFollowsVariadicParameter { parameter } => {
207 write!(f, "Parameter `{parameter}` follows variadic parameter")
208 }
209 ParsingRecursionDepthExceeded => write!(f, "Parsing recursion depth exceeded"),
210 Redefinition {
211 first,
212 first_type,
213 name,
214 second_type,
215 } => {
216 if first_type == second_type {
217 write!(
218 f,
219 "{} `{name}` first defined on line {} is redefined on line {}",
220 capitalize(first_type),
221 first.ordinal(),
222 self.token.line.ordinal(),
223 )
224 } else {
225 write!(
226 f,
227 "{} `{name}` defined on line {} is redefined as {} {second_type} on line {}",
228 capitalize(first_type),
229 first.ordinal(),
230 if *second_type == "alias" { "an" } else { "a" },
231 self.token.line.ordinal(),
232 )
233 }
234 }
235 ShebangAndScriptAttribute { recipe } => write!(
236 f,
237 "Recipe `{recipe}` has both shebang line and `[script]` attribute"
238 ),
239 ShellExpansion { err } => write!(f, "Shell expansion failed: {err}"),
240 RequiredParameterFollowsDefaultParameter { parameter } => write!(
241 f,
242 "Non-default parameter `{parameter}` follows default parameter"
243 ),
244 UndefinedVariable { variable } => write!(f, "Variable `{variable}` not defined"),
245 UnexpectedCharacter { expected } => write!(f, "Expected character `{expected}`"),
246 UnexpectedClosingDelimiter { close } => {
247 write!(f, "Unexpected closing delimiter `{}`", close.close())
248 }
249 UnexpectedEndOfToken { expected } => {
250 write!(f, "Expected character `{expected}` but found end-of-file")
251 }
252 UnexpectedToken {
253 ref expected,
254 found,
255 } => write!(f, "Expected {}, but found {found}", List::or(expected)),
256 UnicodeEscapeCharacter { character } => {
257 write!(f, "expected hex digit [0-9A-Fa-f] but found `{character}`")
258 }
259 UnicodeEscapeDelimiter { character } => write!(
260 f,
261 "expected unicode escape sequence delimiter `{{` but found `{character}`"
262 ),
263 UnicodeEscapeEmpty => write!(f, "unicode escape sequences must not be empty"),
264 UnicodeEscapeLength { hex } => write!(
265 f,
266 "unicode escape sequence starting with `\\u{{{hex}` longer than six hex digits"
267 ),
268 UnicodeEscapeRange { hex } => {
269 write!(
270 f,
271 "unicode escape sequence value `{hex}` greater than maximum valid code point `10FFFF`",
272 )
273 }
274 UnicodeEscapeUnterminated => write!(f, "unterminated unicode escape sequence"),
275 UnknownAliasTarget { alias, target } => {
276 write!(f, "Alias `{alias}` has an unknown target `{target}`")
277 }
278 UnknownAttribute { attribute } => write!(f, "Unknown attribute `{attribute}`"),
279 UnknownDependency { recipe, unknown } => {
280 write!(f, "Recipe `{recipe}` has unknown dependency `{unknown}`")
281 }
282 UnknownFunction { function } => write!(f, "Call to unknown function `{function}`"),
283 UnknownSetting { setting } => write!(f, "Unknown setting `{setting}`"),
284 UnknownStartOfToken => write!(f, "Unknown start of token:"),
285 UnpairedCarriageReturn => write!(f, "Unpaired carriage return"),
286 UnterminatedBacktick => write!(f, "Unterminated backtick"),
287 UnterminatedInterpolation => write!(f, "Unterminated interpolation"),
288 UnterminatedString => write!(f, "Unterminated string"),
289 }
290 }
291}