diff --git a/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file/a.cpp b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file/a.cpp new file mode 100644 index 00000000..102612d5 --- /dev/null +++ b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file/a.cpp @@ -0,0 +1 @@ +#include "a.h" \ No newline at end of file diff --git a/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file/a.h b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file/a.h new file mode 100644 index 00000000..e69de29b diff --git a/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/a.cpp b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/a.cpp new file mode 100644 index 00000000..102612d5 --- /dev/null +++ b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/a.cpp @@ -0,0 +1 @@ +#include "a.h" \ No newline at end of file diff --git a/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/include/a.h b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/include/a.h new file mode 100644 index 00000000..e69de29b diff --git a/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/a.cpp b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/a.cpp new file mode 100644 index 00000000..dfd7cd03 --- /dev/null +++ b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/a.cpp @@ -0,0 +1 @@ +#include "../a.h" \ No newline at end of file diff --git a/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/a.h b/bin/test/data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/a.h new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/utility/file/FilePath.cpp b/src/lib/utility/file/FilePath.cpp index e89a2822..70a7843a 100644 --- a/src/lib/utility/file/FilePath.cpp +++ b/src/lib/utility/file/FilePath.cpp @@ -128,13 +128,17 @@ bool FilePath::isAbsolute() const FilePath FilePath::getParentDirectory() const { FilePath parentDirectory(m_path->parent_path()); - parentDirectory.m_checkedIsDirectory = true; - parentDirectory.m_isDirectory = true; - if (m_checkedExists && m_exists) + if (!parentDirectory.empty()) { - parentDirectory.m_checkedExists = true; - parentDirectory.m_exists = true; + parentDirectory.m_checkedIsDirectory = true; + parentDirectory.m_isDirectory = true; + + if (m_checkedExists && m_exists) + { + parentDirectory.m_checkedExists = true; + parentDirectory.m_exists = true; + } } return parentDirectory; diff --git a/src/lib/utility/file/FileSystem.cpp b/src/lib/utility/file/FileSystem.cpp index 4bb7e731..68a81320 100644 --- a/src/lib/utility/file/FileSystem.cpp +++ b/src/lib/utility/file/FileSystem.cpp @@ -30,7 +30,7 @@ std::vector FileSystem::getFilePathsFromDirectory( } } - if (boost::filesystem::is_regular_file(*it) && ext.find(it->path().extension().string()) != ext.end()) + if (boost::filesystem::is_regular_file(*it) && (ext.empty() || ext.find(it->path().extension().string()) != ext.end())) { files.push_back(FilePath(it->path().generic_string())); } diff --git a/src/lib/utility/file/FileSystem.h b/src/lib/utility/file/FileSystem.h index 25b18146..6c5fbd75 100644 --- a/src/lib/utility/file/FileSystem.h +++ b/src/lib/utility/file/FileSystem.h @@ -12,7 +12,7 @@ class FileSystem { public: static std::vector getFilePathsFromDirectory( - const FilePath& path, const std::vector& extensions); + const FilePath& path, const std::vector& extensions = {}); static FileInfo getFileInfoForPath(const FilePath& filePath); diff --git a/src/lib/utility/text/TextAccess.cpp b/src/lib/utility/text/TextAccess.cpp index 3a21fbbc..7bea33c4 100644 --- a/src/lib/utility/text/TextAccess.cpp +++ b/src/lib/utility/text/TextAccess.cpp @@ -14,11 +14,12 @@ std::shared_ptr TextAccess::createFromFile(const FilePath& filePath) return result; } -std::shared_ptr TextAccess::createFromString(const std::string& text) +std::shared_ptr TextAccess::createFromString(const std::string& text, const FilePath& filePath) { std::shared_ptr result(new TextAccess()); result->m_lines = splitStringByLines(text); + result->m_filePath = filePath; return result; } diff --git a/src/lib/utility/text/TextAccess.h b/src/lib/utility/text/TextAccess.h index 7dcd1b5a..0e45d885 100644 --- a/src/lib/utility/text/TextAccess.h +++ b/src/lib/utility/text/TextAccess.h @@ -11,7 +11,7 @@ class TextAccess { public: static std::shared_ptr createFromFile(const FilePath& filePath); - static std::shared_ptr createFromString(const std::string& text); + static std::shared_ptr createFromString(const std::string& text, const FilePath& filePath = FilePath()); virtual ~TextAccess(); diff --git a/src/lib/utility/utility.h b/src/lib/utility/utility.h index 497509b7..04bba2c8 100644 --- a/src/lib/utility/utility.h +++ b/src/lib/utility/utility.h @@ -51,6 +51,9 @@ namespace utility template std::vector toVector(const std::set& d); + template + std::settoSet(const std::vector& d); + template void fillVectorWithElements(std::vector& v, const T& arg); @@ -191,6 +194,13 @@ std::vector utility::toVector(const std::set& d) return v; } +template +std::set utility::toSet(const std::vector& v) +{ + std::set s(v.begin(), v.end()); + return s; +} + template void utility::fillVectorWithElements(std::vector& v, const T& arg) { diff --git a/src/lib_cxx/CMakeLists.txt b/src/lib_cxx/CMakeLists.txt index c1934ffd..abcacc03 100644 --- a/src/lib_cxx/CMakeLists.txt +++ b/src/lib_cxx/CMakeLists.txt @@ -78,10 +78,6 @@ add_files( data/parser/cxx/utilityClang.cpp data/parser/cxx/utilityClang.h - project/IncludeDirective.cpp - project/IncludeDirective.h - project/IncludeValidation.cpp - project/IncludeValidation.h project/SourceGroupCxx.cpp project/SourceGroupCxx.h project/SourceGroupCxxCdb.cpp @@ -93,4 +89,8 @@ add_files( utility/CompilationDatabase.cpp utility/CompilationDatabase.h + utility/IncludeDirective.cpp + utility/IncludeDirective.h + utility/IncludeProcessing.cpp + utility/IncludeProcessing.h ) diff --git a/src/lib_cxx/project/IncludeValidation.cpp b/src/lib_cxx/project/IncludeValidation.cpp deleted file mode 100644 index 5baf2fa9..00000000 --- a/src/lib_cxx/project/IncludeValidation.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include "project/IncludeValidation.h" - -#include -#include - -#include "project/IncludeDirective.h" -#include "utility/file/FilePath.h" -#include "utility/text/TextAccess.h" -#include "utility/utility.h" -#include "utility/utilityString.h" - -std::vector IncludeValidation::getUnresolvedIncludeDirectives( - const std::vector& sourceFilePaths, - const std::vector& indexedPaths, - const std::vector& headerSearchDirectories, - size_t quantileCount, std::function progress -) -{ - struct IncludeDirectiveComparator - { - bool operator()(const IncludeDirective& a, const IncludeDirective& b) - { - return a.getIncludedFile() < b.getIncludedFile(); - } - }; - - std::unordered_set processedFilePaths; - std::set unresolvedIncludeDirectives; - - quantileCount = std::max(1, std::min(quantileCount, sourceFilePaths.size())); - - std::vector> quantiles; - for (size_t i = 0; i < quantileCount; i++) - { - quantiles.push_back(std::vector()); - } - for (size_t i = 0; i < sourceFilePaths.size(); i++) - { - quantiles[i % quantileCount].push_back(sourceFilePaths[i]); - } - - OrderedCache canonicalPathCache( - [](const FilePath& filePath) - { - return filePath.getCanonical(); - } - ); - - for (size_t i = 0; i < quantiles.size(); i++) - { - const std::vector& quantile = quantiles[i]; - - progress(float(i) / quantiles.size()); - - std::set unprocessedFilePaths(quantile.begin(), quantile.end()); - - while (!unprocessedFilePaths.empty()) - { - std::transform( - unprocessedFilePaths.begin(), unprocessedFilePaths.end(), - std::inserter(processedFilePaths, processedFilePaths.begin()), - [](const FilePath& p){ return p.str(); } - ); - std::set unprocessedFilePathsForNextIteration; - - for (const FilePath& filePath: unprocessedFilePaths) - { - for (const IncludeDirective& includeDirective: getIncludeDirectives(filePath)) - { - const FilePath resolvedIncludePath = - resolveIncludeDirective(includeDirective, headerSearchDirectories, canonicalPathCache).makeCanonical(); - if (resolvedIncludePath.empty()) - { - unresolvedIncludeDirectives.insert(includeDirective); - } - else if (processedFilePaths.find(resolvedIncludePath.str()) == processedFilePaths.end()) - { - for (const FilePath& indexedPath: indexedPaths) - { - if (indexedPath.contains(resolvedIncludePath)) - { - unprocessedFilePathsForNextIteration.insert(resolvedIncludePath); - break; - } - } - } - } - } - - unprocessedFilePaths = unprocessedFilePathsForNextIteration; - } - } - - std::vector ret; - - for (const IncludeDirective& directive: unresolvedIncludeDirectives) - { - ret.push_back(directive); - } - - progress(1.0f); - - return ret; -} - -std::vector IncludeValidation::getIncludeDirectives(const FilePath& filePath) -{ - std::vector includeDirectives; - - if (filePath.exists()) - { - std::shared_ptr textAccess = TextAccess::createFromFile(filePath); - const std::vector lines = textAccess->getAllLines(); - for (size_t i = 0; i < lines.size(); i++) - { - const std::string lineTrimmedToHash = utility::trim(lines[i]); - if (utility::isPrefix("#", lineTrimmedToHash)) - { - const std::string lineTrimmedToInclude = utility::trim(lineTrimmedToHash.substr(1)); - if (utility::isPrefix("include", lineTrimmedToInclude)) - { - std::string includeString = utility::substrBetween(lineTrimmedToInclude, "<", ">"); - bool usesBrackets = true; - if (includeString.empty()) - { - includeString = utility::substrBetween(lineTrimmedToInclude, "\"", "\""); - usesBrackets = false; - } - - if (!includeString.empty()) - { - // lines are 1 based - includeDirectives.push_back(IncludeDirective(FilePath(includeString), filePath, i + 1, usesBrackets)); - } - } - } - } - } - - return includeDirectives; -} - -FilePath IncludeValidation::resolveIncludeDirective( - const IncludeDirective& includeDirective, - const std::vector& headerSearchDirectories, - OrderedCache& canonicalPathCache -) -{ - const FilePath includedFilePath = includeDirective.getIncludedFile(); - - { - // check for an absolute include path - if (includedFilePath.isAbsolute()) - { - const FilePath resolvedIncludePath = canonicalPathCache.getValue(includedFilePath); - if (resolvedIncludePath.exists()) - { - return includedFilePath; - } - } - } - - { - // check for an include path relative to the including path - const FilePath resolvedIncludePath = canonicalPathCache.getValue(includeDirective.getIncludingFile().getParentDirectory().concatenate(includedFilePath)); - if (resolvedIncludePath.exists()) - { - return resolvedIncludePath; - } - } - - { - // check for an include path relative to the header search directories - for (const FilePath& headerSearchDirectory: headerSearchDirectories) - { - const FilePath resolvedIncludePath = canonicalPathCache.getValue(headerSearchDirectory.getConcatenated(includedFilePath)); - if (resolvedIncludePath.exists()) - { - return resolvedIncludePath; - } - } - } - - return FilePath(); -} diff --git a/src/lib_cxx/project/IncludeValidation.h b/src/lib_cxx/project/IncludeValidation.h deleted file mode 100644 index 1d2eb256..00000000 --- a/src/lib_cxx/project/IncludeValidation.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef INCLUDE_VALIDATION_H -#define INCLUDE_VALIDATION_H - -#include - -#include "utility/OrderedCache.h" - -class FilePath; -class IncludeDirective; - -class IncludeValidation -{ -public: - static std::vector getUnresolvedIncludeDirectives( - const std::vector& sourceFilePaths, - const std::vector& indexedPaths, - const std::vector& headerSearchDirectories, - size_t quantileCount, std::function progress); -private: - - static std::vector getIncludeDirectives(const FilePath& filePath); - static FilePath resolveIncludeDirective( - const IncludeDirective& includeDirective, - const std::vector& headerSearchDirectories, - OrderedCache& canonicalPathCache - ); -}; - -#endif // INCLUDE_VALIDATION_H diff --git a/src/lib_cxx/project/IncludeDirective.cpp b/src/lib_cxx/utility/IncludeDirective.cpp similarity index 95% rename from src/lib_cxx/project/IncludeDirective.cpp rename to src/lib_cxx/utility/IncludeDirective.cpp index c136a3f8..486ccc9d 100644 --- a/src/lib_cxx/project/IncludeDirective.cpp +++ b/src/lib_cxx/utility/IncludeDirective.cpp @@ -1,4 +1,4 @@ -#include "project/IncludeDirective.h" +#include "utility/IncludeDirective.h" IncludeDirective::IncludeDirective( const FilePath& includedFilePath, diff --git a/src/lib_cxx/project/IncludeDirective.h b/src/lib_cxx/utility/IncludeDirective.h similarity index 100% rename from src/lib_cxx/project/IncludeDirective.h rename to src/lib_cxx/utility/IncludeDirective.h diff --git a/src/lib_cxx/utility/IncludeProcessing.cpp b/src/lib_cxx/utility/IncludeProcessing.cpp new file mode 100644 index 00000000..a67aafe2 --- /dev/null +++ b/src/lib_cxx/utility/IncludeProcessing.cpp @@ -0,0 +1,335 @@ +#include "utility/IncludeProcessing.h" + +#include +#include + +#include "utility/IncludeDirective.h" +#include "utility/file/FilePath.h" +#include "utility/file/FileSystem.h" +#include "utility/text/TextAccess.h" +#include "utility/utility.h" +#include "utility/utilityString.h" + +namespace +{ + struct IncludeDirectiveComparator + { + bool operator()(const IncludeDirective& a, const IncludeDirective& b) + { + return a.getIncludedFile() < b.getIncludedFile(); + } + }; + + std::vector> splitToQuantiles( + const std::set& sourceFilePaths, + const size_t desiredQuantileCount) + { + size_t quantileCount = std::max(1, std::min(desiredQuantileCount, sourceFilePaths.size())); + + std::vector> quantiles; + for (size_t i = 0; i < quantileCount; i++) + { + quantiles.push_back(std::vector()); + } + + int i = 0; + for (const FilePath& sourceFilePath : sourceFilePaths) + { + quantiles[i % quantileCount].push_back(sourceFilePath); + ++i; + } + + return quantiles; + } +} + +std::vector IncludeProcessing::getUnresolvedIncludeDirectives( + const std::set& sourceFilePaths, + const std::set& indexedPaths, + const std::set& headerSearchDirectories, + const size_t desiredQuantileCount, std::function progress +) +{ + std::unordered_set processedFilePaths; + std::set unresolvedIncludeDirectives; + + std::vector> quantiles = splitToQuantiles(sourceFilePaths, desiredQuantileCount); + + for (size_t i = 0; i < quantiles.size(); i++) + { + const std::vector& quantile = quantiles[i]; + + progress(float(i) / quantiles.size()); + + std::set unprocessedFilePaths(quantile.begin(), quantile.end()); + + while (!unprocessedFilePaths.empty()) + { + std::transform( + unprocessedFilePaths.begin(), unprocessedFilePaths.end(), + std::inserter(processedFilePaths, processedFilePaths.begin()), + [](const FilePath& p){ return p.str(); } + ); + + std::set unprocessedFilePathsForNextIteration; + + for (const FilePath& filePath: unprocessedFilePaths) + { + for (const IncludeDirective& includeDirective: getIncludeDirectives(filePath)) + { + const FilePath resolvedIncludePath = resolveIncludeDirective(includeDirective, headerSearchDirectories).makeCanonical(); + if (resolvedIncludePath.empty()) + { + unresolvedIncludeDirectives.insert(includeDirective); + } + else if (processedFilePaths.find(resolvedIncludePath.str()) == processedFilePaths.end()) + { + for (const FilePath& indexedPath: indexedPaths) + { + if (indexedPath.contains(resolvedIncludePath)) + { + unprocessedFilePathsForNextIteration.insert(resolvedIncludePath); + break; + } + } + } + } + } + + unprocessedFilePaths = unprocessedFilePathsForNextIteration; + } + } + + std::vector ret; + + for (const IncludeDirective& directive: unresolvedIncludeDirectives) + { + ret.push_back(directive); + } + + progress(1.0f); + + return ret; +} + +std::set IncludeProcessing::getHeaderSearchDirectories( + const std::set& sourceFilePaths, + const std::set& searchedPaths, + const size_t desiredQuantileCount, std::function progress +) +{ + progress(0.0f); + + std::map> existingFilePaths; + for (const FilePath& searchedPath : searchedPaths) + { + if (searchedPath.isDirectory()) + { + for (const FilePath& filePath : FileSystem::getFilePathsFromDirectory(searchedPath.getCanonical())) + { + existingFilePaths[filePath.fileName()].insert(filePath); + } + } + else + { + existingFilePaths[searchedPath.fileName()].insert(searchedPath); + } + } + + std::set headerSearchDirectories; + std::unordered_set processedFilePaths; + std::vector> quantiles = splitToQuantiles(sourceFilePaths, desiredQuantileCount); + + for (size_t i = 0; i < quantiles.size(); i++) + { + const std::vector& quantile = quantiles[i]; + + progress(float(i) / quantiles.size()); + + std::set unprocessedFilePaths(quantile.begin(), quantile.end()); + + while (!unprocessedFilePaths.empty()) + { + std::transform( + unprocessedFilePaths.begin(), unprocessedFilePaths.end(), + std::inserter(processedFilePaths, processedFilePaths.begin()), + [](const FilePath& p) { return p.str(); } + ); + + std::set unprocessedFilePathsForNextIteration; + + for (const FilePath& unprocessedFilePath : unprocessedFilePaths) + { + for (const IncludeDirective& includeDirective : getIncludeDirectives(unprocessedFilePath)) + { + const FilePath includedFilePath = includeDirective.getIncludedFile(); + + FilePath foundIncludedPath; + if (includedFilePath.isAbsolute()) + { + foundIncludedPath = includedFilePath; + } + else + { + const FilePath relativeToIncludingPath = includeDirective.getIncludingFile().getParentDirectory().concatenate(includedFilePath); + if (relativeToIncludingPath.exists()) + { + foundIncludedPath = relativeToIncludingPath; + } + else + { + std::map>::const_iterator it = existingFilePaths.find(includedFilePath.fileName()); + if (it != existingFilePaths.end()) + { + // TODO: handle the case where a file can be found by two different paths + for (FilePath existingFilePath : it->second) + { + existingFilePath = existingFilePath.getParentDirectory(); + bool ok = true; + { + FilePath tempIncludedFilePath = includedFilePath.getParentDirectory(); + while (!tempIncludedFilePath.empty()) + { + if (tempIncludedFilePath.fileName() == "..") + { + std::vector subDirectories = FileSystem::getDirectSubDirectories(existingFilePath); + if (!subDirectories.empty()) + { + existingFilePath = subDirectories.front(); + } + else + { + ok = false; + break; + } + } + else + { + existingFilePath = existingFilePath.getParentDirectory(); + } + tempIncludedFilePath = tempIncludedFilePath.getParentDirectory(); + } + } + if (ok) + { + foundIncludedPath = existingFilePath.getConcatenated(includedFilePath); + if (foundIncludedPath.exists()) + { + headerSearchDirectories.insert(existingFilePath); + break; + } + } + } + } + } + } + if (foundIncludedPath.exists()) + { + if (processedFilePaths.find(foundIncludedPath.str()) == processedFilePaths.end()) + { + for (const FilePath& searchedPath : searchedPaths) + { + if (searchedPath.contains(foundIncludedPath)) + { + unprocessedFilePathsForNextIteration.insert(foundIncludedPath); + break; + } + } + } + } + } + } + + unprocessedFilePaths = unprocessedFilePathsForNextIteration; + } + } + + progress(1.0f); + + return headerSearchDirectories; +} + +std::vector IncludeProcessing::getIncludeDirectives(const FilePath& filePath) +{ + if (filePath.exists()) + { + return getIncludeDirectives(TextAccess::createFromFile(filePath)); + } + return std::vector(); +} + +std::vector IncludeProcessing::getIncludeDirectives(std::shared_ptr textAccess) +{ + std::vector includeDirectives; + + const std::vector lines = textAccess->getAllLines(); + for (size_t i = 0; i < lines.size(); i++) + { + const std::string lineTrimmedToHash = utility::trim(lines[i]); + if (utility::isPrefix("#", lineTrimmedToHash)) + { + const std::string lineTrimmedToInclude = utility::trim(lineTrimmedToHash.substr(1)); + if (utility::isPrefix("include", lineTrimmedToInclude)) + { + std::string includeString = utility::substrBetween(lineTrimmedToInclude, "<", ">"); + bool usesBrackets = true; + if (includeString.empty()) + { + includeString = utility::substrBetween(lineTrimmedToInclude, "\"", "\""); + usesBrackets = false; + } + + if (!includeString.empty()) + { + // lines are 1 based + includeDirectives.push_back(IncludeDirective(FilePath(includeString), textAccess->getFilePath(), i + 1, usesBrackets)); + } + } + } + } + + return includeDirectives; +} + +FilePath IncludeProcessing::resolveIncludeDirective( + const IncludeDirective& includeDirective, + const std::set& headerSearchDirectories +) +{ + const FilePath includedFilePath = includeDirective.getIncludedFile(); + + { + // check for an absolute include path + if (includedFilePath.isAbsolute()) + { + const FilePath resolvedIncludePath = includedFilePath; + if (resolvedIncludePath.exists()) + { + return includedFilePath; + } + } + } + + { + // check for an include path relative to the including path + const FilePath resolvedIncludePath = includeDirective.getIncludingFile().getParentDirectory().concatenate(includedFilePath); + if (resolvedIncludePath.exists()) + { + return resolvedIncludePath; + } + } + + { + // check for an include path relative to the header search directories + for (const FilePath& headerSearchDirectory: headerSearchDirectories) + { + const FilePath resolvedIncludePath = headerSearchDirectory.getConcatenated(includedFilePath); + if (resolvedIncludePath.exists()) + { + return resolvedIncludePath; + } + } + } + + return FilePath(); +} diff --git a/src/lib_cxx/utility/IncludeProcessing.h b/src/lib_cxx/utility/IncludeProcessing.h new file mode 100644 index 00000000..1b0d6ecd --- /dev/null +++ b/src/lib_cxx/utility/IncludeProcessing.h @@ -0,0 +1,43 @@ +#ifndef INCLUDE_PROCESSING_H +#define INCLUDE_PROCESSING_H + +#include +#include +#include + +#include "utility/OrderedCache.h" + +class FilePath; +class IncludeDirective; +class TextAccess; + +class IncludeProcessing +{ +public: + static std::vector getUnresolvedIncludeDirectives( + const std::set& sourceFilePaths, + const std::set& indexedPaths, + const std::set& headerSearchDirectories, + size_t quantileCount, std::function progress + ); + + static std::set getHeaderSearchDirectories( + const std::set& sourceFilePaths, + const std::set& searchedPaths, + const size_t desiredQuantileCount, std::function progress + ); + + static std::vector getIncludeDirectives(const FilePath& filePath); + + static std::vector getIncludeDirectives(std::shared_ptr textAccess); + +private: + static FilePath resolveIncludeDirective( + const IncludeDirective& includeDirective, + const std::set& headerSearchDirectories + ); + + IncludeProcessing() = delete; +}; + +#endif // INCLUDE_PROCESSING_H diff --git a/src/lib_gui/CMakeLists.txt b/src/lib_gui/CMakeLists.txt index a0396a44..d152b8b5 100644 --- a/src/lib_gui/CMakeLists.txt +++ b/src/lib_gui/CMakeLists.txt @@ -225,6 +225,8 @@ add_files( qt/window/QtLicenseWindow.h qt/window/QtMainWindow.cpp qt/window/QtMainWindow.h + qt/window/QtPathListDialog.cpp + qt/window/QtPathListDialog.h qt/window/QtPreferencesWindow.cpp qt/window/QtPreferencesWindow.h qt/window/QtSelectPathsDialog.cpp diff --git a/src/lib_gui/qt/element/QtProgressBar.cpp b/src/lib_gui/qt/element/QtProgressBar.cpp index 3a378d9f..c84e8842 100644 --- a/src/lib_gui/qt/element/QtProgressBar.cpp +++ b/src/lib_gui/qt/element/QtProgressBar.cpp @@ -29,6 +29,11 @@ void QtProgressBar::showProgress(size_t percent) update(); } +size_t QtProgressBar::getProgress() const +{ + return m_percent; +} + void QtProgressBar::showUnknownProgressAnimated() { start(); diff --git a/src/lib_gui/qt/element/QtProgressBar.h b/src/lib_gui/qt/element/QtProgressBar.h index 7c671e7d..25ee3853 100644 --- a/src/lib_gui/qt/element/QtProgressBar.h +++ b/src/lib_gui/qt/element/QtProgressBar.h @@ -17,6 +17,7 @@ public: QtProgressBar(QWidget* parent = nullptr); void showProgress(size_t percent); + size_t getProgress() const; void showUnknownProgressAnimated(); diff --git a/src/lib_gui/qt/view/QtDialogView.cpp b/src/lib_gui/qt/view/QtDialogView.cpp index d741a48c..301d7821 100644 --- a/src/lib_gui/qt/view/QtDialogView.cpp +++ b/src/lib_gui/qt/view/QtDialogView.cpp @@ -58,11 +58,10 @@ void QtDialogView::hideUnknownProgressDialog() void QtDialogView::showProgressDialog(const std::string& title, const std::string& message, int progress) { - MessageStatus(title + ": " + message + " [" + std::to_string(progress) + "%]", false, true).dispatch(); - m_onQtThread( [=]() { + bool sendStatusMessage = true; QtIndexingDialog* window = dynamic_cast(m_windowStack.getTopWindow()); if (!window || window->getType() != QtIndexingDialog::DIALOG_PROGRESS) { @@ -71,6 +70,19 @@ void QtDialogView::showProgressDialog(const std::string& title, const std::strin window = createWindow(); window->setupProgress(); } + else + { + sendStatusMessage = ( + window->getTitle() != title || + window->getMessage() != message || + window->getProgress() != progress + ); + } + + if (sendStatusMessage) + { + MessageStatus(title + ": " + message + " [" + std::to_string(progress) + "%]", false, true).dispatch(); + } window->updateTitle(title.c_str()); window->updateMessage(message.c_str()); diff --git a/src/lib_gui/qt/window/QtIndexingDialog.cpp b/src/lib_gui/qt/window/QtIndexingDialog.cpp index eeee4b15..ee220101 100644 --- a/src/lib_gui/qt/window/QtIndexingDialog.cpp +++ b/src/lib_gui/qt/window/QtIndexingDialog.cpp @@ -273,15 +273,29 @@ void QtIndexingDialog::updateMessage(QString message) } } -void QtIndexingDialog::updateProgress(int progress) +std::string QtIndexingDialog::getMessage() const { - int percent = std::min(std::max(progress, 0), 100); + if (m_messageLabel) + { + return m_messageLabel->text().toStdString(); + } + return ""; +} + +void QtIndexingDialog::updateProgress(size_t progress) +{ + size_t percent = std::min(std::max(progress, 0), 100); m_progressBar->showProgress(percent); m_percentLabel->setText(QString::number(percent) + "% Progress"); setGeometries(); } +size_t QtIndexingDialog::getProgress() const +{ + return m_progressBar->getProgress(); +} + void QtIndexingDialog::updateIndexingProgress(size_t fileCount, size_t totalFileCount, std::string sourcePath) { updateMessage(QString::number(fileCount) + "/" + QString::number(totalFileCount) + " File" + (totalFileCount > 1 ? "s" : "")); diff --git a/src/lib_gui/qt/window/QtIndexingDialog.h b/src/lib_gui/qt/window/QtIndexingDialog.h index 5388fa7c..941d5595 100644 --- a/src/lib_gui/qt/window/QtIndexingDialog.h +++ b/src/lib_gui/qt/window/QtIndexingDialog.h @@ -48,7 +48,9 @@ public: void setupProgress(); void updateMessage(QString message); - void updateProgress(int progress); + std::string getMessage() const; + void updateProgress(size_t progress); + size_t getProgress() const; void updateIndexingProgress(size_t fileCount, size_t totalFileCount, std::string sourcePath); void updateErrorCount(size_t errorCount, size_t fatalCount); diff --git a/src/lib_gui/qt/window/QtPathListDialog.cpp b/src/lib_gui/qt/window/QtPathListDialog.cpp new file mode 100644 index 00000000..d9fce73c --- /dev/null +++ b/src/lib_gui/qt/window/QtPathListDialog.cpp @@ -0,0 +1,57 @@ +#include "qt/window/QtPathListDialog.h" + +#include +#include "qt/element/QtDirectoryListBox.h" + +QtPathListDialog::QtPathListDialog(const QString& title, const QString& description, QWidget* parent) + : QtWindow(parent) + , m_title(title) + , m_description(description) +{ +} + +QSize QtPathListDialog::sizeHint() const +{ + return QSize(550, 550); +} + +void QtPathListDialog::setRelativeRootDirectory(const FilePath& dir) +{ + m_pathList->setRelativeRootDirectory(dir); +} + +void QtPathListDialog::setPaths(const std::vector& paths, bool readOnly) +{ + m_pathList->setList(paths, readOnly); +} + +std::vector QtPathListDialog::getPaths() +{ + return m_pathList->getList(); +} + +void QtPathListDialog::populateWindow(QWidget* widget) +{ + QVBoxLayout* layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + + QLabel* description = new QLabel(m_description); + description->setObjectName("description"); + description->setWordWrap(true); + layout->addWidget(description); + + m_pathList = new QtDirectoryListBox(this, m_title); + layout->addWidget(m_pathList); + + widget->setLayout(layout); +} + +void QtPathListDialog::windowReady() +{ + updateNextButton("Save"); + updateCloseButton("Cancel"); + + setPreviousVisible(false); + + updateTitle(m_title); +} diff --git a/src/lib_gui/qt/window/QtPathListDialog.h b/src/lib_gui/qt/window/QtPathListDialog.h new file mode 100644 index 00000000..ed61ea84 --- /dev/null +++ b/src/lib_gui/qt/window/QtPathListDialog.h @@ -0,0 +1,34 @@ +#ifndef QT_PATH_LIST_DIALOG_H +#define QT_PATH_LIST_DIALOG_H + +#include "qt/window/QtWindow.h" + +class FilePath; +class QtDirectoryListBox; + +class QtPathListDialog + : public QtWindow +{ + Q_OBJECT + +public: + QtPathListDialog(const QString& title, const QString& description, QWidget* parent = 0); + + QSize sizeHint() const override; + + void setRelativeRootDirectory(const FilePath& dir); + void setPaths(const std::vector& paths, bool readOnly = false); + std::vector getPaths(); + +protected: + void populateWindow(QWidget* widget) override; + void windowReady() override; + + QString m_title; + QString m_description; + +private: + QtDirectoryListBox* m_pathList; +}; + +#endif // QT_PATH_LIST_DIALOG_H diff --git a/src/lib_gui/qt/window/QtTextEditDialog.h b/src/lib_gui/qt/window/QtTextEditDialog.h index 49a5293a..c2fe7add 100644 --- a/src/lib_gui/qt/window/QtTextEditDialog.h +++ b/src/lib_gui/qt/window/QtTextEditDialog.h @@ -28,7 +28,6 @@ protected: QString m_description; private: - QPlainTextEdit* m_text; }; diff --git a/src/lib_gui/qt/window/QtWindow.cpp b/src/lib_gui/qt/window/QtWindow.cpp index 11032a67..b7b5dc48 100644 --- a/src/lib_gui/qt/window/QtWindow.cpp +++ b/src/lib_gui/qt/window/QtWindow.cpp @@ -213,6 +213,15 @@ void QtWindow::updateTitle(QString title) } } +std::string QtWindow::getTitle() const +{ + if (m_title) + { + return m_title->text().toStdString(); + } + return ""; +} + void QtWindow::updateSubTitle(QString subTitle) { if (m_subTitle) diff --git a/src/lib_gui/qt/window/QtWindow.h b/src/lib_gui/qt/window/QtWindow.h index 4f9a2d82..11ce5b1d 100644 --- a/src/lib_gui/qt/window/QtWindow.h +++ b/src/lib_gui/qt/window/QtWindow.h @@ -35,6 +35,7 @@ public: void moveToCenter(); void updateTitle(QString title); + std::string getTitle() const; void updateSubTitle(QString subTitle); void updateNextButton(QString text); diff --git a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp index 9e10cf63..36e5d170 100644 --- a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp +++ b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.cpp @@ -9,10 +9,11 @@ #include "Application.h" #include "component/view/DialogView.h" #include "data/indexer/IndexerCommandCxxCdb.h" -#include "project/IncludeDirective.h" -#include "project/IncludeValidation.h" +#include "utility/IncludeDirective.h" +#include "utility/IncludeProcessing.h" #include "qt/element/QtDirectoryListBox.h" #include "qt/view/QtDialogView.h" +#include "qt/window/QtPathListDialog.h" #include "qt/window/QtSelectPathsDialog.h" #include "settings/ApplicationSettings.h" #include "settings/SourceGroupSettingsCxxCdb.h" @@ -23,6 +24,7 @@ #include "utility/utility.h" #include "utility/utilityFile.h" #include "utility/utilityPathDetection.h" +#include "utility/utilityString.h" QtProjectWizzardContentPaths::QtProjectWizzardContentPaths( std::shared_ptr settings, QtProjectWizzardWindow* window @@ -429,6 +431,8 @@ QtProjectWizzardContentPathsHeaderSearch::QtProjectWizzardContentPathsHeaderSear : QtProjectWizzardContentPaths(settings, window) , m_showValidationResultFunctor(std::bind( &QtProjectWizzardContentPathsHeaderSearch::showValidationResult, this, std::placeholders::_1)) + , m_showDetectedIncludesResultFunctor(std::bind( + &QtProjectWizzardContentPathsHeaderSearch::showDetectedIncludesResult, this, std::placeholders::_1)) , m_isCdb(isCDB) { setTitleString(m_isCdb ? "Additional Include Paths" : "Include Paths"); @@ -454,11 +458,18 @@ void QtProjectWizzardContentPathsHeaderSearch::populate(QGridLayout* layout, int if (!m_isCdb) { - QPushButton* button = new QPushButton("validate include directives"); - button->setObjectName("windowButton"); - connect(button, &QPushButton::clicked, this, &QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked); - - layout->addWidget(button, row, QtProjectWizzardWindow::BACK_COL, Qt::AlignRight | Qt::AlignTop); + { + QPushButton* detectionButton = new QPushButton("auto-detect"); + detectionButton->setObjectName("windowButton"); + connect(detectionButton, &QPushButton::clicked, this, &QtProjectWizzardContentPathsHeaderSearch::detectIncludesButtonClicked); + layout->addWidget(detectionButton, row, QtProjectWizzardWindow::BACK_COL, Qt::AlignLeft | Qt::AlignTop); + } + { + QPushButton* validateionButton = new QPushButton("validate include directives"); + validateionButton->setObjectName("windowButton"); + connect(validateionButton, &QPushButton::clicked, this, &QtProjectWizzardContentPathsHeaderSearch::validateIncludesButtonClicked); + layout->addWidget(validateionButton, row, QtProjectWizzardWindow::BACK_COL, Qt::AlignRight | Qt::AlignTop); + } row++; } } @@ -486,7 +497,30 @@ bool QtProjectWizzardContentPathsHeaderSearch::isScrollAble() const return true; } -void QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked() +void QtProjectWizzardContentPathsHeaderSearch::detectIncludesButtonClicked() +{ + m_window->saveContent(); + + m_pathsDialog = std::make_shared( + "Detect Include Paths", + "

