1use crate::lossless::{
2 parse, Conditional, Error, ErrorInfo, Include, Makefile, ParseError, Rule, SyntaxNode,
3 VariableDefinition,
4};
5use crate::pattern::matches_pattern;
6use crate::SyntaxKind::*;
7use rowan::ast::AstNode;
8use rowan::GreenNodeBuilder;
9use std::collections::VecDeque;
10
11#[derive(Clone)]
13pub enum MakefileItem {
14 Rule(Rule),
16 Variable(VariableDefinition),
18 Include(Include),
20 Conditional(Conditional),
22}
23
24impl MakefileItem {
25 pub(crate) fn cast(node: SyntaxNode) -> Option<Self> {
27 if let Some(rule) = Rule::cast(node.clone()) {
28 Some(MakefileItem::Rule(rule))
29 } else if let Some(var) = VariableDefinition::cast(node.clone()) {
30 Some(MakefileItem::Variable(var))
31 } else if let Some(inc) = Include::cast(node.clone()) {
32 Some(MakefileItem::Include(inc))
33 } else {
34 Conditional::cast(node).map(MakefileItem::Conditional)
35 }
36 }
37
38 pub(crate) fn syntax(&self) -> &SyntaxNode {
40 match self {
41 MakefileItem::Rule(r) => r.syntax(),
42 MakefileItem::Variable(v) => v.syntax(),
43 MakefileItem::Include(i) => i.syntax(),
44 MakefileItem::Conditional(c) => c.syntax(),
45 }
46 }
47
48 fn get_parent_or_error(&self, action: &str, method: &str) -> Result<SyntaxNode, Error> {
50 self.syntax().parent().ok_or_else(|| {
51 Error::Parse(ParseError {
52 errors: vec![ErrorInfo {
53 message: format!("Cannot {} item without parent", action),
54 line: 1,
55 context: format!("MakefileItem::{}", method),
56 }],
57 })
58 })
59 }
60
61 fn is_regular_comment(token: &rowan::SyntaxToken<crate::lossless::Lang>) -> bool {
63 token.kind() == COMMENT && !token.text().starts_with("#!")
64 }
65
66 fn extract_comment_text(token: &rowan::SyntaxToken<crate::lossless::Lang>) -> String {
68 let text = token.text();
69 text.strip_prefix("# ")
70 .or_else(|| text.strip_prefix('#'))
71 .unwrap_or(text)
72 .to_string()
73 }
74
75 fn collect_preceding_comment_elements(
79 &self,
80 ) -> Vec<rowan::NodeOrToken<SyntaxNode, rowan::SyntaxToken<crate::lossless::Lang>>> {
81 let mut elements = Vec::new();
82 let mut current = self.syntax().prev_sibling_or_token();
83
84 while let Some(element) = current {
85 match &element {
86 rowan::NodeOrToken::Token(token) if Self::is_regular_comment(token) => {
87 elements.push(element.clone());
88 }
89 rowan::NodeOrToken::Token(token)
90 if token.kind() == NEWLINE || token.kind() == WHITESPACE =>
91 {
92 elements.push(element.clone());
93 }
94 rowan::NodeOrToken::Node(n) if n.kind() == BLANK_LINE => {
95 elements.push(element.clone());
96 }
97 rowan::NodeOrToken::Token(token) if token.kind() == COMMENT => {
98 break;
100 }
101 _ => break,
102 }
103 current = element.prev_sibling_or_token();
104 }
105
106 elements
107 }
108
109 fn parse_comment_tokens(
111 comment_text: &str,
112 ) -> (
113 rowan::SyntaxToken<crate::lossless::Lang>,
114 Option<rowan::SyntaxToken<crate::lossless::Lang>>,
115 ) {
116 let comment_line = format!("# {}\n", comment_text);
117 let temp_makefile = crate::lossless::parse(&comment_line, None);
118 let root = temp_makefile.root();
119
120 let mut comment_token = None;
121 let mut newline_token = None;
122 let mut found_comment = false;
123
124 for element in root.syntax().children_with_tokens() {
125 if let rowan::NodeOrToken::Token(token) = element {
126 if token.kind() == COMMENT {
127 comment_token = Some(token);
128 found_comment = true;
129 } else if token.kind() == NEWLINE && found_comment && newline_token.is_none() {
130 newline_token = Some(token);
131 break;
132 }
133 }
134 }
135
136 (
137 comment_token.expect("Failed to extract comment token"),
138 newline_token,
139 )
140 }
141
142 pub fn replace(&mut self, new_item: MakefileItem) -> Result<(), Error> {
159 let parent = self.get_parent_or_error("replace", "replace")?;
160 let current_index = self.syntax().index();
161
162 parent.splice_children(
164 current_index..current_index + 1,
165 vec![new_item.syntax().clone().into()],
166 );
167
168 *self = new_item;
170
171 Ok(())
172 }
173
174 pub fn add_comment(&mut self, comment_text: &str) -> Result<(), Error> {
188 let parent = self.get_parent_or_error("add comment to", "add_comment")?;
189 let current_index = self.syntax().index();
190
191 let (comment_token, newline_token) = Self::parse_comment_tokens(comment_text);
193
194 let mut elements = vec![rowan::NodeOrToken::Token(comment_token)];
195 if let Some(newline) = newline_token {
196 elements.push(rowan::NodeOrToken::Token(newline));
197 }
198
199 parent.splice_children(current_index..current_index, elements);
201
202 Ok(())
203 }
204
205 pub fn preceding_comments(&self) -> impl Iterator<Item = String> {
220 let elements = self.collect_preceding_comment_elements();
221 let mut comments = Vec::new();
222
223 for element in elements.iter().rev() {
225 if let rowan::NodeOrToken::Token(token) = element {
226 if token.kind() == COMMENT {
227 comments.push(Self::extract_comment_text(token));
228 }
229 }
230 }
231
232 comments.into_iter()
233 }
234
235 pub fn remove_comments(&mut self) -> Result<usize, Error> {
249 let parent = self.get_parent_or_error("remove comments from", "remove_comments")?;
250 let collected_elements = self.collect_preceding_comment_elements();
251
252 let mut comment_count = 0;
254 for element in collected_elements.iter() {
255 if let rowan::NodeOrToken::Token(token) = element {
256 if token.kind() == COMMENT {
257 comment_count += 1;
258 }
259 }
260 }
261
262 let mut elements_to_remove = Vec::new();
265 let mut consecutive_newlines = 0;
266 for element in collected_elements.iter().rev() {
267 let should_remove = match element {
268 rowan::NodeOrToken::Token(token) if token.kind() == COMMENT => {
269 consecutive_newlines = 0;
270 true }
272 rowan::NodeOrToken::Token(token) if token.kind() == NEWLINE => {
273 consecutive_newlines += 1;
274 comment_count > 0 && consecutive_newlines <= 1
275 }
276 rowan::NodeOrToken::Token(token) if token.kind() == WHITESPACE => comment_count > 0,
277 rowan::NodeOrToken::Node(n) if n.kind() == BLANK_LINE => {
278 consecutive_newlines += 1;
279 comment_count > 0 && consecutive_newlines <= 1
280 }
281 _ => false,
282 };
283
284 if should_remove {
285 elements_to_remove.push(element.clone());
286 }
287 }
288
289 elements_to_remove.sort_by_key(|el| std::cmp::Reverse(el.index()));
291 for element in elements_to_remove {
292 let idx = element.index();
293 parent.splice_children(idx..idx + 1, vec![]);
294 }
295
296 Ok(comment_count)
297 }
298
299 pub fn modify_comment(&mut self, new_comment_text: &str) -> Result<bool, Error> {
315 let parent = self.get_parent_or_error("modify comment for", "modify_comment")?;
316
317 let collected_elements = self.collect_preceding_comment_elements();
319 let comment_element = collected_elements.iter().find(|element| {
320 if let rowan::NodeOrToken::Token(token) = element {
321 token.kind() == COMMENT
322 } else {
323 false
324 }
325 });
326
327 if let Some(element) = comment_element {
328 let idx = element.index();
329 let (new_comment_token, _) = Self::parse_comment_tokens(new_comment_text);
330 parent.splice_children(
331 idx..idx + 1,
332 vec![rowan::NodeOrToken::Token(new_comment_token)],
333 );
334 Ok(true)
335 } else {
336 Ok(false)
337 }
338 }
339
340 pub fn insert_before(&mut self, new_item: MakefileItem) -> Result<(), Error> {
357 let parent = self.get_parent_or_error("insert before", "insert_before")?;
358 let current_index = self.syntax().index();
359
360 parent.splice_children(
362 current_index..current_index,
363 vec![new_item.syntax().clone().into()],
364 );
365
366 Ok(())
367 }
368
369 pub fn insert_after(&mut self, new_item: MakefileItem) -> Result<(), Error> {
386 let parent = self.get_parent_or_error("insert after", "insert_after")?;
387 let current_index = self.syntax().index();
388
389 parent.splice_children(
391 current_index + 1..current_index + 1,
392 vec![new_item.syntax().clone().into()],
393 );
394
395 Ok(())
396 }
397}
398
399trait ExtractFromItem: Sized {
401 fn extract(item: MakefileItem) -> Option<Self>;
402}
403
404impl ExtractFromItem for Rule {
405 fn extract(item: MakefileItem) -> Option<Self> {
406 match item {
407 MakefileItem::Rule(r) => Some(r),
408 _ => None,
409 }
410 }
411}
412
413impl ExtractFromItem for VariableDefinition {
414 fn extract(item: MakefileItem) -> Option<Self> {
415 match item {
416 MakefileItem::Variable(v) => Some(v),
417 _ => None,
418 }
419 }
420}
421
422struct RecursiveItemsIter<T> {
424 stack: VecDeque<MakefileItem>,
425 _phantom: std::marker::PhantomData<T>,
426}
427
428impl<T> RecursiveItemsIter<T> {
429 fn new(items: impl Iterator<Item = MakefileItem>) -> Self {
430 Self {
431 stack: items.collect(),
432 _phantom: std::marker::PhantomData,
433 }
434 }
435}
436
437impl<T: ExtractFromItem> Iterator for RecursiveItemsIter<T> {
438 type Item = T;
439
440 fn next(&mut self) -> Option<Self::Item> {
441 while let Some(item) = self.stack.pop_front() {
442 if let MakefileItem::Conditional(ref cond) = item {
443 self.stack.extend(cond.if_items());
445 self.stack.extend(cond.else_items());
446 }
447 if let Some(extracted) = T::extract(item) {
448 return Some(extracted);
449 }
450 }
451 None
452 }
453}
454
455impl Makefile {
456 pub fn new() -> Makefile {
458 let mut builder = GreenNodeBuilder::new();
459
460 builder.start_node(ROOT.into());
461 builder.finish_node();
462
463 let syntax = SyntaxNode::new_root_mut(builder.finish());
464 Makefile::cast(syntax).unwrap()
465 }
466
467 pub fn parse(text: &str) -> crate::Parse<Makefile> {
469 crate::Parse::<Makefile>::parse_makefile(text)
470 }
471
472 pub fn code(&self) -> String {
474 self.syntax().text().to_string()
475 }
476
477 pub fn is_root(&self) -> bool {
479 self.syntax().kind() == ROOT
480 }
481
482 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
484 let mut buf = String::new();
485 r.read_to_string(&mut buf)?;
486 buf.parse()
487 }
488
489 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
491 let mut buf = String::new();
492 r.read_to_string(&mut buf)?;
493
494 let parsed = parse(&buf, None);
495 Ok(parsed.root())
496 }
497
498 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
507 RecursiveItemsIter::new(self.items())
508 }
509
510 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
512 self.rules()
513 .filter(move |rule| rule.targets().any(|t| t == target))
514 }
515
516 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> + '_ {
518 RecursiveItemsIter::new(self.items())
519 }
520
521 pub fn conditionals(&self) -> impl Iterator<Item = Conditional> + '_ {
523 self.items().filter_map(|item| match item {
524 MakefileItem::Conditional(c) => Some(c),
525 _ => None,
526 })
527 }
528
529 pub fn items(&self) -> impl Iterator<Item = MakefileItem> + '_ {
545 self.syntax().children().filter_map(MakefileItem::cast)
546 }
547
548 pub fn find_variable<'a>(
563 &'a self,
564 name: &'a str,
565 ) -> impl Iterator<Item = VariableDefinition> + 'a {
566 self.variable_definitions()
567 .filter(move |var| var.name().as_deref() == Some(name))
568 }
569
570 pub fn add_rule(&mut self, target: &str) -> Rule {
580 let mut builder = GreenNodeBuilder::new();
581 builder.start_node(RULE.into());
582 builder.token(IDENTIFIER.into(), target);
583 builder.token(OPERATOR.into(), ":");
584 builder.token(NEWLINE.into(), "\n");
585 builder.finish_node();
586
587 let syntax = SyntaxNode::new_root_mut(builder.finish());
588 let pos = self.syntax().children_with_tokens().count();
589
590 let needs_blank_line = self.syntax().children().any(|c| c.kind() == RULE);
593
594 if needs_blank_line {
595 let mut bl_builder = GreenNodeBuilder::new();
597 bl_builder.start_node(BLANK_LINE.into());
598 bl_builder.token(NEWLINE.into(), "\n");
599 bl_builder.finish_node();
600 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
601
602 self.syntax()
603 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
604 } else {
605 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
606 }
607
608 Rule::cast(self.syntax().children().last().unwrap()).unwrap()
611 }
612
613 pub fn add_conditional(
629 &mut self,
630 conditional_type: &str,
631 condition: &str,
632 if_body: &str,
633 else_body: Option<&str>,
634 ) -> Result<Conditional, Error> {
635 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
637 return Err(Error::Parse(ParseError {
638 errors: vec![ErrorInfo {
639 message: format!(
640 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
641 conditional_type
642 ),
643 line: 1,
644 context: "add_conditional".to_string(),
645 }],
646 }));
647 }
648
649 let mut builder = GreenNodeBuilder::new();
650 builder.start_node(CONDITIONAL.into());
651
652 builder.start_node(CONDITIONAL_IF.into());
654 builder.token(IDENTIFIER.into(), conditional_type);
655 builder.token(WHITESPACE.into(), " ");
656
657 builder.start_node(EXPR.into());
659 builder.token(IDENTIFIER.into(), condition);
660 builder.finish_node();
661
662 builder.token(NEWLINE.into(), "\n");
663 builder.finish_node();
664
665 if !if_body.is_empty() {
667 for line in if_body.lines() {
668 if !line.is_empty() {
669 builder.token(IDENTIFIER.into(), line);
670 }
671 builder.token(NEWLINE.into(), "\n");
672 }
673 if !if_body.ends_with('\n') && !if_body.is_empty() {
675 builder.token(NEWLINE.into(), "\n");
676 }
677 }
678
679 if let Some(else_content) = else_body {
681 builder.start_node(CONDITIONAL_ELSE.into());
682 builder.token(IDENTIFIER.into(), "else");
683 builder.token(NEWLINE.into(), "\n");
684 builder.finish_node();
685
686 if !else_content.is_empty() {
688 for line in else_content.lines() {
689 if !line.is_empty() {
690 builder.token(IDENTIFIER.into(), line);
691 }
692 builder.token(NEWLINE.into(), "\n");
693 }
694 if !else_content.ends_with('\n') && !else_content.is_empty() {
696 builder.token(NEWLINE.into(), "\n");
697 }
698 }
699 }
700
701 builder.start_node(CONDITIONAL_ENDIF.into());
703 builder.token(IDENTIFIER.into(), "endif");
704 builder.token(NEWLINE.into(), "\n");
705 builder.finish_node();
706
707 builder.finish_node();
708
709 let syntax = SyntaxNode::new_root_mut(builder.finish());
710 let pos = self.syntax().children_with_tokens().count();
711
712 let needs_blank_line = self
714 .syntax()
715 .children()
716 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
717
718 if needs_blank_line {
719 let mut bl_builder = GreenNodeBuilder::new();
721 bl_builder.start_node(BLANK_LINE.into());
722 bl_builder.token(NEWLINE.into(), "\n");
723 bl_builder.finish_node();
724 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
725
726 self.syntax()
727 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
728 } else {
729 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
730 }
731
732 Ok(Conditional::cast(self.syntax().children().last().unwrap()).unwrap())
734 }
735
736 pub fn add_conditional_with_items<I1, I2>(
766 &mut self,
767 conditional_type: &str,
768 condition: &str,
769 if_items: I1,
770 else_items: Option<I2>,
771 ) -> Result<Conditional, Error>
772 where
773 I1: IntoIterator<Item = MakefileItem>,
774 I2: IntoIterator<Item = MakefileItem>,
775 {
776 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
778 return Err(Error::Parse(ParseError {
779 errors: vec![ErrorInfo {
780 message: format!(
781 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
782 conditional_type
783 ),
784 line: 1,
785 context: "add_conditional_with_items".to_string(),
786 }],
787 }));
788 }
789
790 let mut builder = GreenNodeBuilder::new();
791 builder.start_node(CONDITIONAL.into());
792
793 builder.start_node(CONDITIONAL_IF.into());
795 builder.token(IDENTIFIER.into(), conditional_type);
796 builder.token(WHITESPACE.into(), " ");
797
798 builder.start_node(EXPR.into());
800 builder.token(IDENTIFIER.into(), condition);
801 builder.finish_node();
802
803 builder.token(NEWLINE.into(), "\n");
804 builder.finish_node();
805
806 for item in if_items {
808 let item_text = item.syntax().to_string();
810 builder.token(IDENTIFIER.into(), item_text.trim());
812 builder.token(NEWLINE.into(), "\n");
813 }
814
815 if let Some(else_iter) = else_items {
817 builder.start_node(CONDITIONAL_ELSE.into());
818 builder.token(IDENTIFIER.into(), "else");
819 builder.token(NEWLINE.into(), "\n");
820 builder.finish_node();
821
822 for item in else_iter {
824 let item_text = item.syntax().to_string();
825 builder.token(IDENTIFIER.into(), item_text.trim());
826 builder.token(NEWLINE.into(), "\n");
827 }
828 }
829
830 builder.start_node(CONDITIONAL_ENDIF.into());
832 builder.token(IDENTIFIER.into(), "endif");
833 builder.token(NEWLINE.into(), "\n");
834 builder.finish_node();
835
836 builder.finish_node();
837
838 let syntax = SyntaxNode::new_root_mut(builder.finish());
839 let pos = self.syntax().children_with_tokens().count();
840
841 let needs_blank_line = self
843 .syntax()
844 .children()
845 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
846
847 if needs_blank_line {
848 let mut bl_builder = GreenNodeBuilder::new();
850 bl_builder.start_node(BLANK_LINE.into());
851 bl_builder.token(NEWLINE.into(), "\n");
852 bl_builder.finish_node();
853 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
854
855 self.syntax()
856 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
857 } else {
858 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
859 }
860
861 Ok(Conditional::cast(self.syntax().children().last().unwrap()).unwrap())
863 }
864
865 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
867 let mut buf = String::new();
868 r.read_to_string(&mut buf)?;
869
870 let parsed = parse(&buf, None);
871 if !parsed.errors.is_empty() {
872 Err(Error::Parse(ParseError {
873 errors: parsed.errors,
874 }))
875 } else {
876 Ok(parsed.root())
877 }
878 }
879
880 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
891 let rules: Vec<_> = self
892 .syntax()
893 .children()
894 .filter(|n| n.kind() == RULE)
895 .collect();
896
897 if rules.is_empty() {
898 return Err(Error::Parse(ParseError {
899 errors: vec![ErrorInfo {
900 message: "Cannot replace rule in empty makefile".to_string(),
901 line: 1,
902 context: "replace_rule".to_string(),
903 }],
904 }));
905 }
906
907 if index >= rules.len() {
908 return Err(Error::Parse(ParseError {
909 errors: vec![ErrorInfo {
910 message: format!(
911 "Rule index {} out of bounds (max {})",
912 index,
913 rules.len() - 1
914 ),
915 line: 1,
916 context: "replace_rule".to_string(),
917 }],
918 }));
919 }
920
921 let target_node = &rules[index];
922 let target_index = target_node.index();
923
924 self.syntax().splice_children(
926 target_index..target_index + 1,
927 vec![new_rule.syntax().clone().into()],
928 );
929 Ok(())
930 }
931
932 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
943 let rules: Vec<_> = self
944 .syntax()
945 .children()
946 .filter(|n| n.kind() == RULE)
947 .collect();
948
949 if rules.is_empty() {
950 return Err(Error::Parse(ParseError {
951 errors: vec![ErrorInfo {
952 message: "Cannot remove rule from empty makefile".to_string(),
953 line: 1,
954 context: "remove_rule".to_string(),
955 }],
956 }));
957 }
958
959 if index >= rules.len() {
960 return Err(Error::Parse(ParseError {
961 errors: vec![ErrorInfo {
962 message: format!(
963 "Rule index {} out of bounds (max {})",
964 index,
965 rules.len() - 1
966 ),
967 line: 1,
968 context: "remove_rule".to_string(),
969 }],
970 }));
971 }
972
973 let target_node = rules[index].clone();
974 let target_index = target_node.index();
975
976 self.syntax()
978 .splice_children(target_index..target_index + 1, vec![]);
979 Ok(Rule::cast(target_node).unwrap())
980 }
981
982 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
994 let rules: Vec<_> = self
995 .syntax()
996 .children()
997 .filter(|n| n.kind() == RULE)
998 .collect();
999
1000 if index > rules.len() {
1001 return Err(Error::Parse(ParseError {
1002 errors: vec![ErrorInfo {
1003 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1004 line: 1,
1005 context: "insert_rule".to_string(),
1006 }],
1007 }));
1008 }
1009
1010 let target_index = if index == rules.len() {
1011 self.syntax().children_with_tokens().count()
1013 } else {
1014 rules[index].index()
1016 };
1017
1018 let mut nodes_to_insert = Vec::new();
1020
1021 if index == 0 && !rules.is_empty() {
1023 nodes_to_insert.push(new_rule.syntax().clone().into());
1027
1028 let mut bl_builder = GreenNodeBuilder::new();
1030 bl_builder.start_node(BLANK_LINE.into());
1031 bl_builder.token(NEWLINE.into(), "\n");
1032 bl_builder.finish_node();
1033 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1034 nodes_to_insert.push(blank_line.into());
1035 } else if index < rules.len() {
1036 let has_blank_before = if target_index > 0 {
1049 self.syntax()
1050 .children_with_tokens()
1051 .nth(target_index - 1)
1052 .and_then(|n| n.as_node().map(|node| node.kind() == BLANK_LINE))
1053 .unwrap_or(false)
1054 } else {
1055 false
1056 };
1057
1058 if !has_blank_before && index > 0 {
1060 let mut bl_builder = GreenNodeBuilder::new();
1061 bl_builder.start_node(BLANK_LINE.into());
1062 bl_builder.token(NEWLINE.into(), "\n");
1063 bl_builder.finish_node();
1064 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1065 nodes_to_insert.push(blank_line.into());
1066 }
1067
1068 nodes_to_insert.push(new_rule.syntax().clone().into());
1070
1071 let mut bl_builder = GreenNodeBuilder::new();
1073 bl_builder.start_node(BLANK_LINE.into());
1074 bl_builder.token(NEWLINE.into(), "\n");
1075 bl_builder.finish_node();
1076 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1077 nodes_to_insert.push(blank_line.into());
1078 } else {
1079 let mut bl_builder = GreenNodeBuilder::new();
1082 bl_builder.start_node(BLANK_LINE.into());
1083 bl_builder.token(NEWLINE.into(), "\n");
1084 bl_builder.finish_node();
1085 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1086 nodes_to_insert.push(blank_line.into());
1087
1088 nodes_to_insert.push(new_rule.syntax().clone().into());
1090 }
1091
1092 self.syntax()
1094 .splice_children(target_index..target_index, nodes_to_insert);
1095 Ok(())
1096 }
1097
1098 pub fn includes(&self) -> impl Iterator<Item = Include> {
1108 self.syntax().children().filter_map(Include::cast)
1109 }
1110
1111 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1121 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1124 let mut includes = Vec::new();
1125
1126 if let Some(include) = Include::cast(node.clone()) {
1128 includes.push(include);
1129 }
1130
1131 for child in node.children() {
1133 includes.extend(collect_includes(&child));
1134 }
1135
1136 includes
1137 }
1138
1139 let includes = collect_includes(self.syntax());
1141
1142 includes.into_iter().map(|include| {
1144 include
1145 .syntax()
1146 .children()
1147 .find(|node| node.kind() == EXPR)
1148 .map(|expr| expr.text().to_string().trim().to_string())
1149 .unwrap_or_default()
1150 })
1151 }
1152
1153 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1164 self.rules()
1165 .find(|rule| rule.targets().any(|t| t == target))
1166 }
1167
1168 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1178 self.rules_by_target(target)
1179 }
1180
1181 pub fn find_rule_by_target_pattern(&self, target: &str) -> Option<Rule> {
1194 self.rules()
1195 .find(|rule| rule.targets().any(|t| matches_pattern(&t, target)))
1196 }
1197
1198 pub fn find_rules_by_target_pattern<'a>(
1211 &'a self,
1212 target: &'a str,
1213 ) -> impl Iterator<Item = Rule> + 'a {
1214 self.rules()
1215 .filter(move |rule| rule.targets().any(|t| matches_pattern(&t, target)))
1216 }
1217
1218 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1228 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1230 if !phony_rule.prerequisites().any(|p| p == target) {
1232 phony_rule.add_prerequisite(target)?;
1233 }
1234 } else {
1235 let mut phony_rule = self.add_rule(".PHONY");
1237 phony_rule.add_prerequisite(target)?;
1238 }
1239 Ok(())
1240 }
1241
1242 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1256 let mut phony_rule = None;
1258 for rule in self.rules_by_target(".PHONY") {
1259 if rule.prerequisites().any(|p| p == target) {
1260 phony_rule = Some(rule);
1261 break;
1262 }
1263 }
1264
1265 let mut phony_rule = match phony_rule {
1266 Some(rule) => rule,
1267 None => return Ok(false),
1268 };
1269
1270 let prereq_count = phony_rule.prerequisites().count();
1272
1273 phony_rule.remove_prerequisite(target)?;
1275
1276 if prereq_count == 1 {
1278 phony_rule.remove()?;
1280 }
1281
1282 Ok(true)
1283 }
1284
1285 pub fn is_phony(&self, target: &str) -> bool {
1296 self.rules_by_target(".PHONY")
1298 .any(|rule| rule.prerequisites().any(|p| p == target))
1299 }
1300
1301 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1311 self.rules_by_target(".PHONY")
1313 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1314 }
1315
1316 pub fn add_include(&mut self, path: &str) -> Include {
1329 let mut builder = GreenNodeBuilder::new();
1330 builder.start_node(INCLUDE.into());
1331 builder.token(IDENTIFIER.into(), "include");
1332 builder.token(WHITESPACE.into(), " ");
1333
1334 builder.start_node(EXPR.into());
1336 builder.token(IDENTIFIER.into(), path);
1337 builder.finish_node();
1338
1339 builder.token(NEWLINE.into(), "\n");
1340 builder.finish_node();
1341
1342 let syntax = SyntaxNode::new_root_mut(builder.finish());
1343
1344 self.syntax().splice_children(0..0, vec![syntax.into()]);
1346
1347 Include::cast(self.syntax().children().next().unwrap()).unwrap()
1349 }
1350
1351 pub fn insert_include(&mut self, index: usize, path: &str) -> Result<Include, Error> {
1368 let items: Vec<_> = self.syntax().children().collect();
1369
1370 if index > items.len() {
1371 return Err(Error::Parse(ParseError {
1372 errors: vec![ErrorInfo {
1373 message: format!("Index {} out of bounds (max {})", index, items.len()),
1374 line: 1,
1375 context: "insert_include".to_string(),
1376 }],
1377 }));
1378 }
1379
1380 let mut builder = GreenNodeBuilder::new();
1381 builder.start_node(INCLUDE.into());
1382 builder.token(IDENTIFIER.into(), "include");
1383 builder.token(WHITESPACE.into(), " ");
1384
1385 builder.start_node(EXPR.into());
1387 builder.token(IDENTIFIER.into(), path);
1388 builder.finish_node();
1389
1390 builder.token(NEWLINE.into(), "\n");
1391 builder.finish_node();
1392
1393 let syntax = SyntaxNode::new_root_mut(builder.finish());
1394
1395 let target_index = if index == items.len() {
1396 self.syntax().children_with_tokens().count()
1398 } else {
1399 items[index].index()
1401 };
1402
1403 self.syntax()
1405 .splice_children(target_index..target_index, vec![syntax.into()]);
1406
1407 Ok(Include::cast(self.syntax().children().nth(index).unwrap()).unwrap())
1410 }
1411
1412 pub fn insert_include_after(
1430 &mut self,
1431 after: &MakefileItem,
1432 path: &str,
1433 ) -> Result<Include, Error> {
1434 let mut builder = GreenNodeBuilder::new();
1435 builder.start_node(INCLUDE.into());
1436 builder.token(IDENTIFIER.into(), "include");
1437 builder.token(WHITESPACE.into(), " ");
1438
1439 builder.start_node(EXPR.into());
1441 builder.token(IDENTIFIER.into(), path);
1442 builder.finish_node();
1443
1444 builder.token(NEWLINE.into(), "\n");
1445 builder.finish_node();
1446
1447 let syntax = SyntaxNode::new_root_mut(builder.finish());
1448
1449 let after_syntax = after.syntax();
1451 let target_index = after_syntax.index() + 1;
1452
1453 self.syntax()
1455 .splice_children(target_index..target_index, vec![syntax.into()]);
1456
1457 let after_child_index = self
1460 .syntax()
1461 .children()
1462 .position(|child| child.text_range() == after_syntax.text_range())
1463 .ok_or_else(|| {
1464 Error::Parse(ParseError {
1465 errors: vec![ErrorInfo {
1466 message: "Could not find the reference item".to_string(),
1467 line: 1,
1468 context: "insert_include_after".to_string(),
1469 }],
1470 })
1471 })?;
1472
1473 Ok(Include::cast(self.syntax().children().nth(after_child_index + 1).unwrap()).unwrap())
1474 }
1475}
1476
1477#[cfg(test)]
1478mod tests {
1479 use super::*;
1480
1481 #[test]
1482 fn test_makefile_item_replace_variable_with_variable() {
1483 let makefile: Makefile = "VAR1 = old\nrule:\n\tcommand\n".parse().unwrap();
1484 let temp: Makefile = "VAR2 = new\n".parse().unwrap();
1485 let new_var = temp.variable_definitions().next().unwrap();
1486 let mut first_item = makefile.items().next().unwrap();
1487 first_item.replace(MakefileItem::Variable(new_var)).unwrap();
1488
1489 let result = makefile.to_string();
1490 assert_eq!(result, "VAR2 = new\nrule:\n\tcommand\n");
1491 }
1492
1493 #[test]
1494 fn test_makefile_item_replace_variable_with_rule() {
1495 let makefile: Makefile = "VAR1 = value\nrule1:\n\tcommand1\n".parse().unwrap();
1496 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1497 let new_rule = temp.rules().next().unwrap();
1498 let mut first_item = makefile.items().next().unwrap();
1499 first_item.replace(MakefileItem::Rule(new_rule)).unwrap();
1500
1501 let result = makefile.to_string();
1502 assert_eq!(result, "new_rule:\n\tnew_command\nrule1:\n\tcommand1\n");
1503 }
1504
1505 #[test]
1506 fn test_makefile_item_replace_preserves_position() {
1507 let makefile: Makefile = "VAR1 = first\nVAR2 = second\nVAR3 = third\n"
1508 .parse()
1509 .unwrap();
1510 let temp: Makefile = "NEW = replacement\n".parse().unwrap();
1511 let new_var = temp.variable_definitions().next().unwrap();
1512
1513 let mut second_item = makefile.items().nth(1).unwrap();
1515 second_item
1516 .replace(MakefileItem::Variable(new_var))
1517 .unwrap();
1518
1519 let items: Vec<_> = makefile.variable_definitions().collect();
1520 assert_eq!(items.len(), 3);
1521 assert_eq!(items[0].name(), Some("VAR1".to_string()));
1522 assert_eq!(items[1].name(), Some("NEW".to_string()));
1523 assert_eq!(items[2].name(), Some("VAR3".to_string()));
1524 }
1525
1526 #[test]
1527 fn test_makefile_item_add_comment() {
1528 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1529 let mut item = makefile.items().next().unwrap();
1530 item.add_comment("This is a variable").unwrap();
1531
1532 let result = makefile.to_string();
1533 assert_eq!(result, "# This is a variable\nVAR = value\n");
1534 }
1535
1536 #[test]
1537 fn test_makefile_item_add_multiple_comments() {
1538 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1539 let mut item = makefile.items().next().unwrap();
1540 item.add_comment("Comment 1").unwrap();
1541 let mut item = makefile.items().next().unwrap();
1543 item.add_comment("Comment 2").unwrap();
1544
1545 let result = makefile.to_string();
1546 assert_eq!(result, "# Comment 1\n# Comment 2\nVAR = value\n");
1549 }
1550
1551 #[test]
1552 fn test_makefile_item_preceding_comments() {
1553 let makefile: Makefile = "# Comment 1\n# Comment 2\nVAR = value\n".parse().unwrap();
1554 let item = makefile.items().next().unwrap();
1555 let comments: Vec<_> = item.preceding_comments().collect();
1556 assert_eq!(comments.len(), 2);
1557 assert_eq!(comments[0], "Comment 1");
1558 assert_eq!(comments[1], "Comment 2");
1559 }
1560
1561 #[test]
1562 fn test_makefile_item_preceding_comments_no_comments() {
1563 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1564 let item = makefile.items().next().unwrap();
1565 let comments: Vec<_> = item.preceding_comments().collect();
1566 assert_eq!(comments.len(), 0);
1567 }
1568
1569 #[test]
1570 fn test_makefile_item_preceding_comments_ignores_shebang() {
1571 let makefile: Makefile = "#!/usr/bin/make\n# Real comment\nVAR = value\n"
1572 .parse()
1573 .unwrap();
1574 let item = makefile.items().next().unwrap();
1575 let comments: Vec<_> = item.preceding_comments().collect();
1576 assert_eq!(comments.len(), 1);
1577 assert_eq!(comments[0], "Real comment");
1578 }
1579
1580 #[test]
1581 fn test_makefile_item_remove_comments() {
1582 let makefile: Makefile = "# Comment 1\n# Comment 2\nVAR = value\n".parse().unwrap();
1583 let mut item = makefile.items().next().unwrap();
1585 let count = item.remove_comments().unwrap();
1586
1587 assert_eq!(count, 2);
1588 let result = makefile.to_string();
1589 assert_eq!(result, "VAR = value\n");
1590 }
1591
1592 #[test]
1593 fn test_makefile_item_remove_comments_no_comments() {
1594 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1595 let mut item = makefile.items().next().unwrap();
1596 let count = item.remove_comments().unwrap();
1597
1598 assert_eq!(count, 0);
1599 assert_eq!(makefile.to_string(), "VAR = value\n");
1600 }
1601
1602 #[test]
1603 fn test_makefile_item_modify_comment() {
1604 let makefile: Makefile = "# Old comment\nVAR = value\n".parse().unwrap();
1605 let mut item = makefile.items().next().unwrap();
1606 let modified = item.modify_comment("New comment").unwrap();
1607
1608 assert!(modified);
1609 let result = makefile.to_string();
1610 assert_eq!(result, "# New comment\nVAR = value\n");
1611 }
1612
1613 #[test]
1614 fn test_makefile_item_modify_comment_no_comment() {
1615 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1616 let mut item = makefile.items().next().unwrap();
1617 let modified = item.modify_comment("New comment").unwrap();
1618
1619 assert!(!modified);
1620 assert_eq!(makefile.to_string(), "VAR = value\n");
1621 }
1622
1623 #[test]
1624 fn test_makefile_item_modify_comment_modifies_closest() {
1625 let makefile: Makefile = "# Comment 1\n# Comment 2\n# Comment 3\nVAR = value\n"
1626 .parse()
1627 .unwrap();
1628 let mut item = makefile.items().next().unwrap();
1629 let modified = item.modify_comment("Modified").unwrap();
1630
1631 assert!(modified);
1632 let result = makefile.to_string();
1633 assert_eq!(
1634 result,
1635 "# Comment 1\n# Comment 2\n# Modified\nVAR = value\n"
1636 );
1637 }
1638
1639 #[test]
1640 fn test_makefile_item_comment_workflow() {
1641 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1643 let mut item = makefile.items().next().unwrap();
1644
1645 item.add_comment("Initial comment").unwrap();
1647 assert_eq!(makefile.to_string(), "# Initial comment\nVAR = value\n");
1648
1649 let mut item = makefile.items().next().unwrap();
1651 item.modify_comment("Updated comment").unwrap();
1653 assert_eq!(makefile.to_string(), "# Updated comment\nVAR = value\n");
1654
1655 let mut item = makefile.items().next().unwrap();
1657 let count = item.remove_comments().unwrap();
1659 assert_eq!(count, 1);
1660 assert_eq!(makefile.to_string(), "VAR = value\n");
1661 }
1662
1663 #[test]
1664 fn test_makefile_item_replace_with_comments() {
1665 let makefile: Makefile = "# Comment for VAR1\nVAR1 = old\nrule:\n\tcommand\n"
1666 .parse()
1667 .unwrap();
1668 let temp: Makefile = "VAR2 = new\n".parse().unwrap();
1669 let new_var = temp.variable_definitions().next().unwrap();
1670 let mut first_item = makefile.items().next().unwrap();
1671
1672 let comments: Vec<_> = first_item.preceding_comments().collect();
1674 assert_eq!(comments.len(), 1);
1675 assert_eq!(comments[0], "Comment for VAR1");
1676
1677 first_item.replace(MakefileItem::Variable(new_var)).unwrap();
1679
1680 let result = makefile.to_string();
1681 assert_eq!(result, "# Comment for VAR1\nVAR2 = new\nrule:\n\tcommand\n");
1683 }
1684
1685 #[test]
1686 fn test_makefile_item_insert_before_variable() {
1687 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1688 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1689 let new_var = temp.variable_definitions().next().unwrap();
1690 let mut second_item = makefile.items().nth(1).unwrap();
1691 second_item
1692 .insert_before(MakefileItem::Variable(new_var))
1693 .unwrap();
1694
1695 let result = makefile.to_string();
1696 assert_eq!(result, "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n");
1697 }
1698
1699 #[test]
1700 fn test_makefile_item_insert_after_variable() {
1701 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1702 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1703 let new_var = temp.variable_definitions().next().unwrap();
1704 let mut first_item = makefile.items().next().unwrap();
1705 first_item
1706 .insert_after(MakefileItem::Variable(new_var))
1707 .unwrap();
1708
1709 let result = makefile.to_string();
1710 assert_eq!(result, "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n");
1711 }
1712
1713 #[test]
1714 fn test_makefile_item_insert_before_first_item() {
1715 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1716 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1717 let new_var = temp.variable_definitions().next().unwrap();
1718 let mut first_item = makefile.items().next().unwrap();
1719 first_item
1720 .insert_before(MakefileItem::Variable(new_var))
1721 .unwrap();
1722
1723 let result = makefile.to_string();
1724 assert_eq!(result, "VAR_NEW = inserted\nVAR1 = first\nVAR2 = second\n");
1725 }
1726
1727 #[test]
1728 fn test_makefile_item_insert_after_last_item() {
1729 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1730 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1731 let new_var = temp.variable_definitions().next().unwrap();
1732 let mut last_item = makefile.items().nth(1).unwrap();
1733 last_item
1734 .insert_after(MakefileItem::Variable(new_var))
1735 .unwrap();
1736
1737 let result = makefile.to_string();
1738 assert_eq!(result, "VAR1 = first\nVAR2 = second\nVAR_NEW = inserted\n");
1739 }
1740
1741 #[test]
1742 fn test_makefile_item_insert_before_include() {
1743 let makefile: Makefile = "VAR1 = value\nrule:\n\tcommand\n".parse().unwrap();
1744 let temp: Makefile = "include test.mk\n".parse().unwrap();
1745 let new_include = temp.includes().next().unwrap();
1746 let mut first_item = makefile.items().next().unwrap();
1747 first_item
1748 .insert_before(MakefileItem::Include(new_include))
1749 .unwrap();
1750
1751 let result = makefile.to_string();
1752 assert_eq!(result, "include test.mk\nVAR1 = value\nrule:\n\tcommand\n");
1753 }
1754
1755 #[test]
1756 fn test_makefile_item_insert_after_include() {
1757 let makefile: Makefile = "VAR1 = value\nrule:\n\tcommand\n".parse().unwrap();
1758 let temp: Makefile = "include test.mk\n".parse().unwrap();
1759 let new_include = temp.includes().next().unwrap();
1760 let mut first_item = makefile.items().next().unwrap();
1761 first_item
1762 .insert_after(MakefileItem::Include(new_include))
1763 .unwrap();
1764
1765 let result = makefile.to_string();
1766 assert_eq!(result, "VAR1 = value\ninclude test.mk\nrule:\n\tcommand\n");
1767 }
1768
1769 #[test]
1770 fn test_makefile_item_insert_before_rule() {
1771 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
1772 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1773 let new_rule = temp.rules().next().unwrap();
1774 let mut second_item = makefile.items().nth(1).unwrap();
1775 second_item
1776 .insert_before(MakefileItem::Rule(new_rule))
1777 .unwrap();
1778
1779 let result = makefile.to_string();
1780 assert_eq!(
1781 result,
1782 "rule1:\n\tcommand1\nnew_rule:\n\tnew_command\nrule2:\n\tcommand2\n"
1783 );
1784 }
1785
1786 #[test]
1787 fn test_makefile_item_insert_after_rule() {
1788 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
1789 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1790 let new_rule = temp.rules().next().unwrap();
1791 let mut first_item = makefile.items().next().unwrap();
1792 first_item
1793 .insert_after(MakefileItem::Rule(new_rule))
1794 .unwrap();
1795
1796 let result = makefile.to_string();
1797 assert_eq!(
1798 result,
1799 "rule1:\n\tcommand1\nnew_rule:\n\tnew_command\nrule2:\n\tcommand2\n"
1800 );
1801 }
1802
1803 #[test]
1804 fn test_makefile_item_insert_before_with_comments() {
1805 let makefile: Makefile = "# Comment 1\nVAR1 = first\n# Comment 2\nVAR2 = second\n"
1806 .parse()
1807 .unwrap();
1808 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1809 let new_var = temp.variable_definitions().next().unwrap();
1810 let mut second_item = makefile.items().nth(1).unwrap();
1811 second_item
1812 .insert_before(MakefileItem::Variable(new_var))
1813 .unwrap();
1814
1815 let result = makefile.to_string();
1816 assert_eq!(
1819 result,
1820 "# Comment 1\nVAR1 = first\n# Comment 2\nVAR_NEW = inserted\nVAR2 = second\n"
1821 );
1822 }
1823
1824 #[test]
1825 fn test_makefile_item_insert_after_with_comments() {
1826 let makefile: Makefile = "# Comment 1\nVAR1 = first\n# Comment 2\nVAR2 = second\n"
1827 .parse()
1828 .unwrap();
1829 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1830 let new_var = temp.variable_definitions().next().unwrap();
1831 let mut first_item = makefile.items().next().unwrap();
1832 first_item
1833 .insert_after(MakefileItem::Variable(new_var))
1834 .unwrap();
1835
1836 let result = makefile.to_string();
1837 assert_eq!(
1839 result,
1840 "# Comment 1\nVAR1 = first\nVAR_NEW = inserted\n# Comment 2\nVAR2 = second\n"
1841 );
1842 }
1843
1844 #[test]
1845 fn test_makefile_item_insert_before_preserves_formatting() {
1846 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1847 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1848 let new_var = temp.variable_definitions().next().unwrap();
1849 let mut second_item = makefile.items().nth(1).unwrap();
1850 second_item
1851 .insert_before(MakefileItem::Variable(new_var))
1852 .unwrap();
1853
1854 let result = makefile.to_string();
1855 assert_eq!(
1857 result,
1858 "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n"
1859 );
1860 }
1861
1862 #[test]
1863 fn test_makefile_item_insert_multiple_items() {
1864 let makefile: Makefile = "VAR1 = first\nVAR2 = last\n".parse().unwrap();
1865 let temp: Makefile = "VAR_A = a\nVAR_B = b\n".parse().unwrap();
1866 let mut new_vars: Vec<_> = temp.variable_definitions().collect();
1867
1868 let mut target_item = makefile.items().nth(1).unwrap();
1869 target_item
1870 .insert_before(MakefileItem::Variable(new_vars.pop().unwrap()))
1871 .unwrap();
1872
1873 let mut target_item = makefile.items().nth(1).unwrap();
1875 target_item
1876 .insert_before(MakefileItem::Variable(new_vars.pop().unwrap()))
1877 .unwrap();
1878
1879 let result = makefile.to_string();
1880 assert_eq!(result, "VAR1 = first\nVAR_A = a\nVAR_B = b\nVAR2 = last\n");
1881 }
1882
1883 #[test]
1884 fn test_rules_after_nested_conditionals() {
1885 let makefile_content = r#"#!/usr/bin/make -f
1888
1889ifeq ($(filter nodoc, $(DEB_BUILD_OPTIONS)),)
1890ifneq ($(shell which valadoc),)
1891 BUILD_DOC:=-Ddocs=true
1892endif
1893endif
1894
1895%:
1896 dh $@
1897
1898override_dh_auto_configure:
1899 echo test
1900"#;
1901 let makefile: Makefile = makefile_content.parse().unwrap();
1902
1903 let rules: Vec<_> = makefile.rules().collect();
1904 let targets: Vec<Vec<_>> = rules.iter().map(|r| r.targets().collect()).collect();
1905 assert_eq!(
1906 rules.len(),
1907 2,
1908 "Expected 2 rules (% and override_dh_auto_configure), got {} rules with targets: {:?}",
1909 rules.len(),
1910 targets
1911 );
1912
1913 assert_eq!(targets[0], vec!["%"]);
1914 assert_eq!(targets[1], vec!["override_dh_auto_configure"]);
1915
1916 assert!(makefile.find_rule_by_target_pattern("build-arch").is_some());
1918 assert!(makefile
1919 .find_rule_by_target_pattern("build-indep")
1920 .is_some());
1921 }
1922}