litcheck_filecheck/pattern/matcher/matchers/
substring.rs1use aho_corasick::{AhoCorasick, AhoCorasickBuilder, AhoCorasickKind, MatchKind, StartKind};
2
3use crate::common::*;
4
5pub struct SubstringMatcher<'a> {
8 span: SourceSpan,
11 pattern: Span<Cow<'a, str>>,
13 searcher: AhoCorasick,
15}
16impl<'a> fmt::Debug for SubstringMatcher<'a> {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 f.debug_struct("SubstringMatcher")
19 .field("pattern", &self.pattern)
20 .field("kind", &self.searcher.kind())
21 .field("start_kind", &self.searcher.start_kind())
22 .field("match_kind", &self.searcher.match_kind())
23 .finish()
24 }
25}
26impl<'a> SubstringMatcher<'a> {
27 pub fn new(pattern: Span<Cow<'a, str>>, config: &Config) -> DiagResult<Self> {
29 Self::new_with_start_kind(pattern, StartKind::Unanchored, config)
30 }
31
32 pub fn new_with_start_kind(
33 pattern: Span<Cow<'a, str>>,
34 start_kind: StartKind,
35 config: &Config,
36 ) -> DiagResult<Self> {
37 assert!(
38 !pattern.is_empty(),
39 "an empty string is not a valid substring pattern"
40 );
41
42 let pattern = pattern
43 .map(|p| text::canonicalize_horizontal_whitespace(p, config.options.strict_whitespace));
44
45 let (span, pattern) = pattern.into_parts();
46 let mut builder = AhoCorasickBuilder::new();
47 let searcher = builder
48 .match_kind(MatchKind::LeftmostLongest)
49 .start_kind(start_kind)
50 .kind(Some(AhoCorasickKind::DFA))
51 .ascii_case_insensitive(config.options.ignore_case)
52 .build([pattern.as_ref()])
53 .map_err(|err| {
54 let diag = Diag::new("failed to build aho-corasick searcher")
55 .with_help("this pattern was constructed as a DFA, with leftmost-longest match semantics, \
56 it is possible a less restrictive configuration would succeed")
57 .and_label(Label::new(span, err.to_string()));
58 Report::from(diag)
59 })?;
60 Ok(Self {
61 span,
62 pattern: Span::new(span, pattern),
63 searcher,
64 })
65 }
66
67 pub fn pattern(&self) -> &str {
68 self.pattern.inner().as_ref()
69 }
70}
71impl<'a> MatcherMut for SubstringMatcher<'a> {
72 fn try_match_mut<'input, 'context, C>(
73 &self,
74 input: Input<'input>,
75 context: &mut C,
76 ) -> DiagResult<MatchResult<'input>>
77 where
78 C: Context<'input, 'context> + ?Sized,
79 {
80 self.try_match(input, context)
81 }
82}
83impl<'a> Matcher for SubstringMatcher<'a> {
84 fn try_match<'input, 'context, C>(
85 &self,
86 input: Input<'input>,
87 context: &C,
88 ) -> DiagResult<MatchResult<'input>>
89 where
90 C: Context<'input, 'context> + ?Sized,
91 {
92 if let Some(matched) = self.searcher.find(input) {
93 let span = SourceSpan::from_range_unchecked(input.source_id(), matched.range());
94 Ok(MatchResult::ok(MatchInfo::new(span, self.span)))
95 } else {
96 Ok(MatchResult::failed(
97 CheckFailedError::MatchNoneButExpected {
98 span: self.span,
99 match_file: context.source_file(self.span.source_id()).unwrap(),
100 note: None,
101 },
102 ))
103 }
104 }
105}
106impl<'a> Spanned for SubstringMatcher<'a> {
107 fn span(&self) -> SourceSpan {
108 self.span
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use crate::source_file;
115
116 use super::*;
117
118 #[test]
119 fn test_substring_matcher() -> DiagResult<()> {
120 let mut context = TestContext::new();
121 let match_file = source_file!(context.config, "CHECK: Name: bar");
122 let input_file = source_file!(
123 context.config,
124 "
125Name: foo
126Field: 1
127
128Name: bar
129Field: 2
130"
131 .trim_start()
132 );
133 context
134 .with_checks(match_file)
135 .with_input(input_file.clone());
136
137 let pattern = Span::new(
138 SourceSpan::from_range_unchecked(input_file.id(), 8..10),
139 Cow::Borrowed("Name: bar"),
140 );
141 let matcher =
142 SubstringMatcher::new(pattern, &context.config).expect("expected pattern to be valid");
143 let mctx = context.match_context();
144 let input = mctx.search();
145 let result = matcher.try_match(input, &mctx)?;
146 let info = result.info.expect("expected match");
147 assert_eq!(info.span.start().to_u32(), 20);
148 assert_eq!(info.span.len(), 9);
149 assert_eq!(input.as_str(info.span.into_slice_index()), "Name: bar");
150
151 Ok(())
152 }
153}