1use crate::lex::lex;
2use crate::SyntaxKind;
3use crate::SyntaxKind::*;
4use rowan::ast::AstNode;
5use std::str::FromStr;
6
7#[derive(Debug)]
8pub enum Error {
10 Io(std::io::Error),
12
13 Parse(ParseError),
15}
16
17impl std::fmt::Display for Error {
18 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19 match &self {
20 Error::Io(e) => write!(f, "IO error: {}", e),
21 Error::Parse(e) => write!(f, "Parse error: {}", e),
22 }
23 }
24}
25
26impl From<std::io::Error> for Error {
27 fn from(e: std::io::Error) -> Self {
28 Error::Io(e)
29 }
30}
31
32impl std::error::Error for Error {}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct ParseError {
37 pub errors: Vec<ErrorInfo>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct ErrorInfo {
44 pub message: String,
46 pub line: usize,
48 pub context: String,
50}
51
52impl std::fmt::Display for ParseError {
53 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54 for err in &self.errors {
55 writeln!(f, "Error at line {}: {}", err.line, err.message)?;
56 writeln!(f, "{}| {}", err.line, err.context)?;
57 }
58 Ok(())
59 }
60}
61
62impl std::error::Error for ParseError {}
63
64impl From<ParseError> for Error {
65 fn from(e: ParseError) -> Self {
66 Error::Parse(e)
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum MakefileVariant {
73 GNUMake,
75 BSDMake,
77 NMake,
79 POSIXMake,
81}
82
83fn matches_pattern(pattern: &str, target: &str) -> bool {
95 if !pattern.contains('%') {
97 return pattern == target;
98 }
99
100 let parts: Vec<&str> = pattern.split('%').collect();
102
103 if parts.len() != 2 {
105 return pattern == target;
107 }
108
109 let prefix = parts[0];
110 let suffix = parts[1];
111
112 if target.len() <= prefix.len() + suffix.len() {
114 return false;
115 }
116
117 target.starts_with(prefix) && target.ends_with(suffix)
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
125pub enum Lang {}
126impl rowan::Language for Lang {
127 type Kind = SyntaxKind;
128 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
129 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
130 }
131 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
132 kind.into()
133 }
134}
135
136use rowan::GreenNode;
139
140use rowan::GreenNodeBuilder;
144
145#[derive(Debug)]
148pub(crate) struct Parse {
149 pub(crate) green_node: GreenNode,
150 #[allow(unused)]
151 pub(crate) errors: Vec<ErrorInfo>,
152}
153
154pub(crate) fn parse(text: &str, variant: Option<MakefileVariant>) -> Parse {
155 struct Parser {
156 tokens: Vec<(SyntaxKind, String)>,
159 builder: GreenNodeBuilder<'static>,
161 errors: Vec<ErrorInfo>,
164 original_text: String,
166 variant: Option<MakefileVariant>,
168 }
169
170 impl Parser {
171 fn error(&mut self, msg: String) {
172 self.builder.start_node(ERROR.into());
173
174 let (line, context) = if self.current() == Some(INDENT) {
175 let lines: Vec<&str> = self.original_text.lines().collect();
177 let tab_line = lines
178 .iter()
179 .enumerate()
180 .find(|(_, line)| line.starts_with('\t'))
181 .map(|(i, _)| i + 1)
182 .unwrap_or(1);
183
184 let next_line = tab_line + 1;
186 if next_line <= lines.len() {
187 (next_line, lines[next_line - 1].to_string())
188 } else {
189 (tab_line, lines[tab_line - 1].to_string())
190 }
191 } else {
192 let line = self.get_line_number_for_position(self.tokens.len());
193 (line, self.get_context_for_line(line))
194 };
195
196 let message = if self.current() == Some(INDENT) && !msg.contains("indented") {
197 if !self.tokens.is_empty() && self.tokens[self.tokens.len() - 1].0 == IDENTIFIER {
198 "expected ':'".to_string()
199 } else {
200 "indented line not part of a rule".to_string()
201 }
202 } else {
203 msg
204 };
205
206 self.errors.push(ErrorInfo {
207 message,
208 line,
209 context,
210 });
211
212 if self.current().is_some() {
213 self.bump();
214 }
215 self.builder.finish_node();
216 }
217
218 fn get_line_number_for_position(&self, position: usize) -> usize {
219 if position >= self.tokens.len() {
220 return self.original_text.matches('\n').count() + 1;
221 }
222
223 self.tokens[0..position]
225 .iter()
226 .filter(|(kind, _)| *kind == NEWLINE)
227 .count()
228 + 1
229 }
230
231 fn get_context_for_line(&self, line_number: usize) -> String {
232 self.original_text
233 .lines()
234 .nth(line_number - 1)
235 .unwrap_or("")
236 .to_string()
237 }
238
239 fn parse_recipe_line(&mut self) {
240 self.builder.start_node(RECIPE.into());
241
242 if self.current() != Some(INDENT) {
244 self.error("recipe line must start with a tab".to_string());
245 self.builder.finish_node();
246 return;
247 }
248 self.bump();
249
250 while self.current().is_some() && self.current() != Some(NEWLINE) {
253 self.bump();
254 }
255
256 if self.current() == Some(NEWLINE) {
258 self.bump();
259 }
260
261 self.builder.finish_node();
262 }
263
264 fn parse_rule_target(&mut self) -> bool {
265 match self.current() {
266 Some(IDENTIFIER) => {
267 if self.is_archive_member() {
269 self.parse_archive_member();
270 } else {
271 self.bump();
272 }
273 true
274 }
275 Some(DOLLAR) => {
276 self.parse_variable_reference();
277 true
278 }
279 _ => {
280 self.error("expected rule target".to_string());
281 false
282 }
283 }
284 }
285
286 fn is_archive_member(&self) -> bool {
287 if self.tokens.len() < 2 {
290 return false;
291 }
292
293 let current_is_identifier = self.current() == Some(IDENTIFIER);
295 let next_is_lparen =
296 self.tokens.len() > 1 && self.tokens[self.tokens.len() - 2].0 == LPAREN;
297
298 current_is_identifier && next_is_lparen
299 }
300
301 fn parse_archive_member(&mut self) {
302 if self.current() == Some(IDENTIFIER) {
313 self.bump();
314 }
315
316 if self.current() == Some(LPAREN) {
318 self.bump();
319
320 self.builder.start_node(ARCHIVE_MEMBERS.into());
322
323 while self.current().is_some() && self.current() != Some(RPAREN) {
325 match self.current() {
326 Some(IDENTIFIER) | Some(TEXT) => {
327 self.builder.start_node(ARCHIVE_MEMBER.into());
329 self.bump();
330 self.builder.finish_node();
331 }
332 Some(WHITESPACE) => self.bump(),
333 Some(DOLLAR) => {
334 self.builder.start_node(ARCHIVE_MEMBER.into());
336 self.parse_variable_reference();
337 self.builder.finish_node();
338 }
339 _ => break,
340 }
341 }
342
343 self.builder.finish_node();
345
346 if self.current() == Some(RPAREN) {
348 self.bump();
349 } else {
350 self.error("expected ')' to close archive member".to_string());
351 }
352 }
353 }
354
355 fn parse_rule_dependencies(&mut self) {
356 self.builder.start_node(PREREQUISITES.into());
357
358 while self.current().is_some() && self.current() != Some(NEWLINE) {
359 match self.current() {
360 Some(WHITESPACE) => {
361 self.bump(); }
363 Some(IDENTIFIER) => {
364 self.builder.start_node(PREREQUISITE.into());
366
367 if self.is_archive_member() {
368 self.parse_archive_member();
369 } else {
370 self.bump(); }
372
373 self.builder.finish_node(); }
375 Some(DOLLAR) => {
376 self.builder.start_node(PREREQUISITE.into());
378
379 self.bump(); if self.current() == Some(LPAREN) {
383 self.bump(); let mut paren_count = 1;
385
386 while self.current().is_some() && paren_count > 0 {
387 if self.current() == Some(LPAREN) {
388 paren_count += 1;
389 } else if self.current() == Some(RPAREN) {
390 paren_count -= 1;
391 }
392 self.bump();
393 }
394 } else {
395 if self.current().is_some() {
397 self.bump();
398 }
399 }
400
401 self.builder.finish_node(); }
403 _ => {
404 self.bump();
406 }
407 }
408 }
409
410 self.builder.finish_node(); }
412
413 fn parse_rule_recipes(&mut self) {
414 loop {
415 match self.current() {
416 Some(INDENT) => {
417 self.parse_recipe_line();
418 }
419 Some(NEWLINE) => {
420 self.bump();
424 }
425 Some(IDENTIFIER) => {
426 let token = &self.tokens.last().unwrap().1.clone();
427 if (token == "ifdef"
429 || token == "ifndef"
430 || token == "ifeq"
431 || token == "ifneq")
432 && matches!(self.variant, None | Some(MakefileVariant::GNUMake))
433 {
434 self.parse_conditional();
435 } else if token == "include" || token == "-include" || token == "sinclude" {
436 self.parse_include();
437 } else if token == "else" || token == "endif" {
438 break;
440 } else {
441 break;
442 }
443 }
444 _ => break,
445 }
446 }
447 }
448
449 fn find_and_consume_colon(&mut self) -> bool {
450 self.skip_ws();
452
453 if self.current() == Some(OPERATOR) && self.tokens.last().unwrap().1 == ":" {
455 self.bump();
456 return true;
457 }
458
459 let has_colon = self
461 .tokens
462 .iter()
463 .rev()
464 .any(|(kind, text)| *kind == OPERATOR && text == ":");
465
466 if has_colon {
467 while self.current().is_some() {
469 if self.current() == Some(OPERATOR)
470 && self.tokens.last().map(|(_, text)| text.as_str()) == Some(":")
471 {
472 self.bump();
473 return true;
474 }
475 self.bump();
476 }
477 }
478
479 self.error("expected ':'".to_string());
480 false
481 }
482
483 fn parse_rule(&mut self) {
484 self.builder.start_node(RULE.into());
485
486 self.skip_ws();
488 self.builder.start_node(TARGETS.into());
489 let has_target = self.parse_rule_targets();
490 self.builder.finish_node();
491
492 let has_colon = if has_target {
494 self.find_and_consume_colon()
495 } else {
496 false
497 };
498
499 if has_target && has_colon {
501 self.skip_ws();
502 self.parse_rule_dependencies();
503 self.expect_eol();
504
505 self.parse_rule_recipes();
507 }
508
509 self.builder.finish_node();
510 }
511
512 fn parse_rule_targets(&mut self) -> bool {
513 let has_first_target = self.parse_rule_target();
515
516 if !has_first_target {
517 return false;
518 }
519
520 loop {
522 self.skip_ws();
523
524 if self.current() == Some(OPERATOR) && self.tokens.last().unwrap().1 == ":" {
526 break;
527 }
528
529 match self.current() {
531 Some(IDENTIFIER) | Some(DOLLAR) => {
532 if !self.parse_rule_target() {
533 break;
534 }
535 }
536 _ => break,
537 }
538 }
539
540 true
541 }
542
543 fn parse_comment(&mut self) {
544 if self.current() == Some(COMMENT) {
545 self.bump(); if self.current() == Some(NEWLINE) {
549 self.bump(); } else if self.current() == Some(WHITESPACE) {
551 self.skip_ws();
553 if self.current() == Some(NEWLINE) {
554 self.bump();
555 }
556 }
557 } else {
559 self.error("expected comment".to_string());
560 }
561 }
562
563 fn parse_assignment(&mut self) {
564 self.builder.start_node(VARIABLE.into());
565
566 self.skip_ws();
568 if self.current() == Some(IDENTIFIER) && self.tokens.last().unwrap().1 == "export" {
569 self.bump();
570 self.skip_ws();
571 }
572
573 match self.current() {
575 Some(IDENTIFIER) => self.bump(),
576 Some(DOLLAR) => self.parse_variable_reference(),
577 _ => {
578 self.error("expected variable name".to_string());
579 self.builder.finish_node();
580 return;
581 }
582 }
583
584 self.skip_ws();
586 match self.current() {
587 Some(OPERATOR) => {
588 let op = &self.tokens.last().unwrap().1;
589 if ["=", ":=", "::=", ":::=", "+=", "?=", "!="].contains(&op.as_str()) {
590 self.bump();
591 self.skip_ws();
592
593 self.builder.start_node(EXPR.into());
595 while self.current().is_some() && self.current() != Some(NEWLINE) {
596 self.bump();
597 }
598 self.builder.finish_node();
599
600 if self.current() == Some(NEWLINE) {
602 self.bump();
603 } else {
604 self.error("expected newline after variable value".to_string());
605 }
606 } else {
607 self.error(format!("invalid assignment operator: {}", op));
608 }
609 }
610 _ => self.error("expected assignment operator".to_string()),
611 }
612
613 self.builder.finish_node();
614 }
615
616 fn parse_variable_reference(&mut self) {
617 self.builder.start_node(EXPR.into());
618 self.bump(); if self.current() == Some(LPAREN) {
621 self.bump(); let mut is_function = false;
625
626 if self.current() == Some(IDENTIFIER) {
627 let function_name = &self.tokens.last().unwrap().1;
628 let known_functions = [
630 "shell", "wildcard", "call", "eval", "file", "abspath", "dir",
631 ];
632 if known_functions.contains(&function_name.as_str()) {
633 is_function = true;
634 }
635 }
636
637 if is_function {
638 self.bump();
640
641 self.consume_balanced_parens(1);
643 } else {
644 self.parse_parenthesized_expr_internal(true);
646 }
647 } else {
648 self.error("expected ( after $ in variable reference".to_string());
649 }
650
651 self.builder.finish_node();
652 }
653
654 fn parse_parenthesized_expr(&mut self) {
656 self.builder.start_node(EXPR.into());
657
658 if self.current() != Some(LPAREN) {
659 self.error("expected opening parenthesis".to_string());
660 self.builder.finish_node();
661 return;
662 }
663
664 self.bump(); self.parse_parenthesized_expr_internal(false);
666 self.builder.finish_node();
667 }
668
669 fn parse_parenthesized_expr_internal(&mut self, is_variable_ref: bool) {
671 let mut paren_count = 1;
672
673 while paren_count > 0 && self.current().is_some() {
674 match self.current() {
675 Some(LPAREN) => {
676 paren_count += 1;
677 self.bump();
678 self.builder.start_node(EXPR.into());
680 }
681 Some(RPAREN) => {
682 paren_count -= 1;
683 self.bump();
684 if paren_count > 0 {
685 self.builder.finish_node();
686 }
687 }
688 Some(QUOTE) => {
689 self.parse_quoted_string();
691 }
692 Some(DOLLAR) => {
693 self.parse_variable_reference();
695 }
696 Some(_) => self.bump(),
697 None => {
698 self.error(if is_variable_ref {
699 "unclosed variable reference".to_string()
700 } else {
701 "unclosed parenthesis".to_string()
702 });
703 break;
704 }
705 }
706 }
707
708 if !is_variable_ref {
709 self.skip_ws();
710 self.expect_eol();
711 }
712 }
713
714 fn parse_quoted_string(&mut self) {
716 self.bump(); while !self.is_at_eof() && self.current() != Some(QUOTE) {
718 self.bump();
719 }
720 if self.current() == Some(QUOTE) {
721 self.bump();
722 }
723 }
724
725 fn parse_conditional_keyword(&mut self) -> Option<String> {
726 if self.current() != Some(IDENTIFIER) {
727 self.error(
728 "expected conditional keyword (ifdef, ifndef, ifeq, or ifneq)".to_string(),
729 );
730 return None;
731 }
732
733 let token = self.tokens.last().unwrap().1.clone();
734 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&token.as_str()) {
735 self.error(format!("unknown conditional directive: {}", token));
736 return None;
737 }
738
739 self.bump();
740 Some(token)
741 }
742
743 fn parse_simple_condition(&mut self) {
744 self.builder.start_node(EXPR.into());
745
746 self.skip_ws();
748
749 let mut found_var = false;
751
752 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
753 match self.current() {
754 Some(WHITESPACE) => self.skip_ws(),
755 Some(DOLLAR) => {
756 found_var = true;
757 self.parse_variable_reference();
758 }
759 Some(_) => {
760 found_var = true;
762 self.bump();
763 }
764 None => break,
765 }
766 }
767
768 if !found_var {
769 self.error("expected condition after conditional directive".to_string());
771 }
772
773 self.builder.finish_node();
774
775 if self.current() == Some(NEWLINE) {
777 self.bump();
778 } else if !self.is_at_eof() {
779 self.skip_until_newline();
780 }
781 }
782
783 fn is_conditional_directive(&self, token: &str) -> bool {
785 token == "ifdef"
786 || token == "ifndef"
787 || token == "ifeq"
788 || token == "ifneq"
789 || token == "else"
790 || token == "endif"
791 }
792
793 fn handle_conditional_token(&mut self, token: &str, depth: &mut usize) -> bool {
795 match token {
796 "ifdef" | "ifndef" | "ifeq" | "ifneq"
797 if matches!(self.variant, None | Some(MakefileVariant::GNUMake)) =>
798 {
799 *depth += 1;
800 self.parse_conditional();
801 true
802 }
803 "else" => {
804 if *depth == 0 {
806 self.error("else without matching if".to_string());
807 self.bump();
809 false
810 } else {
811 self.builder.start_node(CONDITIONAL_ELSE.into());
813
814 self.bump();
816 self.skip_ws();
817
818 if self.current() == Some(IDENTIFIER) {
820 let next_token = &self.tokens.last().unwrap().1;
821 if next_token == "ifdef"
822 || next_token == "ifndef"
823 || next_token == "ifeq"
824 || next_token == "ifneq"
825 {
826 match next_token.as_str() {
829 "ifdef" | "ifndef" => {
830 self.bump(); self.skip_ws();
832 self.parse_simple_condition();
833 }
834 "ifeq" | "ifneq" => {
835 self.bump(); self.skip_ws();
837 self.parse_parenthesized_expr();
838 }
839 _ => unreachable!(),
840 }
841 } else {
843 }
846 } else {
847 }
849
850 self.builder.finish_node(); true
852 }
853 }
854 "endif" => {
855 if *depth == 0 {
857 self.error("endif without matching if".to_string());
858 self.bump();
860 false
861 } else {
862 *depth -= 1;
863
864 self.builder.start_node(CONDITIONAL_ENDIF.into());
866
867 self.bump();
869
870 self.skip_ws();
872
873 if self.current() == Some(COMMENT) {
878 self.parse_comment();
879 } else if self.current() == Some(NEWLINE) {
880 self.bump();
881 } else if self.current() == Some(WHITESPACE) {
882 self.skip_ws();
884 if self.current() == Some(NEWLINE) {
885 self.bump();
886 }
887 } else if !self.is_at_eof() {
889 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
892 self.bump();
893 }
894 if self.current() == Some(NEWLINE) {
895 self.bump();
896 }
897 }
898 self.builder.finish_node(); true
902 }
903 }
904 _ => false,
905 }
906 }
907
908 fn parse_conditional(&mut self) {
909 self.builder.start_node(CONDITIONAL.into());
910
911 self.builder.start_node(CONDITIONAL_IF.into());
913
914 let Some(token) = self.parse_conditional_keyword() else {
916 self.skip_until_newline();
917 self.builder.finish_node(); self.builder.finish_node(); return;
920 };
921
922 self.skip_ws();
924
925 match token.as_str() {
927 "ifdef" | "ifndef" => {
928 self.parse_simple_condition();
929 }
930 "ifeq" | "ifneq" => {
931 self.parse_parenthesized_expr();
932 }
933 _ => unreachable!("Invalid conditional token"),
934 }
935
936 self.skip_ws();
938 if self.current() == Some(COMMENT) {
939 self.parse_comment();
940 }
941 self.builder.finish_node(); let mut depth = 1;
947
948 let mut position_count = std::collections::HashMap::<usize, usize>::new();
950 let max_repetitions = 15; while depth > 0 && !self.is_at_eof() {
953 let current_pos = self.tokens.len();
955 *position_count.entry(current_pos).or_insert(0) += 1;
956
957 if position_count.get(¤t_pos).unwrap() > &max_repetitions {
960 break;
963 }
964
965 match self.current() {
966 None => {
967 self.error("unterminated conditional (missing endif)".to_string());
968 break;
969 }
970 Some(IDENTIFIER) => {
971 let token = self.tokens.last().unwrap().1.clone();
972 if !self.handle_conditional_token(&token, &mut depth) {
973 if token == "include" || token == "-include" || token == "sinclude" {
974 self.parse_include();
975 } else {
976 self.parse_normal_content();
977 }
978 }
979 }
980 Some(INDENT) => self.parse_recipe_line(),
981 Some(WHITESPACE) => self.bump(),
982 Some(COMMENT) => self.parse_comment(),
983 Some(NEWLINE) => self.bump(),
984 Some(DOLLAR) => self.parse_normal_content(),
985 Some(QUOTE) => self.parse_quoted_string(),
986 Some(_) => {
987 self.bump();
989 }
990 }
991 }
992
993 self.builder.finish_node();
994 }
995
996 fn parse_normal_content(&mut self) {
998 self.skip_ws();
1000
1001 if self.is_assignment_line() {
1003 self.parse_assignment();
1004 } else {
1005 self.parse_rule();
1007 }
1008 }
1009
1010 fn parse_include(&mut self) {
1011 self.builder.start_node(INCLUDE.into());
1012
1013 if self.current() != Some(IDENTIFIER)
1015 || (!["include", "-include", "sinclude"]
1016 .contains(&self.tokens.last().unwrap().1.as_str()))
1017 {
1018 self.error("expected include directive".to_string());
1019 self.builder.finish_node();
1020 return;
1021 }
1022 self.bump();
1023 self.skip_ws();
1024
1025 self.builder.start_node(EXPR.into());
1027 let mut found_path = false;
1028
1029 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1030 match self.current() {
1031 Some(WHITESPACE) => self.skip_ws(),
1032 Some(DOLLAR) => {
1033 found_path = true;
1034 self.parse_variable_reference();
1035 }
1036 Some(_) => {
1037 found_path = true;
1039 self.bump();
1040 }
1041 None => break,
1042 }
1043 }
1044
1045 if !found_path {
1046 self.error("expected file path after include".to_string());
1047 }
1048
1049 self.builder.finish_node();
1050
1051 if self.current() == Some(NEWLINE) {
1053 self.bump();
1054 } else if !self.is_at_eof() {
1055 self.error("expected newline after include".to_string());
1056 self.skip_until_newline();
1057 }
1058
1059 self.builder.finish_node();
1060 }
1061
1062 fn parse_identifier_token(&mut self) -> bool {
1063 let token = &self.tokens.last().unwrap().1;
1064
1065 if token.starts_with("%") {
1067 self.parse_rule();
1068 return true;
1069 }
1070
1071 if token.starts_with("if")
1072 && matches!(self.variant, None | Some(MakefileVariant::GNUMake))
1073 {
1074 self.parse_conditional();
1075 return true;
1076 }
1077
1078 if token == "include" || token == "-include" || token == "sinclude" {
1079 self.parse_include();
1080 return true;
1081 }
1082
1083 self.parse_normal_content();
1085 true
1086 }
1087
1088 fn parse_token(&mut self) -> bool {
1089 match self.current() {
1090 None => false,
1091 Some(IDENTIFIER) => {
1092 let token = &self.tokens.last().unwrap().1;
1093 if self.is_conditional_directive(token)
1094 && matches!(self.variant, None | Some(MakefileVariant::GNUMake))
1095 {
1096 self.parse_conditional();
1097 true
1098 } else {
1099 self.parse_identifier_token()
1100 }
1101 }
1102 Some(DOLLAR) => {
1103 self.parse_normal_content();
1104 true
1105 }
1106 Some(NEWLINE) => {
1107 self.builder.start_node(BLANK_LINE.into());
1108 self.bump();
1109 self.builder.finish_node();
1110 true
1111 }
1112 Some(COMMENT) => {
1113 self.parse_comment();
1114 true
1115 }
1116 Some(WHITESPACE) => {
1117 if self.is_end_of_file_or_newline_after_whitespace() {
1119 self.skip_ws();
1122 return true;
1123 }
1124
1125 let look_ahead_pos = self.tokens.len().saturating_sub(1);
1128 let mut is_documentation_or_help = false;
1129
1130 if look_ahead_pos > 0 {
1131 let next_token = &self.tokens[look_ahead_pos - 1];
1132 if next_token.0 == IDENTIFIER
1135 || next_token.0 == COMMENT
1136 || next_token.0 == TEXT
1137 {
1138 is_documentation_or_help = true;
1139 }
1140 }
1141
1142 if is_documentation_or_help {
1143 self.skip_ws();
1146 while self.current().is_some() && self.current() != Some(NEWLINE) {
1147 self.bump();
1148 }
1149 if self.current() == Some(NEWLINE) {
1150 self.bump();
1151 }
1152 } else {
1153 self.skip_ws();
1154 }
1155 true
1156 }
1157 Some(INDENT) => {
1158 self.bump();
1160
1161 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1163 self.bump();
1164 }
1165 if self.current() == Some(NEWLINE) {
1166 self.bump();
1167 }
1168 true
1169 }
1170 Some(kind) => {
1171 self.error(format!("unexpected token {:?}", kind));
1172 self.bump();
1173 true
1174 }
1175 }
1176 }
1177
1178 fn parse(mut self) -> Parse {
1179 self.builder.start_node(ROOT.into());
1180
1181 while self.parse_token() {}
1182
1183 self.builder.finish_node();
1184
1185 Parse {
1186 green_node: self.builder.finish(),
1187 errors: self.errors,
1188 }
1189 }
1190
1191 fn is_assignment_line(&mut self) -> bool {
1193 let assignment_ops = ["=", ":=", "::=", ":::=", "+=", "?=", "!="];
1194 let mut pos = self.tokens.len().saturating_sub(1);
1195 let mut seen_identifier = false;
1196 let mut seen_export = false;
1197
1198 while pos > 0 {
1199 let (kind, text) = &self.tokens[pos];
1200
1201 match kind {
1202 NEWLINE => break,
1203 IDENTIFIER if text == "export" => seen_export = true,
1204 IDENTIFIER if !seen_identifier => seen_identifier = true,
1205 OPERATOR if assignment_ops.contains(&text.as_str()) => {
1206 return seen_identifier || seen_export
1207 }
1208 OPERATOR if text == ":" => return false, WHITESPACE => (),
1210 _ if seen_export => return true, _ => return false,
1212 }
1213 pos = pos.saturating_sub(1);
1214 }
1215 false
1216 }
1217
1218 fn bump(&mut self) {
1220 let (kind, text) = self.tokens.pop().unwrap();
1221 self.builder.token(kind.into(), text.as_str());
1222 }
1223 fn current(&self) -> Option<SyntaxKind> {
1225 self.tokens.last().map(|(kind, _)| *kind)
1226 }
1227
1228 fn expect_eol(&mut self) {
1229 self.skip_ws();
1231
1232 match self.current() {
1233 Some(NEWLINE) => {
1234 self.bump();
1235 }
1236 None => {
1237 }
1239 n => {
1240 self.error(format!("expected newline, got {:?}", n));
1241 self.skip_until_newline();
1243 }
1244 }
1245 }
1246
1247 fn is_at_eof(&self) -> bool {
1249 self.current().is_none()
1250 }
1251
1252 fn is_at_eof_or_only_whitespace(&self) -> bool {
1254 if self.is_at_eof() {
1255 return true;
1256 }
1257
1258 self.tokens
1260 .iter()
1261 .rev()
1262 .all(|(kind, _)| matches!(*kind, WHITESPACE | NEWLINE))
1263 }
1264
1265 fn skip_ws(&mut self) {
1266 while self.current() == Some(WHITESPACE) {
1267 self.bump()
1268 }
1269 }
1270
1271 fn skip_until_newline(&mut self) {
1272 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1273 self.bump();
1274 }
1275 if self.current() == Some(NEWLINE) {
1276 self.bump();
1277 }
1278 }
1279
1280 fn consume_balanced_parens(&mut self, start_paren_count: usize) -> usize {
1282 let mut paren_count = start_paren_count;
1283
1284 while paren_count > 0 && self.current().is_some() {
1285 match self.current() {
1286 Some(LPAREN) => {
1287 paren_count += 1;
1288 self.bump();
1289 }
1290 Some(RPAREN) => {
1291 paren_count -= 1;
1292 self.bump();
1293 if paren_count == 0 {
1294 break;
1295 }
1296 }
1297 Some(DOLLAR) => {
1298 self.parse_variable_reference();
1300 }
1301 Some(_) => self.bump(),
1302 None => {
1303 self.error("unclosed parenthesis".to_string());
1304 break;
1305 }
1306 }
1307 }
1308
1309 paren_count
1310 }
1311
1312 fn is_end_of_file_or_newline_after_whitespace(&self) -> bool {
1314 if self.is_at_eof_or_only_whitespace() {
1316 return true;
1317 }
1318
1319 if self.tokens.len() <= 1 {
1321 return true;
1322 }
1323
1324 false
1325 }
1326 }
1327
1328 let mut tokens = lex(text);
1329 tokens.reverse();
1330 Parser {
1331 tokens,
1332 builder: GreenNodeBuilder::new(),
1333 errors: Vec::new(),
1334 original_text: text.to_string(),
1335 variant,
1336 }
1337 .parse()
1338}
1339
1340type SyntaxNode = rowan::SyntaxNode<Lang>;
1346#[allow(unused)]
1347type SyntaxToken = rowan::SyntaxToken<Lang>;
1348#[allow(unused)]
1349type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
1350
1351impl Parse {
1352 fn syntax(&self) -> SyntaxNode {
1353 SyntaxNode::new_root_mut(self.green_node.clone())
1354 }
1355
1356 fn root(&self) -> Makefile {
1357 Makefile::cast(self.syntax()).unwrap()
1358 }
1359}
1360
1361macro_rules! ast_node {
1362 ($ast:ident, $kind:ident) => {
1363 #[derive(Clone, PartialEq, Eq, Hash)]
1364 #[repr(transparent)]
1365 pub struct $ast(SyntaxNode);
1367
1368 impl AstNode for $ast {
1369 type Language = Lang;
1370
1371 fn can_cast(kind: SyntaxKind) -> bool {
1372 kind == $kind
1373 }
1374
1375 fn cast(syntax: SyntaxNode) -> Option<Self> {
1376 if Self::can_cast(syntax.kind()) {
1377 Some(Self(syntax))
1378 } else {
1379 None
1380 }
1381 }
1382
1383 fn syntax(&self) -> &SyntaxNode {
1384 &self.0
1385 }
1386 }
1387
1388 impl core::fmt::Display for $ast {
1389 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
1390 write!(f, "{}", self.0.text())
1391 }
1392 }
1393 };
1394}
1395
1396ast_node!(Makefile, ROOT);
1397ast_node!(Rule, RULE);
1398ast_node!(Identifier, IDENTIFIER);
1399ast_node!(VariableDefinition, VARIABLE);
1400ast_node!(Include, INCLUDE);
1401ast_node!(ArchiveMembers, ARCHIVE_MEMBERS);
1402ast_node!(ArchiveMember, ARCHIVE_MEMBER);
1403ast_node!(Conditional, CONDITIONAL);
1404
1405#[derive(Clone)]
1407pub enum MakefileItem {
1408 Rule(Rule),
1410 Variable(VariableDefinition),
1412 Include(Include),
1414 Conditional(Conditional),
1416}
1417
1418impl MakefileItem {
1419 fn cast(node: SyntaxNode) -> Option<Self> {
1421 if let Some(rule) = Rule::cast(node.clone()) {
1422 Some(MakefileItem::Rule(rule))
1423 } else if let Some(var) = VariableDefinition::cast(node.clone()) {
1424 Some(MakefileItem::Variable(var))
1425 } else if let Some(inc) = Include::cast(node.clone()) {
1426 Some(MakefileItem::Include(inc))
1427 } else {
1428 Conditional::cast(node).map(MakefileItem::Conditional)
1429 }
1430 }
1431
1432 fn syntax(&self) -> &SyntaxNode {
1434 match self {
1435 MakefileItem::Rule(r) => r.syntax(),
1436 MakefileItem::Variable(v) => v.syntax(),
1437 MakefileItem::Include(i) => i.syntax(),
1438 MakefileItem::Conditional(c) => c.syntax(),
1439 }
1440 }
1441}
1442
1443#[derive(Clone)]
1445pub enum RuleItem {
1446 Recipe(String),
1448 Conditional(Conditional),
1450}
1451
1452impl std::fmt::Debug for RuleItem {
1453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1454 match self {
1455 RuleItem::Recipe(text) => f.debug_tuple("Recipe").field(text).finish(),
1456 RuleItem::Conditional(_) => f
1457 .debug_tuple("Conditional")
1458 .field(&"<Conditional>")
1459 .finish(),
1460 }
1461 }
1462}
1463
1464impl RuleItem {
1465 fn cast(node: SyntaxNode) -> Option<Self> {
1467 match node.kind() {
1468 RECIPE => {
1469 let text = node.children_with_tokens().find_map(|it| {
1471 if let Some(token) = it.as_token() {
1472 if token.kind() == TEXT {
1473 return Some(token.text().to_string());
1474 }
1475 }
1476 None
1477 })?;
1478 Some(RuleItem::Recipe(text))
1479 }
1480 CONDITIONAL => Conditional::cast(node).map(RuleItem::Conditional),
1481 _ => None,
1482 }
1483 }
1484}
1485
1486impl ArchiveMembers {
1487 pub fn archive_name(&self) -> Option<String> {
1489 for element in self.syntax().children_with_tokens() {
1491 if let Some(token) = element.as_token() {
1492 if token.kind() == IDENTIFIER {
1493 return Some(token.text().to_string());
1494 } else if token.kind() == LPAREN {
1495 break;
1497 }
1498 }
1499 }
1500 None
1501 }
1502
1503 pub fn members(&self) -> impl Iterator<Item = ArchiveMember> + '_ {
1505 self.syntax().children().filter_map(ArchiveMember::cast)
1506 }
1507
1508 pub fn member_names(&self) -> Vec<String> {
1510 self.members().map(|m| m.text()).collect()
1511 }
1512}
1513
1514impl ArchiveMember {
1515 pub fn text(&self) -> String {
1517 self.syntax().text().to_string().trim().to_string()
1518 }
1519}
1520
1521fn trim_trailing_newlines(node: &SyntaxNode) {
1526 let mut newlines_to_remove = vec![];
1528 let mut current = node.last_child_or_token();
1529
1530 while let Some(element) = current {
1531 match &element {
1532 rowan::NodeOrToken::Token(token) if token.kind() == NEWLINE => {
1533 newlines_to_remove.push(token.clone());
1534 current = token.prev_sibling_or_token();
1535 }
1536 rowan::NodeOrToken::Node(n) if n.kind() == RECIPE => {
1537 let mut recipe_current = n.last_child_or_token();
1539 while let Some(recipe_element) = recipe_current {
1540 match &recipe_element {
1541 rowan::NodeOrToken::Token(token) if token.kind() == NEWLINE => {
1542 newlines_to_remove.push(token.clone());
1543 recipe_current = token.prev_sibling_or_token();
1544 }
1545 _ => break,
1546 }
1547 }
1548 break; }
1550 _ => break,
1551 }
1552 }
1553
1554 if newlines_to_remove.len() > 1 {
1557 newlines_to_remove.sort_by_key(|t| std::cmp::Reverse(t.index()));
1559
1560 for token in newlines_to_remove.iter().take(newlines_to_remove.len() - 1) {
1561 let parent = token.parent().unwrap();
1562 let idx = token.index();
1563 parent.splice_children(idx..idx + 1, vec![]);
1564 }
1565 }
1566}
1567
1568fn remove_with_preceding_comments(node: &SyntaxNode, parent: &SyntaxNode) {
1576 let mut collected_elements = vec![];
1577 let mut found_comment = false;
1578
1579 let mut current = node.prev_sibling_or_token();
1581 while let Some(element) = current {
1582 match &element {
1583 rowan::NodeOrToken::Token(token) => match token.kind() {
1584 COMMENT => {
1585 if token.text().starts_with("#!") {
1586 break; }
1588 found_comment = true;
1589 collected_elements.push(element.clone());
1590 }
1591 NEWLINE | WHITESPACE => {
1592 collected_elements.push(element.clone());
1593 }
1594 _ => break, },
1596 rowan::NodeOrToken::Node(n) => {
1597 if n.kind() == BLANK_LINE {
1599 collected_elements.push(element.clone());
1600 } else {
1601 break; }
1603 }
1604 }
1605 current = element.prev_sibling_or_token();
1606 }
1607
1608 let mut elements_to_remove = vec![];
1611 let mut consecutive_newlines = 0;
1612 for element in collected_elements.iter().rev() {
1613 let should_remove = match element {
1614 rowan::NodeOrToken::Token(token) => match token.kind() {
1615 COMMENT => {
1616 consecutive_newlines = 0;
1617 found_comment
1618 }
1619 NEWLINE => {
1620 consecutive_newlines += 1;
1621 found_comment && consecutive_newlines <= 1
1622 }
1623 WHITESPACE => found_comment,
1624 _ => false,
1625 },
1626 rowan::NodeOrToken::Node(n) => {
1627 if n.kind() == BLANK_LINE {
1629 consecutive_newlines += 1;
1630 found_comment && consecutive_newlines <= 1
1631 } else {
1632 false
1633 }
1634 }
1635 };
1636
1637 if should_remove {
1638 elements_to_remove.push(element.clone());
1639 }
1640 }
1641
1642 let mut all_to_remove = vec![rowan::NodeOrToken::Node(node.clone())];
1645 all_to_remove.extend(elements_to_remove.into_iter().rev());
1646
1647 all_to_remove.sort_by_key(|el| std::cmp::Reverse(el.index()));
1649
1650 for element in all_to_remove {
1651 let idx = element.index();
1652 parent.splice_children(idx..idx + 1, vec![]);
1653 }
1654}
1655
1656impl VariableDefinition {
1657 pub fn name(&self) -> Option<String> {
1659 self.syntax().children_with_tokens().find_map(|it| {
1660 it.as_token().and_then(|it| {
1661 if it.kind() == IDENTIFIER && it.text() != "export" {
1662 Some(it.text().to_string())
1663 } else {
1664 None
1665 }
1666 })
1667 })
1668 }
1669
1670 pub fn is_export(&self) -> bool {
1672 self.syntax()
1673 .children_with_tokens()
1674 .any(|it| it.as_token().is_some_and(|token| token.text() == "export"))
1675 }
1676
1677 pub fn raw_value(&self) -> Option<String> {
1679 self.syntax()
1680 .children()
1681 .find(|it| it.kind() == EXPR)
1682 .map(|it| it.text().into())
1683 }
1684
1685 pub fn parent(&self) -> Option<MakefileItem> {
1704 self.syntax().parent().and_then(MakefileItem::cast)
1705 }
1706
1707 pub fn remove(&mut self) {
1720 if let Some(parent) = self.syntax().parent() {
1721 remove_with_preceding_comments(self.syntax(), &parent);
1722 }
1723 }
1724
1725 pub fn set_value(&mut self, new_value: &str) {
1738 let expr_index = self
1740 .syntax()
1741 .children()
1742 .find(|it| it.kind() == EXPR)
1743 .map(|it| it.index());
1744
1745 if let Some(expr_idx) = expr_index {
1746 let mut builder = GreenNodeBuilder::new();
1748 builder.start_node(EXPR.into());
1749 builder.token(IDENTIFIER.into(), new_value);
1750 builder.finish_node();
1751
1752 let new_expr = SyntaxNode::new_root_mut(builder.finish());
1753
1754 self.0
1756 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
1757 }
1758 }
1759}
1760
1761impl Makefile {
1762 pub fn new() -> Makefile {
1764 let mut builder = GreenNodeBuilder::new();
1765
1766 builder.start_node(ROOT.into());
1767 builder.finish_node();
1768
1769 let syntax = SyntaxNode::new_root_mut(builder.finish());
1770 Makefile(syntax)
1771 }
1772
1773 pub fn parse(text: &str) -> crate::Parse<Makefile> {
1775 crate::Parse::<Makefile>::parse_makefile(text)
1776 }
1777
1778 pub fn code(&self) -> String {
1780 self.syntax().text().to_string()
1781 }
1782
1783 pub fn is_root(&self) -> bool {
1785 self.syntax().kind() == ROOT
1786 }
1787
1788 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1790 let mut buf = String::new();
1791 r.read_to_string(&mut buf)?;
1792 buf.parse()
1793 }
1794
1795 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1797 let mut buf = String::new();
1798 r.read_to_string(&mut buf)?;
1799
1800 let parsed = parse(&buf, None);
1801 Ok(parsed.root())
1802 }
1803
1804 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
1813 self.syntax().children().filter_map(Rule::cast)
1814 }
1815
1816 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1818 self.rules()
1819 .filter(move |rule| rule.targets().any(|t| t == target))
1820 }
1821
1822 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
1824 self.syntax()
1825 .children()
1826 .filter_map(VariableDefinition::cast)
1827 }
1828
1829 pub fn conditionals(&self) -> impl Iterator<Item = Conditional> + '_ {
1831 self.syntax().children().filter_map(Conditional::cast)
1832 }
1833
1834 pub fn items(&self) -> impl Iterator<Item = MakefileItem> + '_ {
1850 self.syntax().children().filter_map(MakefileItem::cast)
1851 }
1852
1853 pub fn find_variable<'a>(
1868 &'a self,
1869 name: &'a str,
1870 ) -> impl Iterator<Item = VariableDefinition> + 'a {
1871 self.variable_definitions()
1872 .filter(move |var| var.name().as_deref() == Some(name))
1873 }
1874
1875 pub fn add_rule(&mut self, target: &str) -> Rule {
1885 let mut builder = GreenNodeBuilder::new();
1886 builder.start_node(RULE.into());
1887 builder.token(IDENTIFIER.into(), target);
1888 builder.token(OPERATOR.into(), ":");
1889 builder.token(NEWLINE.into(), "\n");
1890 builder.finish_node();
1891
1892 let syntax = SyntaxNode::new_root_mut(builder.finish());
1893 let pos = self.0.children_with_tokens().count();
1894
1895 let needs_blank_line = self.0.children().any(|c| c.kind() == RULE);
1898
1899 if needs_blank_line {
1900 let mut bl_builder = GreenNodeBuilder::new();
1902 bl_builder.start_node(BLANK_LINE.into());
1903 bl_builder.token(NEWLINE.into(), "\n");
1904 bl_builder.finish_node();
1905 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
1906
1907 self.0
1908 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
1909 } else {
1910 self.0.splice_children(pos..pos, vec![syntax.into()]);
1911 }
1912
1913 Rule(self.0.children().last().unwrap())
1916 }
1917
1918 pub fn add_conditional(
1934 &mut self,
1935 conditional_type: &str,
1936 condition: &str,
1937 if_body: &str,
1938 else_body: Option<&str>,
1939 ) -> Result<Conditional, Error> {
1940 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
1942 return Err(Error::Parse(ParseError {
1943 errors: vec![ErrorInfo {
1944 message: format!(
1945 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
1946 conditional_type
1947 ),
1948 line: 1,
1949 context: "add_conditional".to_string(),
1950 }],
1951 }));
1952 }
1953
1954 let mut builder = GreenNodeBuilder::new();
1955 builder.start_node(CONDITIONAL.into());
1956
1957 builder.start_node(CONDITIONAL_IF.into());
1959 builder.token(IDENTIFIER.into(), conditional_type);
1960 builder.token(WHITESPACE.into(), " ");
1961
1962 builder.start_node(EXPR.into());
1964 builder.token(IDENTIFIER.into(), condition);
1965 builder.finish_node();
1966
1967 builder.token(NEWLINE.into(), "\n");
1968 builder.finish_node();
1969
1970 if !if_body.is_empty() {
1972 for line in if_body.lines() {
1973 if !line.is_empty() {
1974 builder.token(IDENTIFIER.into(), line);
1975 }
1976 builder.token(NEWLINE.into(), "\n");
1977 }
1978 if !if_body.ends_with('\n') && !if_body.is_empty() {
1980 builder.token(NEWLINE.into(), "\n");
1981 }
1982 }
1983
1984 if let Some(else_content) = else_body {
1986 builder.start_node(CONDITIONAL_ELSE.into());
1987 builder.token(IDENTIFIER.into(), "else");
1988 builder.token(NEWLINE.into(), "\n");
1989 builder.finish_node();
1990
1991 if !else_content.is_empty() {
1993 for line in else_content.lines() {
1994 if !line.is_empty() {
1995 builder.token(IDENTIFIER.into(), line);
1996 }
1997 builder.token(NEWLINE.into(), "\n");
1998 }
1999 if !else_content.ends_with('\n') && !else_content.is_empty() {
2001 builder.token(NEWLINE.into(), "\n");
2002 }
2003 }
2004 }
2005
2006 builder.start_node(CONDITIONAL_ENDIF.into());
2008 builder.token(IDENTIFIER.into(), "endif");
2009 builder.token(NEWLINE.into(), "\n");
2010 builder.finish_node();
2011
2012 builder.finish_node();
2013
2014 let syntax = SyntaxNode::new_root_mut(builder.finish());
2015 let pos = self.0.children_with_tokens().count();
2016
2017 let needs_blank_line = self
2019 .0
2020 .children()
2021 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
2022
2023 if needs_blank_line {
2024 let mut bl_builder = GreenNodeBuilder::new();
2026 bl_builder.start_node(BLANK_LINE.into());
2027 bl_builder.token(NEWLINE.into(), "\n");
2028 bl_builder.finish_node();
2029 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
2030
2031 self.0
2032 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
2033 } else {
2034 self.0.splice_children(pos..pos, vec![syntax.into()]);
2035 }
2036
2037 Ok(Conditional(self.0.children().last().unwrap()))
2039 }
2040
2041 pub fn add_conditional_with_items<I1, I2>(
2071 &mut self,
2072 conditional_type: &str,
2073 condition: &str,
2074 if_items: I1,
2075 else_items: Option<I2>,
2076 ) -> Result<Conditional, Error>
2077 where
2078 I1: IntoIterator<Item = MakefileItem>,
2079 I2: IntoIterator<Item = MakefileItem>,
2080 {
2081 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&conditional_type) {
2083 return Err(Error::Parse(ParseError {
2084 errors: vec![ErrorInfo {
2085 message: format!(
2086 "Invalid conditional type: {}. Must be one of: ifdef, ifndef, ifeq, ifneq",
2087 conditional_type
2088 ),
2089 line: 1,
2090 context: "add_conditional_with_items".to_string(),
2091 }],
2092 }));
2093 }
2094
2095 let mut builder = GreenNodeBuilder::new();
2096 builder.start_node(CONDITIONAL.into());
2097
2098 builder.start_node(CONDITIONAL_IF.into());
2100 builder.token(IDENTIFIER.into(), conditional_type);
2101 builder.token(WHITESPACE.into(), " ");
2102
2103 builder.start_node(EXPR.into());
2105 builder.token(IDENTIFIER.into(), condition);
2106 builder.finish_node();
2107
2108 builder.token(NEWLINE.into(), "\n");
2109 builder.finish_node();
2110
2111 for item in if_items {
2113 let item_text = item.syntax().to_string();
2115 builder.token(IDENTIFIER.into(), item_text.trim());
2117 builder.token(NEWLINE.into(), "\n");
2118 }
2119
2120 if let Some(else_iter) = else_items {
2122 builder.start_node(CONDITIONAL_ELSE.into());
2123 builder.token(IDENTIFIER.into(), "else");
2124 builder.token(NEWLINE.into(), "\n");
2125 builder.finish_node();
2126
2127 for item in else_iter {
2129 let item_text = item.syntax().to_string();
2130 builder.token(IDENTIFIER.into(), item_text.trim());
2131 builder.token(NEWLINE.into(), "\n");
2132 }
2133 }
2134
2135 builder.start_node(CONDITIONAL_ENDIF.into());
2137 builder.token(IDENTIFIER.into(), "endif");
2138 builder.token(NEWLINE.into(), "\n");
2139 builder.finish_node();
2140
2141 builder.finish_node();
2142
2143 let syntax = SyntaxNode::new_root_mut(builder.finish());
2144 let pos = self.0.children_with_tokens().count();
2145
2146 let needs_blank_line = self
2148 .0
2149 .children()
2150 .any(|c| c.kind() == RULE || c.kind() == VARIABLE || c.kind() == CONDITIONAL);
2151
2152 if needs_blank_line {
2153 let mut bl_builder = GreenNodeBuilder::new();
2155 bl_builder.start_node(BLANK_LINE.into());
2156 bl_builder.token(NEWLINE.into(), "\n");
2157 bl_builder.finish_node();
2158 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
2159
2160 self.0
2161 .splice_children(pos..pos, vec![blank_line.into(), syntax.into()]);
2162 } else {
2163 self.0.splice_children(pos..pos, vec![syntax.into()]);
2164 }
2165
2166 Ok(Conditional(self.0.children().last().unwrap()))
2168 }
2169
2170 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
2172 let mut buf = String::new();
2173 r.read_to_string(&mut buf)?;
2174
2175 let parsed = parse(&buf, None);
2176 if !parsed.errors.is_empty() {
2177 Err(Error::Parse(ParseError {
2178 errors: parsed.errors,
2179 }))
2180 } else {
2181 Ok(parsed.root())
2182 }
2183 }
2184
2185 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
2196 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
2197
2198 if rules.is_empty() {
2199 return Err(Error::Parse(ParseError {
2200 errors: vec![ErrorInfo {
2201 message: "Cannot replace rule in empty makefile".to_string(),
2202 line: 1,
2203 context: "replace_rule".to_string(),
2204 }],
2205 }));
2206 }
2207
2208 if index >= rules.len() {
2209 return Err(Error::Parse(ParseError {
2210 errors: vec![ErrorInfo {
2211 message: format!(
2212 "Rule index {} out of bounds (max {})",
2213 index,
2214 rules.len() - 1
2215 ),
2216 line: 1,
2217 context: "replace_rule".to_string(),
2218 }],
2219 }));
2220 }
2221
2222 let target_node = &rules[index];
2223 let target_index = target_node.index();
2224
2225 self.0.splice_children(
2227 target_index..target_index + 1,
2228 vec![new_rule.0.clone().into()],
2229 );
2230 Ok(())
2231 }
2232
2233 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
2244 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
2245
2246 if rules.is_empty() {
2247 return Err(Error::Parse(ParseError {
2248 errors: vec![ErrorInfo {
2249 message: "Cannot remove rule from empty makefile".to_string(),
2250 line: 1,
2251 context: "remove_rule".to_string(),
2252 }],
2253 }));
2254 }
2255
2256 if index >= rules.len() {
2257 return Err(Error::Parse(ParseError {
2258 errors: vec![ErrorInfo {
2259 message: format!(
2260 "Rule index {} out of bounds (max {})",
2261 index,
2262 rules.len() - 1
2263 ),
2264 line: 1,
2265 context: "remove_rule".to_string(),
2266 }],
2267 }));
2268 }
2269
2270 let target_node = rules[index].clone();
2271 let target_index = target_node.index();
2272
2273 self.0
2275 .splice_children(target_index..target_index + 1, vec![]);
2276 Ok(Rule(target_node))
2277 }
2278
2279 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
2291 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
2292
2293 if index > rules.len() {
2294 return Err(Error::Parse(ParseError {
2295 errors: vec![ErrorInfo {
2296 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
2297 line: 1,
2298 context: "insert_rule".to_string(),
2299 }],
2300 }));
2301 }
2302
2303 let target_index = if index == rules.len() {
2304 self.0.children_with_tokens().count()
2306 } else {
2307 rules[index].index()
2309 };
2310
2311 let mut nodes_to_insert = Vec::new();
2313
2314 if index == 0 && !rules.is_empty() {
2316 nodes_to_insert.push(new_rule.0.clone().into());
2320
2321 let mut bl_builder = GreenNodeBuilder::new();
2323 bl_builder.start_node(BLANK_LINE.into());
2324 bl_builder.token(NEWLINE.into(), "\n");
2325 bl_builder.finish_node();
2326 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
2327 nodes_to_insert.push(blank_line.into());
2328 } else if index < rules.len() {
2329 let has_blank_before = if target_index > 0 {
2342 self.0
2343 .children_with_tokens()
2344 .nth(target_index - 1)
2345 .and_then(|n| n.as_node().map(|node| node.kind() == BLANK_LINE))
2346 .unwrap_or(false)
2347 } else {
2348 false
2349 };
2350
2351 if !has_blank_before && index > 0 {
2353 let mut bl_builder = GreenNodeBuilder::new();
2354 bl_builder.start_node(BLANK_LINE.into());
2355 bl_builder.token(NEWLINE.into(), "\n");
2356 bl_builder.finish_node();
2357 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
2358 nodes_to_insert.push(blank_line.into());
2359 }
2360
2361 nodes_to_insert.push(new_rule.0.clone().into());
2363
2364 let mut bl_builder = GreenNodeBuilder::new();
2366 bl_builder.start_node(BLANK_LINE.into());
2367 bl_builder.token(NEWLINE.into(), "\n");
2368 bl_builder.finish_node();
2369 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
2370 nodes_to_insert.push(blank_line.into());
2371 } else {
2372 let mut bl_builder = GreenNodeBuilder::new();
2375 bl_builder.start_node(BLANK_LINE.into());
2376 bl_builder.token(NEWLINE.into(), "\n");
2377 bl_builder.finish_node();
2378 let blank_line = SyntaxNode::new_root_mut(bl_builder.finish());
2379 nodes_to_insert.push(blank_line.into());
2380
2381 nodes_to_insert.push(new_rule.0.clone().into());
2383 }
2384
2385 self.0
2387 .splice_children(target_index..target_index, nodes_to_insert);
2388 Ok(())
2389 }
2390
2391 pub fn includes(&self) -> impl Iterator<Item = Include> {
2401 self.syntax().children().filter_map(Include::cast)
2402 }
2403
2404 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
2414 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
2417 let mut includes = Vec::new();
2418
2419 if let Some(include) = Include::cast(node.clone()) {
2421 includes.push(include);
2422 }
2423
2424 for child in node.children() {
2426 includes.extend(collect_includes(&child));
2427 }
2428
2429 includes
2430 }
2431
2432 let includes = collect_includes(self.syntax());
2434
2435 includes.into_iter().map(|include| {
2437 include
2438 .syntax()
2439 .children()
2440 .find(|node| node.kind() == EXPR)
2441 .map(|expr| expr.text().to_string().trim().to_string())
2442 .unwrap_or_default()
2443 })
2444 }
2445
2446 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
2457 self.rules()
2458 .find(|rule| rule.targets().any(|t| t == target))
2459 }
2460
2461 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
2471 self.rules_by_target(target)
2472 }
2473
2474 pub fn find_rule_by_target_pattern(&self, target: &str) -> Option<Rule> {
2487 self.rules()
2488 .find(|rule| rule.targets().any(|t| matches_pattern(&t, target)))
2489 }
2490
2491 pub fn find_rules_by_target_pattern<'a>(
2504 &'a self,
2505 target: &'a str,
2506 ) -> impl Iterator<Item = Rule> + 'a {
2507 self.rules()
2508 .filter(move |rule| rule.targets().any(|t| matches_pattern(&t, target)))
2509 }
2510
2511 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
2521 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
2523 if !phony_rule.prerequisites().any(|p| p == target) {
2525 phony_rule.add_prerequisite(target)?;
2526 }
2527 } else {
2528 let mut phony_rule = self.add_rule(".PHONY");
2530 phony_rule.add_prerequisite(target)?;
2531 }
2532 Ok(())
2533 }
2534
2535 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
2549 let mut phony_rule = None;
2551 for rule in self.rules_by_target(".PHONY") {
2552 if rule.prerequisites().any(|p| p == target) {
2553 phony_rule = Some(rule);
2554 break;
2555 }
2556 }
2557
2558 let mut phony_rule = match phony_rule {
2559 Some(rule) => rule,
2560 None => return Ok(false),
2561 };
2562
2563 let prereq_count = phony_rule.prerequisites().count();
2565
2566 phony_rule.remove_prerequisite(target)?;
2568
2569 if prereq_count == 1 {
2571 phony_rule.remove()?;
2573 }
2574
2575 Ok(true)
2576 }
2577
2578 pub fn is_phony(&self, target: &str) -> bool {
2589 self.rules_by_target(".PHONY")
2591 .any(|rule| rule.prerequisites().any(|p| p == target))
2592 }
2593
2594 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
2604 self.rules_by_target(".PHONY")
2606 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
2607 }
2608}
2609
2610impl FromStr for Rule {
2611 type Err = crate::Error;
2612
2613 fn from_str(s: &str) -> Result<Self, Self::Err> {
2614 Rule::parse(s).to_rule_result()
2615 }
2616}
2617
2618impl FromStr for Makefile {
2619 type Err = crate::Error;
2620
2621 fn from_str(s: &str) -> Result<Self, Self::Err> {
2622 Makefile::parse(s).to_result()
2623 }
2624}
2625
2626fn build_prerequisites_node(prereqs: &[String], include_leading_space: bool) -> SyntaxNode {
2628 let mut builder = GreenNodeBuilder::new();
2629 builder.start_node(PREREQUISITES.into());
2630
2631 for (i, prereq) in prereqs.iter().enumerate() {
2632 if (i == 0 && include_leading_space) || i > 0 {
2634 builder.token(WHITESPACE.into(), " ");
2635 }
2636
2637 builder.start_node(PREREQUISITE.into());
2639 builder.token(IDENTIFIER.into(), prereq);
2640 builder.finish_node();
2641 }
2642
2643 builder.finish_node();
2644 SyntaxNode::new_root_mut(builder.finish())
2645}
2646
2647fn build_targets_node(targets: &[String]) -> SyntaxNode {
2649 let mut builder = GreenNodeBuilder::new();
2650 builder.start_node(TARGETS.into());
2651
2652 for (i, target) in targets.iter().enumerate() {
2653 if i > 0 {
2654 builder.token(WHITESPACE.into(), " ");
2655 }
2656 builder.token(IDENTIFIER.into(), target);
2657 }
2658
2659 builder.finish_node();
2660 SyntaxNode::new_root_mut(builder.finish())
2661}
2662
2663impl Rule {
2664 pub fn parse(text: &str) -> crate::Parse<Rule> {
2666 crate::Parse::<Rule>::parse_rule(text)
2667 }
2668
2669 pub fn new(targets: &[&str], prerequisites: &[&str], recipes: &[&str]) -> Rule {
2686 let mut builder = GreenNodeBuilder::new();
2687 builder.start_node(RULE.into());
2688
2689 for (i, target) in targets.iter().enumerate() {
2691 if i > 0 {
2692 builder.token(WHITESPACE.into(), " ");
2693 }
2694 builder.token(IDENTIFIER.into(), target);
2695 }
2696
2697 builder.token(OPERATOR.into(), ":");
2699
2700 if !prerequisites.is_empty() {
2702 builder.token(WHITESPACE.into(), " ");
2703 builder.start_node(PREREQUISITES.into());
2704
2705 for (i, prereq) in prerequisites.iter().enumerate() {
2706 if i > 0 {
2707 builder.token(WHITESPACE.into(), " ");
2708 }
2709 builder.start_node(PREREQUISITE.into());
2710 builder.token(IDENTIFIER.into(), prereq);
2711 builder.finish_node();
2712 }
2713
2714 builder.finish_node();
2715 }
2716
2717 builder.token(NEWLINE.into(), "\n");
2719
2720 for recipe in recipes {
2722 builder.start_node(RECIPE.into());
2723 builder.token(INDENT.into(), "\t");
2724 builder.token(TEXT.into(), recipe);
2725 builder.token(NEWLINE.into(), "\n");
2726 builder.finish_node();
2727 }
2728
2729 builder.finish_node();
2730
2731 let syntax = SyntaxNode::new_root_mut(builder.finish());
2732 Rule(syntax)
2733 }
2734
2735 pub fn parent(&self) -> Option<MakefileItem> {
2756 self.syntax().parent().and_then(MakefileItem::cast)
2757 }
2758
2759 fn collect_variable_reference(
2761 &self,
2762 tokens: &mut std::iter::Peekable<impl Iterator<Item = SyntaxElement>>,
2763 ) -> Option<String> {
2764 let mut var_ref = String::new();
2765
2766 if let Some(token) = tokens.next() {
2768 if let Some(t) = token.as_token() {
2769 if t.kind() == DOLLAR {
2770 var_ref.push_str(t.text());
2771
2772 if let Some(next) = tokens.peek() {
2774 if let Some(nt) = next.as_token() {
2775 if nt.kind() == LPAREN {
2776 var_ref.push_str(nt.text());
2778 tokens.next();
2779
2780 let mut paren_count = 1;
2782
2783 for next_token in tokens.by_ref() {
2785 if let Some(nt) = next_token.as_token() {
2786 var_ref.push_str(nt.text());
2787
2788 if nt.kind() == LPAREN {
2789 paren_count += 1;
2790 } else if nt.kind() == RPAREN {
2791 paren_count -= 1;
2792 if paren_count == 0 {
2793 break;
2794 }
2795 }
2796 }
2797 }
2798
2799 return Some(var_ref);
2800 }
2801 }
2802 }
2803
2804 for next_token in tokens.by_ref() {
2806 if let Some(nt) = next_token.as_token() {
2807 var_ref.push_str(nt.text());
2808 if nt.kind() == RPAREN {
2809 break;
2810 }
2811 }
2812 }
2813 return Some(var_ref);
2814 }
2815 }
2816 }
2817
2818 None
2819 }
2820
2821 fn extract_targets_from_node(node: &SyntaxNode) -> Vec<String> {
2823 let mut result = Vec::new();
2824 let mut current_target = String::new();
2825 let mut in_parens = 0;
2826
2827 for child in node.children_with_tokens() {
2828 if let Some(token) = child.as_token() {
2829 match token.kind() {
2830 IDENTIFIER => {
2831 current_target.push_str(token.text());
2832 }
2833 WHITESPACE => {
2834 if in_parens == 0 && !current_target.is_empty() {
2836 result.push(current_target.clone());
2837 current_target.clear();
2838 } else if in_parens > 0 {
2839 current_target.push_str(token.text());
2840 }
2841 }
2842 LPAREN => {
2843 in_parens += 1;
2844 current_target.push_str(token.text());
2845 }
2846 RPAREN => {
2847 in_parens -= 1;
2848 current_target.push_str(token.text());
2849 }
2850 DOLLAR => {
2851 current_target.push_str(token.text());
2852 }
2853 _ => {
2854 current_target.push_str(token.text());
2855 }
2856 }
2857 } else if let Some(child_node) = child.as_node() {
2858 current_target.push_str(&child_node.text().to_string());
2860 }
2861 }
2862
2863 if !current_target.is_empty() {
2865 result.push(current_target);
2866 }
2867
2868 result
2869 }
2870
2871 pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
2881 for child in self.syntax().children_with_tokens() {
2883 if let Some(node) = child.as_node() {
2884 if node.kind() == TARGETS {
2885 return Self::extract_targets_from_node(node).into_iter();
2887 }
2888 }
2889 if let Some(token) = child.as_token() {
2891 if token.kind() == OPERATOR {
2892 break;
2893 }
2894 }
2895 }
2896
2897 let mut result = Vec::new();
2899 let mut tokens = self
2900 .syntax()
2901 .children_with_tokens()
2902 .take_while(|it| it.as_token().map(|t| t.kind() != OPERATOR).unwrap_or(true))
2903 .peekable();
2904
2905 while let Some(token) = tokens.peek().cloned() {
2906 if let Some(node) = token.as_node() {
2907 tokens.next(); if node.kind() == EXPR {
2909 let mut var_content = String::new();
2911 for child in node.children_with_tokens() {
2912 if let Some(t) = child.as_token() {
2913 var_content.push_str(t.text());
2914 }
2915 }
2916 if !var_content.is_empty() {
2917 result.push(var_content);
2918 }
2919 }
2920 } else if let Some(t) = token.as_token() {
2921 if t.kind() == DOLLAR {
2922 if let Some(var_ref) = self.collect_variable_reference(&mut tokens) {
2923 result.push(var_ref);
2924 }
2925 } else if t.kind() == IDENTIFIER {
2926 let ident_text = t.text().to_string();
2928 tokens.next(); if let Some(next) = tokens.peek() {
2932 if let Some(next_token) = next.as_token() {
2933 if next_token.kind() == LPAREN {
2934 let mut archive_target = ident_text;
2936 archive_target.push_str(next_token.text()); tokens.next(); while let Some(token) = tokens.peek() {
2941 if let Some(node) = token.as_node() {
2942 if node.kind() == ARCHIVE_MEMBERS {
2943 archive_target.push_str(&node.text().to_string());
2944 tokens.next();
2945 } else {
2946 tokens.next();
2947 }
2948 } else if let Some(t) = token.as_token() {
2949 if t.kind() == RPAREN {
2950 archive_target.push_str(t.text());
2951 tokens.next();
2952 break;
2953 } else {
2954 tokens.next();
2955 }
2956 } else {
2957 break;
2958 }
2959 }
2960 result.push(archive_target);
2961 } else {
2962 result.push(ident_text);
2964 }
2965 } else {
2966 result.push(ident_text);
2968 }
2969 } else {
2970 result.push(ident_text);
2972 }
2973 } else {
2974 tokens.next(); }
2976 }
2977 }
2978 result.into_iter()
2979 }
2980
2981 pub fn prerequisites(&self) -> impl Iterator<Item = String> + '_ {
2990 let mut found_operator = false;
2992 let mut prerequisites_node = None;
2993
2994 for element in self.syntax().children_with_tokens() {
2995 if let Some(token) = element.as_token() {
2996 if token.kind() == OPERATOR {
2997 found_operator = true;
2998 }
2999 } else if let Some(node) = element.as_node() {
3000 if found_operator && node.kind() == PREREQUISITES {
3001 prerequisites_node = Some(node.clone());
3002 break;
3003 }
3004 }
3005 }
3006
3007 let result: Vec<String> = if let Some(prereqs) = prerequisites_node {
3008 prereqs
3010 .children()
3011 .filter(|child| child.kind() == PREREQUISITE)
3012 .map(|child| child.text().to_string().trim().to_string())
3013 .collect()
3014 } else {
3015 Vec::new()
3016 };
3017
3018 result.into_iter()
3019 }
3020
3021 pub fn recipes(&self) -> impl Iterator<Item = String> {
3030 self.syntax()
3031 .children()
3032 .filter(|it| it.kind() == RECIPE)
3033 .flat_map(|it| {
3034 it.children_with_tokens().filter_map(|it| {
3035 it.as_token().and_then(|t| {
3036 if t.kind() == TEXT {
3037 Some(t.text().to_string())
3038 } else {
3039 None
3040 }
3041 })
3042 })
3043 })
3044 }
3045
3046 pub fn items(&self) -> impl Iterator<Item = RuleItem> + '_ {
3083 self.syntax()
3084 .children()
3085 .filter(|n| n.kind() == RECIPE || n.kind() == CONDITIONAL)
3086 .filter_map(RuleItem::cast)
3087 }
3088
3089 pub fn replace_command(&mut self, i: usize, line: &str) -> bool {
3099 let recipes: Vec<_> = self
3102 .syntax()
3103 .children()
3104 .filter(|n| {
3105 n.kind() == RECIPE
3106 && n.children_with_tokens()
3107 .any(|t| t.as_token().map(|t| t.kind() == TEXT).unwrap_or(false))
3108 })
3109 .collect();
3110
3111 if i >= recipes.len() {
3112 return false;
3113 }
3114
3115 let target_node = &recipes[i];
3117 let target_index = target_node.index();
3118
3119 let mut builder = GreenNodeBuilder::new();
3120 builder.start_node(RECIPE.into());
3121 builder.token(INDENT.into(), "\t");
3122 builder.token(TEXT.into(), line);
3123 builder.token(NEWLINE.into(), "\n");
3124 builder.finish_node();
3125
3126 let syntax = SyntaxNode::new_root_mut(builder.finish());
3127
3128 self.0
3129 .splice_children(target_index..target_index + 1, vec![syntax.into()]);
3130
3131 true
3132 }
3133
3134 pub fn push_command(&mut self, line: &str) {
3144 let index = self
3146 .0
3147 .children_with_tokens()
3148 .filter(|it| it.kind() == RECIPE)
3149 .last();
3150
3151 let index = index.map_or_else(
3152 || self.0.children_with_tokens().count(),
3153 |it| it.index() + 1,
3154 );
3155
3156 let mut builder = GreenNodeBuilder::new();
3157 builder.start_node(RECIPE.into());
3158 builder.token(INDENT.into(), "\t");
3159 builder.token(TEXT.into(), line);
3160 builder.token(NEWLINE.into(), "\n");
3161 builder.finish_node();
3162 let syntax = SyntaxNode::new_root_mut(builder.finish());
3163
3164 self.0.splice_children(index..index, vec![syntax.into()]);
3165 }
3166
3167 pub fn remove_command(&mut self, index: usize) -> bool {
3177 let recipes: Vec<_> = self
3178 .syntax()
3179 .children()
3180 .filter(|n| n.kind() == RECIPE)
3181 .collect();
3182
3183 if index >= recipes.len() {
3184 return false;
3185 }
3186
3187 let target_node = &recipes[index];
3188 let target_index = target_node.index();
3189
3190 self.0
3191 .splice_children(target_index..target_index + 1, vec![]);
3192 true
3193 }
3194
3195 pub fn insert_command(&mut self, index: usize, line: &str) -> bool {
3206 let recipes: Vec<_> = self
3207 .syntax()
3208 .children()
3209 .filter(|n| n.kind() == RECIPE)
3210 .collect();
3211
3212 if index > recipes.len() {
3213 return false;
3214 }
3215
3216 let target_index = if index == recipes.len() {
3217 recipes.last().map(|n| n.index() + 1).unwrap_or_else(|| {
3219 self.0.children_with_tokens().count()
3221 })
3222 } else {
3223 recipes[index].index()
3225 };
3226
3227 let mut builder = GreenNodeBuilder::new();
3228 builder.start_node(RECIPE.into());
3229 builder.token(INDENT.into(), "\t");
3230 builder.token(TEXT.into(), line);
3231 builder.token(NEWLINE.into(), "\n");
3232 builder.finish_node();
3233 let syntax = SyntaxNode::new_root_mut(builder.finish());
3234
3235 self.0
3236 .splice_children(target_index..target_index, vec![syntax.into()]);
3237 true
3238 }
3239
3240 pub fn recipe_count(&self) -> usize {
3249 self.syntax()
3250 .children()
3251 .filter(|n| n.kind() == RECIPE)
3252 .count()
3253 }
3254
3255 pub fn clear_commands(&mut self) {
3265 let recipes: Vec<_> = self
3266 .syntax()
3267 .children()
3268 .filter(|n| n.kind() == RECIPE)
3269 .collect();
3270
3271 if recipes.is_empty() {
3272 return;
3273 }
3274
3275 for recipe in recipes.iter().rev() {
3277 let index = recipe.index();
3278 self.0.splice_children(index..index + 1, vec![]);
3279 }
3280 }
3281
3282 pub fn remove_prerequisite(&mut self, target: &str) -> Result<bool, Error> {
3295 let mut found_operator = false;
3297 let mut prereqs_node = None;
3298
3299 for child in self.syntax().children_with_tokens() {
3300 if let Some(token) = child.as_token() {
3301 if token.kind() == OPERATOR {
3302 found_operator = true;
3303 }
3304 } else if let Some(node) = child.as_node() {
3305 if found_operator && node.kind() == PREREQUISITES {
3306 prereqs_node = Some(node.clone());
3307 break;
3308 }
3309 }
3310 }
3311
3312 let prereqs_node = match prereqs_node {
3313 Some(node) => node,
3314 None => return Ok(false), };
3316
3317 let current_prereqs: Vec<String> = self.prerequisites().collect();
3319
3320 if !current_prereqs.iter().any(|p| p == target) {
3322 return Ok(false);
3323 }
3324
3325 let new_prereqs: Vec<String> = current_prereqs
3327 .into_iter()
3328 .filter(|p| p != target)
3329 .collect();
3330
3331 let has_leading_whitespace = prereqs_node
3333 .children_with_tokens()
3334 .next()
3335 .map(|e| matches!(e.as_token().map(|t| t.kind()), Some(WHITESPACE)))
3336 .unwrap_or(false);
3337
3338 let prereqs_index = prereqs_node.index();
3340 let new_prereqs_node = build_prerequisites_node(&new_prereqs, has_leading_whitespace);
3341
3342 self.0.splice_children(
3343 prereqs_index..prereqs_index + 1,
3344 vec![new_prereqs_node.into()],
3345 );
3346
3347 Ok(true)
3348 }
3349
3350 pub fn add_prerequisite(&mut self, target: &str) -> Result<(), Error> {
3360 let mut current_prereqs: Vec<String> = self.prerequisites().collect();
3361 current_prereqs.push(target.to_string());
3362 self.set_prerequisites(current_prereqs.iter().map(|s| s.as_str()).collect())
3363 }
3364
3365 pub fn set_prerequisites(&mut self, prereqs: Vec<&str>) -> Result<(), Error> {
3375 let mut prereqs_index = None;
3377 let mut operator_found = false;
3378
3379 for child in self.syntax().children_with_tokens() {
3380 if let Some(token) = child.as_token() {
3381 if token.kind() == OPERATOR {
3382 operator_found = true;
3383 }
3384 } else if let Some(node) = child.as_node() {
3385 if operator_found && node.kind() == PREREQUISITES {
3386 prereqs_index = Some((node.index(), true)); break;
3388 }
3389 }
3390 }
3391
3392 match prereqs_index {
3393 Some((idx, true)) => {
3394 let has_external_whitespace = self
3396 .syntax()
3397 .children_with_tokens()
3398 .skip_while(|e| !matches!(e.as_token().map(|t| t.kind()), Some(OPERATOR)))
3399 .nth(1) .map(|e| matches!(e.as_token().map(|t| t.kind()), Some(WHITESPACE)))
3401 .unwrap_or(false);
3402
3403 let new_prereqs = build_prerequisites_node(
3404 &prereqs.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
3405 !has_external_whitespace, );
3407 self.0
3408 .splice_children(idx..idx + 1, vec![new_prereqs.into()]);
3409 }
3410 _ => {
3411 let new_prereqs = build_prerequisites_node(
3413 &prereqs.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
3414 true, );
3416
3417 let insert_pos = self
3418 .syntax()
3419 .children_with_tokens()
3420 .position(|t| t.as_token().map(|t| t.kind() == OPERATOR).unwrap_or(false))
3421 .map(|p| p + 1)
3422 .ok_or_else(|| {
3423 Error::Parse(ParseError {
3424 errors: vec![ErrorInfo {
3425 message: "No operator found in rule".to_string(),
3426 line: 1,
3427 context: "set_prerequisites".to_string(),
3428 }],
3429 })
3430 })?;
3431
3432 self.0
3433 .splice_children(insert_pos..insert_pos, vec![new_prereqs.into()]);
3434 }
3435 }
3436
3437 Ok(())
3438 }
3439
3440 pub fn rename_target(&mut self, old_name: &str, new_name: &str) -> Result<bool, Error> {
3452 let current_targets: Vec<String> = self.targets().collect();
3454
3455 if !current_targets.iter().any(|t| t == old_name) {
3457 return Ok(false);
3458 }
3459
3460 let new_targets: Vec<String> = current_targets
3462 .into_iter()
3463 .map(|t| {
3464 if t == old_name {
3465 new_name.to_string()
3466 } else {
3467 t
3468 }
3469 })
3470 .collect();
3471
3472 let mut targets_index = None;
3474 for (idx, child) in self.syntax().children_with_tokens().enumerate() {
3475 if let Some(node) = child.as_node() {
3476 if node.kind() == TARGETS {
3477 targets_index = Some(idx);
3478 break;
3479 }
3480 }
3481 }
3482
3483 let targets_index = targets_index.ok_or_else(|| {
3484 Error::Parse(ParseError {
3485 errors: vec![ErrorInfo {
3486 message: "No TARGETS node found in rule".to_string(),
3487 line: 1,
3488 context: "rename_target".to_string(),
3489 }],
3490 })
3491 })?;
3492
3493 let new_targets_node = build_targets_node(&new_targets);
3495
3496 self.0.splice_children(
3498 targets_index..targets_index + 1,
3499 vec![new_targets_node.into()],
3500 );
3501
3502 Ok(true)
3503 }
3504
3505 pub fn add_target(&mut self, target: &str) -> Result<(), Error> {
3515 let mut current_targets: Vec<String> = self.targets().collect();
3516 current_targets.push(target.to_string());
3517 self.set_targets(current_targets.iter().map(|s| s.as_str()).collect())
3518 }
3519
3520 pub fn set_targets(&mut self, targets: Vec<&str>) -> Result<(), Error> {
3532 if targets.is_empty() {
3534 return Err(Error::Parse(ParseError {
3535 errors: vec![ErrorInfo {
3536 message: "Cannot set empty targets list for a rule".to_string(),
3537 line: 1,
3538 context: "set_targets".to_string(),
3539 }],
3540 }));
3541 }
3542
3543 let mut targets_index = None;
3545 for (idx, child) in self.syntax().children_with_tokens().enumerate() {
3546 if let Some(node) = child.as_node() {
3547 if node.kind() == TARGETS {
3548 targets_index = Some(idx);
3549 break;
3550 }
3551 }
3552 }
3553
3554 let targets_index = targets_index.ok_or_else(|| {
3555 Error::Parse(ParseError {
3556 errors: vec![ErrorInfo {
3557 message: "No TARGETS node found in rule".to_string(),
3558 line: 1,
3559 context: "set_targets".to_string(),
3560 }],
3561 })
3562 })?;
3563
3564 let new_targets_node =
3566 build_targets_node(&targets.iter().map(|s| s.to_string()).collect::<Vec<_>>());
3567
3568 self.0.splice_children(
3570 targets_index..targets_index + 1,
3571 vec![new_targets_node.into()],
3572 );
3573
3574 Ok(())
3575 }
3576
3577 pub fn has_target(&self, target: &str) -> bool {
3588 self.targets().any(|t| t == target)
3589 }
3590
3591 pub fn remove_target(&mut self, target_name: &str) -> Result<bool, Error> {
3604 let current_targets: Vec<String> = self.targets().collect();
3606
3607 if !current_targets.iter().any(|t| t == target_name) {
3609 return Ok(false);
3610 }
3611
3612 let new_targets: Vec<String> = current_targets
3614 .into_iter()
3615 .filter(|t| t != target_name)
3616 .collect();
3617
3618 if new_targets.is_empty() {
3620 return Err(Error::Parse(ParseError {
3621 errors: vec![ErrorInfo {
3622 message: "Cannot remove all targets from a rule".to_string(),
3623 line: 1,
3624 context: "remove_target".to_string(),
3625 }],
3626 }));
3627 }
3628
3629 let mut targets_index = None;
3631 for (idx, child) in self.syntax().children_with_tokens().enumerate() {
3632 if let Some(node) = child.as_node() {
3633 if node.kind() == TARGETS {
3634 targets_index = Some(idx);
3635 break;
3636 }
3637 }
3638 }
3639
3640 let targets_index = targets_index.ok_or_else(|| {
3641 Error::Parse(ParseError {
3642 errors: vec![ErrorInfo {
3643 message: "No TARGETS node found in rule".to_string(),
3644 line: 1,
3645 context: "remove_target".to_string(),
3646 }],
3647 })
3648 })?;
3649
3650 let new_targets_node = build_targets_node(&new_targets);
3652
3653 self.0.splice_children(
3655 targets_index..targets_index + 1,
3656 vec![new_targets_node.into()],
3657 );
3658
3659 Ok(true)
3660 }
3661
3662 pub fn remove(self) -> Result<(), Error> {
3677 let parent = self.syntax().parent().ok_or_else(|| {
3678 Error::Parse(ParseError {
3679 errors: vec![ErrorInfo {
3680 message: "Rule has no parent".to_string(),
3681 line: 1,
3682 context: "remove".to_string(),
3683 }],
3684 })
3685 })?;
3686
3687 let is_last_rule = self
3689 .syntax()
3690 .siblings(rowan::Direction::Next)
3691 .skip(1) .all(|sibling| sibling.kind() != RULE);
3693
3694 remove_with_preceding_comments(self.syntax(), &parent);
3695
3696 if is_last_rule {
3698 if let Some(last_rule_node) = parent
3700 .children()
3701 .filter(|child| child.kind() == RULE)
3702 .last()
3703 {
3704 trim_trailing_newlines(&last_rule_node);
3705 }
3706 }
3707
3708 Ok(())
3709 }
3710}
3711
3712impl Default for Makefile {
3713 fn default() -> Self {
3714 Self::new()
3715 }
3716}
3717
3718impl Include {
3719 pub fn path(&self) -> Option<String> {
3721 self.syntax()
3722 .children()
3723 .find(|it| it.kind() == EXPR)
3724 .map(|it| it.text().to_string().trim().to_string())
3725 }
3726
3727 pub fn is_optional(&self) -> bool {
3729 let text = self.syntax().text();
3730 text.to_string().starts_with("-include") || text.to_string().starts_with("sinclude")
3731 }
3732
3733 pub fn parent(&self) -> Option<MakefileItem> {
3752 self.syntax().parent().and_then(MakefileItem::cast)
3753 }
3754}
3755
3756impl Conditional {
3757 pub fn parent(&self) -> Option<MakefileItem> {
3785 self.syntax().parent().and_then(MakefileItem::cast)
3786 }
3787
3788 pub fn conditional_type(&self) -> Option<String> {
3790 self.syntax()
3791 .children()
3792 .find(|it| it.kind() == CONDITIONAL_IF)?
3793 .children_with_tokens()
3794 .find(|it| it.kind() == IDENTIFIER)
3795 .map(|it| it.as_token().unwrap().text().to_string())
3796 }
3797
3798 pub fn condition(&self) -> Option<String> {
3800 let if_node = self
3801 .syntax()
3802 .children()
3803 .find(|it| it.kind() == CONDITIONAL_IF)?;
3804
3805 let expr_node = if_node.children().find(|it| it.kind() == EXPR)?;
3807
3808 Some(expr_node.text().to_string().trim().to_string())
3809 }
3810
3811 pub fn has_else(&self) -> bool {
3813 self.syntax()
3814 .children()
3815 .any(|it| it.kind() == CONDITIONAL_ELSE)
3816 }
3817
3818 pub fn if_body(&self) -> Option<String> {
3820 let mut body = String::new();
3821 let mut in_if_body = false;
3822
3823 for child in self.syntax().children_with_tokens() {
3824 if child.kind() == CONDITIONAL_IF {
3825 in_if_body = true;
3826 continue;
3827 }
3828 if child.kind() == CONDITIONAL_ELSE || child.kind() == CONDITIONAL_ENDIF {
3829 break;
3830 }
3831 if in_if_body {
3832 body.push_str(child.to_string().as_str());
3833 }
3834 }
3835
3836 if body.is_empty() {
3837 None
3838 } else {
3839 Some(body)
3840 }
3841 }
3842
3843 pub fn else_body(&self) -> Option<String> {
3845 if !self.has_else() {
3846 return None;
3847 }
3848
3849 let mut body = String::new();
3850 let mut in_else_body = false;
3851
3852 for child in self.syntax().children_with_tokens() {
3853 if child.kind() == CONDITIONAL_ELSE {
3854 in_else_body = true;
3855 continue;
3856 }
3857 if child.kind() == CONDITIONAL_ENDIF {
3858 break;
3859 }
3860 if in_else_body {
3861 body.push_str(child.to_string().as_str());
3862 }
3863 }
3864
3865 if body.is_empty() {
3866 None
3867 } else {
3868 Some(body)
3869 }
3870 }
3871
3872 pub fn remove(&mut self) -> Result<(), Error> {
3874 let Some(parent) = self.syntax().parent() else {
3875 return Err(Error::Parse(ParseError {
3876 errors: vec![ErrorInfo {
3877 message: "Cannot remove conditional: no parent node".to_string(),
3878 line: 1,
3879 context: "conditional_remove".to_string(),
3880 }],
3881 }));
3882 };
3883
3884 remove_with_preceding_comments(self.syntax(), &parent);
3885
3886 Ok(())
3887 }
3888
3889 pub fn unwrap(&mut self) -> Result<(), Error> {
3908 if self.has_else() {
3910 return Err(Error::Parse(ParseError {
3911 errors: vec![ErrorInfo {
3912 message: "Cannot unwrap conditional with else clause".to_string(),
3913 line: 1,
3914 context: "conditional_unwrap".to_string(),
3915 }],
3916 }));
3917 }
3918
3919 let Some(parent) = self.syntax().parent() else {
3920 return Err(Error::Parse(ParseError {
3921 errors: vec![ErrorInfo {
3922 message: "Cannot unwrap conditional: no parent node".to_string(),
3923 line: 1,
3924 context: "conditional_unwrap".to_string(),
3925 }],
3926 }));
3927 };
3928
3929 let body_nodes: Vec<_> = self
3931 .syntax()
3932 .children_with_tokens()
3933 .skip_while(|n| n.kind() != CONDITIONAL_IF)
3934 .skip(1) .take_while(|n| n.kind() != CONDITIONAL_ENDIF)
3936 .collect();
3937
3938 let conditional_index = self.syntax().index();
3940
3941 parent.splice_children(conditional_index..conditional_index + 1, body_nodes);
3943
3944 Ok(())
3945 }
3946
3947 pub fn if_items(&self) -> impl Iterator<Item = MakefileItem> + '_ {
3963 self.syntax()
3964 .children()
3965 .skip_while(|n| n.kind() != CONDITIONAL_IF)
3966 .skip(1) .take_while(|n| n.kind() != CONDITIONAL_ELSE && n.kind() != CONDITIONAL_ENDIF)
3968 .filter_map(MakefileItem::cast)
3969 }
3970
3971 pub fn else_items(&self) -> impl Iterator<Item = MakefileItem> + '_ {
3987 self.syntax()
3988 .children()
3989 .skip_while(|n| n.kind() != CONDITIONAL_ELSE)
3990 .skip(1) .take_while(|n| n.kind() != CONDITIONAL_ENDIF)
3992 .filter_map(MakefileItem::cast)
3993 }
3994
3995 pub fn add_if_item(&mut self, item: MakefileItem) {
4008 let item_node = item.syntax().clone();
4009
4010 let insert_pos = self
4012 .syntax()
4013 .children_with_tokens()
4014 .position(|n| n.kind() == CONDITIONAL_IF)
4015 .map(|p| p + 1)
4016 .unwrap_or(0);
4017
4018 self.0
4019 .splice_children(insert_pos..insert_pos, vec![item_node.into()]);
4020 }
4021
4022 pub fn add_else_item(&mut self, item: MakefileItem) {
4038 if !self.has_else() {
4040 self.add_else_clause();
4041 }
4042
4043 let item_node = item.syntax().clone();
4044
4045 let insert_pos = self
4047 .syntax()
4048 .children_with_tokens()
4049 .position(|n| n.kind() == CONDITIONAL_ELSE)
4050 .map(|p| p + 1)
4051 .unwrap_or(0);
4052
4053 self.0
4054 .splice_children(insert_pos..insert_pos, vec![item_node.into()]);
4055 }
4056
4057 fn add_else_clause(&mut self) {
4059 if self.has_else() {
4060 return;
4061 }
4062
4063 let mut builder = GreenNodeBuilder::new();
4064 builder.start_node(CONDITIONAL_ELSE.into());
4065 builder.token(IDENTIFIER.into(), "else");
4066 builder.token(NEWLINE.into(), "\n");
4067 builder.finish_node();
4068
4069 let syntax = SyntaxNode::new_root_mut(builder.finish());
4070
4071 let insert_pos = self
4073 .syntax()
4074 .children_with_tokens()
4075 .position(|n| n.kind() == CONDITIONAL_ENDIF)
4076 .unwrap_or(self.syntax().children_with_tokens().count());
4077
4078 self.0
4079 .splice_children(insert_pos..insert_pos, vec![syntax.into()]);
4080 }
4081}
4082
4083#[cfg(test)]
4084mod tests {
4085 use super::*;
4086
4087 #[test]
4088 fn test_conditionals() {
4089 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
4093 let mut buf = code.as_bytes();
4094 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
4095 assert!(makefile.code().contains("DEBUG_FLAG"));
4096
4097 let code =
4099 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
4100 let mut buf = code.as_bytes();
4101 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
4102 assert!(makefile.code().contains("RESULT"));
4103 assert!(makefile.code().contains("windows"));
4104
4105 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
4107 let mut buf = code.as_bytes();
4108 let makefile = Makefile::read_relaxed(&mut buf)
4109 .expect("Failed to parse nested conditionals with else");
4110 assert!(makefile.code().contains("CFLAGS"));
4111 assert!(makefile.code().contains("VERBOSE"));
4112
4113 let code = "ifdef DEBUG\nendif\n";
4115 let mut buf = code.as_bytes();
4116 let makefile =
4117 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
4118 assert!(makefile.code().contains("ifdef DEBUG"));
4119
4120 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelse ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
4122 let mut buf = code.as_bytes();
4123 let makefile =
4124 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with else ifeq");
4125 assert!(makefile.code().contains("EXT"));
4126
4127 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
4129 let mut buf = code.as_bytes();
4130 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
4131 assert!(makefile.code().contains("DEBUG"));
4132
4133 let code = "ifdef \nDEBUG := 1\nendif\n";
4135 let mut buf = code.as_bytes();
4136 let makefile = Makefile::read_relaxed(&mut buf)
4137 .expect("Failed to parse with recovery - missing condition");
4138 assert!(makefile.code().contains("DEBUG"));
4139 }
4140
4141 #[test]
4142 fn test_parse_simple() {
4143 const SIMPLE: &str = r#"VARIABLE = value
4144
4145rule: dependency
4146 command
4147"#;
4148 let parsed = parse(SIMPLE, None);
4149 assert!(parsed.errors.is_empty());
4150 let node = parsed.syntax();
4151 assert_eq!(
4152 format!("{:#?}", node),
4153 r#"ROOT@0..44
4154 VARIABLE@0..17
4155 IDENTIFIER@0..8 "VARIABLE"
4156 WHITESPACE@8..9 " "
4157 OPERATOR@9..10 "="
4158 WHITESPACE@10..11 " "
4159 EXPR@11..16
4160 IDENTIFIER@11..16 "value"
4161 NEWLINE@16..17 "\n"
4162 BLANK_LINE@17..18
4163 NEWLINE@17..18 "\n"
4164 RULE@18..44
4165 TARGETS@18..22
4166 IDENTIFIER@18..22 "rule"
4167 OPERATOR@22..23 ":"
4168 WHITESPACE@23..24 " "
4169 PREREQUISITES@24..34
4170 PREREQUISITE@24..34
4171 IDENTIFIER@24..34 "dependency"
4172 NEWLINE@34..35 "\n"
4173 RECIPE@35..44
4174 INDENT@35..36 "\t"
4175 TEXT@36..43 "command"
4176 NEWLINE@43..44 "\n"
4177"#
4178 );
4179
4180 let root = parsed.root();
4181
4182 let mut rules = root.rules().collect::<Vec<_>>();
4183 assert_eq!(rules.len(), 1);
4184 let rule = rules.pop().unwrap();
4185 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
4186 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
4187 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
4188
4189 let mut variables = root.variable_definitions().collect::<Vec<_>>();
4190 assert_eq!(variables.len(), 1);
4191 let variable = variables.pop().unwrap();
4192 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
4193 assert_eq!(variable.raw_value(), Some("value".to_string()));
4194 }
4195
4196 #[test]
4197 fn test_parse_export_assign() {
4198 const EXPORT: &str = r#"export VARIABLE := value
4199"#;
4200 let parsed = parse(EXPORT, None);
4201 assert!(parsed.errors.is_empty());
4202 let node = parsed.syntax();
4203 assert_eq!(
4204 format!("{:#?}", node),
4205 r#"ROOT@0..25
4206 VARIABLE@0..25
4207 IDENTIFIER@0..6 "export"
4208 WHITESPACE@6..7 " "
4209 IDENTIFIER@7..15 "VARIABLE"
4210 WHITESPACE@15..16 " "
4211 OPERATOR@16..18 ":="
4212 WHITESPACE@18..19 " "
4213 EXPR@19..24
4214 IDENTIFIER@19..24 "value"
4215 NEWLINE@24..25 "\n"
4216"#
4217 );
4218
4219 let root = parsed.root();
4220
4221 let mut variables = root.variable_definitions().collect::<Vec<_>>();
4222 assert_eq!(variables.len(), 1);
4223 let variable = variables.pop().unwrap();
4224 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
4225 assert_eq!(variable.raw_value(), Some("value".to_string()));
4226 }
4227
4228 #[test]
4229 fn test_parse_multiple_prerequisites() {
4230 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
4231 command
4232
4233"#;
4234 let parsed = parse(MULTIPLE_PREREQUISITES, None);
4235 assert!(parsed.errors.is_empty());
4236 let node = parsed.syntax();
4237 assert_eq!(
4238 format!("{:#?}", node),
4239 r#"ROOT@0..40
4240 RULE@0..40
4241 TARGETS@0..4
4242 IDENTIFIER@0..4 "rule"
4243 OPERATOR@4..5 ":"
4244 WHITESPACE@5..6 " "
4245 PREREQUISITES@6..29
4246 PREREQUISITE@6..17
4247 IDENTIFIER@6..17 "dependency1"
4248 WHITESPACE@17..18 " "
4249 PREREQUISITE@18..29
4250 IDENTIFIER@18..29 "dependency2"
4251 NEWLINE@29..30 "\n"
4252 RECIPE@30..39
4253 INDENT@30..31 "\t"
4254 TEXT@31..38 "command"
4255 NEWLINE@38..39 "\n"
4256 NEWLINE@39..40 "\n"
4257"#
4258 );
4259 let root = parsed.root();
4260
4261 let rule = root.rules().next().unwrap();
4262 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
4263 assert_eq!(
4264 rule.prerequisites().collect::<Vec<_>>(),
4265 vec!["dependency1", "dependency2"]
4266 );
4267 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
4268 }
4269
4270 #[test]
4271 fn test_add_rule() {
4272 let mut makefile = Makefile::new();
4273 let rule = makefile.add_rule("rule");
4274 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
4275 assert_eq!(
4276 rule.prerequisites().collect::<Vec<_>>(),
4277 Vec::<String>::new()
4278 );
4279
4280 assert_eq!(makefile.to_string(), "rule:\n");
4281 }
4282
4283 #[test]
4284 fn test_add_rule_with_shebang() {
4285 let content = r#"#!/usr/bin/make -f
4287
4288build: blah
4289 $(MAKE) install
4290
4291clean:
4292 dh_clean
4293"#;
4294
4295 let mut makefile = Makefile::read_relaxed(content.as_bytes()).unwrap();
4296 let initial_count = makefile.rules().count();
4297 assert_eq!(initial_count, 2);
4298
4299 let rule = makefile.add_rule("build-indep");
4301 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["build-indep"]);
4302
4303 assert_eq!(makefile.rules().count(), initial_count + 1);
4305 }
4306
4307 #[test]
4308 fn test_add_rule_formatting() {
4309 let content = r#"build: blah
4311 $(MAKE) install
4312
4313clean:
4314 dh_clean
4315"#;
4316
4317 let mut makefile = Makefile::read_relaxed(content.as_bytes()).unwrap();
4318 let mut rule = makefile.add_rule("build-indep");
4319 rule.add_prerequisite("build").unwrap();
4320
4321 let expected = r#"build: blah
4322 $(MAKE) install
4323
4324clean:
4325 dh_clean
4326
4327build-indep: build
4328"#;
4329
4330 assert_eq!(makefile.to_string(), expected);
4331 }
4332
4333 #[test]
4334 fn test_push_command() {
4335 let mut makefile = Makefile::new();
4336 let mut rule = makefile.add_rule("rule");
4337
4338 rule.push_command("command");
4340 rule.push_command("command2");
4341
4342 assert_eq!(
4344 rule.recipes().collect::<Vec<_>>(),
4345 vec!["command", "command2"]
4346 );
4347
4348 rule.push_command("command3");
4350 assert_eq!(
4351 rule.recipes().collect::<Vec<_>>(),
4352 vec!["command", "command2", "command3"]
4353 );
4354
4355 assert_eq!(
4357 makefile.to_string(),
4358 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
4359 );
4360
4361 assert_eq!(
4363 rule.to_string(),
4364 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
4365 );
4366 }
4367
4368 #[test]
4369 fn test_replace_command() {
4370 let mut makefile = Makefile::new();
4371 let mut rule = makefile.add_rule("rule");
4372
4373 rule.push_command("command");
4375 rule.push_command("command2");
4376
4377 assert_eq!(
4379 rule.recipes().collect::<Vec<_>>(),
4380 vec!["command", "command2"]
4381 );
4382
4383 rule.replace_command(0, "new command");
4385 assert_eq!(
4386 rule.recipes().collect::<Vec<_>>(),
4387 vec!["new command", "command2"]
4388 );
4389
4390 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
4392
4393 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
4395 }
4396
4397 #[test]
4398 fn test_replace_command_with_comments() {
4399 let content = b"override_dh_strip:\n\t# no longer necessary after buster\n\tdh_strip --dbgsym-migration='amule-dbg (<< 1:2.3.2-2~)'\n";
4402
4403 let makefile = Makefile::read_relaxed(&content[..]).unwrap();
4404
4405 let mut rule = makefile.rules().next().unwrap();
4406
4407 assert_eq!(rule.recipes().count(), 1);
4409 assert_eq!(
4410 rule.recipes().collect::<Vec<_>>(),
4411 vec!["dh_strip --dbgsym-migration='amule-dbg (<< 1:2.3.2-2~)'"]
4412 );
4413
4414 assert!(rule.replace_command(0, "dh_strip"));
4416
4417 assert_eq!(rule.recipes().count(), 1);
4419 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["dh_strip"]);
4420 }
4421
4422 #[test]
4423 fn test_parse_rule_without_newline() {
4424 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
4425 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
4426 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
4427 let rule = "rule: dependency".parse::<Rule>().unwrap();
4428 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
4429 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
4430 }
4431
4432 #[test]
4433 fn test_parse_makefile_without_newline() {
4434 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
4435 assert_eq!(makefile.rules().count(), 1);
4436 }
4437
4438 #[test]
4439 fn test_from_reader() {
4440 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
4441 assert_eq!(makefile.rules().count(), 1);
4442 }
4443
4444 #[test]
4445 fn test_parse_with_tab_after_last_newline() {
4446 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
4447 assert_eq!(makefile.rules().count(), 1);
4448 }
4449
4450 #[test]
4451 fn test_parse_with_space_after_last_newline() {
4452 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
4453 assert_eq!(makefile.rules().count(), 1);
4454 }
4455
4456 #[test]
4457 fn test_parse_with_comment_after_last_newline() {
4458 let makefile =
4459 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
4460 assert_eq!(makefile.rules().count(), 1);
4461 }
4462
4463 #[test]
4464 fn test_parse_with_variable_rule() {
4465 let makefile =
4466 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
4467 .unwrap();
4468
4469 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4471 assert_eq!(vars.len(), 1);
4472 assert_eq!(vars[0].name(), Some("RULE".to_string()));
4473 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
4474
4475 let rules = makefile.rules().collect::<Vec<_>>();
4477 assert_eq!(rules.len(), 1);
4478 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
4479 assert_eq!(
4480 rules[0].prerequisites().collect::<Vec<_>>(),
4481 vec!["dependency"]
4482 );
4483 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
4484 }
4485
4486 #[test]
4487 fn test_parse_with_variable_dependency() {
4488 let makefile =
4489 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
4490
4491 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4493 assert_eq!(vars.len(), 1);
4494 assert_eq!(vars[0].name(), Some("DEP".to_string()));
4495 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
4496
4497 let rules = makefile.rules().collect::<Vec<_>>();
4499 assert_eq!(rules.len(), 1);
4500 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
4501 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
4502 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
4503 }
4504
4505 #[test]
4506 fn test_parse_with_variable_command() {
4507 let makefile =
4508 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
4509
4510 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4512 assert_eq!(vars.len(), 1);
4513 assert_eq!(vars[0].name(), Some("COM".to_string()));
4514 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
4515
4516 let rules = makefile.rules().collect::<Vec<_>>();
4518 assert_eq!(rules.len(), 1);
4519 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
4520 assert_eq!(
4521 rules[0].prerequisites().collect::<Vec<_>>(),
4522 vec!["dependency"]
4523 );
4524 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
4525 }
4526
4527 #[test]
4528 fn test_regular_line_error_reporting() {
4529 let input = "rule target\n\tcommand";
4530
4531 let parsed = parse(input, None);
4533 let direct_error = &parsed.errors[0];
4534
4535 assert_eq!(direct_error.line, 2);
4537 assert!(
4538 direct_error.message.contains("expected"),
4539 "Error message should contain 'expected': {}",
4540 direct_error.message
4541 );
4542 assert_eq!(direct_error.context, "\tcommand");
4543
4544 let reader_result = Makefile::from_reader(input.as_bytes());
4546 let parse_error = match reader_result {
4547 Ok(_) => panic!("Expected Parse error from from_reader"),
4548 Err(err) => match err {
4549 self::Error::Parse(parse_err) => parse_err,
4550 _ => panic!("Expected Parse error"),
4551 },
4552 };
4553
4554 let error_text = parse_error.to_string();
4556 assert!(error_text.contains("Error at line 2:"));
4557 assert!(error_text.contains("2| \tcommand"));
4558 }
4559
4560 #[test]
4561 fn test_parsing_error_context_with_bad_syntax() {
4562 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
4564
4565 match Makefile::from_reader(input.as_bytes()) {
4567 Ok(makefile) => {
4568 assert_eq!(
4570 makefile.rules().count(),
4571 0,
4572 "Should not have found any rules"
4573 );
4574 }
4575 Err(err) => match err {
4576 self::Error::Parse(error) => {
4577 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
4579 assert!(
4580 !error.errors[0].context.is_empty(),
4581 "Error context should not be empty"
4582 );
4583 }
4584 _ => panic!("Unexpected error type"),
4585 },
4586 };
4587 }
4588
4589 #[test]
4590 fn test_error_message_format() {
4591 let parse_error = ParseError {
4593 errors: vec![ErrorInfo {
4594 message: "test error".to_string(),
4595 line: 42,
4596 context: "some problematic code".to_string(),
4597 }],
4598 };
4599
4600 let error_text = parse_error.to_string();
4601 assert!(error_text.contains("Error at line 42: test error"));
4602 assert!(error_text.contains("42| some problematic code"));
4603 }
4604
4605 #[test]
4606 fn test_line_number_calculation() {
4607 let test_cases = [
4609 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
4613
4614 for (input, expected_line) in test_cases {
4615 match input.parse::<Makefile>() {
4617 Ok(_) => {
4618 continue;
4621 }
4622 Err(err) => {
4623 if let Error::Parse(parse_err) = err {
4624 assert_eq!(
4626 parse_err.errors[0].line, expected_line,
4627 "Line number should match the expected line"
4628 );
4629
4630 if parse_err.errors[0].message.contains("indented") {
4632 assert!(
4633 parse_err.errors[0].context.starts_with('\t'),
4634 "Context for indentation errors should include the tab character"
4635 );
4636 }
4637 } else {
4638 panic!("Expected parse error, got: {:?}", err);
4639 }
4640 }
4641 }
4642 }
4643 }
4644
4645 #[test]
4646 fn test_conditional_features() {
4647 let code = r#"
4649# Set variables based on DEBUG flag
4650ifdef DEBUG
4651 CFLAGS += -g -DDEBUG
4652else
4653 CFLAGS = -O2
4654endif
4655
4656# Define a build rule
4657all: $(OBJS)
4658 $(CC) $(CFLAGS) -o $@ $^
4659"#;
4660
4661 let mut buf = code.as_bytes();
4662 let makefile =
4663 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
4664
4665 assert!(!makefile.code().is_empty(), "Makefile has content");
4668
4669 let rules = makefile.rules().collect::<Vec<_>>();
4671 assert!(!rules.is_empty(), "Should have found rules");
4672
4673 assert!(code.contains("ifdef DEBUG"));
4675 assert!(code.contains("endif"));
4676
4677 let code_with_var = r#"
4679# Define a variable first
4680CC = gcc
4681
4682ifdef DEBUG
4683 CFLAGS += -g -DDEBUG
4684else
4685 CFLAGS = -O2
4686endif
4687
4688all: $(OBJS)
4689 $(CC) $(CFLAGS) -o $@ $^
4690"#;
4691
4692 let mut buf = code_with_var.as_bytes();
4693 let makefile =
4694 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
4695
4696 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4698 assert!(
4699 !vars.is_empty(),
4700 "Should have found at least the CC variable definition"
4701 );
4702 }
4703
4704 #[test]
4705 fn test_include_directive() {
4706 let parsed = parse(
4707 "include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n",
4708 None,
4709 );
4710 assert!(parsed.errors.is_empty());
4711 let node = parsed.syntax();
4712 assert!(format!("{:#?}", node).contains("INCLUDE@"));
4713 }
4714
4715 #[test]
4716 fn test_export_variables() {
4717 let parsed = parse("export SHELL := /bin/bash\n", None);
4718 assert!(parsed.errors.is_empty());
4719 let makefile = parsed.root();
4720 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4721 assert_eq!(vars.len(), 1);
4722 let shell_var = vars
4723 .iter()
4724 .find(|v| v.name() == Some("SHELL".to_string()))
4725 .unwrap();
4726 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
4727 }
4728
4729 #[test]
4730 fn test_variable_scopes() {
4731 let parsed = parse(
4732 "SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n",
4733 None,
4734 );
4735 assert!(parsed.errors.is_empty());
4736 let makefile = parsed.root();
4737 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4738 assert_eq!(vars.len(), 4);
4739 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
4740 assert!(var_names.contains(&"SIMPLE".to_string()));
4741 assert!(var_names.contains(&"IMMEDIATE".to_string()));
4742 assert!(var_names.contains(&"CONDITIONAL".to_string()));
4743 assert!(var_names.contains(&"APPEND".to_string()));
4744 }
4745
4746 #[test]
4747 fn test_pattern_rule_parsing() {
4748 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n", None);
4749 assert!(parsed.errors.is_empty());
4750 let makefile = parsed.root();
4751 let rules = makefile.rules().collect::<Vec<_>>();
4752 assert_eq!(rules.len(), 1);
4753 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
4754 assert!(rules[0].recipes().next().unwrap().contains("$@"));
4755 }
4756
4757 #[test]
4758 fn test_include_variants() {
4759 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
4761 let parsed = parse(makefile_str, None);
4762 assert!(parsed.errors.is_empty());
4763
4764 let node = parsed.syntax();
4766 let debug_str = format!("{:#?}", node);
4767
4768 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
4770
4771 let makefile = parsed.root();
4773
4774 let include_count = makefile
4776 .syntax()
4777 .children()
4778 .filter(|child| child.kind() == INCLUDE)
4779 .count();
4780 assert_eq!(include_count, 4);
4781
4782 assert!(makefile
4784 .included_files()
4785 .any(|path| path.contains("$(VAR)")));
4786 }
4787
4788 #[test]
4789 fn test_include_api() {
4790 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
4792 let makefile: Makefile = makefile_str.parse().unwrap();
4793
4794 let includes: Vec<_> = makefile.includes().collect();
4796 assert_eq!(includes.len(), 3);
4797
4798 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
4805 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
4806
4807 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
4809 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
4810 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
4811 }
4812
4813 #[test]
4814 fn test_include_integration() {
4815 let phony_makefile = Makefile::from_reader(
4819 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
4820 .as_bytes()
4821 ).unwrap();
4822
4823 assert_eq!(phony_makefile.rules().count(), 2);
4825
4826 let normal_rules_count = phony_makefile
4828 .rules()
4829 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
4830 .count();
4831 assert_eq!(normal_rules_count, 1);
4832
4833 assert_eq!(phony_makefile.includes().count(), 1);
4835 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
4836
4837 let simple_makefile = Makefile::from_reader(
4839 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
4840 .as_bytes(),
4841 )
4842 .unwrap();
4843 assert_eq!(simple_makefile.rules().count(), 1);
4844 assert_eq!(simple_makefile.includes().count(), 1);
4845 }
4846
4847 #[test]
4848 fn test_real_conditional_directives() {
4849 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
4851 let mut buf = conditional.as_bytes();
4852 let makefile =
4853 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
4854 let code = makefile.code();
4855 assert!(code.contains("ifdef DEBUG"));
4856 assert!(code.contains("else"));
4857 assert!(code.contains("endif"));
4858
4859 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
4861 let mut buf = nested.as_bytes();
4862 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
4863 let code = makefile.code();
4864 assert!(code.contains("ifdef DEBUG"));
4865 assert!(code.contains("ifdef VERBOSE"));
4866
4867 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
4869 let mut buf = ifeq.as_bytes();
4870 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
4871 let code = makefile.code();
4872 assert!(code.contains("ifeq"));
4873 assert!(code.contains("Windows_NT"));
4874 }
4875
4876 #[test]
4877 fn test_indented_text_outside_rules() {
4878 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
4880 let parsed = parse(help_text, None);
4881 assert!(parsed.errors.is_empty());
4882
4883 let root = parsed.root();
4885 let rules = root.rules().collect::<Vec<_>>();
4886 assert_eq!(rules.len(), 1);
4887
4888 let help_rule = &rules[0];
4889 let recipes = help_rule.recipes().collect::<Vec<_>>();
4890 assert_eq!(recipes.len(), 2);
4891 assert!(recipes[0].contains("Available targets"));
4892 assert!(recipes[1].contains("help"));
4893 }
4894
4895 #[test]
4896 fn test_comment_handling_in_recipes() {
4897 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
4899
4900 let parsed = parse(recipe_comment, None);
4902
4903 assert!(
4905 parsed.errors.is_empty(),
4906 "Should parse recipe with comments without errors"
4907 );
4908
4909 let root = parsed.root();
4911 let rules = root.rules().collect::<Vec<_>>();
4912 assert_eq!(rules.len(), 1, "Should find exactly one rule");
4913
4914 let build_rule = &rules[0];
4916 assert_eq!(
4917 build_rule.targets().collect::<Vec<_>>(),
4918 vec!["build"],
4919 "Rule should have 'build' as target"
4920 );
4921
4922 let recipes = build_rule.recipes().collect::<Vec<_>>();
4926 assert_eq!(
4927 recipes.len(),
4928 1,
4929 "Should find exactly one recipe line (comment lines are filtered)"
4930 );
4931 assert!(
4932 recipes[0].contains("gcc -o app"),
4933 "Recipe should be the command line"
4934 );
4935 assert!(
4936 !recipes[0].contains("This is a comment"),
4937 "Comments should not be included in recipe lines"
4938 );
4939 }
4940
4941 #[test]
4942 fn test_multiline_variables() {
4943 let multiline = "SOURCES = main.c \\\n util.c\n";
4945
4946 let parsed = parse(multiline, None);
4948
4949 let root = parsed.root();
4951 let vars = root.variable_definitions().collect::<Vec<_>>();
4952 assert!(!vars.is_empty(), "Should find at least one variable");
4953
4954 let operators = "CFLAGS := -Wall \\\n -Werror\n";
4958 let parsed_operators = parse(operators, None);
4959
4960 let root = parsed_operators.root();
4962 let vars = root.variable_definitions().collect::<Vec<_>>();
4963 assert!(
4964 !vars.is_empty(),
4965 "Should find at least one variable with := operator"
4966 );
4967
4968 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
4970 let parsed_append = parse(append, None);
4971
4972 let root = parsed_append.root();
4974 let vars = root.variable_definitions().collect::<Vec<_>>();
4975 assert!(
4976 !vars.is_empty(),
4977 "Should find at least one variable with += operator"
4978 );
4979 }
4980
4981 #[test]
4982 fn test_whitespace_and_eof_handling() {
4983 let blank_lines = "VAR = value\n\n\n";
4985
4986 let parsed_blank = parse(blank_lines, None);
4987
4988 let root = parsed_blank.root();
4990 let vars = root.variable_definitions().collect::<Vec<_>>();
4991 assert_eq!(
4992 vars.len(),
4993 1,
4994 "Should find one variable in blank lines test"
4995 );
4996
4997 let trailing_space = "VAR = value \n";
4999
5000 let parsed_space = parse(trailing_space, None);
5001
5002 let root = parsed_space.root();
5004 let vars = root.variable_definitions().collect::<Vec<_>>();
5005 assert_eq!(
5006 vars.len(),
5007 1,
5008 "Should find one variable in trailing space test"
5009 );
5010
5011 let no_newline = "VAR = value";
5013
5014 let parsed_no_newline = parse(no_newline, None);
5015
5016 let root = parsed_no_newline.root();
5018 let vars = root.variable_definitions().collect::<Vec<_>>();
5019 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
5020 assert_eq!(
5021 vars[0].name(),
5022 Some("VAR".to_string()),
5023 "Variable name should be VAR"
5024 );
5025 }
5026
5027 #[test]
5028 fn test_complex_variable_references() {
5029 let wildcard = "SOURCES = $(wildcard *.c)\n";
5031 let parsed = parse(wildcard, None);
5032 assert!(parsed.errors.is_empty());
5033
5034 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
5036 let parsed = parse(nested, None);
5037 assert!(parsed.errors.is_empty());
5038
5039 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
5041 let parsed = parse(patsubst, None);
5042 assert!(parsed.errors.is_empty());
5043 }
5044
5045 #[test]
5046 fn test_complex_variable_references_minimal() {
5047 let wildcard = "SOURCES = $(wildcard *.c)\n";
5049 let parsed = parse(wildcard, None);
5050 assert!(parsed.errors.is_empty());
5051
5052 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
5054 let parsed = parse(nested, None);
5055 assert!(parsed.errors.is_empty());
5056
5057 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
5059 let parsed = parse(patsubst, None);
5060 assert!(parsed.errors.is_empty());
5061 }
5062
5063 #[test]
5064 fn test_multiline_variable_with_backslash() {
5065 let content = r#"
5066LONG_VAR = This is a long variable \
5067 that continues on the next line \
5068 and even one more line
5069"#;
5070
5071 let mut buf = content.as_bytes();
5073 let makefile =
5074 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
5075
5076 let vars = makefile.variable_definitions().collect::<Vec<_>>();
5078 assert_eq!(
5079 vars.len(),
5080 1,
5081 "Expected 1 variable but found {}",
5082 vars.len()
5083 );
5084 let var_value = vars[0].raw_value();
5085 assert!(var_value.is_some(), "Variable value is None");
5086
5087 let value_str = var_value.unwrap();
5089 assert!(
5090 value_str.contains("long variable"),
5091 "Value doesn't contain expected content"
5092 );
5093 }
5094
5095 #[test]
5096 fn test_multiline_variable_with_mixed_operators() {
5097 let content = r#"
5098PREFIX ?= /usr/local
5099CFLAGS := -Wall -O2 \
5100 -I$(PREFIX)/include \
5101 -DDEBUG
5102"#;
5103 let mut buf = content.as_bytes();
5105 let makefile = Makefile::read_relaxed(&mut buf)
5106 .expect("Failed to parse multiline variable with operators");
5107
5108 let vars = makefile.variable_definitions().collect::<Vec<_>>();
5110 assert!(
5111 vars.len() >= 1,
5112 "Expected at least 1 variable, found {}",
5113 vars.len()
5114 );
5115
5116 let prefix_var = vars
5118 .iter()
5119 .find(|v| v.name().unwrap_or_default() == "PREFIX");
5120 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
5121 assert!(
5122 prefix_var.unwrap().raw_value().is_some(),
5123 "PREFIX variable has no value"
5124 );
5125
5126 let cflags_var = vars
5128 .iter()
5129 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
5130 assert!(
5131 cflags_var.is_some(),
5132 "Expected to find CFLAGS variable (or part of it)"
5133 );
5134 }
5135
5136 #[test]
5137 fn test_indented_help_text() {
5138 let content = r#"
5139.PHONY: help
5140help:
5141 @echo "Available targets:"
5142 @echo " build - Build the project"
5143 @echo " test - Run tests"
5144 @echo " clean - Remove build artifacts"
5145"#;
5146 let mut buf = content.as_bytes();
5148 let makefile =
5149 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
5150
5151 let rules = makefile.rules().collect::<Vec<_>>();
5153 assert!(!rules.is_empty(), "Expected at least one rule");
5154
5155 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
5157 assert!(help_rule.is_some(), "Expected to find help rule");
5158
5159 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
5161 assert!(
5162 !recipes.is_empty(),
5163 "Expected at least one recipe line in help rule"
5164 );
5165 assert!(
5166 recipes.iter().any(|r| r.contains("Available targets")),
5167 "Expected to find 'Available targets' in recipes"
5168 );
5169 }
5170
5171 #[test]
5172 fn test_indented_lines_in_conditionals() {
5173 let content = r#"
5174ifdef DEBUG
5175 CFLAGS += -g -DDEBUG
5176 # This is a comment inside conditional
5177 ifdef VERBOSE
5178 CFLAGS += -v
5179 endif
5180endif
5181"#;
5182 let mut buf = content.as_bytes();
5184 let makefile = Makefile::read_relaxed(&mut buf)
5185 .expect("Failed to parse indented lines in conditionals");
5186
5187 let code = makefile.code();
5189 assert!(code.contains("ifdef DEBUG"));
5190 assert!(code.contains("ifdef VERBOSE"));
5191 assert!(code.contains("endif"));
5192 }
5193
5194 #[test]
5195 fn test_recipe_with_colon() {
5196 let content = r#"
5197build:
5198 @echo "Building at: $(shell date)"
5199 gcc -o program main.c
5200"#;
5201 let parsed = parse(content, None);
5202 assert!(
5203 parsed.errors.is_empty(),
5204 "Failed to parse recipe with colon: {:?}",
5205 parsed.errors
5206 );
5207 }
5208
5209 #[test]
5210 #[ignore]
5211 fn test_double_colon_rules() {
5212 let content = r#"
5215%.o :: %.c
5216 $(CC) -c $< -o $@
5217
5218# Double colon allows multiple rules for same target
5219all:: prerequisite1
5220 @echo "First rule for all"
5221
5222all:: prerequisite2
5223 @echo "Second rule for all"
5224"#;
5225 let mut buf = content.as_bytes();
5226 let makefile =
5227 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
5228
5229 let rules = makefile.rules().collect::<Vec<_>>();
5231 assert!(!rules.is_empty(), "Expected at least one rule");
5232
5233 let all_rules = rules
5235 .iter()
5236 .filter(|r| r.targets().any(|t| t.contains("all")));
5237 assert!(
5238 all_rules.count() > 0,
5239 "Expected to find at least one rule containing 'all'"
5240 );
5241 }
5242
5243 #[test]
5244 fn test_else_conditional_directives() {
5245 let content = r#"
5247ifeq ($(OS),Windows_NT)
5248 TARGET = windows
5249else ifeq ($(OS),Darwin)
5250 TARGET = macos
5251else ifeq ($(OS),Linux)
5252 TARGET = linux
5253else
5254 TARGET = unknown
5255endif
5256"#;
5257 let mut buf = content.as_bytes();
5258 let makefile =
5259 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifeq directive");
5260 assert!(makefile.code().contains("else ifeq"));
5261 assert!(makefile.code().contains("TARGET"));
5262
5263 let content = r#"
5265ifdef WINDOWS
5266 TARGET = windows
5267else ifdef DARWIN
5268 TARGET = macos
5269else ifdef LINUX
5270 TARGET = linux
5271else
5272 TARGET = unknown
5273endif
5274"#;
5275 let mut buf = content.as_bytes();
5276 let makefile =
5277 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifdef directive");
5278 assert!(makefile.code().contains("else ifdef"));
5279
5280 let content = r#"
5282ifndef NOWINDOWS
5283 TARGET = windows
5284else ifndef NODARWIN
5285 TARGET = macos
5286else
5287 TARGET = linux
5288endif
5289"#;
5290 let mut buf = content.as_bytes();
5291 let makefile =
5292 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifndef directive");
5293 assert!(makefile.code().contains("else ifndef"));
5294
5295 let content = r#"
5297ifneq ($(OS),Windows_NT)
5298 TARGET = not_windows
5299else ifneq ($(OS),Darwin)
5300 TARGET = not_macos
5301else
5302 TARGET = darwin
5303endif
5304"#;
5305 let mut buf = content.as_bytes();
5306 let makefile =
5307 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifneq directive");
5308 assert!(makefile.code().contains("else ifneq"));
5309 }
5310
5311 #[test]
5312 fn test_complex_else_conditionals() {
5313 let content = r#"VAR1 := foo
5315VAR2 := bar
5316
5317ifeq ($(VAR1),foo)
5318 RESULT := foo_matched
5319else ifdef VAR2
5320 RESULT := var2_defined
5321else ifndef VAR3
5322 RESULT := var3_not_defined
5323else
5324 RESULT := final_else
5325endif
5326
5327all:
5328 @echo $(RESULT)
5329"#;
5330 let mut buf = content.as_bytes();
5331 let makefile =
5332 Makefile::read_relaxed(&mut buf).expect("Failed to parse complex else conditionals");
5333
5334 let code = makefile.code();
5336 assert!(code.contains("ifeq ($(VAR1),foo)"));
5337 assert!(code.contains("else ifdef VAR2"));
5338 assert!(code.contains("else ifndef VAR3"));
5339 assert!(code.contains("else"));
5340 assert!(code.contains("endif"));
5341 assert!(code.contains("RESULT"));
5342
5343 let rules: Vec<_> = makefile.rules().collect();
5345 assert_eq!(rules.len(), 1);
5346 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["all"]);
5347 }
5348
5349 #[test]
5350 fn test_conditional_token_structure() {
5351 let content = r#"ifdef VAR1
5353X := 1
5354else ifdef VAR2
5355X := 2
5356else
5357X := 3
5358endif
5359"#;
5360 let mut buf = content.as_bytes();
5361 let makefile = Makefile::read_relaxed(&mut buf).unwrap();
5362
5363 let syntax = makefile.syntax();
5365
5366 let mut found_conditional = false;
5368 let mut found_conditional_if = false;
5369 let mut found_conditional_else = false;
5370 let mut found_conditional_endif = false;
5371
5372 fn check_node(
5373 node: &SyntaxNode,
5374 found_cond: &mut bool,
5375 found_if: &mut bool,
5376 found_else: &mut bool,
5377 found_endif: &mut bool,
5378 ) {
5379 match node.kind() {
5380 SyntaxKind::CONDITIONAL => *found_cond = true,
5381 SyntaxKind::CONDITIONAL_IF => *found_if = true,
5382 SyntaxKind::CONDITIONAL_ELSE => *found_else = true,
5383 SyntaxKind::CONDITIONAL_ENDIF => *found_endif = true,
5384 _ => {}
5385 }
5386
5387 for child in node.children() {
5388 check_node(&child, found_cond, found_if, found_else, found_endif);
5389 }
5390 }
5391
5392 check_node(
5393 &syntax,
5394 &mut found_conditional,
5395 &mut found_conditional_if,
5396 &mut found_conditional_else,
5397 &mut found_conditional_endif,
5398 );
5399
5400 assert!(found_conditional, "Should have CONDITIONAL node");
5401 assert!(found_conditional_if, "Should have CONDITIONAL_IF node");
5402 assert!(found_conditional_else, "Should have CONDITIONAL_ELSE node");
5403 assert!(
5404 found_conditional_endif,
5405 "Should have CONDITIONAL_ENDIF node"
5406 );
5407 }
5408
5409 #[test]
5410 fn test_ambiguous_assignment_vs_rule() {
5411 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
5413
5414 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
5415 let makefile =
5416 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
5417
5418 let vars = makefile.variable_definitions().collect::<Vec<_>>();
5419 let rules = makefile.rules().collect::<Vec<_>>();
5420
5421 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
5422 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
5423
5424 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
5425
5426 const SIMPLE_RULE: &str = "target: dependency\n";
5428
5429 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
5430 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
5431
5432 let vars = makefile.variable_definitions().collect::<Vec<_>>();
5433 let rules = makefile.rules().collect::<Vec<_>>();
5434
5435 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
5436 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
5437
5438 let rule = &rules[0];
5439 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
5440 }
5441
5442 #[test]
5443 fn test_nested_conditionals() {
5444 let content = r#"
5445ifdef RELEASE
5446 CFLAGS += -O3
5447 ifndef DEBUG
5448 ifneq ($(ARCH),arm)
5449 CFLAGS += -march=native
5450 else
5451 CFLAGS += -mcpu=cortex-a72
5452 endif
5453 endif
5454endif
5455"#;
5456 let mut buf = content.as_bytes();
5458 let makefile =
5459 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
5460
5461 let code = makefile.code();
5463 assert!(code.contains("ifdef RELEASE"));
5464 assert!(code.contains("ifndef DEBUG"));
5465 assert!(code.contains("ifneq"));
5466 }
5467
5468 #[test]
5469 fn test_space_indented_recipes() {
5470 let content = r#"
5473build:
5474 @echo "Building with spaces instead of tabs"
5475 gcc -o program main.c
5476"#;
5477 let mut buf = content.as_bytes();
5479 let makefile =
5480 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
5481
5482 let rules = makefile.rules().collect::<Vec<_>>();
5484 assert!(!rules.is_empty(), "Expected at least one rule");
5485
5486 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
5488 assert!(build_rule.is_some(), "Expected to find build rule");
5489 }
5490
5491 #[test]
5492 fn test_complex_variable_functions() {
5493 let content = r#"
5494FILES := $(shell find . -name "*.c")
5495OBJS := $(patsubst %.c,%.o,$(FILES))
5496NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
5497HEADERS := ${wildcard *.h}
5498"#;
5499 let parsed = parse(content, None);
5500 assert!(
5501 parsed.errors.is_empty(),
5502 "Failed to parse complex variable functions: {:?}",
5503 parsed.errors
5504 );
5505 }
5506
5507 #[test]
5508 fn test_nested_variable_expansions() {
5509 let content = r#"
5510VERSION = 1.0
5511PACKAGE = myapp
5512TARBALL = $(PACKAGE)-$(VERSION).tar.gz
5513INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
5514"#;
5515 let parsed = parse(content, None);
5516 assert!(
5517 parsed.errors.is_empty(),
5518 "Failed to parse nested variable expansions: {:?}",
5519 parsed.errors
5520 );
5521 }
5522
5523 #[test]
5524 fn test_special_directives() {
5525 let content = r#"
5526# Special makefile directives
5527.PHONY: all clean
5528.SUFFIXES: .c .o
5529.DEFAULT: all
5530
5531# Variable definition and export directive
5532export PATH := /usr/bin:/bin
5533"#;
5534 let mut buf = content.as_bytes();
5536 let makefile =
5537 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
5538
5539 let rules = makefile.rules().collect::<Vec<_>>();
5541
5542 let phony_rule = rules
5544 .iter()
5545 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
5546 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
5547
5548 let vars = makefile.variable_definitions().collect::<Vec<_>>();
5550 assert!(!vars.is_empty(), "Expected to find at least one variable");
5551 }
5552
5553 #[test]
5556 fn test_comprehensive_real_world_makefile() {
5557 let content = r#"
5559# Basic variable assignment
5560VERSION = 1.0.0
5561
5562# Phony target
5563.PHONY: all clean
5564
5565# Simple rule
5566all:
5567 echo "Building version $(VERSION)"
5568
5569# Another rule with dependencies
5570clean:
5571 rm -f *.o
5572"#;
5573
5574 let parsed = parse(content, None);
5576
5577 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
5579
5580 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
5582 assert!(!variables.is_empty(), "Expected at least one variable");
5583 assert_eq!(
5584 variables[0].name(),
5585 Some("VERSION".to_string()),
5586 "Expected VERSION variable"
5587 );
5588
5589 let rules = parsed.root().rules().collect::<Vec<_>>();
5591 assert!(!rules.is_empty(), "Expected at least one rule");
5592
5593 let rule_targets: Vec<String> = rules
5595 .iter()
5596 .flat_map(|r| r.targets().collect::<Vec<_>>())
5597 .collect();
5598 assert!(
5599 rule_targets.contains(&".PHONY".to_string()),
5600 "Expected .PHONY rule"
5601 );
5602 assert!(
5603 rule_targets.contains(&"all".to_string()),
5604 "Expected 'all' rule"
5605 );
5606 assert!(
5607 rule_targets.contains(&"clean".to_string()),
5608 "Expected 'clean' rule"
5609 );
5610 }
5611
5612 #[test]
5613 fn test_indented_help_text_outside_rules() {
5614 let content = r#"
5616# Targets with help text
5617help:
5618 @echo "Available targets:"
5619 @echo " build build the project"
5620 @echo " test run tests"
5621 @echo " clean clean build artifacts"
5622
5623# Another target
5624clean:
5625 rm -rf build/
5626"#;
5627
5628 let parsed = parse(content, None);
5630
5631 assert!(
5633 parsed.errors.is_empty(),
5634 "Failed to parse indented help text"
5635 );
5636
5637 let rules = parsed.root().rules().collect::<Vec<_>>();
5639 assert_eq!(rules.len(), 2, "Expected to find two rules");
5640
5641 let help_rule = rules
5643 .iter()
5644 .find(|r| r.targets().any(|t| t == "help"))
5645 .expect("Expected to find help rule");
5646
5647 let clean_rule = rules
5648 .iter()
5649 .find(|r| r.targets().any(|t| t == "clean"))
5650 .expect("Expected to find clean rule");
5651
5652 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
5654 assert!(
5655 !help_recipes.is_empty(),
5656 "Help rule should have recipe lines"
5657 );
5658 assert!(
5659 help_recipes
5660 .iter()
5661 .any(|line| line.contains("Available targets")),
5662 "Help recipes should include 'Available targets' line"
5663 );
5664
5665 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
5667 assert!(
5668 !clean_recipes.is_empty(),
5669 "Clean rule should have recipe lines"
5670 );
5671 assert!(
5672 clean_recipes.iter().any(|line| line.contains("rm -rf")),
5673 "Clean recipes should include 'rm -rf' command"
5674 );
5675 }
5676
5677 #[test]
5678 fn test_makefile1_phony_pattern() {
5679 let content = "#line 2145\n.PHONY: $(PHONY)\n";
5681
5682 let result = parse(content, None);
5684
5685 assert!(
5687 result.errors.is_empty(),
5688 "Failed to parse .PHONY: $(PHONY) pattern"
5689 );
5690
5691 let rules = result.root().rules().collect::<Vec<_>>();
5693 assert_eq!(rules.len(), 1, "Expected 1 rule");
5694 assert_eq!(
5695 rules[0].targets().next().unwrap(),
5696 ".PHONY",
5697 "Expected .PHONY rule"
5698 );
5699
5700 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
5702 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
5703 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
5704 }
5705
5706 #[test]
5707 fn test_skip_until_newline_behavior() {
5708 let input = "text without newline";
5710 let parsed = parse(input, None);
5711 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
5713
5714 let input_with_newline = "text\nafter newline";
5715 let parsed2 = parse(input_with_newline, None);
5716 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
5717 }
5718
5719 #[test]
5720 #[ignore] fn test_error_with_indent_token() {
5722 let input = "\tinvalid indented line";
5724 let parsed = parse(input, None);
5725 assert!(!parsed.errors.is_empty());
5727
5728 let error_msg = &parsed.errors[0].message;
5729 assert!(error_msg.contains("recipe commences before first target"));
5730 }
5731
5732 #[test]
5733 fn test_conditional_token_handling() {
5734 let input = r#"
5736ifndef VAR
5737 CFLAGS = -DTEST
5738endif
5739"#;
5740 let parsed = parse(input, None);
5741 let makefile = parsed.root();
5743 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
5744 let nested = r#"
5748ifdef DEBUG
5749 ifndef RELEASE
5750 CFLAGS = -g
5751 endif
5752endif
5753"#;
5754 let parsed_nested = parse(nested, None);
5755 let _makefile = parsed_nested.root();
5757 }
5758
5759 #[test]
5760 fn test_include_vs_conditional_logic() {
5761 let input = r#"
5763include file.mk
5764ifdef VAR
5765 VALUE = 1
5766endif
5767"#;
5768 let parsed = parse(input, None);
5769 let makefile = parsed.root();
5771 let includes = makefile.includes().collect::<Vec<_>>();
5772 assert!(includes.len() >= 1 || parsed.errors.len() > 0);
5774
5775 let optional_include = r#"
5777-include optional.mk
5778ifndef VAR
5779 VALUE = default
5780endif
5781"#;
5782 let parsed2 = parse(optional_include, None);
5783 let _makefile = parsed2.root();
5785 }
5786
5787 #[test]
5788 fn test_balanced_parens_counting() {
5789 let input = r#"
5791VAR = $(call func,$(nested,arg),extra)
5792COMPLEX = $(if $(condition),$(then_val),$(else_val))
5793"#;
5794 let parsed = parse(input, None);
5795 assert!(parsed.errors.is_empty());
5796
5797 let makefile = parsed.root();
5798 let vars = makefile.variable_definitions().collect::<Vec<_>>();
5799 assert_eq!(vars.len(), 2);
5800 }
5801
5802 #[test]
5803 fn test_documentation_lookahead() {
5804 let input = r#"
5806# Documentation comment
5807help:
5808 @echo "Usage instructions"
5809 @echo "More help text"
5810"#;
5811 let parsed = parse(input, None);
5812 assert!(parsed.errors.is_empty());
5813
5814 let makefile = parsed.root();
5815 let rules = makefile.rules().collect::<Vec<_>>();
5816 assert_eq!(rules.len(), 1);
5817 assert_eq!(rules[0].targets().next().unwrap(), "help");
5818 }
5819
5820 #[test]
5821 fn test_edge_case_empty_input() {
5822 let parsed = parse("", None);
5824 assert!(parsed.errors.is_empty());
5825
5826 let parsed2 = parse(" \n \n", None);
5828 let _makefile = parsed2.root();
5831 }
5832
5833 #[test]
5834 fn test_malformed_conditional_recovery() {
5835 let input = r#"
5837ifdef
5838 # Missing condition variable
5839endif
5840"#;
5841 let parsed = parse(input, None);
5842 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
5845 }
5846
5847 #[test]
5848 fn test_replace_rule() {
5849 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
5850 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
5851
5852 makefile.replace_rule(0, new_rule).unwrap();
5853
5854 let targets: Vec<_> = makefile
5855 .rules()
5856 .flat_map(|r| r.targets().collect::<Vec<_>>())
5857 .collect();
5858 assert_eq!(targets, vec!["new_rule", "rule2"]);
5859
5860 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
5861 assert_eq!(recipes, vec!["new_command"]);
5862 }
5863
5864 #[test]
5865 fn test_replace_rule_out_of_bounds() {
5866 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
5867 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
5868
5869 let result = makefile.replace_rule(5, new_rule);
5870 assert!(result.is_err());
5871 }
5872
5873 #[test]
5874 fn test_remove_rule() {
5875 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
5876 .parse()
5877 .unwrap();
5878
5879 let removed = makefile.remove_rule(1).unwrap();
5880 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
5881
5882 let remaining_targets: Vec<_> = makefile
5883 .rules()
5884 .flat_map(|r| r.targets().collect::<Vec<_>>())
5885 .collect();
5886 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
5887 assert_eq!(makefile.rules().count(), 2);
5888 }
5889
5890 #[test]
5891 fn test_remove_rule_out_of_bounds() {
5892 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
5893
5894 let result = makefile.remove_rule(5);
5895 assert!(result.is_err());
5896 }
5897
5898 #[test]
5899 fn test_insert_rule() {
5900 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
5901 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
5902
5903 makefile.insert_rule(1, new_rule).unwrap();
5904
5905 let targets: Vec<_> = makefile
5906 .rules()
5907 .flat_map(|r| r.targets().collect::<Vec<_>>())
5908 .collect();
5909 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
5910 assert_eq!(makefile.rules().count(), 3);
5911 }
5912
5913 #[test]
5914 fn test_insert_rule_at_end() {
5915 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
5916 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
5917
5918 makefile.insert_rule(1, new_rule).unwrap();
5919
5920 let targets: Vec<_> = makefile
5921 .rules()
5922 .flat_map(|r| r.targets().collect::<Vec<_>>())
5923 .collect();
5924 assert_eq!(targets, vec!["rule1", "end_rule"]);
5925 }
5926
5927 #[test]
5928 fn test_insert_rule_out_of_bounds() {
5929 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
5930 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
5931
5932 let result = makefile.insert_rule(5, new_rule);
5933 assert!(result.is_err());
5934 }
5935
5936 #[test]
5937 fn test_insert_rule_preserves_blank_line_spacing_at_end() {
5938 let input = "rule1:\n\tcommand1\n\nrule2:\n\tcommand2\n";
5940 let mut makefile: Makefile = input.parse().unwrap();
5941 let new_rule = Rule::new(&["rule3"], &[], &["command3"]);
5942
5943 makefile.insert_rule(2, new_rule).unwrap();
5944
5945 let expected = "rule1:\n\tcommand1\n\nrule2:\n\tcommand2\n\nrule3:\n\tcommand3\n";
5946 assert_eq!(makefile.to_string(), expected);
5947 }
5948
5949 #[test]
5950 fn test_insert_rule_adds_blank_lines_when_missing() {
5951 let input = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n";
5953 let mut makefile: Makefile = input.parse().unwrap();
5954 let new_rule = Rule::new(&["rule3"], &[], &["command3"]);
5955
5956 makefile.insert_rule(2, new_rule).unwrap();
5957
5958 let expected = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n\nrule3:\n\tcommand3\n";
5959 assert_eq!(makefile.to_string(), expected);
5960 }
5961
5962 #[test]
5963 fn test_remove_command() {
5964 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
5965 .parse()
5966 .unwrap();
5967
5968 rule.remove_command(1);
5969 let recipes: Vec<_> = rule.recipes().collect();
5970 assert_eq!(recipes, vec!["command1", "command3"]);
5971 assert_eq!(rule.recipe_count(), 2);
5972 }
5973
5974 #[test]
5975 fn test_remove_command_out_of_bounds() {
5976 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
5977
5978 let result = rule.remove_command(5);
5979 assert!(!result);
5980 }
5981
5982 #[test]
5983 fn test_insert_command() {
5984 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
5985
5986 rule.insert_command(1, "command2");
5987 let recipes: Vec<_> = rule.recipes().collect();
5988 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
5989 }
5990
5991 #[test]
5992 fn test_insert_command_at_end() {
5993 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
5994
5995 rule.insert_command(1, "command2");
5996 let recipes: Vec<_> = rule.recipes().collect();
5997 assert_eq!(recipes, vec!["command1", "command2"]);
5998 }
5999
6000 #[test]
6001 fn test_insert_command_in_empty_rule() {
6002 let mut rule: Rule = "rule:\n".parse().unwrap();
6003
6004 rule.insert_command(0, "new_command");
6005 let recipes: Vec<_> = rule.recipes().collect();
6006 assert_eq!(recipes, vec!["new_command"]);
6007 }
6008
6009 #[test]
6010 fn test_recipe_count() {
6011 let rule1: Rule = "rule:\n".parse().unwrap();
6012 assert_eq!(rule1.recipe_count(), 0);
6013
6014 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
6015 assert_eq!(rule2.recipe_count(), 2);
6016 }
6017
6018 #[test]
6019 fn test_clear_commands() {
6020 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
6021 .parse()
6022 .unwrap();
6023
6024 rule.clear_commands();
6025 assert_eq!(rule.recipe_count(), 0);
6026
6027 let recipes: Vec<_> = rule.recipes().collect();
6028 assert_eq!(recipes, Vec::<String>::new());
6029
6030 let targets: Vec<_> = rule.targets().collect();
6032 assert_eq!(targets, vec!["rule"]);
6033 }
6034
6035 #[test]
6036 fn test_clear_commands_empty_rule() {
6037 let mut rule: Rule = "rule:\n".parse().unwrap();
6038
6039 rule.clear_commands();
6040 assert_eq!(rule.recipe_count(), 0);
6041
6042 let targets: Vec<_> = rule.targets().collect();
6043 assert_eq!(targets, vec!["rule"]);
6044 }
6045
6046 #[test]
6047 fn test_rule_manipulation_preserves_structure() {
6048 let input = r#"# Comment
6050VAR = value
6051
6052rule1:
6053 command1
6054
6055# Another comment
6056rule2:
6057 command2
6058
6059VAR2 = value2
6060"#;
6061
6062 let mut makefile: Makefile = input.parse().unwrap();
6063 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
6064
6065 makefile.insert_rule(1, new_rule).unwrap();
6067
6068 let targets: Vec<_> = makefile
6070 .rules()
6071 .flat_map(|r| r.targets().collect::<Vec<_>>())
6072 .collect();
6073 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
6074
6075 let vars: Vec<_> = makefile.variable_definitions().collect();
6077 assert_eq!(vars.len(), 2);
6078
6079 let output = makefile.code();
6081 assert!(output.contains("# Comment"));
6082 assert!(output.contains("VAR = value"));
6083 assert!(output.contains("# Another comment"));
6084 assert!(output.contains("VAR2 = value2"));
6085 }
6086
6087 #[test]
6088 fn test_replace_rule_with_multiple_targets() {
6089 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
6090 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
6091
6092 makefile.replace_rule(0, new_rule).unwrap();
6093
6094 let targets: Vec<_> = makefile
6095 .rules()
6096 .flat_map(|r| r.targets().collect::<Vec<_>>())
6097 .collect();
6098 assert_eq!(targets, vec!["new_target"]);
6099 }
6100
6101 #[test]
6102 fn test_empty_makefile_operations() {
6103 let mut makefile = Makefile::new();
6104
6105 assert!(makefile
6107 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
6108 .is_err());
6109 assert!(makefile.remove_rule(0).is_err());
6110
6111 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
6113 makefile.insert_rule(0, new_rule).unwrap();
6114 assert_eq!(makefile.rules().count(), 1);
6115 }
6116
6117 #[test]
6118 fn test_command_operations_preserve_indentation() {
6119 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
6120 .parse()
6121 .unwrap();
6122
6123 rule.insert_command(1, "middle_command");
6124 let recipes: Vec<_> = rule.recipes().collect();
6125 assert_eq!(
6126 recipes,
6127 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
6128 );
6129 }
6130
6131 #[test]
6132 fn test_rule_operations_with_variables_and_includes() {
6133 let input = r#"VAR1 = value1
6134include common.mk
6135
6136rule1:
6137 command1
6138
6139VAR2 = value2
6140include other.mk
6141
6142rule2:
6143 command2
6144"#;
6145
6146 let mut makefile: Makefile = input.parse().unwrap();
6147
6148 makefile.remove_rule(0).unwrap();
6150
6151 let output = makefile.code();
6153 assert!(output.contains("VAR1 = value1"));
6154 assert!(output.contains("include common.mk"));
6155 assert!(output.contains("VAR2 = value2"));
6156 assert!(output.contains("include other.mk"));
6157
6158 assert_eq!(makefile.rules().count(), 1);
6160 let remaining_targets: Vec<_> = makefile
6161 .rules()
6162 .flat_map(|r| r.targets().collect::<Vec<_>>())
6163 .collect();
6164 assert_eq!(remaining_targets, vec!["rule2"]);
6165 }
6166
6167 #[test]
6168 fn test_command_manipulation_edge_cases() {
6169 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
6171 assert_eq!(empty_rule.recipe_count(), 0);
6172
6173 empty_rule.insert_command(0, "first_command");
6174 assert_eq!(empty_rule.recipe_count(), 1);
6175
6176 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
6178 empty_rule2.clear_commands();
6179 assert_eq!(empty_rule2.recipe_count(), 0);
6180 }
6181
6182 #[test]
6183 fn test_archive_member_parsing() {
6184 let input = "libfoo.a(bar.o): bar.c\n\tgcc -c bar.c -o bar.o\n\tar r libfoo.a bar.o\n";
6186 let parsed = parse(input, None);
6187 assert!(
6188 parsed.errors.is_empty(),
6189 "Should parse archive member without errors"
6190 );
6191
6192 let makefile = parsed.root();
6193 let rules: Vec<_> = makefile.rules().collect();
6194 assert_eq!(rules.len(), 1);
6195
6196 let target_text = rules[0].targets().next().unwrap();
6198 assert_eq!(target_text, "libfoo.a(bar.o)");
6199 }
6200
6201 #[test]
6202 fn test_archive_member_multiple_members() {
6203 let input = "libfoo.a(bar.o baz.o): bar.c baz.c\n\tgcc -c bar.c baz.c\n\tar r libfoo.a bar.o baz.o\n";
6205 let parsed = parse(input, None);
6206 assert!(
6207 parsed.errors.is_empty(),
6208 "Should parse multiple archive members"
6209 );
6210
6211 let makefile = parsed.root();
6212 let rules: Vec<_> = makefile.rules().collect();
6213 assert_eq!(rules.len(), 1);
6214 }
6215
6216 #[test]
6217 fn test_archive_member_in_dependencies() {
6218 let input =
6220 "program: main.o libfoo.a(bar.o) libfoo.a(baz.o)\n\tgcc -o program main.o libfoo.a\n";
6221 let parsed = parse(input, None);
6222 assert!(
6223 parsed.errors.is_empty(),
6224 "Should parse archive members in dependencies"
6225 );
6226
6227 let makefile = parsed.root();
6228 let rules: Vec<_> = makefile.rules().collect();
6229 assert_eq!(rules.len(), 1);
6230 }
6231
6232 #[test]
6233 fn test_archive_member_with_variables() {
6234 let input = "$(LIB)($(OBJ)): $(SRC)\n\t$(CC) -c $(SRC)\n\t$(AR) r $(LIB) $(OBJ)\n";
6236 let parsed = parse(input, None);
6237 assert!(
6239 parsed.errors.is_empty(),
6240 "Should parse archive members with variables"
6241 );
6242 }
6243
6244 #[test]
6245 fn test_archive_member_ast_access() {
6246 let input = "libtest.a(foo.o bar.o): foo.c bar.c\n\tgcc -c foo.c bar.c\n";
6248 let parsed = parse(input, None);
6249 let makefile = parsed.root();
6250
6251 let archive_member_count = makefile
6253 .syntax()
6254 .descendants()
6255 .filter(|n| n.kind() == ARCHIVE_MEMBERS)
6256 .count();
6257
6258 assert!(
6259 archive_member_count > 0,
6260 "Should find ARCHIVE_MEMBERS nodes in AST"
6261 );
6262 }
6263
6264 #[test]
6265 fn test_large_makefile_performance() {
6266 let mut makefile = Makefile::new();
6268
6269 for i in 0..100 {
6271 let rule_name = format!("rule{}", i);
6272 let _rule = makefile
6273 .add_rule(&rule_name)
6274 .push_command(&format!("command{}", i));
6275 }
6276
6277 assert_eq!(makefile.rules().count(), 100);
6278
6279 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
6281 makefile.replace_rule(50, new_rule).unwrap();
6282
6283 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
6285 assert_eq!(rule_50_targets, vec!["middle_rule"]);
6286
6287 assert_eq!(makefile.rules().count(), 100); }
6289
6290 #[test]
6291 fn test_complex_recipe_manipulation() {
6292 let mut complex_rule: Rule = r#"complex:
6293 @echo "Starting build"
6294 $(CC) $(CFLAGS) -o $@ $<
6295 @echo "Build complete"
6296 chmod +x $@
6297"#
6298 .parse()
6299 .unwrap();
6300
6301 assert_eq!(complex_rule.recipe_count(), 4);
6302
6303 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
6308 assert_eq!(final_recipes.len(), 2);
6309 assert!(final_recipes[0].contains("$(CC)"));
6310 assert!(final_recipes[1].contains("chmod"));
6311 }
6312
6313 #[test]
6314 fn test_variable_definition_remove() {
6315 let makefile: Makefile = r#"VAR1 = value1
6316VAR2 = value2
6317VAR3 = value3
6318"#
6319 .parse()
6320 .unwrap();
6321
6322 assert_eq!(makefile.variable_definitions().count(), 3);
6324
6325 let mut var2 = makefile
6327 .variable_definitions()
6328 .nth(1)
6329 .expect("Should have second variable");
6330 assert_eq!(var2.name(), Some("VAR2".to_string()));
6331 var2.remove();
6332
6333 assert_eq!(makefile.variable_definitions().count(), 2);
6335 let var_names: Vec<_> = makefile
6336 .variable_definitions()
6337 .filter_map(|v| v.name())
6338 .collect();
6339 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
6340 }
6341
6342 #[test]
6343 fn test_variable_definition_set_value() {
6344 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
6345
6346 let mut var = makefile
6347 .variable_definitions()
6348 .next()
6349 .expect("Should have variable");
6350 assert_eq!(var.raw_value(), Some("old_value".to_string()));
6351
6352 var.set_value("new_value");
6354
6355 assert_eq!(var.raw_value(), Some("new_value".to_string()));
6357 assert!(makefile.code().contains("VAR = new_value"));
6358 }
6359
6360 #[test]
6361 fn test_variable_definition_set_value_preserves_format() {
6362 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
6363
6364 let mut var = makefile
6365 .variable_definitions()
6366 .next()
6367 .expect("Should have variable");
6368 assert_eq!(var.raw_value(), Some("old_value".to_string()));
6369
6370 var.set_value("new_value");
6372
6373 assert_eq!(var.raw_value(), Some("new_value".to_string()));
6375 let code = makefile.code();
6376 assert!(code.contains("export"), "Should preserve export prefix");
6377 assert!(code.contains(":="), "Should preserve := operator");
6378 assert!(code.contains("new_value"), "Should have new value");
6379 }
6380
6381 #[test]
6382 fn test_makefile_find_variable() {
6383 let makefile: Makefile = r#"VAR1 = value1
6384VAR2 = value2
6385VAR3 = value3
6386"#
6387 .parse()
6388 .unwrap();
6389
6390 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
6392 assert_eq!(vars.len(), 1);
6393 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
6394 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
6395
6396 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
6398 }
6399
6400 #[test]
6401 fn test_makefile_find_variable_with_export() {
6402 let makefile: Makefile = r#"VAR1 = value1
6403export VAR2 := value2
6404VAR3 = value3
6405"#
6406 .parse()
6407 .unwrap();
6408
6409 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
6411 assert_eq!(vars.len(), 1);
6412 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
6413 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
6414 }
6415
6416 #[test]
6417 fn test_variable_definition_is_export() {
6418 let makefile: Makefile = r#"VAR1 = value1
6419export VAR2 := value2
6420export VAR3 = value3
6421VAR4 := value4
6422"#
6423 .parse()
6424 .unwrap();
6425
6426 let vars: Vec<_> = makefile.variable_definitions().collect();
6427 assert_eq!(vars.len(), 4);
6428
6429 assert_eq!(vars[0].is_export(), false);
6430 assert_eq!(vars[1].is_export(), true);
6431 assert_eq!(vars[2].is_export(), true);
6432 assert_eq!(vars[3].is_export(), false);
6433 }
6434
6435 #[test]
6436 fn test_makefile_find_variable_multiple() {
6437 let makefile: Makefile = r#"VAR1 = value1
6438VAR1 = value2
6439VAR2 = other
6440VAR1 = value3
6441"#
6442 .parse()
6443 .unwrap();
6444
6445 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
6447 assert_eq!(vars.len(), 3);
6448 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
6449 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
6450 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
6451
6452 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
6454 assert_eq!(var2s.len(), 1);
6455 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
6456 }
6457
6458 #[test]
6459 fn test_variable_remove_and_find() {
6460 let makefile: Makefile = r#"VAR1 = value1
6461VAR2 = value2
6462VAR3 = value3
6463"#
6464 .parse()
6465 .unwrap();
6466
6467 let mut var2 = makefile
6469 .find_variable("VAR2")
6470 .next()
6471 .expect("Should find VAR2");
6472 var2.remove();
6473
6474 assert_eq!(makefile.find_variable("VAR2").count(), 0);
6476
6477 assert_eq!(makefile.find_variable("VAR1").count(), 1);
6479 assert_eq!(makefile.find_variable("VAR3").count(), 1);
6480 }
6481
6482 #[test]
6483 fn test_variable_remove_with_comment() {
6484 let makefile: Makefile = r#"VAR1 = value1
6485# This is a comment about VAR2
6486VAR2 = value2
6487VAR3 = value3
6488"#
6489 .parse()
6490 .unwrap();
6491
6492 let mut var2 = makefile
6494 .variable_definitions()
6495 .nth(1)
6496 .expect("Should have second variable");
6497 assert_eq!(var2.name(), Some("VAR2".to_string()));
6498 var2.remove();
6499
6500 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
6502 }
6503
6504 #[test]
6505 fn test_variable_remove_with_multiple_comments() {
6506 let makefile: Makefile = r#"VAR1 = value1
6507# Comment line 1
6508# Comment line 2
6509# Comment line 3
6510VAR2 = value2
6511VAR3 = value3
6512"#
6513 .parse()
6514 .unwrap();
6515
6516 let mut var2 = makefile
6518 .variable_definitions()
6519 .nth(1)
6520 .expect("Should have second variable");
6521 var2.remove();
6522
6523 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
6525 }
6526
6527 #[test]
6528 fn test_variable_remove_with_empty_line() {
6529 let makefile: Makefile = r#"VAR1 = value1
6530
6531# Comment about VAR2
6532VAR2 = value2
6533VAR3 = value3
6534"#
6535 .parse()
6536 .unwrap();
6537
6538 let mut var2 = makefile
6540 .variable_definitions()
6541 .nth(1)
6542 .expect("Should have second variable");
6543 var2.remove();
6544
6545 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
6548 }
6549
6550 #[test]
6551 fn test_variable_remove_with_multiple_empty_lines() {
6552 let makefile: Makefile = r#"VAR1 = value1
6553
6554
6555# Comment about VAR2
6556VAR2 = value2
6557VAR3 = value3
6558"#
6559 .parse()
6560 .unwrap();
6561
6562 let mut var2 = makefile
6564 .variable_definitions()
6565 .nth(1)
6566 .expect("Should have second variable");
6567 var2.remove();
6568
6569 assert_eq!(makefile.code(), "VAR1 = value1\n\nVAR3 = value3\n");
6572 }
6573
6574 #[test]
6575 fn test_rule_remove_with_comment() {
6576 let makefile: Makefile = r#"rule1:
6577 command1
6578
6579# Comment about rule2
6580rule2:
6581 command2
6582rule3:
6583 command3
6584"#
6585 .parse()
6586 .unwrap();
6587
6588 let rule2 = makefile.rules().nth(1).expect("Should have second rule");
6590 rule2.remove().unwrap();
6591
6592 assert_eq!(
6595 makefile.code(),
6596 "rule1:\n\tcommand1\n\nrule3:\n\tcommand3\n"
6597 );
6598 }
6599
6600 #[test]
6601 fn test_variable_remove_preserves_shebang() {
6602 let makefile: Makefile = r#"#!/usr/bin/make -f
6603# This is a regular comment
6604VAR1 = value1
6605VAR2 = value2
6606"#
6607 .parse()
6608 .unwrap();
6609
6610 let mut var1 = makefile.variable_definitions().next().unwrap();
6612 var1.remove();
6613
6614 let code = makefile.code();
6616 assert!(code.starts_with("#!/usr/bin/make -f"));
6617 assert!(!code.contains("regular comment"));
6618 assert!(!code.contains("VAR1"));
6619 assert!(code.contains("VAR2"));
6620 }
6621
6622 #[test]
6623 fn test_variable_remove_preserves_subsequent_comments() {
6624 let makefile: Makefile = r#"VAR1 = value1
6625# Comment about VAR2
6626VAR2 = value2
6627
6628# Comment about VAR3
6629VAR3 = value3
6630"#
6631 .parse()
6632 .unwrap();
6633
6634 let mut var2 = makefile
6636 .variable_definitions()
6637 .nth(1)
6638 .expect("Should have second variable");
6639 var2.remove();
6640
6641 let code = makefile.code();
6643 assert_eq!(
6644 code,
6645 "VAR1 = value1\n\n# Comment about VAR3\nVAR3 = value3\n"
6646 );
6647 }
6648
6649 #[test]
6650 fn test_variable_remove_after_shebang_preserves_empty_line() {
6651 let makefile: Makefile = r#"#!/usr/bin/make -f
6652export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
6653
6654%:
6655 dh $@
6656"#
6657 .parse()
6658 .unwrap();
6659
6660 let mut var = makefile.variable_definitions().next().unwrap();
6662 var.remove();
6663
6664 assert_eq!(makefile.code(), "#!/usr/bin/make -f\n\n%:\n\tdh $@\n");
6666 }
6667
6668 #[test]
6669 fn test_rule_add_prerequisite() {
6670 let mut rule: Rule = "target: dep1\n".parse().unwrap();
6671 rule.add_prerequisite("dep2").unwrap();
6672 assert_eq!(
6673 rule.prerequisites().collect::<Vec<_>>(),
6674 vec!["dep1", "dep2"]
6675 );
6676 assert_eq!(rule.to_string(), "target: dep1 dep2\n");
6678 }
6679
6680 #[test]
6681 fn test_rule_add_prerequisite_to_rule_without_prereqs() {
6682 let mut rule: Rule = "target:\n".parse().unwrap();
6684 rule.add_prerequisite("dep1").unwrap();
6685 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dep1"]);
6686 assert_eq!(rule.to_string(), "target: dep1\n");
6688 }
6689
6690 #[test]
6691 fn test_rule_remove_prerequisite() {
6692 let mut rule: Rule = "target: dep1 dep2 dep3\n".parse().unwrap();
6693 assert!(rule.remove_prerequisite("dep2").unwrap());
6694 assert_eq!(
6695 rule.prerequisites().collect::<Vec<_>>(),
6696 vec!["dep1", "dep3"]
6697 );
6698 assert!(!rule.remove_prerequisite("nonexistent").unwrap());
6699 }
6700
6701 #[test]
6702 fn test_rule_set_prerequisites() {
6703 let mut rule: Rule = "target: old_dep\n".parse().unwrap();
6704 rule.set_prerequisites(vec!["new_dep1", "new_dep2"])
6705 .unwrap();
6706 assert_eq!(
6707 rule.prerequisites().collect::<Vec<_>>(),
6708 vec!["new_dep1", "new_dep2"]
6709 );
6710 }
6711
6712 #[test]
6713 fn test_rule_set_prerequisites_empty() {
6714 let mut rule: Rule = "target: dep1 dep2\n".parse().unwrap();
6715 rule.set_prerequisites(vec![]).unwrap();
6716 assert_eq!(rule.prerequisites().collect::<Vec<_>>().len(), 0);
6717 }
6718
6719 #[test]
6720 fn test_rule_add_target() {
6721 let mut rule: Rule = "target1: dep1\n".parse().unwrap();
6722 rule.add_target("target2").unwrap();
6723 assert_eq!(
6724 rule.targets().collect::<Vec<_>>(),
6725 vec!["target1", "target2"]
6726 );
6727 }
6728
6729 #[test]
6730 fn test_rule_set_targets() {
6731 let mut rule: Rule = "old_target: dependency\n".parse().unwrap();
6732 rule.set_targets(vec!["new_target1", "new_target2"])
6733 .unwrap();
6734 assert_eq!(
6735 rule.targets().collect::<Vec<_>>(),
6736 vec!["new_target1", "new_target2"]
6737 );
6738 }
6739
6740 #[test]
6741 fn test_rule_set_targets_empty() {
6742 let mut rule: Rule = "target: dep1\n".parse().unwrap();
6743 let result = rule.set_targets(vec![]);
6744 assert!(result.is_err());
6745 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
6747 }
6748
6749 #[test]
6750 fn test_rule_has_target() {
6751 let rule: Rule = "target1 target2: dependency\n".parse().unwrap();
6752 assert!(rule.has_target("target1"));
6753 assert!(rule.has_target("target2"));
6754 assert!(!rule.has_target("target3"));
6755 assert!(!rule.has_target("nonexistent"));
6756 }
6757
6758 #[test]
6759 fn test_rule_rename_target() {
6760 let mut rule: Rule = "old_target: dependency\n".parse().unwrap();
6761 assert!(rule.rename_target("old_target", "new_target").unwrap());
6762 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["new_target"]);
6763 assert!(!rule.rename_target("nonexistent", "something").unwrap());
6765 }
6766
6767 #[test]
6768 fn test_rule_rename_target_multiple() {
6769 let mut rule: Rule = "target1 target2 target3: dependency\n".parse().unwrap();
6770 assert!(rule.rename_target("target2", "renamed_target").unwrap());
6771 assert_eq!(
6772 rule.targets().collect::<Vec<_>>(),
6773 vec!["target1", "renamed_target", "target3"]
6774 );
6775 }
6776
6777 #[test]
6778 fn test_rule_remove_target() {
6779 let mut rule: Rule = "target1 target2 target3: dependency\n".parse().unwrap();
6780 assert!(rule.remove_target("target2").unwrap());
6781 assert_eq!(
6782 rule.targets().collect::<Vec<_>>(),
6783 vec!["target1", "target3"]
6784 );
6785 assert!(!rule.remove_target("nonexistent").unwrap());
6787 }
6788
6789 #[test]
6790 fn test_rule_remove_target_last() {
6791 let mut rule: Rule = "single_target: dependency\n".parse().unwrap();
6792 let result = rule.remove_target("single_target");
6793 assert!(result.is_err());
6794 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["single_target"]);
6796 }
6797
6798 #[test]
6799 fn test_rule_target_manipulation_preserves_prerequisites() {
6800 let mut rule: Rule = "target1 target2: dep1 dep2\n\tcommand".parse().unwrap();
6801
6802 rule.remove_target("target1").unwrap();
6804 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target2"]);
6805 assert_eq!(
6806 rule.prerequisites().collect::<Vec<_>>(),
6807 vec!["dep1", "dep2"]
6808 );
6809 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
6810
6811 rule.add_target("target3").unwrap();
6813 assert_eq!(
6814 rule.targets().collect::<Vec<_>>(),
6815 vec!["target2", "target3"]
6816 );
6817 assert_eq!(
6818 rule.prerequisites().collect::<Vec<_>>(),
6819 vec!["dep1", "dep2"]
6820 );
6821 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
6822
6823 rule.rename_target("target2", "renamed").unwrap();
6825 assert_eq!(
6826 rule.targets().collect::<Vec<_>>(),
6827 vec!["renamed", "target3"]
6828 );
6829 assert_eq!(
6830 rule.prerequisites().collect::<Vec<_>>(),
6831 vec!["dep1", "dep2"]
6832 );
6833 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
6834 }
6835
6836 #[test]
6837 fn test_rule_remove() {
6838 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
6839 let rule = makefile.find_rule_by_target("rule1").unwrap();
6840 rule.remove().unwrap();
6841 assert_eq!(makefile.rules().count(), 1);
6842 assert!(makefile.find_rule_by_target("rule1").is_none());
6843 assert!(makefile.find_rule_by_target("rule2").is_some());
6844 }
6845
6846 #[test]
6847 fn test_rule_remove_last_trims_blank_lines() {
6848 let makefile: Makefile =
6850 "%:\n\tdh $@\n\noverride_dh_missing:\n\tdh_missing --fail-missing\n"
6851 .parse()
6852 .unwrap();
6853
6854 let rule = makefile.find_rule_by_target("override_dh_missing").unwrap();
6856 rule.remove().unwrap();
6857
6858 assert_eq!(makefile.code(), "%:\n\tdh $@\n");
6860 assert_eq!(makefile.rules().count(), 1);
6861 }
6862
6863 #[test]
6864 fn test_makefile_find_rule_by_target() {
6865 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
6866 let rule = makefile.find_rule_by_target("rule2");
6867 assert!(rule.is_some());
6868 assert_eq!(rule.unwrap().targets().collect::<Vec<_>>(), vec!["rule2"]);
6869 assert!(makefile.find_rule_by_target("nonexistent").is_none());
6870 }
6871
6872 #[test]
6873 fn test_makefile_find_rules_by_target() {
6874 let makefile: Makefile = "rule1:\n\tcommand1\nrule1:\n\tcommand2\nrule2:\n\tcommand3\n"
6875 .parse()
6876 .unwrap();
6877 assert_eq!(makefile.find_rules_by_target("rule1").count(), 2);
6878 assert_eq!(makefile.find_rules_by_target("rule2").count(), 1);
6879 assert_eq!(makefile.find_rules_by_target("nonexistent").count(), 0);
6880 }
6881
6882 #[test]
6883 fn test_makefile_find_rule_by_target_pattern_simple() {
6884 let makefile: Makefile = "%.o: %.c\n\t$(CC) -c $<\n".parse().unwrap();
6885 let rule = makefile.find_rule_by_target_pattern("foo.o");
6886 assert!(rule.is_some());
6887 assert_eq!(rule.unwrap().targets().next().unwrap(), "%.o");
6888 }
6889
6890 #[test]
6891 fn test_makefile_find_rule_by_target_pattern_no_match() {
6892 let makefile: Makefile = "%.o: %.c\n\t$(CC) -c $<\n".parse().unwrap();
6893 let rule = makefile.find_rule_by_target_pattern("foo.c");
6894 assert!(rule.is_none());
6895 }
6896
6897 #[test]
6898 fn test_makefile_find_rule_by_target_pattern_exact() {
6899 let makefile: Makefile = "foo.o: foo.c\n\t$(CC) -c $<\n".parse().unwrap();
6900 let rule = makefile.find_rule_by_target_pattern("foo.o");
6901 assert!(rule.is_some());
6902 assert_eq!(rule.unwrap().targets().next().unwrap(), "foo.o");
6903 }
6904
6905 #[test]
6906 fn test_makefile_find_rule_by_target_pattern_prefix() {
6907 let makefile: Makefile = "lib%.a: %.o\n\tar rcs $@ $<\n".parse().unwrap();
6908 let rule = makefile.find_rule_by_target_pattern("libfoo.a");
6909 assert!(rule.is_some());
6910 assert_eq!(rule.unwrap().targets().next().unwrap(), "lib%.a");
6911 }
6912
6913 #[test]
6914 fn test_makefile_find_rule_by_target_pattern_suffix() {
6915 let makefile: Makefile = "%_test.o: %.c\n\t$(CC) -c $<\n".parse().unwrap();
6916 let rule = makefile.find_rule_by_target_pattern("foo_test.o");
6917 assert!(rule.is_some());
6918 assert_eq!(rule.unwrap().targets().next().unwrap(), "%_test.o");
6919 }
6920
6921 #[test]
6922 fn test_makefile_find_rule_by_target_pattern_middle() {
6923 let makefile: Makefile = "lib%_debug.a: %.o\n\tar rcs $@ $<\n".parse().unwrap();
6924 let rule = makefile.find_rule_by_target_pattern("libfoo_debug.a");
6925 assert!(rule.is_some());
6926 assert_eq!(rule.unwrap().targets().next().unwrap(), "lib%_debug.a");
6927 }
6928
6929 #[test]
6930 fn test_makefile_find_rule_by_target_pattern_wildcard_only() {
6931 let makefile: Makefile = "%: %.c\n\t$(CC) -o $@ $<\n".parse().unwrap();
6932 let rule = makefile.find_rule_by_target_pattern("anything");
6933 assert!(rule.is_some());
6934 assert_eq!(rule.unwrap().targets().next().unwrap(), "%");
6935 }
6936
6937 #[test]
6938 fn test_makefile_find_rules_by_target_pattern_multiple() {
6939 let makefile: Makefile = "%.o: %.c\n\t$(CC) -c $<\n%.o: %.s\n\t$(AS) -o $@ $<\n"
6940 .parse()
6941 .unwrap();
6942 let rules: Vec<_> = makefile.find_rules_by_target_pattern("foo.o").collect();
6943 assert_eq!(rules.len(), 2);
6944 }
6945
6946 #[test]
6947 fn test_makefile_find_rules_by_target_pattern_mixed() {
6948 let makefile: Makefile =
6949 "%.o: %.c\n\t$(CC) -c $<\nfoo.o: foo.h\n\t$(CC) -c foo.c\nbar.txt: baz.txt\n\tcp $< $@\n"
6950 .parse()
6951 .unwrap();
6952 let rules: Vec<_> = makefile.find_rules_by_target_pattern("foo.o").collect();
6953 assert_eq!(rules.len(), 2); let rules: Vec<_> = makefile.find_rules_by_target_pattern("bar.txt").collect();
6955 assert_eq!(rules.len(), 1); }
6957
6958 #[test]
6959 fn test_makefile_find_rules_by_target_pattern_no_wildcard() {
6960 let makefile: Makefile = "foo.o: foo.c\n\t$(CC) -c $<\n".parse().unwrap();
6961 let rules: Vec<_> = makefile.find_rules_by_target_pattern("foo.o").collect();
6962 assert_eq!(rules.len(), 1);
6963 let rules: Vec<_> = makefile.find_rules_by_target_pattern("bar.o").collect();
6964 assert_eq!(rules.len(), 0);
6965 }
6966
6967 #[test]
6968 fn test_matches_pattern_exact() {
6969 assert!(matches_pattern("foo.o", "foo.o"));
6970 assert!(!matches_pattern("foo.o", "bar.o"));
6971 }
6972
6973 #[test]
6974 fn test_matches_pattern_suffix() {
6975 assert!(matches_pattern("%.o", "foo.o"));
6976 assert!(matches_pattern("%.o", "bar.o"));
6977 assert!(matches_pattern("%.o", "baz/qux.o"));
6978 assert!(!matches_pattern("%.o", "foo.c"));
6979 }
6980
6981 #[test]
6982 fn test_matches_pattern_prefix() {
6983 assert!(matches_pattern("lib%.a", "libfoo.a"));
6984 assert!(matches_pattern("lib%.a", "libbar.a"));
6985 assert!(!matches_pattern("lib%.a", "foo.a"));
6986 assert!(!matches_pattern("lib%.a", "lib.a"));
6987 }
6988
6989 #[test]
6990 fn test_matches_pattern_middle() {
6991 assert!(matches_pattern("lib%_debug.a", "libfoo_debug.a"));
6992 assert!(matches_pattern("lib%_debug.a", "libbar_debug.a"));
6993 assert!(!matches_pattern("lib%_debug.a", "libfoo.a"));
6994 assert!(!matches_pattern("lib%_debug.a", "foo_debug.a"));
6995 }
6996
6997 #[test]
6998 fn test_matches_pattern_wildcard_only() {
6999 assert!(matches_pattern("%", "anything"));
7000 assert!(matches_pattern("%", "foo.o"));
7001 assert!(!matches_pattern("%", ""));
7003 }
7004
7005 #[test]
7006 fn test_matches_pattern_empty_stem() {
7007 assert!(!matches_pattern("%.o", ".o")); assert!(!matches_pattern("lib%", "lib")); assert!(!matches_pattern("lib%.a", "lib.a")); }
7012
7013 #[test]
7014 fn test_matches_pattern_multiple_wildcards_not_supported() {
7015 assert!(!matches_pattern("%foo%bar", "xfooybarz"));
7018 assert!(!matches_pattern("lib%.so.%", "libfoo.so.1"));
7019 }
7020
7021 #[test]
7022 fn test_makefile_add_phony_target() {
7023 let mut makefile = Makefile::new();
7024 makefile.add_phony_target("clean").unwrap();
7025 assert!(makefile.is_phony("clean"));
7026 assert_eq!(makefile.phony_targets().collect::<Vec<_>>(), vec!["clean"]);
7027 }
7028
7029 #[test]
7030 fn test_makefile_add_phony_target_existing() {
7031 let mut makefile: Makefile = ".PHONY: test\n".parse().unwrap();
7032 makefile.add_phony_target("clean").unwrap();
7033 assert!(makefile.is_phony("test"));
7034 assert!(makefile.is_phony("clean"));
7035 let targets: Vec<_> = makefile.phony_targets().collect();
7036 assert!(targets.contains(&"test".to_string()));
7037 assert!(targets.contains(&"clean".to_string()));
7038 }
7039
7040 #[test]
7041 fn test_makefile_remove_phony_target() {
7042 let mut makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
7043 assert!(makefile.remove_phony_target("clean").unwrap());
7044 assert!(!makefile.is_phony("clean"));
7045 assert!(makefile.is_phony("test"));
7046 assert!(!makefile.remove_phony_target("nonexistent").unwrap());
7047 }
7048
7049 #[test]
7050 fn test_makefile_remove_phony_target_last() {
7051 let mut makefile: Makefile = ".PHONY: clean\n".parse().unwrap();
7052 assert!(makefile.remove_phony_target("clean").unwrap());
7053 assert!(!makefile.is_phony("clean"));
7054 assert!(makefile.find_rule_by_target(".PHONY").is_none());
7056 }
7057
7058 #[test]
7059 fn test_makefile_is_phony() {
7060 let makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
7061 assert!(makefile.is_phony("clean"));
7062 assert!(makefile.is_phony("test"));
7063 assert!(!makefile.is_phony("build"));
7064 }
7065
7066 #[test]
7067 fn test_makefile_phony_targets() {
7068 let makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
7069 let phony_targets: Vec<_> = makefile.phony_targets().collect();
7070 assert_eq!(phony_targets, vec!["clean", "test", "build"]);
7071 }
7072
7073 #[test]
7074 fn test_makefile_phony_targets_empty() {
7075 let makefile = Makefile::new();
7076 assert_eq!(makefile.phony_targets().count(), 0);
7077 }
7078
7079 #[test]
7080 fn test_makefile_remove_first_phony_target_no_extra_space() {
7081 let mut makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
7082 assert!(makefile.remove_phony_target("clean").unwrap());
7083 let result = makefile.to_string();
7084 assert_eq!(result, ".PHONY: test build\n");
7085 }
7086
7087 #[test]
7088 fn test_recipe_with_leading_comments_and_blank_lines() {
7089 let makefile_text = r#"#!/usr/bin/make
7093
7094%:
7095 dh $@
7096
7097override_dh_build:
7098 # The next line is empty
7099
7100 dh_python3
7101"#;
7102 let makefile = Makefile::read_relaxed(makefile_text.as_bytes()).unwrap();
7103
7104 let rules: Vec<_> = makefile.rules().collect();
7105 assert_eq!(rules.len(), 2, "Expected 2 rules");
7106
7107 let rule0 = &rules[0];
7109 assert_eq!(rule0.targets().collect::<Vec<_>>(), vec!["%"]);
7110 assert_eq!(rule0.recipes().collect::<Vec<_>>(), vec!["dh $@"]);
7111
7112 let rule1 = &rules[1];
7114 assert_eq!(
7115 rule1.targets().collect::<Vec<_>>(),
7116 vec!["override_dh_build"]
7117 );
7118
7119 let recipes: Vec<_> = rule1.recipes().collect();
7121 assert!(
7122 !recipes.is_empty(),
7123 "Expected at least one recipe for override_dh_build, got none"
7124 );
7125 assert!(
7126 recipes.contains(&"dh_python3".to_string()),
7127 "Expected 'dh_python3' in recipes, got: {:?}",
7128 recipes
7129 );
7130 }
7131
7132 #[test]
7133 fn test_rule_parse_preserves_trailing_blank_lines() {
7134 let input = r#"override_dh_systemd_enable:
7137 dh_systemd_enable -pracoon
7138
7139override_dh_install:
7140 dh_install
7141"#;
7142
7143 let mut mf: Makefile = input.parse().unwrap();
7144
7145 let rule = mf.rules().next().unwrap();
7147 let rule_text = rule.to_string();
7148
7149 assert_eq!(
7151 rule_text,
7152 "override_dh_systemd_enable:\n\tdh_systemd_enable -pracoon\n\n"
7153 );
7154
7155 let modified =
7157 rule_text.replace("override_dh_systemd_enable:", "override_dh_installsystemd:");
7158
7159 let new_rule: Rule = modified.parse().unwrap();
7161 assert_eq!(
7162 new_rule.to_string(),
7163 "override_dh_installsystemd:\n\tdh_systemd_enable -pracoon\n\n"
7164 );
7165
7166 mf.replace_rule(0, new_rule).unwrap();
7168
7169 let output = mf.to_string();
7171 assert!(
7172 output.contains(
7173 "override_dh_installsystemd:\n\tdh_systemd_enable -pracoon\n\noverride_dh_install:"
7174 ),
7175 "Blank line between rules should be preserved. Got: {:?}",
7176 output
7177 );
7178 }
7179
7180 #[test]
7181 fn test_rule_parse_round_trip_with_trailing_newlines() {
7182 let test_cases = vec![
7184 "rule:\n\tcommand\n", "rule:\n\tcommand\n\n", "rule:\n\tcommand\n\n\n", ];
7188
7189 for rule_text in test_cases {
7190 let rule: Rule = rule_text.parse().unwrap();
7191 let result = rule.to_string();
7192 assert_eq!(rule_text, result, "Round-trip failed for {:?}", rule_text);
7193 }
7194 }
7195
7196 #[test]
7197 fn test_rule_clone() {
7198 let rule_text = "rule:\n\tcommand\n\n";
7200 let rule: Rule = rule_text.parse().unwrap();
7201 let cloned = rule.clone();
7202
7203 assert_eq!(rule.to_string(), cloned.to_string());
7205 assert_eq!(rule.to_string(), rule_text);
7206 assert_eq!(cloned.to_string(), rule_text);
7207
7208 assert_eq!(
7210 rule.targets().collect::<Vec<_>>(),
7211 cloned.targets().collect::<Vec<_>>()
7212 );
7213 assert_eq!(
7214 rule.recipes().collect::<Vec<_>>(),
7215 cloned.recipes().collect::<Vec<_>>()
7216 );
7217 }
7218
7219 #[test]
7220 fn test_makefile_clone() {
7221 let input = "VAR = value\n\nrule:\n\tcommand\n";
7223 let makefile: Makefile = input.parse().unwrap();
7224 let cloned = makefile.clone();
7225
7226 assert_eq!(makefile.to_string(), cloned.to_string());
7228 assert_eq!(makefile.to_string(), input);
7229
7230 assert_eq!(makefile.rules().count(), cloned.rules().count());
7232
7233 assert_eq!(
7235 makefile.variable_definitions().count(),
7236 cloned.variable_definitions().count()
7237 );
7238 }
7239
7240 #[test]
7241 fn test_conditional_with_recipe_line() {
7242 let input = "ifeq (,$(X))\n\t./run-tests\nendif\n";
7244 let parsed = parse(input, None);
7245
7246 assert!(
7248 parsed.errors.is_empty(),
7249 "Expected no parse errors, but got: {:?}",
7250 parsed.errors
7251 );
7252
7253 let mf = parsed.root();
7255 assert_eq!(mf.code(), input);
7256 }
7257
7258 #[test]
7259 fn test_conditional_in_rule_recipe() {
7260 let input = "override_dh_auto_test:\nifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))\n\t./run-tests\nendif\n";
7262 let parsed = parse(input, None);
7263
7264 assert!(
7266 parsed.errors.is_empty(),
7267 "Expected no parse errors, but got: {:?}",
7268 parsed.errors
7269 );
7270
7271 let mf = parsed.root();
7273 assert_eq!(mf.code(), input);
7274
7275 assert_eq!(mf.rules().count(), 1);
7277 }
7278
7279 #[test]
7280 fn test_rule_items() {
7281 use crate::RuleItem;
7282
7283 let input = r#"test:
7285 echo "before"
7286ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
7287 ./run-tests
7288endif
7289 echo "after"
7290"#;
7291 let rule: Rule = input.parse().unwrap();
7292
7293 let items: Vec<_> = rule.items().collect();
7294 assert_eq!(
7295 items.len(),
7296 3,
7297 "Expected 3 items: recipe, conditional, recipe"
7298 );
7299
7300 match &items[0] {
7302 RuleItem::Recipe(r) => assert_eq!(r, "echo \"before\""),
7303 RuleItem::Conditional(_) => panic!("Expected recipe, got conditional"),
7304 }
7305
7306 match &items[1] {
7308 RuleItem::Conditional(c) => {
7309 assert_eq!(c.conditional_type(), Some("ifeq".to_string()));
7310 }
7311 RuleItem::Recipe(_) => panic!("Expected conditional, got recipe"),
7312 }
7313
7314 match &items[2] {
7316 RuleItem::Recipe(r) => assert_eq!(r, "echo \"after\""),
7317 RuleItem::Conditional(_) => panic!("Expected recipe, got conditional"),
7318 }
7319
7320 let simple_rule: Rule = "simple:\n\techo one\n\techo two\n".parse().unwrap();
7322 let simple_items: Vec<_> = simple_rule.items().collect();
7323 assert_eq!(simple_items.len(), 2);
7324
7325 match &simple_items[0] {
7326 RuleItem::Recipe(r) => assert_eq!(r, "echo one"),
7327 _ => panic!("Expected recipe"),
7328 }
7329
7330 match &simple_items[1] {
7331 RuleItem::Recipe(r) => assert_eq!(r, "echo two"),
7332 _ => panic!("Expected recipe"),
7333 }
7334
7335 let cond_only: Rule = "condtest:\nifeq (a,b)\n\techo yes\nendif\n"
7337 .parse()
7338 .unwrap();
7339 let cond_items: Vec<_> = cond_only.items().collect();
7340 assert_eq!(cond_items.len(), 1);
7341
7342 match &cond_items[0] {
7343 RuleItem::Conditional(c) => {
7344 assert_eq!(c.conditional_type(), Some("ifeq".to_string()));
7345 }
7346 _ => panic!("Expected conditional"),
7347 }
7348 }
7349
7350 #[test]
7351 fn test_conditionals_iterator() {
7352 let makefile: Makefile = r#"ifdef DEBUG
7353VAR = debug
7354endif
7355
7356ifndef RELEASE
7357OTHER = dev
7358endif
7359"#
7360 .parse()
7361 .unwrap();
7362
7363 let conditionals: Vec<_> = makefile.conditionals().collect();
7364 assert_eq!(conditionals.len(), 2);
7365
7366 assert_eq!(
7367 conditionals[0].conditional_type(),
7368 Some("ifdef".to_string())
7369 );
7370 assert_eq!(
7371 conditionals[1].conditional_type(),
7372 Some("ifndef".to_string())
7373 );
7374 }
7375
7376 #[test]
7377 fn test_conditional_type_and_condition() {
7378 let makefile: Makefile = r#"ifdef DEBUG
7379VAR = debug
7380endif
7381"#
7382 .parse()
7383 .unwrap();
7384
7385 let conditional = makefile.conditionals().next().unwrap();
7386 assert_eq!(conditional.conditional_type(), Some("ifdef".to_string()));
7387 assert_eq!(conditional.condition(), Some("DEBUG".to_string()));
7388 }
7389
7390 #[test]
7391 fn test_conditional_has_else() {
7392 let makefile_with_else: Makefile = r#"ifdef DEBUG
7393VAR = debug
7394else
7395VAR = release
7396endif
7397"#
7398 .parse()
7399 .unwrap();
7400
7401 let conditional = makefile_with_else.conditionals().next().unwrap();
7402 assert!(conditional.has_else());
7403
7404 let makefile_without_else: Makefile = r#"ifdef DEBUG
7405VAR = debug
7406endif
7407"#
7408 .parse()
7409 .unwrap();
7410
7411 let conditional = makefile_without_else.conditionals().next().unwrap();
7412 assert!(!conditional.has_else());
7413 }
7414
7415 #[test]
7416 fn test_conditional_if_body() {
7417 let makefile: Makefile = r#"ifdef DEBUG
7418VAR = debug
7419endif
7420"#
7421 .parse()
7422 .unwrap();
7423
7424 let conditional = makefile.conditionals().next().unwrap();
7425 let if_body = conditional.if_body();
7426 assert!(if_body.is_some());
7427 assert!(if_body.unwrap().contains("VAR = debug"));
7428 }
7429
7430 #[test]
7431 fn test_conditional_else_body() {
7432 let makefile: Makefile = r#"ifdef DEBUG
7433VAR = debug
7434else
7435VAR = release
7436endif
7437"#
7438 .parse()
7439 .unwrap();
7440
7441 let conditional = makefile.conditionals().next().unwrap();
7442 let else_body = conditional.else_body();
7443 assert!(else_body.is_some());
7444 assert!(else_body.unwrap().contains("VAR = release"));
7445 }
7446
7447 #[test]
7448 fn test_add_conditional_ifdef() {
7449 let mut makefile = Makefile::new();
7450 let result = makefile.add_conditional("ifdef", "DEBUG", "VAR = debug\n", None);
7451 assert!(result.is_ok());
7452
7453 let code = makefile.to_string();
7454 assert!(code.contains("ifdef DEBUG"));
7455 assert!(code.contains("VAR = debug"));
7456 assert!(code.contains("endif"));
7457 }
7458
7459 #[test]
7460 fn test_add_conditional_with_else() {
7461 let mut makefile = Makefile::new();
7462 let result =
7463 makefile.add_conditional("ifdef", "DEBUG", "VAR = debug\n", Some("VAR = release\n"));
7464 assert!(result.is_ok());
7465
7466 let code = makefile.to_string();
7467 assert!(code.contains("ifdef DEBUG"));
7468 assert!(code.contains("VAR = debug"));
7469 assert!(code.contains("else"));
7470 assert!(code.contains("VAR = release"));
7471 assert!(code.contains("endif"));
7472 }
7473
7474 #[test]
7475 fn test_add_conditional_invalid_type() {
7476 let mut makefile = Makefile::new();
7477 let result = makefile.add_conditional("invalid", "DEBUG", "VAR = debug\n", None);
7478 assert!(result.is_err());
7479 }
7480
7481 #[test]
7482 fn test_add_conditional_formatting() {
7483 let mut makefile: Makefile = "VAR1 = value1\n".parse().unwrap();
7484 let result = makefile.add_conditional("ifdef", "DEBUG", "VAR = debug\n", None);
7485 assert!(result.is_ok());
7486
7487 let code = makefile.to_string();
7488 assert!(code.contains("\n\nifdef DEBUG"));
7490 }
7491
7492 #[test]
7493 fn test_conditional_remove() {
7494 let makefile: Makefile = r#"ifdef DEBUG
7495VAR = debug
7496endif
7497
7498VAR2 = value2
7499"#
7500 .parse()
7501 .unwrap();
7502
7503 let mut conditional = makefile.conditionals().next().unwrap();
7504 let result = conditional.remove();
7505 assert!(result.is_ok());
7506
7507 let code = makefile.to_string();
7508 assert!(!code.contains("ifdef DEBUG"));
7509 assert!(!code.contains("VAR = debug"));
7510 assert!(code.contains("VAR2 = value2"));
7511 }
7512
7513 #[test]
7514 fn test_add_conditional_ifndef() {
7515 let mut makefile = Makefile::new();
7516 let result = makefile.add_conditional("ifndef", "NDEBUG", "VAR = enabled\n", None);
7517 assert!(result.is_ok());
7518
7519 let code = makefile.to_string();
7520 assert!(code.contains("ifndef NDEBUG"));
7521 assert!(code.contains("VAR = enabled"));
7522 assert!(code.contains("endif"));
7523 }
7524
7525 #[test]
7526 fn test_add_conditional_ifeq() {
7527 let mut makefile = Makefile::new();
7528 let result = makefile.add_conditional("ifeq", "($(OS),Linux)", "VAR = linux\n", None);
7529 assert!(result.is_ok());
7530
7531 let code = makefile.to_string();
7532 assert!(code.contains("ifeq ($(OS),Linux)"));
7533 assert!(code.contains("VAR = linux"));
7534 assert!(code.contains("endif"));
7535 }
7536
7537 #[test]
7538 fn test_add_conditional_ifneq() {
7539 let mut makefile = Makefile::new();
7540 let result = makefile.add_conditional("ifneq", "($(OS),Windows)", "VAR = unix\n", None);
7541 assert!(result.is_ok());
7542
7543 let code = makefile.to_string();
7544 assert!(code.contains("ifneq ($(OS),Windows)"));
7545 assert!(code.contains("VAR = unix"));
7546 assert!(code.contains("endif"));
7547 }
7548
7549 #[test]
7550 fn test_conditional_api_integration() {
7551 let mut makefile: Makefile = r#"VAR1 = value1
7553
7554rule1:
7555 command1
7556"#
7557 .parse()
7558 .unwrap();
7559
7560 makefile
7562 .add_conditional("ifdef", "DEBUG", "CFLAGS += -g\n", Some("CFLAGS += -O2\n"))
7563 .unwrap();
7564
7565 assert_eq!(makefile.conditionals().count(), 1);
7567 let conditional = makefile.conditionals().next().unwrap();
7568 assert_eq!(conditional.conditional_type(), Some("ifdef".to_string()));
7569 assert_eq!(conditional.condition(), Some("DEBUG".to_string()));
7570 assert!(conditional.has_else());
7571
7572 assert_eq!(makefile.variable_definitions().count(), 1);
7574 assert_eq!(makefile.rules().count(), 1);
7575 }
7576
7577 #[test]
7578 fn test_conditional_if_items() {
7579 let makefile: Makefile = r#"ifdef DEBUG
7580VAR = debug
7581rule:
7582 command
7583endif
7584"#
7585 .parse()
7586 .unwrap();
7587
7588 let cond = makefile.conditionals().next().unwrap();
7589 let items: Vec<_> = cond.if_items().collect();
7590 assert_eq!(items.len(), 2); match &items[0] {
7593 MakefileItem::Variable(v) => {
7594 assert_eq!(v.name(), Some("VAR".to_string()));
7595 }
7596 _ => panic!("Expected variable"),
7597 }
7598
7599 match &items[1] {
7600 MakefileItem::Rule(r) => {
7601 assert!(r.targets().any(|t| t == "rule"));
7602 }
7603 _ => panic!("Expected rule"),
7604 }
7605 }
7606
7607 #[test]
7608 fn test_conditional_else_items() {
7609 let makefile: Makefile = r#"ifdef DEBUG
7610VAR = debug
7611else
7612VAR2 = release
7613rule2:
7614 command
7615endif
7616"#
7617 .parse()
7618 .unwrap();
7619
7620 let cond = makefile.conditionals().next().unwrap();
7621 let items: Vec<_> = cond.else_items().collect();
7622 assert_eq!(items.len(), 2); match &items[0] {
7625 MakefileItem::Variable(v) => {
7626 assert_eq!(v.name(), Some("VAR2".to_string()));
7627 }
7628 _ => panic!("Expected variable"),
7629 }
7630
7631 match &items[1] {
7632 MakefileItem::Rule(r) => {
7633 assert!(r.targets().any(|t| t == "rule2"));
7634 }
7635 _ => panic!("Expected rule"),
7636 }
7637 }
7638
7639 #[test]
7640 fn test_conditional_add_if_item() {
7641 let makefile: Makefile = "ifdef DEBUG\nendif\n".parse().unwrap();
7642 let mut cond = makefile.conditionals().next().unwrap();
7643
7644 let temp: Makefile = "CFLAGS = -g\n".parse().unwrap();
7646 let var = temp.variable_definitions().next().unwrap();
7647 cond.add_if_item(MakefileItem::Variable(var));
7648
7649 let code = makefile.to_string();
7650 assert!(code.contains("CFLAGS = -g"));
7651
7652 let cond = makefile.conditionals().next().unwrap();
7654 assert_eq!(cond.if_items().count(), 1);
7655 }
7656
7657 #[test]
7658 fn test_conditional_add_else_item() {
7659 let makefile: Makefile = "ifdef DEBUG\nVAR=1\nendif\n".parse().unwrap();
7660 let mut cond = makefile.conditionals().next().unwrap();
7661
7662 let temp: Makefile = "CFLAGS = -O2\n".parse().unwrap();
7664 let var = temp.variable_definitions().next().unwrap();
7665 cond.add_else_item(MakefileItem::Variable(var));
7666
7667 let code = makefile.to_string();
7668 assert!(code.contains("else"));
7669 assert!(code.contains("CFLAGS = -O2"));
7670
7671 let cond = makefile.conditionals().next().unwrap();
7673 assert_eq!(cond.else_items().count(), 1);
7674 }
7675
7676 #[test]
7677 fn test_add_conditional_with_items() {
7678 let mut makefile = Makefile::new();
7679
7680 let temp1: Makefile = "CFLAGS = -g\n".parse().unwrap();
7682 let var1 = temp1.variable_definitions().next().unwrap();
7683
7684 let temp2: Makefile = "CFLAGS = -O2\n".parse().unwrap();
7685 let var2 = temp2.variable_definitions().next().unwrap();
7686
7687 let temp3: Makefile = "debug:\n\techo debug\n".parse().unwrap();
7688 let rule1 = temp3.rules().next().unwrap();
7689
7690 let result = makefile.add_conditional_with_items(
7691 "ifdef",
7692 "DEBUG",
7693 vec![MakefileItem::Variable(var1), MakefileItem::Rule(rule1)],
7694 Some(vec![MakefileItem::Variable(var2)]),
7695 );
7696
7697 assert!(result.is_ok());
7698
7699 let code = makefile.to_string();
7700 assert!(code.contains("ifdef DEBUG"));
7701 assert!(code.contains("CFLAGS = -g"));
7702 assert!(code.contains("debug:"));
7703 assert!(code.contains("else"));
7704 assert!(code.contains("CFLAGS = -O2"));
7705 }
7706
7707 #[test]
7708 fn test_conditional_items_with_nested_conditional() {
7709 let makefile: Makefile = r#"ifdef DEBUG
7710VAR = debug
7711ifdef VERBOSE
7712 VAR2 = verbose
7713endif
7714endif
7715"#
7716 .parse()
7717 .unwrap();
7718
7719 let cond = makefile.conditionals().next().unwrap();
7720 let items: Vec<_> = cond.if_items().collect();
7721 assert_eq!(items.len(), 2); match &items[0] {
7724 MakefileItem::Variable(v) => {
7725 assert_eq!(v.name(), Some("VAR".to_string()));
7726 }
7727 _ => panic!("Expected variable"),
7728 }
7729
7730 match &items[1] {
7731 MakefileItem::Conditional(c) => {
7732 assert_eq!(c.conditional_type(), Some("ifdef".to_string()));
7733 }
7734 _ => panic!("Expected conditional"),
7735 }
7736 }
7737
7738 #[test]
7739 fn test_conditional_items_with_include() {
7740 let makefile: Makefile = r#"ifdef DEBUG
7741include debug.mk
7742VAR = debug
7743endif
7744"#
7745 .parse()
7746 .unwrap();
7747
7748 let cond = makefile.conditionals().next().unwrap();
7749 let items: Vec<_> = cond.if_items().collect();
7750 assert_eq!(items.len(), 2); match &items[0] {
7753 MakefileItem::Include(i) => {
7754 assert_eq!(i.path(), Some("debug.mk".to_string()));
7755 }
7756 _ => panic!("Expected include"),
7757 }
7758
7759 match &items[1] {
7760 MakefileItem::Variable(v) => {
7761 assert_eq!(v.name(), Some("VAR".to_string()));
7762 }
7763 _ => panic!("Expected variable"),
7764 }
7765 }
7766
7767 #[test]
7768 fn test_makefile_items_iterator() {
7769 let makefile: Makefile = r#"VAR = value
7770ifdef DEBUG
7771CFLAGS = -g
7772endif
7773rule:
7774 command
7775include common.mk
7776"#
7777 .parse()
7778 .unwrap();
7779
7780 assert_eq!(makefile.variable_definitions().count(), 1);
7782 assert_eq!(makefile.conditionals().count(), 1);
7783 assert_eq!(makefile.rules().count(), 1);
7784
7785 let items: Vec<_> = makefile.items().collect();
7786 assert!(
7788 items.len() >= 3,
7789 "Expected at least 3 items, got {}",
7790 items.len()
7791 );
7792
7793 match &items[0] {
7794 MakefileItem::Variable(v) => {
7795 assert_eq!(v.name(), Some("VAR".to_string()));
7796 }
7797 _ => panic!("Expected variable at position 0"),
7798 }
7799
7800 match &items[1] {
7801 MakefileItem::Conditional(c) => {
7802 assert_eq!(c.conditional_type(), Some("ifdef".to_string()));
7803 }
7804 _ => panic!("Expected conditional at position 1"),
7805 }
7806
7807 match &items[2] {
7808 MakefileItem::Rule(r) => {
7809 let targets: Vec<_> = r.targets().collect();
7810 assert_eq!(targets, vec!["rule"]);
7811 }
7812 _ => panic!("Expected rule at position 2"),
7813 }
7814 }
7815
7816 #[test]
7817 fn test_conditional_unwrap() {
7818 let makefile: Makefile = r#"ifdef DEBUG
7819VAR = debug
7820rule:
7821 command
7822endif
7823"#
7824 .parse()
7825 .unwrap();
7826
7827 let mut cond = makefile.conditionals().next().unwrap();
7828 cond.unwrap().unwrap();
7829
7830 let code = makefile.to_string();
7831 let expected = "VAR = debug\nrule:\n\tcommand\n";
7832 assert_eq!(code, expected);
7833
7834 assert_eq!(makefile.conditionals().count(), 0);
7836
7837 assert_eq!(makefile.variable_definitions().count(), 1);
7839 assert_eq!(makefile.rules().count(), 1);
7840 }
7841
7842 #[test]
7843 fn test_conditional_unwrap_with_else_fails() {
7844 let makefile: Makefile = r#"ifdef DEBUG
7845VAR = debug
7846else
7847VAR = release
7848endif
7849"#
7850 .parse()
7851 .unwrap();
7852
7853 let mut cond = makefile.conditionals().next().unwrap();
7854 let result = cond.unwrap();
7855
7856 assert!(result.is_err());
7857 assert!(result
7858 .unwrap_err()
7859 .to_string()
7860 .contains("Cannot unwrap conditional with else clause"));
7861 }
7862
7863 #[test]
7864 fn test_conditional_unwrap_nested() {
7865 let makefile: Makefile = r#"ifdef OUTER
7866VAR = outer
7867ifdef INNER
7868VAR2 = inner
7869endif
7870endif
7871"#
7872 .parse()
7873 .unwrap();
7874
7875 let mut outer_cond = makefile.conditionals().next().unwrap();
7877 outer_cond.unwrap().unwrap();
7878
7879 let code = makefile.to_string();
7880 let expected = "VAR = outer\nifdef INNER\nVAR2 = inner\nendif\n";
7881 assert_eq!(code, expected);
7882 }
7883
7884 #[test]
7885 fn test_conditional_unwrap_empty() {
7886 let makefile: Makefile = r#"ifdef DEBUG
7887endif
7888"#
7889 .parse()
7890 .unwrap();
7891
7892 let mut cond = makefile.conditionals().next().unwrap();
7893 cond.unwrap().unwrap();
7894
7895 let code = makefile.to_string();
7896 assert_eq!(code, "");
7897 }
7898
7899 #[test]
7900 fn test_rule_parent() {
7901 let makefile: Makefile = r#"all:
7902 echo "test"
7903"#
7904 .parse()
7905 .unwrap();
7906
7907 let rule = makefile.rules().next().unwrap();
7908 let parent = rule.parent();
7909 assert!(parent.is_none());
7911 }
7912
7913 #[test]
7914 fn test_variable_parent() {
7915 let makefile: Makefile = "VAR = value\n".parse().unwrap();
7916
7917 let var = makefile.variable_definitions().next().unwrap();
7918 let parent = var.parent();
7919 assert!(parent.is_none());
7921 }
7922
7923 #[test]
7924 fn test_include_parent() {
7925 let makefile: Makefile = "include common.mk\n".parse().unwrap();
7926
7927 let inc = makefile.includes().next().unwrap();
7928 let parent = inc.parent();
7929 assert!(parent.is_none());
7931 }
7932
7933 #[test]
7934 fn test_conditional_parent() {
7935 let makefile: Makefile = r#"ifdef DEBUG
7936VAR = debug
7937endif
7938"#
7939 .parse()
7940 .unwrap();
7941
7942 let cond = makefile.conditionals().next().unwrap();
7943 let parent = cond.parent();
7944 assert!(parent.is_none());
7946 }
7947
7948 #[test]
7949 fn test_item_parent_in_conditional() {
7950 let makefile: Makefile = r#"ifdef DEBUG
7951VAR = debug
7952rule:
7953 command
7954endif
7955"#
7956 .parse()
7957 .unwrap();
7958
7959 let cond = makefile.conditionals().next().unwrap();
7960
7961 let items: Vec<_> = cond.if_items().collect();
7963 assert_eq!(items.len(), 2);
7964
7965 if let MakefileItem::Variable(var) = &items[0] {
7967 let parent = var.parent();
7968 assert!(parent.is_some());
7969 if let Some(MakefileItem::Conditional(_)) = parent {
7970 } else {
7972 panic!("Expected variable parent to be a Conditional");
7973 }
7974 } else {
7975 panic!("Expected first item to be a Variable");
7976 }
7977
7978 if let MakefileItem::Rule(rule) = &items[1] {
7980 let parent = rule.parent();
7981 assert!(parent.is_some());
7982 if let Some(MakefileItem::Conditional(_)) = parent {
7983 } else {
7985 panic!("Expected rule parent to be a Conditional");
7986 }
7987 } else {
7988 panic!("Expected second item to be a Rule");
7989 }
7990 }
7991
7992 #[test]
7993 fn test_nested_conditional_parent() {
7994 let makefile: Makefile = r#"ifdef OUTER
7995VAR = outer
7996ifdef INNER
7997VAR2 = inner
7998endif
7999endif
8000"#
8001 .parse()
8002 .unwrap();
8003
8004 let outer_cond = makefile.conditionals().next().unwrap();
8005
8006 let items: Vec<_> = outer_cond.if_items().collect();
8008
8009 let inner_cond = items
8011 .iter()
8012 .find_map(|item| {
8013 if let MakefileItem::Conditional(c) = item {
8014 Some(c)
8015 } else {
8016 None
8017 }
8018 })
8019 .unwrap();
8020
8021 let parent = inner_cond.parent();
8023 assert!(parent.is_some());
8024 if let Some(MakefileItem::Conditional(_)) = parent {
8025 } else {
8027 panic!("Expected inner conditional's parent to be a Conditional");
8028 }
8029 }
8030}