1use crate::lossless::{
2 parse, Conditional, Error, ErrorInfo, Include, Makefile, ParseError, Rule, SyntaxNode,
3 VariableDefinition, VariableReference,
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 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
455struct CommentBlockIter {
457 elements: Vec<rowan::NodeOrToken<SyntaxNode, rowan::SyntaxToken<crate::lossless::Lang>>>,
458 pos: usize,
459}
460
461impl CommentBlockIter {
462 fn new(root: &SyntaxNode) -> Self {
463 Self {
464 elements: root.children_with_tokens().collect(),
465 pos: 0,
466 }
467 }
468}
469
470impl Iterator for CommentBlockIter {
471 type Item = rowan::TextRange;
472
473 fn next(&mut self) -> Option<Self::Item> {
474 while self.pos < self.elements.len() {
476 if let Some(token) = self.elements[self.pos].as_token() {
477 if token.kind() == COMMENT {
478 break;
479 }
480 }
481 self.pos += 1;
482 }
483
484 if self.pos >= self.elements.len() {
485 return None;
486 }
487
488 let block_start = self.elements[self.pos]
489 .as_token()
490 .unwrap()
491 .text_range()
492 .start();
493 let mut block_end = self.elements[self.pos]
494 .as_token()
495 .unwrap()
496 .text_range()
497 .end();
498 let mut comment_count = 1;
499 self.pos += 1;
500
501 while self.pos < self.elements.len() {
503 match &self.elements[self.pos] {
504 rowan::NodeOrToken::Token(token)
505 if token.kind() == NEWLINE || token.kind() == WHITESPACE =>
506 {
507 self.pos += 1;
508 }
509 rowan::NodeOrToken::Node(node) if node.kind() == BLANK_LINE => {
510 self.pos += 1;
511 }
512 rowan::NodeOrToken::Token(token) if token.kind() == COMMENT => {
513 block_end = token.text_range().end();
514 comment_count += 1;
515 self.pos += 1;
516 }
517 _ => break,
518 }
519 }
520
521 if comment_count >= 2 {
523 Some(rowan::TextRange::new(block_start, block_end))
524 } else {
525 self.next()
527 }
528 }
529}
530
531impl Makefile {
532 pub fn new() -> Makefile {
534 let mut builder = GreenNodeBuilder::new();
535
536 builder.start_node(ROOT.into());
537 builder.finish_node();
538
539 let syntax = SyntaxNode::new_root_mut(builder.finish());
540 Makefile::cast(syntax).unwrap()
541 }
542
543 pub fn parse(text: &str) -> crate::Parse<Makefile> {
545 crate::Parse::<Makefile>::parse_makefile(text)
546 }
547
548 pub fn code(&self) -> String {
550 self.syntax().text().to_string()
551 }
552
553 pub fn is_root(&self) -> bool {
555 self.syntax().kind() == ROOT
556 }
557
558 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
560 let mut buf = String::new();
561 r.read_to_string(&mut buf)?;
562 buf.parse()
563 }
564
565 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
567 let mut buf = String::new();
568 r.read_to_string(&mut buf)?;
569
570 let parsed = parse(&buf, None);
571 Ok(parsed.root())
572 }
573
574 pub fn from_str_relaxed(s: &str) -> (Self, Vec<ErrorInfo>) {
580 let parsed = parse(s, None);
581 (parsed.root(), parsed.errors)
582 }
583
584 pub fn from_file_relaxed(
588 path: impl AsRef<std::path::Path>,
589 ) -> Result<(Self, Vec<ErrorInfo>), std::io::Error> {
590 let text = std::fs::read_to_string(path)?;
591 Ok(Self::from_str_relaxed(&text))
592 }
593
594 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
603 RecursiveItemsIter::new(self.items())
604 }
605
606 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
608 self.rules()
609 .filter(move |rule| rule.targets().any(|t| t == target))
610 }
611
612 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> + '_ {
614 RecursiveItemsIter::new(self.items())
615 }
616
617 pub fn conditionals(&self) -> impl Iterator<Item = Conditional> + '_ {
619 self.items().filter_map(|item| match item {
620 MakefileItem::Conditional(c) => Some(c),
621 _ => None,
622 })
623 }
624
625 pub fn items(&self) -> impl Iterator<Item = MakefileItem> + '_ {
641 self.syntax().children().filter_map(MakefileItem::cast)
642 }
643
644 pub fn find_variable<'a>(
659 &'a self,
660 name: &'a str,
661 ) -> impl Iterator<Item = VariableDefinition> + 'a {
662 self.variable_definitions()
663 .filter(move |var| var.name().as_deref() == Some(name))
664 }
665
666 pub fn variable_references(&self) -> impl Iterator<Item = VariableReference> + '_ {
684 self.syntax()
685 .descendants()
686 .filter_map(VariableReference::cast)
687 }
688
689 pub fn items_in_range(
703 &self,
704 range: rowan::TextRange,
705 ) -> impl Iterator<Item = MakefileItem> + '_ {
706 self.items()
707 .skip_while(move |item| item.syntax().text_range().end() <= range.start())
708 .take_while(move |item| item.syntax().text_range().start() < range.end())
709 }
710
711 pub fn variable_references_in_range(
716 &self,
717 range: rowan::TextRange,
718 ) -> impl Iterator<Item = VariableReference> + '_ {
719 self.items_in_range(range).flat_map(|item| {
720 item.syntax()
721 .descendants()
722 .filter_map(VariableReference::cast)
723 .collect::<Vec<_>>()
724 })
725 }
726
727 pub fn rules_in_range(&self, range: rowan::TextRange) -> impl Iterator<Item = Rule> + '_ {
729 self.items_in_range(range).filter_map(|item| match item {
730 MakefileItem::Rule(r) => Some(r),
731 _ => None,
732 })
733 }
734
735 pub fn variable_definitions_in_range(
737 &self,
738 range: rowan::TextRange,
739 ) -> impl Iterator<Item = VariableDefinition> + '_ {
740 self.items_in_range(range).filter_map(|item| match item {
741 MakefileItem::Variable(v) => Some(v),
742 _ => None,
743 })
744 }
745
746 pub fn comment_blocks(&self) -> impl Iterator<Item = rowan::TextRange> + '_ {
759 CommentBlockIter::new(self.syntax())
760 }
761
762 pub fn add_rule(&mut self, target: &str) -> Rule {
772 let mut builder = GreenNodeBuilder::new();
773 builder.start_node(RULE.into());
774 builder.token(IDENTIFIER.into(), target);
775 builder.token(OPERATOR.into(), ":");
776 builder.token(NEWLINE.into(), "\n");
777 builder.finish_node();
778
779 let syntax = SyntaxNode::new_root_mut(builder.finish());
780 let pos = self.syntax().children_with_tokens().count();
781
782 let needs_blank_line = self.syntax().children().any(|c| c.kind() == RULE);
785
786 if needs_blank_line {
787 let mut bl_builder = GreenNodeBuilder::new();
789 bl_builder.start_node(BLANK_LINE.into());
790 bl_builder.token(NEWLINE.into(), "\n");
791 bl_builder.finish_node();
792 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
793
794 self.syntax()
795 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
796 } else {
797 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
798 }
799
800 Rule::cast(self.syntax().children().last().unwrap()).unwrap()
803 }
804
805 pub fn add_conditional(
821 &mut self,
822 conditional_type: &str,
823 condition: &str,
824 if_body: &str,
825 else_body: Option<&str>,
826 ) -> Result<Conditional, Error> {
827 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
829 return Err(Error::Parse(ParseError {
830 errors: vec![ErrorInfo {
831 message: format!(
832 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
833 conditional_type
834 ),
835 line: 1,
836 context: "add_conditional".to_string(),
837 }],
838 }));
839 }
840
841 let mut builder = GreenNodeBuilder::new();
842 builder.start_node(CONDITIONAL.into());
843
844 builder.start_node(CONDITIONAL_IF.into());
846 builder.token(IDENTIFIER.into(), conditional_type);
847 builder.token(WHITESPACE.into(), " ");
848
849 builder.start_node(EXPR.into());
851 builder.token(IDENTIFIER.into(), condition);
852 builder.finish_node();
853
854 builder.token(NEWLINE.into(), "\n");
855 builder.finish_node();
856
857 if !if_body.is_empty() {
859 for line in if_body.lines() {
860 if !line.is_empty() {
861 builder.token(IDENTIFIER.into(), line);
862 }
863 builder.token(NEWLINE.into(), "\n");
864 }
865 if !if_body.ends_with('\n') && !if_body.is_empty() {
867 builder.token(NEWLINE.into(), "\n");
868 }
869 }
870
871 if let Some(else_content) = else_body {
873 builder.start_node(CONDITIONAL_ELSE.into());
874 builder.token(IDENTIFIER.into(), "else");
875 builder.token(NEWLINE.into(), "\n");
876 builder.finish_node();
877
878 if !else_content.is_empty() {
880 for line in else_content.lines() {
881 if !line.is_empty() {
882 builder.token(IDENTIFIER.into(), line);
883 }
884 builder.token(NEWLINE.into(), "\n");
885 }
886 if !else_content.ends_with('\n') && !else_content.is_empty() {
888 builder.token(NEWLINE.into(), "\n");
889 }
890 }
891 }
892
893 builder.start_node(CONDITIONAL_ENDIF.into());
895 builder.token(IDENTIFIER.into(), "endif");
896 builder.token(NEWLINE.into(), "\n");
897 builder.finish_node();
898
899 builder.finish_node();
900
901 let syntax = SyntaxNode::new_root_mut(builder.finish());
902 let pos = self.syntax().children_with_tokens().count();
903
904 let needs_blank_line = self
906 .syntax()
907 .children()
908 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
909
910 if needs_blank_line {
911 let mut bl_builder = GreenNodeBuilder::new();
913 bl_builder.start_node(BLANK_LINE.into());
914 bl_builder.token(NEWLINE.into(), "\n");
915 bl_builder.finish_node();
916 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
917
918 self.syntax()
919 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
920 } else {
921 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
922 }
923
924 Ok(Conditional::cast(self.syntax().children().last().unwrap()).unwrap())
926 }
927
928 pub fn add_conditional_with_items<I1, I2>(
958 &mut self,
959 conditional_type: &str,
960 condition: &str,
961 if_items: I1,
962 else_items: Option<I2>,
963 ) -> Result<Conditional, Error>
964 where
965 I1: IntoIterator<Item = MakefileItem>,
966 I2: IntoIterator<Item = MakefileItem>,
967 {
968 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
970 return Err(Error::Parse(ParseError {
971 errors: vec![ErrorInfo {
972 message: format!(
973 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
974 conditional_type
975 ),
976 line: 1,
977 context: "add_conditional_with_items".to_string(),
978 }],
979 }));
980 }
981
982 let mut builder = GreenNodeBuilder::new();
983 builder.start_node(CONDITIONAL.into());
984
985 builder.start_node(CONDITIONAL_IF.into());
987 builder.token(IDENTIFIER.into(), conditional_type);
988 builder.token(WHITESPACE.into(), " ");
989
990 builder.start_node(EXPR.into());
992 builder.token(IDENTIFIER.into(), condition);
993 builder.finish_node();
994
995 builder.token(NEWLINE.into(), "\n");
996 builder.finish_node();
997
998 for item in if_items {
1000 let item_text = item.syntax().to_string();
1002 builder.token(IDENTIFIER.into(), item_text.trim());
1004 builder.token(NEWLINE.into(), "\n");
1005 }
1006
1007 if let Some(else_iter) = else_items {
1009 builder.start_node(CONDITIONAL_ELSE.into());
1010 builder.token(IDENTIFIER.into(), "else");
1011 builder.token(NEWLINE.into(), "\n");
1012 builder.finish_node();
1013
1014 for item in else_iter {
1016 let item_text = item.syntax().to_string();
1017 builder.token(IDENTIFIER.into(), item_text.trim());
1018 builder.token(NEWLINE.into(), "\n");
1019 }
1020 }
1021
1022 builder.start_node(CONDITIONAL_ENDIF.into());
1024 builder.token(IDENTIFIER.into(), "endif");
1025 builder.token(NEWLINE.into(), "\n");
1026 builder.finish_node();
1027
1028 builder.finish_node();
1029
1030 let syntax = SyntaxNode::new_root_mut(builder.finish());
1031 let pos = self.syntax().children_with_tokens().count();
1032
1033 let needs_blank_line = self
1035 .syntax()
1036 .children()
1037 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
1038
1039 if needs_blank_line {
1040 let mut bl_builder = GreenNodeBuilder::new();
1042 bl_builder.start_node(BLANK_LINE.into());
1043 bl_builder.token(NEWLINE.into(), "\n");
1044 bl_builder.finish_node();
1045 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1046
1047 self.syntax()
1048 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
1049 } else {
1050 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
1051 }
1052
1053 Ok(Conditional::cast(self.syntax().children().last().unwrap()).unwrap())
1055 }
1056
1057 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1059 let mut buf = String::new();
1060 r.read_to_string(&mut buf)?;
1061
1062 let parsed = parse(&buf, None);
1063 if !parsed.errors.is_empty() {
1064 Err(Error::Parse(ParseError {
1065 errors: parsed.errors,
1066 }))
1067 } else {
1068 Ok(parsed.root())
1069 }
1070 }
1071
1072 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1083 let rules: Vec<_> = self
1084 .syntax()
1085 .children()
1086 .filter(|n| n.kind() == RULE)
1087 .collect();
1088
1089 if rules.is_empty() {
1090 return Err(Error::Parse(ParseError {
1091 errors: vec![ErrorInfo {
1092 message: "Cannot replace rule in empty makefile".to_string(),
1093 line: 1,
1094 context: "replace_rule".to_string(),
1095 }],
1096 }));
1097 }
1098
1099 if index >= rules.len() {
1100 return Err(Error::Parse(ParseError {
1101 errors: vec![ErrorInfo {
1102 message: format!(
1103 "Rule index {} out of bounds (max {})",
1104 index,
1105 rules.len() - 1
1106 ),
1107 line: 1,
1108 context: "replace_rule".to_string(),
1109 }],
1110 }));
1111 }
1112
1113 let target_node = &rules[index];
1114 let target_index = target_node.index();
1115
1116 self.syntax().splice_children(
1118 target_index..target_index + 1,
1119 vec![new_rule.syntax().clone().into()],
1120 );
1121 Ok(())
1122 }
1123
1124 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
1135 let rules: Vec<_> = self
1136 .syntax()
1137 .children()
1138 .filter(|n| n.kind() == RULE)
1139 .collect();
1140
1141 if rules.is_empty() {
1142 return Err(Error::Parse(ParseError {
1143 errors: vec![ErrorInfo {
1144 message: "Cannot remove rule from empty makefile".to_string(),
1145 line: 1,
1146 context: "remove_rule".to_string(),
1147 }],
1148 }));
1149 }
1150
1151 if index >= rules.len() {
1152 return Err(Error::Parse(ParseError {
1153 errors: vec![ErrorInfo {
1154 message: format!(
1155 "Rule index {} out of bounds (max {})",
1156 index,
1157 rules.len() - 1
1158 ),
1159 line: 1,
1160 context: "remove_rule".to_string(),
1161 }],
1162 }));
1163 }
1164
1165 let target_node = rules[index].clone();
1166 let target_index = target_node.index();
1167
1168 self.syntax()
1170 .splice_children(target_index..target_index + 1, vec![]);
1171 Ok(Rule::cast(target_node).unwrap())
1172 }
1173
1174 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1186 let rules: Vec<_> = self
1187 .syntax()
1188 .children()
1189 .filter(|n| n.kind() == RULE)
1190 .collect();
1191
1192 if index > rules.len() {
1193 return Err(Error::Parse(ParseError {
1194 errors: vec![ErrorInfo {
1195 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1196 line: 1,
1197 context: "insert_rule".to_string(),
1198 }],
1199 }));
1200 }
1201
1202 let target_index = if index == rules.len() {
1203 self.syntax().children_with_tokens().count()
1205 } else {
1206 rules[index].index()
1208 };
1209
1210 let mut nodes_to_insert = Vec::new();
1212
1213 if index == 0 && !rules.is_empty() {
1215 nodes_to_insert.push(new_rule.syntax().clone().into());
1219
1220 let mut bl_builder = GreenNodeBuilder::new();
1222 bl_builder.start_node(BLANK_LINE.into());
1223 bl_builder.token(NEWLINE.into(), "\n");
1224 bl_builder.finish_node();
1225 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1226 nodes_to_insert.push(blank_line.into());
1227 } else if index < rules.len() {
1228 let has_blank_before = if target_index > 0 {
1241 self.syntax()
1242 .children_with_tokens()
1243 .nth(target_index - 1)
1244 .and_then(|n| n.as_node().map(|node| node.kind() == BLANK_LINE))
1245 .unwrap_or(false)
1246 } else {
1247 false
1248 };
1249
1250 if !has_blank_before && index > 0 {
1252 let mut bl_builder = GreenNodeBuilder::new();
1253 bl_builder.start_node(BLANK_LINE.into());
1254 bl_builder.token(NEWLINE.into(), "\n");
1255 bl_builder.finish_node();
1256 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1257 nodes_to_insert.push(blank_line.into());
1258 }
1259
1260 nodes_to_insert.push(new_rule.syntax().clone().into());
1262
1263 let mut bl_builder = GreenNodeBuilder::new();
1265 bl_builder.start_node(BLANK_LINE.into());
1266 bl_builder.token(NEWLINE.into(), "\n");
1267 bl_builder.finish_node();
1268 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1269 nodes_to_insert.push(blank_line.into());
1270 } else {
1271 let mut bl_builder = GreenNodeBuilder::new();
1274 bl_builder.start_node(BLANK_LINE.into());
1275 bl_builder.token(NEWLINE.into(), "\n");
1276 bl_builder.finish_node();
1277 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1278 nodes_to_insert.push(blank_line.into());
1279
1280 nodes_to_insert.push(new_rule.syntax().clone().into());
1282 }
1283
1284 self.syntax()
1286 .splice_children(target_index..target_index, nodes_to_insert);
1287 Ok(())
1288 }
1289
1290 pub fn includes(&self) -> impl Iterator<Item = Include> {
1300 self.syntax().children().filter_map(Include::cast)
1301 }
1302
1303 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1313 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1316 let mut includes = Vec::new();
1317
1318 if let Some(include) = Include::cast(node.clone()) {
1320 includes.push(include);
1321 }
1322
1323 for child in node.children() {
1325 includes.extend(collect_includes(&child));
1326 }
1327
1328 includes
1329 }
1330
1331 let includes = collect_includes(self.syntax());
1333
1334 includes.into_iter().map(|include| {
1336 include
1337 .syntax()
1338 .children()
1339 .find(|node| node.kind() == EXPR)
1340 .map(|expr| expr.text().to_string().trim().to_string())
1341 .unwrap_or_default()
1342 })
1343 }
1344
1345 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1356 self.rules()
1357 .find(|rule| rule.targets().any(|t| t == target))
1358 }
1359
1360 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1370 self.rules_by_target(target)
1371 }
1372
1373 pub fn find_rule_by_target_pattern(&self, target: &str) -> Option<Rule> {
1386 self.rules()
1387 .find(|rule| rule.targets().any(|t| matches_pattern(&t, target)))
1388 }
1389
1390 pub fn find_rules_by_target_pattern<'a>(
1403 &'a self,
1404 target: &'a str,
1405 ) -> impl Iterator<Item = Rule> + 'a {
1406 self.rules()
1407 .filter(move |rule| rule.targets().any(|t| matches_pattern(&t, target)))
1408 }
1409
1410 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1420 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1422 if !phony_rule.prerequisites().any(|p| p == target) {
1424 phony_rule.add_prerequisite(target)?;
1425 }
1426 } else {
1427 let mut phony_rule = self.add_rule(".PHONY");
1429 phony_rule.add_prerequisite(target)?;
1430 }
1431 Ok(())
1432 }
1433
1434 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1448 let mut phony_rule = None;
1450 for rule in self.rules_by_target(".PHONY") {
1451 if rule.prerequisites().any(|p| p == target) {
1452 phony_rule = Some(rule);
1453 break;
1454 }
1455 }
1456
1457 let mut phony_rule = match phony_rule {
1458 Some(rule) => rule,
1459 None => return Ok(false),
1460 };
1461
1462 let prereq_count = phony_rule.prerequisites().count();
1464
1465 phony_rule.remove_prerequisite(target)?;
1467
1468 if prereq_count == 1 {
1470 phony_rule.remove()?;
1472 }
1473
1474 Ok(true)
1475 }
1476
1477 pub fn is_phony(&self, target: &str) -> bool {
1488 self.rules_by_target(".PHONY")
1490 .any(|rule| rule.prerequisites().any(|p| p == target))
1491 }
1492
1493 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1503 self.rules_by_target(".PHONY")
1505 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1506 }
1507
1508 pub fn add_include(&mut self, path: &str) -> Include {
1521 let mut builder = GreenNodeBuilder::new();
1522 builder.start_node(INCLUDE.into());
1523 builder.token(IDENTIFIER.into(), "include");
1524 builder.token(WHITESPACE.into(), " ");
1525
1526 builder.start_node(EXPR.into());
1528 builder.token(IDENTIFIER.into(), path);
1529 builder.finish_node();
1530
1531 builder.token(NEWLINE.into(), "\n");
1532 builder.finish_node();
1533
1534 let syntax = SyntaxNode::new_root_mut(builder.finish());
1535
1536 self.syntax().splice_children(0..0, vec![syntax.into()]);
1538
1539 Include::cast(self.syntax().children().next().unwrap()).unwrap()
1541 }
1542
1543 pub fn insert_include(&mut self, index: usize, path: &str) -> Result<Include, Error> {
1560 let items: Vec<_> = self.syntax().children().collect();
1561
1562 if index > items.len() {
1563 return Err(Error::Parse(ParseError {
1564 errors: vec![ErrorInfo {
1565 message: format!("Index {} out of bounds (max {})", index, items.len()),
1566 line: 1,
1567 context: "insert_include".to_string(),
1568 }],
1569 }));
1570 }
1571
1572 let mut builder = GreenNodeBuilder::new();
1573 builder.start_node(INCLUDE.into());
1574 builder.token(IDENTIFIER.into(), "include");
1575 builder.token(WHITESPACE.into(), " ");
1576
1577 builder.start_node(EXPR.into());
1579 builder.token(IDENTIFIER.into(), path);
1580 builder.finish_node();
1581
1582 builder.token(NEWLINE.into(), "\n");
1583 builder.finish_node();
1584
1585 let syntax = SyntaxNode::new_root_mut(builder.finish());
1586
1587 let target_index = if index == items.len() {
1588 self.syntax().children_with_tokens().count()
1590 } else {
1591 items[index].index()
1593 };
1594
1595 self.syntax()
1597 .splice_children(target_index..target_index, vec![syntax.into()]);
1598
1599 Ok(Include::cast(self.syntax().children().nth(index).unwrap()).unwrap())
1602 }
1603
1604 pub fn insert_include_after(
1622 &mut self,
1623 after: &MakefileItem,
1624 path: &str,
1625 ) -> Result<Include, Error> {
1626 let mut builder = GreenNodeBuilder::new();
1627 builder.start_node(INCLUDE.into());
1628 builder.token(IDENTIFIER.into(), "include");
1629 builder.token(WHITESPACE.into(), " ");
1630
1631 builder.start_node(EXPR.into());
1633 builder.token(IDENTIFIER.into(), path);
1634 builder.finish_node();
1635
1636 builder.token(NEWLINE.into(), "\n");
1637 builder.finish_node();
1638
1639 let syntax = SyntaxNode::new_root_mut(builder.finish());
1640
1641 let after_syntax = after.syntax();
1643 let target_index = after_syntax.index() + 1;
1644
1645 self.syntax()
1647 .splice_children(target_index..target_index, vec![syntax.into()]);
1648
1649 let after_child_index = self
1652 .syntax()
1653 .children()
1654 .position(|child| child.text_range() == after_syntax.text_range())
1655 .ok_or_else(|| {
1656 Error::Parse(ParseError {
1657 errors: vec![ErrorInfo {
1658 message: "Could not find the reference item".to_string(),
1659 line: 1,
1660 context: "insert_include_after".to_string(),
1661 }],
1662 })
1663 })?;
1664
1665 Ok(Include::cast(self.syntax().children().nth(after_child_index + 1).unwrap()).unwrap())
1666 }
1667}
1668
1669#[cfg(test)]
1670mod tests {
1671 use super::*;
1672
1673 #[test]
1674 fn test_makefile_item_replace_variable_with_variable() {
1675 let makefile: Makefile = "VAR1 = old\nrule:\n\tcommand\n".parse().unwrap();
1676 let temp: Makefile = "VAR2 = new\n".parse().unwrap();
1677 let new_var = temp.variable_definitions().next().unwrap();
1678 let mut first_item = makefile.items().next().unwrap();
1679 first_item.replace(MakefileItem::Variable(new_var)).unwrap();
1680
1681 let result = makefile.to_string();
1682 assert_eq!(result, "VAR2 = new\nrule:\n\tcommand\n");
1683 }
1684
1685 #[test]
1686 fn test_makefile_item_replace_variable_with_rule() {
1687 let makefile: Makefile = "VAR1 = value\nrule1:\n\tcommand1\n".parse().unwrap();
1688 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1689 let new_rule = temp.rules().next().unwrap();
1690 let mut first_item = makefile.items().next().unwrap();
1691 first_item.replace(MakefileItem::Rule(new_rule)).unwrap();
1692
1693 let result = makefile.to_string();
1694 assert_eq!(result, "new_rule:\n\tnew_command\nrule1:\n\tcommand1\n");
1695 }
1696
1697 #[test]
1698 fn test_makefile_item_replace_preserves_position() {
1699 let makefile: Makefile = "VAR1 = first\nVAR2 = second\nVAR3 = third\n"
1700 .parse()
1701 .unwrap();
1702 let temp: Makefile = "NEW = replacement\n".parse().unwrap();
1703 let new_var = temp.variable_definitions().next().unwrap();
1704
1705 let mut second_item = makefile.items().nth(1).unwrap();
1707 second_item
1708 .replace(MakefileItem::Variable(new_var))
1709 .unwrap();
1710
1711 let items: Vec<_> = makefile.variable_definitions().collect();
1712 assert_eq!(items.len(), 3);
1713 assert_eq!(items[0].name(), Some("VAR1".to_string()));
1714 assert_eq!(items[1].name(), Some("NEW".to_string()));
1715 assert_eq!(items[2].name(), Some("VAR3".to_string()));
1716 }
1717
1718 #[test]
1719 fn test_makefile_item_add_comment() {
1720 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1721 let mut item = makefile.items().next().unwrap();
1722 item.add_comment("This is a variable").unwrap();
1723
1724 let result = makefile.to_string();
1725 assert_eq!(result, "# This is a variable\nVAR = value\n");
1726 }
1727
1728 #[test]
1729 fn test_makefile_item_add_multiple_comments() {
1730 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1731 let mut item = makefile.items().next().unwrap();
1732 item.add_comment("Comment 1").unwrap();
1733 let mut item = makefile.items().next().unwrap();
1735 item.add_comment("Comment 2").unwrap();
1736
1737 let result = makefile.to_string();
1738 assert_eq!(result, "# Comment 1\n# Comment 2\nVAR = value\n");
1741 }
1742
1743 #[test]
1744 fn test_makefile_item_preceding_comments() {
1745 let makefile: Makefile = "# Comment 1\n# Comment 2\nVAR = value\n".parse().unwrap();
1746 let item = makefile.items().next().unwrap();
1747 let comments: Vec<_> = item.preceding_comments().collect();
1748 assert_eq!(comments.len(), 2);
1749 assert_eq!(comments[0], "Comment 1");
1750 assert_eq!(comments[1], "Comment 2");
1751 }
1752
1753 #[test]
1754 fn test_makefile_item_preceding_comments_no_comments() {
1755 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1756 let item = makefile.items().next().unwrap();
1757 let comments: Vec<_> = item.preceding_comments().collect();
1758 assert_eq!(comments.len(), 0);
1759 }
1760
1761 #[test]
1762 fn test_makefile_item_preceding_comments_ignores_shebang() {
1763 let makefile: Makefile = "#!/usr/bin/make\n# Real comment\nVAR = value\n"
1764 .parse()
1765 .unwrap();
1766 let item = makefile.items().next().unwrap();
1767 let comments: Vec<_> = item.preceding_comments().collect();
1768 assert_eq!(comments.len(), 1);
1769 assert_eq!(comments[0], "Real comment");
1770 }
1771
1772 #[test]
1773 fn test_makefile_item_remove_comments() {
1774 let makefile: Makefile = "# Comment 1\n# Comment 2\nVAR = value\n".parse().unwrap();
1775 let mut item = makefile.items().next().unwrap();
1777 let count = item.remove_comments().unwrap();
1778
1779 assert_eq!(count, 2);
1780 let result = makefile.to_string();
1781 assert_eq!(result, "VAR = value\n");
1782 }
1783
1784 #[test]
1785 fn test_makefile_item_remove_comments_no_comments() {
1786 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1787 let mut item = makefile.items().next().unwrap();
1788 let count = item.remove_comments().unwrap();
1789
1790 assert_eq!(count, 0);
1791 assert_eq!(makefile.to_string(), "VAR = value\n");
1792 }
1793
1794 #[test]
1795 fn test_makefile_item_modify_comment() {
1796 let makefile: Makefile = "# Old comment\nVAR = value\n".parse().unwrap();
1797 let mut item = makefile.items().next().unwrap();
1798 let modified = item.modify_comment("New comment").unwrap();
1799
1800 assert!(modified);
1801 let result = makefile.to_string();
1802 assert_eq!(result, "# New comment\nVAR = value\n");
1803 }
1804
1805 #[test]
1806 fn test_makefile_item_modify_comment_no_comment() {
1807 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1808 let mut item = makefile.items().next().unwrap();
1809 let modified = item.modify_comment("New comment").unwrap();
1810
1811 assert!(!modified);
1812 assert_eq!(makefile.to_string(), "VAR = value\n");
1813 }
1814
1815 #[test]
1816 fn test_makefile_item_modify_comment_modifies_closest() {
1817 let makefile: Makefile = "# Comment 1\n# Comment 2\n# Comment 3\nVAR = value\n"
1818 .parse()
1819 .unwrap();
1820 let mut item = makefile.items().next().unwrap();
1821 let modified = item.modify_comment("Modified").unwrap();
1822
1823 assert!(modified);
1824 let result = makefile.to_string();
1825 assert_eq!(
1826 result,
1827 "# Comment 1\n# Comment 2\n# Modified\nVAR = value\n"
1828 );
1829 }
1830
1831 #[test]
1832 fn test_makefile_item_comment_workflow() {
1833 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1835 let mut item = makefile.items().next().unwrap();
1836
1837 item.add_comment("Initial comment").unwrap();
1839 assert_eq!(makefile.to_string(), "# Initial comment\nVAR = value\n");
1840
1841 let mut item = makefile.items().next().unwrap();
1843 item.modify_comment("Updated comment").unwrap();
1845 assert_eq!(makefile.to_string(), "# Updated comment\nVAR = value\n");
1846
1847 let mut item = makefile.items().next().unwrap();
1849 let count = item.remove_comments().unwrap();
1851 assert_eq!(count, 1);
1852 assert_eq!(makefile.to_string(), "VAR = value\n");
1853 }
1854
1855 #[test]
1856 fn test_makefile_item_replace_with_comments() {
1857 let makefile: Makefile = "# Comment for VAR1\nVAR1 = old\nrule:\n\tcommand\n"
1858 .parse()
1859 .unwrap();
1860 let temp: Makefile = "VAR2 = new\n".parse().unwrap();
1861 let new_var = temp.variable_definitions().next().unwrap();
1862 let mut first_item = makefile.items().next().unwrap();
1863
1864 let comments: Vec<_> = first_item.preceding_comments().collect();
1866 assert_eq!(comments.len(), 1);
1867 assert_eq!(comments[0], "Comment for VAR1");
1868
1869 first_item.replace(MakefileItem::Variable(new_var)).unwrap();
1871
1872 let result = makefile.to_string();
1873 assert_eq!(result, "# Comment for VAR1\nVAR2 = new\nrule:\n\tcommand\n");
1875 }
1876
1877 #[test]
1878 fn test_makefile_item_insert_before_variable() {
1879 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1880 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1881 let new_var = temp.variable_definitions().next().unwrap();
1882 let mut second_item = makefile.items().nth(1).unwrap();
1883 second_item
1884 .insert_before(MakefileItem::Variable(new_var))
1885 .unwrap();
1886
1887 let result = makefile.to_string();
1888 assert_eq!(result, "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n");
1889 }
1890
1891 #[test]
1892 fn test_makefile_item_insert_after_variable() {
1893 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1894 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1895 let new_var = temp.variable_definitions().next().unwrap();
1896 let mut first_item = makefile.items().next().unwrap();
1897 first_item
1898 .insert_after(MakefileItem::Variable(new_var))
1899 .unwrap();
1900
1901 let result = makefile.to_string();
1902 assert_eq!(result, "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n");
1903 }
1904
1905 #[test]
1906 fn test_makefile_item_insert_before_first_item() {
1907 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1908 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1909 let new_var = temp.variable_definitions().next().unwrap();
1910 let mut first_item = makefile.items().next().unwrap();
1911 first_item
1912 .insert_before(MakefileItem::Variable(new_var))
1913 .unwrap();
1914
1915 let result = makefile.to_string();
1916 assert_eq!(result, "VAR_NEW = inserted\nVAR1 = first\nVAR2 = second\n");
1917 }
1918
1919 #[test]
1920 fn test_makefile_item_insert_after_last_item() {
1921 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1922 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1923 let new_var = temp.variable_definitions().next().unwrap();
1924 let mut last_item = makefile.items().nth(1).unwrap();
1925 last_item
1926 .insert_after(MakefileItem::Variable(new_var))
1927 .unwrap();
1928
1929 let result = makefile.to_string();
1930 assert_eq!(result, "VAR1 = first\nVAR2 = second\nVAR_NEW = inserted\n");
1931 }
1932
1933 #[test]
1934 fn test_makefile_item_insert_before_include() {
1935 let makefile: Makefile = "VAR1 = value\nrule:\n\tcommand\n".parse().unwrap();
1936 let temp: Makefile = "include test.mk\n".parse().unwrap();
1937 let new_include = temp.includes().next().unwrap();
1938 let mut first_item = makefile.items().next().unwrap();
1939 first_item
1940 .insert_before(MakefileItem::Include(new_include))
1941 .unwrap();
1942
1943 let result = makefile.to_string();
1944 assert_eq!(result, "include test.mk\nVAR1 = value\nrule:\n\tcommand\n");
1945 }
1946
1947 #[test]
1948 fn test_makefile_item_insert_after_include() {
1949 let makefile: Makefile = "VAR1 = value\nrule:\n\tcommand\n".parse().unwrap();
1950 let temp: Makefile = "include test.mk\n".parse().unwrap();
1951 let new_include = temp.includes().next().unwrap();
1952 let mut first_item = makefile.items().next().unwrap();
1953 first_item
1954 .insert_after(MakefileItem::Include(new_include))
1955 .unwrap();
1956
1957 let result = makefile.to_string();
1958 assert_eq!(result, "VAR1 = value\ninclude test.mk\nrule:\n\tcommand\n");
1959 }
1960
1961 #[test]
1962 fn test_makefile_item_insert_before_rule() {
1963 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
1964 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1965 let new_rule = temp.rules().next().unwrap();
1966 let mut second_item = makefile.items().nth(1).unwrap();
1967 second_item
1968 .insert_before(MakefileItem::Rule(new_rule))
1969 .unwrap();
1970
1971 let result = makefile.to_string();
1972 assert_eq!(
1973 result,
1974 "rule1:\n\tcommand1\nnew_rule:\n\tnew_command\nrule2:\n\tcommand2\n"
1975 );
1976 }
1977
1978 #[test]
1979 fn test_makefile_item_insert_after_rule() {
1980 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
1981 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1982 let new_rule = temp.rules().next().unwrap();
1983 let mut first_item = makefile.items().next().unwrap();
1984 first_item
1985 .insert_after(MakefileItem::Rule(new_rule))
1986 .unwrap();
1987
1988 let result = makefile.to_string();
1989 assert_eq!(
1990 result,
1991 "rule1:\n\tcommand1\nnew_rule:\n\tnew_command\nrule2:\n\tcommand2\n"
1992 );
1993 }
1994
1995 #[test]
1996 fn test_makefile_item_insert_before_with_comments() {
1997 let makefile: Makefile = "# Comment 1\nVAR1 = first\n# Comment 2\nVAR2 = second\n"
1998 .parse()
1999 .unwrap();
2000 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
2001 let new_var = temp.variable_definitions().next().unwrap();
2002 let mut second_item = makefile.items().nth(1).unwrap();
2003 second_item
2004 .insert_before(MakefileItem::Variable(new_var))
2005 .unwrap();
2006
2007 let result = makefile.to_string();
2008 assert_eq!(
2011 result,
2012 "# Comment 1\nVAR1 = first\n# Comment 2\nVAR_NEW = inserted\nVAR2 = second\n"
2013 );
2014 }
2015
2016 #[test]
2017 fn test_makefile_item_insert_after_with_comments() {
2018 let makefile: Makefile = "# Comment 1\nVAR1 = first\n# Comment 2\nVAR2 = second\n"
2019 .parse()
2020 .unwrap();
2021 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
2022 let new_var = temp.variable_definitions().next().unwrap();
2023 let mut first_item = makefile.items().next().unwrap();
2024 first_item
2025 .insert_after(MakefileItem::Variable(new_var))
2026 .unwrap();
2027
2028 let result = makefile.to_string();
2029 assert_eq!(
2031 result,
2032 "# Comment 1\nVAR1 = first\nVAR_NEW = inserted\n# Comment 2\nVAR2 = second\n"
2033 );
2034 }
2035
2036 #[test]
2037 fn test_makefile_item_insert_before_preserves_formatting() {
2038 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
2039 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
2040 let new_var = temp.variable_definitions().next().unwrap();
2041 let mut second_item = makefile.items().nth(1).unwrap();
2042 second_item
2043 .insert_before(MakefileItem::Variable(new_var))
2044 .unwrap();
2045
2046 let result = makefile.to_string();
2047 assert_eq!(
2049 result,
2050 "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n"
2051 );
2052 }
2053
2054 #[test]
2055 fn test_makefile_item_insert_multiple_items() {
2056 let makefile: Makefile = "VAR1 = first\nVAR2 = last\n".parse().unwrap();
2057 let temp: Makefile = "VAR_A = a\nVAR_B = b\n".parse().unwrap();
2058 let mut new_vars: Vec<_> = temp.variable_definitions().collect();
2059
2060 let mut target_item = makefile.items().nth(1).unwrap();
2061 target_item
2062 .insert_before(MakefileItem::Variable(new_vars.pop().unwrap()))
2063 .unwrap();
2064
2065 let mut target_item = makefile.items().nth(1).unwrap();
2067 target_item
2068 .insert_before(MakefileItem::Variable(new_vars.pop().unwrap()))
2069 .unwrap();
2070
2071 let result = makefile.to_string();
2072 assert_eq!(result, "VAR1 = first\nVAR_A = a\nVAR_B = b\nVAR2 = last\n");
2073 }
2074
2075 #[test]
2076 fn test_rules_after_nested_conditionals() {
2077 let makefile_content = r#"#!/usr/bin/make -f
2080
2081ifeq ($(filter nodoc, $(DEB_BUILD_OPTIONS)),)
2082ifneq ($(shell which valadoc),)
2083 BUILD_DOC:=-Ddocs=true
2084endif
2085endif
2086
2087%:
2088 dh $@
2089
2090override_dh_auto_configure:
2091 echo test
2092"#;
2093 let makefile: Makefile = makefile_content.parse().unwrap();
2094
2095 let rules: Vec<_> = makefile.rules().collect();
2096 let targets: Vec<Vec<_>> = rules.iter().map(|r| r.targets().collect()).collect();
2097 assert_eq!(
2098 rules.len(),
2099 2,
2100 "Expected 2 rules (% and override_dh_auto_configure), got {} rules with targets: {:?}",
2101 rules.len(),
2102 targets
2103 );
2104
2105 assert_eq!(targets[0], vec!["%"]);
2106 assert_eq!(targets[1], vec!["override_dh_auto_configure"]);
2107
2108 assert!(makefile.find_rule_by_target_pattern("build-arch").is_some());
2110 assert!(makefile
2111 .find_rule_by_target_pattern("build-indep")
2112 .is_some());
2113 }
2114
2115 #[test]
2116 fn test_items_in_range_single_item() {
2117 let input = "CC = gcc\n\nall: build\n\techo done\n";
2118 let makefile: Makefile = input.parse().unwrap();
2119 let range = rowan::TextRange::new(0.into(), 8.into());
2121 let items: Vec<_> = makefile.items_in_range(range).collect();
2122 assert_eq!(items.len(), 1);
2123 assert!(matches!(items[0], MakefileItem::Variable(_)));
2124 }
2125
2126 #[test]
2127 fn test_items_in_range_multiple_items() {
2128 let input = "CC = gcc\n\nall: build\n\techo done\n";
2129 let makefile: Makefile = input.parse().unwrap();
2130 let range = rowan::TextRange::new(0.into(), (input.len() as u32).into());
2132 let items: Vec<_> = makefile.items_in_range(range).collect();
2133 assert_eq!(items.len(), 2);
2134 }
2135
2136 #[test]
2137 fn test_items_in_range_no_overlap() {
2138 let input = "CC = gcc\n\nall: build\n\techo done\n";
2139 let makefile: Makefile = input.parse().unwrap();
2140 let range = rowan::TextRange::new(9.into(), 10.into());
2142 let items: Vec<_> = makefile.items_in_range(range).collect();
2143 assert_eq!(items.len(), 0);
2144 }
2145
2146 #[test]
2147 fn test_items_in_range_partial_overlap() {
2148 let input = "CC = gcc\nLD = ld\nall: build\n\techo done\n";
2149 let makefile: Makefile = input.parse().unwrap();
2150 let range = rowan::TextRange::new(5.into(), 12.into());
2152 let items: Vec<_> = makefile.items_in_range(range).collect();
2153 assert_eq!(items.len(), 2);
2154 }
2155
2156 #[test]
2157 fn test_rules_in_range() {
2158 let input = "CC = gcc\nall: build\n\techo done\nclean:\n\trm -rf build\n";
2159 let makefile: Makefile = input.parse().unwrap();
2160 let range = rowan::TextRange::new(0.into(), (input.len() as u32).into());
2162 let rules: Vec<_> = makefile.rules_in_range(range).collect();
2163 assert_eq!(rules.len(), 2);
2164
2165 let range = rowan::TextRange::new(0.into(), 9.into());
2167 let rules: Vec<_> = makefile.rules_in_range(range).collect();
2168 assert_eq!(rules.len(), 0);
2169 }
2170
2171 #[test]
2172 fn test_variable_definitions_in_range() {
2173 let input = "CC = gcc\nLD = ld\nall: build\n\techo done\n";
2174 let makefile: Makefile = input.parse().unwrap();
2175 let range = rowan::TextRange::new(0.into(), 17.into());
2177 let vars: Vec<_> = makefile.variable_definitions_in_range(range).collect();
2178 assert_eq!(vars.len(), 2);
2179
2180 let range = rowan::TextRange::new(17.into(), (input.len() as u32).into());
2182 let vars: Vec<_> = makefile.variable_definitions_in_range(range).collect();
2183 assert_eq!(vars.len(), 0);
2184 }
2185
2186 #[test]
2187 fn test_variable_references_in_range() {
2188 let input = "CFLAGS = $(BASE) -Wall\nall: $(TARGETS)\n\techo done\n";
2189 let makefile: Makefile = input.parse().unwrap();
2190
2191 let all_refs: Vec<_> = makefile.variable_references().collect();
2193 let range = rowan::TextRange::new(0.into(), (input.len() as u32).into());
2194 let refs: Vec<_> = makefile.variable_references_in_range(range).collect();
2195 assert_eq!(refs.len(), all_refs.len());
2196
2197 let var_item = makefile.items().next().unwrap();
2200 let var_end: u32 = var_item.syntax().text_range().end().into();
2201 let range = rowan::TextRange::new(0.into(), var_end.into());
2202 let refs: Vec<_> = makefile.variable_references_in_range(range).collect();
2203 assert_eq!(refs.len(), 1);
2204 assert_eq!(refs[0].name(), Some("BASE".to_string()));
2205
2206 let range = rowan::TextRange::new(var_end.into(), (input.len() as u32).into());
2208 let refs: Vec<_> = makefile.variable_references_in_range(range).collect();
2209 assert_eq!(refs.len(), 1);
2210 assert_eq!(refs[0].name(), Some("TARGETS".to_string()));
2211 }
2212
2213 #[test]
2214 fn test_comment_blocks_single_block() {
2215 let makefile: Makefile = "# line 1\n# line 2\n# line 3\nall:\n\techo done\n"
2216 .parse()
2217 .unwrap();
2218 let blocks: Vec<_> = makefile.comment_blocks().collect();
2219 assert_eq!(blocks.len(), 1);
2220 let text = &makefile.to_string()[std::ops::Range::from(blocks[0])];
2221 assert!(text.contains("# line 1"));
2222 assert!(text.contains("# line 3"));
2223 }
2224
2225 #[test]
2226 fn test_comment_blocks_multiple_blocks() {
2227 let makefile: Makefile =
2228 "# block 1a\n# block 1b\nVAR = value\n# block 2a\n# block 2b\nall:\n\techo done\n"
2229 .parse()
2230 .unwrap();
2231 let blocks: Vec<_> = makefile.comment_blocks().collect();
2232 assert_eq!(blocks.len(), 2);
2233 }
2234
2235 #[test]
2236 fn test_comment_blocks_no_blocks() {
2237 let makefile: Makefile = "VAR = value\nall:\n\techo done\n".parse().unwrap();
2238 let blocks: Vec<_> = makefile.comment_blocks().collect();
2239 assert_eq!(blocks.len(), 0);
2240 }
2241
2242 #[test]
2243 fn test_comment_blocks_single_comment_not_a_block() {
2244 let makefile: Makefile = "# just one line\nVAR = value\n".parse().unwrap();
2245 let blocks: Vec<_> = makefile.comment_blocks().collect();
2246 assert_eq!(blocks.len(), 0);
2247 }
2248
2249 #[test]
2250 fn test_comment_blocks_with_blank_line_between() {
2251 let makefile: Makefile = "# line 1\n\n# line 2\nVAR = value\n".parse().unwrap();
2252 let blocks: Vec<_> = makefile.comment_blocks().collect();
2253 assert_eq!(blocks.len(), 1);
2255 }
2256}