dte test coverage


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