Repository: google/argh (language: rust)

Issue:
positional arguments made on field with reserved named (ref_) keep the trailing _ in help text
This kind of declaration

```rust
	#[argh(positional)]
	/// reference (tag or branch)
	pub ref_: Option<String>,
```
will generate this as help text

```
Positional Arguments:
  ref_              reference (tag or branch)
```

When using option or switch, fields are correctly renamed
positional arguments made on field with reserved named (ref_) keep the trailing _ in help text
This kind of declaration

```rust
	#[argh(positional)]
	/// reference (tag or branch)
	pub ref_: Option<String>,
```
will generate this as help text

```
Positional Arguments:
  ref_              reference (tag or branch)
```

When using option or switch, fields are correctly renamed


Discussion / hints:

Fix patch:
diff --git a/argh_derive/src/help.rs b/argh_derive/src/help.rs
index c9daad5..2121d64 100644
--- a/argh_derive/src/help.rs
+++ b/argh_derive/src/help.rs
@@ -27,7 +27,6 @@ pub(crate) fn help(
     fields: &[StructField<'_>],
     subcommand: Option<&StructField<'_>>,
 ) -> TokenStream {
-    #![allow(clippy::format_push_string)]
     let mut format_lit = "Usage: {command_name}".to_string();
 
     let positional =
@@ -154,7 +153,7 @@ fn positional_usage(out: &mut String, field: &StructField<'_>) {
     if field.attrs.greedy.is_none() {
         out.push('<');
     }
-    let name = field.arg_name();
+    let name = field.positional_arg_name();
     out.push_str(&name);
     if field.optionality == Optionality::Repeating {
         out.push_str("...");
@@ -229,7 +228,7 @@ Add a doc comment or an `#[argh(description = \"...\")]` attribute.",
 /// Describes a positional argument like this:
 ///  hello       positional argument description
 fn positional_description(out: &mut String, field: &StructField<'_>) {
-    let field_name = field.arg_name();
+    let field_name = field.positional_arg_name();
 
     let mut description = String::from("");
     if let Some(desc) = &field.attrs.description {
diff --git a/argh_derive/src/lib.rs b/argh_derive/src/lib.rs
index 49a6c17..183cb6c 100644
--- a/argh_derive/src/lib.rs
+++ b/argh_derive/src/lib.rs
@@ -196,8 +196,12 @@ impl<'a> StructField<'a> {
         Some(StructField { field, attrs, kind, optionality, ty_without_wrapper, name, long_name })
     }
 
-    pub(crate) fn arg_name(&self) -> String {
-        self.attrs.arg_name.as_ref().map(LitStr::value).unwrap_or_else(|| self.name.to_string())
+    pub(crate) fn positional_arg_name(&self) -> String {
+        self.attrs
+            .arg_name
+            .as_ref()
+            .map(LitStr::value)
+            .unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned())
     }
 }
 
@@ -530,7 +534,7 @@ fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {
                     first_use_field,
                     &format!("The short name of \"-{}\" was already used here.", short_name),
                 );
-                errors.err_span_tokens(&field.field, "Later usage here.");
+                errors.err_span_tokens(field.field, "Later usage here.");
             }
 
             seen_short_names.insert(short_name, &field.field);
@@ -542,7 +546,7 @@ fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {
                     *first_use_field,
                     &format!("The long name of \"{}\" was already used here.", long_name),
                 );
-                errors.err_span_tokens(&field.field, "Later usage here.");
+                errors.err_span_tokens(field.field, "Later usage here.");
             }
 
             seen_long_names.insert(long_name, field.field);
@@ -710,7 +714,7 @@ fn declare_local_storage_for_redacted_fields<'a>(
                     }
                 };
 
-                let arg_name = field.arg_name();
+                let arg_name = field.positional_arg_name();
                 quote! {
                     let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> =
                         argh::ParseValueSlotTy {
@@ -802,7 +806,7 @@ fn append_missing_requirements<'a>(
         match field.kind {
             FieldKind::Switch => unreachable!("switches are always optional"),
             FieldKind::Positional => {
-                let name = field.arg_name();
+                let name = field.positional_arg_name();
                 quote! {
                     if #field_name.slot.is_none() {
                         #mri.missing_positional_arg(#name)


Test patch:
diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs
index 584897c..cc6a0a8 100644
--- a/argh/tests/lib.rs
+++ b/argh/tests/lib.rs
@@ -703,6 +703,26 @@ Required options not provided:
             },
         );
     }
+
+    #[derive(FromArgs, Debug, PartialEq)]
+    /// Woot
+    struct Underscores {
+        #[argh(positional)]
+        /// fooey
+        a_: String,
+    }
+
+    #[test]
+    fn positional_name_with_underscores() {
+        assert_output(&["first"], Underscores { a_: "first".into() });
+
+        assert_error::<Underscores>(
+            &[],
+            r###"Required positional arguments not provided:
+    a
+"###,
+        );
+    }
 }
 
 /// Tests derived from