makefile_lossless/ast/
variable.rs1use super::makefile::MakefileItem;
2use crate::lossless::{remove_with_preceding_comments, VariableDefinition};
3use crate::SyntaxKind::*;
4use rowan::ast::AstNode;
5use rowan::{GreenNodeBuilder, SyntaxNode};
6
7impl VariableDefinition {
8 pub fn name(&self) -> Option<String> {
10 self.syntax().children_with_tokens().find_map(|it| {
11 it.as_token().and_then(|it| {
12 if it.kind() == IDENTIFIER && it.text() != "export" {
13 Some(it.text().to_string())
14 } else {
15 None
16 }
17 })
18 })
19 }
20
21 pub fn is_export(&self) -> bool {
23 self.syntax()
24 .children_with_tokens()
25 .any(|it| it.as_token().is_some_and(|token| token.text() == "export"))
26 }
27
28 pub fn assignment_operator(&self) -> Option<String> {
40 self.syntax().children_with_tokens().find_map(|it| {
41 it.as_token().and_then(|token| {
42 if token.kind() == OPERATOR {
43 Some(token.text().to_string())
44 } else {
45 None
46 }
47 })
48 })
49 }
50
51 pub fn raw_value(&self) -> Option<String> {
53 self.syntax()
54 .children()
55 .find(|it| it.kind() == EXPR)
56 .map(|it| it.text().into())
57 }
58
59 pub fn parent(&self) -> Option<MakefileItem> {
78 self.syntax().parent().and_then(MakefileItem::cast)
79 }
80
81 pub fn remove(&mut self) {
94 if let Some(parent) = self.syntax().parent() {
95 remove_with_preceding_comments(self.syntax(), &parent);
96 }
97 }
98
99 pub fn set_assignment_operator(&mut self, op: &str) {
115 let mut builder = GreenNodeBuilder::new();
117 builder.start_node(VARIABLE.into());
118
119 for child in self.syntax().children_with_tokens() {
120 match child {
121 rowan::NodeOrToken::Token(token) if token.kind() == OPERATOR => {
122 builder.token(OPERATOR.into(), op);
123 }
124 rowan::NodeOrToken::Token(token) => {
125 builder.token(token.kind().into(), token.text());
126 }
127 rowan::NodeOrToken::Node(node) => {
128 builder.start_node(node.kind().into());
130 for node_child in node.children_with_tokens() {
131 if let rowan::NodeOrToken::Token(token) = node_child {
132 builder.token(token.kind().into(), token.text());
133 }
134 }
135 builder.finish_node();
136 }
137 }
138 }
139
140 builder.finish_node();
141 let new_variable = SyntaxNode::new_root_mut(builder.finish());
142
143 let index = self.syntax().index();
145 if let Some(parent) = self.syntax().parent() {
146 parent.splice_children(index..index + 1, vec![new_variable.clone().into()]);
147
148 *self = VariableDefinition::cast(
150 parent
151 .children_with_tokens()
152 .nth(index)
153 .and_then(|it| it.into_node())
154 .unwrap(),
155 )
156 .unwrap();
157 }
158 }
159
160 pub fn set_value(&mut self, new_value: &str) {
173 let expr_index = self
175 .syntax()
176 .children()
177 .find(|it| it.kind() == EXPR)
178 .map(|it| it.index());
179
180 if let Some(expr_idx) = expr_index {
181 let mut builder = GreenNodeBuilder::new();
183 builder.start_node(EXPR.into());
184 builder.token(IDENTIFIER.into(), new_value);
185 builder.finish_node();
186
187 let new_expr = SyntaxNode::new_root_mut(builder.finish());
188
189 self.syntax()
191 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198
199 use crate::lossless::Makefile;
200
201 #[test]
202 fn test_variable_parent() {
203 let makefile: Makefile = "VAR = value\n".parse().unwrap();
204
205 let var = makefile.variable_definitions().next().unwrap();
206 let parent = var.parent();
207 assert!(parent.is_none());
209 }
210
211 #[test]
212 fn test_assignment_operator_simple() {
213 let makefile: Makefile = "VAR = value\n".parse().unwrap();
214 let var = makefile.variable_definitions().next().unwrap();
215 assert_eq!(var.assignment_operator(), Some("=".to_string()));
216 }
217
218 #[test]
219 fn test_assignment_operator_recursive() {
220 let makefile: Makefile = "VAR := value\n".parse().unwrap();
221 let var = makefile.variable_definitions().next().unwrap();
222 assert_eq!(var.assignment_operator(), Some(":=".to_string()));
223 }
224
225 #[test]
226 fn test_assignment_operator_conditional() {
227 let makefile: Makefile = "VAR ?= value\n".parse().unwrap();
228 let var = makefile.variable_definitions().next().unwrap();
229 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
230 }
231
232 #[test]
233 fn test_assignment_operator_append() {
234 let makefile: Makefile = "VAR += value\n".parse().unwrap();
235 let var = makefile.variable_definitions().next().unwrap();
236 assert_eq!(var.assignment_operator(), Some("+=".to_string()));
237 }
238
239
240 #[test]
241 fn test_assignment_operator_export() {
242 let makefile: Makefile = "export VAR := value\n".parse().unwrap();
243 let var = makefile.variable_definitions().next().unwrap();
244 assert_eq!(var.assignment_operator(), Some(":=".to_string()));
245 }
246
247 #[test]
248 fn test_set_assignment_operator_simple_to_conditional() {
249 let makefile: Makefile = "VAR = value\n".parse().unwrap();
250 let mut var = makefile.variable_definitions().next().unwrap();
251 var.set_assignment_operator("?=");
252 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
253 assert_eq!(makefile.code(), "VAR ?= value\n");
254 }
255
256 #[test]
257 fn test_set_assignment_operator_recursive_to_conditional() {
258 let makefile: Makefile = "VAR := value\n".parse().unwrap();
259 let mut var = makefile.variable_definitions().next().unwrap();
260 var.set_assignment_operator("?=");
261 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
262 assert_eq!(makefile.code(), "VAR ?= value\n");
263 }
264
265 #[test]
266 fn test_set_assignment_operator_preserves_export() {
267 let makefile: Makefile = "export VAR := value\n".parse().unwrap();
268 let mut var = makefile.variable_definitions().next().unwrap();
269 var.set_assignment_operator("?=");
270 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
271 assert!(var.is_export());
272 assert_eq!(makefile.code(), "export VAR ?= value\n");
273 }
274
275 #[test]
276 fn test_set_assignment_operator_preserves_whitespace() {
277 let makefile: Makefile = "VAR := value\n".parse().unwrap();
278 let mut var = makefile.variable_definitions().next().unwrap();
279 var.set_assignment_operator("?=");
280 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
281 assert_eq!(makefile.code(), "VAR ?= value\n");
282 }
283
284 #[test]
285 fn test_set_assignment_operator_preserves_value() {
286 let makefile: Makefile = "VAR := old_value\n".parse().unwrap();
287 let mut var = makefile.variable_definitions().next().unwrap();
288 var.set_assignment_operator("=");
289 assert_eq!(var.assignment_operator(), Some("=".to_string()));
290 assert_eq!(var.raw_value(), Some("old_value".to_string()));
291 assert_eq!(makefile.code(), "VAR = old_value\n");
292 }
293
294 #[test]
295 fn test_set_assignment_operator_to_triple_colon() {
296 let makefile: Makefile = "VAR := value\n".parse().unwrap();
297 let mut var = makefile.variable_definitions().next().unwrap();
298 var.set_assignment_operator("::=");
299 assert_eq!(var.assignment_operator(), Some("::=".to_string()));
300 assert_eq!(makefile.code(), "VAR ::= value\n");
301 }
302
303 #[test]
304 fn test_combined_operations() {
305 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
306 let mut var = makefile.variable_definitions().next().unwrap();
307
308 var.set_assignment_operator("?=");
310 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
311
312 var.set_value("new_value");
314 assert_eq!(var.raw_value(), Some("new_value".to_string()));
315
316 assert!(var.is_export());
318 assert_eq!(var.name(), Some("VAR".to_string()));
319 assert_eq!(makefile.code(), "export VAR ?= new_value\n");
320 }
321}