diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 794efa5cd..c768c3e86 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -35,6 +35,7 @@ pub enum BadSequence { EmptySet2WhenNotTruncatingSet1, ClassExceptLowerUpperInSet2, ClassInSet2NotMatchedBySet1, + Set1LongerSet2EndsInClass, } impl Display for BadSequence { @@ -62,6 +63,9 @@ impl Display for BadSequence { Self::ClassInSet2NotMatchedBySet1 => { write!(f, "when translating, every 'upper'/'lower' in set2 must be matched by a 'upper'/'lower' in the same position in set1") } + Self::Set1LongerSet2EndsInClass => { + write!(f, "when translating with string1 longer than string2,\nthe latter string must not end with a character class") + } } } } @@ -259,6 +263,15 @@ impl Sequence { .filter_map(to_u8) .collect(); + if set2_solved.len() < set1_solved.len() + && !truncate_set1_flag + && matches!( + set2.last().copied(), + Some(Self::Class(Class::Upper)) | Some(Self::Class(Class::Lower)) + ) + { + return Err(BadSequence::Set1LongerSet2EndsInClass); + } //Truncation is done dead last. It has no influence on the other conversion steps if truncate_set1_flag { set1_solved.truncate(set2_solved.len()); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 6a820ed99..a68a793bc 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1374,3 +1374,15 @@ fn check_disallow_blank_in_set2_when_translating() { fn check_class_in_set2_must_be_matched_in_set1() { new_ucmd!().args(&["-t", "1[:upper:]", "[:upper:]"]).fails(); } + +#[test] +fn check_set1_longer_set2_ends_in_class() { + new_ucmd!().args(&["[:lower:]a", "[:upper:]"]).fails(); +} + +#[test] +fn check_set1_longer_set2_ends_in_class_with_trunc() { + new_ucmd!() + .args(&["-t", "[:lower:]a", "[:upper:]"]) + .succeeds(); +}