Repository: clap-rs/clap
Pull request #3168 (2021-12-13 19:20:42)

Ensure trailing newlines at the end of errors
Related to #2787.

I didn't update the CHANGELOG because this looks like a regression; it looks like clap3 always added a newline.


Patch:
diff --git a/clap_generate/src/generators/shells/zsh.rs b/clap_generate/src/generators/shells/zsh.rs
index c2f9fb45..db62ab08 100644
--- a/clap_generate/src/generators/shells/zsh.rs
+++ b/clap_generate/src/generators/shells/zsh.rs
@@ -396,9 +396,6 @@ fn value_completion(arg: &Arg) -> Option<String> {
                 ValueHint::Hostname => "_hosts",
                 ValueHint::Url => "_urls",
                 ValueHint::EmailAddress => "_email_addresses",
-                _ => {
-                    return None;
-                }
             }
             .to_string(),
         )
diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs
index f166795e..d9ae9fb1 100644
--- a/src/build/app/settings.rs
+++ b/src/build/app/settings.rs
@@ -23,7 +23,6 @@ impl Default for AppFlags {
 ///
 /// [`App`]: crate::App
 #[derive(Debug, PartialEq, Copy, Clone)]
-#[non_exhaustive]
 pub enum AppSettings {
     /// Try not to fail on parse errors, like missing option values.
     ///
diff --git a/src/build/arg/settings.rs b/src/build/arg/settings.rs
index d44ce378..f6d45859 100644
--- a/src/build/arg/settings.rs
+++ b/src/build/arg/settings.rs
@@ -25,7 +25,6 @@ impl Default for ArgFlags {
 /// [`Arg::unset_setting`]: crate::Arg::unset_setting()
 /// [`Arg::is_set`]: crate::Arg::is_set()
 #[derive(Debug, PartialEq, Copy, Clone)]
-#[non_exhaustive]
 pub enum ArgSettings {
     /// Specifies that an arg must be used
     Required,
diff --git a/src/build/arg/value_hint.rs b/src/build/arg/value_hint.rs
index 45109d2d..5a3bf40d 100644
--- a/src/build/arg/value_hint.rs
+++ b/src/build/arg/value_hint.rs
@@ -25,7 +25,6 @@ use std::str::FromStr;
 /// [^1]: fish completions currently only support named arguments (e.g. -o or --opt), not
 ///       positional arguments.
 #[derive(Debug, PartialEq, Copy, Clone)]
-#[non_exhaustive]
 pub enum ValueHint {
     /// Default value if hint is not specified. Follows shell default behavior, which is usually
     /// auto-completing filenames.
diff --git a/src/parse/errors.rs b/src/parse/errors.rs
index e2e3c0b4..655b8834 100644
--- a/src/parse/errors.rs
+++ b/src/parse/errors.rs
@@ -24,7 +24,6 @@ pub type Result<T> = StdResult<T, Error>;
 
 /// Command line argument parser kind of error
 #[derive(Debug, Copy, Clone, PartialEq)]
-#[non_exhaustive]
 pub enum ErrorKind {
     /// Occurs when an [`Arg`] has a set of possible values,
     /// and the user provides a value which isn't in that set.
@@ -1067,7 +1066,7 @@ impl Error {
 
         start_error(&mut c, "The argument '");
         c.warning(arg.clone());
-        c.none("' wasn't found");
+        c.none("' wasn't found\n");
 
         Self::new(c, ErrorKind::ArgumentNotFound, false).set_info(vec![arg])
     }
@@ -1134,6 +1133,8 @@ fn try_help(app: &App, c: &mut Colorizer) {
         c.none("\n\nFor more information try ");
         c.good("help");
         c.none("\n");
+    } else {
+        c.none("\n");
     }
 }
 


Test patch:
diff --git a/tests/builder/error.rs b/tests/builder/error.rs
index e9bde59c..027fb6e9 100644
--- a/tests/builder/error.rs
+++ b/tests/builder/error.rs
@@ -1,6 +1,6 @@
 use crate::utils;
 
-use clap::{App, Arg, Error, ErrorKind};
+use clap::{arg, App, Arg, Error, ErrorKind};
 
 fn compare_error(
     err: Error,
@@ -57,3 +57,39 @@ For more information try --help
     let err = app.error(expected_kind, "Failed for mysterious reasons");
     assert!(compare_error(err, expected_kind, MESSAGE, true));
 }
+
+#[test]
+fn value_validation_has_newline() {
+    let m = App::new("test")
+        .arg(arg!(<PORT>).help("Network port to use"))
+        .try_get_matches_from(["test", "foo"])
+        .unwrap();
+
+    let res = m.value_of_t::<usize>("PORT");
+
+    assert!(res.is_err());
+    let err = res.unwrap_err();
+    assert!(
+        err.to_string().ends_with('\n'),
+        "Errors should have a trailing newline, got {:?}",
+        err.to_string()
+    );
+}
+
+#[test]
+fn argument_not_found_auto_has_newline() {
+    let m = App::new("test")
+        .arg(arg!([PORT]).help("Network port to use"))
+        .try_get_matches_from(["test"])
+        .unwrap();
+
+    let res = m.value_of_t::<usize>("PORT");
+
+    assert!(res.is_err());
+    let err = res.unwrap_err();
+    assert!(
+        err.to_string().ends_with('\n'),
+        "Errors should have a trailing newline, got {:?}",
+        err.to_string()
+    );
+}
diff --git a/tests/builder/help.rs b/tests/builder/help.rs
index c613bfed..78ab8394 100644
--- a/tests/builder/help.rs
+++ b/tests/builder/help.rs
@@ -2643,6 +2643,11 @@ fn disabled_help_flag_and_subcommand() {
     let err = res.unwrap_err();
     assert_eq!(err.kind, ErrorKind::UnrecognizedSubcommand);
     assert_eq!(err.info, &["help"]);
+    assert!(
+        err.to_string().ends_with('\n'),
+        "Errors should have a trailing newline, got {:?}",
+        err.to_string()
+    );
 }
 
 #[test]