dte test coverage


Directory: ./
File: src/editorconfig/editorconfig.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 97 108 89.8%
Functions: 8 8 100.0%
Branches: 48 84 57.1%

Line Branch Exec Source
1 #include <stddef.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "editorconfig.h"
5 #include "ini.h"
6 #include "match.h"
7 #include "util/debug.h"
8 #include "util/path.h"
9 #include "util/readfile.h"
10 #include "util/string.h"
11 #include "util/string-view.h"
12 #include "util/strtonum.h"
13 #include "util/xstring.h"
14
15 enum {
16 MAX_FILESIZE = 32u << 20, // 32 MiB
17 };
18
19 typedef struct {
20 const char *const pathname;
21 StringView config_file_dir;
22 EditorConfigOptions options;
23 bool match;
24 } UserData;
25
26 typedef enum {
27 ECONF_CHARSET,
28 ECONF_END_OF_LINE,
29 ECONF_INDENT_SIZE,
30 ECONF_INDENT_STYLE,
31 ECONF_INSERT_FINAL_NL,
32 ECONF_MAX_LINE_LENGTH,
33 ECONF_TAB_WIDTH,
34 ECONF_TRIM_TRAILING_WS,
35 ECONF_UNKNOWN_PROPERTY,
36 } PropertyType;
37
38 #define CMP(s, val) if (mem_equal(name->data, s, STRLEN(s))) return val;
39
40 9 static PropertyType lookup_property(const StringView *name)
41 {
42
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
9 switch (name->length) {
43 case 7: CMP("charset", ECONF_CHARSET); break;
44
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 case 9: CMP("tab_width", ECONF_TAB_WIDTH); break;
45 3 case 11:
46
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 CMP("indent_size", ECONF_INDENT_SIZE);
47 CMP("end_of_line", ECONF_END_OF_LINE);
48 break;
49
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case 12: CMP("indent_style", ECONF_INDENT_STYLE); break;
50
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 case 15: CMP("max_line_length", ECONF_MAX_LINE_LENGTH); break;
51 case 20: CMP("insert_final_newline", ECONF_INSERT_FINAL_NL); break;
52 case 24: CMP("trim_trailing_whitespace", ECONF_TRIM_TRAILING_WS); break;
53 }
54 return ECONF_UNKNOWN_PROPERTY;
55 }
56
57 3 static EditorConfigIndentStyle lookup_indent_style(const StringView *val)
58 {
59
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (strview_equal_cstring_icase(val, "space")) {
60 return INDENT_STYLE_SPACE;
61 } else if (strview_equal_cstring_icase(val, "tab")) {
62 return INDENT_STYLE_TAB;
63 }
64 return INDENT_STYLE_UNSPECIFIED;
65 }
66
67 5 static unsigned int parse_indent_digit(const StringView *val)
68 {
69 5 const unsigned char *data = val->data;
70
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 unsigned int indent = (val->length == 1) ? data[0] - '0' : 0;
71
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 return (indent <= 8) ? indent : 0; // Valid indent widths are 1-8
72 }
73
74 3 static void parse_indent_size(EditorConfigOptions *options, const StringView *val)
75 {
76 3 bool tab = strview_equal_cstring_icase(val, "tab");
77 3 options->indent_size_is_tab = tab;
78
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 options->indent_size = tab ? 0 : parse_indent_digit(val);
79 3 }
80
81 1 static unsigned int parse_max_line_length(const StringView *val)
82 {
83 1 unsigned int maxlen = 0;
84 1 size_t ndigits = buf_parse_uint(val->data, val->length, &maxlen);
85
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 return (ndigits == val->length) ? maxlen : 0;
86 }
87
88 9 static void editorconfig_option_set (
89 EditorConfigOptions *options,
90 const StringView *name,
91 const StringView *val
92 ) {
93
4/6
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
9 switch (lookup_property(name)) {
94 3 case ECONF_INDENT_STYLE:
95 3 options->indent_style = lookup_indent_style(val);
96 3 break;
97 3 case ECONF_INDENT_SIZE:
98 3 parse_indent_size(options, val);
99 3 break;
100 2 case ECONF_TAB_WIDTH:
101 2 options->tab_width = parse_indent_digit(val);
102 2 break;
103 1 case ECONF_MAX_LINE_LENGTH:
104 1 options->max_line_length = parse_max_line_length(val);
105 1 break;
106 case ECONF_CHARSET:
107 case ECONF_END_OF_LINE:
108 case ECONF_INSERT_FINAL_NL:
109 case ECONF_TRIM_TRAILING_WS:
110 case ECONF_UNKNOWN_PROPERTY:
111 break;
112 default:
113 BUG("unhandled property type");
114 }
115 9 }
116
117 4 static void editorconfig_parse(const char *buf, size_t size, UserData *data)
118 {
119 4 IniParser ini = {
120 .input = buf,
121 .input_len = size,
122 };
123
124
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (size >= 3 && mem_equal(buf, "\xEF\xBB\xBF", 3)) {
125 // Skip past UTF-8 BOM
126 2 ini.pos += 3;
127 }
128
129
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 4 times.
34 while (ini_parse(&ini)) {
130
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 26 times.
30 if (ini.section.length == 0) {
131
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (
132 4 strview_equal_cstring_icase(&ini.name, "root")
133
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 && strview_equal_cstring_icase(&ini.value, "true")
134 ) {
135 // root=true, clear all previous values
136 4 data->options = editorconfig_options_init();
137 }
138 4 continue;
139 }
140
141
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 14 times.
26 if (ini.name_count == 1) {
142 // If name_count is 1, it indicates that the name/value pair is
143 // the first in the section and therefore requires a new pattern
144 // to be built and tested for a match
145 12 const StringView ecdir = data->config_file_dir;
146 12 String pattern = string_new(ecdir.length + ini.section.length + 16);
147
148 // Escape editorconfig special chars in path
149
2/2
✓ Branch 0 taken 296 times.
✓ Branch 1 taken 12 times.
308 for (size_t i = 0, n = ecdir.length; i < n; i++) {
150 296 const char ch = ecdir.data[i];
151
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 296 times.
296 switch (ch) {
152 case '*': case ',': case '-':
153 case '?': case '[': case '\\':
154 case ']': case '{': case '}':
155 string_append_byte(&pattern, '\\');
156 // Fallthrough
157 296 default:
158 296 string_append_byte(&pattern, ch);
159 }
160 }
161
162
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 if (!strview_memchr(&ini.section, '/')) {
163 // No slash in pattern, append "**/"
164 10 string_append_literal(&pattern, "**/");
165
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 } else if (ini.section.data[0] != '/') {
166 // Pattern contains at least one slash but not at the start, add one
167 2 string_append_byte(&pattern, '/');
168 }
169
170 12 string_append_strview(&pattern, &ini.section);
171 24 data->match = ec_pattern_match (
172 12 pattern.buffer,
173 pattern.len,
174 12 data->pathname
175 );
176 12 string_free(&pattern);
177 } else {
178 // Otherwise, the section is the same as was passed for the first
179 // name/value pair in the section and the value of data->match
180 // can just be reused
181 26 }
182
183
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 17 times.
26 if (data->match) {
184 9 editorconfig_option_set(&data->options, &ini.name, &ini.value);
185 }
186 }
187 4 }
188
189 2 int get_editorconfig_options(const char *pathname, EditorConfigOptions *opts)
190 {
191 2 BUG_ON(!path_is_absolute(pathname));
192 2 UserData data = {
193 .pathname = pathname,
194 .config_file_dir = STRING_VIEW_INIT,
195 .match = false
196 };
197
198 2 static const char ecfilename[16] = "/.editorconfig";
199 2 char buf[8192];
200 2 memcpy(buf, ecfilename, sizeof ecfilename);
201
202 2 const char *ptr = pathname + 1;
203 2 size_t dir_len = 1;
204
205 // Iterate up directory tree, looking for ".editorconfig" at each level
206 22 while (1) {
207 12 char *text;
208 12 ssize_t len = read_file(buf, &text, MAX_FILESIZE);
209
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
12 if (len >= 0) {
210 4 data.config_file_dir = string_view(buf, dir_len);
211 4 editorconfig_parse(text, len, &data);
212 4 free(text);
213 }
214
215 12 const char *slash = strchr(ptr, '/');
216
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 if (!slash) {
217 break;
218 }
219
220 10 dir_len = slash - pathname;
221 10 memcpy(buf, pathname, dir_len);
222 10 memcpy(buf + dir_len, ecfilename, sizeof ecfilename);
223 10 ptr = slash + 1;
224 }
225
226 // Set indent_size to "tab" if indent_size is not specified and
227 // indent_style is set to "tab"
228 2 EditorConfigOptions *o = &data.options;
229
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
2 if (o->indent_size == 0 && o->indent_style == INDENT_STYLE_TAB) {
230 o->indent_size_is_tab = true;
231 }
232
233 // Set indent_size to tab_width if indent_size is "tab" and
234 // tab_width is specified
235
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (o->indent_size_is_tab && o->tab_width > 0) {
236 o->indent_size = o->tab_width;
237 }
238
239 // Set tab_width to indent_size if indent_size is specified as
240 // something other than "tab" and tab_width is unspecified
241
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
2 if (o->indent_size != 0 && o->tab_width == 0 && !o->indent_size_is_tab) {
242 1 o->tab_width = o->indent_size;
243 }
244
245 2 *opts = data.options;
246 2 return 0;
247 }
248