nixd
Loading...
Searching...
No Matches
PathResolve.h
Go to the documentation of this file.
1/// \file
2/// \brief Shared path resolution utilities for Nix path literals.
3///
4/// This module provides unified path resolution logic used by both
5/// DocumentLink and Go-to-Definition features.
6
7#pragma once
8
9#include "lspserver/Logger.h"
10
11#include <filesystem>
12#include <optional>
13#include <string>
14
15namespace nixd {
16
17/// \brief Resolve a Nix expression path to a real filesystem path.
18///
19/// This function resolves relative paths against the base file's directory,
20/// handles directory → default.nix conversion, and validates the result.
21///
22/// Used by both DocumentLink (for clickable links) and Definition (for
23/// go-to-definition on path literals).
24///
25/// \param BasePath The path of the file containing the path literal.
26/// \param ExprPath The path literal string from the Nix expression.
27/// \return The resolved absolute path, or nullopt if resolution fails.
28inline std::optional<std::string> resolveExprPath(const std::string &BasePath,
29 const std::string &ExprPath) {
30 namespace fs = std::filesystem;
31
32 lspserver::vlog("path-resolve: BasePath={0}, ExprPath={1}", BasePath,
33 ExprPath);
34
35 // Input validation: reject empty or excessively long paths
36 if (ExprPath.empty() || ExprPath.length() > 4096) {
37 lspserver::elog("path-resolve: input validation failed (empty={0}, "
38 "length={1})",
39 ExprPath.empty(), ExprPath.length());
40 return std::nullopt;
41 }
42
43 try {
44 // Get the base directory from the file path
45 std::error_code EC;
46 fs::path BaseDir = fs::path(BasePath).parent_path();
47 if (BaseDir.empty()) {
48 lspserver::elog("path-resolve: base directory is empty");
49 return std::nullopt;
50 }
51
52 // Canonicalize base directory to resolve symlinks
53 fs::path BaseDirCanonical = fs::canonical(BaseDir, EC);
54 if (EC) {
55 lspserver::elog("path-resolve: canonical failed for {0}: {1}",
56 BaseDir.string(), EC.message());
57 return std::nullopt;
58 }
59
60 // Resolve the user-provided path relative to base directory
61 // Use weakly_canonical to handle non-existent intermediate components
62 fs::path ResolvedPath =
63 fs::weakly_canonical(BaseDirCanonical / ExprPath, EC);
64 if (EC) {
65 lspserver::elog("path-resolve: weakly_canonical failed for {0}: {1}",
66 (BaseDirCanonical / ExprPath).string(), EC.message());
67 return std::nullopt;
68 }
69
70 // Check if target exists
71 auto Status = fs::status(ResolvedPath, EC);
72 if (EC || !fs::exists(Status)) {
73 lspserver::elog("path-resolve: target does not exist: {0}",
74 ResolvedPath.string());
75 return std::nullopt;
76 }
77
78 // If it's a directory, append default.nix (Nix import convention)
79 if (fs::is_directory(Status)) {
80 ResolvedPath = ResolvedPath / "default.nix";
81 if (!fs::exists(ResolvedPath, EC) || EC) {
82 lspserver::elog("path-resolve: default.nix not found in directory: {0}",
83 ResolvedPath.string());
84 return std::nullopt;
85 }
86 }
87
88 lspserver::vlog("path-resolve: resolved to {0}", ResolvedPath.string());
89 return ResolvedPath.string();
90 } catch (const fs::filesystem_error &E) {
91 lspserver::elog("path-resolve: filesystem error: {0}", E.what());
92 return std::nullopt;
93 }
94}
95
96} // namespace nixd
void elog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:52
void vlog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:63
std::optional< std::string > resolveExprPath(const std::string &BasePath, const std::string &ExprPath)
Resolve a Nix expression path to a real filesystem path.
Definition PathResolve.h:28