dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 69 / 0 / 69
Functions: 100.0% 5 / 0 / 5
Branches: 85.0% 51 / 6 / 66

src/ctags.c
Line Branch Exec Source
1 #include <stdlib.h>
2 #include "ctags.h"
3 #include "util/arith.h"
4 #include "util/ascii.h"
5 #include "util/debug.h"
6 #include "util/str-util.h"
7 #include "util/strtonum.h"
8 #include "util/xmalloc.h"
9
10 // Convert an ex(1) style pattern from a tags(5) file to a basic POSIX
11 // regex ("BRE"), so that it can be compiled with regcomp(3)
12 19 static size_t regex_from_ex_pattern(StringView ex, char **regex_str)
13 {
14 19 BUG_ON(ex.length == 0);
15 19 const char open_delim = ex.data[0];
16 19 BUG_ON(open_delim != '/' && open_delim != '?');
17 19 char *buf = xmalloc(xmul(2, ex.length));
18
19 // The pattern isn't a real regex; special chars need to be escaped
20
2/2
✓ Branch 20 → 9 taken 890 times.
✓ Branch 20 → 21 taken 1 time.
910 for (size_t i = 1, j = 0; i < ex.length; i++) {
21 890 char c = ex.data[i];
22
2/2
✓ Branch 9 → 10 taken 889 times.
✓ Branch 9 → 21 taken 1 time.
890 if (c == '\0') {
23 break;
24
2/2
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 14 taken 886 times.
889 } else if (c == '\\') {
25
1/2
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 21 not taken.
3 if (unlikely(++i >= ex.length)) {
26 break;
27 }
28 3 c = ex.data[i];
29
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 19 taken 2 times.
3 if (c == '\\') {
30 // Escape "\\" as "\\" (any other "\x" becomes just "x")
31 1 buf[j++] = '\\';
32 }
33
4/4
✓ Branch 14 → 15 taken 859 times.
✓ Branch 14 → 16 taken 27 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 858 times.
886 } else if (c == '*' || c == '[' || c == ']') {
34 28 buf[j++] = '\\';
35
2/2
✓ Branch 17 → 18 taken 17 times.
✓ Branch 17 → 19 taken 841 times.
858 } else if (c == open_delim) {
36 17 buf[j] = '\0';
37 17 *regex_str = buf;
38 17 return i + 1;
39 }
40 872 buf[j++] = c;
41 }
42
43 // End of string reached without a matching end delimiter; invalid input
44 2 free(buf);
45 2 return 0;
46 }
47
48 21 static size_t parse_ex_cmd(Tag *tag, StringView cmd)
49 {
50
1/2
✓ Branch 2 → 3 taken 21 times.
✗ Branch 2 → 13 not taken.
21 if (unlikely(cmd.length == 0)) {
51 return 0;
52 }
53
54 21 size_t n;
55
2/2
✓ Branch 4 → 5 taken 19 times.
✓ Branch 4 → 6 taken 2 times.
21 if (strview_has_either_prefix(cmd, "/", "?")) {
56 19 n = regex_from_ex_pattern(cmd, &tag->pattern);
57 } else {
58 2 n = buf_parse_ulong(cmd, &tag->lineno);
59 }
60
61
2/2
✓ Branch 7 → 8 taken 19 times.
✓ Branch 7 → 13 taken 2 times.
21 if (n == 0) {
62 return 0;
63 }
64
65 19 strview_remove_prefix(&cmd, n);
66 19 StringView delim = strview(";\"");
67 19 bool trailing_comment = strview_has_sv_prefix(cmd, delim);
68
2/2
✓ Branch 10 → 11 taken 16 times.
✓ Branch 10 → 12 taken 3 times.
19 return n + (trailing_comment ? delim.length : 0);
69 }
70
71 21 bool parse_ctags_line(Tag *tag, StringView line)
72 {
73 21 size_t pos = 0;
74 21 *tag = (Tag){.name = get_delim(line.data, &pos, line.length, '\t')};
75
2/4
✓ Branch 3 → 4 taken 21 times.
✗ Branch 3 → 26 not taken.
✓ Branch 4 → 5 taken 21 times.
✗ Branch 4 → 26 not taken.
21 if (tag->name.length == 0 || pos >= line.length) {
76 return false;
77 }
78
79 21 tag->filename = get_delim(line.data, &pos, line.length, '\t');
80
2/4
✓ Branch 6 → 7 taken 21 times.
✗ Branch 6 → 26 not taken.
✓ Branch 7 → 8 taken 21 times.
✗ Branch 7 → 26 not taken.
21 if (tag->filename.length == 0 || pos >= line.length) {
81 return false;
82 }
83
84 21 size_t len = parse_ex_cmd(tag, strview_from_slice(line.data, pos, line.length));
85
2/2
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 13 taken 19 times.
21 if (len == 0) {
86 2 BUG_ON(tag->pattern);
87 return false;
88 }
89
90 19 pos += len;
91
2/2
✓ Branch 13 → 14 taken 18 times.
✓ Branch 13 → 26 taken 1 time.
19 if (pos >= line.length) {
92 return true;
93 }
94
95 /*
96 * Extension fields (key:[value]):
97 *
98 * file: visibility limited to this file
99 * struct:NAME tag is member of struct NAME
100 * union:NAME tag is member of union NAME
101 * typeref:struct:NAME::MEMBER_TYPE MEMBER_TYPE is type of the tag
102 */
103
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 25 taken 17 times.
18 if (line.data[pos++] != '\t') {
104 // free `pattern` allocated by parse_ex_cmd()
105 1 free_tag(tag);
106 1 tag->pattern = NULL;
107 1 return false;
108 }
109
110
2/2
✓ Branch 25 → 17 taken 38 times.
✓ Branch 25 → 26 taken 17 times.
55 while (pos < line.length) {
111 38 StringView field = get_delim(line.data, &pos, line.length, '\t');
112
3/4
✓ Branch 18 → 19 taken 17 times.
✓ Branch 18 → 21 taken 21 times.
✓ Branch 19 → 20 taken 17 times.
✗ Branch 19 → 21 not taken.
38 if (field.length == 1 && ascii_isalpha(field.data[0])) {
113 17 tag->kind = field.data[0];
114
2/2
✓ Branch 22 → 23 taken 7 times.
✓ Branch 22 → 24 taken 14 times.
21 } else if (strview_equal_cstring(field, "file:")) {
115 7 tag->local = true;
116 }
117 // TODO: struct/union/typeref
118 }
119
120 return true;
121 }
122
123 18 bool next_tag (
124 StringView text, // Tag file contents
125 size_t *posp, // Current position within `text` [in-out param]
126 StringView prefix,
127 bool exact,
128 Tag *tag // [out param]
129 ) {
130
2/2
✓ Branch 14 → 3 taken 55 times.
✓ Branch 14 → 15 taken 4 times.
59 for (size_t pos = *posp; pos < text.length; ) {
131 55 StringView line = buf_slice_next_line(text.data, &pos, text.length);
132 55 if (
133
1/2
✓ Branch 4 → 5 taken 55 times.
✗ Branch 4 → 13 not taken.
55 line.length > 0 // Line is non-empty
134
2/2
✓ Branch 5 → 6 taken 37 times.
✓ Branch 5 → 13 taken 18 times.
55 && line.data[0] != '!' // and not a comment
135
2/2
✓ Branch 7 → 8 taken 15 times.
✓ Branch 7 → 13 taken 22 times.
37 && strview_has_sv_prefix(line, prefix) // and starts with `prefix`
136
4/4
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 13 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 13 taken 1 time.
15 && (!exact || line.data[prefix.length] == '\t') // and matches `prefix` exactly, if applicable
137
1/2
✓ Branch 11 → 12 taken 14 times.
✗ Branch 11 → 13 not taken.
14 && parse_ctags_line(tag, line) // and is a valid tags(5) entry
138 ) {
139 // Advance the position; `tag` has been filled by parse_ctags_line()
140 14 *posp = pos;
141 14 return true;
142 }
143 }
144
145 // No matching tags remaining
146 4 return false;
147 }
148
149 // NOTE: tag itself is not freed
150 22 void free_tag(Tag *tag)
151 {
152 22 free(tag->pattern);
153 22 }
154