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

zbraniecki / icu4x / 11904027177

19 Nov 2024 12:33AM UTC coverage: 75.477% (+0.3%) from 75.174%
11904027177

push

github

web-flow
Move DateTimePattern into pattern module (#5834)

#1317

Also removes `NeoNeverMarker` and fixes #5689

258 of 319 new or added lines in 6 files covered. (80.88%)

6967 existing lines in 278 files now uncovered.

54522 of 72237 relevant lines covered (75.48%)

655305.49 hits per line

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

95.65
/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::borrow::Cow;
9
#[cfg(feature = "datagen")]
10
use icu_provider::DataError;
11
use writeable::{LengthHint, Writeable};
12

13
impl ListFormatterPatternsV2<'_> {
14
    /// Creates a new [`ListFormatterPatternsV2`] from the given patterns. Fails if any pattern is invalid.
15
    #[cfg(feature = "datagen")]
16
    pub fn try_new(start: &str, middle: &str, end: &str, pair: &str) -> Result<Self, DataError> {
173✔
17
        let err = DataError::custom("Invalid list pattern");
173✔
18
        Ok(Self {
173✔
19
            start: ListJoinerPattern::try_from_str(start, true, false)?,
173✔
20
            middle: middle
173✔
21
                .strip_prefix("{0}")
UNCOV
22
                .ok_or(err)?
×
23
                .strip_suffix("{1}")
UNCOV
24
                .ok_or(err)?
×
25
                .to_string()
26
                .into(),
27
            end: ListJoinerPattern::try_from_str(end, false, true)?.into(),
173✔
28
            pair: if end != pair {
212✔
29
                Some(ListJoinerPattern::try_from_str(pair, true, true)?.into())
39✔
30
            } else {
31
                None
134✔
32
            },
33
        })
×
34
    }
173✔
35

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

51
type PatternParts<'a> = (&'a str, &'a str, &'a str);
52

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

68
    /// The expected length of this pattern
69
    fn size_hint(&'a self) -> LengthHint {
71✔
70
        let mut hint = self.default.size_hint();
71✔
71
        if let Some(special_case) = &self.special_case {
71✔
72
            hint |= special_case.pattern.size_hint()
49✔
73
        }
74
        hint
71✔
75
    }
71✔
76
}
77

78
impl<'data> ListJoinerPattern<'data> {
79
    #[cfg(feature = "datagen")]
80
    /// TODO
81
    pub fn try_from_str(
421✔
82
        pattern: &str,
83
        allow_prefix: bool,
84
        allow_suffix: bool,
85
    ) -> Result<Self, DataError> {
86
        match (pattern.find("{0}"), pattern.find("{1}")) {
421✔
87
            (Some(index_0), Some(index_1))
831✔
88
                if index_0 < index_1
417✔
89
                    && (allow_prefix || index_0 == 0)
416✔
90
                    && (allow_suffix || index_1 == pattern.len() - 3) =>
414✔
91
            {
92
                if (index_0 > 0 && !cfg!(test)) || index_1 - 3 >= 256 {
414✔
UNCOV
93
                    return Err(DataError::custom(
×
94
                        "Found valid pattern that cannot be stored in ListFormatterPatternsV2",
95
                    )
96
                    .with_debug_context(pattern));
97
                }
98
                #[allow(clippy::indexing_slicing)] // find
99
                Ok(ListJoinerPattern {
414✔
100
                    string: Cow::Owned(alloc::format!(
1,242✔
101
                        "{}{}{}",
102
                        &pattern[0..index_0],
414✔
103
                        &pattern[index_0 + 3..index_1],
414✔
104
                        &pattern[index_1 + 3..]
414✔
105
                    )),
106
                    index_0: index_0 as u8,
414✔
107
                    index_1: (index_1 - 3) as u8,
414✔
108
                })
109
            }
414✔
110
            _ => Err(DataError::custom("Invalid list pattern").with_debug_context(pattern)),
7✔
111
        }
112
    }
421✔
113

114
    pub(crate) fn parts(&'data self) -> PatternParts<'data> {
135✔
115
        #![allow(clippy::indexing_slicing)] // by invariant
116
        let index_0 = self.index_0 as usize;
135✔
117
        let index_1 = self.index_1 as usize;
135✔
118
        (
135✔
119
            &self.string[0..index_0],
135✔
120
            &self.string[index_0..index_1],
135✔
121
            &self.string[index_1..],
135✔
122
        )
123
    }
135✔
124

125
    fn size_hint(&self) -> LengthHint {
134✔
126
        LengthHint::exact(self.string.len())
134✔
127
    }
134✔
128
}
129

130
#[cfg(feature = "datagen")]
131
impl<'data> From<ListJoinerPattern<'data>> for ConditionalListJoinerPattern<'data> {
132
    fn from(default: ListJoinerPattern<'data>) -> Self {
212✔
133
        Self {
212✔
134
            default,
135
            special_case: None,
212✔
136
        }
137
    }
212✔
138
}
139

