nixd
Loading...
Searching...
No Matches
JsonToNix.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of JSON to Nix conversion code action.
3
4#include "JsonToNix.h"
5#include "Utils.h"
6
7#include <llvm/Support/JSON.h>
9
10#include <iomanip>
11#include <sstream>
12
13namespace nixd {
14
15namespace {
16
17/// \brief Convert a JSON value to Nix expression syntax.
18/// \param V The JSON value to convert
19/// \param Indent Current indentation level (for pretty-printing)
20/// \param Depth Current recursion depth (safety limit)
21/// \return Nix expression string, or empty string on error
22std::string jsonToNix(const llvm::json::Value &V, size_t Indent = 0,
23 size_t Depth = 0) {
24 if (Depth > MaxJsonDepth)
25 return ""; // Safety limit exceeded
26
27 std::string IndentStr(Indent * 2, ' ');
28 std::string NextIndent((Indent + 1) * 2, ' ');
29 std::string Out;
30
31 if (V.kind() == llvm::json::Value::Null) {
32 Out = "null";
33 } else if (auto B = V.getAsBoolean()) {
34 Out = *B ? "true" : "false";
35 } else if (auto I = V.getAsInteger()) {
36 Out = std::to_string(*I);
37 } else if (auto D = V.getAsNumber()) {
38 // Format floating point with enough precision
39 std::ostringstream SS;
40 SS << std::setprecision(17) << *D;
41 Out = SS.str();
42 } else if (auto S = V.getAsString()) {
43 Out = "\"" + escapeNixString(*S) + "\"";
44 } else if (const auto *A = V.getAsArray()) {
45 if (A->size() > MaxJsonWidth)
46 return ""; // Width limit exceeded
47 if (A->empty()) {
48 Out = "[ ]";
49 } else {
50 // Pre-allocate memory to reduce reallocations
51 // Estimate: opening + closing + elements * (indent + value_estimate +
52 // newline)
53 size_t EstimatedSize = 4 + A->size() * ((Indent + 1) * 2 + 20);
54 Out.reserve(EstimatedSize);
55 Out = "[\n";
56 for (size_t I = 0; I < A->size(); ++I) {
57 std::string Elem = jsonToNix((*A)[I], Indent + 1, Depth + 1);
58 if (Elem.empty())
59 return ""; // Propagate error
60 Out += NextIndent + Elem;
61 if (I + 1 < A->size())
62 Out += "\n";
63 }
64 Out += "\n" + IndentStr + "]";
65 }
66 } else if (const auto *O = V.getAsObject()) {
67 if (O->size() > MaxJsonWidth)
68 return ""; // Width limit exceeded
69 if (O->empty()) {
70 Out = "{ }";
71 } else {
72 // Pre-allocate memory to reduce reallocations
73 // Estimate: braces + elements * (indent + key + " = " + value_estimate +
74 // ";\n")
75 size_t EstimatedSize = 4 + O->size() * ((Indent + 1) * 2 + 30);
76 Out.reserve(EstimatedSize);
77 Out = "{\n";
78 size_t I = 0;
79 for (const auto &KV : *O) {
80 std::string Key = quoteNixAttrKey(KV.first.str());
81 std::string Val = jsonToNix(KV.second, Indent + 1, Depth + 1);
82 if (Val.empty())
83 return ""; // Propagate error
84 Out += NextIndent + Key + " = " + Val + ";";
85 if (I + 1 < O->size())
86 Out += "\n";
87 ++I;
88 }
89 Out += "\n" + IndentStr + "}";
90 }
91 }
92 return Out;
93}
94
95} // namespace
96
97void addJsonToNixAction(llvm::StringRef Src, const lspserver::Range &Range,
98 const std::string &FileURI,
99 std::vector<lspserver::CodeAction> &Actions) {
100 // Convert LSP positions to byte offsets
101 llvm::Expected<size_t> StartOffset =
103 llvm::Expected<size_t> EndOffset =
105
106 if (!StartOffset || !EndOffset) {
107 if (!StartOffset)
108 llvm::consumeError(StartOffset.takeError());
109 if (!EndOffset)
110 llvm::consumeError(EndOffset.takeError());
111 return;
112 }
113
114 // Validate range
115 if (*StartOffset >= *EndOffset || *EndOffset > Src.size())
116 return;
117
118 // Extract selected text
119 llvm::StringRef SelectedText =
120 Src.substr(*StartOffset, *EndOffset - *StartOffset);
121
122 // Skip if selection is too short (minimum valid JSON is "{}" or "[]")
123 if (SelectedText.size() < 2)
124 return;
125
126 // Skip if first character is not { or [ (quick rejection)
127 char First = SelectedText.front();
128 if (First != '{' && First != '[')
129 return;
130
131 // Try to parse as JSON
132 llvm::Expected<llvm::json::Value> JsonVal = llvm::json::parse(SelectedText);
133 if (!JsonVal) {
134 llvm::consumeError(JsonVal.takeError());
135 return;
136 }
137
138 // Skip empty JSON structures - already valid Nix
139 if (const auto *A = JsonVal->getAsArray()) {
140 if (A->empty())
141 return;
142 } else if (const auto *O = JsonVal->getAsObject()) {
143 if (O->empty())
144 return;
145 }
146
147 // Convert JSON to Nix
148 std::string NixText = jsonToNix(*JsonVal);
149 if (NixText.empty())
150 return;
151
152 Actions.emplace_back(createSingleEditAction(
154 FileURI, Range, std::move(NixText)));
155}
156
157} // namespace nixd
Code action for converting JSON to Nix expressions.
Shared utilities for code actions.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength=true)
void addJsonToNixAction(llvm::StringRef Src, const lspserver::Range &Range, const std::string &FileURI, std::vector< lspserver::CodeAction > &Actions)
Add JSON to Nix conversion action for selected JSON text.
Definition JsonToNix.cpp:97
constexpr size_t MaxJsonDepth
Maximum recursion depth for JSON to Nix conversion.
Definition Utils.h:44
constexpr size_t MaxJsonWidth
Maximum array/object width for JSON to Nix conversion.
Definition Utils.h:47
lspserver::CodeAction createSingleEditAction(const std::string &Title, llvm::StringLiteral Kind, const std::string &FileURI, const lspserver::Range &EditRange, std::string NewText)
Create a CodeAction with a single text edit.
Definition Utils.cpp:10
std::string escapeNixString(llvm::StringRef S)
Escape special characters for Nix double-quoted string literals.
Definition Utils.cpp:52
std::string quoteNixAttrKey(const std::string &Key)
Quote and escape a Nix attribute key if necessary.
Definition Utils.cpp:89
static const llvm::StringLiteral REFACTOR_REWRITE_KIND
Position start
The range's start position.
Position end
The range's end position.