dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 136 / 0 / 136
Functions: 100.0% 6 / 0 / 6
Branches: 90.8% 69 / 6 / 82

src/editorconfig/match.c
Line Branch Exec Source
1 #include <regex.h>
2 #include <stdlib.h>
3 #include "match.h"
4 #include "regexp.h"
5 #include "util/ascii.h"
6 #include "util/debug.h"
7 #include "util/xstring.h"
8
9 66 static size_t get_last_paired_brace_index(const char *str, size_t len)
10 {
11 66 size_t last_paired_index = 0;
12 66 size_t open_braces = 0;
13
2/2
✓ Branch 9 → 3 taken 717 times.
✓ Branch 9 → 10 taken 66 times.
783 for (size_t i = 0; i < len; i++) {
14 717 const char ch = str[i];
15
4/4
✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 5 taken 48 times.
✓ Branch 3 → 6 taken 49 times.
✓ Branch 3 → 8 taken 607 times.
717 switch (ch) {
16 13 case '\\':
17 13 i++;
18 13 break;
19 48 case '{':
20 48 open_braces++;
21
1/2
✓ Branch 5 → 8 taken 48 times.
✗ Branch 5 → 12 not taken.
48 if (open_braces >= 32) {
22 // If nesting goes too deep, just return 0 and let
23 // ec_pattern_match() escape all braces
24 return 0;
25 }
26 break;
27 49 case '}':
28
2/2
✓ Branch 6 → 7 taken 47 times.
✓ Branch 6 → 8 taken 2 times.
49 if (open_braces != 0) {
29 47 last_paired_index = i;
30 47 open_braces--;
31 }
32 break;
33 }
34 }
35
36 // If there are unclosed braces, just return 0
37
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 65 times.
66 return open_braces ? 0 : last_paired_index;
38 }
39
40 16 static size_t handle_bracket_expression(const char *pat, size_t len, String *buf)
41 {
42 16 BUG_ON(len == 0);
43
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 15 times.
16 if (len == 1) {
44 1 string_append_literal(buf, "\\[");
45 1 return 0;
46 }
47
48 // Skip past opening bracket
49 15 pat++;
50
51 15 bool closed = false;
52 15 size_t i = 0;
53
2/2
✓ Branch 9 → 8 taken 54 times.
✓ Branch 9 → 10 taken 3 times.
57 while (i < len) {
54 54 const char ch = pat[i++];
55
2/2
✓ Branch 8 → 9 taken 42 times.
✓ Branch 8 → 10 taken 12 times.
54 if (ch == ']') {
56 closed = true;
57 break;
58 }
59 }
60
61
2/2
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 13 taken 12 times.
15 if (!closed) {
62 3 string_append_literal(buf, "\\[");
63 3 return 0;
64 }
65
66 // TODO: interpret characters according to editorconfig instead
67 // of just copying the bracket expression to be interpreted as
68 // regex
69
70 12 char *s = string_reserve_space(buf, i + 1);
71 12 s[0] = '[';
72 12 memcpy(s + 1, pat, i);
73
2/2
✓ Branch 14 → 15 taken 10 times.
✓ Branch 14 → 16 taken 2 times.
12 s[1] = (s[1] == '!') ? '^' : s[1]; // Replace any leading ! with ^
74 12 buf->len += i + 1;
75 12 return i;
76 }
77
78 // Skips past empty alternates in brace groups and returns the number
79 // of bytes (commas) skipped
80 88 static size_t skip_empty_alternates(const char *str, size_t len)
81 {
82 88 size_t i = 1;
83
3/4
✓ Branch 4 → 5 taken 102 times.
✗ Branch 4 → 6 not taken.
✓ Branch 5 → 3 taken 14 times.
✓ Branch 5 → 6 taken 88 times.
102 while (i < len && str[i] == ',') {
84 14 i++;
85 }
86 88 return i - 1;
87 }
88
89 66 static void section_to_regex(String *buf, StringView section)
90 {
91 66 const char *pattern = section.data;
92 66 size_t pattern_len = section.length;
93 66 size_t brace_level = 0;
94 66 size_t last_paired_brace_index = get_last_paired_brace_index(pattern, pattern_len);
95 66 bool brace_group_has_empty_alternate[32] = {false};
96
97
2/2
✓ Branch 51 → 4 taken 651 times.
✓ Branch 51 → 52 taken 66 times.
717 for (size_t i = 0; i < pattern_len; i++) {
98 651 char ch = pattern[i];
99
10/10
✓ Branch 4 → 5 taken 13 times.
✓ Branch 4 → 10 taken 4 times.
✓ Branch 4 → 12 taken 41 times.
✓ Branch 4 → 17 taken 16 times.
✓ Branch 4 → 19 taken 48 times.
✓ Branch 4 → 28 taken 48 times.
✓ Branch 4 → 34 taken 45 times.
✓ Branch 4 → 42 taken 18 times.
✓ Branch 4 → 48 taken 59 times.
✓ Branch 4 → 49 taken 359 times.
651 switch (ch) {
100 13 case '\\':
101
2/2
✓ Branch 5 → 6 taken 11 times.
✓ Branch 5 → 9 taken 2 times.
13 if (i + 1 < pattern_len) {
102 11 ch = pattern[++i];
103
1/2
✓ Branch 6 → 7 taken 11 times.
✗ Branch 6 → 8 not taken.
11 if (is_regex_special_char(ch)) {
104 11 string_append_byte(buf, '\\');
105 }
106 11 string_append_byte(buf, ch);
107 } else {
108 2 string_append_literal(buf, "\\\\");
109 }
110 break;
111 4 case '?':
112 4 string_append_literal(buf, "[^/]");
113 4 break;
114 41 case '*':
115
4/4
✓ Branch 12 → 13 taken 37 times.
✓ Branch 12 → 16 taken 4 times.
✓ Branch 13 → 14 taken 4 times.
✓ Branch 13 → 16 taken 33 times.
41 if (i + 1 < pattern_len && pattern[i + 1] == '*') {
116 4 string_append_literal(buf, ".*");
117 4 i++;
118 } else {
119 37 string_append_literal(buf, "[^/]*");
120 }
121 break;
122 16 case '[':
123 // The entire bracket expression is handled in a separate
124 // loop because the POSIX regex escaping rules are different
125 // in that context
126 16 i += handle_bracket_expression(pattern + i, pattern_len - i, buf);
127 16 break;
128 48 case '{': {
129
2/2
✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 22 taken 45 times.
48 if (i >= last_paired_brace_index) {
130 3 string_append_literal(buf, "\\{");
131 3 break;
132 }
133 45 brace_level++;
134 45 size_t skip = skip_empty_alternates(pattern + i, pattern_len - i);
135
2/2
✓ Branch 22 → 23 taken 3 times.
✓ Branch 22 → 24 taken 42 times.
45 if (skip > 0) {
136 3 i += skip;
137 3 brace_group_has_empty_alternate[brace_level] = true;
138 }
139
3/4
✓ Branch 24 → 25 taken 45 times.
✗ Branch 24 → 27 not taken.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 27 taken 44 times.
45 if (i + 1 < pattern_len && pattern[i + 1] == '}') {
140 // If brace group contains only empty alternates, emit nothing
141 1 brace_group_has_empty_alternate[brace_level] = false;
142 1 i++;
143 1 brace_level--;
144 } else {
145 44 string_append_byte(buf, '(');
146 }
147 break;
148 }
149 48 case '}':
150
2/2
✓ Branch 28 → 29 taken 4 times.
✓ Branch 28 → 30 taken 44 times.
48 if (i > last_paired_brace_index || brace_level == 0) {
151 4 goto append_byte;
152 }
153 44 string_append_byte(buf, ')');
154
2/2
✓ Branch 31 → 32 taken 9 times.
✓ Branch 31 → 33 taken 35 times.
44 if (brace_group_has_empty_alternate[brace_level]) {
155 9 string_append_byte(buf, '?');
156 }
157 44 brace_group_has_empty_alternate[brace_level] = false;
158 44 brace_level--;
159 44 break;
160 45 case ',': {
161
2/2
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 36 taken 43 times.
45 if (i >= last_paired_brace_index || brace_level == 0) {
162 2 goto append_byte;
163 }
164 43 size_t skip = skip_empty_alternates(pattern + i, pattern_len - i);
165
2/2
✓ Branch 36 → 37 taken 8 times.
✓ Branch 36 → 38 taken 35 times.
43 if (skip > 0) {
166 8 i += skip;
167 8 brace_group_has_empty_alternate[brace_level] = true;
168 }
169
3/4
✓ Branch 38 → 39 taken 43 times.
✗ Branch 38 → 41 not taken.
✓ Branch 39 → 40 taken 7 times.
✓ Branch 39 → 41 taken 36 times.
43 if (i + 1 < pattern_len && pattern[i + 1] == '}') {
170 7 brace_group_has_empty_alternate[brace_level] = true;
171 } else {
172 36 string_append_byte(buf, '|');
173 }
174 break;
175 }
176 18 case '/':
177
3/4
✓ Branch 42 → 43 taken 18 times.
✗ Branch 42 → 47 not taken.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 47 taken 17 times.
18 if (i + 3 < pattern_len && mem_equal(pattern + i, "/**/", 4)) {
178 1 string_append_literal(buf, "/(.*/)?");
179 1 i += 3;
180 1 break;
181 }
182 17 goto append_byte;
183 59 case '.':
184 case '(':
185 case ')':
186 case '|':
187 case '+':
188 59 string_append_byte(buf, '\\');
189 // Fallthrough
190 default:
191 441 append_byte:
192 441 string_append_byte(buf, ch);
193 }
194 }
195 66 }
196
197 66 String ec_pattern_to_regex(StringView section, StringView dir)
198 {
199 66 BUG_ON(section.length == 0);
200 66 BUG_ON(dir.length == 0);
201
202 66 String s = string_new((2 * dir.length) + section.length + 16);
203 66 string_append_byte(&s, '^');
204 66 string_append_escaped_regex(&s, dir);
205
206
2/2
✓ Branch 9 → 10 taken 58 times.
✓ Branch 9 → 11 taken 8 times.
66 if (!strview_memchr(section, '/')) {
207 // Section contains no slashes; insert "/(.*/)?" between `dir`
208 // and `section`, so as to match files in or below `dir`
209 58 string_append_literal(&s, "/(.*/)?");
210
2/2
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 13 taken 4 times.
8 } else if (section.data[0] != '/') {
211 // Section contains slashes, but not at the start; insert '/'
212 // between `dir` and `section`
213 4 string_append_byte(&s, '/');
214 }
215
216 66 section_to_regex(&s, section);
217 66 string_append_byte(&s, '$');
218 66 return s;
219 }
220
221 62 bool ec_pattern_match(StringView section, StringView dir, const char *path)
222 {
223 62 String re_str = ec_pattern_to_regex(section, dir);
224 62 int flags = REG_EXTENDED | REG_NOSUB;
225 62 regex_t re;
226 62 bool compiled = !regcomp(&re, string_borrow_cstring(&re_str), flags);
227 62 string_free(&re_str);
228
229
1/2
✓ Branch 6 → 7 taken 62 times.
✗ Branch 6 → 10 not taken.
62 if (!compiled) {
230 return false;
231 }
232
233 62 bool match = !regexec(&re, path, 0, NULL, 0);
234 62 regfree(&re);
235 62 return match;
236 }
237