dte test coverage


Directory: ./
File: src/editorconfig/match.c
Date: 2025-05-08 15:05:54
Exec Total Coverage
Lines: 119 119 100.0%
Functions: 4 4 100.0%
Branches: 66 72 91.7%

Line Branch Exec Source
1 #include <regex.h>
2 #include <stdlib.h>
3 #include "match.h"
4 #include "util/ascii.h"
5 #include "util/debug.h"
6 #include "util/string.h"
7 #include "util/xstring.h"
8
9 58 static size_t get_last_paired_brace_index(const char *str, size_t len)
10 {
11 58 size_t last_paired_index = 0;
12 58 size_t open_braces = 0;
13
2/2
✓ Branch 0 (9→3) taken 973 times.
✓ Branch 1 (9→10) taken 58 times.
1031 for (size_t i = 0; i < len; i++) {
14 973 const char ch = str[i];
15
4/4
✓ Branch 0 (3→4) taken 8 times.
✓ Branch 1 (3→5) taken 48 times.
✓ Branch 2 (3→6) taken 49 times.
✓ Branch 3 (3→8) taken 868 times.
973 switch (ch) {
16 8 case '\\':
17 8 i++;
18 8 break;
19 48 case '{':
20 48 open_braces++;
21
1/2
✓ Branch 0 (5→8) taken 48 times.
✗ Branch 1 (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 0 (6→7) taken 47 times.
✓ Branch 1 (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 0 (10→11) taken 1 times.
✓ Branch 1 (10→12) taken 57 times.
58 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 0 (4→5) taken 1 times.
✓ Branch 1 (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 0 (9→8) taken 54 times.
✓ Branch 1 (9→10) taken 3 times.
57 while (i < len) {
54 54 const char ch = pat[i++];
55
2/2
✓ Branch 0 (8→9) taken 42 times.
✓ Branch 1 (8→10) taken 12 times.
54 if (ch == ']') {
56 closed = true;
57 break;
58 }
59 }
60
61
2/2
✓ Branch 0 (10→11) taken 3 times.
✓ Branch 1 (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 string_append_byte(buf, '[');
71
2/2
✓ Branch 0 (14→15) taken 2 times.
✓ Branch 1 (14→17) taken 10 times.
12 if (pat[0] == '!') {
72 2 string_append_byte(buf, '^');
73 2 string_append_buf(buf, pat + 1, i - 1);
74 } else {
75 10 string_append_buf(buf, pat, i);
76 }
77 return i;
78 }
79
80 // Skips past empty alternates in brace groups and returns the number
81 // of bytes (commas) skipped
82 88 static size_t skip_empty_alternates(const char *str, size_t len)
83 {
84 88 size_t i = 1;
85
3/4
✓ Branch 0 (4→5) taken 102 times.
✗ Branch 1 (4→6) not taken.
✓ Branch 2 (5→3) taken 14 times.
✓ Branch 3 (5→6) taken 88 times.
102 while (i < len && str[i] == ',') {
86 14 i++;
87 }
88 88 return i - 1;
89 }
90
91 58 bool ec_pattern_match(const char *pattern, size_t pattern_len, const char *path)
92 {
93 58 String buf = string_new(pattern_len * 2);
94 58 size_t brace_level = 0;
95 58 size_t last_paired_brace_index = get_last_paired_brace_index(pattern, pattern_len);
96 58 bool brace_group_has_empty_alternate[32] = {false};
97
98
2/2
✓ Branch 0 (52→5) taken 901 times.
✓ Branch 1 (52→53) taken 58 times.
959 for (size_t i = 0; i < pattern_len; i++) {
99 901 char ch = pattern[i];
100
10/10
✓ Branch 0 (5→6) taken 8 times.
✓ Branch 1 (5→11) taken 4 times.
✓ Branch 2 (5→13) taken 45 times.
✓ Branch 3 (5→18) taken 16 times.
✓ Branch 4 (5→20) taken 48 times.
✓ Branch 5 (5→29) taken 48 times.
✓ Branch 6 (5→35) taken 45 times.
✓ Branch 7 (5→43) taken 61 times.
✓ Branch 8 (5→49) taken 53 times.
✓ Branch 9 (5→50) taken 573 times.
901 switch (ch) {
101 8 case '\\':
102
2/2
✓ Branch 0 (6→7) taken 6 times.
✓ Branch 1 (6→10) taken 2 times.
8 if (i + 1 < pattern_len) {
103 6 ch = pattern[++i];
104
1/2
✓ Branch 0 (7→8) taken 6 times.
✗ Branch 1 (7→9) not taken.
6 if (is_regex_special_char(ch)) {
105 6 string_append_byte(&buf, '\\');
106 }
107 6 string_append_byte(&buf, ch);
108 } else {
109 2 string_append_literal(&buf, "\\\\");
110 }
111 break;
112 4 case '?':
113 4 string_append_literal(&buf, "[^/]");
114 4 break;
115 45 case '*':
116
4/4
✓ Branch 0 (13→14) taken 42 times.
✓ Branch 1 (13→17) taken 3 times.
✓ Branch 2 (14→15) taken 10 times.
✓ Branch 3 (14→17) taken 32 times.
45 if (i + 1 < pattern_len && pattern[i + 1] == '*') {
117 10 string_append_literal(&buf, ".*");
118 10 i++;
119 } else {
120 35 string_append_literal(&buf, "[^/]*");
121 }
122 break;
123 16 case '[':
124 // The entire bracket expression is handled in a separate
125 // loop because the POSIX regex escaping rules are different
126 // in that context
127 16 i += handle_bracket_expression(pattern + i, pattern_len - i, &buf);
128 16 break;
129 48 case '{': {
130
2/2
✓ Branch 0 (20→21) taken 3 times.
✓ Branch 1 (20→23) taken 45 times.
48 if (i >= last_paired_brace_index) {
131 3 string_append_literal(&buf, "\\{");
132 3 break;
133 }
134 45 brace_level++;
135 45 size_t skip = skip_empty_alternates(pattern + i, pattern_len - i);
136
2/2
✓ Branch 0 (23→24) taken 3 times.
✓ Branch 1 (23→25) taken 42 times.
45 if (skip > 0) {
137 3 i += skip;
138 3 brace_group_has_empty_alternate[brace_level] = true;
139 }
140
3/4
✓ Branch 0 (25→26) taken 45 times.
✗ Branch 1 (25→28) not taken.
✓ Branch 2 (26→27) taken 1 times.
✓ Branch 3 (26→28) taken 44 times.
45 if (i + 1 < pattern_len && pattern[i + 1] == '}') {
141 // If brace group contains only empty alternates, emit nothing
142 1 brace_group_has_empty_alternate[brace_level] = false;
143 1 i++;
144 1 brace_level--;
145 } else {
146 44 string_append_byte(&buf, '(');
147 }
148 break;
149 }
150 48 case '}':
151
2/2
✓ Branch 0 (29→30) taken 4 times.
✓ Branch 1 (29→31) taken 44 times.
48 if (i > last_paired_brace_index || brace_level == 0) {
152 4 goto append_byte;
153 }
154 44 string_append_byte(&buf, ')');
155
2/2
✓ Branch 0 (32→33) taken 9 times.
✓ Branch 1 (32→34) taken 35 times.
44 if (brace_group_has_empty_alternate[brace_level]) {
156 9 string_append_byte(&buf, '?');
157 }
158 44 brace_group_has_empty_alternate[brace_level] = false;
159 44 brace_level--;
160 44 break;
161 45 case ',': {
162
2/2
✓ Branch 0 (35→36) taken 2 times.
✓ Branch 1 (35→37) taken 43 times.
45 if (i >= last_paired_brace_index || brace_level == 0) {
163 2 goto append_byte;
164 }
165 43 size_t skip = skip_empty_alternates(pattern + i, pattern_len - i);
166
2/2
✓ Branch 0 (37→38) taken 8 times.
✓ Branch 1 (37→39) taken 35 times.
43 if (skip > 0) {
167 8 i += skip;
168 8 brace_group_has_empty_alternate[brace_level] = true;
169 }
170
3/4
✓ Branch 0 (39→40) taken 43 times.
✗ Branch 1 (39→42) not taken.
✓ Branch 2 (40→41) taken 7 times.
✓ Branch 3 (40→42) taken 36 times.
43 if (i + 1 < pattern_len && pattern[i + 1] == '}') {
171 7 brace_group_has_empty_alternate[brace_level] = true;
172 } else {
173 36 string_append_byte(&buf, '|');
174 }
175 break;
176 }
177 61 case '/':
178
4/4
✓ Branch 0 (43→44) taken 59 times.
✓ Branch 1 (43→48) taken 2 times.
✓ Branch 2 (45→46) taken 1 times.
✓ Branch 3 (45→48) taken 58 times.
61 if (i + 3 < pattern_len && mem_equal(pattern + i, "/**/", 4)) {
179 1 string_append_literal(&buf, "(/|/.*/)");
180 1 i += 3;
181 1 break;
182 }
183 60 goto append_byte;
184 53 case '.':
185 case '(':
186 case ')':
187 case '|':
188 case '+':
189 53 string_append_byte(&buf, '\\');
190 // Fallthrough
191 default:
192 692 append_byte:
193 692 string_append_byte(&buf, ch);
194 }
195 }
196
197 58 string_append_byte(&buf, '$');
198 58 char *regex_pattern = string_steal_cstring(&buf);
199
200 58 regex_t re;
201 58 bool compiled = !regcomp(&re, regex_pattern, REG_EXTENDED | REG_NOSUB);
202 58 free(regex_pattern);
203
1/2
✓ Branch 0 (56→57) taken 58 times.
✗ Branch 1 (56→60) not taken.
58 if (!compiled) {
204 return false;
205 }
206
207 58 int res = regexec(&re, path, 0, NULL, 0);
208 58 regfree(&re);
209 58 return res == 0;
210 }
211