dte test coverage


Directory: ./
File: src/editorconfig/match.c
Date: 2025-06-04 06:50:24
Exec Total Coverage
Lines: 120 120 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 char *s = string_reserve_space(buf, i + 1);
71 12 s[0] = '[';
72 12 memcpy(s + 1, pat, i);
73
2/2
✓ Branch 0 (14→15) taken 10 times.
✓ Branch 1 (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 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] == ',') {
84 14 i++;
85 }
86 88 return i - 1;
87 }
88
89 58 bool ec_pattern_match(const char *pattern, size_t pattern_len, const char *path)
90 {
91 58 String buf = string_new(pattern_len * 2);
92 58 size_t brace_level = 0;
93 58 size_t last_paired_brace_index = get_last_paired_brace_index(pattern, pattern_len);
94 58 bool brace_group_has_empty_alternate[32] = {false};
95
96
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++) {
97 901 char ch = pattern[i];
98
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) {
99 8 case '\\':
100
2/2
✓ Branch 0 (6→7) taken 6 times.
✓ Branch 1 (6→10) taken 2 times.
8 if (i + 1 < pattern_len) {
101 6 ch = pattern[++i];
102
1/2
✓ Branch 0 (7→8) taken 6 times.
✗ Branch 1 (7→9) not taken.
6 if (is_regex_special_char(ch)) {
103 6 string_append_byte(&buf, '\\');
104 }
105 6 string_append_byte(&buf, ch);
106 } else {
107 2 string_append_literal(&buf, "\\\\");
108 }
109 break;
110 4 case '?':
111 4 string_append_literal(&buf, "[^/]");
112 4 break;
113 45 case '*':
114
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] == '*') {
115 10 string_append_literal(&buf, ".*");
116 10 i++;
117 } else {
118 35 string_append_literal(&buf, "[^/]*");
119 }
120 break;
121 16 case '[':
122 // The entire bracket expression is handled in a separate
123 // loop because the POSIX regex escaping rules are different
124 // in that context
125 16 i += handle_bracket_expression(pattern + i, pattern_len - i, &buf);
126 16 break;
127 48 case '{': {
128
2/2
✓ Branch 0 (20→21) taken 3 times.
✓ Branch 1 (20→23) taken 45 times.
48 if (i >= last_paired_brace_index) {
129 3 string_append_literal(&buf, "\\{");
130 3 break;
131 }
132 45 brace_level++;
133 45 size_t skip = skip_empty_alternates(pattern + i, pattern_len - i);
134
2/2
✓ Branch 0 (23→24) taken 3 times.
✓ Branch 1 (23→25) taken 42 times.
45 if (skip > 0) {
135 3 i += skip;
136 3 brace_group_has_empty_alternate[brace_level] = true;
137 }
138
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] == '}') {
139 // If brace group contains only empty alternates, emit nothing
140 1 brace_group_has_empty_alternate[brace_level] = false;
141 1 i++;
142 1 brace_level--;
143 } else {
144 44 string_append_byte(&buf, '(');
145 }
146 break;
147 }
148 48 case '}':
149
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) {
150 4 goto append_byte;
151 }
152 44 string_append_byte(&buf, ')');
153
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]) {
154 9 string_append_byte(&buf, '?');
155 }
156 44 brace_group_has_empty_alternate[brace_level] = false;
157 44 brace_level--;
158 44 break;
159 45 case ',': {
160
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) {
161 2 goto append_byte;
162 }
163 43 size_t skip = skip_empty_alternates(pattern + i, pattern_len - i);
164
2/2
✓ Branch 0 (37→38) taken 8 times.
✓ Branch 1 (37→39) taken 35 times.
43 if (skip > 0) {
165 8 i += skip;
166 8 brace_group_has_empty_alternate[brace_level] = true;
167 }
168
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] == '}') {
169 7 brace_group_has_empty_alternate[brace_level] = true;
170 } else {
171 36 string_append_byte(&buf, '|');
172 }
173 break;
174 }
175 61 case '/':
176
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)) {
177 1 string_append_literal(&buf, "(/|/.*/)");
178 1 i += 3;
179 1 break;
180 }
181 60 goto append_byte;
182 53 case '.':
183 case '(':
184 case ')':
185 case '|':
186 case '+':
187 53 string_append_byte(&buf, '\\');
188 // Fallthrough
189 default:
190 692 append_byte:
191 692 string_append_byte(&buf, ch);
192 }
193 }
194
195 58 string_append_byte(&buf, '$');
196 58 char *regex_pattern = string_steal_cstring(&buf);
197
198 58 regex_t re;
199 58 bool compiled = !regcomp(&re, regex_pattern, REG_EXTENDED | REG_NOSUB);
200 58 free(regex_pattern);
201
1/2
✓ Branch 0 (56→57) taken 58 times.
✗ Branch 1 (56→60) not taken.
58 if (!compiled) {
202 return false;
203 }
204
205 58 int res = regexec(&re, path, 0, NULL, 0);
206 58 regfree(&re);
207 58 return res == 0;
208 }
209