ArKanjo 0.5.1
A tool for find code duplicated functions in codebases
tree_sitter_parser.cpp
Go to the documentation of this file.
2#include <iostream>
3#include <unordered_map>
8
9std::unordered_map<std::string, TSLanguage* (*)()> get_language_map();
10std::unordered_map<std::string, std::string> get_extension_map();
11
12bool TreeSitterParser::is_function_empty(TSNode body) {
13 if (ts_node_is_null(body)) return true;
14
15 uint32_t n = ts_node_named_child_count(body);
16
17 return n == 0;
18}
19
20std::string TreeSitterParser::detect_language(const fs::path& path) {
21 static auto ext_map = get_extension_map();
22
23 std::string ext = fs::path(path).extension().string();
24
25 auto it = ext_map.find(ext);
26 if (it != ext_map.end()) {
27 return it->second;
28 }
29
30 return "";
31}
32
33std::string TreeSitterParser::get_full_signature(TSNode func_node, const std::string& source) {
34 uint32_t signature_start = ts_node_start_byte(func_node);
35
36 TSNode body = get_body(func_node);
37
38 uint32_t signature_end = ts_node_start_byte(body);
39
40 return source.substr(signature_start, signature_end - signature_start);
41}
42
43static const char* field_names[] = {
44 "name", "declarator"
45};
46
47std::string extract_name_generic(TSNode node, const std::string& source) {
48 std::string type_identifier_fallback;
49
50 for (const auto& field_name : field_names) {
51 TSNode name = ts_node_child_by_field_name(node, field_name, strlen(field_name));
52 if (!ts_node_is_null(name)) {
53 std::string_view type = ts_node_type(name);
54 if (type == "identifier") {
55 return FeatureExtractor::get_node_text(name, source);
56 }
57
58 if (type == "qualified_identifier") {
59 TSNode qualified_identifier_name = ts_node_child_by_field_name(name, "name", strlen("name"));
60 if (!ts_node_is_null(qualified_identifier_name)) {
61 std::string result = extract_name_generic(
62 qualified_identifier_name,
63 source
64 );
65
66 if (!result.empty())
67 return result;
68
69 return source.substr(
70 ts_node_start_byte(qualified_identifier_name),
71 ts_node_end_byte(qualified_identifier_name) - ts_node_start_byte(qualified_identifier_name)
72 );
73 }
74 }
75
76 if (type == "type_identifier") {
77 if (type_identifier_fallback.empty())
78 type_identifier_fallback = FeatureExtractor::get_node_text(name, source);
79 }
80 }
81 }
82
83 uint32_t count = ts_node_child_count(node);
84 for (uint32_t i = 0; i < count; i++) {
85 TSNode child = ts_node_child(node, i);
86 std::string result = extract_name_generic(child, source);
87 if (!result.empty()) return result;
88 }
89
90 if (!type_identifier_fallback.empty())
91 return type_identifier_fallback;
92
93 return "";
94}
95
96std::string TreeSitterParser::get_function_name(TSNode func_node, const std::string& source) {
97 return extract_name_generic(func_node, source);
98}
99
100TSNode TreeSitterParser::get_body(TSNode node) {
101 TSNode body = ts_node_child_by_field_name(node, "body", strlen("body"));
102
103 if (!ts_node_is_null(body))
104 return body;
105
106 uint32_t count = ts_node_child_count(node);
107
108 for (uint32_t i = 0; i < count; i++) {
109 TSNode child = ts_node_child(node, i);
110 std::string_view type = ts_node_type(child);
112 return child;
113 }
114 }
115
116 return TSNode{};
117}
118
119bool TreeSitterParser::should_ignore(TSNode node) {
120 size_t errors = 0;
121 size_t size = 0;
122
123 std::function<void(TSNode)> dfs = [&](TSNode n) {
124 size++;
125
126 if (ts_node_is_error(n))
127 errors++;
128
129 uint32_t child_count = ts_node_child_count(n);
130 for (uint32_t i = 0; i < child_count; i++)
131 dfs(ts_node_child(n, i));
132 };
133
134 dfs(node);
135
136 if (size == 0) return false;
137
138 double ratio = static_cast<double>(errors) / static_cast<double>(size);
139 int threshold = 1 + static_cast<int>(std::log2(size));
140
141 return (errors > threshold) || (ratio > 0.1);
142}
143
144void TreeSitterParser::collect_functions(
145 TSNode node,
146 const std::string& source,
147 const fs::path& relative_path,
148 const std::shared_ptr<TSTree>& tree,
149 std::function<void(const FunctionData&)> callback,
150 SkipStats& stats
151) {
152 std::string_view type = ts_node_type(node);
154 TSPoint start = ts_node_start_point(node);
155 TSPoint end = ts_node_end_point(node);
156
157 if (TreeSitterParser::should_ignore(node)) {
158 stats.errors++;
159 return;
160 }
161
162 TSNode body = get_body(node);
163
164 if (is_function_empty(body)) return;
165
166 std::string function_name = get_function_name(node, source);
167 if (function_name.empty()) return;
168
169 TSPoint body_start = ts_node_start_point(body);
170 uint32_t start_byte = ts_node_start_byte(node);
171 uint32_t end_byte = ts_node_end_byte(node);
172
173 std::string signature = get_full_signature(node, source);
174 std::string code = source.substr(start_byte + signature.size(), end_byte - (start_byte + signature.size()));
175
176 FunctionData function;
177 function.path = relative_path.string();
178 function.function_name = function_name;
179
180 auto source = std::make_shared<SourceFeature>();
181 source->code = code;
182 function.add_feature(source);
183
184 auto ast = std::make_shared<ASTFeature>();
185 ast->tree = tree;
186 ast->root = body;
187 function.add_feature(ast);
188
189 auto metadata = std::make_shared<MetadataFeature>();
190 metadata->signature = signature;
191 metadata->line_declaration = start.row;
192 metadata->start_number_line = body_start.row;
193 metadata->end_number_line = end.row;
194 function.add_feature(metadata);
195
196 callback(function);
197 }
198
199 uint32_t count = ts_node_child_count(node);
200 for (uint32_t i = 0; i < count; i++) {
201 collect_functions(
202 ts_node_child(node, i),
203 source,
204 relative_path,
205 tree,
206 callback,
207 stats
208 );
209 }
210}
211
212std::shared_ptr<TSTree> TreeSitterParser::parse_source(
213 const fs::path& file_path,
214 const std::string& source_code
215) {
216 auto lang_name = detect_language(file_path);
217
218 if (lang_name.empty()) return nullptr;
219
220 static auto language_map = get_language_map();
221 auto it = language_map.find(lang_name);
222 if (it == language_map.end()) {
223 std::cerr << "Language not found: " << lang_name << "\n";
224 return nullptr;
225 }
226 TSLanguage* language = it->second();
227
228 static TSLanguage* current_language = nullptr;
229 static std::unique_ptr<TSParser, decltype(&ts_parser_delete)> parser(
230 ts_parser_new(),
231 ts_parser_delete
232 );
233 if (current_language != language) {
234 if (!ts_parser_set_language(parser.get(), language)) {
235 return nullptr;
236 }
237 current_language = language;
238 }
239
240 return std::shared_ptr<TSTree>(
241 ts_parser_parse_string(
242 parser.get(),
243 nullptr,
244 source_code.c_str(),
245 source_code.size()
246 ),
247 ts_tree_delete
248 );
249}
250
252 const fs::path& file_path,
253 const fs::path& relative_path,
254 const std::string& source_code,
255 std::function<void(const FunctionData&)> callback,
256 SkipStats& stats
257) {
258 std::shared_ptr<TSTree> tree = parse_source(file_path, source_code);
259 if (!tree) return;
260
261 TSNode root_node = ts_tree_root_node(tree.get());
262
263 collect_functions(root_node, source_code, relative_path, tree, callback, stats);
264
265 tree.reset();
266}
267
269 const fs::path& file_path,
270 const fs::path& relative_path,
271 const std::string& source_code,
272 std::function<void(const FunctionData&)> callback,
273 SkipStats& stats
274) {
275 std::shared_ptr<TSTree> tree = parse_source(file_path, source_code);
276 if (!tree) return;
277
278 TSNode root_node = ts_tree_root_node(tree.get());
279 TSPoint end = ts_node_end_point(root_node);
280
281 FunctionData function;
282 function.path = relative_path.string();
283 function.function_name = relative_path.stem().string();
284
285 auto source = std::make_shared<SourceFeature>();
286 source->code = source_code;
287 function.add_feature(source);
288
289 auto ast = std::make_shared<ASTFeature>();
290 ast->tree = tree;
291 ast->root = root_node;
292 function.add_feature(ast);
293
294 auto metadata = std::make_shared<MetadataFeature>();
295 metadata->signature = "";
296 metadata->line_declaration = 0;
297 metadata->start_number_line = 0;
298 metadata->end_number_line = end.row;
299 function.add_feature(metadata);
300
301 callback(function);
302
303 tree.reset();
304}
static std::string get_node_text(TSNode node, const std::string &source)
static bool is_block_node(std::string_view type)
static bool is_function_node(std::string_view type)
std::string function_name
Name of the function.
std::string path
void add_feature(std::shared_ptr< T > feature)
static void process_file_as_unit(const fs::path &file_path, const fs::path &relative_path, const std::string &source_code, std::function< void(const FunctionData &)> callback, SkipStats &stats)
static void process_file(const fs::path &file_path, const fs::path &relative_path, const std::string &source_code, std::function< void(const FunctionData &)> callback, SkipStats &stats)
std::unordered_map< std::string, std::string > get_extension_map()
std::string extract_name_generic(TSNode node, const std::string &source)
std::unordered_map< std::string, TSLanguage *(*)()> get_language_map()
Defines utility functions used across all files.