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;
9
10#[derive(Clone)]
12pub enum MakefileItem {
13 Rule(Rule),
15 Variable(VariableDefinition),
17 Include(Include),
19 Conditional(Conditional),
21}
22
23impl MakefileItem {
24 pub(crate) fn cast(node: SyntaxNode) -> Option<Self> {
26 if let Some(rule) = Rule::cast(node.clone()) {
27 Some(MakefileItem::Rule(rule))
28 } else if let Some(var) = VariableDefinition::cast(node.clone()) {
29 Some(MakefileItem::Variable(var))
30 } else if let Some(inc) = Include::cast(node.clone()) {
31 Some(MakefileItem::Include(inc))
32 } else {
33 Conditional::cast(node).map(MakefileItem::Conditional)
34 }
35 }
36
37 pub(crate) fn syntax(&self) -> &SyntaxNode {
39 match self {
40 MakefileItem::Rule(r) => r.syntax(),
41 MakefileItem::Variable(v) => v.syntax(),
42 MakefileItem::Include(i) => i.syntax(),
43 MakefileItem::Conditional(c) => c.syntax(),
44 }
45 }
46
47 fn get_parent_or_error(&self, action: &str, method: &str) -> Result<SyntaxNode, Error> {
49 self.syntax().parent().ok_or_else(|| {
50 Error::Parse(ParseError {
51 errors: vec![ErrorInfo {
52 message: format!("Cannot {} item without parent", action),
53 line: 1,
54 context: format!("MakefileItem::{}", method),
55 }],
56 })
57 })
58 }
59
60 fn is_regular_comment(token: &rowan::SyntaxToken<crate::lossless::Lang>) -> bool {
62 token.kind() == COMMENT && !token.text().starts_with("#!")
63 }
64
65 fn extract_comment_text(token: &rowan::SyntaxToken<crate::lossless::Lang>) -> String {
67 let text = token.text();
68 text.strip_prefix("# ")
69 .or_else(|| text.strip_prefix('#'))
70 .unwrap_or(text)
71 .to_string()
72 }
73
74 fn collect_preceding_comment_elements(
78 &self,
79 ) -> Vec<rowan::NodeOrToken<SyntaxNode, rowan::SyntaxToken<crate::lossless::Lang>>> {
80 let mut elements = Vec::new();
81 let mut current = self.syntax().prev_sibling_or_token();
82
83 while let Some(element) = current {
84 match &element {
85 rowan::NodeOrToken::Token(token) if Self::is_regular_comment(token) => {
86 elements.push(element.clone());
87 }
88 rowan::NodeOrToken::Token(token)
89 if token.kind() == NEWLINE || token.kind() == WHITESPACE =>
90 {
91 elements.push(element.clone());
92 }
93 rowan::NodeOrToken::Node(n) if n.kind() == BLANK_LINE => {
94 elements.push(element.clone());
95 }
96 rowan::NodeOrToken::Token(token) if token.kind() == COMMENT => {
97 break;
99 }
100 _ => break,
101 }
102 current = element.prev_sibling_or_token();
103 }
104
105 elements
106 }
107
108 fn parse_comment_tokens(
110 comment_text: &str,
111 ) -> (
112 rowan::SyntaxToken<crate::lossless::Lang>,
113 Option<rowan::SyntaxToken<crate::lossless::Lang>>,
114 ) {
115 let comment_line = format!("# {}\n", comment_text);
116 let temp_makefile = crate::lossless::parse(&comment_line, None);
117 let root = temp_makefile.root();
118
119 let mut comment_token = None;
120 let mut newline_token = None;
121 let mut found_comment = false;
122
123 for element in root.syntax().children_with_tokens() {
124 if let rowan::NodeOrToken::Token(token) = element {
125 if token.kind() == COMMENT {
126 comment_token = Some(token);
127 found_comment = true;
128 } else if token.kind() == NEWLINE && found_comment && newline_token.is_none() {
129 newline_token = Some(token);
130 break;
131 }
132 }
133 }
134
135 (
136 comment_token.expect("Failed to extract comment token"),
137 newline_token,
138 )
139 }
140
141 pub fn replace(&mut self, new_item: MakefileItem) -> Result<(), Error> {
158 let parent = self.get_parent_or_error("replace", "replace")?;
159 let current_index = self.syntax().index();
160
161 parent.splice_children(
163 current_index..current_index + 1,
164 vec![new_item.syntax().clone().into()],
165 );
166
167 *self = new_item;
169
170 Ok(())
171 }
172
173 pub fn add_comment(&mut self, comment_text: &str) -> Result<(), Error> {
187 let parent = self.get_parent_or_error("add comment to", "add_comment")?;
188 let current_index = self.syntax().index();
189
190 let (comment_token, newline_token) = Self::parse_comment_tokens(comment_text);
192
193 let mut elements = vec![rowan::NodeOrToken::Token(comment_token)];
194 if let Some(newline) = newline_token {
195 elements.push(rowan::NodeOrToken::Token(newline));
196 }
197
198 parent.splice_children(current_index..current_index, elements);
200
201 Ok(())
202 }
203
204 pub fn preceding_comments(&self) -> impl Iterator<Item = String> {
219 let elements = self.collect_preceding_comment_elements();
220 let mut comments = Vec::new();
221
222 for element in elements.iter().rev() {
224 if let rowan::NodeOrToken::Token(token) = element {
225 if token.kind() == COMMENT {
226 comments.push(Self::extract_comment_text(token));
227 }
228 }
229 }
230
231 comments.into_iter()
232 }
233
234 pub fn remove_comments(&mut self) -> Result<usize, Error> {
248 let parent = self.get_parent_or_error("remove comments from", "remove_comments")?;
249 let collected_elements = self.collect_preceding_comment_elements();
250
251 let mut comment_count = 0;
253 for element in collected_elements.iter() {
254 if let rowan::NodeOrToken::Token(token) = element {
255 if token.kind() == COMMENT {
256 comment_count += 1;
257 }
258 }
259 }
260
261 let mut elements_to_remove = Vec::new();
264 let mut consecutive_newlines = 0;
265 for element in collected_elements.iter().rev() {
266 let should_remove = match element {
267 rowan::NodeOrToken::Token(token) if token.kind() == COMMENT => {
268 consecutive_newlines = 0;
269 true }
271 rowan::NodeOrToken::Token(token) if token.kind() == NEWLINE => {
272 consecutive_newlines += 1;
273 comment_count > 0 && consecutive_newlines <= 1
274 }
275 rowan::NodeOrToken::Token(token) if token.kind() == WHITESPACE => comment_count > 0,
276 rowan::NodeOrToken::Node(n) if n.kind() == BLANK_LINE => {
277 consecutive_newlines += 1;
278 comment_count > 0 && consecutive_newlines <= 1
279 }
280 _ => false,
281 };
282
283 if should_remove {
284 elements_to_remove.push(element.clone());
285 }
286 }
287
288 elements_to_remove.sort_by_key(|el| std::cmp::Reverse(el.index()));
290 for element in elements_to_remove {
291 let idx = element.index();
292 parent.splice_children(idx..idx + 1, vec![]);
293 }
294
295 Ok(comment_count)
296 }
297
298 pub fn modify_comment(&mut self, new_comment_text: &str) -> Result<bool, Error> {
314 let parent = self.get_parent_or_error("modify comment for", "modify_comment")?;
315
316 let collected_elements = self.collect_preceding_comment_elements();
318 let comment_element = collected_elements.iter().find(|element| {
319 if let rowan::NodeOrToken::Token(token) = element {
320 token.kind() == COMMENT
321 } else {
322 false
323 }
324 });
325
326 if let Some(element) = comment_element {
327 let idx = element.index();
328 let (new_comment_token, _) = Self::parse_comment_tokens(new_comment_text);
329 parent.splice_children(
330 idx..idx + 1,
331 vec![rowan::NodeOrToken::Token(new_comment_token)],
332 );
333 Ok(true)
334 } else {
335 Ok(false)
336 }
337 }
338
339 pub fn insert_before(&mut self, new_item: MakefileItem) -> Result<(), Error> {
356 let parent = self.get_parent_or_error("insert before", "insert_before")?;
357 let current_index = self.syntax().index();
358
359 parent.splice_children(
361 current_index..current_index,
362 vec![new_item.syntax().clone().into()],
363 );
364
365 Ok(())
366 }
367
368 pub fn insert_after(&mut self, new_item: MakefileItem) -> Result<(), Error> {
385 let parent = self.get_parent_or_error("insert after", "insert_after")?;
386 let current_index = self.syntax().index();
387
388 parent.splice_children(
390 current_index + 1..current_index + 1,
391 vec![new_item.syntax().clone().into()],
392 );
393
394 Ok(())
395 }
396}
397
398impl Makefile {
399 pub fn new() -> Makefile {
401 let mut builder = GreenNodeBuilder::new();
402
403 builder.start_node(ROOT.into());
404 builder.finish_node();
405
406 let syntax = SyntaxNode::new_root_mut(builder.finish());
407 Makefile::cast(syntax).unwrap()
408 }
409
410 pub fn parse(text: &str) -> crate::Parse<Makefile> {
412 crate::Parse::<Makefile>::parse_makefile(text)
413 }
414
415 pub fn code(&self) -> String {
417 self.syntax().text().to_string()
418 }
419
420 pub fn is_root(&self) -> bool {
422 self.syntax().kind() == ROOT
423 }
424
425 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
427 let mut buf = String::new();
428 r.read_to_string(&mut buf)?;
429 buf.parse()
430 }
431
432 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
434 let mut buf = String::new();
435 r.read_to_string(&mut buf)?;
436
437 let parsed = parse(&buf, None);
438 Ok(parsed.root())
439 }
440
441 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
450 self.syntax().children().filter_map(Rule::cast)
451 }
452
453 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
455 self.rules()
456 .filter(move |rule| rule.targets().any(|t| t == target))
457 }
458
459 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
461 self.syntax()
462 .children()
463 .filter_map(VariableDefinition::cast)
464 }
465
466 pub fn conditionals(&self) -> impl Iterator<Item = Conditional> + '_ {
468 self.syntax().children().filter_map(Conditional::cast)
469 }
470
471 pub fn items(&self) -> impl Iterator<Item = MakefileItem> + '_ {
487 self.syntax().children().filter_map(MakefileItem::cast)
488 }
489
490 pub fn find_variable<'a>(
505 &'a self,
506 name: &'a str,
507 ) -> impl Iterator<Item = VariableDefinition> + 'a {
508 self.variable_definitions()
509 .filter(move |var| var.name().as_deref() == Some(name))
510 }
511
512 pub fn add_rule(&mut self, target: &str) -> Rule {
522 let mut builder = GreenNodeBuilder::new();
523 builder.start_node(RULE.into());
524 builder.token(IDENTIFIER.into(), target);
525 builder.token(OPERATOR.into(), ":");
526 builder.token(NEWLINE.into(), "\n");
527 builder.finish_node();
528
529 let syntax = SyntaxNode::new_root_mut(builder.finish());
530 let pos = self.syntax().children_with_tokens().count();
531
532 let needs_blank_line = self.syntax().children().any(|c| c.kind() == RULE);
535
536 if needs_blank_line {
537 let mut bl_builder = GreenNodeBuilder::new();
539 bl_builder.start_node(BLANK_LINE.into());
540 bl_builder.token(NEWLINE.into(), "\n");
541 bl_builder.finish_node();
542 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
543
544 self.syntax()
545 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
546 } else {
547 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
548 }
549
550 Rule::cast(self.syntax().children().last().unwrap()).unwrap()
553 }
554
555 pub fn add_conditional(
571 &mut self,
572 conditional_type: &str,
573 condition: &str,
574 if_body: &str,
575 else_body: Option<&str>,
576 ) -> Result<Conditional, Error> {
577 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
579 return Err(Error::Parse(ParseError {
580 errors: vec![ErrorInfo {
581 message: format!(
582 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
583 conditional_type
584 ),
585 line: 1,
586 context: "add_conditional".to_string(),
587 }],
588 }));
589 }
590
591 let mut builder = GreenNodeBuilder::new();
592 builder.start_node(CONDITIONAL.into());
593
594 builder.start_node(CONDITIONAL_IF.into());
596 builder.token(IDENTIFIER.into(), conditional_type);
597 builder.token(WHITESPACE.into(), " ");
598
599 builder.start_node(EXPR.into());
601 builder.token(IDENTIFIER.into(), condition);
602 builder.finish_node();
603
604 builder.token(NEWLINE.into(), "\n");
605 builder.finish_node();
606
607 if !if_body.is_empty() {
609 for line in if_body.lines() {
610 if !line.is_empty() {
611 builder.token(IDENTIFIER.into(), line);
612 }
613 builder.token(NEWLINE.into(), "\n");
614 }
615 if !if_body.ends_with('\n') && !if_body.is_empty() {
617 builder.token(NEWLINE.into(), "\n");
618 }
619 }
620
621 if let Some(else_content) = else_body {
623 builder.start_node(CONDITIONAL_ELSE.into());
624 builder.token(IDENTIFIER.into(), "else");
625 builder.token(NEWLINE.into(), "\n");
626 builder.finish_node();
627
628 if !else_content.is_empty() {
630 for line in else_content.lines() {
631 if !line.is_empty() {
632 builder.token(IDENTIFIER.into(), line);
633 }
634 builder.token(NEWLINE.into(), "\n");
635 }
636 if !else_content.ends_with('\n') && !else_content.is_empty() {
638 builder.token(NEWLINE.into(), "\n");
639 }
640 }
641 }
642
643 builder.start_node(CONDITIONAL_ENDIF.into());
645 builder.token(IDENTIFIER.into(), "endif");
646 builder.token(NEWLINE.into(), "\n");
647 builder.finish_node();
648
649 builder.finish_node();
650
651 let syntax = SyntaxNode::new_root_mut(builder.finish());
652 let pos = self.syntax().children_with_tokens().count();
653
654 let needs_blank_line = self
656 .syntax()
657 .children()
658 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
659
660 if needs_blank_line {
661 let mut bl_builder = GreenNodeBuilder::new();
663 bl_builder.start_node(BLANK_LINE.into());
664 bl_builder.token(NEWLINE.into(), "\n");
665 bl_builder.finish_node();
666 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
667
668 self.syntax()
669 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
670 } else {
671 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
672 }
673
674 Ok(Conditional::cast(self.syntax().children().last().unwrap()).unwrap())
676 }
677
678 pub fn add_conditional_with_items<I1, I2>(
708 &mut self,
709 conditional_type: &str,
710 condition: &str,
711 if_items: I1,
712 else_items: Option<I2>,
713 ) -> Result<Conditional, Error>
714 where
715 I1: IntoIterator<Item = MakefileItem>,
716 I2: IntoIterator<Item = MakefileItem>,
717 {
718 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
720 return Err(Error::Parse(ParseError {
721 errors: vec![ErrorInfo {
722 message: format!(
723 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
724 conditional_type
725 ),
726 line: 1,
727 context: "add_conditional_with_items".to_string(),
728 }],
729 }));
730 }
731
732 let mut builder = GreenNodeBuilder::new();
733 builder.start_node(CONDITIONAL.into());
734
735 builder.start_node(CONDITIONAL_IF.into());
737 builder.token(IDENTIFIER.into(), conditional_type);
738 builder.token(WHITESPACE.into(), " ");
739
740 builder.start_node(EXPR.into());
742 builder.token(IDENTIFIER.into(), condition);
743 builder.finish_node();
744
745 builder.token(NEWLINE.into(), "\n");
746 builder.finish_node();
747
748 for item in if_items {
750 let item_text = item.syntax().to_string();
752 builder.token(IDENTIFIER.into(), item_text.trim());
754 builder.token(NEWLINE.into(), "\n");
755 }
756
757 if let Some(else_iter) = else_items {
759 builder.start_node(CONDITIONAL_ELSE.into());
760 builder.token(IDENTIFIER.into(), "else");
761 builder.token(NEWLINE.into(), "\n");
762 builder.finish_node();
763
764 for item in else_iter {
766 let item_text = item.syntax().to_string();
767 builder.token(IDENTIFIER.into(), item_text.trim());
768 builder.token(NEWLINE.into(), "\n");
769 }
770 }
771
772 builder.start_node(CONDITIONAL_ENDIF.into());
774 builder.token(IDENTIFIER.into(), "endif");
775 builder.token(NEWLINE.into(), "\n");
776 builder.finish_node();
777
778 builder.finish_node();
779
780 let syntax = SyntaxNode::new_root_mut(builder.finish());
781 let pos = self.syntax().children_with_tokens().count();
782
783 let needs_blank_line = self
785 .syntax()
786 .children()
787 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
788
789 if needs_blank_line {
790 let mut bl_builder = GreenNodeBuilder::new();
792 bl_builder.start_node(BLANK_LINE.into());
793 bl_builder.token(NEWLINE.into(), "\n");
794 bl_builder.finish_node();
795 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
796
797 self.syntax()
798 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
799 } else {
800 self.syntax().splice_children(pos..pos, vec![syntax.into()]);
801 }
802
803 Ok(Conditional::cast(self.syntax().children().last().unwrap()).unwrap())
805 }
806
807 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
809 let mut buf = String::new();
810 r.read_to_string(&mut buf)?;
811
812 let parsed = parse(&buf, None);
813 if !parsed.errors.is_empty() {
814 Err(Error::Parse(ParseError {
815 errors: parsed.errors,
816 }))
817 } else {
818 Ok(parsed.root())
819 }
820 }
821
822 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
833 let rules: Vec<_> = self
834 .syntax()
835 .children()
836 .filter(|n| n.kind() == RULE)
837 .collect();
838
839 if rules.is_empty() {
840 return Err(Error::Parse(ParseError {
841 errors: vec![ErrorInfo {
842 message: "Cannot replace rule in empty makefile".to_string(),
843 line: 1,
844 context: "replace_rule".to_string(),
845 }],
846 }));
847 }
848
849 if index >= rules.len() {
850 return Err(Error::Parse(ParseError {
851 errors: vec![ErrorInfo {
852 message: format!(
853 "Rule index {} out of bounds (max {})",
854 index,
855 rules.len() - 1
856 ),
857 line: 1,
858 context: "replace_rule".to_string(),
859 }],
860 }));
861 }
862
863 let target_node = &rules[index];
864 let target_index = target_node.index();
865
866 self.syntax().splice_children(
868 target_index..target_index + 1,
869 vec![new_rule.syntax().clone().into()],
870 );
871 Ok(())
872 }
873
874 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
885 let rules: Vec<_> = self
886 .syntax()
887 .children()
888 .filter(|n| n.kind() == RULE)
889 .collect();
890
891 if rules.is_empty() {
892 return Err(Error::Parse(ParseError {
893 errors: vec![ErrorInfo {
894 message: "Cannot remove rule from empty makefile".to_string(),
895 line: 1,
896 context: "remove_rule".to_string(),
897 }],
898 }));
899 }
900
901 if index >= rules.len() {
902 return Err(Error::Parse(ParseError {
903 errors: vec![ErrorInfo {
904 message: format!(
905 "Rule index {} out of bounds (max {})",
906 index,
907 rules.len() - 1
908 ),
909 line: 1,
910 context: "remove_rule".to_string(),
911 }],
912 }));
913 }
914
915 let target_node = rules[index].clone();
916 let target_index = target_node.index();
917
918 self.syntax()
920 .splice_children(target_index..target_index + 1, vec![]);
921 Ok(Rule::cast(target_node).unwrap())
922 }
923
924 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
936 let rules: Vec<_> = self
937 .syntax()
938 .children()
939 .filter(|n| n.kind() == RULE)
940 .collect();
941
942 if index > rules.len() {
943 return Err(Error::Parse(ParseError {
944 errors: vec![ErrorInfo {
945 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
946 line: 1,
947 context: "insert_rule".to_string(),
948 }],
949 }));
950 }
951
952 let target_index = if index == rules.len() {
953 self.syntax().children_with_tokens().count()
955 } else {
956 rules[index].index()
958 };
959
960 let mut nodes_to_insert = Vec::new();
962
963 if index == 0 && !rules.is_empty() {
965 nodes_to_insert.push(new_rule.syntax().clone().into());
969
970 let mut bl_builder = GreenNodeBuilder::new();
972 bl_builder.start_node(BLANK_LINE.into());
973 bl_builder.token(NEWLINE.into(), "\n");
974 bl_builder.finish_node();
975 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
976 nodes_to_insert.push(blank_line.into());
977 } else if index < rules.len() {
978 let has_blank_before = if target_index > 0 {
991 self.syntax()
992 .children_with_tokens()
993 .nth(target_index - 1)
994 .and_then(|n| n.as_node().map(|node| node.kind() == BLANK_LINE))
995 .unwrap_or(false)
996 } else {
997 false
998 };
999
1000 if !has_blank_before && index > 0 {
1002 let mut bl_builder = GreenNodeBuilder::new();
1003 bl_builder.start_node(BLANK_LINE.into());
1004 bl_builder.token(NEWLINE.into(), "\n");
1005 bl_builder.finish_node();
1006 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1007 nodes_to_insert.push(blank_line.into());
1008 }
1009
1010 nodes_to_insert.push(new_rule.syntax().clone().into());
1012
1013 let mut bl_builder = GreenNodeBuilder::new();
1015 bl_builder.start_node(BLANK_LINE.into());
1016 bl_builder.token(NEWLINE.into(), "\n");
1017 bl_builder.finish_node();
1018 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1019 nodes_to_insert.push(blank_line.into());
1020 } else {
1021 let mut bl_builder = GreenNodeBuilder::new();
1024 bl_builder.start_node(BLANK_LINE.into());
1025 bl_builder.token(NEWLINE.into(), "\n");
1026 bl_builder.finish_node();
1027 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1028 nodes_to_insert.push(blank_line.into());
1029
1030 nodes_to_insert.push(new_rule.syntax().clone().into());
1032 }
1033
1034 self.syntax()
1036 .splice_children(target_index..target_index, nodes_to_insert);
1037 Ok(())
1038 }
1039
1040 pub fn includes(&self) -> impl Iterator<Item = Include> {
1050 self.syntax().children().filter_map(Include::cast)
1051 }
1052
1053 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1063 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1066 let mut includes = Vec::new();
1067
1068 if let Some(include) = Include::cast(node.clone()) {
1070 includes.push(include);
1071 }
1072
1073 for child in node.children() {
1075 includes.extend(collect_includes(&child));
1076 }
1077
1078 includes
1079 }
1080
1081 let includes = collect_includes(self.syntax());
1083
1084 includes.into_iter().map(|include| {
1086 include
1087 .syntax()
1088 .children()
1089 .find(|node| node.kind() == EXPR)
1090 .map(|expr| expr.text().to_string().trim().to_string())
1091 .unwrap_or_default()
1092 })
1093 }
1094
1095 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1106 self.rules()
1107 .find(|rule| rule.targets().any(|t| t == target))
1108 }
1109
1110 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1120 self.rules_by_target(target)
1121 }
1122
1123 pub fn find_rule_by_target_pattern(&self, target: &str) -> Option<Rule> {
1136 self.rules()
1137 .find(|rule| rule.targets().any(|t| matches_pattern(&t, target)))
1138 }
1139
1140 pub fn find_rules_by_target_pattern<'a>(
1153 &'a self,
1154 target: &'a str,
1155 ) -> impl Iterator<Item = Rule> + 'a {
1156 self.rules()
1157 .filter(move |rule| rule.targets().any(|t| matches_pattern(&t, target)))
1158 }
1159
1160 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1170 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1172 if !phony_rule.prerequisites().any(|p| p == target) {
1174 phony_rule.add_prerequisite(target)?;
1175 }
1176 } else {
1177 let mut phony_rule = self.add_rule(".PHONY");
1179 phony_rule.add_prerequisite(target)?;
1180 }
1181 Ok(())
1182 }
1183
1184 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1198 let mut phony_rule = None;
1200 for rule in self.rules_by_target(".PHONY") {
1201 if rule.prerequisites().any(|p| p == target) {
1202 phony_rule = Some(rule);
1203 break;
1204 }
1205 }
1206
1207 let mut phony_rule = match phony_rule {
1208 Some(rule) => rule,
1209 None => return Ok(false),
1210 };
1211
1212 let prereq_count = phony_rule.prerequisites().count();
1214
1215 phony_rule.remove_prerequisite(target)?;
1217
1218 if prereq_count == 1 {
1220 phony_rule.remove()?;
1222 }
1223
1224 Ok(true)
1225 }
1226
1227 pub fn is_phony(&self, target: &str) -> bool {
1238 self.rules_by_target(".PHONY")
1240 .any(|rule| rule.prerequisites().any(|p| p == target))
1241 }
1242
1243 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1253 self.rules_by_target(".PHONY")
1255 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1256 }
1257
1258 pub fn add_include(&mut self, path: &str) -> Include {
1271 let mut builder = GreenNodeBuilder::new();
1272 builder.start_node(INCLUDE.into());
1273 builder.token(IDENTIFIER.into(), "include");
1274 builder.token(WHITESPACE.into(), " ");
1275
1276 builder.start_node(EXPR.into());
1278 builder.token(IDENTIFIER.into(), path);
1279 builder.finish_node();
1280
1281 builder.token(NEWLINE.into(), "\n");
1282 builder.finish_node();
1283
1284 let syntax = SyntaxNode::new_root_mut(builder.finish());
1285
1286 self.syntax().splice_children(0..0, vec![syntax.into()]);
1288
1289 Include::cast(self.syntax().children().next().unwrap()).unwrap()
1291 }
1292
1293 pub fn insert_include(&mut self, index: usize, path: &str) -> Result<Include, Error> {
1310 let items: Vec<_> = self.syntax().children().collect();
1311
1312 if index > items.len() {
1313 return Err(Error::Parse(ParseError {
1314 errors: vec![ErrorInfo {
1315 message: format!("Index {} out of bounds (max {})", index, items.len()),
1316 line: 1,
1317 context: "insert_include".to_string(),
1318 }],
1319 }));
1320 }
1321
1322 let mut builder = GreenNodeBuilder::new();
1323 builder.start_node(INCLUDE.into());
1324 builder.token(IDENTIFIER.into(), "include");
1325 builder.token(WHITESPACE.into(), " ");
1326
1327 builder.start_node(EXPR.into());
1329 builder.token(IDENTIFIER.into(), path);
1330 builder.finish_node();
1331
1332 builder.token(NEWLINE.into(), "\n");
1333 builder.finish_node();
1334
1335 let syntax = SyntaxNode::new_root_mut(builder.finish());
1336
1337 let target_index = if index == items.len() {
1338 self.syntax().children_with_tokens().count()
1340 } else {
1341 items[index].index()
1343 };
1344
1345 self.syntax()
1347 .splice_children(target_index..target_index, vec![syntax.into()]);
1348
1349 Ok(Include::cast(self.syntax().children().nth(index).unwrap()).unwrap())
1352 }
1353
1354 pub fn insert_include_after(
1372 &mut self,
1373 after: &MakefileItem,
1374 path: &str,
1375 ) -> Result<Include, Error> {
1376 let mut builder = GreenNodeBuilder::new();
1377 builder.start_node(INCLUDE.into());
1378 builder.token(IDENTIFIER.into(), "include");
1379 builder.token(WHITESPACE.into(), " ");
1380
1381 builder.start_node(EXPR.into());
1383 builder.token(IDENTIFIER.into(), path);
1384 builder.finish_node();
1385
1386 builder.token(NEWLINE.into(), "\n");
1387 builder.finish_node();
1388
1389 let syntax = SyntaxNode::new_root_mut(builder.finish());
1390
1391 let after_syntax = after.syntax();
1393 let target_index = after_syntax.index() + 1;
1394
1395 self.syntax()
1397 .splice_children(target_index..target_index, vec![syntax.into()]);
1398
1399 let after_child_index = self
1402 .syntax()
1403 .children()
1404 .position(|child| child.text_range() == after_syntax.text_range())
1405 .ok_or_else(|| {
1406 Error::Parse(ParseError {
1407 errors: vec![ErrorInfo {
1408 message: "Could not find the reference item".to_string(),
1409 line: 1,
1410 context: "insert_include_after".to_string(),
1411 }],
1412 })
1413 })?;
1414
1415 Ok(Include::cast(self.syntax().children().nth(after_child_index + 1).unwrap()).unwrap())
1416 }
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421 use super::*;
1422
1423 #[test]
1424 fn test_makefile_item_replace_variable_with_variable() {
1425 let makefile: Makefile = "VAR1 = old\nrule:\n\tcommand\n".parse().unwrap();
1426 let temp: Makefile = "VAR2 = new\n".parse().unwrap();
1427 let new_var = temp.variable_definitions().next().unwrap();
1428 let mut first_item = makefile.items().next().unwrap();
1429 first_item.replace(MakefileItem::Variable(new_var)).unwrap();
1430
1431 let result = makefile.to_string();
1432 assert_eq!(result, "VAR2 = new\nrule:\n\tcommand\n");
1433 }
1434
1435 #[test]
1436 fn test_makefile_item_replace_variable_with_rule() {
1437 let makefile: Makefile = "VAR1 = value\nrule1:\n\tcommand1\n".parse().unwrap();
1438 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1439 let new_rule = temp.rules().next().unwrap();
1440 let mut first_item = makefile.items().next().unwrap();
1441 first_item.replace(MakefileItem::Rule(new_rule)).unwrap();
1442
1443 let result = makefile.to_string();
1444 assert_eq!(result, "new_rule:\n\tnew_command\nrule1:\n\tcommand1\n");
1445 }
1446
1447 #[test]
1448 fn test_makefile_item_replace_preserves_position() {
1449 let makefile: Makefile = "VAR1 = first\nVAR2 = second\nVAR3 = third\n"
1450 .parse()
1451 .unwrap();
1452 let temp: Makefile = "NEW = replacement\n".parse().unwrap();
1453 let new_var = temp.variable_definitions().next().unwrap();
1454
1455 let mut second_item = makefile.items().nth(1).unwrap();
1457 second_item
1458 .replace(MakefileItem::Variable(new_var))
1459 .unwrap();
1460
1461 let items: Vec<_> = makefile.variable_definitions().collect();
1462 assert_eq!(items.len(), 3);
1463 assert_eq!(items[0].name(), Some("VAR1".to_string()));
1464 assert_eq!(items[1].name(), Some("NEW".to_string()));
1465 assert_eq!(items[2].name(), Some("VAR3".to_string()));
1466 }
1467
1468 #[test]
1469 fn test_makefile_item_add_comment() {
1470 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1471 let mut item = makefile.items().next().unwrap();
1472 item.add_comment("This is a variable").unwrap();
1473
1474 let result = makefile.to_string();
1475 assert_eq!(result, "# This is a variable\nVAR = value\n");
1476 }
1477
1478 #[test]
1479 fn test_makefile_item_add_multiple_comments() {
1480 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1481 let mut item = makefile.items().next().unwrap();
1482 item.add_comment("Comment 1").unwrap();
1483 let mut item = makefile.items().next().unwrap();
1485 item.add_comment("Comment 2").unwrap();
1486
1487 let result = makefile.to_string();
1488 assert_eq!(result, "# Comment 1\n# Comment 2\nVAR = value\n");
1491 }
1492
1493 #[test]
1494 fn test_makefile_item_preceding_comments() {
1495 let makefile: Makefile = "# Comment 1\n# Comment 2\nVAR = value\n".parse().unwrap();
1496 let item = makefile.items().next().unwrap();
1497 let comments: Vec<_> = item.preceding_comments().collect();
1498 assert_eq!(comments.len(), 2);
1499 assert_eq!(comments[0], "Comment 1");
1500 assert_eq!(comments[1], "Comment 2");
1501 }
1502
1503 #[test]
1504 fn test_makefile_item_preceding_comments_no_comments() {
1505 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1506 let item = makefile.items().next().unwrap();
1507 let comments: Vec<_> = item.preceding_comments().collect();
1508 assert_eq!(comments.len(), 0);
1509 }
1510
1511 #[test]
1512 fn test_makefile_item_preceding_comments_ignores_shebang() {
1513 let makefile: Makefile = "#!/usr/bin/make\n# Real comment\nVAR = value\n"
1514 .parse()
1515 .unwrap();
1516 let item = makefile.items().next().unwrap();
1517 let comments: Vec<_> = item.preceding_comments().collect();
1518 assert_eq!(comments.len(), 1);
1519 assert_eq!(comments[0], "Real comment");
1520 }
1521
1522 #[test]
1523 fn test_makefile_item_remove_comments() {
1524 let makefile: Makefile = "# Comment 1\n# Comment 2\nVAR = value\n".parse().unwrap();
1525 let mut item = makefile.items().next().unwrap();
1527 let count = item.remove_comments().unwrap();
1528
1529 assert_eq!(count, 2);
1530 let result = makefile.to_string();
1531 assert_eq!(result, "VAR = value\n");
1532 }
1533
1534 #[test]
1535 fn test_makefile_item_remove_comments_no_comments() {
1536 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1537 let mut item = makefile.items().next().unwrap();
1538 let count = item.remove_comments().unwrap();
1539
1540 assert_eq!(count, 0);
1541 assert_eq!(makefile.to_string(), "VAR = value\n");
1542 }
1543
1544 #[test]
1545 fn test_makefile_item_modify_comment() {
1546 let makefile: Makefile = "# Old comment\nVAR = value\n".parse().unwrap();
1547 let mut item = makefile.items().next().unwrap();
1548 let modified = item.modify_comment("New comment").unwrap();
1549
1550 assert!(modified);
1551 let result = makefile.to_string();
1552 assert_eq!(result, "# New comment\nVAR = value\n");
1553 }
1554
1555 #[test]
1556 fn test_makefile_item_modify_comment_no_comment() {
1557 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1558 let mut item = makefile.items().next().unwrap();
1559 let modified = item.modify_comment("New comment").unwrap();
1560
1561 assert!(!modified);
1562 assert_eq!(makefile.to_string(), "VAR = value\n");
1563 }
1564
1565 #[test]
1566 fn test_makefile_item_modify_comment_modifies_closest() {
1567 let makefile: Makefile = "# Comment 1\n# Comment 2\n# Comment 3\nVAR = value\n"
1568 .parse()
1569 .unwrap();
1570 let mut item = makefile.items().next().unwrap();
1571 let modified = item.modify_comment("Modified").unwrap();
1572
1573 assert!(modified);
1574 let result = makefile.to_string();
1575 assert_eq!(
1576 result,
1577 "# Comment 1\n# Comment 2\n# Modified\nVAR = value\n"
1578 );
1579 }
1580
1581 #[test]
1582 fn test_makefile_item_comment_workflow() {
1583 let makefile: Makefile = "VAR = value\n".parse().unwrap();
1585 let mut item = makefile.items().next().unwrap();
1586
1587 item.add_comment("Initial comment").unwrap();
1589 assert_eq!(makefile.to_string(), "# Initial comment\nVAR = value\n");
1590
1591 let mut item = makefile.items().next().unwrap();
1593 item.modify_comment("Updated comment").unwrap();
1595 assert_eq!(makefile.to_string(), "# Updated comment\nVAR = value\n");
1596
1597 let mut item = makefile.items().next().unwrap();
1599 let count = item.remove_comments().unwrap();
1601 assert_eq!(count, 1);
1602 assert_eq!(makefile.to_string(), "VAR = value\n");
1603 }
1604
1605 #[test]
1606 fn test_makefile_item_replace_with_comments() {
1607 let makefile: Makefile = "# Comment for VAR1\nVAR1 = old\nrule:\n\tcommand\n"
1608 .parse()
1609 .unwrap();
1610 let temp: Makefile = "VAR2 = new\n".parse().unwrap();
1611 let new_var = temp.variable_definitions().next().unwrap();
1612 let mut first_item = makefile.items().next().unwrap();
1613
1614 let comments: Vec<_> = first_item.preceding_comments().collect();
1616 assert_eq!(comments.len(), 1);
1617 assert_eq!(comments[0], "Comment for VAR1");
1618
1619 first_item.replace(MakefileItem::Variable(new_var)).unwrap();
1621
1622 let result = makefile.to_string();
1623 assert_eq!(result, "# Comment for VAR1\nVAR2 = new\nrule:\n\tcommand\n");
1625 }
1626
1627 #[test]
1628 fn test_makefile_item_insert_before_variable() {
1629 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1630 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1631 let new_var = temp.variable_definitions().next().unwrap();
1632 let mut second_item = makefile.items().nth(1).unwrap();
1633 second_item
1634 .insert_before(MakefileItem::Variable(new_var))
1635 .unwrap();
1636
1637 let result = makefile.to_string();
1638 assert_eq!(result, "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n");
1639 }
1640
1641 #[test]
1642 fn test_makefile_item_insert_after_variable() {
1643 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1644 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1645 let new_var = temp.variable_definitions().next().unwrap();
1646 let mut first_item = makefile.items().next().unwrap();
1647 first_item
1648 .insert_after(MakefileItem::Variable(new_var))
1649 .unwrap();
1650
1651 let result = makefile.to_string();
1652 assert_eq!(result, "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n");
1653 }
1654
1655 #[test]
1656 fn test_makefile_item_insert_before_first_item() {
1657 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1658 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1659 let new_var = temp.variable_definitions().next().unwrap();
1660 let mut first_item = makefile.items().next().unwrap();
1661 first_item
1662 .insert_before(MakefileItem::Variable(new_var))
1663 .unwrap();
1664
1665 let result = makefile.to_string();
1666 assert_eq!(result, "VAR_NEW = inserted\nVAR1 = first\nVAR2 = second\n");
1667 }
1668
1669 #[test]
1670 fn test_makefile_item_insert_after_last_item() {
1671 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1672 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1673 let new_var = temp.variable_definitions().next().unwrap();
1674 let mut last_item = makefile.items().nth(1).unwrap();
1675 last_item
1676 .insert_after(MakefileItem::Variable(new_var))
1677 .unwrap();
1678
1679 let result = makefile.to_string();
1680 assert_eq!(result, "VAR1 = first\nVAR2 = second\nVAR_NEW = inserted\n");
1681 }
1682
1683 #[test]
1684 fn test_makefile_item_insert_before_include() {
1685 let makefile: Makefile = "VAR1 = value\nrule:\n\tcommand\n".parse().unwrap();
1686 let temp: Makefile = "include test.mk\n".parse().unwrap();
1687 let new_include = temp.includes().next().unwrap();
1688 let mut first_item = makefile.items().next().unwrap();
1689 first_item
1690 .insert_before(MakefileItem::Include(new_include))
1691 .unwrap();
1692
1693 let result = makefile.to_string();
1694 assert_eq!(result, "include test.mk\nVAR1 = value\nrule:\n\tcommand\n");
1695 }
1696
1697 #[test]
1698 fn test_makefile_item_insert_after_include() {
1699 let makefile: Makefile = "VAR1 = value\nrule:\n\tcommand\n".parse().unwrap();
1700 let temp: Makefile = "include test.mk\n".parse().unwrap();
1701 let new_include = temp.includes().next().unwrap();
1702 let mut first_item = makefile.items().next().unwrap();
1703 first_item
1704 .insert_after(MakefileItem::Include(new_include))
1705 .unwrap();
1706
1707 let result = makefile.to_string();
1708 assert_eq!(result, "VAR1 = value\ninclude test.mk\nrule:\n\tcommand\n");
1709 }
1710
1711 #[test]
1712 fn test_makefile_item_insert_before_rule() {
1713 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
1714 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1715 let new_rule = temp.rules().next().unwrap();
1716 let mut second_item = makefile.items().nth(1).unwrap();
1717 second_item
1718 .insert_before(MakefileItem::Rule(new_rule))
1719 .unwrap();
1720
1721 let result = makefile.to_string();
1722 assert_eq!(
1723 result,
1724 "rule1:\n\tcommand1\nnew_rule:\n\tnew_command\nrule2:\n\tcommand2\n"
1725 );
1726 }
1727
1728 #[test]
1729 fn test_makefile_item_insert_after_rule() {
1730 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
1731 let temp: Makefile = "new_rule:\n\tnew_command\n".parse().unwrap();
1732 let new_rule = temp.rules().next().unwrap();
1733 let mut first_item = makefile.items().next().unwrap();
1734 first_item
1735 .insert_after(MakefileItem::Rule(new_rule))
1736 .unwrap();
1737
1738 let result = makefile.to_string();
1739 assert_eq!(
1740 result,
1741 "rule1:\n\tcommand1\nnew_rule:\n\tnew_command\nrule2:\n\tcommand2\n"
1742 );
1743 }
1744
1745 #[test]
1746 fn test_makefile_item_insert_before_with_comments() {
1747 let makefile: Makefile = "# Comment 1\nVAR1 = first\n# Comment 2\nVAR2 = second\n"
1748 .parse()
1749 .unwrap();
1750 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1751 let new_var = temp.variable_definitions().next().unwrap();
1752 let mut second_item = makefile.items().nth(1).unwrap();
1753 second_item
1754 .insert_before(MakefileItem::Variable(new_var))
1755 .unwrap();
1756
1757 let result = makefile.to_string();
1758 assert_eq!(
1761 result,
1762 "# Comment 1\nVAR1 = first\n# Comment 2\nVAR_NEW = inserted\nVAR2 = second\n"
1763 );
1764 }
1765
1766 #[test]
1767 fn test_makefile_item_insert_after_with_comments() {
1768 let makefile: Makefile = "# Comment 1\nVAR1 = first\n# Comment 2\nVAR2 = second\n"
1769 .parse()
1770 .unwrap();
1771 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1772 let new_var = temp.variable_definitions().next().unwrap();
1773 let mut first_item = makefile.items().next().unwrap();
1774 first_item
1775 .insert_after(MakefileItem::Variable(new_var))
1776 .unwrap();
1777
1778 let result = makefile.to_string();
1779 assert_eq!(
1781 result,
1782 "# Comment 1\nVAR1 = first\nVAR_NEW = inserted\n# Comment 2\nVAR2 = second\n"
1783 );
1784 }
1785
1786 #[test]
1787 fn test_makefile_item_insert_before_preserves_formatting() {
1788 let makefile: Makefile = "VAR1 = first\nVAR2 = second\n".parse().unwrap();
1789 let temp: Makefile = "VAR_NEW = inserted\n".parse().unwrap();
1790 let new_var = temp.variable_definitions().next().unwrap();
1791 let mut second_item = makefile.items().nth(1).unwrap();
1792 second_item
1793 .insert_before(MakefileItem::Variable(new_var))
1794 .unwrap();
1795
1796 let result = makefile.to_string();
1797 assert_eq!(
1799 result,
1800 "VAR1 = first\nVAR_NEW = inserted\nVAR2 = second\n"
1801 );
1802 }
1803
1804 #[test]
1805 fn test_makefile_item_insert_multiple_items() {
1806 let makefile: Makefile = "VAR1 = first\nVAR2 = last\n".parse().unwrap();
1807 let temp: Makefile = "VAR_A = a\nVAR_B = b\n".parse().unwrap();
1808 let mut new_vars: Vec<_> = temp.variable_definitions().collect();
1809
1810 let mut target_item = makefile.items().nth(1).unwrap();
1811 target_item
1812 .insert_before(MakefileItem::Variable(new_vars.pop().unwrap()))
1813 .unwrap();
1814
1815 let mut target_item = makefile.items().nth(1).unwrap();
1817 target_item
1818 .insert_before(MakefileItem::Variable(new_vars.pop().unwrap()))
1819 .unwrap();
1820
1821 let result = makefile.to_string();
1822 assert_eq!(result, "VAR1 = first\nVAR_A = a\nVAR_B = b\nVAR2 = last\n");
1823 }
1824}