mirror of https://gitlab.com/cppit/jucipp
34 changed files with 1565 additions and 298 deletions
@ -1 +1 @@
|
||||
Subproject commit 955c113c73ecdfbf5fecc1c0b8fef537693c2f25 |
||||
Subproject commit b242f8d6a73cb211ea9faa9bbfd4f444e0afbf0a |
||||
@ -0,0 +1,714 @@
|
||||
#include "usages_clang.h" |
||||
#include "compile_commands.h" |
||||
#include "config.h" |
||||
#include "dialogs.h" |
||||
#include "filesystem.h" |
||||
#include <chrono> |
||||
#include <fstream> |
||||
#include <regex> |
||||
#include <thread> |
||||
|
||||
// #include <iostream> //TODO: remove
|
||||
|
||||
const boost::filesystem::path Usages::Clang::cache_folder = ".usages_clang"; |
||||
std::map<boost::filesystem::path, Usages::Clang::Cache> Usages::Clang::caches; |
||||
std::mutex Usages::Clang::caches_mutex; |
||||
std::atomic<size_t> Usages::Clang::cache_in_progress_count(0); |
||||
|
||||
bool Usages::Clang::Cache::Cursor::operator==(const Cursor &o) { |
||||
for(auto &usr : usrs) { |
||||
if(o.usrs.count(usr)) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
Usages::Clang::Cache::Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||
std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens) |
||||
: project_path(project_path), build_path(build_path) { |
||||
for(auto &clang_token : *clang_tokens) { |
||||
tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), static_cast<size_t>(-1)}); |
||||
|
||||
if(clang_token.is_identifier()) { |
||||
auto clang_cursor = clang_token.get_cursor().get_referenced(); |
||||
if(clang_cursor) { |
||||
Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()}; |
||||
for(size_t c = 0; c < cursors.size(); ++c) { |
||||
if(cursor == cursors[c]) { |
||||
tokens.back().cursor_id = c; |
||||
break; |
||||
} |
||||
} |
||||
if(tokens.back().cursor_id == static_cast<size_t>(-1)) { |
||||
cursors.emplace_back(cursor); |
||||
tokens.back().cursor_id = cursors.size() - 1; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
boost::system::error_code ec; |
||||
auto last_write_time = boost::filesystem::last_write_time(path, ec); |
||||
if(ec) |
||||
last_write_time = 0; |
||||
if(last_write_time > before_parse_time) |
||||
last_write_time = 0; |
||||
paths_and_last_write_times.emplace(path, last_write_time); |
||||
|
||||
class VisitorData { |
||||
public: |
||||
const boost::filesystem::path &project_path; |
||||
const boost::filesystem::path &path; |
||||
std::time_t before_parse_time; |
||||
std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times; |
||||
}; |
||||
VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times}; |
||||
|
||||
clang_getInclusions(translation_unit->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData data) { |
||||
auto visitor_data = static_cast<VisitorData *>(data); |
||||
auto path = filesystem::get_normal_path(clangmm::to_string(clang_getFileName(included_file))); |
||||
if(filesystem::file_in_path(path, visitor_data->project_path)) { |
||||
for(unsigned c = 0; c < include_len; ++c) { |
||||
auto from_path = filesystem::get_normal_path(clangmm::SourceLocation(inclusion_stack[c]).get_path()); |
||||
if(from_path == visitor_data->path) { |
||||
boost::system::error_code ec; |
||||
auto last_write_time = boost::filesystem::last_write_time(path, ec); |
||||
if(ec) |
||||
last_write_time = 0; |
||||
if(last_write_time > visitor_data->before_parse_time) |
||||
last_write_time = 0; |
||||
visitor_data->paths_and_last_write_times.emplace(path, last_write_time); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
&visitor_data); |
||||
} |
||||
|
||||
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, |
||||
const std::unordered_set<std::string> &usrs) const { |
||||
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; |
||||
for(auto &token : tokens) { |
||||
if(token.cursor_id != static_cast<size_t>(-1)) { |
||||
auto &cursor = cursors[token.cursor_id]; |
||||
if(clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) { |
||||
for(auto &usr : cursor.usrs) { |
||||
if(usrs.count(usr)) { |
||||
offsets.emplace_back(token.offsets); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return offsets; |
||||
} |
||||
|
||||
std::vector<Usages::Clang::Usages> Usages::Clang::get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path, |
||||
const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units) { |
||||
std::vector<Usages> usages; |
||||
|
||||
if(spelling.empty()) |
||||
return usages; |
||||
|
||||
PathSet visited; |
||||
|
||||
auto usr_extended = cursor.get_usr_extended(); |
||||
if(!usr_extended.empty() && usr_extended[0] >= '0' && usr_extended[0] <= '9') { //if declared within a function, return
|
||||
if(!translation_units.empty()) |
||||
add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_units.front(), false); |
||||
return usages; |
||||
} |
||||
|
||||
for(auto &translation_unit : translation_units) |
||||
add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_unit, false); |
||||
|
||||
for(auto &translation_unit : translation_units) |
||||
add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false); |
||||
|
||||
if(project_path.empty()) |
||||
return usages; |
||||
|
||||
auto paths = find_paths(project_path, build_path, debug_path); |
||||
auto pair = parse_paths(spelling, paths); |
||||
auto pair2 = find_potential_paths(cursor.get_canonical().get_source_location().get_path(), project_path, pair.first, pair.second); |
||||
auto &potential_paths = pair2.first; |
||||
auto &all_includes = pair2.second; |
||||
|
||||
// Remove visited paths
|
||||
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||
if(visited.find(*it) != visited.end()) |
||||
it = potential_paths.erase(it); |
||||
else |
||||
++it; |
||||
} |
||||
|
||||
// Wait for current caching to finish
|
||||
std::unique_ptr<Dialog::Message> message; |
||||
const std::string message_string = "Please wait while finding usages"; |
||||
if(cache_in_progress_count != 0) { |
||||
message = std::make_unique<Dialog::Message>(message_string); |
||||
while(cache_in_progress_count != 0) { |
||||
while(Gtk::Main::events_pending()) |
||||
Gtk::Main::iteration(false); |
||||
} |
||||
} |
||||
|
||||
// Use cache
|
||||
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
auto caches_it = caches.find(*it); |
||||
|
||||
// Load cache from file if not found in memory and if cache file exists
|
||||
if(caches_it == caches.end()) { |
||||
auto cache = read_cache(project_path, build_path, *it); |
||||
if(cache) { |
||||
auto pair = caches.emplace(*it, std::move(cache)); |
||||
caches_it = pair.first; |
||||
} |
||||
} |
||||
|
||||
if(caches_it != caches.end()) { |
||||
if(add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second)) |
||||
it = potential_paths.erase(it); |
||||
else { |
||||
caches.erase(caches_it); |
||||
++it; |
||||
} |
||||
} |
||||
else |
||||
++it; |
||||
} |
||||
|
||||
// Remove paths that has been included
|
||||
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||
if(all_includes.find(*it) != all_includes.end()) |
||||
it = potential_paths.erase(it); |
||||
else |
||||
++it; |
||||
} |
||||
|
||||
// Parse potential paths
|
||||
if(!potential_paths.empty()) { |
||||
if(!message) |
||||
message = std::make_unique<Dialog::Message>(message_string); |
||||
|
||||
std::vector<std::thread> threads; |
||||
auto it = potential_paths.begin(); |
||||
auto number_of_threads = Config::get().source.clang_usages_threads; |
||||
if(number_of_threads == static_cast<unsigned>(-1)) { |
||||
number_of_threads = std::thread::hardware_concurrency(); |
||||
if(number_of_threads == 0) |
||||
number_of_threads = 1; |
||||
} |
||||
for(unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) { |
||||
threads.emplace_back([&potential_paths, &it, &build_path, |
||||
&project_path, &usages, &visited, &spelling, &cursor] { |
||||
while(true) { |
||||
boost::filesystem::path path; |
||||
{ |
||||
static std::mutex mutex; |
||||
std::unique_lock<std::mutex> lock(mutex); |
||||
if(it == potential_paths.end()) |
||||
return; |
||||
path = *it; |
||||
++it; |
||||
} |
||||
clangmm::Index index(0, 0); |
||||
|
||||
{ |
||||
static std::mutex mutex; |
||||
std::unique_lock<std::mutex> lock(mutex); |
||||
// std::cout << "parsing: " << path << std::endl;
|
||||
} |
||||
// auto before_time = std::chrono::system_clock::now();
|
||||
|
||||
std::ifstream stream(path.string(), std::ifstream::binary); |
||||
std::string buffer; |
||||
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); |
||||
|
||||
auto arguments = CompileCommands::get_arguments(build_path, path); |
||||
arguments.emplace_back("-w"); // Disable all warnings
|
||||
for(auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers
|
||||
if(*it == "-fretain-comments-from-system-headers") |
||||
it = arguments.erase(it); |
||||
else |
||||
++it; |
||||
} |
||||
int flags = CXTranslationUnit_Incomplete; |
||||
#if CINDEX_VERSION_MAJOR > 0 || (CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR >= 35) |
||||
flags |= CXTranslationUnit_KeepGoing; |
||||
#endif |
||||
|
||||
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags); |
||||
|
||||
{ |
||||
static std::mutex mutex; |
||||
std::unique_lock<std::mutex> lock(mutex); |
||||
add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true); |
||||
add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true); |
||||
} |
||||
|
||||
// auto time = std::chrono::system_clock::now();
|
||||
// std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl;
|
||||
} |
||||
}); |
||||
} |
||||
for(auto &thread : threads) |
||||
thread.join(); |
||||
} |
||||
|
||||
if(message) |
||||
message->hide(); |
||||
|
||||
return usages; |
||||
} |
||||
|
||||
void Usages::Clang::cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||
std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens) { |
||||
class ScopeExit { |
||||
public: |
||||
std::function<void()> f; |
||||
~ScopeExit() { |
||||
f(); |
||||
} |
||||
}; |
||||
ScopeExit scope_exit{[] { |
||||
--cache_in_progress_count; |
||||
}}; |
||||
|
||||
if(project_path.empty()) |
||||
return; |
||||
|
||||
{ |
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
if(project_paths_in_use.count(project_path)) { |
||||
caches.erase(path); |
||||
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); |
||||
} |
||||
else |
||||
write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); |
||||
} |
||||
|
||||
class VisitorData { |
||||
public: |
||||
const boost::filesystem::path &project_path; |
||||
PathSet paths; |
||||
}; |
||||
VisitorData visitor_data{project_path, {}}; |
||||
|
||||
auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); |
||||
clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { |
||||
auto visitor_data = static_cast<VisitorData *>(data); |
||||
|
||||
auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); |
||||
if(filesystem::file_in_path(path, visitor_data->project_path)) |
||||
visitor_data->paths.emplace(path); |
||||
|
||||
return CXChildVisit_Continue; |
||||
}, |
||||
&visitor_data); |
||||
|
||||
visitor_data.paths.erase(path); |
||||
|
||||
for(auto &path : visitor_data.paths) { |
||||
boost::system::error_code ec; |
||||
auto file_size = boost::filesystem::file_size(path, ec); |
||||
if(file_size == static_cast<boost::uintmax_t>(-1) || ec) |
||||
continue; |
||||
auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); |
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
if(project_paths_in_use.count(project_path)) { |
||||
caches.erase(path); |
||||
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); |
||||
} |
||||
else |
||||
write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); |
||||
} |
||||
} |
||||
|
||||
void Usages::Clang::erase_unused_caches(const PathSet &project_paths_in_use) { |
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
for(auto it = caches.begin(); it != caches.end();) { |
||||
bool found = false; |
||||
for(auto &project_path : project_paths_in_use) { |
||||
if(filesystem::file_in_path(it->first, project_path)) { |
||||
found = true; |
||||
break; |
||||
} |
||||
} |
||||
if(!found) { |
||||
write_cache(it->first, it->second); |
||||
it = caches.erase(it); |
||||
} |
||||
else |
||||
++it; |
||||
} |
||||
} |
||||
|
||||
void Usages::Clang::erase_cache(const boost::filesystem::path &path) { |
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
|
||||
auto it = caches.find(path); |
||||
if(it == caches.end()) |
||||
return; |
||||
|
||||
auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times); |
||||
for(auto &path_and_last_write_time : paths_and_last_write_times) |
||||
caches.erase(path_and_last_write_time.first); |
||||
} |
||||
|
||||
void Usages::Clang::erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) { |
||||
if(project_path.empty()) |
||||
return; |
||||
|
||||
if(cache_in_progress_count != 0) |
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); |
||||
|
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
boost::system::error_code ec; |
||||
auto usages_clang_path = build_path / cache_folder; |
||||
if(boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) { |
||||
for(boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) { |
||||
if(it->path().extension() == ".usages") |
||||
boost::filesystem::remove(it->path(), ec); |
||||
} |
||||
} |
||||
|
||||
for(auto it = caches.begin(); it != caches.end();) { |
||||
if(filesystem::file_in_path(it->first, project_path)) |
||||
it = caches.erase(it); |
||||
else |
||||
++it; |
||||
} |
||||
} |
||||
|
||||
void Usages::Clang::cache_in_progress() { |
||||
++cache_in_progress_count; |
||||
} |
||||
|
||||
void Usages::Clang::add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_, |
||||
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, |
||||
clangmm::TranslationUnit *translation_unit, bool store_in_cache) { |
||||
std::unique_ptr<clangmm::Tokens> tokens; |
||||
boost::filesystem::path path; |
||||
auto before_parse_time = std::time(nullptr); |
||||
auto all_usr_extended = cursor.get_all_usr_extended(); |
||||
if(path_.empty()) { |
||||
path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu)); |
||||
if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) |
||||
return; |
||||
tokens = translation_unit->get_tokens(); |
||||
} |
||||
else { |
||||
path = path_; |
||||
if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) |
||||
return; |
||||
boost::system::error_code ec; |
||||
auto file_size = boost::filesystem::file_size(path, ec); |
||||
if(file_size == static_cast<boost::uintmax_t>(-1) || ec) |
||||
return; |
||||
tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); |
||||
} |
||||
|
||||
auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended); |
||||
std::vector<std::string> lines; |
||||
for(auto &offset : offsets) { |
||||
std::string line; |
||||
auto line_nr = offset.second.line; |
||||
for(auto &token : *tokens) { |
||||
auto offset = token.get_source_location().get_offset(); |
||||
if(offset.line == line_nr) { |
||||
while(line.size() < offset.index - 1) |
||||
line += ' '; |
||||
line += token.get_spelling(); |
||||
} |
||||
} |
||||
lines.emplace_back(std::move(line)); |
||||
} |
||||
|
||||
if(store_in_cache && filesystem::file_in_path(path, project_path)) { |
||||
std::unique_lock<std::mutex> lock(caches_mutex); |
||||
caches.erase(path); |
||||
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); |
||||
} |
||||
|
||||
visited.emplace(path); |
||||
if(!offsets.empty()) |
||||
usages.emplace_back(Usages{std::move(path), std::move(offsets), lines}); |
||||
} |
||||
|
||||
bool Usages::Clang::add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, |
||||
const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache) { |
||||
for(auto &path_and_last_write_time : cache.paths_and_last_write_times) { |
||||
boost::system::error_code ec; |
||||
auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec); |
||||
if(ec || last_write_time > path_and_last_write_time.second) { |
||||
// std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl;
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended()); |
||||
|
||||
std::vector<std::string> lines; |
||||
for(auto &offset : offsets) { |
||||
std::string line; |
||||
auto line_nr = offset.second.line; |
||||
for(auto &token : cache.tokens) { |
||||
auto &offset = token.offsets.first; |
||||
if(offset.line == line_nr) { |
||||
while(line.size() < offset.index - 1) |
||||
line += ' '; |
||||
line += token.spelling; |
||||
} |
||||
} |
||||
lines.emplace_back(std::move(line)); |
||||
} |
||||
|
||||
visited.emplace(path); |
||||
if(!offsets.empty()) |
||||
usages.emplace_back(Usages{path, std::move(offsets), lines}); |
||||
return true; |
||||
} |
||||
|
||||
void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, |
||||
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, |
||||
clangmm::TranslationUnit *translation_unit, bool store_in_cache) { |
||||
if(project_path.empty()) |
||||
return; |
||||
|
||||
class VisitorData { |
||||
public: |
||||
const boost::filesystem::path &project_path; |
||||
const std::string &spelling; |
||||
PathSet &visited; |
||||
PathSet paths; |
||||
}; |
||||
VisitorData visitor_data{project_path, spelling, visited, {}}; |
||||
|
||||
auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); |
||||
clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { |
||||
auto visitor_data = static_cast<VisitorData *>(data); |
||||
|
||||
auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); |
||||
if(visitor_data->visited.find(path) == visitor_data->visited.end() && filesystem::file_in_path(path, visitor_data->project_path)) |
||||
visitor_data->paths.emplace(path); |
||||
|
||||
return CXChildVisit_Continue; |
||||
}, |
||||
&visitor_data); |
||||
|
||||
for(auto &path : visitor_data.paths) |
||||
add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache); |
||||
} |
||||
|
||||
Usages::Clang::PathSet Usages::Clang::find_paths(const boost::filesystem::path &project_path, |
||||
const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path) { |
||||
PathSet paths; |
||||
|
||||
CompileCommands compile_commands(build_path); |
||||
|
||||
for(boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) { |
||||
auto &path = it->path(); |
||||
if(!boost::filesystem::is_regular_file(path)) { |
||||
if(path == build_path || path == debug_path || path.filename() == ".git") |
||||
it.no_push(); |
||||
continue; |
||||
} |
||||
|
||||
if(is_header(path)) |
||||
paths.emplace(path); |
||||
else if(is_source(path)) { |
||||
for(auto &command : compile_commands.commands) { |
||||
if(filesystem::get_normal_path(command.file) == path) { |
||||
paths.emplace(path); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return paths; |
||||
} |
||||
|
||||
bool Usages::Clang::is_header(const boost::filesystem::path &path) { |
||||
auto ext = path.extension(); |
||||
if(ext == ".h" || // c headers
|
||||
ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc") // c++ headers
|
||||
return true; |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
bool Usages::Clang::is_source(const boost::filesystem::path &path) { |
||||
auto ext = path.extension(); |
||||
if(ext == ".c" || // c sources
|
||||
ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++") // c++ sources
|
||||
return true; |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
std::pair<std::map<boost::filesystem::path, Usages::Clang::PathSet>, Usages::Clang::PathSet> Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) { |
||||
std::map<boost::filesystem::path, PathSet> paths_includes; |
||||
PathSet paths_with_spelling; |
||||
|
||||
const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$"); |
||||
|
||||
auto is_spelling_char = [](char chr) { |
||||
return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; |
||||
}; |
||||
|
||||
for(auto &path : paths) { |
||||
auto paths_includes_it = paths_includes.emplace(path, PathSet()).first; |
||||
bool paths_with_spelling_emplaced = false; |
||||
|
||||
std::ifstream stream(path.string(), std::ifstream::binary); |
||||
if(!stream) |
||||
continue; |
||||
std::string line; |
||||
while(std::getline(stream, line)) { |
||||
std::smatch sm; |
||||
if(std::regex_match(line, sm, include_regex)) { |
||||
boost::filesystem::path path(sm[1].str()); |
||||
boost::filesystem::path include_path; |
||||
// remove .. and .
|
||||
for(auto &part : path) { |
||||
if(part == "..") |
||||
include_path = include_path.parent_path(); |
||||
else if(part == ".") |
||||
continue; |
||||
else |
||||
include_path /= part; |
||||
} |
||||
auto distance = std::distance(include_path.begin(), include_path.end()); |
||||
for(auto &path : paths) { |
||||
auto path_distance = std::distance(path.begin(), path.end()); |
||||
if(path_distance >= distance) { |
||||
auto it = path.begin(); |
||||
std::advance(it, path_distance - distance); |
||||
if(std::equal(it, path.end(), include_path.begin(), include_path.end())) |
||||
paths_includes_it->second.emplace(path); |
||||
} |
||||
} |
||||
} |
||||
else if(!paths_with_spelling_emplaced) { |
||||
auto pos = line.find(spelling); |
||||
if(pos != std::string::npos && |
||||
((!spelling.empty() && !is_spelling_char(spelling[0])) || |
||||
((pos == 0 || !is_spelling_char(line[pos - 1])) && |
||||
(pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) { |
||||
paths_with_spelling.emplace(path); |
||||
paths_with_spelling_emplaced = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return {paths_includes, paths_with_spelling}; |
||||
} |
||||
|
||||
Usages::Clang::PathSet Usages::Clang::get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes) { |
||||
PathSet all_includes; |
||||
|
||||
class Recursive { |
||||
public: |
||||
static void f(PathSet &all_includes, const boost::filesystem::path &path, |
||||
const std::map<boost::filesystem::path, PathSet> &paths_includes) { |
||||
auto paths_includes_it = paths_includes.find(path); |
||||
if(paths_includes_it != paths_includes.end()) { |
||||
for(auto &include : paths_includes_it->second) { |
||||
auto pair = all_includes.emplace(include); |
||||
if(pair.second) |
||||
f(all_includes, include, paths_includes); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
Recursive::f(all_includes, path, paths_includes); |
||||
|
||||
return all_includes; |
||||
} |
||||
|
||||
std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_potential_paths(const boost::filesystem::path &path, const boost::filesystem::path &project_path, |
||||
const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling) { |
||||
PathSet potential_paths; |
||||
PathSet all_includes; |
||||
|
||||
if(filesystem::file_in_path(path, project_path)) { |
||||
for(auto &path_with_spelling : paths_with_spelling) { |
||||
auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); |
||||
if((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { |
||||
potential_paths.emplace(path_with_spelling); |
||||
|
||||
for(auto &include : path_all_includes) |
||||
all_includes.emplace(include); |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
for(auto &path_with_spelling : paths_with_spelling) { |
||||
potential_paths.emplace(path_with_spelling); |
||||
|
||||
auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); |
||||
for(auto &include : path_all_includes) |
||||
all_includes.emplace(include); |
||||
} |
||||
} |
||||
|
||||
return {potential_paths, all_includes}; |
||||
} |
||||
|
||||
void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang::Cache &cache) { |
||||
auto cache_path = cache.build_path / cache_folder; |
||||
boost::system::error_code ec; |
||||
if(!boost::filesystem::exists(cache_path, ec)) { |
||||
boost::filesystem::create_directory(cache_path, ec); |
||||
if(ec) |
||||
return; |
||||
} |
||||
else if(!boost::filesystem::is_directory(cache_path, ec) || ec) |
||||
return; |
||||
|
||||
auto path_str = filesystem::get_relative_path(path, cache.project_path).string(); |
||||
for(auto &chr : path_str) { |
||||
if(chr == '/' || chr == '\\') |
||||
chr = '_'; |
||||
} |
||||
auto full_cache_path = cache_path / (path_str + ".usages"); |
||||
|
||||
std::ofstream stream(full_cache_path.string()); |
||||
if(stream) { |
||||
try { |
||||
boost::archive::text_oarchive text_oarchive(stream); |
||||
text_oarchive << cache; |
||||
} |
||||
catch(...) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
Usages::Clang::Cache Usages::Clang::read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path) { |
||||
auto path_str = filesystem::get_relative_path(path, project_path).string(); |
||||
for(auto &chr : path_str) { |
||||
if(chr == '/' || chr == '\\') |
||||
chr = '_'; |
||||
} |
||||
auto cache_path = build_path / cache_folder / (path_str + ".usages"); |
||||
|
||||
boost::system::error_code ec; |
||||
if(boost::filesystem::exists(cache_path, ec)) { |
||||
std::ifstream stream(cache_path.string()); |
||||
if(stream) { |
||||
Cache cache; |
||||
boost::archive::text_iarchive text_iarchive(stream); |
||||
try { |
||||
text_iarchive >> cache; |
||||
return cache; |
||||
} |
||||
catch(...) { |
||||
} |
||||
} |
||||
} |
||||
return Cache(); |
||||
} |
||||
@ -0,0 +1,151 @@
|
||||
#ifndef JUCI_USAGES_CLANG_H_ |
||||
#define JUCI_USAGES_CLANG_H_ |
||||
#include "clangmm.h" |
||||
#include <atomic> |
||||
#include <boost/archive/text_iarchive.hpp> |
||||
#include <boost/archive/text_oarchive.hpp> |
||||
#include <boost/filesystem.hpp> |
||||
#include <boost/serialization/map.hpp> |
||||
#include <boost/serialization/unordered_set.hpp> |
||||
#include <boost/serialization/vector.hpp> |
||||
#include <map> |
||||
#include <mutex> |
||||
#include <regex> |
||||
#include <set> |
||||
#include <unordered_set> |
||||
|
||||
namespace boost { |
||||
namespace serialization { |
||||
template <class Archive> |
||||
void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) { |
||||
std::string path_str; |
||||
if(Archive::is_saving::value) |
||||
path_str = path.string(); |
||||
ar &path_str; |
||||
if(Archive::is_loading::value) |
||||
path = path_str; |
||||
} |
||||
} // namespace serialization
|
||||
} // namespace boost
|
||||
|
||||
namespace Usages { |
||||
class Clang { |
||||
public: |
||||
typedef std::set<boost::filesystem::path> PathSet; |
||||
|
||||
class Usages { |
||||
public: |
||||
boost::filesystem::path path; |
||||
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; |
||||
std::vector<std::string> lines; |
||||
}; |
||||
|
||||
class Cache { |
||||
friend class boost::serialization::access; |
||||
template <class Archive> |
||||
void serialize(Archive &ar, const unsigned int version) { |
||||
ar &project_path; |
||||
ar &build_path; |
||||
ar &tokens; |
||||
ar &cursors; |
||||
ar &paths_and_last_write_times; |
||||
} |
||||
|
||||
public: |
||||
class Cursor { |
||||
friend class boost::serialization::access; |
||||
template <class Archive> |
||||
void serialize(Archive &ar, const unsigned int version) { |
||||
ar &kind; |
||||
ar &usrs; |
||||
} |
||||
|
||||
public: |
||||
clangmm::Cursor::Kind kind; |
||||
std::unordered_set<std::string> usrs; |
||||
|
||||
bool operator==(const Cursor &o); |
||||
}; |
||||
|
||||
class Token { |
||||
friend class boost::serialization::access; |
||||
template <class Archive> |
||||
void serialize(Archive &ar, const unsigned int version) { |
||||
ar &spelling; |
||||
ar &offsets.first.line &offsets.first.index; |
||||
ar &offsets.second.line &offsets.second.index; |
||||
ar &cursor_id; |
||||
} |
||||
|
||||
public: |
||||
std::string spelling; |
||||
std::pair<clangmm::Offset, clangmm::Offset> offsets; |
||||
size_t cursor_id; |
||||
}; |
||||
|
||||
boost::filesystem::path project_path; |
||||
boost::filesystem::path build_path; |
||||
|
||||
std::vector<Token> tokens; |
||||
std::vector<Cursor> cursors; |
||||
std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times; |
||||
|
||||
Cache() {} |
||||
Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||
std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens); |
||||
|
||||
operator bool() const { return !paths_and_last_write_times.empty(); } |
||||
|
||||
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, |
||||
const std::unordered_set<std::string> &usrs) const; |
||||
}; |
||||
|
||||
private: |
||||
const static boost::filesystem::path cache_folder; |
||||
|
||||
static std::map<boost::filesystem::path, Cache> caches; |
||||
static std::mutex caches_mutex; |
||||
|
||||
static std::atomic<size_t> cache_in_progress_count; |
||||
|
||||
public: |
||||
static std::vector<Usages> get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path, |
||||
const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units); |
||||
|
||||
static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, |
||||
std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens); |
||||
static void erase_unused_caches(const PathSet &project_paths_in_use); |
||||
static void erase_cache(const boost::filesystem::path &path); |
||||
static void erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path); |
||||
static void cache_in_progress(); |
||||
|
||||
private: |
||||
static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_, |
||||
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, |
||||
clangmm::TranslationUnit *translation_unit, bool store_in_cache); |
||||
|
||||
static bool add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, |
||||
const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache); |
||||
|
||||
static void add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, |
||||
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, |
||||
clangmm::TranslationUnit *translation_unit, bool store_in_cache); |
||||
|
||||
static PathSet find_paths(const boost::filesystem::path &project_path, |
||||
const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path); |
||||
|
||||
static bool is_header(const boost::filesystem::path &path); |
||||
static bool is_source(const boost::filesystem::path &path); |
||||
|
||||
static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> parse_paths(const std::string &spelling, const PathSet &paths); |
||||
|
||||
static PathSet get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes); |
||||
|
||||
static std::pair<Clang::PathSet, Clang::PathSet> find_potential_paths(const boost::filesystem::path &project_path, const boost::filesystem::path &include_path, |
||||
const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling); |
||||
|
||||
static void write_cache(const boost::filesystem::path &path, const Cache &cache); |
||||
static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path); |
||||
}; |
||||
} // namespace Usages
|
||||
#endif // JUCI_USAGES_CLANG_H_
|
||||
@ -0,0 +1,273 @@
|
||||
#include "clangmm.h" |
||||
#include "compile_commands.h" |
||||
#include "meson.h" |
||||
#include "project.h" |
||||
#include "usages_clang.h" |
||||
#include <cassert> |
||||
#include <fstream> |
||||
|
||||
#include <iostream> |
||||
|
||||
int main() { |
||||
auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH); |
||||
auto project_path = boost::filesystem::canonical(tests_path / "usages_clang_test_files"); |
||||
auto build_path = project_path / "build"; |
||||
|
||||
auto build = Project::Build::create(project_path); |
||||
g_assert(build->project_path == project_path); |
||||
|
||||
{ |
||||
clangmm::Index index(0, 0); |
||||
auto path = project_path / "main.cpp"; |
||||
std::ifstream stream(path.string(), std::ifstream::binary); |
||||
assert(stream); |
||||
std::string buffer; |
||||
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); |
||||
auto arguments = CompileCommands::get_arguments(build_path, path); |
||||
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer); |
||||
auto tokens = translation_unit.get_tokens(); |
||||
clangmm::Token *found_token = nullptr; |
||||
for(auto &token : *tokens) { |
||||
if(token.get_spelling() == "a") { |
||||
found_token = &token; |
||||
break; |
||||
} |
||||
} |
||||
assert(found_token); |
||||
auto spelling = found_token->get_spelling(); |
||||
auto cursor = found_token->get_cursor().get_referenced(); |
||||
std::vector<Usages::Clang::Usages> usages; |
||||
Usages::Clang::PathSet visited; |
||||
|
||||
Usages::Clang::add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, &translation_unit, false); |
||||
assert(usages.size() == 1); |
||||
assert(usages[0].path == path); |
||||
assert(usages[0].lines.size() == 1); |
||||
assert(usages[0].lines[0] == " test.a=2;"); |
||||
assert(usages[0].offsets.size() == 1); |
||||
assert(usages[0].offsets[0].first.line == 6); |
||||
assert(usages[0].offsets[0].first.index == 8); |
||||
assert(usages[0].offsets[0].second.line == 6); |
||||
assert(usages[0].offsets[0].second.index == 9); |
||||
|
||||
Usages::Clang::add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, false); |
||||
assert(usages.size() == 2); |
||||
assert(usages[1].path == project_path / "test.hpp"); |
||||
assert(usages[1].lines.size() == 2); |
||||
assert(usages[1].lines[0] == " int a=0;"); |
||||
assert(usages[1].lines[1] == " ++a;"); |
||||
assert(usages[1].offsets.size() == 2); |
||||
assert(usages[1].offsets[0].first.line == 6); |
||||
assert(usages[1].offsets[0].first.index == 7); |
||||
assert(usages[1].offsets[0].second.line == 6); |
||||
assert(usages[1].offsets[0].second.index == 8); |
||||
assert(usages[1].offsets[1].first.line == 8); |
||||
assert(usages[1].offsets[1].first.index == 7); |
||||
assert(usages[1].offsets[1].second.line == 8); |
||||
assert(usages[1].offsets[1].second.index == 8); |
||||
|
||||
auto paths = Usages::Clang::find_paths(project_path, build_path, build_path / "debug"); |
||||
auto pair = Usages::Clang::parse_paths(spelling, paths); |
||||
|
||||
auto &paths_includes = pair.first; |
||||
assert(paths_includes.size() == 3); |
||||
assert(paths_includes.find(project_path / "main.cpp") != paths_includes.end()); |
||||
assert(paths_includes.find(project_path / "test.hpp") != paths_includes.end()); |
||||
assert(paths_includes.find(project_path / "test2.hpp") != paths_includes.end()); |
||||
|
||||
auto &paths_with_spelling = pair.second; |
||||
assert(paths_with_spelling.size() == 3); |
||||
assert(paths_with_spelling.find(project_path / "main.cpp") != paths_with_spelling.end()); |
||||
assert(paths_with_spelling.find(project_path / "test.hpp") != paths_with_spelling.end()); |
||||
assert(paths_with_spelling.find(project_path / "test2.hpp") != paths_with_spelling.end()); |
||||
|
||||
auto pair2 = Usages::Clang::find_potential_paths(cursor.get_canonical().get_source_location().get_path(), project_path, pair.first, pair.second); |
||||
|
||||
auto &potential_paths = pair2.first; |
||||
assert(potential_paths.size() == 3); |
||||
assert(potential_paths.find(project_path / "main.cpp") != potential_paths.end()); |
||||
assert(potential_paths.find(project_path / "test.hpp") != potential_paths.end()); |
||||
assert(potential_paths.find(project_path / "test2.hpp") != potential_paths.end()); |
||||
|
||||
auto &all_includes = pair2.second; |
||||
assert(all_includes.size() == 1); |
||||
assert(*all_includes.begin() == project_path / "test.hpp"); |
||||
|
||||
// Remove visited paths
|
||||
for(auto it = potential_paths.begin(); it != potential_paths.end();) { |
||||
if(visited.find(*it) != visited.end()) |
||||
it = potential_paths.erase(it); |
||||
else |
||||
++it; |
||||
} |
||||
|
||||
assert(potential_paths.size() == 1); |
||||
assert(*potential_paths.begin() == project_path / "test2.hpp"); |
||||
|
||||
{ |
||||
auto path = *potential_paths.begin(); |
||||
std::ifstream stream(path.string(), std::ifstream::binary); |
||||
std::string buffer; |
||||
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); |
||||
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer); |
||||
Usages::Clang::add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true); |
||||
Usages::Clang::add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true); |
||||
assert(usages.size() == 3); |
||||
assert(usages[2].path == path); |
||||
assert(usages[2].lines.size() == 1); |
||||
assert(usages[2].lines[0] == " ++a;"); |
||||
assert(usages[2].offsets.size() == 1); |
||||
assert(usages[2].offsets[0].first.line == 5); |
||||
assert(usages[2].offsets[0].first.index == 7); |
||||
assert(usages[2].offsets[0].second.line == 5); |
||||
assert(usages[2].offsets[0].second.index == 8); |
||||
} |
||||
|
||||
assert(Usages::Clang::caches.size() == 1); |
||||
auto cache_it = Usages::Clang::caches.begin(); |
||||
assert(cache_it->first == project_path / "test2.hpp"); |
||||
assert(cache_it->second.build_path == build_path); |
||||
assert(cache_it->second.paths_and_last_write_times.size() == 2); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test2.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.tokens.size()); |
||||
assert(cache_it->second.cursors.size()); |
||||
{ |
||||
std::vector<Usages::Clang::Usages> usages; |
||||
Usages::Clang::PathSet visited; |
||||
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||
assert(usages.size() == 1); |
||||
assert(usages[0].path == cache_it->first); |
||||
assert(usages[0].lines.size() == 1); |
||||
assert(usages[0].lines[0] == " ++a;"); |
||||
assert(usages[0].offsets.size() == 1); |
||||
assert(usages[0].offsets[0].first.line == 5); |
||||
assert(usages[0].offsets[0].first.index == 7); |
||||
assert(usages[0].offsets[0].second.line == 5); |
||||
assert(usages[0].offsets[0].second.index == 8); |
||||
} |
||||
|
||||
Usages::Clang::erase_unused_caches({project_path}); |
||||
Usages::Clang::cache(project_path, build_path, path, time(nullptr), {project_path}, &translation_unit, tokens.get()); |
||||
assert(Usages::Clang::caches.size() == 3); |
||||
assert(Usages::Clang::caches.find(project_path / "main.cpp") != Usages::Clang::caches.end()); |
||||
assert(Usages::Clang::caches.find(project_path / "test.hpp") != Usages::Clang::caches.end()); |
||||
assert(Usages::Clang::caches.find(project_path / "test2.hpp") != Usages::Clang::caches.end()); |
||||
|
||||
Usages::Clang::erase_unused_caches({}); |
||||
Usages::Clang::cache(project_path, build_path, path, time(nullptr), {}, &translation_unit, tokens.get()); |
||||
assert(Usages::Clang::caches.size() == 0); |
||||
|
||||
auto cache = Usages::Clang::read_cache(project_path, build_path, project_path / "main.cpp"); |
||||
assert(cache); |
||||
Usages::Clang::caches.emplace(project_path / "main.cpp", std::move(cache)); |
||||
assert(!cache); |
||||
assert(Usages::Clang::caches.size() == 1); |
||||
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test.hpp"); |
||||
assert(cache); |
||||
Usages::Clang::caches.emplace(project_path / "test.hpp", std::move(cache)); |
||||
assert(!cache); |
||||
assert(Usages::Clang::caches.size() == 2); |
||||
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test2.hpp"); |
||||
assert(cache); |
||||
Usages::Clang::caches.emplace(project_path / "test2.hpp", std::move(cache)); |
||||
assert(!cache); |
||||
assert(Usages::Clang::caches.size() == 3); |
||||
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test_not_existing.hpp"); |
||||
assert(!cache); |
||||
assert(Usages::Clang::caches.size() == 3); |
||||
{ |
||||
auto cache_it = Usages::Clang::caches.find(project_path / "main.cpp"); |
||||
assert(cache_it != Usages::Clang::caches.end()); |
||||
assert(cache_it->first == project_path / "main.cpp"); |
||||
assert(cache_it->second.build_path == build_path); |
||||
assert(cache_it->second.paths_and_last_write_times.size() == 2); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "main.cpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.tokens.size()); |
||||
assert(cache_it->second.cursors.size()); |
||||
{ |
||||
std::vector<Usages::Clang::Usages> usages; |
||||
Usages::Clang::PathSet visited; |
||||
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||
assert(usages.size() == 1); |
||||
assert(usages[0].path == cache_it->first); |
||||
assert(usages[0].lines.size() == 1); |
||||
assert(usages[0].lines[0] == " test.a=2;"); |
||||
assert(usages[0].offsets.size() == 1); |
||||
assert(usages[0].offsets[0].first.line == 6); |
||||
assert(usages[0].offsets[0].first.index == 8); |
||||
assert(usages[0].offsets[0].second.line == 6); |
||||
assert(usages[0].offsets[0].second.index == 9); |
||||
} |
||||
} |
||||
{ |
||||
auto cache_it = Usages::Clang::caches.find(project_path / "test.hpp"); |
||||
assert(cache_it != Usages::Clang::caches.end()); |
||||
assert(cache_it->first == project_path / "test.hpp"); |
||||
assert(cache_it->second.build_path == build_path); |
||||
assert(cache_it->second.paths_and_last_write_times.size() == 1); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.tokens.size()); |
||||
assert(cache_it->second.cursors.size()); |
||||
{ |
||||
std::vector<Usages::Clang::Usages> usages; |
||||
Usages::Clang::PathSet visited; |
||||
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||
assert(usages.size() == 1); |
||||
assert(usages[0].path == cache_it->first); |
||||
assert(usages[0].lines.size() == 2); |
||||
assert(usages[0].lines[0] == " int a=0;"); |
||||
assert(usages[0].lines[1] == " ++a;"); |
||||
assert(usages[0].offsets.size() == 2); |
||||
assert(usages[0].offsets[0].first.line == 6); |
||||
assert(usages[0].offsets[0].first.index == 7); |
||||
assert(usages[0].offsets[0].second.line == 6); |
||||
assert(usages[0].offsets[0].second.index == 8); |
||||
assert(usages[0].offsets[1].first.line == 8); |
||||
assert(usages[0].offsets[1].first.index == 7); |
||||
assert(usages[0].offsets[1].second.line == 8); |
||||
assert(usages[0].offsets[1].second.index == 8); |
||||
} |
||||
} |
||||
{ |
||||
auto cache_it = Usages::Clang::caches.find(project_path / "test2.hpp"); |
||||
assert(cache_it != Usages::Clang::caches.end()); |
||||
assert(cache_it->first == project_path / "test2.hpp"); |
||||
assert(cache_it->second.build_path == build_path); |
||||
assert(cache_it->second.paths_and_last_write_times.size() == 2); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test2.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end()); |
||||
assert(cache_it->second.tokens.size()); |
||||
assert(cache_it->second.cursors.size()); |
||||
{ |
||||
std::vector<Usages::Clang::Usages> usages; |
||||
Usages::Clang::PathSet visited; |
||||
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second); |
||||
assert(usages.size() == 1); |
||||
assert(usages[0].path == cache_it->first); |
||||
assert(usages[0].lines.size() == 1); |
||||
assert(usages[0].lines[0] == " ++a;"); |
||||
assert(usages[0].offsets.size() == 1); |
||||
assert(usages[0].offsets[0].first.line == 5); |
||||
assert(usages[0].offsets[0].first.index == 7); |
||||
assert(usages[0].offsets[0].second.line == 5); |
||||
assert(usages[0].offsets[0].second.index == 8); |
||||
} |
||||
} |
||||
} |
||||
{ |
||||
assert(!Usages::Clang::caches.empty()); |
||||
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder)); |
||||
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"main.cpp.usages")); |
||||
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test.hpp.usages")); |
||||
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test2.hpp.usages")); |
||||
|
||||
Usages::Clang::erase_all_caches_for_project(project_path, build_path); |
||||
assert(Usages::Clang::caches.empty()); |
||||
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder)); |
||||
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"main.cpp.usages")); |
||||
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test.hpp.usages")); |
||||
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test2.hpp.usages")); |
||||
} |
||||
} |
||||
@ -0,0 +1,7 @@
|
||||
[ |
||||
{ |
||||
"directory": "jucipp/tests/usages_clang_test_files/build", |
||||
"command": "c++ -Ihello@exe -I. -I.. -Xclang -fcolor-diagnostics -pipe -Wall -Winvalid-pch -Wnon-virtual-dtor -O0 -g -std=c++11 -Wall -Wextra -MMD -MQ 'hello@exe/main.cpp.o' -MF 'hello@exe/main.cpp.o.d' -o 'hello@exe/main.cpp.o' -c ../main.cpp", |
||||
"file": "../main.cpp" |
||||
} |
||||
] |
||||
@ -0,0 +1,9 @@
|
||||
#include <iostream> |
||||
#include "test.hpp" |
||||
|
||||
int main() { |
||||
Test test; |
||||
test.a=2; |
||||
test.b(); |
||||
test.c(); |
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
project('hello.world', 'cpp') |
||||
|
||||
compiler_args = ['-std=c++11', '-Wall', '-Wextra'] |
||||
|
||||
executable('hello', 'main.cpp', cpp_args: compiler_args) |
||||
@ -0,0 +1,13 @@
|
||||
class Test { |
||||
public: |
||||
Test() {} |
||||
~Test() {} |
||||
|
||||
int a=0; |
||||
void b() { |
||||
++a; |
||||
} |
||||
void c() { |
||||
b(); |
||||
} |
||||
}; |
||||
Loading…
Reference in new issue