Automatically search the paths provided below for additional include paths that " + "can be used to resolve include directives within your source code.

" + "

The indexed paths will be searched by default but you can add further paths if required.

" + ); + + m_pathsDialog->setup(); + m_pathsDialog->updateNextButton("Next"); + m_pathsDialog->setCloseVisible(true); + + m_pathsDialog->setRelativeRootDirectory(m_settings->getProjectDirectoryPath()); + m_pathsDialog->setPaths(m_settings->getSourcePaths(), true); + m_pathsDialog->showWindow(); + + connect(m_pathsDialog.get(), &QtPathListDialog::finished, this, &QtProjectWizzardContentPathsHeaderSearch::finishedSelectDetectIncludesRootPathsDialog); + connect(m_pathsDialog.get(), &QtPathListDialog::canceled, this, &QtProjectWizzardContentPathsHeaderSearch::closedPathsDialog); +} + +void QtProjectWizzardContentPathsHeaderSearch::validateIncludesButtonClicked() { // TODO: regard Force Includes here, too! m_window->saveContent(); @@ -497,7 +531,7 @@ void QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked() { std::shared_ptr dialogView = Application::getInstance()->getDialogView(); - std::vector sourceFilePaths; + std::set sourceFilePaths; std::vector indexedFilePaths; std::vector headerSearchPaths; @@ -514,7 +548,7 @@ void QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked() m_settings->getExcludePathsExpandedAndAbsolute(), m_settings->getSourceExtensions() ); - sourceFilePaths = utility::toVector(fileManager.getAllSourceFilePaths()); + sourceFilePaths = fileManager.getAllSourceFilePaths(); headerSearchPaths = ApplicationSettings::getInstance()->getHeaderSearchPathsExpanded(); @@ -531,10 +565,10 @@ void QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked() dialogView->hideProgressDialog(); }); - unresolvedIncludes = IncludeValidation::getUnresolvedIncludeDirectives( + unresolvedIncludes = IncludeProcessing::getUnresolvedIncludeDirectives( sourceFilePaths, - indexedFilePaths, - headerSearchPaths, + utility::toSet(indexedFilePaths), + utility::toSet(headerSearchPaths), log2(sourceFilePaths.size()), [&](const float progress) { @@ -549,6 +583,147 @@ void QtProjectWizzardContentPathsHeaderSearch::validateButtonClicked() }).detach(); } + +void QtProjectWizzardContentPathsHeaderSearch::finishedSelectDetectIncludesRootPathsDialog() +{ + // TODO: regard Force Includes here, too! + const std::vector searchedPaths = m_settings->makePathsExpandedAndAbsolute(m_pathsDialog->getPaths()); + + closedPathsDialog(); + + std::thread([=]() + { + std::set detectedHeaderSearchPaths; + { + std::shared_ptr dialogView = std::dynamic_pointer_cast(Application::getInstance()->getDialogView()); + + std::set sourceFilePaths; + + { + dialogView->setParentWindow(m_window); + dialogView->showUnknownProgressDialog("Processing", "Gathering Source Files"); + ScopedFunctor dialogHider([&dialogView]() { + dialogView->hideUnknownProgressDialog(); + }); + + FileManager fileManager; + fileManager.update( + m_settings->getSourcePathsExpandedAndAbsolute(), + m_settings->getExcludePathsExpandedAndAbsolute(), + m_settings->getSourceExtensions() + ); + sourceFilePaths = fileManager.getAllSourceFilePaths(); + } + { + dialogView->setParentWindow(m_window); + ScopedFunctor dialogHider([&dialogView]() { + dialogView->hideProgressDialog(); + }); + + detectedHeaderSearchPaths = IncludeProcessing::getHeaderSearchDirectories( + sourceFilePaths, + utility::toSet(searchedPaths), + log2(sourceFilePaths.size()), + [&](const float progress) + { + Application::getInstance()->getDialogView()->showProgressDialog( + "Processing", std::to_string(int(progress * sourceFilePaths.size())) + " Files", int(progress * 100.0f) + ); + } + ); + } + } + + m_showDetectedIncludesResultFunctor(detectedHeaderSearchPaths); + }).detach(); +} + +void QtProjectWizzardContentPathsHeaderSearch::finishedAcceptDetectedIncludePathsDialog() +{ + const std::vector detectedPaths = utility::splitToVector(m_filesDialog->getText(), "\n"); + closedFilesDialog(); + + std::vector headerSearchPaths = m_list->getStringList(); + + headerSearchPaths.reserve(headerSearchPaths.size() + detectedPaths.size()); + for (const std::string& detectedPath : detectedPaths) + { + if (!detectedPath.empty()) + { + headerSearchPaths.push_back(detectedPath); + } + } + + m_list->setStringList(headerSearchPaths); +} + +void QtProjectWizzardContentPathsHeaderSearch::closedPathsDialog() +{ + m_pathsDialog->hide(); + m_pathsDialog.reset(); + + window()->raise(); +} + +void QtProjectWizzardContentPathsHeaderSearch::showDetectedIncludesResult(const std::set& detectedHeaderSearchPaths) +{ + const std::set headerSearchPaths = utility::toSet(m_settings->makePathsExpandedAndAbsolute(m_list->getList())); + + std::vector additionalHeaderSearchPaths; + for (const FilePath& detectedHeaderSearchPath : detectedHeaderSearchPaths) + { + if (headerSearchPaths.find(detectedHeaderSearchPath) == headerSearchPaths.end()) + { + additionalHeaderSearchPaths.push_back(detectedHeaderSearchPath); + } + } + + if (additionalHeaderSearchPaths.empty()) + { + QMessageBox msgBox; + msgBox.setText("

