1#![allow(unused_assignments)]
2use crate::{Span, Type, ast::RedirectionSource, did_you_mean};
3use miette::Diagnostic;
4use serde::{Deserialize, Serialize};
5use std::{
6 fmt::Display,
7 str::{Utf8Error, from_utf8},
8};
9use thiserror::Error;
10
11#[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize, PartialEq)]
12pub enum ParseError {
13 #[error("Extra tokens in code.")]
17 #[diagnostic(
18 code(nu::parser::extra_tokens),
19 help(
20 "Remove the unexpected tokens, or check for a missing operator, delimiter, or newline above."
21 )
22 )]
23 ExtraTokens(#[label = "extra tokens"] Span),
24
25 #[error("Invalid characters after closing delimiter")]
26 #[diagnostic(
27 code(nu::parser::extra_token_after_closing_delimiter),
28 help("Remove these characters, or check that a delimiter closed too early above.")
29 )]
30 ExtraTokensAfterClosingDelimiter(#[label = "invalid characters"] Span),
31
32 #[error("Extra positional argument.")]
33 #[diagnostic(code(nu::parser::extra_positional), help("Usage: {0}"))]
34 ExtraPositional(String, #[label = "extra positional argument"] Span),
35
36 #[error("Required positional parameter after optional parameter")]
37 #[diagnostic(code(nu::parser::required_after_optional))]
38 RequiredAfterOptional(
39 String,
40 #[label = "required parameter {0} after optional parameter"] Span,
41 ),
42
43 #[error("Unexpected end of code.")]
44 #[diagnostic(code(nu::parser::unexpected_eof))]
45 UnexpectedEof(String, #[label("expected closing {0}")] Span),
46
47 #[error("Unclosed delimiter.")]
57 #[diagnostic(code(nu::parser::unclosed_delimiter), help("{3}"))]
58 Unclosed(
59 &'static str,
60 #[label("unclosed — opens here (need `{0}`)")] Span,
61 #[label(primary, "expected `{0}` here")] Span,
62 String,
63 ),
64
65 #[error("Unbalanced delimiter.")]
71 #[diagnostic(code(nu::parser::unbalanced_delimiter), help("{3}"))]
72 Unbalanced(
73 &'static str,
74 &'static str,
75 #[label("unexpected `{1}` (unbalanced with `{0}`)")] Span,
76 String,
77 ),
78
79 #[error("Parse mismatch: expected {0}.")]
80 #[diagnostic(
81 code(nu::parser::parse_mismatch),
82 help(
83 "Check the syntax around this position — a typo, missing delimiter, or wrong separator is common."
84 )
85 )]
86 Expected(&'static str, #[label("expected {0}")] Span),
87
88 #[error("Parse mismatch: expected {0}.")]
89 #[diagnostic(
90 code(nu::parser::parse_mismatch_with_full_string_msg),
91 help(
92 "Check the syntax around this position — a typo, missing delimiter, or wrong separator is common."
93 )
94 )]
95 ExpectedWithStringMsg(String, #[label("expected {0}")] Span),
96
97 #[error("Parse mismatch: expected {0}.")]
98 #[diagnostic(
99 code(nu::parser::parse_mismatch_with_did_you_mean),
100 help(
101 "Check the syntax around this position — a typo, missing delimiter, or wrong separator is common."
102 )
103 )]
104 ExpectedWithDidYouMean(&'static str, DidYouMean, #[label("expected {0}. {1}")] Span),
105
106 #[error("Command does not support {0} input.")]
107 #[diagnostic(code(nu::parser::input_type_mismatch))]
108 InputMismatch(String, #[label("command doesn't support {0} input")] Span),
109
110 #[error("Command output doesn't match {0}.")]
111 #[diagnostic(code(nu::parser::output_type_mismatch))]
112 OutputMismatch(
113 Type,
114 String,
115 #[label("expected {0}, but command outputs {1}")] Span,
116 ),
117
118 #[error("Type mismatch during operation.")]
119 #[diagnostic(code(nu::parser::type_mismatch))]
120 Mismatch(String, String, #[label("expected {0}, found {1}")] Span), #[error("The '&&' operator is not supported in Nushell")]
123 #[diagnostic(
124 code(nu::parser::shell_andand),
125 help("use ';' instead of the shell '&&', or 'and' instead of the boolean '&&'")
126 )]
127 ShellAndAnd(#[label("instead of '&&', use ';' or 'and'")] Span),
128
129 #[error("The '||' operator is not supported in Nushell")]
130 #[diagnostic(
131 code(nu::parser::shell_oror),
132 help("use 'try' instead of the shell '||', or 'or' instead of the boolean '||'")
133 )]
134 ShellOrOr(#[label("instead of '||', use 'try' or 'or'")] Span),
135
136 #[error("The '2>' shell operation is 'err>' in Nushell.")]
137 #[diagnostic(code(nu::parser::shell_err))]
138 ShellErrRedirect(#[label("use 'err>' instead of '2>' in Nushell")] Span),
139
140 #[error("The '2>&1' shell operation is 'out+err>' in Nushell.")]
141 #[diagnostic(
142 code(nu::parser::shell_outerr),
143 help("Nushell redirection will write all of stdout before stderr.")
144 )]
145 ShellOutErrRedirect(#[label("use 'out+err>' instead of '2>&1' in Nushell")] Span),
146
147 #[error("Multiple redirections provided for {0}.")]
148 #[diagnostic(code(nu::parser::multiple_redirections))]
149 MultipleRedirections(
150 RedirectionSource,
151 #[label = "first redirection"] Span,
152 #[label = "second redirection"] Span,
153 ),
154
155 #[error("Unexpected redirection.")]
156 #[diagnostic(code(nu::parser::unexpected_redirection))]
157 UnexpectedRedirection {
158 #[label = "redirecting nothing"]
159 span: Span,
160 },
161
162 #[error("The '{op}' operator does not work on values of type '{unsupported}'.")]
164 #[diagnostic(code(nu::parser::operator_unsupported_type))]
165 OperatorUnsupportedType {
166 op: &'static str,
167 unsupported: Type,
168 #[label = "does not support '{unsupported}'"]
169 op_span: Span,
170 #[label("{unsupported}")]
171 unsupported_span: Span,
172 #[help]
173 help: Option<&'static str>,
174 },
175
176 #[error("Types '{lhs}' and '{rhs}' are not compatible for the '{op}' operator.")]
178 #[diagnostic(code(nu::parser::operator_incompatible_types))]
179 OperatorIncompatibleTypes {
180 op: &'static str,
181 lhs: Type,
182 rhs: Type,
183 #[label = "does not operate between '{lhs}' and '{rhs}'"]
184 op_span: Span,
185 #[label("{lhs}")]
186 lhs_span: Span,
187 #[label("{rhs}")]
188 rhs_span: Span,
189 #[help]
190 help: Option<&'static str>,
191 },
192
193 #[error("Capture of mutable variable.")]
194 #[diagnostic(code(nu::parser::expected_keyword))]
195 CaptureOfMutableVar(#[label("capture of mutable variable")] Span),
196
197 #[error("Expected keyword.")]
198 #[diagnostic(code(nu::parser::expected_keyword))]
199 ExpectedKeyword(String, #[label("expected {0}")] Span),
200
201 #[error("Unexpected keyword.")]
202 #[diagnostic(
203 code(nu::parser::unexpected_keyword),
204 help("'{0}' keyword is allowed only in a module.")
205 )]
206 UnexpectedKeyword(String, #[label("unexpected {0}")] Span),
207
208 #[error("Module `{0}` has a `main` command but `{0}` is a built-in parser keyword.")]
209 #[diagnostic(
210 code(nu::parser::keyword_shadow_module_main),
211 help(
212 "The `main` command cannot be invoked because `{0}` is intercepted by the parser. Either rename the module file, or remove `export def main` and use `use {0}.nu *` to import other commands."
213 )
214 )]
215 KeywordShadowModuleMain(String, #[label("`{0}` is a parser keyword")] Span),
216
217 #[error("Can't create alias to parser keyword.")]
218 #[diagnostic(
219 code(nu::parser::cant_alias_keyword),
220 help("Only the following keywords can be aliased: {0}.")
221 )]
222 CantAliasKeyword(String, #[label("not supported in alias")] Span),
223
224 #[error("Can't create alias to expression.")]
225 #[diagnostic(
226 code(nu::parser::cant_alias_expression),
227 help("Only command calls can be aliased.")
228 )]
229 CantAliasExpression(String, #[label("aliasing {0} is not supported")] Span),
230
231 #[error("Unknown operator")]
232 #[diagnostic(code(nu::parser::unknown_operator), help("{1}"))]
233 UnknownOperator(
234 &'static str,
235 &'static str,
236 #[label("Operator '{0}' not supported")] Span,
237 ),
238
239 #[error("Statement used in pipeline.")]
240 #[diagnostic(
241 code(nu::parser::unexpected_keyword),
242 help(
243 "'{0}' keyword is not allowed in pipeline. Use '{0}' by itself, outside of a pipeline."
244 )
245 )]
246 BuiltinCommandInPipeline(String, #[label("not allowed in pipeline")] Span),
247
248 #[error("{0} statement used in pipeline.")]
249 #[diagnostic(
250 code(nu::parser::unexpected_keyword),
251 help(
252 "Assigning '{1}' to '{2}' does not produce a value to be piped. If the pipeline result is meant to be assigned to '{2}', use '{0} {2} = ({1} | ...)'."
253 )
254 )]
255 AssignInPipeline(String, String, String, #[label("'{0}' in pipeline")] Span),
256
257 #[error("`{0}` used as variable name.")]
258 #[diagnostic(
259 code(nu::parser::name_is_builtin_var),
260 help(
261 "'{0}' is the name of a builtin Nushell variable and cannot be used as a variable name"
262 )
263 )]
264 NameIsBuiltinVar(String, #[label("already a builtin variable")] Span),
265
266 #[error("Can't use parser keyword `{0}` as {1} name.")]
267 #[diagnostic(
268 code(nu::parser::name_is_keyword),
269 help(
270 "Parser keywords cannot be shadowed (including via module exports and `use *`). Choose a different {1} name so language constructs keep working."
271 )
272 )]
273 NameIsKeyword(String, String, #[label("'{0}' is a parser keyword")] Span),
274
275 #[error("Incorrect value")]
276 #[diagnostic(code(nu::parser::incorrect_value), help("{2}"))]
277 IncorrectValue(String, #[label("unexpected {0}")] Span, String),
278
279 #[error("Invalid binary string.")]
280 #[diagnostic(code(nu::parser::invalid_binary_string), help("{1}"))]
281 InvalidBinaryString(#[label("invalid binary string")] Span, String),
282
283 #[error("Multiple rest params.")]
284 #[diagnostic(code(nu::parser::multiple_rest_params))]
285 MultipleRestParams(#[label = "multiple rest params"] Span),
286
287 #[error("Variable not found.")]
288 #[diagnostic(code(nu::parser::variable_not_found))]
289 VariableNotFound(DidYouMean, #[label = "variable not found. {0}"] Span),
290
291 #[error("Use $env.{0} instead of ${0}.")]
292 #[diagnostic(code(nu::parser::env_var_not_var))]
293 EnvVarNotVar(String, #[label = "use $env.{0} instead of ${0}"] Span),
294
295 #[error("Variable name not supported.")]
296 #[diagnostic(code(nu::parser::variable_not_valid))]
297 VariableNotValid(#[label = "variable name can't contain spaces or quotes"] Span),
298
299 #[error("Alias name not supported.")]
300 #[diagnostic(code(nu::parser::variable_not_valid))]
301 AliasNotValid(
302 #[label = "alias name can't be a number, a filesize, or contain #, ^, or %"] Span,
303 ),
304
305 #[error("Command name not supported.")]
306 #[diagnostic(code(nu::parser::variable_not_valid))]
307 CommandDefNotValid(
308 #[label = "command name can't be a number, a filesize, or contain #, ^, or %"] Span,
309 ),
310
311 #[error("Module not found.")]
312 #[diagnostic(
313 code(nu::parser::module_not_found),
314 help(
315 "module files and their paths must be available before your script is run as parsing occurs before anything is evaluated"
316 )
317 )]
318 ModuleNotFound(#[label = "module {1} not found"] Span, String),
319
320 #[error("Missing mod.nu file.")]
321 #[diagnostic(
322 code(nu::parser::module_missing_mod_nu_file),
323 help(
324 "Directory {0} is missing a mod.nu file.\n\nWhen importing a directory as a Nushell module, it needs to contain a mod.nu file (can be empty). Alternatively, you can use .nu files in the directory as modules individually."
325 )
326 )]
327 ModuleMissingModNuFile(
328 String,
329 #[label = "module directory is missing a mod.nu file"] Span,
330 ),
331
332 #[error("Circular import.")]
333 #[diagnostic(code(nu::parser::circular_import), help("{0}"))]
334 CircularImport(String, #[label = "detected circular import"] Span),
335
336 #[error("Can't export {0} named same as the module.")]
337 #[diagnostic(
338 code(nu::parser::named_as_module),
339 help(
340 "Module {1} can't export {0} named the same as the module. Either change the module name, or export `{2}` {0}."
341 )
342 )]
343 NamedAsModule(
344 String,
345 String,
346 String,
347 #[label = "can't export from module {1}"] Span,
348 ),
349
350 #[error("Module already contains 'main' command.")]
351 #[diagnostic(
352 code(nu::parser::module_double_main),
353 help("Tried to add 'main' command to module '{0}' but it has already been added.")
354 )]
355 ModuleDoubleMain(
356 String,
357 #[label = "module '{0}' already contains 'main'"] Span,
358 ),
359
360 #[error("Can't export alias defined as 'main'.")]
361 #[diagnostic(
362 code(nu::parser::export_main_alias_not_allowed),
363 help(
364 "Exporting aliases as 'main' is not allowed. Either rename the alias or convert it to a custom command."
365 )
366 )]
367 ExportMainAliasNotAllowed(#[label = "can't export from module"] Span),
368
369 #[error("Active overlay not found.")]
370 #[diagnostic(code(nu::parser::active_overlay_not_found))]
371 ActiveOverlayNotFound(#[label = "not an active overlay"] Span),
372
373 #[error("Overlay prefix mismatch.")]
374 #[diagnostic(
375 code(nu::parser::overlay_prefix_mismatch),
376 help(
377 "Overlay {0} already exists {1} a prefix. To add it again, do it {1} the --prefix flag."
378 )
379 )]
380 OverlayPrefixMismatch(
381 String,
382 String,
383 #[label = "already exists {1} a prefix"] Span,
384 ),
385
386 #[error("Module or overlay not found.")]
387 #[diagnostic(
388 code(nu::parser::module_or_overlay_not_found),
389 help(
390 "Requires either an existing overlay, a module, or an import pattern defining a module."
391 )
392 )]
393 ModuleOrOverlayNotFound(#[label = "not a module or an overlay"] Span),
394
395 #[error("Cannot remove the last overlay.")]
396 #[diagnostic(
397 code(nu::parser::cant_remove_last_overlay),
398 help("At least one overlay must always be active.")
399 )]
400 CantRemoveLastOverlay(#[label = "this is the last overlay, can't remove it"] Span),
401
402 #[error("Cannot hide default overlay.")]
403 #[diagnostic(
404 code(nu::parser::cant_hide_default_overlay),
405 help("'{0}' is a default overlay. Default overlays cannot be hidden.")
406 )]
407 CantHideDefaultOverlay(String, #[label = "can't hide overlay"] Span),
408
409 #[error("Cannot add overlay.")]
410 #[diagnostic(code(nu::parser::cant_add_overlay_help), help("{0}"))]
411 CantAddOverlayHelp(String, #[label = "cannot add this overlay"] Span),
412
413 #[error("Duplicate command definition within a block.")]
414 #[diagnostic(code(nu::parser::duplicate_command_def))]
415 DuplicateCommandDef(#[label = "defined more than once"] Span),
416
417 #[error("Unknown command.")]
418 #[diagnostic(
419 code(nu::parser::unknown_command),
420 )]
422 UnknownCommand(#[label = "unknown command"] Span),
423
424 #[error("Non-UTF8 string.")]
425 #[diagnostic(code(nu::parser::non_utf8))]
426 NonUtf8(#[label = "non-UTF8 string"] Span),
427
428 #[error("The `{0}` command doesn't have flag `{1}`.")]
429 #[diagnostic(code(nu::parser::unknown_flag), help("{3}"))]
430 UnknownFlag(String, String, #[label = "unknown flag"] Span, String),
431
432 #[error("Unknown type.")]
433 #[diagnostic(code(nu::parser::unknown_type))]
434 UnknownType(#[label = "unknown type"] Span),
435
436 #[error("Missing flag argument.")]
437 #[diagnostic(code(nu::parser::missing_flag_param))]
438 MissingFlagParam(String, #[label = "flag missing {0} argument"] Span),
439
440 #[error("Only the last flag in a short flag batch can take an argument.")]
441 #[diagnostic(code(nu::parser::only_last_flag_in_batch_can_take_arg))]
442 OnlyLastFlagInBatchCanTakeArg(#[label = "only the last flag can take args"] Span),
443
444 #[error("Missing required positional argument.")]
445 #[diagnostic(
446 code(nu::parser::missing_positional),
447 help("Usage: {2}. Use `--help` for more information.")
448 )]
449 MissingPositional(String, #[label("missing {0}")] Span, String),
450
451 #[error("Missing argument to `{1}`.")]
452 #[diagnostic(code(nu::parser::keyword_missing_arg))]
453 KeywordMissingArgument(
454 String,
455 String,
456 #[label("missing {0} value that follows {1}")] Span,
457 ),
458
459 #[error("Missing type.")]
460 #[diagnostic(code(nu::parser::missing_type))]
461 MissingType(#[label = "expected type"] Span),
462
463 #[error("Type mismatch.")]
464 #[diagnostic(code(nu::parser::type_mismatch))]
465 TypeMismatch(Type, Type, #[label("expected {0}, found {1}")] Span), #[error("Type mismatch.")]
468 #[diagnostic(code(nu::parser::type_mismatch_help), help("{3}"))]
469 TypeMismatchHelp(Type, Type, #[label("expected {0}, found {1}")] Span, String), #[error("Missing required flag.")]
472 #[diagnostic(code(nu::parser::missing_required_flag))]
473 MissingRequiredFlag(String, #[label("missing required flag {0}")] Span),
474
475 #[error("Incomplete math expression.")]
476 #[diagnostic(code(nu::parser::incomplete_math_expression))]
477 IncompleteMathExpression(#[label = "incomplete math expression"] Span),
478
479 #[error("Unknown state.")]
480 #[diagnostic(code(nu::parser::unknown_state))]
481 UnknownState(String, #[label("{0}")] Span),
482
483 #[error("Internal error.")]
484 #[diagnostic(code(nu::parser::unknown_state))]
485 InternalError(String, #[label("{0}")] Span),
486
487 #[error("Parser incomplete.")]
488 #[diagnostic(code(nu::parser::parser_incomplete))]
489 IncompleteParser(#[label = "parser support missing for this expression"] Span),
490
491 #[error("Rest parameter needs a name.")]
492 #[diagnostic(code(nu::parser::rest_needs_name))]
493 RestNeedsName(#[label = "needs a parameter name"] Span),
494
495 #[error("Parameter not correct type.")]
496 #[diagnostic(code(nu::parser::parameter_mismatch_type))]
497 ParameterMismatchType(
498 String,
499 String,
500 String,
501 #[label = "parameter {0} needs to be '{1}' instead of '{2}'"] Span,
502 ),
503
504 #[error("Default values should be constant expressions.")]
505 #[diagnostic(code(nu::parser::non_constant_default_value))]
506 NonConstantDefaultValue(#[label = "expected a constant value"] Span),
507
508 #[error("Extra columns.")]
509 #[diagnostic(code(nu::parser::extra_columns))]
510 ExtraColumns(
511 usize,
512 #[label("expected {0} column{}", if *.0 == 1 { "" } else { "s" })] Span,
513 ),
514
515 #[error("Missing columns.")]
516 #[diagnostic(code(nu::parser::missing_columns))]
517 MissingColumns(
518 usize,
519 #[label("expected {0} column{}", if *.0 == 1 { "" } else { "s" })] Span,
520 ),
521
522 #[error("{0}")]
523 #[diagnostic(code(nu::parser::assignment_mismatch))]
524 AssignmentMismatch(String, String, #[label("{1}")] Span),
525
526 #[error("Wrong import pattern structure.")]
527 #[diagnostic(code(nu::parser::wrong_import_pattern))]
528 WrongImportPattern(String, #[label = "{0}"] Span),
529
530 #[error("Export not found.")]
531 #[diagnostic(code(nu::parser::export_not_found))]
532 ExportNotFound(#[label = "could not find imports"] Span),
533
534 #[error("File not found")]
535 #[diagnostic(
536 code(nu::parser::sourced_file_not_found),
537 help("sourced files need to be available before your script is run")
538 )]
539 SourcedFileNotFound(String, #[label("File not found: {0}")] Span),
540
541 #[error("Script file is too large to load with `run`")]
542 #[diagnostic(
543 code(nu::parser::script_file_too_large),
544 help(
545 "The `run` command refuses files larger than {max_size} bytes at parse time (file is {size} bytes). Use a smaller script, or invoke large scripts with `nu path/to/script.nu`."
546 )
547 )]
548 ScriptFileTooLarge {
549 path: String,
550 size: u64,
551 max_size: u64,
552 #[label("file too large for `run`: {path}")]
553 span: Span,
554 },
555
556 #[error("Script file does not appear to be text")]
557 #[diagnostic(
558 code(nu::parser::script_file_not_text),
559 help(
560 "The `run` command only loads UTF-8 text scripts. Binary data (NUL bytes, invalid UTF-8, or dense control characters) is rejected."
561 )
562 )]
563 ScriptFileNotText {
564 path: String,
565 #[label("not a text script for `run`: {path}")]
566 span: Span,
567 },
568
569 #[error("File not found")]
570 #[diagnostic(
571 code(nu::parser::registered_file_not_found),
572 help("registered files need to be available before your script is run")
573 )]
574 RegisteredFileNotFound(String, #[label("File not found: {0}")] Span),
575
576 #[error("File not found")]
577 #[diagnostic(code(nu::parser::file_not_found))]
578 FileNotFound(String, #[label("File not found: {0}")] Span),
579
580 #[error("Plugin not found")]
581 #[diagnostic(
582 code(nu::parser::plugin_not_found),
583 help(
584 "plugins need to be added to the plugin registry file before your script is run (see `plugin add`)"
585 )
586 )]
587 PluginNotFound {
588 name: String,
589 #[label("Plugin not found: {name}")]
590 name_span: Span,
591 #[label("in this registry file")]
592 plugin_config_span: Option<Span>,
593 },
594
595 #[error("Invalid literal")] #[diagnostic()]
597 InvalidLiteral(String, String, #[label("{0} in {1}")] Span),
598
599 #[error("{0}")]
600 #[diagnostic()]
601 LabeledError(String, String, #[label("{1}")] Span),
602
603 #[error("{error}")]
604 #[diagnostic(help("{help}"))]
605 LabeledErrorWithHelp {
606 error: String,
607 label: String,
608 help: String,
609 #[label("{label}")]
610 span: Span,
611 },
612
613 #[error("Redirection can not be used with {0}.")]
614 #[diagnostic()]
615 RedirectingBuiltinCommand(
616 &'static str,
617 #[label("not allowed here")] Span,
618 #[label("...and here")] Option<Span>,
619 ),
620
621 #[error("This command does not have a ...rest parameter")]
622 #[diagnostic(
623 code(nu::parser::unexpected_spread_arg),
624 help(
625 "To spread arguments, the command needs to define a multi-positional parameter in its signature, such as ...rest"
626 )
627 )]
628 UnexpectedSpreadArg(String, #[label = "unexpected spread argument"] Span),
629
630 #[error("Assignment to an immutable variable.")]
636 #[diagnostic(
637 code(nu::parser::assignment_requires_mutable_variable),
638 help("declare the variable with `mut`, or shadow it again with `let`")
639 )]
640 AssignmentRequiresMutableVar(#[label("needs to be a mutable variable")] Span),
641
642 #[error("Assignment operations require a variable.")]
648 #[diagnostic(
649 code(nu::parser::assignment_requires_variable),
650 help("try assigning to a variable or a cell path of a variable")
651 )]
652 AssignmentRequiresVar(#[label("needs to be a variable")] Span),
653
654 #[error("Attributes must be followed by a definition.")]
655 #[diagnostic(
656 code(nu::parser::attribute_requires_definition),
657 help("try following this line with a `def` or `extern` definition")
658 )]
659 AttributeRequiresDefinition(#[label("must be followed by a definition")] Span),
660}
661
662impl ParseError {
663 pub fn opener_span(span: Span, len: usize) -> Span {
667 Span::new(span.start, span.start.saturating_add(len).min(span.end))
668 }
669
670 pub fn unclosed(delimiter: &'static str, open_span: Span, end_span: Span) -> Self {
672 Self::Unclosed(
673 delimiter,
674 open_span,
675 end_span,
676 default_unclosed_help(delimiter, None),
677 )
678 }
679
680 pub fn unclosed_with_hint(
683 delimiter: &'static str,
684 open_span: Span,
685 end_span: Span,
686 structure_hint: Option<&str>,
687 ) -> Self {
688 Self::Unclosed(
689 delimiter,
690 open_span,
691 end_span,
692 default_unclosed_help(delimiter, structure_hint),
693 )
694 }
695
696 pub fn unbalanced(open: &'static str, close: &'static str, close_span: Span) -> Self {
698 Self::Unbalanced(
699 open,
700 close,
701 close_span,
702 default_unbalanced_help(open, close),
703 )
704 }
705
706 pub fn span(&self) -> Span {
707 match self {
708 ParseError::ExtraTokens(s) => *s,
709 ParseError::ExtraPositional(_, s) => *s,
710 ParseError::UnexpectedEof(_, s) => *s,
711 ParseError::Unclosed(_, _open, end, _) => *end,
713 ParseError::Unbalanced(_, _, close, _) => *close,
714 ParseError::Expected(_, s) => *s,
715 ParseError::ExpectedWithStringMsg(_, s) => *s,
716 ParseError::ExpectedWithDidYouMean(_, _, s) => *s,
717 ParseError::Mismatch(_, _, s) => *s,
718 ParseError::OperatorUnsupportedType { op_span, .. } => *op_span,
719 ParseError::OperatorIncompatibleTypes { op_span, .. } => *op_span,
720 ParseError::ExpectedKeyword(_, s) => *s,
721 ParseError::UnexpectedKeyword(_, s) => *s,
722 ParseError::CantAliasKeyword(_, s) => *s,
723 ParseError::CantAliasExpression(_, s) => *s,
724 ParseError::BuiltinCommandInPipeline(_, s) => *s,
725 ParseError::AssignInPipeline(_, _, _, s) => *s,
726 ParseError::NameIsBuiltinVar(_, s) => *s,
727 ParseError::NameIsKeyword(_, _, s) => *s,
728 ParseError::CaptureOfMutableVar(s) => *s,
729 ParseError::IncorrectValue(_, s, _) => *s,
730 ParseError::InvalidBinaryString(s, _) => *s,
731 ParseError::MultipleRestParams(s) => *s,
732 ParseError::VariableNotFound(_, s) => *s,
733 ParseError::EnvVarNotVar(_, s) => *s,
734 ParseError::VariableNotValid(s) => *s,
735 ParseError::AliasNotValid(s) => *s,
736 ParseError::CommandDefNotValid(s) => *s,
737 ParseError::ModuleNotFound(s, _) => *s,
738 ParseError::ModuleMissingModNuFile(_, s) => *s,
739 ParseError::NamedAsModule(_, _, _, s) => *s,
740 ParseError::ModuleDoubleMain(_, s) => *s,
741 ParseError::ExportMainAliasNotAllowed(s) => *s,
742 ParseError::CircularImport(_, s) => *s,
743 ParseError::ModuleOrOverlayNotFound(s) => *s,
744 ParseError::ActiveOverlayNotFound(s) => *s,
745 ParseError::OverlayPrefixMismatch(_, _, s) => *s,
746 ParseError::CantRemoveLastOverlay(s) => *s,
747 ParseError::CantHideDefaultOverlay(_, s) => *s,
748 ParseError::CantAddOverlayHelp(_, s) => *s,
749 ParseError::DuplicateCommandDef(s) => *s,
750 ParseError::UnknownCommand(s) => *s,
751 ParseError::NonUtf8(s) => *s,
752 ParseError::UnknownFlag(_, _, s, _) => *s,
753 ParseError::RequiredAfterOptional(_, s) => *s,
754 ParseError::UnknownType(s) => *s,
755 ParseError::MissingFlagParam(_, s) => *s,
756 ParseError::OnlyLastFlagInBatchCanTakeArg(s) => *s,
757 ParseError::MissingPositional(_, s, _) => *s,
758 ParseError::KeywordMissingArgument(_, _, s) => *s,
759 ParseError::MissingType(s) => *s,
760 ParseError::TypeMismatch(_, _, s) => *s,
761 ParseError::TypeMismatchHelp(_, _, s, _) => *s,
762 ParseError::InputMismatch(_, s) => *s,
763 ParseError::OutputMismatch(_, _, s) => *s,
764 ParseError::MissingRequiredFlag(_, s) => *s,
765 ParseError::IncompleteMathExpression(s) => *s,
766 ParseError::UnknownState(_, s) => *s,
767 ParseError::InternalError(_, s) => *s,
768 ParseError::IncompleteParser(s) => *s,
769 ParseError::RestNeedsName(s) => *s,
770 ParseError::ParameterMismatchType(_, _, _, s) => *s,
771 ParseError::NonConstantDefaultValue(s) => *s,
772 ParseError::ExtraColumns(_, s) => *s,
773 ParseError::MissingColumns(_, s) => *s,
774 ParseError::AssignmentMismatch(_, _, s) => *s,
775 ParseError::WrongImportPattern(_, s) => *s,
776 ParseError::ExportNotFound(s) => *s,
777 ParseError::SourcedFileNotFound(_, s) => *s,
778 ParseError::ScriptFileTooLarge { span, .. } => *span,
779 ParseError::ScriptFileNotText { span, .. } => *span,
780 ParseError::RegisteredFileNotFound(_, s) => *s,
781 ParseError::FileNotFound(_, s) => *s,
782 ParseError::PluginNotFound { name_span, .. } => *name_span,
783 ParseError::LabeledError(_, _, s) => *s,
784 ParseError::ShellAndAnd(s) => *s,
785 ParseError::ShellOrOr(s) => *s,
786 ParseError::ShellErrRedirect(s) => *s,
787 ParseError::ShellOutErrRedirect(s) => *s,
788 ParseError::MultipleRedirections(_, _, s) => *s,
789 ParseError::UnexpectedRedirection { span } => *span,
790 ParseError::UnknownOperator(_, _, s) => *s,
791 ParseError::InvalidLiteral(_, _, s) => *s,
792 ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
793 ParseError::RedirectingBuiltinCommand(_, s, _) => *s,
794 ParseError::UnexpectedSpreadArg(_, s) => *s,
795 ParseError::ExtraTokensAfterClosingDelimiter(s) => *s,
796 ParseError::AssignmentRequiresVar(s) => *s,
797 ParseError::AssignmentRequiresMutableVar(s) => *s,
798 ParseError::AttributeRequiresDefinition(s) => *s,
799 ParseError::KeywordShadowModuleMain(_, s) => *s,
800 }
801 }
802}
803
804fn default_unclosed_help(delimiter: &str, structure_hint: Option<&str>) -> String {
805 match structure_hint {
806 Some(hint) if !hint.is_empty() => format!(
807 "Add a matching `{delimiter}` to close {hint}, or check that an earlier closer closed the wrong block."
808 ),
809 _ => format!(
810 "Add a matching `{delimiter}` to close this delimiter, or check that an earlier closer closed the wrong block."
811 ),
812 }
813}
814
815fn default_unbalanced_help(open: &str, close: &str) -> String {
816 format!(
817 "Remove this `{close}` if it is extra, or add a matching `{open}` earlier. If you closed a block too early, the real mistake may be above."
818 )
819}
820
821#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
822pub struct DidYouMean(Option<String>);
823
824fn did_you_mean_impl(possibilities_bytes: &[&[u8]], input_bytes: &[u8]) -> Option<String> {
825 let input = from_utf8(input_bytes).ok()?;
826 let possibilities = possibilities_bytes
827 .iter()
828 .map(|p| from_utf8(p))
829 .collect::<Result<Vec<&str>, Utf8Error>>()
830 .ok()?;
831 did_you_mean(&possibilities, input)
832}
833impl DidYouMean {
834 pub fn new(possibilities_bytes: &[&[u8]], input_bytes: &[u8]) -> DidYouMean {
835 DidYouMean(did_you_mean_impl(possibilities_bytes, input_bytes))
836 }
837}
838
839impl From<Option<String>> for DidYouMean {
840 fn from(value: Option<String>) -> Self {
841 Self(value)
842 }
843}
844
845impl Display for DidYouMean {
846 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
847 if let Some(suggestion) = &self.0 {
848 write!(f, "Did you mean '{suggestion}'?")
849 } else {
850 write!(f, "")
851 }
852 }
853}