1use crate::lex::lex;
2use crate::MakefileVariant;
3use crate::SyntaxKind;
4use crate::SyntaxKind::*;
5use rowan::ast::AstNode;
6use std::str::FromStr;
7
8#[derive(Debug)]
9pub enum Error {
11 Io(std::io::Error),
13
14 Parse(ParseError),
16}
17
18impl std::fmt::Display for Error {
19 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
20 match &self {
21 Error::Io(e) => write!(f, "IO error: {}", e),
22 Error::Parse(e) => write!(f, "Parse error: {}", e),
23 }
24 }
25}
26
27impl From<std::io::Error> for Error {
28 fn from(e: std::io::Error) -> Self {
29 Error::Io(e)
30 }
31}
32
33impl std::error::Error for Error {}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct ParseError {
38 pub errors: Vec<ErrorInfo>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43pub struct ErrorInfo {
45 pub message: String,
47 pub line: usize,
49 pub context: String,
51}
52
53impl std::fmt::Display for ParseError {
54 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
55 for err in &self.errors {
56 writeln!(f, "Error at line {}: {}", err.line, err.message)?;
57 writeln!(f, "{}| {}", err.line, err.context)?;
58 }
59 Ok(())
60 }
61}
62
63impl std::error::Error for ParseError {}
64
65impl From<ParseError> for Error {
66 fn from(e: ParseError) -> Self {
67 Error::Parse(e)
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
74pub enum Lang {}
75impl rowan::Language for Lang {
76 type Kind = SyntaxKind;
77 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
78 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
79 }
80 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
81 kind.into()
82 }
83}
84
85use rowan::GreenNode;
88
89use rowan::GreenNodeBuilder;
93
94#[derive(Debug)]
97pub(crate) struct Parse {
98 pub(crate) green_node: GreenNode,
99 #[allow(unused)]
100 pub(crate) errors: Vec<ErrorInfo>,
101}
102
103pub(crate) fn parse(text: &str, variant: Option<MakefileVariant>) -> Parse {
104 struct Parser {
105 tokens: Vec<(SyntaxKind, String)>,
108 builder: GreenNodeBuilder<'static>,
110 errors: Vec<ErrorInfo>,
113 original_text: String,
115 variant: Option<MakefileVariant>,
117 }
118
119 impl Parser {
120 fn error(&mut self, msg: String) {
121 self.builder.start_node(ERROR.into());
122
123 let (line, context) = if self.current() == Some(INDENT) {
124 let lines: Vec<&str> = self.original_text.lines().collect();
126 let tab_line = lines
127 .iter()
128 .enumerate()
129 .find(|(_, line)| line.starts_with('\t'))
130 .map(|(i, _)| i + 1)
131 .unwrap_or(1);
132
133 let next_line = tab_line + 1;
135 if next_line <= lines.len() {
136 (next_line, lines[next_line - 1].to_string())
137 } else {
138 (tab_line, lines[tab_line - 1].to_string())
139 }
140 } else {
141 let line = self.get_line_number_for_position(self.tokens.len());
142 (line, self.get_context_for_line(line))
143 };
144
145 let message = if self.current() == Some(INDENT) && !msg.contains("indented") {
146 if !self.tokens.is_empty() && self.tokens[self.tokens.len() - 1].0 == IDENTIFIER {
147 "expected ':'".to_string()
148 } else {
149 "indented line not part of a rule".to_string()
150 }
151 } else {
152 msg
153 };
154
155 self.errors.push(ErrorInfo {
156 message,
157 line,
158 context,
159 });
160
161 if self.current().is_some() {
162 self.bump();
163 }
164 self.builder.finish_node();
165 }
166
167 fn get_line_number_for_position(&self, position: usize) -> usize {
168 if position >= self.tokens.len() {
169 return self.original_text.matches('\n').count() + 1;
170 }
171
172 self.tokens[0..position]
174 .iter()
175 .filter(|(kind, _)| *kind == NEWLINE)
176 .count()
177 + 1
178 }
179
180 fn get_context_for_line(&self, line_number: usize) -> String {
181 self.original_text
182 .lines()
183 .nth(line_number - 1)
184 .unwrap_or("")
185 .to_string()
186 }
187
188 fn parse_recipe_line(&mut self) {
189 self.builder.start_node(RECIPE.into());
190
191 if self.current() != Some(INDENT) {
193 self.error("recipe line must start with a tab".to_string());
194 self.builder.finish_node();
195 return;
196 }
197 self.bump();
198
199 loop {
201 let mut last_text_content: Option<String> = None;
202
203 while self.current().is_some() && self.current() != Some(NEWLINE) {
205 if self.current() == Some(TEXT) {
207 if let Some((_kind, text)) = self.tokens.last() {
208 last_text_content = Some(text.clone());
209 }
210 }
211 self.bump();
212 }
213
214 if self.current() == Some(NEWLINE) {
216 self.bump();
217 }
218
219 let is_continuation = last_text_content
221 .as_ref()
222 .map(|text| text.trim_end().ends_with('\\'))
223 .unwrap_or(false);
224
225 if is_continuation {
226 if self.current() == Some(INDENT) {
228 self.bump();
229 continue;
231 } else {
232 break;
234 }
235 } else {
236 break;
238 }
239 }
240
241 self.builder.finish_node();
242 }
243
244 fn parse_rule_target(&mut self) -> bool {
245 match self.current() {
246 Some(IDENTIFIER) => {
247 if self.is_archive_member() {
249 self.parse_archive_member();
250 } else {
251 self.bump();
252 }
253 true
254 }
255 Some(DOLLAR) => {
256 self.parse_variable_reference();
257 true
258 }
259 _ => {
260 self.error("expected rule target".to_string());
261 false
262 }
263 }
264 }
265
266 fn is_archive_member(&self) -> bool {
267 if self.tokens.len() < 2 {
270 return false;
271 }
272
273 let current_is_identifier = self.current() == Some(IDENTIFIER);
275 let next_is_lparen =
276 self.tokens.len() > 1 && self.tokens[self.tokens.len() - 2].0 == LPAREN;
277
278 current_is_identifier && next_is_lparen
279 }
280
281 fn parse_archive_member(&mut self) {
282 if self.current() == Some(IDENTIFIER) {
293 self.bump();
294 }
295
296 if self.current() == Some(LPAREN) {
298 self.bump();
299
300 self.builder.start_node(ARCHIVE_MEMBERS.into());
302
303 while self.current().is_some() && self.current() != Some(RPAREN) {
305 match self.current() {
306 Some(IDENTIFIER) | Some(TEXT) => {
307 self.builder.start_node(ARCHIVE_MEMBER.into());
309 self.bump();
310 self.builder.finish_node();
311 }
312 Some(WHITESPACE) => self.bump(),
313 Some(DOLLAR) => {
314 self.builder.start_node(ARCHIVE_MEMBER.into());
316 self.parse_variable_reference();
317 self.builder.finish_node();
318 }
319 _ => break,
320 }
321 }
322
323 self.builder.finish_node();
325
326 if self.current() == Some(RPAREN) {
328 self.bump();
329 } else {
330 self.error("expected ')' to close archive member".to_string());
331 }
332 }
333 }
334
335 fn parse_rule_dependencies(&mut self) {
336 self.builder.start_node(PREREQUISITES.into());
337
338 while self.current().is_some() && self.current() != Some(NEWLINE) {
339 match self.current() {
340 Some(WHITESPACE) => {
341 self.bump(); }
343 Some(IDENTIFIER) => {
344 self.builder.start_node(PREREQUISITE.into());
346
347 if self.is_archive_member() {
348 self.parse_archive_member();
349 } else {
350 self.bump(); }
352
353 self.builder.finish_node(); }
355 Some(DOLLAR) => {
356 self.builder.start_node(PREREQUISITE.into());
358
359 self.bump(); if self.current() == Some(LPAREN) {
363 self.bump(); let mut paren_count = 1;
365
366 while self.current().is_some() && paren_count > 0 {
367 if self.current() == Some(LPAREN) {
368 paren_count += 1;
369 } else if self.current() == Some(RPAREN) {
370 paren_count -= 1;
371 }
372 self.bump();
373 }
374 } else {
375 if self.current().is_some() {
377 self.bump();
378 }
379 }
380
381 self.builder.finish_node(); }
383 _ => {
384 self.bump();
386 }
387 }
388 }
389
390 self.builder.finish_node(); }
392
393 fn parse_rule_recipes(&mut self) {
394 let mut conditional_depth = 0;
396 let mut newline_count = 0;
398
399 loop {
400 match self.current() {
401 Some(INDENT) => {
402 newline_count = 0;
403 self.parse_recipe_line();
404 }
405 Some(NEWLINE) => {
406 newline_count += 1;
407 self.bump();
408 }
409 Some(COMMENT) => {
410 if conditional_depth == 0 && newline_count >= 1 {
412 break;
413 }
414 newline_count = 0;
415 self.parse_comment();
416 }
417 Some(IDENTIFIER) => {
418 let token = &self.tokens.last().unwrap().1.clone();
419 if (token == "ifdef"
421 || token == "ifndef"
422 || token == "ifeq"
423 || token == "ifneq")
424 && matches!(self.variant, None | Some(MakefileVariant::GNUMake))
425 {
426 if conditional_depth == 0 && newline_count >= 1 {
429 break;
430 }
431 newline_count = 0;
432 conditional_depth += 1;
433 self.parse_conditional();
434 conditional_depth -= 1;
437 } else if token == "include" || token == "-include" || token == "sinclude" {
438 if conditional_depth == 0 && newline_count >= 1 {
440 break;
441 }
442 newline_count = 0;
443 self.parse_include();
444 } else if token == "else" || token == "endif" {
445 break;
448 } else {
449 if conditional_depth == 0 {
451 break;
452 }
453 break;
456 }
457 }
458 _ => break,
459 }
460 }
461 }
462
463 fn find_and_consume_colon(&mut self) -> bool {
464 self.skip_ws();
466
467 if self.current() == Some(OPERATOR) && self.tokens.last().unwrap().1 == ":" {
469 self.bump();
470 return true;
471 }
472
473 let has_colon = self
475 .tokens
476 .iter()
477 .rev()
478 .any(|(kind, text)| *kind == OPERATOR && text == ":");
479
480 if has_colon {
481 while self.current().is_some() {
483 if self.current() == Some(OPERATOR)
484 && self.tokens.last().map(|(_, text)| text.as_str()) == Some(":")
485 {
486 self.bump();
487 return true;
488 }
489 self.bump();
490 }
491 }
492
493 self.error("expected ':'".to_string());
494 false
495 }
496
497 fn parse_rule(&mut self) {
498 self.builder.start_node(RULE.into());
499
500 self.skip_ws();
502 self.builder.start_node(TARGETS.into());
503 let has_target = self.parse_rule_targets();
504 self.builder.finish_node();
505
506 let has_colon = if has_target {
508 self.find_and_consume_colon()
509 } else {
510 false
511 };
512
513 if has_target && has_colon {
515 self.skip_ws();
516 self.parse_rule_dependencies();
517 self.expect_eol();
518
519 self.parse_rule_recipes();
521 }
522
523 self.builder.finish_node();
524 }
525
526 fn parse_rule_targets(&mut self) -> bool {
527 let has_first_target = self.parse_rule_target();
529
530 if !has_first_target {
531 return false;
532 }
533
534 loop {
536 self.skip_ws();
537
538 if self.current() == Some(OPERATOR) && self.tokens.last().unwrap().1 == ":" {
540 break;
541 }
542
543 match self.current() {
545 Some(IDENTIFIER) | Some(DOLLAR) => {
546 if !self.parse_rule_target() {
547 break;
548 }
549 }
550 _ => break,
551 }
552 }
553
554 true
555 }
556
557 fn parse_comment(&mut self) {
558 if self.current() == Some(COMMENT) {
559 self.bump(); if self.current() == Some(NEWLINE) {
563 self.bump(); } else if self.current() == Some(WHITESPACE) {
565 self.skip_ws();
567 if self.current() == Some(NEWLINE) {
568 self.bump();
569 }
570 }
571 } else {
573 self.error("expected comment".to_string());
574 }
575 }
576
577 fn parse_assignment(&mut self) {
578 self.builder.start_node(VARIABLE.into());
579
580 self.skip_ws();
582 if self.current() == Some(IDENTIFIER) && self.tokens.last().unwrap().1 == "export" {
583 self.bump();
584 self.skip_ws();
585 }
586
587 match self.current() {
589 Some(IDENTIFIER) => self.bump(),
590 Some(DOLLAR) => self.parse_variable_reference(),
591 _ => {
592 self.error("expected variable name".to_string());
593 self.builder.finish_node();
594 return;
595 }
596 }
597
598 self.skip_ws();
600 match self.current() {
601 Some(OPERATOR) => {
602 let op = &self.tokens.last().unwrap().1;
603 if ["=", ":=", "::=", ":::=", "+=", "?=", "!="].contains(&op.as_str()) {
604 self.bump();
605 self.skip_ws();
606
607 self.builder.start_node(EXPR.into());
609 while self.current().is_some() && self.current() != Some(NEWLINE) {
610 self.bump();
611 }
612 self.builder.finish_node();
613
614 if self.current() == Some(NEWLINE) {
616 self.bump();
617 } else {
618 self.error("expected newline after variable value".to_string());
619 }
620 } else {
621 self.error(format!("invalid assignment operator: {}", op));
622 }
623 }
624 _ => self.error("expected assignment operator".to_string()),
625 }
626
627 self.builder.finish_node();
628 }
629
630 fn parse_variable_reference(&mut self) {
631 self.builder.start_node(EXPR.into());
632 self.bump(); if self.current() == Some(LPAREN) {
635 self.bump(); let mut is_function = false;
639
640 if self.current() == Some(IDENTIFIER) {
641 let function_name = &self.tokens.last().unwrap().1;
642 let known_functions = [
644 "shell", "wildcard", "call", "eval", "file", "abspath", "dir",
645 ];
646 if known_functions.contains(&function_name.as_str()) {
647 is_function = true;
648 }
649 }
650
651 if is_function {
652 self.bump();
654
655 self.consume_balanced_parens(1);
657 } else {
658 self.parse_parenthesized_expr_internal(true);
660 }
661 } else {
662 self.error("expected ( after $ in variable reference".to_string());
663 }
664
665 self.builder.finish_node();
666 }
667
668 fn parse_parenthesized_expr(&mut self) {
671 self.builder.start_node(EXPR.into());
672
673 if self.current() == Some(LPAREN) {
675 self.bump(); self.parse_parenthesized_expr_internal(false);
678 } else if self.current() == Some(QUOTE) {
679 self.parse_quoted_comparison();
681 } else {
682 self.error("expected opening parenthesis or quote".to_string());
683 }
684
685 self.builder.finish_node();
686 }
687
688 fn parse_parenthesized_expr_internal(&mut self, is_variable_ref: bool) {
690 let mut paren_count = 1;
691
692 while paren_count > 0 && self.current().is_some() {
693 match self.current() {
694 Some(LPAREN) => {
695 paren_count += 1;
696 self.bump();
697 self.builder.start_node(EXPR.into());
699 }
700 Some(RPAREN) => {
701 paren_count -= 1;
702 self.bump();
703 if paren_count > 0 {
704 self.builder.finish_node();
705 }
706 }
707 Some(QUOTE) => {
708 self.parse_quoted_string();
710 }
711 Some(DOLLAR) => {
712 self.parse_variable_reference();
714 }
715 Some(_) => self.bump(),
716 None => {
717 self.error(if is_variable_ref {
718 "unclosed variable reference".to_string()
719 } else {
720 "unclosed parenthesis".to_string()
721 });
722 break;
723 }
724 }
725 }
726
727 if !is_variable_ref {
728 self.skip_ws();
729 self.expect_eol();
730 }
731 }
732
733 fn parse_quoted_comparison(&mut self) {
736 if self.current() == Some(QUOTE) {
738 self.bump(); } else {
740 self.error("expected first quoted argument".to_string());
741 }
742
743 self.skip_ws();
745
746 if self.current() == Some(QUOTE) {
748 self.bump(); } else {
750 self.error("expected second quoted argument".to_string());
751 }
752
753 self.skip_ws();
755 self.expect_eol();
756 }
757
758 fn parse_quoted_string(&mut self) {
760 self.bump(); while !self.is_at_eof() && self.current() != Some(QUOTE) {
762 self.bump();
763 }
764 if self.current() == Some(QUOTE) {
765 self.bump();
766 }
767 }
768
769 fn parse_conditional_keyword(&mut self) -> Option<String> {
770 if self.current() != Some(IDENTIFIER) {
771 self.error(
772 "expected conditional keyword (ifdef, ifndef, ifeq, or ifneq)".to_string(),
773 );
774 return None;
775 }
776
777 let token = self.tokens.last().unwrap().1.clone();
778 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&token.as_str()) {
779 self.error(format!("unknown conditional directive: {}", token));
780 return None;
781 }
782
783 self.bump();
784 Some(token)
785 }
786
787 fn parse_simple_condition(&mut self) {
788 self.builder.start_node(EXPR.into());
789
790 self.skip_ws();
792
793 let mut found_var = false;
795
796 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
797 match self.current() {
798 Some(WHITESPACE) => self.skip_ws(),
799 Some(DOLLAR) => {
800 found_var = true;
801 self.parse_variable_reference();
802 }
803 Some(_) => {
804 found_var = true;
806 self.bump();
807 }
808 None => break,
809 }
810 }
811
812 if !found_var {
813 self.error("expected condition after conditional directive".to_string());
815 }
816
817 self.builder.finish_node();
818
819 if self.current() == Some(NEWLINE) {
821 self.bump();
822 } else if !self.is_at_eof() {
823 self.skip_until_newline();
824 }
825 }
826
827 fn is_conditional_directive(&self, token: &str) -> bool {
829 token == "ifdef"
830 || token == "ifndef"
831 || token == "ifeq"
832 || token == "ifneq"
833 || token == "else"
834 || token == "endif"
835 }
836
837 fn handle_conditional_token(&mut self, token: &str, depth: &mut usize) -> bool {
839 match token {
840 "ifdef" | "ifndef" | "ifeq" | "ifneq"
841 if matches!(self.variant, None | Some(MakefileVariant::GNUMake)) =>
842 {
843 self.parse_conditional();
846 true
847 }
848 "else" => {
849 if *depth == 0 {
851 self.error("else without matching if".to_string());
852 self.bump();
854 false
855 } else {
856 self.builder.start_node(CONDITIONAL_ELSE.into());
858
859 self.bump();
861 self.skip_ws();
862
863 if self.current() == Some(IDENTIFIER) {
865 let next_token = &self.tokens.last().unwrap().1;
866 if next_token == "ifdef"
867 || next_token == "ifndef"
868 || next_token == "ifeq"
869 || next_token == "ifneq"
870 {
871 match next_token.as_str() {
874 "ifdef" | "ifndef" => {
875 self.bump(); self.skip_ws();
877 self.parse_simple_condition();
878 }
879 "ifeq" | "ifneq" => {
880 self.bump(); self.skip_ws();
882 self.parse_parenthesized_expr();
883 }
884 _ => unreachable!(),
885 }
886 } else {
888 }
891 } else {
892 }
894
895 self.builder.finish_node(); true
897 }
898 }
899 "endif" => {
900 if *depth == 0 {
902 self.error("endif without matching if".to_string());
903 self.bump();
905 false
906 } else {
907 *depth -= 1;
908
909 self.builder.start_node(CONDITIONAL_ENDIF.into());
911
912 self.bump();
914
915 self.skip_ws();
917
918 if self.current() == Some(COMMENT) {
923 self.parse_comment();
924 } else if self.current() == Some(NEWLINE) {
925 self.bump();
926 } else if self.current() == Some(WHITESPACE) {
927 self.skip_ws();
929 if self.current() == Some(NEWLINE) {
930 self.bump();
931 }
932 } else if !self.is_at_eof() {
934 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
937 self.bump();
938 }
939 if self.current() == Some(NEWLINE) {
940 self.bump();
941 }
942 }
943 self.builder.finish_node(); true
947 }
948 }
949 _ => false,
950 }
951 }
952
953 fn parse_conditional(&mut self) {
954 self.builder.start_node(CONDITIONAL.into());
955
956 self.builder.start_node(CONDITIONAL_IF.into());
958
959 let Some(token) = self.parse_conditional_keyword() else {
961 self.skip_until_newline();
962 self.builder.finish_node(); self.builder.finish_node(); return;
965 };
966
967 self.skip_ws();
969
970 match token.as_str() {
972 "ifdef" | "ifndef" => {
973 self.parse_simple_condition();
974 }
975 "ifeq" | "ifneq" => {
976 self.parse_parenthesized_expr();
977 }
978 _ => unreachable!("Invalid conditional token"),
979 }
980
981 self.skip_ws();
983 if self.current() == Some(COMMENT) {
984 self.parse_comment();
985 }
986 self.builder.finish_node(); let mut depth = 1;
992
993 let mut position_count = std::collections::HashMap::<usize, usize>::new();
995 let max_repetitions = 15; while depth > 0 && !self.is_at_eof() {
998 let current_pos = self.tokens.len();
1000 *position_count.entry(current_pos).or_insert(0) += 1;
1001
1002 if position_count.get(¤t_pos).unwrap() > &max_repetitions {
1005 break;
1008 }
1009
1010 match self.current() {
1011 None => {
1012 self.error("unterminated conditional (missing endif)".to_string());
1013 break;
1014 }
1015 Some(IDENTIFIER) => {
1016 let token = self.tokens.last().unwrap().1.clone();
1017 if !self.handle_conditional_token(&token, &mut depth) {
1018 if token == "include" || token == "-include" || token == "sinclude" {
1019 self.parse_include();
1020 } else {
1021 self.parse_normal_content();
1022 }
1023 }
1024 }
1025 Some(INDENT) => self.parse_recipe_line(),
1026 Some(WHITESPACE) => self.bump(),
1027 Some(COMMENT) => self.parse_comment(),
1028 Some(NEWLINE) => self.bump(),
1029 Some(DOLLAR) => self.parse_normal_content(),
1030 Some(QUOTE) => self.parse_quoted_string(),
1031 Some(_) => {
1032 self.bump();
1034 }
1035 }
1036 }
1037
1038 self.builder.finish_node();
1039 }
1040
1041 fn parse_normal_content(&mut self) {
1043 self.skip_ws();
1045
1046 if self.is_assignment_line() {
1048 self.parse_assignment();
1049 } else {
1050 self.parse_rule();
1052 }
1053 }
1054
1055 fn parse_include(&mut self) {
1056 self.builder.start_node(INCLUDE.into());
1057
1058 if self.current() != Some(IDENTIFIER)
1060 || (!["include", "-include", "sinclude"]
1061 .contains(&self.tokens.last().unwrap().1.as_str()))
1062 {
1063 self.error("expected include directive".to_string());
1064 self.builder.finish_node();
1065 return;
1066 }
1067 self.bump();
1068 self.skip_ws();
1069
1070 self.builder.start_node(EXPR.into());
1072 let mut found_path = false;
1073
1074 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1075 match self.current() {
1076 Some(WHITESPACE) => self.skip_ws(),
1077 Some(DOLLAR) => {
1078 found_path = true;
1079 self.parse_variable_reference();
1080 }
1081 Some(_) => {
1082 found_path = true;
1084 self.bump();
1085 }
1086 None => break,
1087 }
1088 }
1089
1090 if !found_path {
1091 self.error("expected file path after include".to_string());
1092 }
1093
1094 self.builder.finish_node();
1095
1096 if self.current() == Some(NEWLINE) {
1098 self.bump();
1099 } else if !self.is_at_eof() {
1100 self.error("expected newline after include".to_string());
1101 self.skip_until_newline();
1102 }
1103
1104 self.builder.finish_node();
1105 }
1106
1107 fn parse_identifier_token(&mut self) -> bool {
1108 let token = &self.tokens.last().unwrap().1;
1109
1110 if token.starts_with("%") {
1112 self.parse_rule();
1113 return true;
1114 }
1115
1116 if token.starts_with("if")
1117 && matches!(self.variant, None | Some(MakefileVariant::GNUMake))
1118 {
1119 self.parse_conditional();
1120 return true;
1121 }
1122
1123 if token == "include" || token == "-include" || token == "sinclude" {
1124 self.parse_include();
1125 return true;
1126 }
1127
1128 self.parse_normal_content();
1130 true
1131 }
1132
1133 fn parse_token(&mut self) -> bool {
1134 match self.current() {
1135 None => false,
1136 Some(IDENTIFIER) => {
1137 let token = &self.tokens.last().unwrap().1;
1138 if self.is_conditional_directive(token)
1139 && matches!(self.variant, None | Some(MakefileVariant::GNUMake))
1140 {
1141 self.parse_conditional();
1142 true
1143 } else {
1144 self.parse_identifier_token()
1145 }
1146 }
1147 Some(DOLLAR) => {
1148 self.parse_normal_content();
1149 true
1150 }
1151 Some(NEWLINE) => {
1152 self.builder.start_node(BLANK_LINE.into());
1153 self.bump();
1154 self.builder.finish_node();
1155 true
1156 }
1157 Some(COMMENT) => {
1158 self.parse_comment();
1159 true
1160 }
1161 Some(WHITESPACE) => {
1162 if self.is_end_of_file_or_newline_after_whitespace() {
1164 self.skip_ws();
1167 return true;
1168 }
1169
1170 let look_ahead_pos = self.tokens.len().saturating_sub(1);
1173 let mut is_documentation_or_help = false;
1174
1175 if look_ahead_pos > 0 {
1176 let next_token = &self.tokens[look_ahead_pos - 1];
1177 if next_token.0 == IDENTIFIER
1180 || next_token.0 == COMMENT
1181 || next_token.0 == TEXT
1182 {
1183 is_documentation_or_help = true;
1184 }
1185 }
1186
1187 if is_documentation_or_help {
1188 self.skip_ws();
1191 while self.current().is_some() && self.current() != Some(NEWLINE) {
1192 self.bump();
1193 }
1194 if self.current() == Some(NEWLINE) {
1195 self.bump();
1196 }
1197 } else {
1198 self.skip_ws();
1199 }
1200 true
1201 }
1202 Some(INDENT) => {
1203 self.bump();
1205
1206 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1208 self.bump();
1209 }
1210 if self.current() == Some(NEWLINE) {
1211 self.bump();
1212 }
1213 true
1214 }
1215 Some(kind) => {
1216 self.error(format!("unexpected token {:?}", kind));
1217 self.bump();
1218 true
1219 }
1220 }
1221 }
1222
1223 fn parse(mut self) -> Parse {
1224 self.builder.start_node(ROOT.into());
1225
1226 while self.parse_token() {}
1227
1228 self.builder.finish_node();
1229
1230 Parse {
1231 green_node: self.builder.finish(),
1232 errors: self.errors,
1233 }
1234 }
1235
1236 fn is_assignment_line(&mut self) -> bool {
1238 let assignment_ops = ["=", ":=", "::=", ":::=", "+=", "?=", "!="];
1239 let mut pos = self.tokens.len().saturating_sub(1);
1240 let mut seen_identifier = false;
1241 let mut seen_export = false;
1242
1243 while pos > 0 {
1244 let (kind, text) = &self.tokens[pos];
1245
1246 match kind {
1247 NEWLINE => break,
1248 IDENTIFIER if text == "export" => seen_export = true,
1249 IDENTIFIER if !seen_identifier => seen_identifier = true,
1250 OPERATOR if assignment_ops.contains(&text.as_str()) => {
1251 return seen_identifier || seen_export
1252 }
1253 OPERATOR if text == ":" => return false, WHITESPACE => (),
1255 _ if seen_export => return true, _ => return false,
1257 }
1258 pos = pos.saturating_sub(1);
1259 }
1260 false
1261 }
1262
1263 fn bump(&mut self) {
1265 let (kind, text) = self.tokens.pop().unwrap();
1266 self.builder.token(kind.into(), text.as_str());
1267 }
1268 fn current(&self) -> Option<SyntaxKind> {
1270 self.tokens.last().map(|(kind, _)| *kind)
1271 }
1272
1273 fn expect_eol(&mut self) {
1274 self.skip_ws();
1276
1277 match self.current() {
1278 Some(NEWLINE) => {
1279 self.bump();
1280 }
1281 None => {
1282 }
1284 n => {
1285 self.error(format!("expected newline, got {:?}", n));
1286 self.skip_until_newline();
1288 }
1289 }
1290 }
1291
1292 fn is_at_eof(&self) -> bool {
1294 self.current().is_none()
1295 }
1296
1297 fn is_at_eof_or_only_whitespace(&self) -> bool {
1299 if self.is_at_eof() {
1300 return true;
1301 }
1302
1303 self.tokens
1305 .iter()
1306 .rev()
1307 .all(|(kind, _)| matches!(*kind, WHITESPACE | NEWLINE))
1308 }
1309
1310 fn skip_ws(&mut self) {
1311 while self.current() == Some(WHITESPACE) {
1312 self.bump()
1313 }
1314 }
1315
1316 fn skip_until_newline(&mut self) {
1317 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1318 self.bump();
1319 }
1320 if self.current() == Some(NEWLINE) {
1321 self.bump();
1322 }
1323 }
1324
1325 fn consume_balanced_parens(&mut self, start_paren_count: usize) -> usize {
1327 let mut paren_count = start_paren_count;
1328
1329 while paren_count > 0 && self.current().is_some() {
1330 match self.current() {
1331 Some(LPAREN) => {
1332 paren_count += 1;
1333 self.bump();
1334 }
1335 Some(RPAREN) => {
1336 paren_count -= 1;
1337 self.bump();
1338 if paren_count == 0 {
1339 break;
1340 }
1341 }
1342 Some(DOLLAR) => {
1343 self.parse_variable_reference();
1345 }
1346 Some(_) => self.bump(),
1347 None => {
1348 self.error("unclosed parenthesis".to_string());
1349 break;
1350 }
1351 }
1352 }
1353
1354 paren_count
1355 }
1356
1357 fn is_end_of_file_or_newline_after_whitespace(&self) -> bool {
1359 if self.is_at_eof_or_only_whitespace() {
1361 return true;
1362 }
1363
1364 if self.tokens.len() <= 1 {
1366 return true;
1367 }
1368
1369 false
1370 }
1371 }
1372
1373 let mut tokens = lex(text);
1374 tokens.reverse();
1375 Parser {
1376 tokens,
1377 builder: GreenNodeBuilder::new(),
1378 errors: Vec::new(),
1379 original_text: text.to_string(),
1380 variant,
1381 }
1382 .parse()
1383}
1384
1385pub(crate) type SyntaxNode = rowan::SyntaxNode<Lang>;
1391#[allow(unused)]
1392type SyntaxToken = rowan::SyntaxToken<Lang>;
1393#[allow(unused)]
1394pub(crate) type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
1395
1396impl Parse {
1397 fn syntax(&self) -> SyntaxNode {
1398 SyntaxNode::new_root_mut(self.green_node.clone())
1399 }
1400
1401 pub(crate) fn root(&self) -> Makefile {
1402 Makefile::cast(self.syntax()).unwrap()
1403 }
1404}
1405
1406fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
1409 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
1410 let mut line = 0;
1411 let mut last_newline_offset = rowan::TextSize::from(0);
1412
1413 for element in root.preorder_with_tokens() {
1414 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
1415 if token.text_range().start() >= offset {
1416 break;
1417 }
1418
1419 for (idx, _) in token.text().match_indices('\n') {
1421 line += 1;
1422 last_newline_offset =
1423 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
1424 }
1425 }
1426 }
1427
1428 let column: usize = (offset - last_newline_offset).into();
1429 (line, column)
1430}
1431
1432macro_rules! ast_node {
1433 ($ast:ident, $kind:ident) => {
1434 #[derive(Clone, PartialEq, Eq, Hash)]
1435 #[repr(transparent)]
1436 pub struct $ast(SyntaxNode);
1438
1439 impl AstNode for $ast {
1440 type Language = Lang;
1441
1442 fn can_cast(kind: SyntaxKind) -> bool {
1443 kind == $kind
1444 }
1445
1446 fn cast(syntax: SyntaxNode) -> Option<Self> {
1447 if Self::can_cast(syntax.kind()) {
1448 Some(Self(syntax))
1449 } else {
1450 None
1451 }
1452 }
1453
1454 fn syntax(&self) -> &SyntaxNode {
1455 &self.0
1456 }
1457 }
1458
1459 impl $ast {
1460 pub fn line(&self) -> usize {
1462 line_col_at_offset(&self.0, self.0.text_range().start()).0
1463 }
1464
1465 pub fn column(&self) -> usize {
1467 line_col_at_offset(&self.0, self.0.text_range().start()).1
1468 }
1469
1470 pub fn line_col(&self) -> (usize, usize) {
1473 line_col_at_offset(&self.0, self.0.text_range().start())
1474 }
1475 }
1476
1477 impl core::fmt::Display for $ast {
1478 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
1479 write!(f, "{}", self.0.text())
1480 }
1481 }
1482 };
1483}
1484
1485ast_node!(Makefile, ROOT);
1486ast_node!(Rule, RULE);
1487ast_node!(Recipe, RECIPE);
1488ast_node!(Identifier, IDENTIFIER);
1489ast_node!(VariableDefinition, VARIABLE);
1490ast_node!(Include, INCLUDE);
1491ast_node!(ArchiveMembers, ARCHIVE_MEMBERS);
1492ast_node!(ArchiveMember, ARCHIVE_MEMBER);
1493ast_node!(Conditional, CONDITIONAL);
1494
1495impl Recipe {
1496 pub fn text(&self) -> String {
1508 let tokens: Vec<_> = self
1509 .syntax()
1510 .children_with_tokens()
1511 .filter_map(|it| it.as_token().cloned())
1512 .collect();
1513
1514 if tokens.is_empty() {
1515 return String::new();
1516 }
1517
1518 let start = if tokens.first().map(|t| t.kind()) == Some(INDENT) {
1520 1
1521 } else {
1522 0
1523 };
1524
1525 let end = if tokens.last().map(|t| t.kind()) == Some(NEWLINE) {
1527 tokens.len() - 1
1528 } else {
1529 tokens.len()
1530 };
1531
1532 let mut after_newline = false;
1536 tokens[start..end]
1537 .iter()
1538 .filter_map(|t| match t.kind() {
1539 TEXT => {
1540 after_newline = false;
1541 Some(t.text().to_string())
1542 }
1543 NEWLINE => {
1544 after_newline = true;
1545 Some(t.text().to_string())
1546 }
1547 INDENT if after_newline => {
1548 after_newline = false;
1549 let text = t.text();
1551 Some(text.strip_prefix('\t').unwrap_or(text).to_string())
1552 }
1553 _ => None,
1554 })
1555 .collect()
1556 }
1557
1558 pub fn comment(&self) -> Option<String> {
1574 self.syntax()
1575 .children_with_tokens()
1576 .filter_map(|it| {
1577 if let Some(token) = it.as_token() {
1578 if token.kind() == COMMENT {
1579 return Some(token.text().to_string());
1580 }
1581 }
1582 None
1583 })
1584 .next()
1585 }
1586
1587 pub fn full(&self) -> String {
1603 self.syntax()
1604 .children_with_tokens()
1605 .filter_map(|it| {
1606 if let Some(token) = it.as_token() {
1607 if token.kind() == TEXT || token.kind() == COMMENT {
1609 return Some(token.text().to_string());
1610 }
1611 }
1612 None
1613 })
1614 .collect::<Vec<_>>()
1615 .join("")
1616 }
1617
1618 pub fn parent(&self) -> Option<Rule> {
1631 self.syntax().parent().and_then(Rule::cast)
1632 }
1633
1634 pub fn is_silent(&self) -> bool {
1647 let text = self.text();
1648 text.starts_with('@') || text.starts_with("-@") || text.starts_with("+@")
1649 }
1650
1651 pub fn is_ignore_errors(&self) -> bool {
1664 let text = self.text();
1665 text.starts_with('-') || text.starts_with("@-") || text.starts_with("+-")
1666 }
1667
1668 pub fn set_prefix(&mut self, prefix: &str) {
1685 let text = self.text();
1686
1687 let stripped = text.trim_start_matches(['@', '-', '+']);
1689
1690 let new_text = format!("{}{}", prefix, stripped);
1692
1693 self.replace_text(&new_text);
1694 }
1695
1696 pub fn replace_text(&mut self, new_text: &str) {
1709 let node = self.syntax();
1710 let parent = node.parent().expect("Recipe node must have a parent");
1711 let node_index = node.index();
1712
1713 let mut builder = GreenNodeBuilder::new();
1715 builder.start_node(RECIPE.into());
1716
1717 if let Some(indent_token) = node
1719 .children_with_tokens()
1720 .find(|it| it.as_token().map(|t| t.kind() == INDENT).unwrap_or(false))
1721 {
1722 builder.token(INDENT.into(), indent_token.as_token().unwrap().text());
1723 } else {
1724 builder.token(INDENT.into(), "\t");
1725 }
1726
1727 builder.token(TEXT.into(), new_text);
1728
1729 if let Some(newline_token) = node
1731 .children_with_tokens()
1732 .find(|it| it.as_token().map(|t| t.kind() == NEWLINE).unwrap_or(false))
1733 {
1734 builder.token(NEWLINE.into(), newline_token.as_token().unwrap().text());
1735 } else {
1736 builder.token(NEWLINE.into(), "\n");
1737 }
1738
1739 builder.finish_node();
1740 let new_syntax = SyntaxNode::new_root_mut(builder.finish());
1741
1742 parent.splice_children(node_index..node_index + 1, vec![new_syntax.into()]);
1744
1745 *self = parent
1749 .children_with_tokens()
1750 .nth(node_index)
1751 .and_then(|element| element.into_node())
1752 .and_then(Recipe::cast)
1753 .expect("New recipe node should exist at the same index");
1754 }
1755
1756 pub fn insert_before(&self, text: &str) {
1769 let node = self.syntax();
1770 let parent = node.parent().expect("Recipe node must have a parent");
1771 let node_index = node.index();
1772
1773 let mut builder = GreenNodeBuilder::new();
1775 builder.start_node(RECIPE.into());
1776 builder.token(INDENT.into(), "\t");
1777 builder.token(TEXT.into(), text);
1778 builder.token(NEWLINE.into(), "\n");
1779 builder.finish_node();
1780 let new_syntax = SyntaxNode::new_root_mut(builder.finish());
1781
1782 parent.splice_children(node_index..node_index, vec![new_syntax.into()]);
1784 }
1785
1786 pub fn insert_after(&self, text: &str) {
1799 let node = self.syntax();
1800 let parent = node.parent().expect("Recipe node must have a parent");
1801 let node_index = node.index();
1802
1803 let mut builder = GreenNodeBuilder::new();
1805 builder.start_node(RECIPE.into());
1806 builder.token(INDENT.into(), "\t");
1807 builder.token(TEXT.into(), text);
1808 builder.token(NEWLINE.into(), "\n");
1809 builder.finish_node();
1810 let new_syntax = SyntaxNode::new_root_mut(builder.finish());
1811
1812 parent.splice_children(node_index + 1..node_index + 1, vec![new_syntax.into()]);
1814 }
1815
1816 pub fn remove(&self) {
1829 let node = self.syntax();
1830 let parent = node.parent().expect("Recipe node must have a parent");
1831 let node_index = node.index();
1832
1833 parent.splice_children(node_index..node_index + 1, vec![]);
1835 }
1836}
1837
1838pub(crate) fn trim_trailing_newlines(node: &SyntaxNode) {
1842 let mut newlines_to_remove = vec![];
1844 let mut current = node.last_child_or_token();
1845
1846 while let Some(element) = current {
1847 match &element {
1848 rowan::NodeOrToken::Token(token) if token.kind() == NEWLINE => {
1849 newlines_to_remove.push(token.clone());
1850 current = token.prev_sibling_or_token();
1851 }
1852 rowan::NodeOrToken::Node(n) if n.kind() == RECIPE => {
1853 let mut recipe_current = n.last_child_or_token();
1855 while let Some(recipe_element) = recipe_current {
1856 match &recipe_element {
1857 rowan::NodeOrToken::Token(token) if token.kind() == NEWLINE => {
1858 newlines_to_remove.push(token.clone());
1859 recipe_current = token.prev_sibling_or_token();
1860 }
1861 _ => break,
1862 }
1863 }
1864 break; }
1866 _ => break,
1867 }
1868 }
1869
1870 if newlines_to_remove.len() > 1 {
1873 newlines_to_remove.sort_by_key(|t| std::cmp::Reverse(t.index()));
1875
1876 for token in newlines_to_remove.iter().take(newlines_to_remove.len() - 1) {
1877 let parent = token.parent().unwrap();
1878 let idx = token.index();
1879 parent.splice_children(idx..idx + 1, vec![]);
1880 }
1881 }
1882}
1883
1884pub(crate) fn remove_with_preceding_comments(node: &SyntaxNode, parent: &SyntaxNode) {
1892 let mut collected_elements = vec![];
1893 let mut found_comment = false;
1894
1895 let mut current = node.prev_sibling_or_token();
1897 while let Some(element) = current {
1898 match &element {
1899 rowan::NodeOrToken::Token(token) => match token.kind() {
1900 COMMENT => {
1901 if token.text().starts_with("#!") {
1902 break; }
1904 found_comment = true;
1905 collected_elements.push(element.clone());
1906 }
1907 NEWLINE | WHITESPACE => {
1908 collected_elements.push(element.clone());
1909 }
1910 _ => break, },
1912 rowan::NodeOrToken::Node(n) => {
1913 if n.kind() == BLANK_LINE {
1915 collected_elements.push(element.clone());
1916 } else {
1917 break; }
1919 }
1920 }
1921 current = element.prev_sibling_or_token();
1922 }
1923
1924 let mut elements_to_remove = vec![];
1927 let mut consecutive_newlines = 0;
1928 for element in collected_elements.iter().rev() {
1929 let should_remove = match element {
1930 rowan::NodeOrToken::Token(token) => match token.kind() {
1931 COMMENT => {
1932 consecutive_newlines = 0;
1933 found_comment
1934 }
1935 NEWLINE => {
1936 consecutive_newlines += 1;
1937 found_comment && consecutive_newlines <= 1
1938 }
1939 WHITESPACE => found_comment,
1940 _ => false,
1941 },
1942 rowan::NodeOrToken::Node(n) => {
1943 if n.kind() == BLANK_LINE {
1945 consecutive_newlines += 1;
1946 found_comment && consecutive_newlines <= 1
1947 } else {
1948 false
1949 }
1950 }
1951 };
1952
1953 if should_remove {
1954 elements_to_remove.push(element.clone());
1955 }
1956 }
1957
1958 let mut all_to_remove = vec![rowan::NodeOrToken::Node(node.clone())];
1961 all_to_remove.extend(elements_to_remove.into_iter().rev());
1962
1963 all_to_remove.sort_by_key(|el| std::cmp::Reverse(el.index()));
1965
1966 for element in all_to_remove {
1967 let idx = element.index();
1968 parent.splice_children(idx..idx + 1, vec![]);
1969 }
1970}
1971
1972impl FromStr for Rule {
1973 type Err = crate::Error;
1974
1975 fn from_str(s: &str) -> Result<Self, Self::Err> {
1976 Rule::parse(s).to_rule_result()
1977 }
1978}
1979
1980impl FromStr for Makefile {
1981 type Err = crate::Error;
1982
1983 fn from_str(s: &str) -> Result<Self, Self::Err> {
1984 Makefile::parse(s).to_result()
1985 }
1986}
1987
1988#[cfg(test)]
1989mod tests {
1990 use super::*;
1991 use crate::ast::makefile::MakefileItem;
1992 use crate::pattern::matches_pattern;
1993
1994 #[test]
1995 fn test_conditionals() {
1996 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
2000 let mut buf = code.as_bytes();
2001 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
2002 assert!(makefile.code().contains("DEBUG_FLAG"));
2003
2004 let code =
2006 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
2007 let mut buf = code.as_bytes();
2008 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
2009 assert!(makefile.code().contains("RESULT"));
2010 assert!(makefile.code().contains("windows"));
2011
2012 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
2014 let mut buf = code.as_bytes();
2015 let makefile = Makefile::read_relaxed(&mut buf)
2016 .expect("Failed to parse nested conditionals with else");
2017 assert!(makefile.code().contains("CFLAGS"));
2018 assert!(makefile.code().contains("VERBOSE"));
2019
2020 let code = "ifdef DEBUG\nendif\n";
2022 let mut buf = code.as_bytes();
2023 let makefile =
2024 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
2025 assert!(makefile.code().contains("ifdef DEBUG"));
2026
2027 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelse ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
2029 let mut buf = code.as_bytes();
2030 let makefile =
2031 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with else ifeq");
2032 assert!(makefile.code().contains("EXT"));
2033
2034 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
2036 let mut buf = code.as_bytes();
2037 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
2038 assert!(makefile.code().contains("DEBUG"));
2039
2040 let code = "ifdef \nDEBUG := 1\nendif\n";
2042 let mut buf = code.as_bytes();
2043 let makefile = Makefile::read_relaxed(&mut buf)
2044 .expect("Failed to parse with recovery - missing condition");
2045 assert!(makefile.code().contains("DEBUG"));
2046 }
2047
2048 #[test]
2049 fn test_parse_simple() {
2050 const SIMPLE: &str = r#"VARIABLE = value
2051
2052rule: dependency
2053 command
2054"#;
2055 let parsed = parse(SIMPLE, None);
2056 assert!(parsed.errors.is_empty());
2057 let node = parsed.syntax();
2058 assert_eq!(
2059 format!("{:#?}", node),
2060 r#"ROOT@0..44
2061 VARIABLE@0..17
2062 IDENTIFIER@0..8 "VARIABLE"
2063 WHITESPACE@8..9 " "
2064 OPERATOR@9..10 "="
2065 WHITESPACE@10..11 " "
2066 EXPR@11..16
2067 IDENTIFIER@11..16 "value"
2068 NEWLINE@16..17 "\n"
2069 BLANK_LINE@17..18
2070 NEWLINE@17..18 "\n"
2071 RULE@18..44
2072 TARGETS@18..22
2073 IDENTIFIER@18..22 "rule"
2074 OPERATOR@22..23 ":"
2075 WHITESPACE@23..24 " "
2076 PREREQUISITES@24..34
2077 PREREQUISITE@24..34
2078 IDENTIFIER@24..34 "dependency"
2079 NEWLINE@34..35 "\n"
2080 RECIPE@35..44
2081 INDENT@35..36 "\t"
2082 TEXT@36..43 "command"
2083 NEWLINE@43..44 "\n"
2084"#
2085 );
2086
2087 let root = parsed.root();
2088
2089 let mut rules = root.rules().collect::<Vec<_>>();
2090 assert_eq!(rules.len(), 1);
2091 let rule = rules.pop().unwrap();
2092 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2093 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
2094 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2095
2096 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2097 assert_eq!(variables.len(), 1);
2098 let variable = variables.pop().unwrap();
2099 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2100 assert_eq!(variable.raw_value(), Some("value".to_string()));
2101 }
2102
2103 #[test]
2104 fn test_parse_export_assign() {
2105 const EXPORT: &str = r#"export VARIABLE := value
2106"#;
2107 let parsed = parse(EXPORT, None);
2108 assert!(parsed.errors.is_empty());
2109 let node = parsed.syntax();
2110 assert_eq!(
2111 format!("{:#?}", node),
2112 r#"ROOT@0..25
2113 VARIABLE@0..25
2114 IDENTIFIER@0..6 "export"
2115 WHITESPACE@6..7 " "
2116 IDENTIFIER@7..15 "VARIABLE"
2117 WHITESPACE@15..16 " "
2118 OPERATOR@16..18 ":="
2119 WHITESPACE@18..19 " "
2120 EXPR@19..24
2121 IDENTIFIER@19..24 "value"
2122 NEWLINE@24..25 "\n"
2123"#
2124 );
2125
2126 let root = parsed.root();
2127
2128 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2129 assert_eq!(variables.len(), 1);
2130 let variable = variables.pop().unwrap();
2131 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2132 assert_eq!(variable.raw_value(), Some("value".to_string()));
2133 }
2134
2135 #[test]
2136 fn test_parse_multiple_prerequisites() {
2137 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
2138 command
2139
2140"#;
2141 let parsed = parse(MULTIPLE_PREREQUISITES, None);
2142 assert!(parsed.errors.is_empty());
2143 let node = parsed.syntax();
2144 assert_eq!(
2145 format!("{:#?}", node),
2146 r#"ROOT@0..40
2147 RULE@0..40
2148 TARGETS@0..4
2149 IDENTIFIER@0..4 "rule"
2150 OPERATOR@4..5 ":"
2151 WHITESPACE@5..6 " "
2152 PREREQUISITES@6..29
2153 PREREQUISITE@6..17
2154 IDENTIFIER@6..17 "dependency1"
2155 WHITESPACE@17..18 " "
2156 PREREQUISITE@18..29
2157 IDENTIFIER@18..29 "dependency2"
2158 NEWLINE@29..30 "\n"
2159 RECIPE@30..39
2160 INDENT@30..31 "\t"
2161 TEXT@31..38 "command"
2162 NEWLINE@38..39 "\n"
2163 NEWLINE@39..40 "\n"
2164"#
2165 );
2166 let root = parsed.root();
2167
2168 let rule = root.rules().next().unwrap();
2169 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2170 assert_eq!(
2171 rule.prerequisites().collect::<Vec<_>>(),
2172 vec!["dependency1", "dependency2"]
2173 );
2174 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2175 }
2176
2177 #[test]
2178 fn test_add_rule() {
2179 let mut makefile = Makefile::new();
2180 let rule = makefile.add_rule("rule");
2181 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2182 assert_eq!(
2183 rule.prerequisites().collect::<Vec<_>>(),
2184 Vec::<String>::new()
2185 );
2186
2187 assert_eq!(makefile.to_string(), "rule:\n");
2188 }
2189
2190 #[test]
2191 fn test_add_rule_with_shebang() {
2192 let content = r#"#!/usr/bin/make -f
2194
2195build: blah
2196 $(MAKE) install
2197
2198clean:
2199 dh_clean
2200"#;
2201
2202 let mut makefile = Makefile::read_relaxed(content.as_bytes()).unwrap();
2203 let initial_count = makefile.rules().count();
2204 assert_eq!(initial_count, 2);
2205
2206 let rule = makefile.add_rule("build-indep");
2208 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["build-indep"]);
2209
2210 assert_eq!(makefile.rules().count(), initial_count + 1);
2212 }
2213
2214 #[test]
2215 fn test_add_rule_formatting() {
2216 let content = r#"build: blah
2218 $(MAKE) install
2219
2220clean:
2221 dh_clean
2222"#;
2223
2224 let mut makefile = Makefile::read_relaxed(content.as_bytes()).unwrap();
2225 let mut rule = makefile.add_rule("build-indep");
2226 rule.add_prerequisite("build").unwrap();
2227
2228 let expected = r#"build: blah
2229 $(MAKE) install
2230
2231clean:
2232 dh_clean
2233
2234build-indep: build
2235"#;
2236
2237 assert_eq!(makefile.to_string(), expected);
2238 }
2239
2240 #[test]
2241 fn test_push_command() {
2242 let mut makefile = Makefile::new();
2243 let mut rule = makefile.add_rule("rule");
2244
2245 rule.push_command("command");
2247 rule.push_command("command2");
2248
2249 assert_eq!(
2251 rule.recipes().collect::<Vec<_>>(),
2252 vec!["command", "command2"]
2253 );
2254
2255 rule.push_command("command3");
2257 assert_eq!(
2258 rule.recipes().collect::<Vec<_>>(),
2259 vec!["command", "command2", "command3"]
2260 );
2261
2262 assert_eq!(
2264 makefile.to_string(),
2265 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2266 );
2267
2268 assert_eq!(
2270 rule.to_string(),
2271 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2272 );
2273 }
2274
2275 #[test]
2276 fn test_replace_command() {
2277 let mut makefile = Makefile::new();
2278 let mut rule = makefile.add_rule("rule");
2279
2280 rule.push_command("command");
2282 rule.push_command("command2");
2283
2284 assert_eq!(
2286 rule.recipes().collect::<Vec<_>>(),
2287 vec!["command", "command2"]
2288 );
2289
2290 rule.replace_command(0, "new command");
2292 assert_eq!(
2293 rule.recipes().collect::<Vec<_>>(),
2294 vec!["new command", "command2"]
2295 );
2296
2297 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2299
2300 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2302 }
2303
2304 #[test]
2305 fn test_replace_command_with_comments() {
2306 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";
2309
2310 let makefile = Makefile::read_relaxed(&content[..]).unwrap();
2311
2312 let mut rule = makefile.rules().next().unwrap();
2313
2314 assert_eq!(rule.recipe_nodes().count(), 2);
2316 let recipes: Vec<_> = rule.recipe_nodes().collect();
2317 assert_eq!(recipes[0].text(), ""); assert_eq!(
2319 recipes[1].text(),
2320 "dh_strip --dbgsym-migration='amule-dbg (<< 1:2.3.2-2~)'"
2321 );
2322
2323 assert!(rule.replace_command(1, "dh_strip"));
2325
2326 assert_eq!(rule.recipe_nodes().count(), 2);
2328 let recipes: Vec<_> = rule.recipe_nodes().collect();
2329 assert_eq!(recipes[0].text(), ""); assert_eq!(recipes[1].text(), "dh_strip");
2331 }
2332
2333 #[test]
2334 fn test_parse_rule_without_newline() {
2335 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
2336 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2337 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2338 let rule = "rule: dependency".parse::<Rule>().unwrap();
2339 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2340 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
2341 }
2342
2343 #[test]
2344 fn test_parse_makefile_without_newline() {
2345 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
2346 assert_eq!(makefile.rules().count(), 1);
2347 }
2348
2349 #[test]
2350 fn test_from_reader() {
2351 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
2352 assert_eq!(makefile.rules().count(), 1);
2353 }
2354
2355 #[test]
2356 fn test_parse_with_tab_after_last_newline() {
2357 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
2358 assert_eq!(makefile.rules().count(), 1);
2359 }
2360
2361 #[test]
2362 fn test_parse_with_space_after_last_newline() {
2363 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
2364 assert_eq!(makefile.rules().count(), 1);
2365 }
2366
2367 #[test]
2368 fn test_parse_with_comment_after_last_newline() {
2369 let makefile =
2370 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
2371 assert_eq!(makefile.rules().count(), 1);
2372 }
2373
2374 #[test]
2375 fn test_parse_with_variable_rule() {
2376 let makefile =
2377 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
2378 .unwrap();
2379
2380 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2382 assert_eq!(vars.len(), 1);
2383 assert_eq!(vars[0].name(), Some("RULE".to_string()));
2384 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
2385
2386 let rules = makefile.rules().collect::<Vec<_>>();
2388 assert_eq!(rules.len(), 1);
2389 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
2390 assert_eq!(
2391 rules[0].prerequisites().collect::<Vec<_>>(),
2392 vec!["dependency"]
2393 );
2394 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2395 }
2396
2397 #[test]
2398 fn test_parse_with_variable_dependency() {
2399 let makefile =
2400 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
2401
2402 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2404 assert_eq!(vars.len(), 1);
2405 assert_eq!(vars[0].name(), Some("DEP".to_string()));
2406 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
2407
2408 let rules = makefile.rules().collect::<Vec<_>>();
2410 assert_eq!(rules.len(), 1);
2411 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2412 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
2413 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2414 }
2415
2416 #[test]
2417 fn test_parse_with_variable_command() {
2418 let makefile =
2419 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
2420
2421 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2423 assert_eq!(vars.len(), 1);
2424 assert_eq!(vars[0].name(), Some("COM".to_string()));
2425 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
2426
2427 let rules = makefile.rules().collect::<Vec<_>>();
2429 assert_eq!(rules.len(), 1);
2430 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2431 assert_eq!(
2432 rules[0].prerequisites().collect::<Vec<_>>(),
2433 vec!["dependency"]
2434 );
2435 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
2436 }
2437
2438 #[test]
2439 fn test_regular_line_error_reporting() {
2440 let input = "rule target\n\tcommand";
2441
2442 let parsed = parse(input, None);
2444 let direct_error = &parsed.errors[0];
2445
2446 assert_eq!(direct_error.line, 2);
2448 assert!(
2449 direct_error.message.contains("expected"),
2450 "Error message should contain 'expected': {}",
2451 direct_error.message
2452 );
2453 assert_eq!(direct_error.context, "\tcommand");
2454
2455 let reader_result = Makefile::from_reader(input.as_bytes());
2457 let parse_error = match reader_result {
2458 Ok(_) => panic!("Expected Parse error from from_reader"),
2459 Err(err) => match err {
2460 self::Error::Parse(parse_err) => parse_err,
2461 _ => panic!("Expected Parse error"),
2462 },
2463 };
2464
2465 let error_text = parse_error.to_string();
2467 assert!(error_text.contains("Error at line 2:"));
2468 assert!(error_text.contains("2| \tcommand"));
2469 }
2470
2471 #[test]
2472 fn test_parsing_error_context_with_bad_syntax() {
2473 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
2475
2476 match Makefile::from_reader(input.as_bytes()) {
2478 Ok(makefile) => {
2479 assert_eq!(
2481 makefile.rules().count(),
2482 0,
2483 "Should not have found any rules"
2484 );
2485 }
2486 Err(err) => match err {
2487 self::Error::Parse(error) => {
2488 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
2490 assert!(
2491 !error.errors[0].context.is_empty(),
2492 "Error context should not be empty"
2493 );
2494 }
2495 _ => panic!("Unexpected error type"),
2496 },
2497 };
2498 }
2499
2500 #[test]
2501 fn test_error_message_format() {
2502 let parse_error = ParseError {
2504 errors: vec![ErrorInfo {
2505 message: "test error".to_string(),
2506 line: 42,
2507 context: "some problematic code".to_string(),
2508 }],
2509 };
2510
2511 let error_text = parse_error.to_string();
2512 assert!(error_text.contains("Error at line 42: test error"));
2513 assert!(error_text.contains("42| some problematic code"));
2514 }
2515
2516 #[test]
2517 fn test_line_number_calculation() {
2518 let test_cases = [
2520 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
2524
2525 for (input, expected_line) in test_cases {
2526 match input.parse::<Makefile>() {
2528 Ok(_) => {
2529 continue;
2532 }
2533 Err(err) => {
2534 if let Error::Parse(parse_err) = err {
2535 assert_eq!(
2537 parse_err.errors[0].line, expected_line,
2538 "Line number should match the expected line"
2539 );
2540
2541 if parse_err.errors[0].message.contains("indented") {
2543 assert!(
2544 parse_err.errors[0].context.starts_with('\t'),
2545 "Context for indentation errors should include the tab character"
2546 );
2547 }
2548 } else {
2549 panic!("Expected parse error, got: {:?}", err);
2550 }
2551 }
2552 }
2553 }
2554 }
2555
2556 #[test]
2557 fn test_conditional_features() {
2558 let code = r#"
2560# Set variables based on DEBUG flag
2561ifdef DEBUG
2562 CFLAGS += -g -DDEBUG
2563else
2564 CFLAGS = -O2
2565endif
2566
2567# Define a build rule
2568all: $(OBJS)
2569 $(CC) $(CFLAGS) -o $@ $^
2570"#;
2571
2572 let mut buf = code.as_bytes();
2573 let makefile =
2574 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
2575
2576 assert!(!makefile.code().is_empty(), "Makefile has content");
2579
2580 let rules = makefile.rules().collect::<Vec<_>>();
2582 assert!(!rules.is_empty(), "Should have found rules");
2583
2584 assert!(code.contains("ifdef DEBUG"));
2586 assert!(code.contains("endif"));
2587
2588 let code_with_var = r#"
2590# Define a variable first
2591CC = gcc
2592
2593ifdef DEBUG
2594 CFLAGS += -g -DDEBUG
2595else
2596 CFLAGS = -O2
2597endif
2598
2599all: $(OBJS)
2600 $(CC) $(CFLAGS) -o $@ $^
2601"#;
2602
2603 let mut buf = code_with_var.as_bytes();
2604 let makefile =
2605 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
2606
2607 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2609 assert!(
2610 !vars.is_empty(),
2611 "Should have found at least the CC variable definition"
2612 );
2613 }
2614
2615 #[test]
2616 fn test_include_directive() {
2617 let parsed = parse(
2618 "include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n",
2619 None,
2620 );
2621 assert!(parsed.errors.is_empty());
2622 let node = parsed.syntax();
2623 assert!(format!("{:#?}", node).contains("INCLUDE@"));
2624 }
2625
2626 #[test]
2627 fn test_export_variables() {
2628 let parsed = parse("export SHELL := /bin/bash\n", None);
2629 assert!(parsed.errors.is_empty());
2630 let makefile = parsed.root();
2631 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2632 assert_eq!(vars.len(), 1);
2633 let shell_var = vars
2634 .iter()
2635 .find(|v| v.name() == Some("SHELL".to_string()))
2636 .unwrap();
2637 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
2638 }
2639
2640 #[test]
2641 fn test_variable_scopes() {
2642 let parsed = parse(
2643 "SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n",
2644 None,
2645 );
2646 assert!(parsed.errors.is_empty());
2647 let makefile = parsed.root();
2648 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2649 assert_eq!(vars.len(), 4);
2650 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
2651 assert!(var_names.contains(&"SIMPLE".to_string()));
2652 assert!(var_names.contains(&"IMMEDIATE".to_string()));
2653 assert!(var_names.contains(&"CONDITIONAL".to_string()));
2654 assert!(var_names.contains(&"APPEND".to_string()));
2655 }
2656
2657 #[test]
2658 fn test_pattern_rule_parsing() {
2659 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n", None);
2660 assert!(parsed.errors.is_empty());
2661 let makefile = parsed.root();
2662 let rules = makefile.rules().collect::<Vec<_>>();
2663 assert_eq!(rules.len(), 1);
2664 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
2665 assert!(rules[0].recipes().next().unwrap().contains("$@"));
2666 }
2667
2668 #[test]
2669 fn test_include_variants() {
2670 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
2672 let parsed = parse(makefile_str, None);
2673 assert!(parsed.errors.is_empty());
2674
2675 let node = parsed.syntax();
2677 let debug_str = format!("{:#?}", node);
2678
2679 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
2681
2682 let makefile = parsed.root();
2684
2685 let include_count = makefile
2687 .syntax()
2688 .children()
2689 .filter(|child| child.kind() == INCLUDE)
2690 .count();
2691 assert_eq!(include_count, 4);
2692
2693 assert!(makefile
2695 .included_files()
2696 .any(|path| path.contains("$(VAR)")));
2697 }
2698
2699 #[test]
2700 fn test_include_api() {
2701 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
2703 let makefile: Makefile = makefile_str.parse().unwrap();
2704
2705 let includes: Vec<_> = makefile.includes().collect();
2707 assert_eq!(includes.len(), 3);
2708
2709 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
2716 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
2717
2718 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
2720 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
2721 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
2722 }
2723
2724 #[test]
2725 fn test_include_integration() {
2726 let phony_makefile = Makefile::from_reader(
2730 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
2731 .as_bytes()
2732 ).unwrap();
2733
2734 assert_eq!(phony_makefile.rules().count(), 2);
2736
2737 let normal_rules_count = phony_makefile
2739 .rules()
2740 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
2741 .count();
2742 assert_eq!(normal_rules_count, 1);
2743
2744 assert_eq!(phony_makefile.includes().count(), 1);
2746 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
2747
2748 let simple_makefile = Makefile::from_reader(
2750 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
2751 .as_bytes(),
2752 )
2753 .unwrap();
2754 assert_eq!(simple_makefile.rules().count(), 1);
2755 assert_eq!(simple_makefile.includes().count(), 1);
2756 }
2757
2758 #[test]
2759 fn test_real_conditional_directives() {
2760 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
2762 let mut buf = conditional.as_bytes();
2763 let makefile =
2764 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
2765 let code = makefile.code();
2766 assert!(code.contains("ifdef DEBUG"));
2767 assert!(code.contains("else"));
2768 assert!(code.contains("endif"));
2769
2770 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
2772 let mut buf = nested.as_bytes();
2773 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
2774 let code = makefile.code();
2775 assert!(code.contains("ifdef DEBUG"));
2776 assert!(code.contains("ifdef VERBOSE"));
2777
2778 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
2780 let mut buf = ifeq.as_bytes();
2781 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
2782 let code = makefile.code();
2783 assert!(code.contains("ifeq"));
2784 assert!(code.contains("Windows_NT"));
2785 }
2786
2787 #[test]
2788 fn test_indented_text_outside_rules() {
2789 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
2791 let parsed = parse(help_text, None);
2792 assert!(parsed.errors.is_empty());
2793
2794 let root = parsed.root();
2796 let rules = root.rules().collect::<Vec<_>>();
2797 assert_eq!(rules.len(), 1);
2798
2799 let help_rule = &rules[0];
2800 let recipes = help_rule.recipes().collect::<Vec<_>>();
2801 assert_eq!(recipes.len(), 2);
2802 assert!(recipes[0].contains("Available targets"));
2803 assert!(recipes[1].contains("help"));
2804 }
2805
2806 #[test]
2807 fn test_comment_handling_in_recipes() {
2808 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
2810
2811 let parsed = parse(recipe_comment, None);
2813
2814 assert!(
2816 parsed.errors.is_empty(),
2817 "Should parse recipe with comments without errors"
2818 );
2819
2820 let root = parsed.root();
2822 let rules = root.rules().collect::<Vec<_>>();
2823 assert_eq!(rules.len(), 1, "Should find exactly one rule");
2824
2825 let build_rule = &rules[0];
2827 assert_eq!(
2828 build_rule.targets().collect::<Vec<_>>(),
2829 vec!["build"],
2830 "Rule should have 'build' as target"
2831 );
2832
2833 let recipes = build_rule.recipe_nodes().collect::<Vec<_>>();
2836 assert_eq!(recipes.len(), 2, "Should find two recipe nodes");
2837
2838 assert_eq!(recipes[0].text(), "");
2840 assert_eq!(
2841 recipes[0].comment(),
2842 Some("# This is a comment".to_string())
2843 );
2844
2845 assert_eq!(recipes[1].text(), "gcc -o app main.c");
2847 assert_eq!(recipes[1].comment(), None);
2848 }
2849
2850 #[test]
2851 fn test_multiline_variables() {
2852 let multiline = "SOURCES = main.c \\\n util.c\n";
2854
2855 let parsed = parse(multiline, None);
2857
2858 let root = parsed.root();
2860 let vars = root.variable_definitions().collect::<Vec<_>>();
2861 assert!(!vars.is_empty(), "Should find at least one variable");
2862
2863 let operators = "CFLAGS := -Wall \\\n -Werror\n";
2867 let parsed_operators = parse(operators, None);
2868
2869 let root = parsed_operators.root();
2871 let vars = root.variable_definitions().collect::<Vec<_>>();
2872 assert!(
2873 !vars.is_empty(),
2874 "Should find at least one variable with := operator"
2875 );
2876
2877 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
2879 let parsed_append = parse(append, None);
2880
2881 let root = parsed_append.root();
2883 let vars = root.variable_definitions().collect::<Vec<_>>();
2884 assert!(
2885 !vars.is_empty(),
2886 "Should find at least one variable with += operator"
2887 );
2888 }
2889
2890 #[test]
2891 fn test_whitespace_and_eof_handling() {
2892 let blank_lines = "VAR = value\n\n\n";
2894
2895 let parsed_blank = parse(blank_lines, None);
2896
2897 let root = parsed_blank.root();
2899 let vars = root.variable_definitions().collect::<Vec<_>>();
2900 assert_eq!(
2901 vars.len(),
2902 1,
2903 "Should find one variable in blank lines test"
2904 );
2905
2906 let trailing_space = "VAR = value \n";
2908
2909 let parsed_space = parse(trailing_space, None);
2910
2911 let root = parsed_space.root();
2913 let vars = root.variable_definitions().collect::<Vec<_>>();
2914 assert_eq!(
2915 vars.len(),
2916 1,
2917 "Should find one variable in trailing space test"
2918 );
2919
2920 let no_newline = "VAR = value";
2922
2923 let parsed_no_newline = parse(no_newline, None);
2924
2925 let root = parsed_no_newline.root();
2927 let vars = root.variable_definitions().collect::<Vec<_>>();
2928 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
2929 assert_eq!(
2930 vars[0].name(),
2931 Some("VAR".to_string()),
2932 "Variable name should be VAR"
2933 );
2934 }
2935
2936 #[test]
2937 fn test_complex_variable_references() {
2938 let wildcard = "SOURCES = $(wildcard *.c)\n";
2940 let parsed = parse(wildcard, None);
2941 assert!(parsed.errors.is_empty());
2942
2943 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
2945 let parsed = parse(nested, None);
2946 assert!(parsed.errors.is_empty());
2947
2948 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
2950 let parsed = parse(patsubst, None);
2951 assert!(parsed.errors.is_empty());
2952 }
2953
2954 #[test]
2955 fn test_complex_variable_references_minimal() {
2956 let wildcard = "SOURCES = $(wildcard *.c)\n";
2958 let parsed = parse(wildcard, None);
2959 assert!(parsed.errors.is_empty());
2960
2961 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
2963 let parsed = parse(nested, None);
2964 assert!(parsed.errors.is_empty());
2965
2966 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
2968 let parsed = parse(patsubst, None);
2969 assert!(parsed.errors.is_empty());
2970 }
2971
2972 #[test]
2973 fn test_multiline_variable_with_backslash() {
2974 let content = r#"
2975LONG_VAR = This is a long variable \
2976 that continues on the next line \
2977 and even one more line
2978"#;
2979
2980 let mut buf = content.as_bytes();
2982 let makefile =
2983 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
2984
2985 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2987 assert_eq!(
2988 vars.len(),
2989 1,
2990 "Expected 1 variable but found {}",
2991 vars.len()
2992 );
2993 let var_value = vars[0].raw_value();
2994 assert!(var_value.is_some(), "Variable value is None");
2995
2996 let value_str = var_value.unwrap();
2998 assert!(
2999 value_str.contains("long variable"),
3000 "Value doesn't contain expected content"
3001 );
3002 }
3003
3004 #[test]
3005 fn test_multiline_variable_with_mixed_operators() {
3006 let content = r#"
3007PREFIX ?= /usr/local
3008CFLAGS := -Wall -O2 \
3009 -I$(PREFIX)/include \
3010 -DDEBUG
3011"#;
3012 let mut buf = content.as_bytes();
3014 let makefile = Makefile::read_relaxed(&mut buf)
3015 .expect("Failed to parse multiline variable with operators");
3016
3017 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3019 assert!(
3020 !vars.is_empty(),
3021 "Expected at least 1 variable, found {}",
3022 vars.len()
3023 );
3024
3025 let prefix_var = vars
3027 .iter()
3028 .find(|v| v.name().unwrap_or_default() == "PREFIX");
3029 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
3030 assert!(
3031 prefix_var.unwrap().raw_value().is_some(),
3032 "PREFIX variable has no value"
3033 );
3034
3035 let cflags_var = vars
3037 .iter()
3038 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
3039 assert!(
3040 cflags_var.is_some(),
3041 "Expected to find CFLAGS variable (or part of it)"
3042 );
3043 }
3044
3045 #[test]
3046 fn test_indented_help_text() {
3047 let content = r#"
3048.PHONY: help
3049help:
3050 @echo "Available targets:"
3051 @echo " build - Build the project"
3052 @echo " test - Run tests"
3053 @echo " clean - Remove build artifacts"
3054"#;
3055 let mut buf = content.as_bytes();
3057 let makefile =
3058 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
3059
3060 let rules = makefile.rules().collect::<Vec<_>>();
3062 assert!(!rules.is_empty(), "Expected at least one rule");
3063
3064 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
3066 assert!(help_rule.is_some(), "Expected to find help rule");
3067
3068 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
3070 assert!(
3071 !recipes.is_empty(),
3072 "Expected at least one recipe line in help rule"
3073 );
3074 assert!(
3075 recipes.iter().any(|r| r.contains("Available targets")),
3076 "Expected to find 'Available targets' in recipes"
3077 );
3078 }
3079
3080 #[test]
3081 fn test_indented_lines_in_conditionals() {
3082 let content = r#"
3083ifdef DEBUG
3084 CFLAGS += -g -DDEBUG
3085 # This is a comment inside conditional
3086 ifdef VERBOSE
3087 CFLAGS += -v
3088 endif
3089endif
3090"#;
3091 let mut buf = content.as_bytes();
3093 let makefile = Makefile::read_relaxed(&mut buf)
3094 .expect("Failed to parse indented lines in conditionals");
3095
3096 let code = makefile.code();
3098 assert!(code.contains("ifdef DEBUG"));
3099 assert!(code.contains("ifdef VERBOSE"));
3100 assert!(code.contains("endif"));
3101 }
3102
3103 #[test]
3104 fn test_recipe_with_colon() {
3105 let content = r#"
3106build:
3107 @echo "Building at: $(shell date)"
3108 gcc -o program main.c
3109"#;
3110 let parsed = parse(content, None);
3111 assert!(
3112 parsed.errors.is_empty(),
3113 "Failed to parse recipe with colon: {:?}",
3114 parsed.errors
3115 );
3116 }
3117
3118 #[test]
3119 #[ignore]
3120 fn test_double_colon_rules() {
3121 let content = r#"
3124%.o :: %.c
3125 $(CC) -c $< -o $@
3126
3127# Double colon allows multiple rules for same target
3128all:: prerequisite1
3129 @echo "First rule for all"
3130
3131all:: prerequisite2
3132 @echo "Second rule for all"
3133"#;
3134 let mut buf = content.as_bytes();
3135 let makefile =
3136 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
3137
3138 let rules = makefile.rules().collect::<Vec<_>>();
3140 assert!(!rules.is_empty(), "Expected at least one rule");
3141
3142 let all_rules = rules
3144 .iter()
3145 .filter(|r| r.targets().any(|t| t.contains("all")));
3146 assert!(
3147 all_rules.count() > 0,
3148 "Expected to find at least one rule containing 'all'"
3149 );
3150 }
3151
3152 #[test]
3153 fn test_else_conditional_directives() {
3154 let content = r#"
3156ifeq ($(OS),Windows_NT)
3157 TARGET = windows
3158else ifeq ($(OS),Darwin)
3159 TARGET = macos
3160else ifeq ($(OS),Linux)
3161 TARGET = linux
3162else
3163 TARGET = unknown
3164endif
3165"#;
3166 let mut buf = content.as_bytes();
3167 let makefile =
3168 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifeq directive");
3169 assert!(makefile.code().contains("else ifeq"));
3170 assert!(makefile.code().contains("TARGET"));
3171
3172 let content = r#"
3174ifdef WINDOWS
3175 TARGET = windows
3176else ifdef DARWIN
3177 TARGET = macos
3178else ifdef LINUX
3179 TARGET = linux
3180else
3181 TARGET = unknown
3182endif
3183"#;
3184 let mut buf = content.as_bytes();
3185 let makefile =
3186 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifdef directive");
3187 assert!(makefile.code().contains("else ifdef"));
3188
3189 let content = r#"
3191ifndef NOWINDOWS
3192 TARGET = windows
3193else ifndef NODARWIN
3194 TARGET = macos
3195else
3196 TARGET = linux
3197endif
3198"#;
3199 let mut buf = content.as_bytes();
3200 let makefile =
3201 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifndef directive");
3202 assert!(makefile.code().contains("else ifndef"));
3203
3204 let content = r#"
3206ifneq ($(OS),Windows_NT)
3207 TARGET = not_windows
3208else ifneq ($(OS),Darwin)
3209 TARGET = not_macos
3210else
3211 TARGET = darwin
3212endif
3213"#;
3214 let mut buf = content.as_bytes();
3215 let makefile =
3216 Makefile::read_relaxed(&mut buf).expect("Failed to parse else ifneq directive");
3217 assert!(makefile.code().contains("else ifneq"));
3218 }
3219
3220 #[test]
3221 fn test_complex_else_conditionals() {
3222 let content = r#"VAR1 := foo
3224VAR2 := bar
3225
3226ifeq ($(VAR1),foo)
3227 RESULT := foo_matched
3228else ifdef VAR2
3229 RESULT := var2_defined
3230else ifndef VAR3
3231 RESULT := var3_not_defined
3232else
3233 RESULT := final_else
3234endif
3235
3236all:
3237 @echo $(RESULT)
3238"#;
3239 let mut buf = content.as_bytes();
3240 let makefile =
3241 Makefile::read_relaxed(&mut buf).expect("Failed to parse complex else conditionals");
3242
3243 let code = makefile.code();
3245 assert!(code.contains("ifeq ($(VAR1),foo)"));
3246 assert!(code.contains("else ifdef VAR2"));
3247 assert!(code.contains("else ifndef VAR3"));
3248 assert!(code.contains("else"));
3249 assert!(code.contains("endif"));
3250 assert!(code.contains("RESULT"));
3251
3252 let rules: Vec<_> = makefile.rules().collect();
3254 assert_eq!(rules.len(), 1);
3255 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["all"]);
3256 }
3257
3258 #[test]
3259 fn test_conditional_token_structure() {
3260 let content = r#"ifdef VAR1
3262X := 1
3263else ifdef VAR2
3264X := 2
3265else
3266X := 3
3267endif
3268"#;
3269 let mut buf = content.as_bytes();
3270 let makefile = Makefile::read_relaxed(&mut buf).unwrap();
3271
3272 let syntax = makefile.syntax();
3274
3275 let mut found_conditional = false;
3277 let mut found_conditional_if = false;
3278 let mut found_conditional_else = false;
3279 let mut found_conditional_endif = false;
3280
3281 fn check_node(
3282 node: &SyntaxNode,
3283 found_cond: &mut bool,
3284 found_if: &mut bool,
3285 found_else: &mut bool,
3286 found_endif: &mut bool,
3287 ) {
3288 match node.kind() {
3289 SyntaxKind::CONDITIONAL => *found_cond = true,
3290 SyntaxKind::CONDITIONAL_IF => *found_if = true,
3291 SyntaxKind::CONDITIONAL_ELSE => *found_else = true,
3292 SyntaxKind::CONDITIONAL_ENDIF => *found_endif = true,
3293 _ => {}
3294 }
3295
3296 for child in node.children() {
3297 check_node(&child, found_cond, found_if, found_else, found_endif);
3298 }
3299 }
3300
3301 check_node(
3302 syntax,
3303 &mut found_conditional,
3304 &mut found_conditional_if,
3305 &mut found_conditional_else,
3306 &mut found_conditional_endif,
3307 );
3308
3309 assert!(found_conditional, "Should have CONDITIONAL node");
3310 assert!(found_conditional_if, "Should have CONDITIONAL_IF node");
3311 assert!(found_conditional_else, "Should have CONDITIONAL_ELSE node");
3312 assert!(
3313 found_conditional_endif,
3314 "Should have CONDITIONAL_ENDIF node"
3315 );
3316 }
3317
3318 #[test]
3319 fn test_ambiguous_assignment_vs_rule() {
3320 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
3322
3323 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
3324 let makefile =
3325 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
3326
3327 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3328 let rules = makefile.rules().collect::<Vec<_>>();
3329
3330 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
3331 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
3332
3333 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
3334
3335 const SIMPLE_RULE: &str = "target: dependency\n";
3337
3338 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
3339 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
3340
3341 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3342 let rules = makefile.rules().collect::<Vec<_>>();
3343
3344 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
3345 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
3346
3347 let rule = &rules[0];
3348 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
3349 }
3350
3351 #[test]
3352 fn test_nested_conditionals() {
3353 let content = r#"
3354ifdef RELEASE
3355 CFLAGS += -O3
3356 ifndef DEBUG
3357 ifneq ($(ARCH),arm)
3358 CFLAGS += -march=native
3359 else
3360 CFLAGS += -mcpu=cortex-a72
3361 endif
3362 endif
3363endif
3364"#;
3365 let mut buf = content.as_bytes();
3367 let makefile =
3368 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
3369
3370 let code = makefile.code();
3372 assert!(code.contains("ifdef RELEASE"));
3373 assert!(code.contains("ifndef DEBUG"));
3374 assert!(code.contains("ifneq"));
3375 }
3376
3377 #[test]
3378 fn test_space_indented_recipes() {
3379 let content = r#"
3382build:
3383 @echo "Building with spaces instead of tabs"
3384 gcc -o program main.c
3385"#;
3386 let mut buf = content.as_bytes();
3388 let makefile =
3389 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
3390
3391 let rules = makefile.rules().collect::<Vec<_>>();
3393 assert!(!rules.is_empty(), "Expected at least one rule");
3394
3395 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
3397 assert!(build_rule.is_some(), "Expected to find build rule");
3398 }
3399
3400 #[test]
3401 fn test_complex_variable_functions() {
3402 let content = r#"
3403FILES := $(shell find . -name "*.c")
3404OBJS := $(patsubst %.c,%.o,$(FILES))
3405NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
3406HEADERS := ${wildcard *.h}
3407"#;
3408 let parsed = parse(content, None);
3409 assert!(
3410 parsed.errors.is_empty(),
3411 "Failed to parse complex variable functions: {:?}",
3412 parsed.errors
3413 );
3414 }
3415
3416 #[test]
3417 fn test_nested_variable_expansions() {
3418 let content = r#"
3419VERSION = 1.0
3420PACKAGE = myapp
3421TARBALL = $(PACKAGE)-$(VERSION).tar.gz
3422INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
3423"#;
3424 let parsed = parse(content, None);
3425 assert!(
3426 parsed.errors.is_empty(),
3427 "Failed to parse nested variable expansions: {:?}",
3428 parsed.errors
3429 );
3430 }
3431
3432 #[test]
3433 fn test_special_directives() {
3434 let content = r#"
3435# Special makefile directives
3436.PHONY: all clean
3437.SUFFIXES: .c .o
3438.DEFAULT: all
3439
3440# Variable definition and export directive
3441export PATH := /usr/bin:/bin
3442"#;
3443 let mut buf = content.as_bytes();
3445 let makefile =
3446 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
3447
3448 let rules = makefile.rules().collect::<Vec<_>>();
3450
3451 let phony_rule = rules
3453 .iter()
3454 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
3455 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
3456
3457 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3459 assert!(!vars.is_empty(), "Expected to find at least one variable");
3460 }
3461
3462 #[test]
3465 fn test_comprehensive_real_world_makefile() {
3466 let content = r#"
3468# Basic variable assignment
3469VERSION = 1.0.0
3470
3471# Phony target
3472.PHONY: all clean
3473
3474# Simple rule
3475all:
3476 echo "Building version $(VERSION)"
3477
3478# Another rule with dependencies
3479clean:
3480 rm -f *.o
3481"#;
3482
3483 let parsed = parse(content, None);
3485
3486 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
3488
3489 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
3491 assert!(!variables.is_empty(), "Expected at least one variable");
3492 assert_eq!(
3493 variables[0].name(),
3494 Some("VERSION".to_string()),
3495 "Expected VERSION variable"
3496 );
3497
3498 let rules = parsed.root().rules().collect::<Vec<_>>();
3500 assert!(!rules.is_empty(), "Expected at least one rule");
3501
3502 let rule_targets: Vec<String> = rules
3504 .iter()
3505 .flat_map(|r| r.targets().collect::<Vec<_>>())
3506 .collect();
3507 assert!(
3508 rule_targets.contains(&".PHONY".to_string()),
3509 "Expected .PHONY rule"
3510 );
3511 assert!(
3512 rule_targets.contains(&"all".to_string()),
3513 "Expected 'all' rule"
3514 );
3515 assert!(
3516 rule_targets.contains(&"clean".to_string()),
3517 "Expected 'clean' rule"
3518 );
3519 }
3520
3521 #[test]
3522 fn test_indented_help_text_outside_rules() {
3523 let content = r#"
3525# Targets with help text
3526help:
3527 @echo "Available targets:"
3528 @echo " build build the project"
3529 @echo " test run tests"
3530 @echo " clean clean build artifacts"
3531
3532# Another target
3533clean:
3534 rm -rf build/
3535"#;
3536
3537 let parsed = parse(content, None);
3539
3540 assert!(
3542 parsed.errors.is_empty(),
3543 "Failed to parse indented help text"
3544 );
3545
3546 let rules = parsed.root().rules().collect::<Vec<_>>();
3548 assert_eq!(rules.len(), 2, "Expected to find two rules");
3549
3550 let help_rule = rules
3552 .iter()
3553 .find(|r| r.targets().any(|t| t == "help"))
3554 .expect("Expected to find help rule");
3555
3556 let clean_rule = rules
3557 .iter()
3558 .find(|r| r.targets().any(|t| t == "clean"))
3559 .expect("Expected to find clean rule");
3560
3561 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
3563 assert!(
3564 !help_recipes.is_empty(),
3565 "Help rule should have recipe lines"
3566 );
3567 assert!(
3568 help_recipes
3569 .iter()
3570 .any(|line| line.contains("Available targets")),
3571 "Help recipes should include 'Available targets' line"
3572 );
3573
3574 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
3576 assert!(
3577 !clean_recipes.is_empty(),
3578 "Clean rule should have recipe lines"
3579 );
3580 assert!(
3581 clean_recipes.iter().any(|line| line.contains("rm -rf")),
3582 "Clean recipes should include 'rm -rf' command"
3583 );
3584 }
3585
3586 #[test]
3587 fn test_makefile1_phony_pattern() {
3588 let content = "#line 2145\n.PHONY: $(PHONY)\n";
3590
3591 let result = parse(content, None);
3593
3594 assert!(
3596 result.errors.is_empty(),
3597 "Failed to parse .PHONY: $(PHONY) pattern"
3598 );
3599
3600 let rules = result.root().rules().collect::<Vec<_>>();
3602 assert_eq!(rules.len(), 1, "Expected 1 rule");
3603 assert_eq!(
3604 rules[0].targets().next().unwrap(),
3605 ".PHONY",
3606 "Expected .PHONY rule"
3607 );
3608
3609 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
3611 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
3612 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
3613 }
3614
3615 #[test]
3616 fn test_skip_until_newline_behavior() {
3617 let input = "text without newline";
3619 let parsed = parse(input, None);
3620 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3622
3623 let input_with_newline = "text\nafter newline";
3624 let parsed2 = parse(input_with_newline, None);
3625 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
3626 }
3627
3628 #[test]
3629 #[ignore] fn test_error_with_indent_token() {
3631 let input = "\tinvalid indented line";
3633 let parsed = parse(input, None);
3634 assert!(!parsed.errors.is_empty());
3636
3637 let error_msg = &parsed.errors[0].message;
3638 assert!(error_msg.contains("recipe commences before first target"));
3639 }
3640
3641 #[test]
3642 fn test_conditional_token_handling() {
3643 let input = r#"
3645ifndef VAR
3646 CFLAGS = -DTEST
3647endif
3648"#;
3649 let parsed = parse(input, None);
3650 let makefile = parsed.root();
3652 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
3653 let nested = r#"
3657ifdef DEBUG
3658 ifndef RELEASE
3659 CFLAGS = -g
3660 endif
3661endif
3662"#;
3663 let parsed_nested = parse(nested, None);
3664 let _makefile = parsed_nested.root();
3666 }
3667
3668 #[test]
3669 fn test_include_vs_conditional_logic() {
3670 let input = r#"
3672include file.mk
3673ifdef VAR
3674 VALUE = 1
3675endif
3676"#;
3677 let parsed = parse(input, None);
3678 let makefile = parsed.root();
3680 let includes = makefile.includes().collect::<Vec<_>>();
3681 assert!(!includes.is_empty() || !parsed.errors.is_empty());
3683
3684 let optional_include = r#"
3686-include optional.mk
3687ifndef VAR
3688 VALUE = default
3689endif
3690"#;
3691 let parsed2 = parse(optional_include, None);
3692 let _makefile = parsed2.root();
3694 }
3695
3696 #[test]
3697 fn test_balanced_parens_counting() {
3698 let input = r#"
3700VAR = $(call func,$(nested,arg),extra)
3701COMPLEX = $(if $(condition),$(then_val),$(else_val))
3702"#;
3703 let parsed = parse(input, None);
3704 assert!(parsed.errors.is_empty());
3705
3706 let makefile = parsed.root();
3707 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3708 assert_eq!(vars.len(), 2);
3709 }
3710
3711 #[test]
3712 fn test_documentation_lookahead() {
3713 let input = r#"
3715# Documentation comment
3716help:
3717 @echo "Usage instructions"
3718 @echo "More help text"
3719"#;
3720 let parsed = parse(input, None);
3721 assert!(parsed.errors.is_empty());
3722
3723 let makefile = parsed.root();
3724 let rules = makefile.rules().collect::<Vec<_>>();
3725 assert_eq!(rules.len(), 1);
3726 assert_eq!(rules[0].targets().next().unwrap(), "help");
3727 }
3728
3729 #[test]
3730 fn test_edge_case_empty_input() {
3731 let parsed = parse("", None);
3733 assert!(parsed.errors.is_empty());
3734
3735 let parsed2 = parse(" \n \n", None);
3737 let _makefile = parsed2.root();
3740 }
3741
3742 #[test]
3743 fn test_malformed_conditional_recovery() {
3744 let input = r#"
3746ifdef
3747 # Missing condition variable
3748endif
3749"#;
3750 let parsed = parse(input, None);
3751 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3754 }
3755
3756 #[test]
3757 fn test_replace_rule() {
3758 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
3759 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3760
3761 makefile.replace_rule(0, new_rule).unwrap();
3762
3763 let targets: Vec<_> = makefile
3764 .rules()
3765 .flat_map(|r| r.targets().collect::<Vec<_>>())
3766 .collect();
3767 assert_eq!(targets, vec!["new_rule", "rule2"]);
3768
3769 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
3770 assert_eq!(recipes, vec!["new_command"]);
3771 }
3772
3773 #[test]
3774 fn test_replace_rule_out_of_bounds() {
3775 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3776 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3777
3778 let result = makefile.replace_rule(5, new_rule);
3779 assert!(result.is_err());
3780 }
3781
3782 #[test]
3783 fn test_remove_rule() {
3784 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
3785 .parse()
3786 .unwrap();
3787
3788 let removed = makefile.remove_rule(1).unwrap();
3789 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
3790
3791 let remaining_targets: Vec<_> = makefile
3792 .rules()
3793 .flat_map(|r| r.targets().collect::<Vec<_>>())
3794 .collect();
3795 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
3796 assert_eq!(makefile.rules().count(), 2);
3797 }
3798
3799 #[test]
3800 fn test_remove_rule_out_of_bounds() {
3801 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3802
3803 let result = makefile.remove_rule(5);
3804 assert!(result.is_err());
3805 }
3806
3807 #[test]
3808 fn test_insert_rule() {
3809 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
3810 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
3811
3812 makefile.insert_rule(1, new_rule).unwrap();
3813
3814 let targets: Vec<_> = makefile
3815 .rules()
3816 .flat_map(|r| r.targets().collect::<Vec<_>>())
3817 .collect();
3818 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
3819 assert_eq!(makefile.rules().count(), 3);
3820 }
3821
3822 #[test]
3823 fn test_insert_rule_at_end() {
3824 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3825 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
3826
3827 makefile.insert_rule(1, new_rule).unwrap();
3828
3829 let targets: Vec<_> = makefile
3830 .rules()
3831 .flat_map(|r| r.targets().collect::<Vec<_>>())
3832 .collect();
3833 assert_eq!(targets, vec!["rule1", "end_rule"]);
3834 }
3835
3836 #[test]
3837 fn test_insert_rule_out_of_bounds() {
3838 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
3839 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3840
3841 let result = makefile.insert_rule(5, new_rule);
3842 assert!(result.is_err());
3843 }
3844
3845 #[test]
3846 fn test_insert_rule_preserves_blank_line_spacing_at_end() {
3847 let input = "rule1:\n\tcommand1\n\nrule2:\n\tcommand2\n";
3849 let mut makefile: Makefile = input.parse().unwrap();
3850 let new_rule = Rule::new(&["rule3"], &[], &["command3"]);
3851
3852 makefile.insert_rule(2, new_rule).unwrap();
3853
3854 let expected = "rule1:\n\tcommand1\n\nrule2:\n\tcommand2\n\nrule3:\n\tcommand3\n";
3855 assert_eq!(makefile.to_string(), expected);
3856 }
3857
3858 #[test]
3859 fn test_insert_rule_adds_blank_lines_when_missing() {
3860 let input = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n";
3862 let mut makefile: Makefile = input.parse().unwrap();
3863 let new_rule = Rule::new(&["rule3"], &[], &["command3"]);
3864
3865 makefile.insert_rule(2, new_rule).unwrap();
3866
3867 let expected = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n\nrule3:\n\tcommand3\n";
3868 assert_eq!(makefile.to_string(), expected);
3869 }
3870
3871 #[test]
3872 fn test_remove_command() {
3873 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
3874 .parse()
3875 .unwrap();
3876
3877 rule.remove_command(1);
3878 let recipes: Vec<_> = rule.recipes().collect();
3879 assert_eq!(recipes, vec!["command1", "command3"]);
3880 assert_eq!(rule.recipe_count(), 2);
3881 }
3882
3883 #[test]
3884 fn test_remove_command_out_of_bounds() {
3885 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
3886
3887 let result = rule.remove_command(5);
3888 assert!(!result);
3889 }
3890
3891 #[test]
3892 fn test_insert_command() {
3893 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
3894
3895 rule.insert_command(1, "command2");
3896 let recipes: Vec<_> = rule.recipes().collect();
3897 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
3898 }
3899
3900 #[test]
3901 fn test_insert_command_at_end() {
3902 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
3903
3904 rule.insert_command(1, "command2");
3905 let recipes: Vec<_> = rule.recipes().collect();
3906 assert_eq!(recipes, vec!["command1", "command2"]);
3907 }
3908
3909 #[test]
3910 fn test_insert_command_in_empty_rule() {
3911 let mut rule: Rule = "rule:\n".parse().unwrap();
3912
3913 rule.insert_command(0, "new_command");
3914 let recipes: Vec<_> = rule.recipes().collect();
3915 assert_eq!(recipes, vec!["new_command"]);
3916 }
3917
3918 #[test]
3919 fn test_recipe_count() {
3920 let rule1: Rule = "rule:\n".parse().unwrap();
3921 assert_eq!(rule1.recipe_count(), 0);
3922
3923 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
3924 assert_eq!(rule2.recipe_count(), 2);
3925 }
3926
3927 #[test]
3928 fn test_clear_commands() {
3929 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
3930 .parse()
3931 .unwrap();
3932
3933 rule.clear_commands();
3934 assert_eq!(rule.recipe_count(), 0);
3935
3936 let recipes: Vec<_> = rule.recipes().collect();
3937 assert_eq!(recipes, Vec::<String>::new());
3938
3939 let targets: Vec<_> = rule.targets().collect();
3941 assert_eq!(targets, vec!["rule"]);
3942 }
3943
3944 #[test]
3945 fn test_clear_commands_empty_rule() {
3946 let mut rule: Rule = "rule:\n".parse().unwrap();
3947
3948 rule.clear_commands();
3949 assert_eq!(rule.recipe_count(), 0);
3950
3951 let targets: Vec<_> = rule.targets().collect();
3952 assert_eq!(targets, vec!["rule"]);
3953 }
3954
3955 #[test]
3956 fn test_rule_manipulation_preserves_structure() {
3957 let input = r#"# Comment
3959VAR = value
3960
3961rule1:
3962 command1
3963
3964# Another comment
3965rule2:
3966 command2
3967
3968VAR2 = value2
3969"#;
3970
3971 let mut makefile: Makefile = input.parse().unwrap();
3972 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
3973
3974 makefile.insert_rule(1, new_rule).unwrap();
3976
3977 let targets: Vec<_> = makefile
3979 .rules()
3980 .flat_map(|r| r.targets().collect::<Vec<_>>())
3981 .collect();
3982 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
3983
3984 let vars: Vec<_> = makefile.variable_definitions().collect();
3986 assert_eq!(vars.len(), 2);
3987
3988 let output = makefile.code();
3990 assert!(output.contains("# Comment"));
3991 assert!(output.contains("VAR = value"));
3992 assert!(output.contains("# Another comment"));
3993 assert!(output.contains("VAR2 = value2"));
3994 }
3995
3996 #[test]
3997 fn test_replace_rule_with_multiple_targets() {
3998 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
3999 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
4000
4001 makefile.replace_rule(0, new_rule).unwrap();
4002
4003 let targets: Vec<_> = makefile
4004 .rules()
4005 .flat_map(|r| r.targets().collect::<Vec<_>>())
4006 .collect();
4007 assert_eq!(targets, vec!["new_target"]);
4008 }
4009
4010 #[test]
4011 fn test_empty_makefile_operations() {
4012 let mut makefile = Makefile::new();
4013
4014 assert!(makefile
4016 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
4017 .is_err());
4018 assert!(makefile.remove_rule(0).is_err());
4019
4020 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
4022 makefile.insert_rule(0, new_rule).unwrap();
4023 assert_eq!(makefile.rules().count(), 1);
4024 }
4025
4026 #[test]
4027 fn test_command_operations_preserve_indentation() {
4028 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
4029 .parse()
4030 .unwrap();
4031
4032 rule.insert_command(1, "middle_command");
4033 let recipes: Vec<_> = rule.recipes().collect();
4034 assert_eq!(
4035 recipes,
4036 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
4037 );
4038 }
4039
4040 #[test]
4041 fn test_rule_operations_with_variables_and_includes() {
4042 let input = r#"VAR1 = value1
4043include common.mk
4044
4045rule1:
4046 command1
4047
4048VAR2 = value2
4049include other.mk
4050
4051rule2:
4052 command2
4053"#;
4054
4055 let mut makefile: Makefile = input.parse().unwrap();
4056
4057 makefile.remove_rule(0).unwrap();
4059
4060 let output = makefile.code();
4062 assert!(output.contains("VAR1 = value1"));
4063 assert!(output.contains("include common.mk"));
4064 assert!(output.contains("VAR2 = value2"));
4065 assert!(output.contains("include other.mk"));
4066
4067 assert_eq!(makefile.rules().count(), 1);
4069 let remaining_targets: Vec<_> = makefile
4070 .rules()
4071 .flat_map(|r| r.targets().collect::<Vec<_>>())
4072 .collect();
4073 assert_eq!(remaining_targets, vec!["rule2"]);
4074 }
4075
4076 #[test]
4077 fn test_command_manipulation_edge_cases() {
4078 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
4080 assert_eq!(empty_rule.recipe_count(), 0);
4081
4082 empty_rule.insert_command(0, "first_command");
4083 assert_eq!(empty_rule.recipe_count(), 1);
4084
4085 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
4087 empty_rule2.clear_commands();
4088 assert_eq!(empty_rule2.recipe_count(), 0);
4089 }
4090
4091 #[test]
4092 fn test_large_makefile_performance() {
4093 let mut makefile = Makefile::new();
4095
4096 for i in 0..100 {
4098 let rule_name = format!("rule{}", i);
4099 makefile
4100 .add_rule(&rule_name)
4101 .push_command(&format!("command{}", i));
4102 }
4103
4104 assert_eq!(makefile.rules().count(), 100);
4105
4106 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
4108 makefile.replace_rule(50, new_rule).unwrap();
4109
4110 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
4112 assert_eq!(rule_50_targets, vec!["middle_rule"]);
4113
4114 assert_eq!(makefile.rules().count(), 100); }
4116
4117 #[test]
4118 fn test_complex_recipe_manipulation() {
4119 let mut complex_rule: Rule = r#"complex:
4120 @echo "Starting build"
4121 $(CC) $(CFLAGS) -o $@ $<
4122 @echo "Build complete"
4123 chmod +x $@
4124"#
4125 .parse()
4126 .unwrap();
4127
4128 assert_eq!(complex_rule.recipe_count(), 4);
4129
4130 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
4135 assert_eq!(final_recipes.len(), 2);
4136 assert!(final_recipes[0].contains("$(CC)"));
4137 assert!(final_recipes[1].contains("chmod"));
4138 }
4139
4140 #[test]
4141 fn test_variable_definition_remove() {
4142 let makefile: Makefile = r#"VAR1 = value1
4143VAR2 = value2
4144VAR3 = value3
4145"#
4146 .parse()
4147 .unwrap();
4148
4149 assert_eq!(makefile.variable_definitions().count(), 3);
4151
4152 let mut var2 = makefile
4154 .variable_definitions()
4155 .nth(1)
4156 .expect("Should have second variable");
4157 assert_eq!(var2.name(), Some("VAR2".to_string()));
4158 var2.remove();
4159
4160 assert_eq!(makefile.variable_definitions().count(), 2);
4162 let var_names: Vec<_> = makefile
4163 .variable_definitions()
4164 .filter_map(|v| v.name())
4165 .collect();
4166 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
4167 }
4168
4169 #[test]
4170 fn test_variable_definition_set_value() {
4171 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
4172
4173 let mut var = makefile
4174 .variable_definitions()
4175 .next()
4176 .expect("Should have variable");
4177 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4178
4179 var.set_value("new_value");
4181
4182 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4184 assert!(makefile.code().contains("VAR = new_value"));
4185 }
4186
4187 #[test]
4188 fn test_variable_definition_set_value_preserves_format() {
4189 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
4190
4191 let mut var = makefile
4192 .variable_definitions()
4193 .next()
4194 .expect("Should have variable");
4195 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4196
4197 var.set_value("new_value");
4199
4200 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4202 let code = makefile.code();
4203 assert!(code.contains("export"), "Should preserve export prefix");
4204 assert!(code.contains(":="), "Should preserve := operator");
4205 assert!(code.contains("new_value"), "Should have new value");
4206 }
4207
4208 #[test]
4209 fn test_makefile_find_variable() {
4210 let makefile: Makefile = r#"VAR1 = value1
4211VAR2 = value2
4212VAR3 = value3
4213"#
4214 .parse()
4215 .unwrap();
4216
4217 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4219 assert_eq!(vars.len(), 1);
4220 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4221 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4222
4223 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
4225 }
4226
4227 #[test]
4228 fn test_makefile_find_variable_with_export() {
4229 let makefile: Makefile = r#"VAR1 = value1
4230export VAR2 := value2
4231VAR3 = value3
4232"#
4233 .parse()
4234 .unwrap();
4235
4236 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4238 assert_eq!(vars.len(), 1);
4239 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4240 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4241 }
4242
4243 #[test]
4244 fn test_variable_definition_is_export() {
4245 let makefile: Makefile = r#"VAR1 = value1
4246export VAR2 := value2
4247export VAR3 = value3
4248VAR4 := value4
4249"#
4250 .parse()
4251 .unwrap();
4252
4253 let vars: Vec<_> = makefile.variable_definitions().collect();
4254 assert_eq!(vars.len(), 4);
4255
4256 assert!(!vars[0].is_export());
4257 assert!(vars[1].is_export());
4258 assert!(vars[2].is_export());
4259 assert!(!vars[3].is_export());
4260 }
4261
4262 #[test]
4263 fn test_makefile_find_variable_multiple() {
4264 let makefile: Makefile = r#"VAR1 = value1
4265VAR1 = value2
4266VAR2 = other
4267VAR1 = value3
4268"#
4269 .parse()
4270 .unwrap();
4271
4272 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
4274 assert_eq!(vars.len(), 3);
4275 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
4276 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
4277 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
4278
4279 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
4281 assert_eq!(var2s.len(), 1);
4282 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
4283 }
4284
4285 #[test]
4286 fn test_variable_remove_and_find() {
4287 let makefile: Makefile = r#"VAR1 = value1
4288VAR2 = value2
4289VAR3 = value3
4290"#
4291 .parse()
4292 .unwrap();
4293
4294 let mut var2 = makefile
4296 .find_variable("VAR2")
4297 .next()
4298 .expect("Should find VAR2");
4299 var2.remove();
4300
4301 assert_eq!(makefile.find_variable("VAR2").count(), 0);
4303
4304 assert_eq!(makefile.find_variable("VAR1").count(), 1);
4306 assert_eq!(makefile.find_variable("VAR3").count(), 1);
4307 }
4308
4309 #[test]
4310 fn test_variable_remove_with_comment() {
4311 let makefile: Makefile = r#"VAR1 = value1
4312# This is a comment about VAR2
4313VAR2 = value2
4314VAR3 = value3
4315"#
4316 .parse()
4317 .unwrap();
4318
4319 let mut var2 = makefile
4321 .variable_definitions()
4322 .nth(1)
4323 .expect("Should have second variable");
4324 assert_eq!(var2.name(), Some("VAR2".to_string()));
4325 var2.remove();
4326
4327 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4329 }
4330
4331 #[test]
4332 fn test_variable_remove_with_multiple_comments() {
4333 let makefile: Makefile = r#"VAR1 = value1
4334# Comment line 1
4335# Comment line 2
4336# Comment line 3
4337VAR2 = value2
4338VAR3 = value3
4339"#
4340 .parse()
4341 .unwrap();
4342
4343 let mut var2 = makefile
4345 .variable_definitions()
4346 .nth(1)
4347 .expect("Should have second variable");
4348 var2.remove();
4349
4350 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4352 }
4353
4354 #[test]
4355 fn test_variable_remove_with_empty_line() {
4356 let makefile: Makefile = r#"VAR1 = value1
4357
4358# Comment about VAR2
4359VAR2 = value2
4360VAR3 = value3
4361"#
4362 .parse()
4363 .unwrap();
4364
4365 let mut var2 = makefile
4367 .variable_definitions()
4368 .nth(1)
4369 .expect("Should have second variable");
4370 var2.remove();
4371
4372 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4375 }
4376
4377 #[test]
4378 fn test_variable_remove_with_multiple_empty_lines() {
4379 let makefile: Makefile = r#"VAR1 = value1
4380
4381
4382# Comment about VAR2
4383VAR2 = value2
4384VAR3 = value3
4385"#
4386 .parse()
4387 .unwrap();
4388
4389 let mut var2 = makefile
4391 .variable_definitions()
4392 .nth(1)
4393 .expect("Should have second variable");
4394 var2.remove();
4395
4396 assert_eq!(makefile.code(), "VAR1 = value1\n\nVAR3 = value3\n");
4399 }
4400
4401 #[test]
4402 fn test_rule_remove_with_comment() {
4403 let makefile: Makefile = r#"rule1:
4404 command1
4405
4406# Comment about rule2
4407rule2:
4408 command2
4409rule3:
4410 command3
4411"#
4412 .parse()
4413 .unwrap();
4414
4415 let rule2 = makefile.rules().nth(1).expect("Should have second rule");
4417 rule2.remove().unwrap();
4418
4419 assert_eq!(
4422 makefile.code(),
4423 "rule1:\n\tcommand1\n\nrule3:\n\tcommand3\n"
4424 );
4425 }
4426
4427 #[test]
4428 fn test_variable_remove_preserves_shebang() {
4429 let makefile: Makefile = r#"#!/usr/bin/make -f
4430# This is a regular comment
4431VAR1 = value1
4432VAR2 = value2
4433"#
4434 .parse()
4435 .unwrap();
4436
4437 let mut var1 = makefile.variable_definitions().next().unwrap();
4439 var1.remove();
4440
4441 let code = makefile.code();
4443 assert!(code.starts_with("#!/usr/bin/make -f"));
4444 assert!(!code.contains("regular comment"));
4445 assert!(!code.contains("VAR1"));
4446 assert!(code.contains("VAR2"));
4447 }
4448
4449 #[test]
4450 fn test_variable_remove_preserves_subsequent_comments() {
4451 let makefile: Makefile = r#"VAR1 = value1
4452# Comment about VAR2
4453VAR2 = value2
4454
4455# Comment about VAR3
4456VAR3 = value3
4457"#
4458 .parse()
4459 .unwrap();
4460
4461 let mut var2 = makefile
4463 .variable_definitions()
4464 .nth(1)
4465 .expect("Should have second variable");
4466 var2.remove();
4467
4468 let code = makefile.code();
4470 assert_eq!(
4471 code,
4472 "VAR1 = value1\n\n# Comment about VAR3\nVAR3 = value3\n"
4473 );
4474 }
4475
4476 #[test]
4477 fn test_variable_remove_after_shebang_preserves_empty_line() {
4478 let makefile: Makefile = r#"#!/usr/bin/make -f
4479export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
4480
4481%:
4482 dh $@
4483"#
4484 .parse()
4485 .unwrap();
4486
4487 let mut var = makefile.variable_definitions().next().unwrap();
4489 var.remove();
4490
4491 assert_eq!(makefile.code(), "#!/usr/bin/make -f\n\n%:\n\tdh $@\n");
4493 }
4494
4495 #[test]
4496 fn test_rule_add_prerequisite() {
4497 let mut rule: Rule = "target: dep1\n".parse().unwrap();
4498 rule.add_prerequisite("dep2").unwrap();
4499 assert_eq!(
4500 rule.prerequisites().collect::<Vec<_>>(),
4501 vec!["dep1", "dep2"]
4502 );
4503 assert_eq!(rule.to_string(), "target: dep1 dep2\n");
4505 }
4506
4507 #[test]
4508 fn test_rule_add_prerequisite_to_rule_without_prereqs() {
4509 let mut rule: Rule = "target:\n".parse().unwrap();
4511 rule.add_prerequisite("dep1").unwrap();
4512 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dep1"]);
4513 assert_eq!(rule.to_string(), "target: dep1\n");
4515 }
4516
4517 #[test]
4518 fn test_rule_remove_prerequisite() {
4519 let mut rule: Rule = "target: dep1 dep2 dep3\n".parse().unwrap();
4520 assert!(rule.remove_prerequisite("dep2").unwrap());
4521 assert_eq!(
4522 rule.prerequisites().collect::<Vec<_>>(),
4523 vec!["dep1", "dep3"]
4524 );
4525 assert!(!rule.remove_prerequisite("nonexistent").unwrap());
4526 }
4527
4528 #[test]
4529 fn test_rule_set_prerequisites() {
4530 let mut rule: Rule = "target: old_dep\n".parse().unwrap();
4531 rule.set_prerequisites(vec!["new_dep1", "new_dep2"])
4532 .unwrap();
4533 assert_eq!(
4534 rule.prerequisites().collect::<Vec<_>>(),
4535 vec!["new_dep1", "new_dep2"]
4536 );
4537 }
4538
4539 #[test]
4540 fn test_rule_set_prerequisites_empty() {
4541 let mut rule: Rule = "target: dep1 dep2\n".parse().unwrap();
4542 rule.set_prerequisites(vec![]).unwrap();
4543 assert_eq!(rule.prerequisites().collect::<Vec<_>>().len(), 0);
4544 }
4545
4546 #[test]
4547 fn test_rule_add_target() {
4548 let mut rule: Rule = "target1: dep1\n".parse().unwrap();
4549 rule.add_target("target2").unwrap();
4550 assert_eq!(
4551 rule.targets().collect::<Vec<_>>(),
4552 vec!["target1", "target2"]
4553 );
4554 }
4555
4556 #[test]
4557 fn test_rule_set_targets() {
4558 let mut rule: Rule = "old_target: dependency\n".parse().unwrap();
4559 rule.set_targets(vec!["new_target1", "new_target2"])
4560 .unwrap();
4561 assert_eq!(
4562 rule.targets().collect::<Vec<_>>(),
4563 vec!["new_target1", "new_target2"]
4564 );
4565 }
4566
4567 #[test]
4568 fn test_rule_set_targets_empty() {
4569 let mut rule: Rule = "target: dep1\n".parse().unwrap();
4570 let result = rule.set_targets(vec![]);
4571 assert!(result.is_err());
4572 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
4574 }
4575
4576 #[test]
4577 fn test_rule_has_target() {
4578 let rule: Rule = "target1 target2: dependency\n".parse().unwrap();
4579 assert!(rule.has_target("target1"));
4580 assert!(rule.has_target("target2"));
4581 assert!(!rule.has_target("target3"));
4582 assert!(!rule.has_target("nonexistent"));
4583 }
4584
4585 #[test]
4586 fn test_rule_rename_target() {
4587 let mut rule: Rule = "old_target: dependency\n".parse().unwrap();
4588 assert!(rule.rename_target("old_target", "new_target").unwrap());
4589 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["new_target"]);
4590 assert!(!rule.rename_target("nonexistent", "something").unwrap());
4592 }
4593
4594 #[test]
4595 fn test_rule_rename_target_multiple() {
4596 let mut rule: Rule = "target1 target2 target3: dependency\n".parse().unwrap();
4597 assert!(rule.rename_target("target2", "renamed_target").unwrap());
4598 assert_eq!(
4599 rule.targets().collect::<Vec<_>>(),
4600 vec!["target1", "renamed_target", "target3"]
4601 );
4602 }
4603
4604 #[test]
4605 fn test_rule_remove_target() {
4606 let mut rule: Rule = "target1 target2 target3: dependency\n".parse().unwrap();
4607 assert!(rule.remove_target("target2").unwrap());
4608 assert_eq!(
4609 rule.targets().collect::<Vec<_>>(),
4610 vec!["target1", "target3"]
4611 );
4612 assert!(!rule.remove_target("nonexistent").unwrap());
4614 }
4615
4616 #[test]
4617 fn test_rule_remove_target_last() {
4618 let mut rule: Rule = "single_target: dependency\n".parse().unwrap();
4619 let result = rule.remove_target("single_target");
4620 assert!(result.is_err());
4621 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["single_target"]);
4623 }
4624
4625 #[test]
4626 fn test_rule_target_manipulation_preserves_prerequisites() {
4627 let mut rule: Rule = "target1 target2: dep1 dep2\n\tcommand".parse().unwrap();
4628
4629 rule.remove_target("target1").unwrap();
4631 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target2"]);
4632 assert_eq!(
4633 rule.prerequisites().collect::<Vec<_>>(),
4634 vec!["dep1", "dep2"]
4635 );
4636 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
4637
4638 rule.add_target("target3").unwrap();
4640 assert_eq!(
4641 rule.targets().collect::<Vec<_>>(),
4642 vec!["target2", "target3"]
4643 );
4644 assert_eq!(
4645 rule.prerequisites().collect::<Vec<_>>(),
4646 vec!["dep1", "dep2"]
4647 );
4648 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
4649
4650 rule.rename_target("target2", "renamed").unwrap();
4652 assert_eq!(
4653 rule.targets().collect::<Vec<_>>(),
4654 vec!["renamed", "target3"]
4655 );
4656 assert_eq!(
4657 rule.prerequisites().collect::<Vec<_>>(),
4658 vec!["dep1", "dep2"]
4659 );
4660 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
4661 }
4662
4663 #[test]
4664 fn test_rule_remove() {
4665 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4666 let rule = makefile.find_rule_by_target("rule1").unwrap();
4667 rule.remove().unwrap();
4668 assert_eq!(makefile.rules().count(), 1);
4669 assert!(makefile.find_rule_by_target("rule1").is_none());
4670 assert!(makefile.find_rule_by_target("rule2").is_some());
4671 }
4672
4673 #[test]
4674 fn test_rule_remove_last_trims_blank_lines() {
4675 let makefile: Makefile =
4677 "%:\n\tdh $@\n\noverride_dh_missing:\n\tdh_missing --fail-missing\n"
4678 .parse()
4679 .unwrap();
4680
4681 let rule = makefile.find_rule_by_target("override_dh_missing").unwrap();
4683 rule.remove().unwrap();
4684
4685 assert_eq!(makefile.code(), "%:\n\tdh $@\n");
4687 assert_eq!(makefile.rules().count(), 1);
4688 }
4689
4690 #[test]
4691 fn test_makefile_find_rule_by_target() {
4692 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4693 let rule = makefile.find_rule_by_target("rule2");
4694 assert!(rule.is_some());
4695 assert_eq!(rule.unwrap().targets().collect::<Vec<_>>(), vec!["rule2"]);
4696 assert!(makefile.find_rule_by_target("nonexistent").is_none());
4697 }
4698
4699 #[test]
4700 fn test_makefile_find_rules_by_target() {
4701 let makefile: Makefile = "rule1:\n\tcommand1\nrule1:\n\tcommand2\nrule2:\n\tcommand3\n"
4702 .parse()
4703 .unwrap();
4704 assert_eq!(makefile.find_rules_by_target("rule1").count(), 2);
4705 assert_eq!(makefile.find_rules_by_target("rule2").count(), 1);
4706 assert_eq!(makefile.find_rules_by_target("nonexistent").count(), 0);
4707 }
4708
4709 #[test]
4710 fn test_makefile_find_rule_by_target_pattern_simple() {
4711 let makefile: Makefile = "%.o: %.c\n\t$(CC) -c $<\n".parse().unwrap();
4712 let rule = makefile.find_rule_by_target_pattern("foo.o");
4713 assert!(rule.is_some());
4714 assert_eq!(rule.unwrap().targets().next().unwrap(), "%.o");
4715 }
4716
4717 #[test]
4718 fn test_makefile_find_rule_by_target_pattern_no_match() {
4719 let makefile: Makefile = "%.o: %.c\n\t$(CC) -c $<\n".parse().unwrap();
4720 let rule = makefile.find_rule_by_target_pattern("foo.c");
4721 assert!(rule.is_none());
4722 }
4723
4724 #[test]
4725 fn test_makefile_find_rule_by_target_pattern_exact() {
4726 let makefile: Makefile = "foo.o: foo.c\n\t$(CC) -c $<\n".parse().unwrap();
4727 let rule = makefile.find_rule_by_target_pattern("foo.o");
4728 assert!(rule.is_some());
4729 assert_eq!(rule.unwrap().targets().next().unwrap(), "foo.o");
4730 }
4731
4732 #[test]
4733 fn test_makefile_find_rule_by_target_pattern_prefix() {
4734 let makefile: Makefile = "lib%.a: %.o\n\tar rcs $@ $<\n".parse().unwrap();
4735 let rule = makefile.find_rule_by_target_pattern("libfoo.a");
4736 assert!(rule.is_some());
4737 assert_eq!(rule.unwrap().targets().next().unwrap(), "lib%.a");
4738 }
4739
4740 #[test]
4741 fn test_makefile_find_rule_by_target_pattern_suffix() {
4742 let makefile: Makefile = "%_test.o: %.c\n\t$(CC) -c $<\n".parse().unwrap();
4743 let rule = makefile.find_rule_by_target_pattern("foo_test.o");
4744 assert!(rule.is_some());
4745 assert_eq!(rule.unwrap().targets().next().unwrap(), "%_test.o");
4746 }
4747
4748 #[test]
4749 fn test_makefile_find_rule_by_target_pattern_middle() {
4750 let makefile: Makefile = "lib%_debug.a: %.o\n\tar rcs $@ $<\n".parse().unwrap();
4751 let rule = makefile.find_rule_by_target_pattern("libfoo_debug.a");
4752 assert!(rule.is_some());
4753 assert_eq!(rule.unwrap().targets().next().unwrap(), "lib%_debug.a");
4754 }
4755
4756 #[test]
4757 fn test_makefile_find_rule_by_target_pattern_wildcard_only() {
4758 let makefile: Makefile = "%: %.c\n\t$(CC) -o $@ $<\n".parse().unwrap();
4759 let rule = makefile.find_rule_by_target_pattern("anything");
4760 assert!(rule.is_some());
4761 assert_eq!(rule.unwrap().targets().next().unwrap(), "%");
4762 }
4763
4764 #[test]
4765 fn test_makefile_find_rules_by_target_pattern_multiple() {
4766 let makefile: Makefile = "%.o: %.c\n\t$(CC) -c $<\n%.o: %.s\n\t$(AS) -o $@ $<\n"
4767 .parse()
4768 .unwrap();
4769 let rules: Vec<_> = makefile.find_rules_by_target_pattern("foo.o").collect();
4770 assert_eq!(rules.len(), 2);
4771 }
4772
4773 #[test]
4774 fn test_makefile_find_rules_by_target_pattern_mixed() {
4775 let makefile: Makefile =
4776 "%.o: %.c\n\t$(CC) -c $<\nfoo.o: foo.h\n\t$(CC) -c foo.c\nbar.txt: baz.txt\n\tcp $< $@\n"
4777 .parse()
4778 .unwrap();
4779 let rules: Vec<_> = makefile.find_rules_by_target_pattern("foo.o").collect();
4780 assert_eq!(rules.len(), 2); let rules: Vec<_> = makefile.find_rules_by_target_pattern("bar.txt").collect();
4782 assert_eq!(rules.len(), 1); }
4784
4785 #[test]
4786 fn test_makefile_find_rules_by_target_pattern_no_wildcard() {
4787 let makefile: Makefile = "foo.o: foo.c\n\t$(CC) -c $<\n".parse().unwrap();
4788 let rules: Vec<_> = makefile.find_rules_by_target_pattern("foo.o").collect();
4789 assert_eq!(rules.len(), 1);
4790 let rules: Vec<_> = makefile.find_rules_by_target_pattern("bar.o").collect();
4791 assert_eq!(rules.len(), 0);
4792 }
4793
4794 #[test]
4795 fn test_matches_pattern_exact() {
4796 assert!(matches_pattern("foo.o", "foo.o"));
4797 assert!(!matches_pattern("foo.o", "bar.o"));
4798 }
4799
4800 #[test]
4801 fn test_matches_pattern_suffix() {
4802 assert!(matches_pattern("%.o", "foo.o"));
4803 assert!(matches_pattern("%.o", "bar.o"));
4804 assert!(matches_pattern("%.o", "baz/qux.o"));
4805 assert!(!matches_pattern("%.o", "foo.c"));
4806 }
4807
4808 #[test]
4809 fn test_matches_pattern_prefix() {
4810 assert!(matches_pattern("lib%.a", "libfoo.a"));
4811 assert!(matches_pattern("lib%.a", "libbar.a"));
4812 assert!(!matches_pattern("lib%.a", "foo.a"));
4813 assert!(!matches_pattern("lib%.a", "lib.a"));
4814 }
4815
4816 #[test]
4817 fn test_matches_pattern_middle() {
4818 assert!(matches_pattern("lib%_debug.a", "libfoo_debug.a"));
4819 assert!(matches_pattern("lib%_debug.a", "libbar_debug.a"));
4820 assert!(!matches_pattern("lib%_debug.a", "libfoo.a"));
4821 assert!(!matches_pattern("lib%_debug.a", "foo_debug.a"));
4822 }
4823
4824 #[test]
4825 fn test_matches_pattern_wildcard_only() {
4826 assert!(matches_pattern("%", "anything"));
4827 assert!(matches_pattern("%", "foo.o"));
4828 assert!(!matches_pattern("%", ""));
4830 }
4831
4832 #[test]
4833 fn test_matches_pattern_empty_stem() {
4834 assert!(!matches_pattern("%.o", ".o")); assert!(!matches_pattern("lib%", "lib")); assert!(!matches_pattern("lib%.a", "lib.a")); }
4839
4840 #[test]
4841 fn test_matches_pattern_multiple_wildcards_not_supported() {
4842 assert!(!matches_pattern("%foo%bar", "xfooybarz"));
4845 assert!(!matches_pattern("lib%.so.%", "libfoo.so.1"));
4846 }
4847
4848 #[test]
4849 fn test_makefile_add_phony_target() {
4850 let mut makefile = Makefile::new();
4851 makefile.add_phony_target("clean").unwrap();
4852 assert!(makefile.is_phony("clean"));
4853 assert_eq!(makefile.phony_targets().collect::<Vec<_>>(), vec!["clean"]);
4854 }
4855
4856 #[test]
4857 fn test_makefile_add_phony_target_existing() {
4858 let mut makefile: Makefile = ".PHONY: test\n".parse().unwrap();
4859 makefile.add_phony_target("clean").unwrap();
4860 assert!(makefile.is_phony("test"));
4861 assert!(makefile.is_phony("clean"));
4862 let targets: Vec<_> = makefile.phony_targets().collect();
4863 assert!(targets.contains(&"test".to_string()));
4864 assert!(targets.contains(&"clean".to_string()));
4865 }
4866
4867 #[test]
4868 fn test_makefile_remove_phony_target() {
4869 let mut makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4870 assert!(makefile.remove_phony_target("clean").unwrap());
4871 assert!(!makefile.is_phony("clean"));
4872 assert!(makefile.is_phony("test"));
4873 assert!(!makefile.remove_phony_target("nonexistent").unwrap());
4874 }
4875
4876 #[test]
4877 fn test_makefile_remove_phony_target_last() {
4878 let mut makefile: Makefile = ".PHONY: clean\n".parse().unwrap();
4879 assert!(makefile.remove_phony_target("clean").unwrap());
4880 assert!(!makefile.is_phony("clean"));
4881 assert!(makefile.find_rule_by_target(".PHONY").is_none());
4883 }
4884
4885 #[test]
4886 fn test_makefile_is_phony() {
4887 let makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4888 assert!(makefile.is_phony("clean"));
4889 assert!(makefile.is_phony("test"));
4890 assert!(!makefile.is_phony("build"));
4891 }
4892
4893 #[test]
4894 fn test_makefile_phony_targets() {
4895 let makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
4896 let phony_targets: Vec<_> = makefile.phony_targets().collect();
4897 assert_eq!(phony_targets, vec!["clean", "test", "build"]);
4898 }
4899
4900 #[test]
4901 fn test_makefile_phony_targets_empty() {
4902 let makefile = Makefile::new();
4903 assert_eq!(makefile.phony_targets().count(), 0);
4904 }
4905
4906 #[test]
4907 fn test_makefile_remove_first_phony_target_no_extra_space() {
4908 let mut makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
4909 assert!(makefile.remove_phony_target("clean").unwrap());
4910 let result = makefile.to_string();
4911 assert_eq!(result, ".PHONY: test build\n");
4912 }
4913
4914 #[test]
4915 fn test_recipe_with_leading_comments_and_blank_lines() {
4916 let makefile_text = r#"#!/usr/bin/make
4920
4921%:
4922 dh $@
4923
4924override_dh_build:
4925 # The next line is empty
4926
4927 dh_python3
4928"#;
4929 let makefile = Makefile::read_relaxed(makefile_text.as_bytes()).unwrap();
4930
4931 let rules: Vec<_> = makefile.rules().collect();
4932 assert_eq!(rules.len(), 2, "Expected 2 rules");
4933
4934 let rule0 = &rules[0];
4936 assert_eq!(rule0.targets().collect::<Vec<_>>(), vec!["%"]);
4937 assert_eq!(rule0.recipes().collect::<Vec<_>>(), vec!["dh $@"]);
4938
4939 let rule1 = &rules[1];
4941 assert_eq!(
4942 rule1.targets().collect::<Vec<_>>(),
4943 vec!["override_dh_build"]
4944 );
4945
4946 let recipes: Vec<_> = rule1.recipes().collect();
4948 assert!(
4949 !recipes.is_empty(),
4950 "Expected at least one recipe for override_dh_build, got none"
4951 );
4952 assert!(
4953 recipes.contains(&"dh_python3".to_string()),
4954 "Expected 'dh_python3' in recipes, got: {:?}",
4955 recipes
4956 );
4957 }
4958
4959 #[test]
4960 fn test_rule_parse_preserves_trailing_blank_lines() {
4961 let input = r#"override_dh_systemd_enable:
4964 dh_systemd_enable -pracoon
4965
4966override_dh_install:
4967 dh_install
4968"#;
4969
4970 let mut mf: Makefile = input.parse().unwrap();
4971
4972 let rule = mf.rules().next().unwrap();
4974 let rule_text = rule.to_string();
4975
4976 assert_eq!(
4978 rule_text,
4979 "override_dh_systemd_enable:\n\tdh_systemd_enable -pracoon\n\n"
4980 );
4981
4982 let modified =
4984 rule_text.replace("override_dh_systemd_enable:", "override_dh_installsystemd:");
4985
4986 let new_rule: Rule = modified.parse().unwrap();
4988 assert_eq!(
4989 new_rule.to_string(),
4990 "override_dh_installsystemd:\n\tdh_systemd_enable -pracoon\n\n"
4991 );
4992
4993 mf.replace_rule(0, new_rule).unwrap();
4995
4996 let output = mf.to_string();
4998 assert!(
4999 output.contains(
5000 "override_dh_installsystemd:\n\tdh_systemd_enable -pracoon\n\noverride_dh_install:"
5001 ),
5002 "Blank line between rules should be preserved. Got: {:?}",
5003 output
5004 );
5005 }
5006
5007 #[test]
5008 fn test_rule_parse_round_trip_with_trailing_newlines() {
5009 let test_cases = vec![
5011 "rule:\n\tcommand\n", "rule:\n\tcommand\n\n", "rule:\n\tcommand\n\n\n", ];
5015
5016 for rule_text in test_cases {
5017 let rule: Rule = rule_text.parse().unwrap();
5018 let result = rule.to_string();
5019 assert_eq!(rule_text, result, "Round-trip failed for {:?}", rule_text);
5020 }
5021 }
5022
5023 #[test]
5024 fn test_rule_clone() {
5025 let rule_text = "rule:\n\tcommand\n\n";
5027 let rule: Rule = rule_text.parse().unwrap();
5028 let cloned = rule.clone();
5029
5030 assert_eq!(rule.to_string(), cloned.to_string());
5032 assert_eq!(rule.to_string(), rule_text);
5033 assert_eq!(cloned.to_string(), rule_text);
5034
5035 assert_eq!(
5037 rule.targets().collect::<Vec<_>>(),
5038 cloned.targets().collect::<Vec<_>>()
5039 );
5040 assert_eq!(
5041 rule.recipes().collect::<Vec<_>>(),
5042 cloned.recipes().collect::<Vec<_>>()
5043 );
5044 }
5045
5046 #[test]
5047 fn test_makefile_clone() {
5048 let input = "VAR = value\n\nrule:\n\tcommand\n";
5050 let makefile: Makefile = input.parse().unwrap();
5051 let cloned = makefile.clone();
5052
5053 assert_eq!(makefile.to_string(), cloned.to_string());
5055 assert_eq!(makefile.to_string(), input);
5056
5057 assert_eq!(makefile.rules().count(), cloned.rules().count());
5059
5060 assert_eq!(
5062 makefile.variable_definitions().count(),
5063 cloned.variable_definitions().count()
5064 );
5065 }
5066
5067 #[test]
5068 fn test_conditional_with_recipe_line() {
5069 let input = "ifeq (,$(X))\n\t./run-tests\nendif\n";
5071 let parsed = parse(input, None);
5072
5073 assert!(
5075 parsed.errors.is_empty(),
5076 "Expected no parse errors, but got: {:?}",
5077 parsed.errors
5078 );
5079
5080 let mf = parsed.root();
5082 assert_eq!(mf.code(), input);
5083 }
5084
5085 #[test]
5086 fn test_conditional_in_rule_recipe() {
5087 let input = "override_dh_auto_test:\nifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))\n\t./run-tests\nendif\n";
5089 let parsed = parse(input, None);
5090
5091 assert!(
5093 parsed.errors.is_empty(),
5094 "Expected no parse errors, but got: {:?}",
5095 parsed.errors
5096 );
5097
5098 let mf = parsed.root();
5100 assert_eq!(mf.code(), input);
5101
5102 assert_eq!(mf.rules().count(), 1);
5104 }
5105
5106 #[test]
5107 fn test_rule_items() {
5108 use crate::RuleItem;
5109
5110 let input = r#"test:
5112 echo "before"
5113ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
5114 ./run-tests
5115endif
5116 echo "after"
5117"#;
5118 let rule: Rule = input.parse().unwrap();
5119
5120 let items: Vec<_> = rule.items().collect();
5121 assert_eq!(
5122 items.len(),
5123 3,
5124 "Expected 3 items: recipe, conditional, recipe"
5125 );
5126
5127 match &items[0] {
5129 RuleItem::Recipe(r) => assert_eq!(r, "echo \"before\""),
5130 RuleItem::Conditional(_) => panic!("Expected recipe, got conditional"),
5131 }
5132
5133 match &items[1] {
5135 RuleItem::Conditional(c) => {
5136 assert_eq!(c.conditional_type(), Some("ifeq".to_string()));
5137 }
5138 RuleItem::Recipe(_) => panic!("Expected conditional, got recipe"),
5139 }
5140
5141 match &items[2] {
5143 RuleItem::Recipe(r) => assert_eq!(r, "echo \"after\""),
5144 RuleItem::Conditional(_) => panic!("Expected recipe, got conditional"),
5145 }
5146
5147 let simple_rule: Rule = "simple:\n\techo one\n\techo two\n".parse().unwrap();
5149 let simple_items: Vec<_> = simple_rule.items().collect();
5150 assert_eq!(simple_items.len(), 2);
5151
5152 match &simple_items[0] {
5153 RuleItem::Recipe(r) => assert_eq!(r, "echo one"),
5154 _ => panic!("Expected recipe"),
5155 }
5156
5157 match &simple_items[1] {
5158 RuleItem::Recipe(r) => assert_eq!(r, "echo two"),
5159 _ => panic!("Expected recipe"),
5160 }
5161
5162 let cond_only: Rule = "condtest:\nifeq (a,b)\n\techo yes\nendif\n"
5164 .parse()
5165 .unwrap();
5166 let cond_items: Vec<_> = cond_only.items().collect();
5167 assert_eq!(cond_items.len(), 1);
5168
5169 match &cond_items[0] {
5170 RuleItem::Conditional(c) => {
5171 assert_eq!(c.conditional_type(), Some("ifeq".to_string()));
5172 }
5173 _ => panic!("Expected conditional"),
5174 }
5175 }
5176
5177 #[test]
5178 fn test_conditionals_iterator() {
5179 let makefile: Makefile = r#"ifdef DEBUG
5180VAR = debug
5181endif
5182
5183ifndef RELEASE
5184OTHER = dev
5185endif
5186"#
5187 .parse()
5188 .unwrap();
5189
5190 let conditionals: Vec<_> = makefile.conditionals().collect();
5191 assert_eq!(conditionals.len(), 2);
5192
5193 assert_eq!(
5194 conditionals[0].conditional_type(),
5195 Some("ifdef".to_string())
5196 );
5197 assert_eq!(
5198 conditionals[1].conditional_type(),
5199 Some("ifndef".to_string())
5200 );
5201 }
5202
5203 #[test]
5204 fn test_conditional_type_and_condition() {
5205 let makefile: Makefile = r#"ifdef DEBUG
5206VAR = debug
5207endif
5208"#
5209 .parse()
5210 .unwrap();
5211
5212 let conditional = makefile.conditionals().next().unwrap();
5213 assert_eq!(conditional.conditional_type(), Some("ifdef".to_string()));
5214 assert_eq!(conditional.condition(), Some("DEBUG".to_string()));
5215 }
5216
5217 #[test]
5218 fn test_conditional_has_else() {
5219 let makefile_with_else: Makefile = r#"ifdef DEBUG
5220VAR = debug
5221else
5222VAR = release
5223endif
5224"#
5225 .parse()
5226 .unwrap();
5227
5228 let conditional = makefile_with_else.conditionals().next().unwrap();
5229 assert!(conditional.has_else());
5230
5231 let makefile_without_else: Makefile = r#"ifdef DEBUG
5232VAR = debug
5233endif
5234"#
5235 .parse()
5236 .unwrap();
5237
5238 let conditional = makefile_without_else.conditionals().next().unwrap();
5239 assert!(!conditional.has_else());
5240 }
5241
5242 #[test]
5243 fn test_conditional_if_body() {
5244 let makefile: Makefile = r#"ifdef DEBUG
5245VAR = debug
5246endif
5247"#
5248 .parse()
5249 .unwrap();
5250
5251 let conditional = makefile.conditionals().next().unwrap();
5252 let if_body = conditional.if_body();
5253 assert!(if_body.is_some());
5254 assert!(if_body.unwrap().contains("VAR = debug"));
5255 }
5256
5257 #[test]
5258 fn test_conditional_else_body() {
5259 let makefile: Makefile = r#"ifdef DEBUG
5260VAR = debug
5261else
5262VAR = release
5263endif
5264"#
5265 .parse()
5266 .unwrap();
5267
5268 let conditional = makefile.conditionals().next().unwrap();
5269 let else_body = conditional.else_body();
5270 assert!(else_body.is_some());
5271 assert!(else_body.unwrap().contains("VAR = release"));
5272 }
5273
5274 #[test]
5275 fn test_add_conditional_ifdef() {
5276 let mut makefile = Makefile::new();
5277 let result = makefile.add_conditional("ifdef", "DEBUG", "VAR = debug\n", None);
5278 assert!(result.is_ok());
5279
5280 let code = makefile.to_string();
5281 assert!(code.contains("ifdef DEBUG"));
5282 assert!(code.contains("VAR = debug"));
5283 assert!(code.contains("endif"));
5284 }
5285
5286 #[test]
5287 fn test_add_conditional_with_else() {
5288 let mut makefile = Makefile::new();
5289 let result =
5290 makefile.add_conditional("ifdef", "DEBUG", "VAR = debug\n", Some("VAR = release\n"));
5291 assert!(result.is_ok());
5292
5293 let code = makefile.to_string();
5294 assert!(code.contains("ifdef DEBUG"));
5295 assert!(code.contains("VAR = debug"));
5296 assert!(code.contains("else"));
5297 assert!(code.contains("VAR = release"));
5298 assert!(code.contains("endif"));
5299 }
5300
5301 #[test]
5302 fn test_add_conditional_invalid_type() {
5303 let mut makefile = Makefile::new();
5304 let result = makefile.add_conditional("invalid", "DEBUG", "VAR = debug\n", None);
5305 assert!(result.is_err());
5306 }
5307
5308 #[test]
5309 fn test_add_conditional_formatting() {
5310 let mut makefile: Makefile = "VAR1 = value1\n".parse().unwrap();
5311 let result = makefile.add_conditional("ifdef", "DEBUG", "VAR = debug\n", None);
5312 assert!(result.is_ok());
5313
5314 let code = makefile.to_string();
5315 assert!(code.contains("\n\nifdef DEBUG"));
5317 }
5318
5319 #[test]
5320 fn test_conditional_remove() {
5321 let makefile: Makefile = r#"ifdef DEBUG
5322VAR = debug
5323endif
5324
5325VAR2 = value2
5326"#
5327 .parse()
5328 .unwrap();
5329
5330 let mut conditional = makefile.conditionals().next().unwrap();
5331 let result = conditional.remove();
5332 assert!(result.is_ok());
5333
5334 let code = makefile.to_string();
5335 assert!(!code.contains("ifdef DEBUG"));
5336 assert!(!code.contains("VAR = debug"));
5337 assert!(code.contains("VAR2 = value2"));
5338 }
5339
5340 #[test]
5341 fn test_add_conditional_ifndef() {
5342 let mut makefile = Makefile::new();
5343 let result = makefile.add_conditional("ifndef", "NDEBUG", "VAR = enabled\n", None);
5344 assert!(result.is_ok());
5345
5346 let code = makefile.to_string();
5347 assert!(code.contains("ifndef NDEBUG"));
5348 assert!(code.contains("VAR = enabled"));
5349 assert!(code.contains("endif"));
5350 }
5351
5352 #[test]
5353 fn test_add_conditional_ifeq() {
5354 let mut makefile = Makefile::new();
5355 let result = makefile.add_conditional("ifeq", "($(OS),Linux)", "VAR = linux\n", None);
5356 assert!(result.is_ok());
5357
5358 let code = makefile.to_string();
5359 assert!(code.contains("ifeq ($(OS),Linux)"));
5360 assert!(code.contains("VAR = linux"));
5361 assert!(code.contains("endif"));
5362 }
5363
5364 #[test]
5365 fn test_add_conditional_ifneq() {
5366 let mut makefile = Makefile::new();
5367 let result = makefile.add_conditional("ifneq", "($(OS),Windows)", "VAR = unix\n", None);
5368 assert!(result.is_ok());
5369
5370 let code = makefile.to_string();
5371 assert!(code.contains("ifneq ($(OS),Windows)"));
5372 assert!(code.contains("VAR = unix"));
5373 assert!(code.contains("endif"));
5374 }
5375
5376 #[test]
5377 fn test_conditional_api_integration() {
5378 let mut makefile: Makefile = r#"VAR1 = value1
5380
5381rule1:
5382 command1
5383"#
5384 .parse()
5385 .unwrap();
5386
5387 makefile
5389 .add_conditional("ifdef", "DEBUG", "CFLAGS += -g\n", Some("CFLAGS += -O2\n"))
5390 .unwrap();
5391
5392 assert_eq!(makefile.conditionals().count(), 1);
5394 let conditional = makefile.conditionals().next().unwrap();
5395 assert_eq!(conditional.conditional_type(), Some("ifdef".to_string()));
5396 assert_eq!(conditional.condition(), Some("DEBUG".to_string()));
5397 assert!(conditional.has_else());
5398
5399 assert_eq!(makefile.variable_definitions().count(), 1);
5401 assert_eq!(makefile.rules().count(), 1);
5402 }
5403
5404 #[test]
5405 fn test_conditional_if_items() {
5406 let makefile: Makefile = r#"ifdef DEBUG
5407VAR = debug
5408rule:
5409 command
5410endif
5411"#
5412 .parse()
5413 .unwrap();
5414
5415 let cond = makefile.conditionals().next().unwrap();
5416 let items: Vec<_> = cond.if_items().collect();
5417 assert_eq!(items.len(), 2); match &items[0] {
5420 MakefileItem::Variable(v) => {
5421 assert_eq!(v.name(), Some("VAR".to_string()));
5422 }
5423 _ => panic!("Expected variable"),
5424 }
5425
5426 match &items[1] {
5427 MakefileItem::Rule(r) => {
5428 assert!(r.targets().any(|t| t == "rule"));
5429 }
5430 _ => panic!("Expected rule"),
5431 }
5432 }
5433
5434 #[test]
5435 fn test_conditional_else_items() {
5436 let makefile: Makefile = r#"ifdef DEBUG
5437VAR = debug
5438else
5439VAR2 = release
5440rule2:
5441 command
5442endif
5443"#
5444 .parse()
5445 .unwrap();
5446
5447 let cond = makefile.conditionals().next().unwrap();
5448 let items: Vec<_> = cond.else_items().collect();
5449 assert_eq!(items.len(), 2); match &items[0] {
5452 MakefileItem::Variable(v) => {
5453 assert_eq!(v.name(), Some("VAR2".to_string()));
5454 }
5455 _ => panic!("Expected variable"),
5456 }
5457
5458 match &items[1] {
5459 MakefileItem::Rule(r) => {
5460 assert!(r.targets().any(|t| t == "rule2"));
5461 }
5462 _ => panic!("Expected rule"),
5463 }
5464 }
5465
5466 #[test]
5467 fn test_conditional_add_if_item() {
5468 let makefile: Makefile = "ifdef DEBUG\nendif\n".parse().unwrap();
5469 let mut cond = makefile.conditionals().next().unwrap();
5470
5471 let temp: Makefile = "CFLAGS = -g\n".parse().unwrap();
5473 let var = temp.variable_definitions().next().unwrap();
5474 cond.add_if_item(MakefileItem::Variable(var));
5475
5476 let code = makefile.to_string();
5477 assert!(code.contains("CFLAGS = -g"));
5478
5479 let cond = makefile.conditionals().next().unwrap();
5481 assert_eq!(cond.if_items().count(), 1);
5482 }
5483
5484 #[test]
5485 fn test_conditional_add_else_item() {
5486 let makefile: Makefile = "ifdef DEBUG\nVAR=1\nendif\n".parse().unwrap();
5487 let mut cond = makefile.conditionals().next().unwrap();
5488
5489 let temp: Makefile = "CFLAGS = -O2\n".parse().unwrap();
5491 let var = temp.variable_definitions().next().unwrap();
5492 cond.add_else_item(MakefileItem::Variable(var));
5493
5494 let code = makefile.to_string();
5495 assert!(code.contains("else"));
5496 assert!(code.contains("CFLAGS = -O2"));
5497
5498 let cond = makefile.conditionals().next().unwrap();
5500 assert_eq!(cond.else_items().count(), 1);
5501 }
5502
5503 #[test]
5504 fn test_add_conditional_with_items() {
5505 let mut makefile = Makefile::new();
5506
5507 let temp1: Makefile = "CFLAGS = -g\n".parse().unwrap();
5509 let var1 = temp1.variable_definitions().next().unwrap();
5510
5511 let temp2: Makefile = "CFLAGS = -O2\n".parse().unwrap();
5512 let var2 = temp2.variable_definitions().next().unwrap();
5513
5514 let temp3: Makefile = "debug:\n\techo debug\n".parse().unwrap();
5515 let rule1 = temp3.rules().next().unwrap();
5516
5517 let result = makefile.add_conditional_with_items(
5518 "ifdef",
5519 "DEBUG",
5520 vec![MakefileItem::Variable(var1), MakefileItem::Rule(rule1)],
5521 Some(vec![MakefileItem::Variable(var2)]),
5522 );
5523
5524 assert!(result.is_ok());
5525
5526 let code = makefile.to_string();
5527 assert!(code.contains("ifdef DEBUG"));
5528 assert!(code.contains("CFLAGS = -g"));
5529 assert!(code.contains("debug:"));
5530 assert!(code.contains("else"));
5531 assert!(code.contains("CFLAGS = -O2"));
5532 }
5533
5534 #[test]
5535 fn test_conditional_items_with_nested_conditional() {
5536 let makefile: Makefile = r#"ifdef DEBUG
5537VAR = debug
5538ifdef VERBOSE
5539 VAR2 = verbose
5540endif
5541endif
5542"#
5543 .parse()
5544 .unwrap();
5545
5546 let cond = makefile.conditionals().next().unwrap();
5547 let items: Vec<_> = cond.if_items().collect();
5548 assert_eq!(items.len(), 2); match &items[0] {
5551 MakefileItem::Variable(v) => {
5552 assert_eq!(v.name(), Some("VAR".to_string()));
5553 }
5554 _ => panic!("Expected variable"),
5555 }
5556
5557 match &items[1] {
5558 MakefileItem::Conditional(c) => {
5559 assert_eq!(c.conditional_type(), Some("ifdef".to_string()));
5560 }
5561 _ => panic!("Expected conditional"),
5562 }
5563 }
5564
5565 #[test]
5566 fn test_conditional_items_with_include() {
5567 let makefile: Makefile = r#"ifdef DEBUG
5568include debug.mk
5569VAR = debug
5570endif
5571"#
5572 .parse()
5573 .unwrap();
5574
5575 let cond = makefile.conditionals().next().unwrap();
5576 let items: Vec<_> = cond.if_items().collect();
5577 assert_eq!(items.len(), 2); match &items[0] {
5580 MakefileItem::Include(i) => {
5581 assert_eq!(i.path(), Some("debug.mk".to_string()));
5582 }
5583 _ => panic!("Expected include"),
5584 }
5585
5586 match &items[1] {
5587 MakefileItem::Variable(v) => {
5588 assert_eq!(v.name(), Some("VAR".to_string()));
5589 }
5590 _ => panic!("Expected variable"),
5591 }
5592 }
5593
5594 #[test]
5595 fn test_makefile_items_iterator() {
5596 let makefile: Makefile = r#"VAR = value
5597ifdef DEBUG
5598CFLAGS = -g
5599endif
5600rule:
5601 command
5602include common.mk
5603"#
5604 .parse()
5605 .unwrap();
5606
5607 assert_eq!(makefile.variable_definitions().count(), 2);
5610 assert_eq!(makefile.conditionals().count(), 1);
5611 assert_eq!(makefile.rules().count(), 1);
5612
5613 let items: Vec<_> = makefile.items().collect();
5614 assert!(
5616 items.len() >= 3,
5617 "Expected at least 3 items, got {}",
5618 items.len()
5619 );
5620
5621 match &items[0] {
5622 MakefileItem::Variable(v) => {
5623 assert_eq!(v.name(), Some("VAR".to_string()));
5624 }
5625 _ => panic!("Expected variable at position 0"),
5626 }
5627
5628 match &items[1] {
5629 MakefileItem::Conditional(c) => {
5630 assert_eq!(c.conditional_type(), Some("ifdef".to_string()));
5631 }
5632 _ => panic!("Expected conditional at position 1"),
5633 }
5634
5635 match &items[2] {
5636 MakefileItem::Rule(r) => {
5637 let targets: Vec<_> = r.targets().collect();
5638 assert_eq!(targets, vec!["rule"]);
5639 }
5640 _ => panic!("Expected rule at position 2"),
5641 }
5642 }
5643
5644 #[test]
5645 fn test_conditional_unwrap() {
5646 let makefile: Makefile = r#"ifdef DEBUG
5647VAR = debug
5648rule:
5649 command
5650endif
5651"#
5652 .parse()
5653 .unwrap();
5654
5655 let mut cond = makefile.conditionals().next().unwrap();
5656 cond.unwrap().unwrap();
5657
5658 let code = makefile.to_string();
5659 let expected = "VAR = debug\nrule:\n\tcommand\n";
5660 assert_eq!(code, expected);
5661
5662 assert_eq!(makefile.conditionals().count(), 0);
5664
5665 assert_eq!(makefile.variable_definitions().count(), 1);
5667 assert_eq!(makefile.rules().count(), 1);
5668 }
5669
5670 #[test]
5671 fn test_conditional_unwrap_with_else_fails() {
5672 let makefile: Makefile = r#"ifdef DEBUG
5673VAR = debug
5674else
5675VAR = release
5676endif
5677"#
5678 .parse()
5679 .unwrap();
5680
5681 let mut cond = makefile.conditionals().next().unwrap();
5682 let result = cond.unwrap();
5683
5684 assert!(result.is_err());
5685 assert!(result
5686 .unwrap_err()
5687 .to_string()
5688 .contains("Cannot unwrap conditional with else clause"));
5689 }
5690
5691 #[test]
5692 fn test_conditional_unwrap_nested() {
5693 let makefile: Makefile = r#"ifdef OUTER
5694VAR = outer
5695ifdef INNER
5696VAR2 = inner
5697endif
5698endif
5699"#
5700 .parse()
5701 .unwrap();
5702
5703 let mut outer_cond = makefile.conditionals().next().unwrap();
5705 outer_cond.unwrap().unwrap();
5706
5707 let code = makefile.to_string();
5708 let expected = "VAR = outer\nifdef INNER\nVAR2 = inner\nendif\n";
5709 assert_eq!(code, expected);
5710 }
5711
5712 #[test]
5713 fn test_conditional_unwrap_empty() {
5714 let makefile: Makefile = r#"ifdef DEBUG
5715endif
5716"#
5717 .parse()
5718 .unwrap();
5719
5720 let mut cond = makefile.conditionals().next().unwrap();
5721 cond.unwrap().unwrap();
5722
5723 let code = makefile.to_string();
5724 assert_eq!(code, "");
5725 }
5726
5727 #[test]
5728 fn test_rule_parent() {
5729 let makefile: Makefile = r#"all:
5730 echo "test"
5731"#
5732 .parse()
5733 .unwrap();
5734
5735 let rule = makefile.rules().next().unwrap();
5736 let parent = rule.parent();
5737 assert!(parent.is_none());
5739 }
5740
5741 #[test]
5742 fn test_item_parent_in_conditional() {
5743 let makefile: Makefile = r#"ifdef DEBUG
5744VAR = debug
5745rule:
5746 command
5747endif
5748"#
5749 .parse()
5750 .unwrap();
5751
5752 let cond = makefile.conditionals().next().unwrap();
5753
5754 let items: Vec<_> = cond.if_items().collect();
5756 assert_eq!(items.len(), 2);
5757
5758 if let MakefileItem::Variable(var) = &items[0] {
5760 let parent = var.parent();
5761 assert!(parent.is_some());
5762 if let Some(MakefileItem::Conditional(_)) = parent {
5763 } else {
5765 panic!("Expected variable parent to be a Conditional");
5766 }
5767 } else {
5768 panic!("Expected first item to be a Variable");
5769 }
5770
5771 if let MakefileItem::Rule(rule) = &items[1] {
5773 let parent = rule.parent();
5774 assert!(parent.is_some());
5775 if let Some(MakefileItem::Conditional(_)) = parent {
5776 } else {
5778 panic!("Expected rule parent to be a Conditional");
5779 }
5780 } else {
5781 panic!("Expected second item to be a Rule");
5782 }
5783 }
5784
5785 #[test]
5786 fn test_nested_conditional_parent() {
5787 let makefile: Makefile = r#"ifdef OUTER
5788VAR = outer
5789ifdef INNER
5790VAR2 = inner
5791endif
5792endif
5793"#
5794 .parse()
5795 .unwrap();
5796
5797 let outer_cond = makefile.conditionals().next().unwrap();
5798
5799 let items: Vec<_> = outer_cond.if_items().collect();
5801
5802 let inner_cond = items
5804 .iter()
5805 .find_map(|item| {
5806 if let MakefileItem::Conditional(c) = item {
5807 Some(c)
5808 } else {
5809 None
5810 }
5811 })
5812 .unwrap();
5813
5814 let parent = inner_cond.parent();
5816 assert!(parent.is_some());
5817 if let Some(MakefileItem::Conditional(_)) = parent {
5818 } else {
5820 panic!("Expected inner conditional's parent to be a Conditional");
5821 }
5822 }
5823
5824 #[test]
5825 fn test_line_col() {
5826 let text = r#"# Comment at line 0
5827VAR1 = value1
5828VAR2 = value2
5829
5830rule1: dep1 dep2
5831 command1
5832 command2
5833
5834rule2:
5835 command3
5836
5837ifdef DEBUG
5838CFLAGS = -g
5839endif
5840"#;
5841 let makefile: Makefile = text.parse().unwrap();
5842
5843 let vars: Vec<_> = makefile.variable_definitions().collect();
5846 assert_eq!(vars.len(), 3);
5847
5848 assert_eq!(vars[0].line(), 1);
5850 assert_eq!(vars[0].column(), 0);
5851 assert_eq!(vars[0].line_col(), (1, 0));
5852
5853 assert_eq!(vars[1].line(), 2);
5855 assert_eq!(vars[1].column(), 0);
5856
5857 assert_eq!(vars[2].line(), 12);
5859 assert_eq!(vars[2].column(), 0);
5860
5861 let rules: Vec<_> = makefile.rules().collect();
5863 assert_eq!(rules.len(), 2);
5864
5865 assert_eq!(rules[0].line(), 4);
5867 assert_eq!(rules[0].column(), 0);
5868 assert_eq!(rules[0].line_col(), (4, 0));
5869
5870 assert_eq!(rules[1].line(), 8);
5872 assert_eq!(rules[1].column(), 0);
5873
5874 let conditionals: Vec<_> = makefile.conditionals().collect();
5876 assert_eq!(conditionals.len(), 1);
5877
5878 assert_eq!(conditionals[0].line(), 11);
5880 assert_eq!(conditionals[0].column(), 0);
5881 assert_eq!(conditionals[0].line_col(), (11, 0));
5882 }
5883
5884 #[test]
5885 fn test_line_col_multiline() {
5886 let text = "SOURCES = \\\n\tfile1.c \\\n\tfile2.c\n\ntarget: $(SOURCES)\n\tgcc -o target $(SOURCES)\n";
5887 let makefile: Makefile = text.parse().unwrap();
5888
5889 let vars: Vec<_> = makefile.variable_definitions().collect();
5891 assert_eq!(vars.len(), 1);
5892 assert_eq!(vars[0].line(), 0);
5893 assert_eq!(vars[0].column(), 0);
5894
5895 let rules: Vec<_> = makefile.rules().collect();
5897 assert_eq!(rules.len(), 1);
5898 assert_eq!(rules[0].line(), 4);
5899 assert_eq!(rules[0].column(), 0);
5900 }
5901
5902 #[test]
5903 fn test_line_col_includes() {
5904 let text = "VAR = value\n\ninclude config.mk\n-include optional.mk\n";
5905 let makefile: Makefile = text.parse().unwrap();
5906
5907 let vars: Vec<_> = makefile.variable_definitions().collect();
5909 assert_eq!(vars[0].line(), 0);
5910
5911 let includes: Vec<_> = makefile.includes().collect();
5913 assert_eq!(includes.len(), 2);
5914 assert_eq!(includes[0].line(), 2);
5915 assert_eq!(includes[0].column(), 0);
5916 assert_eq!(includes[1].line(), 3);
5917 assert_eq!(includes[1].column(), 0);
5918 }
5919
5920 #[test]
5921 fn test_conditional_in_rule_vs_toplevel() {
5922 let text1 = r#"rule:
5924 command
5925ifeq (,$(X))
5926 test
5927endif
5928"#;
5929 let makefile: Makefile = text1.parse().unwrap();
5930 let rules: Vec<_> = makefile.rules().collect();
5931 let conditionals: Vec<_> = makefile.conditionals().collect();
5932
5933 assert_eq!(rules.len(), 1);
5934 assert_eq!(
5935 conditionals.len(),
5936 0,
5937 "Conditional should be part of rule, not top-level"
5938 );
5939
5940 let text2 = r#"rule:
5942 command
5943
5944ifeq (,$(X))
5945 test
5946endif
5947"#;
5948 let makefile: Makefile = text2.parse().unwrap();
5949 let rules: Vec<_> = makefile.rules().collect();
5950 let conditionals: Vec<_> = makefile.conditionals().collect();
5951
5952 assert_eq!(rules.len(), 1);
5953 assert_eq!(
5954 conditionals.len(),
5955 1,
5956 "Conditional after blank line should be top-level"
5957 );
5958 assert_eq!(conditionals[0].line(), 3);
5959 }
5960
5961 #[test]
5962 fn test_nested_conditionals_line_tracking() {
5963 let text = r#"ifdef OUTER
5964VAR1 = value1
5965ifdef INNER
5966VAR2 = value2
5967endif
5968VAR3 = value3
5969endif
5970"#;
5971 let makefile: Makefile = text.parse().unwrap();
5972
5973 let conditionals: Vec<_> = makefile.conditionals().collect();
5974 assert_eq!(
5975 conditionals.len(),
5976 1,
5977 "Only outer conditional should be top-level"
5978 );
5979 assert_eq!(conditionals[0].line(), 0);
5980 assert_eq!(conditionals[0].column(), 0);
5981 }
5982
5983 #[test]
5984 fn test_conditional_else_line_tracking() {
5985 let text = r#"VAR1 = before
5986
5987ifdef DEBUG
5988DEBUG_FLAGS = -g
5989else
5990DEBUG_FLAGS = -O2
5991endif
5992
5993VAR2 = after
5994"#;
5995 let makefile: Makefile = text.parse().unwrap();
5996
5997 let conditionals: Vec<_> = makefile.conditionals().collect();
5998 assert_eq!(conditionals.len(), 1);
5999 assert_eq!(conditionals[0].line(), 2);
6000 assert_eq!(conditionals[0].column(), 0);
6001 }
6002
6003 #[test]
6004 fn test_broken_conditional_endif_without_if() {
6005 let text = "VAR = value\nendif\n";
6007 let makefile = Makefile::read_relaxed(&mut text.as_bytes()).unwrap();
6008
6009 let vars: Vec<_> = makefile.variable_definitions().collect();
6011 assert_eq!(vars.len(), 1);
6012 assert_eq!(vars[0].line(), 0);
6013 }
6014
6015 #[test]
6016 fn test_broken_conditional_else_without_if() {
6017 let text = "VAR = value\nelse\nVAR2 = other\n";
6019 let makefile = Makefile::read_relaxed(&mut text.as_bytes()).unwrap();
6020
6021 let vars: Vec<_> = makefile.variable_definitions().collect();
6023 assert!(!vars.is_empty(), "Should parse at least the first variable");
6024 assert_eq!(vars[0].line(), 0);
6025 }
6026
6027 #[test]
6028 fn test_broken_conditional_missing_endif() {
6029 let text = r#"ifdef DEBUG
6031DEBUG_FLAGS = -g
6032VAR = value
6033"#;
6034 let makefile = Makefile::read_relaxed(&mut text.as_bytes()).unwrap();
6035
6036 assert!(makefile.code().contains("ifdef DEBUG"));
6038 }
6039
6040 #[test]
6041 fn test_multiple_conditionals_line_tracking() {
6042 let text = r#"ifdef A
6043VAR_A = a
6044endif
6045
6046ifdef B
6047VAR_B = b
6048endif
6049
6050ifdef C
6051VAR_C = c
6052endif
6053"#;
6054 let makefile: Makefile = text.parse().unwrap();
6055
6056 let conditionals: Vec<_> = makefile.conditionals().collect();
6057 assert_eq!(conditionals.len(), 3);
6058 assert_eq!(conditionals[0].line(), 0);
6059 assert_eq!(conditionals[1].line(), 4);
6060 assert_eq!(conditionals[2].line(), 8);
6061 }
6062
6063 #[test]
6064 fn test_conditional_with_multiple_else_ifeq() {
6065 let text = r#"ifeq ($(OS),Windows)
6066EXT = .exe
6067else ifeq ($(OS),Linux)
6068EXT = .bin
6069else
6070EXT = .out
6071endif
6072"#;
6073 let makefile = Makefile::read_relaxed(&mut text.as_bytes()).unwrap();
6074
6075 let conditionals: Vec<_> = makefile.conditionals().collect();
6076 assert_eq!(conditionals.len(), 1);
6077 assert_eq!(conditionals[0].line(), 0);
6078 assert_eq!(conditionals[0].column(), 0);
6079 }
6080
6081 #[test]
6082 fn test_conditional_types_line_tracking() {
6083 let text = r#"ifdef VAR1
6084A = 1
6085endif
6086
6087ifndef VAR2
6088B = 2
6089endif
6090
6091ifeq ($(X),y)
6092C = 3
6093endif
6094
6095ifneq ($(Y),n)
6096D = 4
6097endif
6098"#;
6099 let makefile: Makefile = text.parse().unwrap();
6100
6101 let conditionals: Vec<_> = makefile.conditionals().collect();
6102 assert_eq!(conditionals.len(), 4);
6103
6104 assert_eq!(conditionals[0].line(), 0); assert_eq!(
6106 conditionals[0].conditional_type(),
6107 Some("ifdef".to_string())
6108 );
6109
6110 assert_eq!(conditionals[1].line(), 4); assert_eq!(
6112 conditionals[1].conditional_type(),
6113 Some("ifndef".to_string())
6114 );
6115
6116 assert_eq!(conditionals[2].line(), 8); assert_eq!(conditionals[2].conditional_type(), Some("ifeq".to_string()));
6118
6119 assert_eq!(conditionals[3].line(), 12); assert_eq!(
6121 conditionals[3].conditional_type(),
6122 Some("ifneq".to_string())
6123 );
6124 }
6125
6126 #[test]
6127 fn test_conditional_in_rule_with_recipes() {
6128 let text = r#"test:
6129 echo "start"
6130ifdef VERBOSE
6131 echo "verbose mode"
6132endif
6133 echo "end"
6134"#;
6135 let makefile: Makefile = text.parse().unwrap();
6136
6137 let rules: Vec<_> = makefile.rules().collect();
6138 let conditionals: Vec<_> = makefile.conditionals().collect();
6139
6140 assert_eq!(rules.len(), 1);
6141 assert_eq!(rules[0].line(), 0);
6142 assert_eq!(conditionals.len(), 0);
6144 }
6145
6146 #[test]
6147 fn test_broken_conditional_double_else() {
6148 let text = r#"ifdef DEBUG
6150A = 1
6151else
6152B = 2
6153else
6154C = 3
6155endif
6156"#;
6157 let makefile = Makefile::read_relaxed(&mut text.as_bytes()).unwrap();
6158
6159 assert!(makefile.code().contains("ifdef DEBUG"));
6161 }
6162
6163 #[test]
6164 fn test_broken_conditional_mismatched_nesting() {
6165 let text = r#"ifdef A
6167VAR = value
6168endif
6169endif
6170"#;
6171 let makefile = Makefile::read_relaxed(&mut text.as_bytes()).unwrap();
6172
6173 let conditionals: Vec<_> = makefile.conditionals().collect();
6176 assert!(
6177 !conditionals.is_empty(),
6178 "Should parse at least the first conditional"
6179 );
6180 }
6181
6182 #[test]
6183 fn test_conditional_with_comment_line_tracking() {
6184 let text = r#"# This is a comment
6185ifdef DEBUG
6186# Another comment
6187CFLAGS = -g
6188endif
6189# Final comment
6190"#;
6191 let makefile: Makefile = text.parse().unwrap();
6192
6193 let conditionals: Vec<_> = makefile.conditionals().collect();
6194 assert_eq!(conditionals.len(), 1);
6195 assert_eq!(conditionals[0].line(), 1);
6196 assert_eq!(conditionals[0].column(), 0);
6197 }
6198
6199 #[test]
6200 fn test_conditional_after_variable_with_blank_lines() {
6201 let text = r#"VAR1 = value1
6202
6203
6204ifdef DEBUG
6205VAR2 = value2
6206endif
6207"#;
6208 let makefile: Makefile = text.parse().unwrap();
6209
6210 let vars: Vec<_> = makefile.variable_definitions().collect();
6211 let conditionals: Vec<_> = makefile.conditionals().collect();
6212
6213 assert_eq!(vars.len(), 2);
6215 assert_eq!(vars[0].line(), 0); assert_eq!(vars[1].line(), 4); assert_eq!(conditionals.len(), 1);
6219 assert_eq!(conditionals[0].line(), 3);
6220 }
6221
6222 #[test]
6223 fn test_empty_conditional_line_tracking() {
6224 let text = r#"ifdef DEBUG
6225endif
6226
6227ifndef RELEASE
6228endif
6229"#;
6230 let makefile: Makefile = text.parse().unwrap();
6231
6232 let conditionals: Vec<_> = makefile.conditionals().collect();
6233 assert_eq!(conditionals.len(), 2);
6234 assert_eq!(conditionals[0].line(), 0);
6235 assert_eq!(conditionals[1].line(), 3);
6236 }
6237
6238 #[test]
6239 fn test_recipe_line_tracking() {
6240 let text = r#"build:
6241 echo "Building..."
6242 gcc -o app main.c
6243 echo "Done"
6244
6245test:
6246 ./run-tests
6247"#;
6248 let makefile: Makefile = text.parse().unwrap();
6249
6250 let rule1 = makefile.rules().next().expect("Should have first rule");
6252 let recipes: Vec<_> = rule1.recipe_nodes().collect();
6253 assert_eq!(recipes.len(), 3);
6254
6255 assert_eq!(recipes[0].text(), "echo \"Building...\"");
6256 assert_eq!(recipes[0].line(), 1);
6257 assert_eq!(recipes[0].column(), 0);
6258
6259 assert_eq!(recipes[1].text(), "gcc -o app main.c");
6260 assert_eq!(recipes[1].line(), 2);
6261 assert_eq!(recipes[1].column(), 0);
6262
6263 assert_eq!(recipes[2].text(), "echo \"Done\"");
6264 assert_eq!(recipes[2].line(), 3);
6265 assert_eq!(recipes[2].column(), 0);
6266
6267 let rule2 = makefile.rules().nth(1).expect("Should have second rule");
6269 let recipes2: Vec<_> = rule2.recipe_nodes().collect();
6270 assert_eq!(recipes2.len(), 1);
6271
6272 assert_eq!(recipes2[0].text(), "./run-tests");
6273 assert_eq!(recipes2[0].line(), 6);
6274 assert_eq!(recipes2[0].column(), 0);
6275 }
6276
6277 #[test]
6278 fn test_recipe_with_variables_line_tracking() {
6279 let text = r#"install:
6280 mkdir -p $(DESTDIR)
6281 cp $(BINARY) $(DESTDIR)/
6282"#;
6283 let makefile: Makefile = text.parse().unwrap();
6284 let rule = makefile.rules().next().expect("Should have rule");
6285 let recipes: Vec<_> = rule.recipe_nodes().collect();
6286
6287 assert_eq!(recipes.len(), 2);
6288 assert_eq!(recipes[0].line(), 1);
6289 assert_eq!(recipes[1].line(), 2);
6290 }
6291
6292 #[test]
6293 fn test_recipe_text_no_leading_tab() {
6294 let text = "test:\n\techo hello\n\t\techo nested\n\t echo with spaces\n";
6296 let makefile: Makefile = text.parse().unwrap();
6297 let rule = makefile.rules().next().expect("Should have rule");
6298 let recipes: Vec<_> = rule.recipe_nodes().collect();
6299
6300 assert_eq!(recipes.len(), 3);
6301
6302 eprintln!("Recipe 0 syntax tree:\n{:#?}", recipes[0].syntax());
6304
6305 assert_eq!(recipes[0].text(), "echo hello");
6307
6308 eprintln!("Recipe 1 syntax tree:\n{:#?}", recipes[1].syntax());
6310 assert_eq!(recipes[1].text(), "\techo nested");
6311
6312 eprintln!("Recipe 2 syntax tree:\n{:#?}", recipes[2].syntax());
6314 assert_eq!(recipes[2].text(), " echo with spaces");
6315 }
6316
6317 #[test]
6318 fn test_recipe_parent() {
6319 let makefile: Makefile = "all: dep\n\techo hello\n".parse().unwrap();
6320 let rule = makefile.rules().next().unwrap();
6321 let recipe = rule.recipe_nodes().next().unwrap();
6322
6323 let parent = recipe.parent().expect("Recipe should have parent");
6324 assert_eq!(parent.targets().collect::<Vec<_>>(), vec!["all"]);
6325 assert_eq!(parent.prerequisites().collect::<Vec<_>>(), vec!["dep"]);
6326 }
6327
6328 #[test]
6329 fn test_recipe_is_silent_various_prefixes() {
6330 let makefile: Makefile = r#"test:
6331 @echo silent
6332 -echo ignore
6333 +echo always
6334 @-echo silent_ignore
6335 -@echo ignore_silent
6336 +@echo always_silent
6337 echo normal
6338"#
6339 .parse()
6340 .unwrap();
6341
6342 let rule = makefile.rules().next().unwrap();
6343 let recipes: Vec<_> = rule.recipe_nodes().collect();
6344
6345 assert_eq!(recipes.len(), 7);
6346 assert!(recipes[0].is_silent(), "@echo should be silent");
6347 assert!(!recipes[1].is_silent(), "-echo should not be silent");
6348 assert!(!recipes[2].is_silent(), "+echo should not be silent");
6349 assert!(recipes[3].is_silent(), "@-echo should be silent");
6350 assert!(recipes[4].is_silent(), "-@echo should be silent");
6351 assert!(recipes[5].is_silent(), "+@echo should be silent");
6352 assert!(!recipes[6].is_silent(), "echo should not be silent");
6353 }
6354
6355 #[test]
6356 fn test_recipe_is_ignore_errors_various_prefixes() {
6357 let makefile: Makefile = r#"test:
6358 @echo silent
6359 -echo ignore
6360 +echo always
6361 @-echo silent_ignore
6362 -@echo ignore_silent
6363 +-echo always_ignore
6364 echo normal
6365"#
6366 .parse()
6367 .unwrap();
6368
6369 let rule = makefile.rules().next().unwrap();
6370 let recipes: Vec<_> = rule.recipe_nodes().collect();
6371
6372 assert_eq!(recipes.len(), 7);
6373 assert!(
6374 !recipes[0].is_ignore_errors(),
6375 "@echo should not ignore errors"
6376 );
6377 assert!(recipes[1].is_ignore_errors(), "-echo should ignore errors");
6378 assert!(
6379 !recipes[2].is_ignore_errors(),
6380 "+echo should not ignore errors"
6381 );
6382 assert!(recipes[3].is_ignore_errors(), "@-echo should ignore errors");
6383 assert!(recipes[4].is_ignore_errors(), "-@echo should ignore errors");
6384 assert!(recipes[5].is_ignore_errors(), "+-echo should ignore errors");
6385 assert!(
6386 !recipes[6].is_ignore_errors(),
6387 "echo should not ignore errors"
6388 );
6389 }
6390
6391 #[test]
6392 fn test_recipe_set_prefix_add() {
6393 let makefile: Makefile = "all:\n\techo hello\n".parse().unwrap();
6394 let rule = makefile.rules().next().unwrap();
6395 let mut recipe = rule.recipe_nodes().next().unwrap();
6396
6397 recipe.set_prefix("@");
6398 assert_eq!(recipe.text(), "@echo hello");
6399 assert!(recipe.is_silent());
6400 }
6401
6402 #[test]
6403 fn test_recipe_set_prefix_change() {
6404 let makefile: Makefile = "all:\n\t@echo hello\n".parse().unwrap();
6405 let rule = makefile.rules().next().unwrap();
6406 let mut recipe = rule.recipe_nodes().next().unwrap();
6407
6408 recipe.set_prefix("-");
6409 assert_eq!(recipe.text(), "-echo hello");
6410 assert!(!recipe.is_silent());
6411 assert!(recipe.is_ignore_errors());
6412 }
6413
6414 #[test]
6415 fn test_recipe_set_prefix_remove() {
6416 let makefile: Makefile = "all:\n\t@-echo hello\n".parse().unwrap();
6417 let rule = makefile.rules().next().unwrap();
6418 let mut recipe = rule.recipe_nodes().next().unwrap();
6419
6420 recipe.set_prefix("");
6421 assert_eq!(recipe.text(), "echo hello");
6422 assert!(!recipe.is_silent());
6423 assert!(!recipe.is_ignore_errors());
6424 }
6425
6426 #[test]
6427 fn test_recipe_set_prefix_combinations() {
6428 let makefile: Makefile = "all:\n\techo hello\n".parse().unwrap();
6429 let rule = makefile.rules().next().unwrap();
6430 let mut recipe = rule.recipe_nodes().next().unwrap();
6431
6432 recipe.set_prefix("@-");
6433 assert_eq!(recipe.text(), "@-echo hello");
6434 assert!(recipe.is_silent());
6435 assert!(recipe.is_ignore_errors());
6436
6437 recipe.set_prefix("-@");
6438 assert_eq!(recipe.text(), "-@echo hello");
6439 assert!(recipe.is_silent());
6440 assert!(recipe.is_ignore_errors());
6441 }
6442
6443 #[test]
6444 fn test_recipe_replace_text_basic() {
6445 let makefile: Makefile = "all:\n\techo hello\n".parse().unwrap();
6446 let rule = makefile.rules().next().unwrap();
6447 let mut recipe = rule.recipe_nodes().next().unwrap();
6448
6449 recipe.replace_text("echo world");
6450 assert_eq!(recipe.text(), "echo world");
6451
6452 let rule = makefile.rules().next().unwrap();
6454 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["echo world"]);
6455 }
6456
6457 #[test]
6458 fn test_recipe_replace_text_with_prefix() {
6459 let makefile: Makefile = "all:\n\t@echo hello\n".parse().unwrap();
6460 let rule = makefile.rules().next().unwrap();
6461 let mut recipe = rule.recipe_nodes().next().unwrap();
6462
6463 recipe.replace_text("@echo goodbye");
6464 assert_eq!(recipe.text(), "@echo goodbye");
6465 assert!(recipe.is_silent());
6466 }
6467
6468 #[test]
6469 fn test_recipe_insert_before_single() {
6470 let makefile: Makefile = "all:\n\techo world\n".parse().unwrap();
6471 let rule = makefile.rules().next().unwrap();
6472 let recipe = rule.recipe_nodes().next().unwrap();
6473
6474 recipe.insert_before("echo hello");
6475
6476 let rule = makefile.rules().next().unwrap();
6477 let recipes: Vec<_> = rule.recipes().collect();
6478 assert_eq!(recipes, vec!["echo hello", "echo world"]);
6479 }
6480
6481 #[test]
6482 fn test_recipe_insert_before_multiple() {
6483 let makefile: Makefile = "all:\n\techo one\n\techo two\n\techo three\n"
6484 .parse()
6485 .unwrap();
6486 let rule = makefile.rules().next().unwrap();
6487 let recipes: Vec<_> = rule.recipe_nodes().collect();
6488
6489 recipes[1].insert_before("echo middle");
6491
6492 let rule = makefile.rules().next().unwrap();
6493 let new_recipes: Vec<_> = rule.recipes().collect();
6494 assert_eq!(
6495 new_recipes,
6496 vec!["echo one", "echo middle", "echo two", "echo three"]
6497 );
6498 }
6499
6500 #[test]
6501 fn test_recipe_insert_before_first() {
6502 let makefile: Makefile = "all:\n\techo one\n\techo two\n".parse().unwrap();
6503 let rule = makefile.rules().next().unwrap();
6504 let recipes: Vec<_> = rule.recipe_nodes().collect();
6505
6506 recipes[0].insert_before("echo zero");
6507
6508 let rule = makefile.rules().next().unwrap();
6509 let new_recipes: Vec<_> = rule.recipes().collect();
6510 assert_eq!(new_recipes, vec!["echo zero", "echo one", "echo two"]);
6511 }
6512
6513 #[test]
6514 fn test_recipe_insert_after_single() {
6515 let makefile: Makefile = "all:\n\techo hello\n".parse().unwrap();
6516 let rule = makefile.rules().next().unwrap();
6517 let recipe = rule.recipe_nodes().next().unwrap();
6518
6519 recipe.insert_after("echo world");
6520
6521 let rule = makefile.rules().next().unwrap();
6522 let recipes: Vec<_> = rule.recipes().collect();
6523 assert_eq!(recipes, vec!["echo hello", "echo world"]);
6524 }
6525
6526 #[test]
6527 fn test_recipe_insert_after_multiple() {
6528 let makefile: Makefile = "all:\n\techo one\n\techo two\n\techo three\n"
6529 .parse()
6530 .unwrap();
6531 let rule = makefile.rules().next().unwrap();
6532 let recipes: Vec<_> = rule.recipe_nodes().collect();
6533
6534 recipes[1].insert_after("echo middle");
6536
6537 let rule = makefile.rules().next().unwrap();
6538 let new_recipes: Vec<_> = rule.recipes().collect();
6539 assert_eq!(
6540 new_recipes,
6541 vec!["echo one", "echo two", "echo middle", "echo three"]
6542 );
6543 }
6544
6545 #[test]
6546 fn test_recipe_insert_after_last() {
6547 let makefile: Makefile = "all:\n\techo one\n\techo two\n".parse().unwrap();
6548 let rule = makefile.rules().next().unwrap();
6549 let recipes: Vec<_> = rule.recipe_nodes().collect();
6550
6551 recipes[1].insert_after("echo three");
6552
6553 let rule = makefile.rules().next().unwrap();
6554 let new_recipes: Vec<_> = rule.recipes().collect();
6555 assert_eq!(new_recipes, vec!["echo one", "echo two", "echo three"]);
6556 }
6557
6558 #[test]
6559 fn test_recipe_remove_single() {
6560 let makefile: Makefile = "all:\n\techo hello\n".parse().unwrap();
6561 let rule = makefile.rules().next().unwrap();
6562 let recipe = rule.recipe_nodes().next().unwrap();
6563
6564 recipe.remove();
6565
6566 let rule = makefile.rules().next().unwrap();
6567 assert_eq!(rule.recipes().count(), 0);
6568 }
6569
6570 #[test]
6571 fn test_recipe_remove_first() {
6572 let makefile: Makefile = "all:\n\techo one\n\techo two\n\techo three\n"
6573 .parse()
6574 .unwrap();
6575 let rule = makefile.rules().next().unwrap();
6576 let recipes: Vec<_> = rule.recipe_nodes().collect();
6577
6578 recipes[0].remove();
6579
6580 let rule = makefile.rules().next().unwrap();
6581 let new_recipes: Vec<_> = rule.recipes().collect();
6582 assert_eq!(new_recipes, vec!["echo two", "echo three"]);
6583 }
6584
6585 #[test]
6586 fn test_recipe_remove_middle() {
6587 let makefile: Makefile = "all:\n\techo one\n\techo two\n\techo three\n"
6588 .parse()
6589 .unwrap();
6590 let rule = makefile.rules().next().unwrap();
6591 let recipes: Vec<_> = rule.recipe_nodes().collect();
6592
6593 recipes[1].remove();
6594
6595 let rule = makefile.rules().next().unwrap();
6596 let new_recipes: Vec<_> = rule.recipes().collect();
6597 assert_eq!(new_recipes, vec!["echo one", "echo three"]);
6598 }
6599
6600 #[test]
6601 fn test_recipe_remove_last() {
6602 let makefile: Makefile = "all:\n\techo one\n\techo two\n\techo three\n"
6603 .parse()
6604 .unwrap();
6605 let rule = makefile.rules().next().unwrap();
6606 let recipes: Vec<_> = rule.recipe_nodes().collect();
6607
6608 recipes[2].remove();
6609
6610 let rule = makefile.rules().next().unwrap();
6611 let new_recipes: Vec<_> = rule.recipes().collect();
6612 assert_eq!(new_recipes, vec!["echo one", "echo two"]);
6613 }
6614
6615 #[test]
6616 fn test_recipe_multiple_operations() {
6617 let makefile: Makefile = "all:\n\techo one\n\techo two\n".parse().unwrap();
6618 let rule = makefile.rules().next().unwrap();
6619 let mut recipe = rule.recipe_nodes().next().unwrap();
6620
6621 recipe.replace_text("echo modified");
6623 assert_eq!(recipe.text(), "echo modified");
6624
6625 recipe.set_prefix("@");
6627 assert_eq!(recipe.text(), "@echo modified");
6628
6629 recipe.insert_after("echo three");
6631
6632 let rule = makefile.rules().next().unwrap();
6634 let recipes: Vec<_> = rule.recipes().collect();
6635 assert_eq!(recipes, vec!["@echo modified", "echo three", "echo two"]);
6636 }
6637}
6638
6639#[cfg(test)]
6640mod test_continuation {
6641 use super::*;
6642
6643 #[test]
6644 fn test_recipe_continuation_lines() {
6645 let makefile_content = r#"override_dh_autoreconf:
6646 set -x; [ -f binoculars-ng/src/Hkl/H5.hs.orig ] || \
6647 dpkg --compare-versions '$(HDF5_VERSION)' '<<' 1.12.0 || \
6648 sed -i.orig 's/H5L_info_t/H5L_info1_t/g;s/h5l_iterate/h5l_iterate1/g' binoculars-ng/src/Hkl/H5.hs
6649 dh_autoreconf
6650"#;
6651
6652 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6653 let rule = makefile.rules().next().unwrap();
6654
6655 let recipes: Vec<_> = rule.recipe_nodes().collect();
6656
6657 assert_eq!(recipes.len(), 2);
6659
6660 let expected_first = "set -x; [ -f binoculars-ng/src/Hkl/H5.hs.orig ] || \\\n dpkg --compare-versions '$(HDF5_VERSION)' '<<' 1.12.0 || \\\n sed -i.orig 's/H5L_info_t/H5L_info1_t/g;s/h5l_iterate/h5l_iterate1/g' binoculars-ng/src/Hkl/H5.hs";
6663 assert_eq!(recipes[0].text(), expected_first);
6664
6665 assert_eq!(recipes[1].text(), "dh_autoreconf");
6667 }
6668
6669 #[test]
6670 fn test_simple_continuation() {
6671 let makefile_content = "test:\n\techo hello && \\\n\t echo world\n";
6672
6673 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6674 let rule = makefile.rules().next().unwrap();
6675 let recipes: Vec<_> = rule.recipe_nodes().collect();
6676
6677 assert_eq!(recipes.len(), 1);
6678 assert_eq!(recipes[0].text(), "echo hello && \\\n echo world");
6679 }
6680
6681 #[test]
6682 fn test_multiple_continuations() {
6683 let makefile_content = "test:\n\techo line1 && \\\n\t echo line2 && \\\n\t echo line3 && \\\n\t echo line4\n";
6684
6685 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6686 let rule = makefile.rules().next().unwrap();
6687 let recipes: Vec<_> = rule.recipe_nodes().collect();
6688
6689 assert_eq!(recipes.len(), 1);
6690 assert_eq!(
6691 recipes[0].text(),
6692 "echo line1 && \\\n echo line2 && \\\n echo line3 && \\\n echo line4"
6693 );
6694 }
6695
6696 #[test]
6697 fn test_continuation_round_trip() {
6698 let makefile_content = "test:\n\techo hello && \\\n\t echo world\n\techo done\n";
6699
6700 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6701 let output = makefile.to_string();
6702
6703 assert_eq!(output, makefile_content);
6705 }
6706
6707 #[test]
6708 fn test_continuation_with_silent_prefix() {
6709 let makefile_content = "test:\n\t@echo hello && \\\n\t echo world\n";
6710
6711 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6712 let rule = makefile.rules().next().unwrap();
6713 let recipes: Vec<_> = rule.recipe_nodes().collect();
6714
6715 assert_eq!(recipes.len(), 1);
6716 assert_eq!(recipes[0].text(), "@echo hello && \\\n echo world");
6717 assert!(recipes[0].is_silent());
6718 }
6719
6720 #[test]
6721 fn test_mixed_continued_and_non_continued() {
6722 let makefile_content = r#"test:
6723 echo first
6724 echo second && \
6725 echo third
6726 echo fourth
6727"#;
6728
6729 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6730 let rule = makefile.rules().next().unwrap();
6731 let recipes: Vec<_> = rule.recipe_nodes().collect();
6732
6733 assert_eq!(recipes.len(), 3);
6734 assert_eq!(recipes[0].text(), "echo first");
6735 assert_eq!(recipes[1].text(), "echo second && \\\n echo third");
6736 assert_eq!(recipes[2].text(), "echo fourth");
6737 }
6738
6739 #[test]
6740 fn test_continuation_replace_command() {
6741 let makefile_content = "test:\n\techo hello && \\\n\t echo world\n\techo done\n";
6742
6743 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6744 let mut rule = makefile.rules().next().unwrap();
6745
6746 rule.replace_command(0, "echo replaced");
6748
6749 let recipes: Vec<_> = rule.recipe_nodes().collect();
6750 assert_eq!(recipes.len(), 2);
6751 assert_eq!(recipes[0].text(), "echo replaced");
6752 assert_eq!(recipes[1].text(), "echo done");
6753 }
6754
6755 #[test]
6756 fn test_continuation_count() {
6757 let makefile_content = "test:\n\techo hello && \\\n\t echo world\n\techo done\n";
6758
6759 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6760 let rule = makefile.rules().next().unwrap();
6761
6762 assert_eq!(rule.recipe_count(), 2);
6764 assert_eq!(rule.recipe_nodes().count(), 2);
6765
6766 let recipes_list: Vec<_> = rule.recipes().collect();
6768 assert_eq!(
6769 recipes_list,
6770 vec!["echo hello && \\\n echo world", "echo done"]
6771 );
6772 }
6773
6774 #[test]
6775 fn test_backslash_in_middle_of_line() {
6776 let makefile_content = "test:\n\techo hello\\nworld\n\techo done\n";
6778
6779 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6780 let rule = makefile.rules().next().unwrap();
6781 let recipes: Vec<_> = rule.recipe_nodes().collect();
6782
6783 assert_eq!(recipes.len(), 2);
6784 assert_eq!(recipes[0].text(), "echo hello\\nworld");
6785 assert_eq!(recipes[1].text(), "echo done");
6786 }
6787
6788 #[test]
6789 fn test_shell_for_loop_with_continuation() {
6790 let makefile_content = r#"override_dh_installman:
6794 for i in foo bar; do \
6795 pod2man --section=1 $$i ; \
6796 done
6797"#;
6798
6799 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6800 let rule = makefile.rules().next().unwrap();
6801
6802 let recipes: Vec<_> = rule.recipe_nodes().collect();
6804 assert_eq!(recipes.len(), 1);
6805
6806 let recipe_text = recipes[0].text();
6808 let expected_recipe = "for i in foo bar; do \\\n\tpod2man --section=1 $$i ; \\\ndone";
6809 assert_eq!(recipe_text, expected_recipe);
6810
6811 let output = makefile.to_string();
6813 assert_eq!(output, makefile_content);
6814 }
6815
6816 #[test]
6817 fn test_shell_for_loop_remove_command() {
6818 let makefile_content = r#"override_dh_installman:
6821 for i in foo bar; do \
6822 pod2man --section=1 $$i ; \
6823 done
6824 echo "Done with man pages"
6825"#;
6826
6827 let makefile = Makefile::read_relaxed(makefile_content.as_bytes()).unwrap();
6828 let mut rule = makefile.rules().next().unwrap();
6829
6830 assert_eq!(rule.recipe_count(), 2);
6832
6833 rule.remove_command(1);
6835
6836 let recipes: Vec<_> = rule.recipe_nodes().collect();
6838 assert_eq!(recipes.len(), 1);
6839
6840 let output = makefile.to_string();
6842 let expected_output = r#"override_dh_installman:
6843 for i in foo bar; do \
6844 pod2man --section=1 $$i ; \
6845 done
6846"#;
6847 assert_eq!(output, expected_output);
6848 }
6849}