ArKanjo 0.5.1
A tool for find code duplicated functions in codebases
similarity_explorer.cpp
Go to the documentation of this file.
1/*
2This file expect the output of parser.cpp
3This code filter the output only printing the files similar to files that contains a given pattern passed as argc
4The code filter every file that has the pattern as a substring, so be carefull with duplications
5*/
6
7#include <algorithm>
8#include <utility>
9
14
16
17int SimilarityExplorer::find_number_pairs_show(int number_pair_found) const {
18 if (limit_on_results == UNLIMITED_RESULTS) {
19 return number_pair_found;
20 }
21 return std::min(limit_on_results, number_pair_found);
22}
23
24bool SimilarityExplorer::match_pattern(const Function& function1, const Function& function2) const {
25 bool match1 = function1.contains_given_pattern(pattern_to_match);
26 bool match2 = function2.contains_given_pattern(pattern_to_match);
27
28 if (both_path_need_to_match_pattern) {
29 return match1 && match2;
30 }
31 return match1 || match2;
32}
33
34SimilarityExplorerEntry SimilarityExplorer::process_similar_path_pair(const Path& path1, const Path& path2) {
35 FunctionLoader loader;
36 auto function1 = loader.load_metadata(path1);
37 auto location1 = function1.scope_location();
38
39 auto function2 = loader.load_metadata(path2);
40 auto location2 = function2.scope_location();
41
42 if (!match_pattern(function1, function2))
43 return {};
44 if (limit_on_results != UNLIMITED_RESULTS && processed_results >= limit_on_results) {
45 return {};
46 }
47 processed_results++;
48
49 return {
52 .dir_a = path1.relative_path().parent_path().string(),
53 .dir_b = path2.relative_path().parent_path().string(),
54 .filename_a = path1.relative_path().filename().string(),
55 .filename_b = path2.relative_path().filename().string(),
56 .func_a = function1.name(),
57 .func_b = function2.name(),
58 .start_a = location1.declaration,
59 .start_b = location2.declaration,
60 .end_a = location1.end,
61 .end_b = location2.end,
62 .duplicated_lines = location1.size(),
63 .similarity = similarity_table->get_similarity(path1, path2),
64 };
65}
66
67int SimilarityExplorer::find_number_pair_found(const std::vector<SimilarPair>& similar_path_pairs) const {
68 int count = 0;
69 FunctionLoader loader;
70 for (const auto& similar_pair : similar_path_pairs) {
71 const Path& path1 = similarity_table->get_path(similar_pair.id1);
72 const Path& path2 = similarity_table->get_path(similar_pair.id2);
73
74 auto function1 = loader.load_metadata(path1);
75 auto function2 = loader.load_metadata(path2);
76
77 if (match_pattern(function1, function2))
78 count++;
79 }
80 return count;
81}
82
83std::vector<SimilarPair> SimilarityExplorer::build_similar_path_pairs() {
84 std::vector<SimilarPair> similar_path_pairs = similarity_table->get_all_similar_pairs();
85 if (sorted_by_number_of_duplicated_code) {
86 similarity_table->sort_pairs_by_line_number(similar_path_pairs);
87 } else {
88 similarity_table->sort_pairs_by_similarity(similar_path_pairs);
89 }
90 return similar_path_pairs;
91}
92
93void SimilarityExplorer::explorer_clusters() {
94 auto clusters_info = similarity_table->get_clusters_info(sorted_by_number_of_duplicated_code);
95
96 int clusters_to_show = limit_on_results > 0
97 ? std::min(limit_on_results, (int)clusters_info.size())
98 : (int)clusters_info.size();
99
100 for (int idx = 0; idx < clusters_to_show; idx++) {
101 const auto& info = clusters_info[idx];
102 fm::write("Cluster #" + std::to_string(idx + 1)
103 + " (Files: " + std::to_string(info.paths.size())
104 + ", Pairs: " + std::to_string(info.total_pairs)
105 + ", Lines: " + std::to_string(info.total_lines)
106 + ", Score: " + std::to_string(info.score()) + ")");
108
109 std::vector<SimilarityExplorerEntry> entries{};
110 FunctionLoader loader;
111 for (const auto& path : info.paths) {
112 auto function = loader.load_metadata(path);
113 auto location = function.scope_location();
114 entries.push_back({
116 "",
117 path.relative_path().parent_path().string(),
118 "",
119 path.relative_path().filename().string(),
120 "",
121 function.name(),
122 "",
123 location.declaration,
124 0,
125 location.end,
126 0,
127 location.size(),
128 0.0,
129 });
130 }
131
132 fm::write(TEMPLATE_PROCESSED_RESULTS_CLUSTERS, entries, Format::AUTO, [](size_t i) {
133 return (i % 2 == 0)
134 ? fm::get_formatter()->style().at("row_even")
135 : fm::get_formatter()->style().at("row_odd");
136 });
137 fm::write("");
138 }
139}
140
141void SimilarityExplorer::explorer() {
142 auto start = std::chrono::high_resolution_clock::now();
143
144 if (mode_verbose)
145 fm::write("Building pairs of similar paths...");
146
147 auto similar_path_pairs = build_similar_path_pairs();
148
149 if (mode_verbose)
150 fm::write("Finding pair of numbers found and show...");
151
152 int number_pair_found = find_number_pair_found(similar_path_pairs);
153 int number_pairs_show = find_number_pairs_show(number_pair_found);
154
155 if (mode_verbose)
156 fm::time("Execution time build similar path pairs:", start);
157
158 fm::write(TEMPLATE_INITIAL_TEXT, SimilarityExplorerInitialMessage{
159 number_pair_found, number_pairs_show
160 }, Format::TEXT);
162
163 std::vector<SimilarityExplorerEntry> vector_entry = {};
164 for (const auto& similar_pair : similar_path_pairs) {
165 const Path& path1 = similarity_table->get_path(similar_pair.id1);
166 const Path& path2 = similarity_table->get_path(similar_pair.id2);
167
168 auto entry = process_similar_path_pair(path1, path2);
169 if (entry.duplicated_lines <= 0)
170 continue;
171 vector_entry.push_back(entry);
172 }
173 fm::write(template_processed_results_output, vector_entry, Format::AUTO, [](size_t i) {
174 return (i % 2 == 0)
175 ? fm::get_formatter()->style().at("row_even")
176 : fm::get_formatter()->style().at("row_odd");
177 });
178}
179
180SimilarityExplorer::SimilarityExplorer(Similarity_Table* table)
181 : similarity_table(table),
182 limit_on_results(0),
183 pattern_to_match(""),
184 both_path_need_to_match_pattern(false),
185 sorted_by_number_of_duplicated_code(false),
186 use_clusters(false) {}
187
189 try {
190 if (auto limiter = options.get_as<int>("limiter"))
191 this->limit_on_results = *limiter;
192 } catch (const std::invalid_argument&) {
193 throw CLIError(
194 "--limiter must be a valid number (passing " +
195 std::string(*options.get("limiter")) + ")"
196 );
197 } catch (const std::out_of_range&) {
198 throw CLIError("--limiter outside the permitted range");
199 }
200
201 return true;
202}
203
204void SimilarityExplorer::print_template_variables() {
205 SimilarityExplorerEntry entry = {};
206 json j;
207 to_json(j, entry);
208
209 fm::write(BOLD("Available variables for --template:"));
210 for (const auto& [k, _] : j.items()) {
211 fm::write(" {" + k + "}");
212 }
213
214 fm::write("");
215 fm::write(BOLD("Example:"));
216 fm::write(" arkanjo explorer --template \"{func_a:function} ~ {func_b:function} ({duplicated_lines:number} lines)\"\n");
217}
218
220 if (options.has("template-help")) {
221 print_template_variables();
222 return true;
223 }
224
225 if (auto pattern = options.get("pattern"))
226 this->pattern_to_match = *pattern;
227
228 both_path_need_to_match_pattern = options.has("both-match");
229 sorted_by_number_of_duplicated_code = options.has("sort");
230 use_clusters = options.has("cluster");
231
232 if (auto template_value = options.get("template"))
233 this->template_processed_results_output = *template_value;
234
235 if (use_clusters) {
236 explorer_clusters();
237 return true;
238 }
239
240 explorer();
241 return true;
242}
Base class for CLI-related errors.
Definition: cli_error.hpp:10
const CliOption * options() const final
static void write(const std::string &template_str, const std::vector< T > &data, enum Format effective=Format::AUTO, RowColorFn color_fn=nullptr, std::ostream &out=std::cout)
static std::shared_ptr< IFormatter > get_formatter()
static void time(const std::string &str, std::chrono::high_resolution_clock::time_point start, std::ostream &out=std::cout)
Function load_metadata(const Path &path)
Loads function metadata from a path.
static std::string format_path_message_in_pair(const Function &function)
Formats path for display.
Represents a function extracted from a codebase.
Definition: function.hpp:45
bool contains_given_pattern(const std::string &pattern) const
Checks whether the function contains a pattern.
Definition: function.cpp:41
const std::string & name() const
Gets the function name.
Definition: function.cpp:9
const FunctionLocation & scope_location() const
Gets the function location inside the source file.
Definition: function.cpp:17
Represents a logical path to a function in the codebase.
Definition: path.hpp:26
fs::path relative_path() const
Gets the directory portion of the path.
Definition: path.cpp:15
static constexpr int UNLIMITED_RESULTS
Constant for unlimited results display.
bool validate(const ParsedOptions &options) override
Validate the arguments already analyzed.
bool run(const ParsedOptions &options) override
Handles code exploration command.
Represents a similarity graph between functions (paths).
std::vector< SimilarPair > get_all_similar_pairs()
Gets all similar path pairs with scores, sorted.
std::vector< ClusterInfo > get_clusters_info(bool sorted)
Returns detailed information about all clusters found in the similarity table.
const Path & get_path(PathId id) const
void sort_pairs_by_line_number(std::vector< SimilarPair > &similar_pairs) const
Sorts path pairs by line count.
void sort_pairs_by_similarity(std::vector< SimilarPair > &similar_pairs) const
Sorts path pairs by similarity.
double get_similarity(const Path &path1, const Path &path2)
Gets similarity between two paths.
nlohmann::json json
Provides functionality to load Function objects from external sources.
Provides utilities for printing function information and source code.
@ BOLD
Bold text.
Definition: utils.hpp:123
const std::string LIMITER_PRINT
Constant string used as a visual delimiter/separator in prints.
Definition: utils.hpp:33
Duplicate function exploration interface.
void to_json(json &j, const SimilarityExplorerEntry &d)