• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 13958601093

19 Mar 2025 04:17PM UTC coverage: 74.164% (-1.5%) from 75.71%
13958601093

push

github

web-flow
Clean up properties docs (#6315)

58056 of 78281 relevant lines covered (74.16%)

819371.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.73
/components/list/src/patterns.rs
1
// This file is part of ICU4X. For terms of use, please see the file
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4

5
use crate::lazy_automaton::LazyAutomaton;
6
use crate::provider::*;
7
#[cfg(feature = "datagen")]
8
use alloc::string::ToString;
9
#[cfg(feature = "datagen")]
10
use icu_provider::DataError;
11
use writeable::{LengthHint, Writeable};
12
#[cfg(feature = "datagen")]
13
use zerovec::VarZeroCow;
14

15
impl ListFormatterPatterns<'_> {
16
    /// Creates a new [`ListFormatterPatterns`] from the given patterns. Fails if any pattern is invalid.
17
    #[cfg(feature = "datagen")]
18
    pub fn try_new(start: &str, middle: &str, end: &str, pair: &str) -> Result<Self, DataError> {
173✔
19
        use zerovec::VarZeroCow;
20

21
        let err = DataError::custom("Invalid list pattern");
173✔
22
        Ok(Self {
173✔
23
            start: ListJoinerPattern::try_from_str(start, true, false)?,
173✔
24
            middle: VarZeroCow::new_owned(
173✔
25
                middle
173✔
26
                    .strip_prefix("{0}")
27
                    .ok_or(err)?
×
28
                    .strip_suffix("{1}")
29
                    .ok_or(err)?
×
30
                    .to_string()
31
                    .into_boxed_str(),
32
            ),
33
            end: ListJoinerPattern::try_from_str(end, false, true)?.into(),
173✔
34
            pair: if end != pair {
212✔
35
                Some(ListJoinerPattern::try_from_str(pair, true, true)?.into())
39✔
36
            } else {
37
                None
134✔
38
            },
39
        })
×
40
    }
173✔
41

42
    /// The range of the number of bytes required by the list literals to join a
43
    /// list of length `len`. If none of the patterns are conditional, this is exact.
44
    pub(crate) fn length_hint(&self, len: usize) -> LengthHint {
77✔
45
        match len {
77✔
46
            0 | 1 => LengthHint::exact(0),
6✔
47
            2 => self.pair.as_ref().unwrap_or(&self.end).size_hint(),
57✔
48
            n => {
49
                self.start.size_hint()
42✔
50
                    + self.middle.writeable_length_hint() * (n - 3)
14✔
51
                    + self.end.size_hint()
14✔
52
            }
53
        }
54
    }
77✔
55
}
56

57
type PatternParts<'a> = (&'a str, &'a str, &'a str);
58

59
impl<'a> ConditionalListJoinerPattern<'a> {
60
    pub(crate) fn parts<'b, W: Writeable + ?Sized>(
98✔
61
        &'a self,
62
        following_value: &'b W,
63
    ) -> PatternParts<'a> {
64
        match &self.special_case {
98✔
65
            Some(SpecialCasePattern { condition, pattern })
71✔
66
                if condition.deref().matches_earliest_fwd_lazy(following_value) =>
71✔
67
            {
68
                pattern.parts()
51✔
69
            }
70
            _ => self.default.parts(),
47✔
71
        }
72
    }
98✔
73

74
    /// The expected length of this pattern
75
    fn size_hint(&'a self) -> LengthHint {
71✔
76
        let mut hint = self.default.size_hint();
71✔
77
        if let Some(special_case) = &self.special_case {
71✔
78
            hint |= special_case.pattern.size_hint()
49✔
79
        }
80
        hint
71✔
81
    }
71✔
82
}
83

84
impl<'data> ListJoinerPattern<'data> {
85
    #[cfg(feature = "datagen")]
86
    /// Parses a [`ListJoinerPattern`] from a string containing the "{0}" and "{1}" placeholders.
87
    pub fn try_from_str(
421✔
88
        pattern: &str,
89
        allow_prefix: bool,
90
        allow_suffix: bool,
91
    ) -> Result<Self, DataError> {
92
        match (pattern.find("{0}"), pattern.find("{1}")) {
421✔
93
            (Some(index_0), Some(index_1))
831✔
94
                if index_0 < index_1
417✔
95
                    && (allow_prefix || index_0 == 0)
416✔
96
                    && (allow_suffix || index_1 == pattern.len() - 3) =>
414✔
97
            {
98
                if (index_0 > 0 && !cfg!(test)) || index_1 - 3 >= 256 {
414✔
99
                    return Err(DataError::custom(
×
100
                        "Found valid pattern that cannot be stored in ListFormatterPatterns",
101
                    )
102
                    .with_debug_context(pattern));
103
                }
104
                #[allow(clippy::indexing_slicing)] // find
105
                Ok(ListJoinerPattern {
414✔
106
                    string: VarZeroCow::new_owned(
414✔
107
                        alloc::format!(
1,242✔
108
                            "{}{}{}",
109
                            &pattern[0..index_0],
414✔
110
                            &pattern[index_0 + 3..index_1],
414✔
111
                            &pattern[index_1 + 3..]
414✔
112
                        )
113
                        .into_boxed_str(),
114
                    ),
115
                    index_0: index_0 as u8,
414✔
116
                    index_1: (index_1 - 3) as u8,
414✔
117
                })
118
            }
414✔
119
            _ => Err(DataError::custom("Invalid list pattern").with_debug_context(pattern)),
7✔
120
        }
121
    }
421✔
122

123
    pub(crate) fn parts(&'data self) -> PatternParts<'data> {
135✔
124
        #![allow(clippy::indexing_slicing)] // by invariant
125
        let index_0 = self.index_0 as usize;
135✔
126
        let index_1 = self.index_1 as usize;
135✔
127
        (
135✔
128
            &self.string[0..index_0],
135✔
129
            &self.string[index_0..index_1],
135✔
130
            &self.string[index_1..],
135✔
131
        )
132
    }
135✔
133

134
    fn size_hint(&self) -> LengthHint {
134✔
135
        LengthHint::exact(self.string.len())
134✔
136
    }
134✔
137
}
138

139
#[cfg(feature = "datagen")]
140
impl<'data> From<ListJoinerPattern<'data>> for ConditionalListJoinerPattern<'data> {
141
    fn from(default: ListJoinerPattern<'data>) -> Self {
212✔
142
        Self {
212✔
143
            default,
144
            special_case: None,
212✔
145
        }
146
    }
212✔
147
}
148