140
#[cfg(all(test, feature = "datagen"))]
141
pub mod test {
142
    use super::*;
143

144
    pub fn test_patterns_general() -> ListFormatterPatternsV2<'static> {
4✔
145
        ListFormatterPatternsV2::try_new("@{0}:{1}", "{0},{1}", "{0}.{1}!", "${0};{1}+").unwrap()
4✔
146
    }
4✔
147

148
    pub fn test_patterns_lengths() -> ListFormatterPatternsV2<'static> {
1✔
149
        ListFormatterPatternsV2::try_new("{0}1{1}", "{0}12{1}", "{0}12{1}34", "{0}123{1}456")
1✔
150
            .unwrap()
151
    }
1✔
152

153
    pub fn test_patterns_conditional() -> ListFormatterPatternsV2<'static> {
6✔
154
        let mut patterns =
155
            ListFormatterPatternsV2::try_new("{0}: {1}", "{0}, {1}", "{0}. {1}", "{0}. {1}")
6✔
156
                .unwrap();
157
        patterns.end.special_case = Some(SpecialCasePattern {
6✔
158
            condition: SerdeDFA::new(Cow::Borrowed("^a")).unwrap(),
6✔
159
            pattern: ListJoinerPattern::try_from_str("{0} :o {1}", false, false).unwrap(),
6✔
UNCOV
160
        });
×
161
        patterns
6✔
162
    }
6✔
163

164
    #[test]
165
    fn rejects_bad_patterns() {
2✔
166
        assert!(ListJoinerPattern::try_from_str("{0} and", true, true).is_err());
1✔
167
        assert!(ListJoinerPattern::try_from_str("and {1}", true, true).is_err());
1✔
168
        assert!(ListJoinerPattern::try_from_str("{1} and {0}", true, true).is_err());
1✔
169
        assert!(ListJoinerPattern::try_from_str("{1{0}}", true, true).is_err());
1✔
170
        assert!(ListJoinerPattern::try_from_str("{0\u{202e}} and {1}", true, true).is_err());
1✔
171
        assert!(ListJoinerPattern::try_from_str("{{0}} {{1}}", true, true).is_ok());
1✔
172

173
        assert!(ListJoinerPattern::try_from_str("{0} and {1} ", true, true).is_ok());
1✔
174
        assert!(ListJoinerPattern::try_from_str("{0} and {1} ", true, false).is_err());
1✔
175
        assert!(ListJoinerPattern::try_from_str(" {0} and {1}", true, true).is_ok());
1✔
176
        assert!(ListJoinerPattern::try_from_str(" {0} and {1}", false, true).is_err());
1✔
177
    }
2✔
178

179
    #[test]
180
    fn produces_correct_parts() {
2✔
181
        assert_eq!(
2✔
182
            test_patterns_general().pair.unwrap().parts(""),
1✔
183
            ("$", ";", "+")
184
        );
185
    }
2✔
186

187
    #[test]
188
    fn produces_correct_parts_conditionally() {
2✔
189
        assert_eq!(test_patterns_conditional().end.parts("a"), ("", " :o ", ""));
2✔
190
        assert_eq!(
2✔
191
            test_patterns_conditional().end.parts("ab"),
1✔
192
            ("", " :o ", "")
193
        );
194
        assert_eq!(test_patterns_conditional().end.parts("b"), ("", ". ", ""));
2✔
195
        assert_eq!(test_patterns_conditional().end.parts("ba"), ("", ". ", ""));
2✔
196
    }
2✔
197

198
    #[test]
199
    fn size_hint_works() {
2✔
200
        let pattern = test_patterns_lengths();
1✔
201

202
        assert_eq!(pattern.length_hint(0), LengthHint::exact(0));
1✔
203
        assert_eq!(pattern.length_hint(1), LengthHint::exact(0));
1✔
204

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

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

211
        let pattern = test_patterns_conditional();
1✔
212

213
        // patterns "{0}: {1}", "{0}, {1}" (x197), and "{0} :o {1}" or "{0}. {1}"
214
        assert_eq!(
1✔
215
            pattern.length_hint(200),
1✔
216
            LengthHint::exact(2 + 197 * 2) + LengthHint::between(2, 4)
1✔
217
        );
218
    }
2✔
219
}
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