nixd
Loading...
Searching...
No Matches
FlattenAttrs.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of flatten nested attribute sets code action.
3
4#include "FlattenAttrs.h"
5#include "Utils.h"
6
7#include "../Convert.h"
8
10
11#include <algorithm>
12
13namespace nixd {
14
15namespace {
16
17/// \brief Check if an ExprAttrs can be flattened (no rec, inherit, dynamic).
18/// Returns the Binds node if flattenable, nullptr otherwise.
19const nixf::Binds *getFlattenableBinds(const nixf::ExprAttrs &Attrs) {
20 // Block if recursive attribute set
21 if (Attrs.isRecursive())
22 return nullptr;
23
24 const nixf::Binds *B = Attrs.binds();
25 if (!B || B->bindings().empty())
26 return nullptr;
27
28 // Check all bindings: must be plain Binding nodes (no Inherit)
29 // and all attribute names must be static (no dynamic ${} interpolation)
30 for (const auto &Child : B->bindings()) {
31 if (Child->kind() != nixf::Node::NK_Binding)
32 return nullptr; // Inherit node found
33
34 const auto &Bind = static_cast<const nixf::Binding &>(*Child);
35 for (const auto &Name : Bind.path().names()) {
36 if (!Name->isStatic())
37 return nullptr; // Dynamic attribute name
38 }
39 }
40
41 return B;
42}
43
44} // namespace
45
48 const std::string &FileURI, llvm::StringRef Src,
49 std::vector<lspserver::CodeAction> &Actions) {
50 // Find if we're inside a Binding
51 const nixf::Node *BindingNode = PM.upTo(N, nixf::Node::NK_Binding);
52 if (!BindingNode)
53 return;
54
55 const auto &Bind = static_cast<const nixf::Binding &>(*BindingNode);
56
57 // Check if the binding's value is an ExprAttrs
58 if (!Bind.value() || Bind.value()->kind() != nixf::Node::NK_ExprAttrs)
59 return;
60
61 const auto &NestedAttrs = static_cast<const nixf::ExprAttrs &>(*Bind.value());
62
63 // Check if flattenable
64 const nixf::Binds *NestedBinds = getFlattenableBinds(NestedAttrs);
65 if (!NestedBinds)
66 return;
67
68 // Check outer path is static too
69 for (const auto &Name : Bind.path().names()) {
70 if (!Name->isStatic())
71 return;
72 }
73
74 // Parser recovery can leave missing delimiters. Only rewrite complete
75 // bindings, so an inner delimiter cannot be mistaken for the outer one.
76 const auto CompleteBinding = [&](const nixf::Binding &B) {
77 return B.eq() && B.value() &&
78 B.rCur().offset() > B.value()->rCur().offset() &&
79 B.src(Src).back() == ';';
80 };
81 if (!CompleteBinding(Bind))
82 return;
83
84 const size_t Open = NestedAttrs.lCur().offset();
85 const size_t Close = NestedAttrs.rCur().offset() - 1;
86 if (Src[Open] != '{' || Src[Close] != '}')
87 return;
88
89 for (const auto &Child : NestedBinds->bindings()) {
90 const auto &Inner = static_cast<const nixf::Binding &>(*Child);
91 if (!CompleteBinding(Inner) || Inner.rCur().offset() > Close)
92 return;
93 }
94
95 // Copy names without the trivia between path segments. That trivia belongs
96 // to the outer binding and must survive once, not once per nested binding.
97 std::string Prefix;
98 for (const auto &Name : Bind.path().names()) {
99 Prefix += Name->src(Src);
100 Prefix += ".";
101 }
102
103 std::string NewText;
104 const auto AppendSource = [&](size_t Begin, size_t End) {
105 NewText += Src.substr(Begin, End - Begin);
106 };
107
108 // Remove only the outer path's tokens, retaining comments between them.
109 auto PathTokens = Bind.path().children();
110 std::sort(PathTokens.begin(), PathTokens.end(),
111 [](const nixf::Node *L, const nixf::Node *R) {
112 return L->lCur().offset() < R->lCur().offset();
113 });
114 size_t Pos = Bind.lCur().offset();
115 for (const auto *Token : PathTokens) {
116 AppendSource(Pos, Token->lCur().offset());
117 Pos = Token->rCur().offset();
118 }
119 AppendSource(Pos, Bind.eq()->lCur().offset());
120 AppendSource(Bind.eq()->rCur().offset(), Open);
121
122 // Preserve the entire body, including gaps between bindings and the original
123 // values. In particular, do not reindent multiline strings or line comments.
124 Pos = Open + 1;
125 for (const auto &Child : NestedBinds->bindings()) {
126 AppendSource(Pos, Child->lCur().offset());
127 NewText += Prefix;
128 Pos = Child->lCur().offset();
129 }
130 AppendSource(Pos, Close);
131 AppendSource(Close + 1, Bind.rCur().offset() - 1);
132
133 Actions.emplace_back(createSingleEditAction(
134 "Flatten nested attribute set",
136 toLSPRange(Src, Bind.range()), std::move(NewText)));
137}
138
139} // namespace nixd
Convert between LSP and nixf types.
Code action for flattening nested attribute sets.
Shared utilities for code actions.
const std::vector< std::shared_ptr< Node > > & bindings() const
Definition Attrs.h:180
bool isRecursive() const
Definition Attrs.h:287
const Binds * binds() const
Definition Attrs.h:284
std::size_t offset() const
Offset in the source file, starting from 0.
Definition Range.h:102
std::string_view src(std::string_view Src) const
Definition Basic.h:63
LexerCursor rCur() const
Definition Basic.h:38
const Node * upTo(const Node &N, Node::NodeKind Kind) const
Search up until some kind of node is found.
Definition ParentMap.cpp:27
void addFlattenAttrsAction(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, const std::string &FileURI, llvm::StringRef Src, std::vector< lspserver::CodeAction > &Actions)
Add flatten action for nested attribute sets.
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
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
Definition Convert.cpp:40
static const llvm::StringLiteral REFACTOR_REWRITE_KIND