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 #[test]
240 fn test_assignment_operator_export() {
241 let makefile: Makefile = "export VAR := value\n".parse().unwrap();
242 let var = makefile.variable_definitions().next().unwrap();
243 assert_eq!(var.assignment_operator(), Some(":=".to_string()));
244 }
245
246 #[test]
247 fn test_set_assignment_operator_simple_to_conditional() {
248 let makefile: Makefile = "VAR = value\n".parse().unwrap();
249 let mut var = makefile.variable_definitions().next().unwrap();
250 var.set_assignment_operator("?=");
251 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
252 assert_eq!(makefile.code(), "VAR ?= value\n");
253 }
254
255 #[test]
256 fn test_set_assignment_operator_recursive_to_conditional() {
257 let makefile: Makefile = "VAR := value\n".parse().unwrap();
258 let mut var = makefile.variable_definitions().next().unwrap();
259 var.set_assignment_operator("?=");
260 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
261 assert_eq!(makefile.code(), "VAR ?= value\n");
262 }
263
264 #[test]
265 fn test_set_assignment_operator_preserves_export() {
266 let makefile: Makefile = "export VAR := value\n".parse().unwrap();
267 let mut var = makefile.variable_definitions().next().unwrap();
268 var.set_assignment_operator("?=");
269 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
270 assert!(var.is_export());
271 assert_eq!(makefile.code(), "export VAR ?= value\n");
272 }
273
274 #[test]
275 fn test_set_assignment_operator_preserves_whitespace() {
276 let makefile: Makefile = "VAR := value\n".parse().unwrap();
277 let mut var = makefile.variable_definitions().next().unwrap();
278 var.set_assignment_operator("?=");
279 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
280 assert_eq!(makefile.code(), "VAR ?= value\n");
281 }
282
283 #[test]
284 fn test_set_assignment_operator_preserves_value() {
285 let makefile: Makefile = "VAR := old_value\n".parse().unwrap();
286 let mut var = makefile.variable_definitions().next().unwrap();
287 var.set_assignment_operator("=");
288 assert_eq!(var.assignment_operator(), Some("=".to_string()));
289 assert_eq!(var.raw_value(), Some("old_value".to_string()));
290 assert_eq!(makefile.code(), "VAR = old_value\n");
291 }
292
293 #[test]
294 fn test_set_assignment_operator_to_triple_colon() {
295 let makefile: Makefile = "VAR := value\n".parse().unwrap();
296 let mut var = makefile.variable_definitions().next().unwrap();
297 var.set_assignment_operator("::=");
298 assert_eq!(var.assignment_operator(), Some("::=".to_string()));
299 assert_eq!(makefile.code(), "VAR ::= value\n");
300 }
301
302 #[test]
303 fn test_combined_operations() {
304 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
305 let mut var = makefile.variable_definitions().next().unwrap();
306
307 var.set_assignment_operator("?=");
309 assert_eq!(var.assignment_operator(), Some("?=".to_string()));
310
311 var.set_value("new_value");
313 assert_eq!(var.raw_value(), Some("new_value".to_string()));
314
315 assert!(var.is_export());
317 assert_eq!(var.name(), Some("VAR".to_string()));
318 assert_eq!(makefile.code(), "export VAR ?= new_value\n");
319 }
320}