Skip to content

Commit 2e3e9d6

Browse files
nfc: Add new path handling functions (#339)
1 parent c99aae8 commit 2e3e9d6

File tree

3 files changed

+128
-2
lines changed

3 files changed

+128
-2
lines changed

indexer/Path.cc

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,44 @@ std::optional<std::string_view> AbsolutePathRef::fileName() const {
5555
return ::fileName(this->asStringView());
5656
}
5757

58+
bool AbsolutePathRef::isNormalized() const {
59+
auto start = llvm::sys::path::begin(this->value);
60+
auto end = llvm::sys::path::end(this->value);
61+
for (auto it = start; it != end; ++it) {
62+
if (it->equals(".") || it->equals("..")) {
63+
return false;
64+
}
65+
}
66+
if (this->value.find("//") != std::string::npos) {
67+
return false;
68+
}
69+
70+
#ifdef _WIN32
71+
auto ix = this->value.find_last_of("\\");
72+
// Absolute paths on Windows can begin with double backslash
73+
if (ix != std::string::npos && ix != 0) {
74+
return false;
75+
}
76+
#endif
77+
78+
return true;
79+
}
80+
81+
void AbsolutePathRef::normalize(llvm::SmallVectorImpl<char> &newStorage) {
82+
newStorage.clear();
83+
newStorage.append(this->asStringView().begin(), this->asStringView().end());
84+
llvm::sys::path::remove_dots(newStorage);
85+
*this =
86+
AbsolutePathRef(std::string_view(newStorage.data(), newStorage.size()));
87+
}
88+
89+
std::optional<AbsolutePathRef> AbsolutePathRef::prefix() const {
90+
// NOTE(def: no-trailing-slash-for-dirs): The parent_path function omits
91+
// any trailing separators for directories, so we should make sure that other
92+
// places relying on path matching do the same.
93+
return AbsolutePathRef::tryFrom(llvm::sys::path::parent_path(this->value));
94+
}
95+
5896
AbsolutePathRef AbsolutePath::asRef() const {
5997
auto sv = std::string_view(this->value.data(), this->value.size());
6098
auto optRef = AbsolutePathRef::tryFrom(sv);
@@ -152,4 +190,4 @@ AbsolutePath RootPath::makeAbsoluteAllowKindMismatch(
152190
return AbsolutePath(fmt::format("{}{}{}", absPath, nativeSeparator, relPath));
153191
}
154192

155-
} // namespace scip_clang
193+
} // namespace scip_clang

indexer/Path.h

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
#define SCIP_CLANG_PATH_H
33

44
#include <compare>
5+
#include <iterator>
56
#include <optional>
67
#include <string>
78
#include <string_view>
89
#include <utility>
910

1011
#include "llvm/ADT/StringRef.h"
12+
#include "llvm/ADT/iterator.h"
1113
#include "llvm/Support/JSON.h"
1214

1315
#include "indexer/Comparison.h"
@@ -16,6 +18,34 @@
1618

1719
namespace scip_clang {
1820

21+
template <typename T>
22+
class PathPrefixIterator
23+
: llvm::iterator_facade_base<PathPrefixIterator<T>, std::input_iterator_tag,
24+
const T> {
25+
std::optional<T> data;
26+
27+
using Self = PathPrefixIterator<T>;
28+
using Base =
29+
llvm::iterator_facade_base<Self, std::input_iterator_tag, const T>;
30+
31+
public:
32+
explicit PathPrefixIterator<T>(std::optional<T> data) : data(data) {}
33+
bool operator==(const Self &other) const {
34+
return this->data == other.data;
35+
}
36+
37+
typename Base::reference operator*() const {
38+
return this->data.value();
39+
}
40+
41+
friend T;
42+
43+
Self &operator++() {
44+
this->data = this->data->prefix();
45+
return *this;
46+
}
47+
};
48+
1949
class AbsolutePathRef {
2050
std::string_view value; // is non-empty
2151

@@ -44,6 +74,20 @@ class AbsolutePathRef {
4474
/// Try to get the file name by slicing off the prefix till the last
4575
/// path separator.
4676
std::optional<std::string_view> fileName() const;
77+
78+
bool isNormalized() const;
79+
80+
void normalize(llvm::SmallVectorImpl<char> &newStorage);
81+
82+
PathPrefixIterator<AbsolutePathRef> prefixesBegin() const {
83+
return PathPrefixIterator<AbsolutePathRef>{*this};
84+
}
85+
PathPrefixIterator<AbsolutePathRef> prefixesEnd() const {
86+
return PathPrefixIterator<AbsolutePathRef>{std::nullopt};
87+
}
88+
89+
// Generally this should be avoided in favor
90+
std::optional<AbsolutePathRef> prefix() const;
4791
};
4892

4993
/// Typically used when referring to paths for files which may or may not
@@ -174,4 +218,4 @@ class RootPath final {
174218

175219
} // namespace scip_clang
176220

177-
#endif // SCIP_CLANG_PATH_H
221+
#endif // SCIP_CLANG_PATH_H

test/test_main.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,50 @@ TEST_CASE("UNIT_TESTS") {
107107
shouldntMatch));
108108
}
109109
}
110+
111+
{
112+
struct PathNormalizationTestCase {
113+
std::string path;
114+
bool isNormalized;
115+
};
116+
std::vector<PathNormalizationTestCase> testCases{
117+
{"/a/b/c", true},
118+
{"/a/./c", false},
119+
{"/a/../b", false},
120+
{"/a///b", false},
121+
};
122+
for (auto &testCase : testCases) {
123+
auto path = AbsolutePath(std::string(testCase.path));
124+
CHECK_MESSAGE(path.asRef().isNormalized() == testCase.isNormalized,
125+
fmt::format("expected {} to be {}normalized", testCase.path,
126+
testCase.isNormalized ? "" : "non-"));
127+
}
128+
}
129+
130+
{
131+
struct PathPrefixesTestCase {
132+
std::string path;
133+
std::vector<std::string> prefixes;
134+
};
135+
std::vector<PathPrefixesTestCase> testCases{
136+
{"/a/b", {"/", "/a", "/a/b"}},
137+
};
138+
for (auto &testCase : testCases) {
139+
auto &expectedPrefixes = testCase.prefixes;
140+
auto path = AbsolutePath(std::string(testCase.path));
141+
auto start = path.asRef().prefixesBegin();
142+
auto end = path.asRef().prefixesEnd();
143+
std::vector<std::string> gotPrefixes;
144+
for (auto it = start; it != end; ++it) {
145+
gotPrefixes.push_back(std::string((*it).asStringView()));
146+
}
147+
absl::c_reverse(gotPrefixes);
148+
CHECK_MESSAGE(absl::c_equal(expectedPrefixes, gotPrefixes),
149+
fmt::format("expected prefixes: {}\n actual prefixes: {}",
150+
fmt::join(expectedPrefixes, ", "),
151+
fmt::join(gotPrefixes, ", ")));
152+
}
153+
}
110154
};
111155

112156
TEST_CASE("COMPDB_PARSING") {

0 commit comments

Comments
 (0)