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 |