No additional include paths have been detected while searching the provided paths.

"); + msgBox.exec(); + } + else + { + std::string detailedText = ""; + FilePath relativeRoot = m_list->getRelativeRootDirectory(); + for (const FilePath& path : additionalHeaderSearchPaths) + { + if (!relativeRoot.empty()) + { + const FilePath relPath = path.getRelativeTo(relativeRoot); + if (relPath.str().size() < path.str().size()) + { + detailedText += relPath.str() + "\n"; + continue; + } + } + + detailedText += path.str() + "\n"; + } + + m_filesDialog = std::make_shared( + "Detected Include Paths", + ( + "

The following " + std::to_string(additionalHeaderSearchPaths.size()) + " include paths have been " + "detected and will be added to the include paths that are already defined by this Source Group." + ).c_str() + ); + + m_filesDialog->setup(); + m_filesDialog->setCloseVisible(true); + m_filesDialog->updateNextButton("Finish"); + + m_filesDialog->setReadOnly(true); + m_filesDialog->setText(detailedText); + m_filesDialog->showWindow(); + + connect(m_filesDialog.get(), &QtTextEditDialog::finished, this, &QtProjectWizzardContentPathsHeaderSearch::finishedAcceptDetectedIncludePathsDialog); + connect(m_filesDialog.get(), &QtTextEditDialog::canceled, this, &QtProjectWizzardContentPathsHeaderSearch::closedFilesDialog); + } +} + void QtProjectWizzardContentPathsHeaderSearch::showValidationResult(const std::vector& unresolvedIncludes) { if (unresolvedIncludes.empty()) diff --git a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h index 9a74db8c..e748281c 100644 --- a/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h +++ b/src/lib_gui/qt/window/project_wizzard/QtProjectWizzardContentPaths.h @@ -1,6 +1,8 @@ #ifndef QT_PROJECT_WIZZARD_CONTENT_PATHS_H #define QT_PROJECT_WIZZARD_CONTENT_PATHS_H +#include + #include "qt/window/project_wizzard/QtProjectWizzardContent.h" #include "utility/path_detector/CombinedPathDetector.h" @@ -9,6 +11,7 @@ class QCheckBox; class QComboBox; class QPushButton; class QtDirectoryListBox; +class QtPathListDialog; class SourceGroupSettings; class SourceGroupSettingsCxxCdb; @@ -119,12 +122,19 @@ public: virtual bool isScrollAble() const override; private slots: - void validateButtonClicked(); + void detectIncludesButtonClicked(); + void validateIncludesButtonClicked(); + void finishedSelectDetectIncludesRootPathsDialog(); + void finishedAcceptDetectedIncludePathsDialog(); + void closedPathsDialog(); private: + void showDetectedIncludesResult(const std::set& detectedHeaderSearchPaths); void showValidationResult(const std::vector& unresolvedIncludes); + QtThreadedFunctor> m_showDetectedIncludesResultFunctor; QtThreadedFunctor> m_showValidationResultFunctor; + std::shared_ptr m_pathsDialog; const bool m_isCdb; }; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index f97b5562..e18a4899 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -11,6 +11,7 @@ add_files( CommandlineTestSuite.h ConfigManagerTestSuite.h + CxxIncludeProcessingTestSuite.h CxxIndexSampleProjectsTestSuite.h CxxParserTestSuite.h CxxTypeNameTestSuite.h diff --git a/src/test/CxxIncludeProcessingTestSuite.h b/src/test/CxxIncludeProcessingTestSuite.h new file mode 100644 index 00000000..ecb85b14 --- /dev/null +++ b/src/test/CxxIncludeProcessingTestSuite.h @@ -0,0 +1,118 @@ +#include "cxxtest/TestSuite.h" + +#include "utility/text/TextAccess.h" +#include "utility/IncludeDirective.h" +#include "utility/IncludeProcessing.h" + +class CxxIncludeProcessingTestSuite: public CxxTest::TestSuite +{ +public: + void test_include_detection_finds_include_with_quotes() + { + std::vector includeDirectives = IncludeProcessing::getIncludeDirectives(TextAccess::createFromString( + "#include \"foo.h\"", + FilePath("foo.cpp") + )); + + TS_ASSERT(!includeDirectives.empty()); + + if (!includeDirectives.empty()) + { + TS_ASSERT_EQUALS("foo.h", includeDirectives.front().getIncludedFile().str()); + TS_ASSERT_EQUALS("foo.cpp", includeDirectives.front().getIncludingFile().str()); + } + } + + void test_include_detection_finds_include_with_angle_brackets() + { + std::vector includeDirectives = IncludeProcessing::getIncludeDirectives(TextAccess::createFromString( + "#include ", + FilePath("foo.cpp") + )); + + TS_ASSERT(!includeDirectives.empty()); + + if (!includeDirectives.empty()) + { + TS_ASSERT_EQUALS("foo.h", includeDirectives.front().getIncludedFile().str()); + TS_ASSERT_EQUALS("foo.cpp", includeDirectives.front().getIncludingFile().str()); + } + } + void test_include_detection_finds_include_with_quotes_and_space_before_keyword() + { + std::vector includeDirectives = IncludeProcessing::getIncludeDirectives(TextAccess::createFromString( + "# include \"foo.h\"", + FilePath("foo.cpp") + )); + + TS_ASSERT(!includeDirectives.empty()); + + if (!includeDirectives.empty()) + { + TS_ASSERT_EQUALS("foo.h", includeDirectives.front().getIncludedFile().str()); + TS_ASSERT_EQUALS("foo.cpp", includeDirectives.front().getIncludingFile().str()); + } + } + + void test_include_detection_does_not_find_include_in_empty_file() + { + TS_ASSERT(IncludeProcessing::getIncludeDirectives(TextAccess::createFromString("")).empty()); + } + + void test_include_detection_does_not_find_include_in_file_without_preprocessor_directive() + { + TS_ASSERT(IncludeProcessing::getIncludeDirectives(TextAccess::createFromString("foo")).empty()); + } + + void test_include_detection_does_not_find_include_in_file_without_include_preprocessor_directive() + { + TS_ASSERT(IncludeProcessing::getIncludeDirectives(TextAccess::createFromString("#ifdef xx\n#endif")).empty()); + } + + void test_header_search_path_detection_does_not_find_path_relative_to_including_file() + { + std::set headerSearchDirectoies = IncludeProcessing::getHeaderSearchDirectories( + { FilePath("data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file/a.cpp") }, + { FilePath("data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_does_not_find_path_relative_to_including_file") }, + 1, [&](float) {} + ); + + TS_ASSERT(headerSearchDirectoies.empty()); + } + + void test_header_search_path_detection_finds_path_inside_sub_directory() + { + std::set headerSearchDirectoies = IncludeProcessing::getHeaderSearchDirectories( + { FilePath("data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/a.cpp") }, + { FilePath("data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory") }, + 1, [&](float) {} + ); + + TS_ASSERT(!headerSearchDirectoies.empty()); + if (!headerSearchDirectoies.empty()) + { + TS_ASSERT_EQUALS( + "CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_inside_sub_directory/include", + headerSearchDirectoies.begin()->getRelativeTo(FilePath("data").getAbsolute()).str() + ); + } + } + + void test_header_search_path_detection_finds_path_relative_to_sub_directory() + { + std::set headerSearchDirectoies = IncludeProcessing::getHeaderSearchDirectories( + { FilePath("data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/a.cpp") }, + { FilePath("data/CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory") }, + 1, [&](float) {} + ); + + TS_ASSERT(!headerSearchDirectoies.empty()); + if (!headerSearchDirectoies.empty()) + { + TS_ASSERT_EQUALS( + "CxxIncludeProcessingTestSuite/test_header_search_path_detection_finds_path_relative_to_sub_directory/include", + headerSearchDirectoies.begin()->getRelativeTo(FilePath("data").getAbsolute()).str() + ); + } + } +}; diff --git a/src/test/FilePathTestSuite.h b/src/test/FilePathTestSuite.h index 430caf80..ecda69c3 100644 --- a/src/test/FilePathTestSuite.h +++ b/src/test/FilePathTestSuite.h @@ -74,6 +74,13 @@ public: TS_ASSERT(path.getParentDirectory().empty()); } + void test_file_path_without_parent_has_empty_parent_directory() + { + const FilePath path("a.cpp"); + + TS_ASSERT(path.getParentDirectory().empty()); + } + void test_file_path_is_absolute() { const FilePath path("data/FilePathTestSuite/a.cpp");