Skip to content

Commit 1385e79

Browse files
committed
Implement path canonicalization function
To search for config files, we need to resolve symbolic links and simplify paths. Create a function, canonicalize_path, which computes an absolute canonical path. A future commit will use the new canonicalize_path function.
1 parent 9aef286 commit 1385e79

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

src/file.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616
#include <quick-lint-js/math-overflow.h>
1717
#include <quick-lint-js/narrow-cast.h>
1818
#include <quick-lint-js/utf-16.h>
19+
#include <stdlib.h>
1920
#include <string>
2021

2122
#if QLJS_HAVE_FCNTL_H
2223
#include <fcntl.h>
2324
#endif
2425

26+
#if QLJS_HAVE_STD_FILESYSTEM
27+
#include <filesystem>
28+
#endif
29+
2530
#if QLJS_HAVE_SYS_STAT_H
2631
#include <sys/stat.h>
2732
#endif
@@ -271,6 +276,50 @@ void write_file(const char *path, string8_view content) {
271276

272277
std::fclose(file);
273278
}
279+
280+
const char *canonical_path_result::c_str() const noexcept {
281+
QLJS_ASSERT(this->ok());
282+
return this->path.c_str();
283+
}
284+
285+
canonical_path_result canonical_path_result::failure(std::string &&error) {
286+
canonical_path_result result;
287+
result.error = std::move(error);
288+
QLJS_ASSERT(!result.ok());
289+
return result;
290+
}
291+
292+
canonical_path_result canonicalize_path(const char *path) {
293+
#if QLJS_HAVE_STD_FILESYSTEM
294+
std::error_code error;
295+
std::filesystem::path canonical = std::filesystem::canonical(path, error);
296+
if (error) {
297+
return canonical_path_result::failure(
298+
std::string("failed to canonicalize path ") + path + ": " +
299+
error.message());
300+
}
301+
canonical_path_result result;
302+
result.path = canonical.string();
303+
return result;
304+
#elif QLJS_HAVE_REALPATH
305+
char *allocated_canonical = ::realpath(path, nullptr);
306+
if (!allocated_canonical) {
307+
return canonical_path_result::failure(
308+
std::string("failed to canonicalize path ") + path + ": " +
309+
std::strerror(errno));
310+
}
311+
canonical_path_result result;
312+
result.path = allocated_canonical;
313+
std::free(allocated_canonical);
314+
return result;
315+
#else
316+
#error "Unsupported platform"
317+
#endif
318+
}
319+
320+
canonical_path_result canonicalize_path(const std::string &path) {
321+
return canonicalize_path(path.c_str());
322+
}
274323
}
275324

276325
// quick-lint-js finds bugs in JavaScript programs.

src/quick-lint-js/file.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ read_file_result read_stdin(void);
2929

3030
void write_file(const std::string &path, string8_view content);
3131
void write_file(const char *path, string8_view content);
32+
33+
struct canonical_path_result {
34+
std::string path;
35+
std::string error;
36+
37+
bool ok() const noexcept { return this->error.empty(); }
38+
39+
const char *c_str() const noexcept;
40+
41+
static canonical_path_result failure(std::string &&error);
42+
};
43+
44+
canonical_path_result canonicalize_path(const char *path);
45+
canonical_path_result canonicalize_path(const std::string &path);
3246
}
3347

3448
#endif

src/quick-lint-js/have.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@
136136
#endif
137137
#endif
138138

139+
#if !defined(QLJS_HAVE_REALPATH)
140+
#if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L) || \
141+
(defined(__APPLE__) && defined(_POSIX_VERSION) && \
142+
_POSIX_VERSION >= 200112L)
143+
#define QLJS_HAVE_REALPATH 1
144+
#else
145+
#define QLJS_HAVE_REALPATH 0
146+
#endif
147+
#endif
148+
139149
#if !defined(QLJS_HAVE_SETRLIMIT)
140150
#if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L) || \
141151
(defined(__APPLE__) && defined(_POSIX_VERSION) && \

test/test-file.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,29 @@ TEST_F(test_file, read_pipe_empty_writes) {
200200

201201
writer_thread.join();
202202
}
203+
204+
TEST_F(test_file, canonical_path_to_regular_file) {
205+
std::string temp_file_path = this->make_temporary_directory() + "/temp.js";
206+
write_file(temp_file_path, u8"hello\nworld!\n");
207+
208+
canonical_path_result canonical = canonicalize_path(temp_file_path);
209+
ASSERT_TRUE(canonical.ok()) << canonical.error;
210+
211+
read_file_result file_content = read_file(canonical.c_str());
212+
ASSERT_TRUE(file_content.ok()) << file_content.error;
213+
EXPECT_EQ(file_content.content, string8_view(u8"hello\nworld!\n"));
214+
}
215+
216+
TEST_F(test_file, canonical_path_to_non_existing_file) {
217+
std::string temp_file_path =
218+
this->make_temporary_directory() + "/does-not-exist.js";
219+
220+
canonical_path_result canonical = canonicalize_path(temp_file_path);
221+
EXPECT_FALSE(canonical.ok());
222+
EXPECT_THAT(canonical.error, HasSubstr("does-not-exist.js"));
223+
EXPECT_THAT(canonical.error,
224+
AnyOf(HasSubstr("No such file"), HasSubstr("cannot find")));
225+
}
203226
}
204227
}
205228

0 commit comments

Comments
 (0)