149
#[cfg(all(test, feature = "datagen"))]
150
pub mod test {
151
    use super::*;
152
    use std::borrow::Cow;
153

154
    pub fn test_patterns_general() -> ListFormatterPatterns<'static> {
4✔
155
        ListFormatterPatterns::try_new("@{0}:{1}", "{0},{1}", "{0}.{1}!", "${0};{1}+").unwrap()
4✔
156
    }
4✔
157

158
    pub fn test_patterns_lengths() -> ListFormatterPatterns<'static> {
1✔
159
        ListFormatterPatterns::try_new("{0}1{1}", "{0}12{1}", "{0}12{1}34", "{0}123{1}456").unwrap()
1✔
160
    }
1✔
161

162
    pub fn test_patterns_conditional() -> ListFormatterPatterns<'static> {
6✔
163
        let mut patterns =
164
            ListFormatterPatterns::try_new("{0}: {1}", "{0}, {1}", "{0}. {1}", "{0}. {1}").unwrap();
6✔
165
        patterns.end.special_case = Some(SpecialCasePattern {
6✔
166
            condition: SerdeDFA::new(Cow::Borrowed("^a")).unwrap(),
6✔
167
            pattern: ListJoinerPattern::try_from_str("{0} :o {1}", false, false).unwrap(),
6✔
168
        });
×
169
        patterns
6✔
170
    }
6✔
171

172
    #[test]
173
    fn rejects_bad_patterns() {
2✔
174
        assert!(ListJoinerPattern::try_from_str("{0} and", true, true).is_err());
1✔
175
        assert!(ListJoinerPattern::try_from_str("and {1}", true, true).is_err());
1✔
176
        assert!(ListJoinerPattern::try_from_str("{1} and {0}", true, true).is_err());
1✔
177
        assert!(ListJoinerPattern::try_from_str("{1{0}}", true, true).is_err());
1✔
178
        assert!(ListJoinerPattern::try_from_str("{0\u{202e}} and {1}", true, true).is_err());
1✔
179
        assert!(ListJoinerPattern::try_from_str("{{0}} {{1}}", true, true).is_ok());
1✔
180

181
        assert!(ListJoinerPattern::try_from_str("{0} and {1} ", true, true).is_ok());
1✔
182
        assert!(ListJoinerPattern::try_from_str("{0} and {1} ", true, false).is_err());
1✔
183
        assert!(ListJoinerPattern::try_from_str(" {0} and {1}", true, true).is_ok());
1✔
184
        assert!(ListJoinerPattern::try_from_str(" {0} and {1}", false, true).is_err());
1✔
185
    }
2✔
186

187
    #[test]
188
    fn produces_correct_parts() {
2✔
189
        assert_eq!(
2✔
190
            test_patterns_general().pair.unwrap().parts(""),
1✔
191
            ("$", ";", "+")
192
        );
193
    }
2✔
194

195
    #[test]
196
    fn produces_correct_parts_conditionally() {
2✔
197
        assert_eq!(test_patterns_conditional().end.parts("a"), ("", " :o ", ""));
2✔
198
        assert_eq!(
2✔
199
            test_patterns_conditional().end.parts("ab"),
1✔
200
            ("", " :o ", "")
201
        );
202
        assert_eq!(test_patterns_conditional().end.parts("b"), ("", ". ", ""));
2✔
203
        assert_eq!(test_patterns_conditional().end.parts("ba"), ("", ". ", ""));
2✔
204
    }
2✔
205

206
    #[test]
207
    fn size_hint_works() {
2✔
208
        let pattern = test_patterns_lengths();
1✔
209

210
        assert_eq!(pattern.length_hint(0), LengthHint::exact(0));
1✔
211
        assert_eq!(pattern.length_hint(1), LengthHint::exact(0));
1✔
212

213
        // pair pattern "{0}123{1}456"
214
        assert_eq!(pattern.length_hint(2), LengthHint::exact(6));
1✔
215

216
        // patterns "{0}1{1}", "{0}12{1}" (x197), and "{0}12{1}34"
217
        assert_eq!(pattern.length_hint(200), LengthHint::exact(1 + 2 * 197 + 4));
1✔
218

219
        let pattern = test_patterns_conditional();
1✔
220

221
        // patterns "{0}: {1}", "{0}, {1}" (x197), and "{0} :o {1}" or "{0}. {1}"
222
        assert_eq!(
1✔
223
            pattern.length_hint(200),
1✔
224
            LengthHint::exact(2 + 197 * 2) + LengthHint::between(2, 4)
1✔
225
        );
226
    }
2✔
227
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc