1use crate::lex::lex;
2use crate::SyntaxKind;
3use crate::SyntaxKind::*;
4use rowan::ast::AstNode;
5use std::str::FromStr;
6
7#[derive(Debug)]
8pub enum Error {
10 Io(std::io::Error),
12
13 Parse(ParseError),
15}
16
17impl std::fmt::Display for Error {
18 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19 match &self {
20 Error::Io(e) => write!(f, "IO error: {}", e),
21 Error::Parse(e) => write!(f, "Parse error: {}", e),
22 }
23 }
24}
25
26impl From<std::io::Error> for Error {
27 fn from(e: std::io::Error) -> Self {
28 Error::Io(e)
29 }
30}
31
32impl std::error::Error for Error {}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct ParseError {
37 pub errors: Vec<ErrorInfo>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct ErrorInfo {
44 pub message: String,
46 pub line: usize,
48 pub context: String,
50}
51
52impl std::fmt::Display for ParseError {
53 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54 for err in &self.errors {
55 writeln!(f, "Error at line {}: {}", err.line, err.message)?;
56 writeln!(f, "{}| {}", err.line, err.context)?;
57 }
58 Ok(())
59 }
60}
61
62impl std::error::Error for ParseError {}
63
64impl From<ParseError> for Error {
65 fn from(e: ParseError) -> Self {
66 Error::Parse(e)
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, 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) -> Parse {
104 struct Parser {
105 tokens: Vec<(SyntaxKind, String)>,
108 builder: GreenNodeBuilder<'static>,
110 errors: Vec<ErrorInfo>,
113 original_text: String,
115 }
116
117 impl Parser {
118 fn error(&mut self, msg: String) {
119 self.builder.start_node(ERROR.into());
120
121 let (line, context) = if self.current() == Some(INDENT) {
122 let lines: Vec<&str> = self.original_text.lines().collect();
124 let tab_line = lines
125 .iter()
126 .enumerate()
127 .find(|(_, line)| line.starts_with('\t'))
128 .map(|(i, _)| i + 1)
129 .unwrap_or(1);
130
131 let next_line = tab_line + 1;
133 if next_line <= lines.len() {
134 (next_line, lines[next_line - 1].to_string())
135 } else {
136 (tab_line, lines[tab_line - 1].to_string())
137 }
138 } else {
139 let line = self.get_line_number_for_position(self.tokens.len());
140 (line, self.get_context_for_line(line))
141 };
142
143 let message = if self.current() == Some(INDENT) && !msg.contains("indented") {
144 if !self.tokens.is_empty() && self.tokens[self.tokens.len() - 1].0 == IDENTIFIER {
145 "expected ':'".to_string()
146 } else {
147 "indented line not part of a rule".to_string()
148 }
149 } else {
150 msg
151 };
152
153 self.errors.push(ErrorInfo {
154 message,
155 line,
156 context,
157 });
158
159 if self.current().is_some() {
160 self.bump();
161 }
162 self.builder.finish_node();
163 }
164
165 fn get_line_number_for_position(&self, position: usize) -> usize {
166 if position >= self.tokens.len() {
167 return self.original_text.matches('\n').count() + 1;
168 }
169
170 self.tokens[0..position]
172 .iter()
173 .filter(|(kind, _)| *kind == NEWLINE)
174 .count()
175 + 1
176 }
177
178 fn get_context_for_line(&self, line_number: usize) -> String {
179 self.original_text
180 .lines()
181 .nth(line_number - 1)
182 .unwrap_or("")
183 .to_string()
184 }
185
186 fn parse_recipe_line(&mut self) {
187 self.builder.start_node(RECIPE.into());
188
189 if self.current() != Some(INDENT) {
191 self.error("recipe line must start with a tab".to_string());
192 self.builder.finish_node();
193 return;
194 }
195 self.bump();
196
197 while self.current().is_some() && self.current() != Some(NEWLINE) {
200 self.bump();
201 }
202
203 if self.current() == Some(NEWLINE) {
205 self.bump();
206 }
207
208 self.builder.finish_node();
209 }
210
211 fn parse_rule_target(&mut self) -> bool {
212 match self.current() {
213 Some(IDENTIFIER) => {
214 if self.is_archive_member() {
216 self.parse_archive_member();
217 } else {
218 self.bump();
219 }
220 true
221 }
222 Some(DOLLAR) => {
223 self.parse_variable_reference();
224 true
225 }
226 _ => {
227 self.error("expected rule target".to_string());
228 false
229 }
230 }
231 }
232
233 fn is_archive_member(&self) -> bool {
234 if self.tokens.len() < 2 {
237 return false;
238 }
239
240 let current_is_identifier = self.current() == Some(IDENTIFIER);
242 let next_is_lparen =
243 self.tokens.len() > 1 && self.tokens[self.tokens.len() - 2].0 == LPAREN;
244
245 current_is_identifier && next_is_lparen
246 }
247
248 fn parse_archive_member(&mut self) {
249 if self.current() == Some(IDENTIFIER) {
260 self.bump();
261 }
262
263 if self.current() == Some(LPAREN) {
265 self.bump();
266
267 self.builder.start_node(ARCHIVE_MEMBERS.into());
269
270 while self.current().is_some() && self.current() != Some(RPAREN) {
272 match self.current() {
273 Some(IDENTIFIER) | Some(TEXT) => {
274 self.builder.start_node(ARCHIVE_MEMBER.into());
276 self.bump();
277 self.builder.finish_node();
278 }
279 Some(WHITESPACE) => self.bump(),
280 Some(DOLLAR) => {
281 self.builder.start_node(ARCHIVE_MEMBER.into());
283 self.parse_variable_reference();
284 self.builder.finish_node();
285 }
286 _ => break,
287 }
288 }
289
290 self.builder.finish_node();
292
293 if self.current() == Some(RPAREN) {
295 self.bump();
296 } else {
297 self.error("expected ')' to close archive member".to_string());
298 }
299 }
300 }
301
302 fn parse_rule_dependencies(&mut self) {
303 self.builder.start_node(PREREQUISITES.into());
304
305 while self.current().is_some() && self.current() != Some(NEWLINE) {
306 match self.current() {
307 Some(WHITESPACE) => {
308 self.bump(); }
310 Some(IDENTIFIER) => {
311 self.builder.start_node(PREREQUISITE.into());
313
314 if self.is_archive_member() {
315 self.parse_archive_member();
316 } else {
317 self.bump(); }
319
320 self.builder.finish_node(); }
322 Some(DOLLAR) => {
323 self.builder.start_node(PREREQUISITE.into());
325
326 self.bump(); if self.current() == Some(LPAREN) {
330 self.bump(); let mut paren_count = 1;
332
333 while self.current().is_some() && paren_count > 0 {
334 if self.current() == Some(LPAREN) {
335 paren_count += 1;
336 } else if self.current() == Some(RPAREN) {
337 paren_count -= 1;
338 }
339 self.bump();
340 }
341 } else {
342 if self.current().is_some() {
344 self.bump();
345 }
346 }
347
348 self.builder.finish_node(); }
350 _ => {
351 self.bump();
353 }
354 }
355 }
356
357 self.builder.finish_node(); }
359
360 fn parse_rule_recipes(&mut self) {
361 loop {
362 match self.current() {
363 Some(INDENT) => {
364 self.parse_recipe_line();
365 }
366 Some(NEWLINE) => {
367 self.bump();
368 break;
369 }
370 _ => break,
371 }
372 }
373 }
374
375 fn find_and_consume_colon(&mut self) -> bool {
376 self.skip_ws();
378
379 if self.current() == Some(OPERATOR) && self.tokens.last().unwrap().1 == ":" {
381 self.bump();
382 return true;
383 }
384
385 let has_colon = self
387 .tokens
388 .iter()
389 .rev()
390 .any(|(kind, text)| *kind == OPERATOR && text == ":");
391
392 if has_colon {
393 while self.current().is_some() {
395 if self.current() == Some(OPERATOR)
396 && self.tokens.last().map(|(_, text)| text.as_str()) == Some(":")
397 {
398 self.bump();
399 return true;
400 }
401 self.bump();
402 }
403 }
404
405 self.error("expected ':'".to_string());
406 false
407 }
408
409 fn parse_rule(&mut self) {
410 self.builder.start_node(RULE.into());
411
412 self.skip_ws();
414 let has_target = self.parse_rule_target();
415
416 let has_colon = if has_target {
418 self.find_and_consume_colon()
419 } else {
420 false
421 };
422
423 if has_target && has_colon {
425 self.skip_ws();
426 self.parse_rule_dependencies();
427 self.expect_eol();
428
429 self.parse_rule_recipes();
431 }
432
433 self.builder.finish_node();
434 }
435
436 fn parse_comment(&mut self) {
437 if self.current() == Some(COMMENT) {
438 self.bump(); if self.current() == Some(NEWLINE) {
442 self.bump(); } else if self.current() == Some(WHITESPACE) {
444 self.skip_ws();
446 if self.current() == Some(NEWLINE) {
447 self.bump();
448 }
449 }
450 } else {
452 self.error("expected comment".to_string());
453 }
454 }
455
456 fn parse_assignment(&mut self) {
457 self.builder.start_node(VARIABLE.into());
458
459 self.skip_ws();
461 if self.current() == Some(IDENTIFIER) && self.tokens.last().unwrap().1 == "export" {
462 self.bump();
463 self.skip_ws();
464 }
465
466 match self.current() {
468 Some(IDENTIFIER) => self.bump(),
469 Some(DOLLAR) => self.parse_variable_reference(),
470 _ => {
471 self.error("expected variable name".to_string());
472 self.builder.finish_node();
473 return;
474 }
475 }
476
477 self.skip_ws();
479 match self.current() {
480 Some(OPERATOR) => {
481 let op = &self.tokens.last().unwrap().1;
482 if ["=", ":=", "::=", ":::=", "+=", "?=", "!="].contains(&op.as_str()) {
483 self.bump();
484 self.skip_ws();
485
486 self.builder.start_node(EXPR.into());
488 while self.current().is_some() && self.current() != Some(NEWLINE) {
489 self.bump();
490 }
491 self.builder.finish_node();
492
493 if self.current() == Some(NEWLINE) {
495 self.bump();
496 } else {
497 self.error("expected newline after variable value".to_string());
498 }
499 } else {
500 self.error(format!("invalid assignment operator: {}", op));
501 }
502 }
503 _ => self.error("expected assignment operator".to_string()),
504 }
505
506 self.builder.finish_node();
507 }
508
509 fn parse_variable_reference(&mut self) {
510 self.builder.start_node(EXPR.into());
511 self.bump(); if self.current() == Some(LPAREN) {
514 self.bump(); let mut is_function = false;
518
519 if self.current() == Some(IDENTIFIER) {
520 let function_name = &self.tokens.last().unwrap().1;
521 let known_functions = [
523 "shell", "wildcard", "call", "eval", "file", "abspath", "dir",
524 ];
525 if known_functions.contains(&function_name.as_str()) {
526 is_function = true;
527 }
528 }
529
530 if is_function {
531 self.bump();
533
534 self.consume_balanced_parens(1);
536 } else {
537 self.parse_parenthesized_expr_internal(true);
539 }
540 } else {
541 self.error("expected ( after $ in variable reference".to_string());
542 }
543
544 self.builder.finish_node();
545 }
546
547 fn parse_parenthesized_expr(&mut self) {
549 self.builder.start_node(EXPR.into());
550
551 if self.current() != Some(LPAREN) {
552 self.error("expected opening parenthesis".to_string());
553 self.builder.finish_node();
554 return;
555 }
556
557 self.bump(); self.parse_parenthesized_expr_internal(false);
559 self.builder.finish_node();
560 }
561
562 fn parse_parenthesized_expr_internal(&mut self, is_variable_ref: bool) {
564 let mut paren_count = 1;
565
566 while paren_count > 0 && self.current().is_some() {
567 match self.current() {
568 Some(LPAREN) => {
569 paren_count += 1;
570 self.bump();
571 self.builder.start_node(EXPR.into());
573 }
574 Some(RPAREN) => {
575 paren_count -= 1;
576 self.bump();
577 if paren_count > 0 {
578 self.builder.finish_node();
579 }
580 }
581 Some(QUOTE) => {
582 self.parse_quoted_string();
584 }
585 Some(DOLLAR) => {
586 self.parse_variable_reference();
588 }
589 Some(_) => self.bump(),
590 None => {
591 self.error(if is_variable_ref {
592 "unclosed variable reference".to_string()
593 } else {
594 "unclosed parenthesis".to_string()
595 });
596 break;
597 }
598 }
599 }
600
601 if !is_variable_ref {
602 self.skip_ws();
603 self.expect_eol();
604 }
605 }
606
607 fn parse_quoted_string(&mut self) {
609 self.bump(); while !self.is_at_eof() && self.current() != Some(QUOTE) {
611 self.bump();
612 }
613 if self.current() == Some(QUOTE) {
614 self.bump();
615 }
616 }
617
618 fn parse_conditional_keyword(&mut self) -> Option<String> {
619 if self.current() != Some(IDENTIFIER) {
620 self.error(
621 "expected conditional keyword (ifdef, ifndef, ifeq, or ifneq)".to_string(),
622 );
623 return None;
624 }
625
626 let token = self.tokens.last().unwrap().1.clone();
627 if !["ifdef", "ifndef", "ifeq", "ifneq"].contains(&token.as_str()) {
628 self.error(format!("unknown conditional directive: {}", token));
629 return None;
630 }
631
632 self.bump();
633 Some(token)
634 }
635
636 fn parse_simple_condition(&mut self) {
637 self.builder.start_node(EXPR.into());
638
639 self.skip_ws();
641
642 let mut found_var = false;
644
645 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
646 match self.current() {
647 Some(WHITESPACE) => self.skip_ws(),
648 Some(DOLLAR) => {
649 found_var = true;
650 self.parse_variable_reference();
651 }
652 Some(_) => {
653 found_var = true;
655 self.bump();
656 }
657 None => break,
658 }
659 }
660
661 if !found_var {
662 self.error("expected condition after conditional directive".to_string());
664 }
665
666 self.builder.finish_node();
667
668 if self.current() == Some(NEWLINE) {
670 self.bump();
671 } else if !self.is_at_eof() {
672 self.skip_until_newline();
673 }
674 }
675
676 fn is_conditional_directive(&self, token: &str) -> bool {
678 token == "ifdef"
679 || token == "ifndef"
680 || token == "ifeq"
681 || token == "ifneq"
682 || token == "else"
683 || token == "elif"
684 || token == "endif"
685 }
686
687 fn handle_conditional_token(&mut self, token: &str, depth: &mut usize) -> bool {
689 match token {
690 "ifdef" | "ifndef" | "ifeq" | "ifneq" => {
691 *depth += 1;
692 self.parse_conditional();
693 true
694 }
695 "else" | "elif" => {
696 if *depth == 0 {
698 self.error(format!("{} without matching if", token));
699 self.bump();
701 false
702 } else {
703 self.bump();
705
706 if token == "elif" {
708 self.skip_ws();
709
710 if self.current() == Some(IDENTIFIER) {
712 let next_token = &self.tokens.last().unwrap().1;
713 if next_token == "ifeq"
714 || next_token == "ifdef"
715 || next_token == "ifndef"
716 || next_token == "ifneq"
717 {
718 match next_token.as_str() {
720 "ifdef" | "ifndef" => {
721 self.bump(); self.skip_ws();
723 self.parse_simple_condition();
724 }
725 "ifeq" | "ifneq" => {
726 self.bump(); self.skip_ws();
728 self.parse_parenthesized_expr();
729 }
730 _ => unreachable!(),
731 }
732 } else {
733 self.builder.start_node(EXPR.into());
735 while self.current().is_some()
737 && self.current() != Some(NEWLINE)
738 {
739 self.bump();
740 }
741 self.builder.finish_node();
742 if self.current() == Some(NEWLINE) {
743 self.bump();
744 }
745 }
746 } else {
747 self.builder.start_node(EXPR.into());
749 while self.current().is_some() && self.current() != Some(NEWLINE) {
751 self.bump();
752 }
753 self.builder.finish_node();
754 if self.current() == Some(NEWLINE) {
755 self.bump();
756 }
757 }
758 } else {
759 self.expect_eol();
761 }
762 true
763 }
764 }
765 "endif" => {
766 if *depth == 0 {
768 self.error("endif without matching if".to_string());
769 self.bump();
771 false
772 } else {
773 *depth -= 1;
774 self.bump();
776
777 self.skip_ws();
779
780 if self.current() == Some(COMMENT) {
785 self.parse_comment();
786 } else if self.current() == Some(NEWLINE) {
787 self.bump();
788 } else if self.current() == Some(WHITESPACE) {
789 self.skip_ws();
791 if self.current() == Some(NEWLINE) {
792 self.bump();
793 }
794 } else if !self.is_at_eof() {
796 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
799 self.bump();
800 }
801 if self.current() == Some(NEWLINE) {
802 self.bump();
803 }
804 }
805 true
808 }
809 }
810 _ => false,
811 }
812 }
813
814 fn parse_conditional(&mut self) {
815 self.builder.start_node(CONDITIONAL.into());
816
817 let Some(token) = self.parse_conditional_keyword() else {
819 self.skip_until_newline();
820 self.builder.finish_node();
821 return;
822 };
823
824 self.skip_ws();
826
827 match token.as_str() {
829 "ifdef" | "ifndef" => {
830 self.parse_simple_condition();
831 }
832 "ifeq" | "ifneq" => {
833 self.parse_parenthesized_expr();
834 }
835 _ => unreachable!("Invalid conditional token"),
836 }
837
838 self.skip_ws();
840 if self.current() == Some(COMMENT) {
841 self.parse_comment();
842 } else {
843 self.expect_eol();
844 }
845
846 let mut depth = 1;
848
849 let mut position_count = std::collections::HashMap::<usize, usize>::new();
851 let max_repetitions = 15; while depth > 0 && !self.is_at_eof() {
854 let current_pos = self.tokens.len();
856 *position_count.entry(current_pos).or_insert(0) += 1;
857
858 if position_count.get(¤t_pos).unwrap() > &max_repetitions {
861 break;
864 }
865
866 match self.current() {
867 None => {
868 self.error("unterminated conditional (missing endif)".to_string());
869 break;
870 }
871 Some(IDENTIFIER) => {
872 let token = self.tokens.last().unwrap().1.clone();
873 if !self.handle_conditional_token(&token, &mut depth) {
874 if token == "include" || token == "-include" || token == "sinclude" {
875 self.parse_include();
876 } else {
877 self.parse_normal_content();
878 }
879 }
880 }
881 Some(INDENT) => self.parse_recipe_line(),
882 Some(WHITESPACE) => self.bump(),
883 Some(COMMENT) => self.parse_comment(),
884 Some(NEWLINE) => self.bump(),
885 Some(DOLLAR) => self.parse_normal_content(),
886 Some(QUOTE) => self.parse_quoted_string(),
887 Some(_) => {
888 self.bump();
890 }
891 }
892 }
893
894 self.builder.finish_node();
895 }
896
897 fn parse_normal_content(&mut self) {
899 self.skip_ws();
901
902 if self.is_assignment_line() {
904 self.parse_assignment();
905 } else {
906 self.parse_rule();
908 }
909 }
910
911 fn parse_include(&mut self) {
912 self.builder.start_node(INCLUDE.into());
913
914 if self.current() != Some(IDENTIFIER)
916 || (!["include", "-include", "sinclude"]
917 .contains(&self.tokens.last().unwrap().1.as_str()))
918 {
919 self.error("expected include directive".to_string());
920 self.builder.finish_node();
921 return;
922 }
923 self.bump();
924 self.skip_ws();
925
926 self.builder.start_node(EXPR.into());
928 let mut found_path = false;
929
930 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
931 match self.current() {
932 Some(WHITESPACE) => self.skip_ws(),
933 Some(DOLLAR) => {
934 found_path = true;
935 self.parse_variable_reference();
936 }
937 Some(_) => {
938 found_path = true;
940 self.bump();
941 }
942 None => break,
943 }
944 }
945
946 if !found_path {
947 self.error("expected file path after include".to_string());
948 }
949
950 self.builder.finish_node();
951
952 if self.current() == Some(NEWLINE) {
954 self.bump();
955 } else if !self.is_at_eof() {
956 self.error("expected newline after include".to_string());
957 self.skip_until_newline();
958 }
959
960 self.builder.finish_node();
961 }
962
963 fn parse_identifier_token(&mut self) -> bool {
964 let token = &self.tokens.last().unwrap().1;
965
966 if token.starts_with("%") {
968 self.parse_rule();
969 return true;
970 }
971
972 if token.starts_with("if") {
973 self.parse_conditional();
974 return true;
975 }
976
977 if token == "include" || token == "-include" || token == "sinclude" {
978 self.parse_include();
979 return true;
980 }
981
982 self.parse_normal_content();
984 true
985 }
986
987 fn parse_token(&mut self) -> bool {
988 match self.current() {
989 None => false,
990 Some(IDENTIFIER) => {
991 let token = &self.tokens.last().unwrap().1;
992 if self.is_conditional_directive(token) {
993 self.parse_conditional();
994 true
995 } else {
996 self.parse_identifier_token()
997 }
998 }
999 Some(DOLLAR) => {
1000 self.parse_normal_content();
1001 true
1002 }
1003 Some(NEWLINE) => {
1004 self.bump();
1005 true
1006 }
1007 Some(COMMENT) => {
1008 self.parse_comment();
1009 true
1010 }
1011 Some(WHITESPACE) => {
1012 if self.is_end_of_file_or_newline_after_whitespace() {
1014 self.skip_ws();
1017 return true;
1018 }
1019
1020 let look_ahead_pos = self.tokens.len().saturating_sub(1);
1023 let mut is_documentation_or_help = false;
1024
1025 if look_ahead_pos > 0 {
1026 let next_token = &self.tokens[look_ahead_pos - 1];
1027 if next_token.0 == IDENTIFIER
1030 || next_token.0 == COMMENT
1031 || next_token.0 == TEXT
1032 {
1033 is_documentation_or_help = true;
1034 }
1035 }
1036
1037 if is_documentation_or_help {
1038 self.skip_ws();
1041 while self.current().is_some() && self.current() != Some(NEWLINE) {
1042 self.bump();
1043 }
1044 if self.current() == Some(NEWLINE) {
1045 self.bump();
1046 }
1047 } else {
1048 self.skip_ws();
1049 }
1050 true
1051 }
1052 Some(INDENT) => {
1053 #[cfg(test)]
1058 {
1059 let is_in_test = self.original_text.lines().count() < 20;
1062 let tokens_as_str = self
1063 .tokens
1064 .iter()
1065 .rev()
1066 .take(10)
1067 .map(|(_kind, text)| text.as_str())
1068 .collect::<Vec<_>>()
1069 .join(" ");
1070
1071 let in_conditional = tokens_as_str.contains("ifdef")
1073 || tokens_as_str.contains("ifndef")
1074 || tokens_as_str.contains("ifeq")
1075 || tokens_as_str.contains("ifneq")
1076 || tokens_as_str.contains("else")
1077 || tokens_as_str.contains("endif");
1078
1079 if is_in_test && !in_conditional {
1080 self.error("indented line not part of a rule".to_string());
1081 }
1082 }
1083
1084 self.bump();
1086
1087 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1089 self.bump();
1090 }
1091 if self.current() == Some(NEWLINE) {
1092 self.bump();
1093 }
1094 true
1095 }
1096 Some(kind) => {
1097 self.error(format!("unexpected token {:?}", kind));
1098 self.bump();
1099 true
1100 }
1101 }
1102 }
1103
1104 fn parse(mut self) -> Parse {
1105 self.builder.start_node(ROOT.into());
1106
1107 while self.parse_token() {}
1108
1109 self.builder.finish_node();
1110
1111 Parse {
1112 green_node: self.builder.finish(),
1113 errors: self.errors,
1114 }
1115 }
1116
1117 fn is_assignment_line(&mut self) -> bool {
1119 let assignment_ops = ["=", ":=", "::=", ":::=", "+=", "?=", "!="];
1120 let mut pos = self.tokens.len().saturating_sub(1);
1121 let mut seen_identifier = false;
1122 let mut seen_export = false;
1123
1124 while pos > 0 {
1125 let (kind, text) = &self.tokens[pos];
1126
1127 match kind {
1128 NEWLINE => break,
1129 IDENTIFIER if text == "export" => seen_export = true,
1130 IDENTIFIER if !seen_identifier => seen_identifier = true,
1131 OPERATOR if assignment_ops.contains(&text.as_str()) => {
1132 return seen_identifier || seen_export
1133 }
1134 OPERATOR if text == ":" => return false, WHITESPACE => (),
1136 _ if seen_export => return true, _ => return false,
1138 }
1139 pos = pos.saturating_sub(1);
1140 }
1141 false
1142 }
1143
1144 fn bump(&mut self) {
1146 let (kind, text) = self.tokens.pop().unwrap();
1147 self.builder.token(kind.into(), text.as_str());
1148 }
1149 fn current(&self) -> Option<SyntaxKind> {
1151 self.tokens.last().map(|(kind, _)| *kind)
1152 }
1153
1154 fn expect_eol(&mut self) {
1155 self.skip_ws();
1157
1158 match self.current() {
1159 Some(NEWLINE) => {
1160 self.bump();
1161 }
1162 None => {
1163 }
1165 n => {
1166 self.error(format!("expected newline, got {:?}", n));
1167 self.skip_until_newline();
1169 }
1170 }
1171 }
1172
1173 fn is_at_eof(&self) -> bool {
1175 self.current().is_none()
1176 }
1177
1178 fn is_at_eof_or_only_whitespace(&self) -> bool {
1180 if self.is_at_eof() {
1181 return true;
1182 }
1183
1184 self.tokens
1186 .iter()
1187 .rev()
1188 .all(|(kind, _)| matches!(*kind, WHITESPACE | NEWLINE))
1189 }
1190
1191 fn skip_ws(&mut self) {
1192 while self.current() == Some(WHITESPACE) {
1193 self.bump()
1194 }
1195 }
1196
1197 fn skip_until_newline(&mut self) {
1198 while !self.is_at_eof() && self.current() != Some(NEWLINE) {
1199 self.bump();
1200 }
1201 if self.current() == Some(NEWLINE) {
1202 self.bump();
1203 }
1204 }
1205
1206 fn consume_balanced_parens(&mut self, start_paren_count: usize) -> usize {
1208 let mut paren_count = start_paren_count;
1209
1210 while paren_count > 0 && self.current().is_some() {
1211 match self.current() {
1212 Some(LPAREN) => {
1213 paren_count += 1;
1214 self.bump();
1215 }
1216 Some(RPAREN) => {
1217 paren_count -= 1;
1218 self.bump();
1219 if paren_count == 0 {
1220 break;
1221 }
1222 }
1223 Some(DOLLAR) => {
1224 self.parse_variable_reference();
1226 }
1227 Some(_) => self.bump(),
1228 None => {
1229 self.error("unclosed parenthesis".to_string());
1230 break;
1231 }
1232 }
1233 }
1234
1235 paren_count
1236 }
1237
1238 fn is_end_of_file_or_newline_after_whitespace(&self) -> bool {
1240 if self.is_at_eof_or_only_whitespace() {
1242 return true;
1243 }
1244
1245 if self.tokens.len() <= 1 {
1247 return true;
1248 }
1249
1250 false
1251 }
1252
1253 #[cfg(test)]
1255 fn is_in_test_environment(&self) -> bool {
1256 self.original_text.lines().count() < 20
1259 }
1260 }
1261
1262 let mut tokens = lex(text);
1263 tokens.reverse();
1264 Parser {
1265 tokens,
1266 builder: GreenNodeBuilder::new(),
1267 errors: Vec::new(),
1268 original_text: text.to_string(),
1269 }
1270 .parse()
1271}
1272
1273type SyntaxNode = rowan::SyntaxNode<Lang>;
1279#[allow(unused)]
1280type SyntaxToken = rowan::SyntaxToken<Lang>;
1281#[allow(unused)]
1282type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
1283
1284impl Parse {
1285 fn syntax(&self) -> SyntaxNode {
1286 SyntaxNode::new_root_mut(self.green_node.clone())
1287 }
1288
1289 fn root(&self) -> Makefile {
1290 Makefile::cast(self.syntax()).unwrap()
1291 }
1292}
1293
1294macro_rules! ast_node {
1295 ($ast:ident, $kind:ident) => {
1296 #[derive(PartialEq, Eq, Hash)]
1297 #[repr(transparent)]
1298 pub struct $ast(SyntaxNode);
1300
1301 impl AstNode for $ast {
1302 type Language = Lang;
1303
1304 fn can_cast(kind: SyntaxKind) -> bool {
1305 kind == $kind
1306 }
1307
1308 fn cast(syntax: SyntaxNode) -> Option<Self> {
1309 if Self::can_cast(syntax.kind()) {
1310 Some(Self(syntax))
1311 } else {
1312 None
1313 }
1314 }
1315
1316 fn syntax(&self) -> &SyntaxNode {
1317 &self.0
1318 }
1319 }
1320
1321 impl core::fmt::Display for $ast {
1322 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
1323 write!(f, "{}", self.0.text())
1324 }
1325 }
1326 };
1327}
1328
1329ast_node!(Makefile, ROOT);
1330ast_node!(Rule, RULE);
1331ast_node!(Identifier, IDENTIFIER);
1332ast_node!(VariableDefinition, VARIABLE);
1333ast_node!(Include, INCLUDE);
1334ast_node!(ArchiveMembers, ARCHIVE_MEMBERS);
1335ast_node!(ArchiveMember, ARCHIVE_MEMBER);
1336
1337impl ArchiveMembers {
1338 pub fn archive_name(&self) -> Option<String> {
1340 for element in self.syntax().children_with_tokens() {
1342 if let Some(token) = element.as_token() {
1343 if token.kind() == IDENTIFIER {
1344 return Some(token.text().to_string());
1345 } else if token.kind() == LPAREN {
1346 break;
1348 }
1349 }
1350 }
1351 None
1352 }
1353
1354 pub fn members(&self) -> impl Iterator<Item = ArchiveMember> + '_ {
1356 self.syntax().children().filter_map(ArchiveMember::cast)
1357 }
1358
1359 pub fn member_names(&self) -> Vec<String> {
1361 self.members().map(|m| m.text()).collect()
1362 }
1363}
1364
1365impl ArchiveMember {
1366 pub fn text(&self) -> String {
1368 self.syntax().text().to_string().trim().to_string()
1369 }
1370}
1371
1372fn remove_with_preceding_comments(node: &SyntaxNode, parent: &SyntaxNode) {
1380 let mut elements_to_remove = vec![];
1382
1383 let mut current = node.prev_sibling_or_token();
1385 let mut consecutive_newlines = 0;
1386
1387 while let Some(element) = current {
1388 let should_include = match &element {
1389 rowan::NodeOrToken::Token(token) => match token.kind() {
1390 COMMENT => {
1391 consecutive_newlines = 0; true
1393 }
1394 NEWLINE => {
1395 consecutive_newlines += 1;
1396 consecutive_newlines <= 1
1399 }
1400 WHITESPACE => true,
1401 _ => false, },
1403 rowan::NodeOrToken::Node(_) => false, };
1405
1406 if !should_include {
1407 break;
1408 }
1409
1410 elements_to_remove.push(element.clone());
1411 current = element.prev_sibling_or_token();
1412 }
1413
1414 let node_index = node.index();
1416 parent.splice_children(node_index..node_index + 1, vec![]);
1417
1418 for element in elements_to_remove {
1420 let idx = element.index();
1421 parent.splice_children(idx..idx + 1, vec![]);
1422 }
1423}
1424
1425impl VariableDefinition {
1426 pub fn name(&self) -> Option<String> {
1428 self.syntax().children_with_tokens().find_map(|it| {
1429 it.as_token().and_then(|it| {
1430 if it.kind() == IDENTIFIER && it.text() != "export" {
1431 Some(it.text().to_string())
1432 } else {
1433 None
1434 }
1435 })
1436 })
1437 }
1438
1439 pub fn raw_value(&self) -> Option<String> {
1441 self.syntax()
1442 .children()
1443 .find(|it| it.kind() == EXPR)
1444 .map(|it| it.text().into())
1445 }
1446
1447 pub fn remove(&mut self) {
1460 if let Some(parent) = self.syntax().parent() {
1461 remove_with_preceding_comments(self.syntax(), &parent);
1462 }
1463 }
1464
1465 pub fn set_value(&mut self, new_value: &str) {
1478 let expr_index = self
1480 .syntax()
1481 .children()
1482 .find(|it| it.kind() == EXPR)
1483 .map(|it| it.index());
1484
1485 if let Some(expr_idx) = expr_index {
1486 let mut builder = GreenNodeBuilder::new();
1488 builder.start_node(EXPR.into());
1489 builder.token(IDENTIFIER.into(), new_value);
1490 builder.finish_node();
1491
1492 let new_expr = SyntaxNode::new_root_mut(builder.finish());
1493
1494 self.0
1496 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
1497 }
1498 }
1499}
1500
1501impl Makefile {
1502 pub fn new() -> Makefile {
1504 let mut builder = GreenNodeBuilder::new();
1505
1506 builder.start_node(ROOT.into());
1507 builder.finish_node();
1508
1509 let syntax = SyntaxNode::new_root_mut(builder.finish());
1510 Makefile(syntax)
1511 }
1512
1513 pub fn parse(text: &str) -> crate::Parse<Makefile> {
1515 crate::Parse::<Makefile>::parse_makefile(text)
1516 }
1517
1518 pub fn code(&self) -> String {
1520 self.syntax().text().to_string()
1521 }
1522
1523 pub fn is_root(&self) -> bool {
1525 self.syntax().kind() == ROOT
1526 }
1527
1528 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1530 let mut buf = String::new();
1531 r.read_to_string(&mut buf)?;
1532 buf.parse()
1533 }
1534
1535 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1537 let mut buf = String::new();
1538 r.read_to_string(&mut buf)?;
1539
1540 let parsed = parse(&buf);
1541 Ok(parsed.root())
1542 }
1543
1544 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
1553 self.syntax().children().filter_map(Rule::cast)
1554 }
1555
1556 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1558 self.rules()
1559 .filter(move |rule| rule.targets().any(|t| t == target))
1560 }
1561
1562 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
1564 self.syntax()
1565 .children()
1566 .filter_map(VariableDefinition::cast)
1567 }
1568
1569 pub fn find_variable<'a>(
1584 &'a self,
1585 name: &'a str,
1586 ) -> impl Iterator<Item = VariableDefinition> + 'a {
1587 self.variable_definitions()
1588 .filter(move |var| var.name().as_deref() == Some(name))
1589 }
1590
1591 pub fn add_rule(&mut self, target: &str) -> Rule {
1601 let mut builder = GreenNodeBuilder::new();
1602 builder.start_node(RULE.into());
1603 builder.token(IDENTIFIER.into(), target);
1604 builder.token(OPERATOR.into(), ":");
1605 builder.token(NEWLINE.into(), "\n");
1606 builder.finish_node();
1607
1608 let syntax = SyntaxNode::new_root_mut(builder.finish());
1609 let pos = self.0.children_with_tokens().count();
1610 self.0.splice_children(pos..pos, vec![syntax.into()]);
1611 Rule(self.0.children().nth(pos).unwrap())
1612 }
1613
1614 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1616 let mut buf = String::new();
1617 r.read_to_string(&mut buf)?;
1618
1619 let parsed = parse(&buf);
1620 if !parsed.errors.is_empty() {
1621 Err(Error::Parse(ParseError {
1622 errors: parsed.errors,
1623 }))
1624 } else {
1625 Ok(parsed.root())
1626 }
1627 }
1628
1629 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1640 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1641
1642 if rules.is_empty() {
1643 return Err(Error::Parse(ParseError {
1644 errors: vec![ErrorInfo {
1645 message: "Cannot replace rule in empty makefile".to_string(),
1646 line: 1,
1647 context: "replace_rule".to_string(),
1648 }],
1649 }));
1650 }
1651
1652 if index >= rules.len() {
1653 return Err(Error::Parse(ParseError {
1654 errors: vec![ErrorInfo {
1655 message: format!(
1656 "Rule index {} out of bounds (max {})",
1657 index,
1658 rules.len() - 1
1659 ),
1660 line: 1,
1661 context: "replace_rule".to_string(),
1662 }],
1663 }));
1664 }
1665
1666 let target_node = &rules[index];
1667 let target_index = target_node.index();
1668
1669 self.0.splice_children(
1671 target_index..target_index + 1,
1672 vec![new_rule.0.clone().into()],
1673 );
1674 Ok(())
1675 }
1676
1677 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
1688 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1689
1690 if rules.is_empty() {
1691 return Err(Error::Parse(ParseError {
1692 errors: vec![ErrorInfo {
1693 message: "Cannot remove rule from empty makefile".to_string(),
1694 line: 1,
1695 context: "remove_rule".to_string(),
1696 }],
1697 }));
1698 }
1699
1700 if index >= rules.len() {
1701 return Err(Error::Parse(ParseError {
1702 errors: vec![ErrorInfo {
1703 message: format!(
1704 "Rule index {} out of bounds (max {})",
1705 index,
1706 rules.len() - 1
1707 ),
1708 line: 1,
1709 context: "remove_rule".to_string(),
1710 }],
1711 }));
1712 }
1713
1714 let target_node = rules[index].clone();
1715 let target_index = target_node.index();
1716
1717 self.0
1719 .splice_children(target_index..target_index + 1, vec![]);
1720 Ok(Rule(target_node))
1721 }
1722
1723 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1735 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1736
1737 if index > rules.len() {
1738 return Err(Error::Parse(ParseError {
1739 errors: vec![ErrorInfo {
1740 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1741 line: 1,
1742 context: "insert_rule".to_string(),
1743 }],
1744 }));
1745 }
1746
1747 let target_index = if index == rules.len() {
1748 self.0.children_with_tokens().count()
1750 } else {
1751 rules[index].index()
1753 };
1754
1755 self.0
1757 .splice_children(target_index..target_index, vec![new_rule.0.clone().into()]);
1758 Ok(())
1759 }
1760
1761 pub fn includes(&self) -> impl Iterator<Item = Include> {
1771 self.syntax().children().filter_map(Include::cast)
1772 }
1773
1774 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1784 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1787 let mut includes = Vec::new();
1788
1789 if let Some(include) = Include::cast(node.clone()) {
1791 includes.push(include);
1792 }
1793
1794 for child in node.children() {
1796 includes.extend(collect_includes(&child));
1797 }
1798
1799 includes
1800 }
1801
1802 let includes = collect_includes(self.syntax());
1804
1805 includes.into_iter().map(|include| {
1807 include
1808 .syntax()
1809 .children()
1810 .find(|node| node.kind() == EXPR)
1811 .map(|expr| expr.text().to_string().trim().to_string())
1812 .unwrap_or_default()
1813 })
1814 }
1815
1816 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1827 self.rules()
1828 .find(|rule| rule.targets().any(|t| t == target))
1829 }
1830
1831 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1841 self.rules_by_target(target)
1842 }
1843
1844 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1854 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1856 if !phony_rule.prerequisites().any(|p| p == target) {
1858 phony_rule.add_prerequisite(target)?;
1859 }
1860 } else {
1861 let mut phony_rule = self.add_rule(".PHONY");
1863 phony_rule.add_prerequisite(target)?;
1864 }
1865 Ok(())
1866 }
1867
1868 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1882 let mut phony_rule = None;
1884 for rule in self.rules_by_target(".PHONY") {
1885 if rule.prerequisites().any(|p| p == target) {
1886 phony_rule = Some(rule);
1887 break;
1888 }
1889 }
1890
1891 let mut phony_rule = match phony_rule {
1892 Some(rule) => rule,
1893 None => return Ok(false),
1894 };
1895
1896 let prereq_count = phony_rule.prerequisites().count();
1898
1899 phony_rule.remove_prerequisite(target)?;
1901
1902 if prereq_count == 1 {
1904 phony_rule.remove()?;
1906 }
1907
1908 Ok(true)
1909 }
1910
1911 pub fn is_phony(&self, target: &str) -> bool {
1922 self.rules_by_target(".PHONY")
1924 .any(|rule| rule.prerequisites().any(|p| p == target))
1925 }
1926
1927 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1937 self.rules_by_target(".PHONY")
1939 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1940 }
1941}
1942
1943impl FromStr for Rule {
1944 type Err = crate::Error;
1945
1946 fn from_str(s: &str) -> Result<Self, Self::Err> {
1947 Rule::parse(s).to_rule_result()
1948 }
1949}
1950
1951impl FromStr for Makefile {
1952 type Err = crate::Error;
1953
1954 fn from_str(s: &str) -> Result<Self, Self::Err> {
1955 Makefile::parse(s).to_result()
1956 }
1957}
1958
1959fn build_prerequisites_node(prereqs: &[String]) -> SyntaxNode {
1961 let mut builder = GreenNodeBuilder::new();
1962 builder.start_node(PREREQUISITES.into());
1963
1964 for (i, prereq) in prereqs.iter().enumerate() {
1965 if i > 0 {
1966 builder.token(WHITESPACE.into(), " ");
1967 }
1968
1969 builder.start_node(PREREQUISITE.into());
1971 builder.token(IDENTIFIER.into(), prereq);
1972 builder.finish_node();
1973 }
1974
1975 builder.finish_node();
1976 SyntaxNode::new_root_mut(builder.finish())
1977}
1978
1979impl Rule {
1980 pub fn parse(text: &str) -> crate::Parse<Rule> {
1982 crate::Parse::<Rule>::parse_rule(text)
1983 }
1984
1985 fn collect_variable_reference(
1987 &self,
1988 tokens: &mut std::iter::Peekable<impl Iterator<Item = SyntaxElement>>,
1989 ) -> Option<String> {
1990 let mut var_ref = String::new();
1991
1992 if let Some(token) = tokens.next() {
1994 if let Some(t) = token.as_token() {
1995 if t.kind() == DOLLAR {
1996 var_ref.push_str(t.text());
1997
1998 if let Some(next) = tokens.peek() {
2000 if let Some(nt) = next.as_token() {
2001 if nt.kind() == LPAREN {
2002 var_ref.push_str(nt.text());
2004 tokens.next();
2005
2006 let mut paren_count = 1;
2008
2009 for next_token in tokens.by_ref() {
2011 if let Some(nt) = next_token.as_token() {
2012 var_ref.push_str(nt.text());
2013
2014 if nt.kind() == LPAREN {
2015 paren_count += 1;
2016 } else if nt.kind() == RPAREN {
2017 paren_count -= 1;
2018 if paren_count == 0 {
2019 break;
2020 }
2021 }
2022 }
2023 }
2024
2025 return Some(var_ref);
2026 }
2027 }
2028 }
2029
2030 for next_token in tokens.by_ref() {
2032 if let Some(nt) = next_token.as_token() {
2033 var_ref.push_str(nt.text());
2034 if nt.kind() == RPAREN {
2035 break;
2036 }
2037 }
2038 }
2039 return Some(var_ref);
2040 }
2041 }
2042 }
2043
2044 None
2045 }
2046
2047 pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
2057 let mut result = Vec::new();
2058 let mut tokens = self
2059 .syntax()
2060 .children_with_tokens()
2061 .take_while(|it| it.as_token().map(|t| t.kind() != OPERATOR).unwrap_or(true))
2062 .peekable();
2063
2064 while let Some(token) = tokens.peek().cloned() {
2065 if let Some(node) = token.as_node() {
2066 tokens.next(); if node.kind() == EXPR {
2068 let mut var_content = String::new();
2070 for child in node.children_with_tokens() {
2071 if let Some(t) = child.as_token() {
2072 var_content.push_str(t.text());
2073 }
2074 }
2075 if !var_content.is_empty() {
2076 result.push(var_content);
2077 }
2078 }
2079 } else if let Some(t) = token.as_token() {
2080 if t.kind() == DOLLAR {
2081 if let Some(var_ref) = self.collect_variable_reference(&mut tokens) {
2082 result.push(var_ref);
2083 }
2084 } else if t.kind() == IDENTIFIER {
2085 let ident_text = t.text().to_string();
2087 tokens.next(); if let Some(next) = tokens.peek() {
2091 if let Some(next_token) = next.as_token() {
2092 if next_token.kind() == LPAREN {
2093 let mut archive_target = ident_text;
2095 archive_target.push_str(next_token.text()); tokens.next(); while let Some(token) = tokens.peek() {
2100 if let Some(node) = token.as_node() {
2101 if node.kind() == ARCHIVE_MEMBERS {
2102 archive_target.push_str(&node.text().to_string());
2103 tokens.next();
2104 } else {
2105 tokens.next();
2106 }
2107 } else if let Some(t) = token.as_token() {
2108 if t.kind() == RPAREN {
2109 archive_target.push_str(t.text());
2110 tokens.next();
2111 break;
2112 } else {
2113 tokens.next();
2114 }
2115 } else {
2116 break;
2117 }
2118 }
2119 result.push(archive_target);
2120 } else {
2121 result.push(ident_text);
2123 }
2124 } else {
2125 result.push(ident_text);
2127 }
2128 } else {
2129 result.push(ident_text);
2131 }
2132 } else {
2133 tokens.next(); }
2135 }
2136 }
2137 result.into_iter()
2138 }
2139
2140 pub fn prerequisites(&self) -> impl Iterator<Item = String> + '_ {
2149 let mut found_operator = false;
2151 let mut prerequisites_node = None;
2152
2153 for element in self.syntax().children_with_tokens() {
2154 if let Some(token) = element.as_token() {
2155 if token.kind() == OPERATOR {
2156 found_operator = true;
2157 }
2158 } else if let Some(node) = element.as_node() {
2159 if found_operator && node.kind() == PREREQUISITES {
2160 prerequisites_node = Some(node.clone());
2161 break;
2162 }
2163 }
2164 }
2165
2166 let result: Vec<String> = if let Some(prereqs) = prerequisites_node {
2167 prereqs
2169 .children()
2170 .filter(|child| child.kind() == PREREQUISITE)
2171 .map(|child| child.text().to_string().trim().to_string())
2172 .collect()
2173 } else {
2174 Vec::new()
2175 };
2176
2177 result.into_iter()
2178 }
2179
2180 pub fn recipes(&self) -> impl Iterator<Item = String> {
2189 self.syntax()
2190 .children()
2191 .filter(|it| it.kind() == RECIPE)
2192 .flat_map(|it| {
2193 it.children_with_tokens().filter_map(|it| {
2194 it.as_token().and_then(|t| {
2195 if t.kind() == TEXT {
2196 Some(t.text().to_string())
2197 } else {
2198 None
2199 }
2200 })
2201 })
2202 })
2203 }
2204
2205 pub fn replace_command(&mut self, i: usize, line: &str) -> bool {
2215 let index = self
2217 .syntax()
2218 .children()
2219 .filter(|it| it.kind() == RECIPE)
2220 .nth(i);
2221
2222 let index = match index {
2223 Some(node) => node.index(),
2224 None => return false,
2225 };
2226
2227 let mut builder = GreenNodeBuilder::new();
2228 builder.start_node(RECIPE.into());
2229 builder.token(INDENT.into(), "\t");
2230 builder.token(TEXT.into(), line);
2231 builder.token(NEWLINE.into(), "\n");
2232 builder.finish_node();
2233
2234 let syntax = SyntaxNode::new_root_mut(builder.finish());
2235
2236 self.0
2237 .splice_children(index..index + 1, vec![syntax.into()]);
2238
2239 true
2240 }
2241
2242 pub fn push_command(&mut self, line: &str) {
2252 let index = self
2254 .0
2255 .children_with_tokens()
2256 .filter(|it| it.kind() == RECIPE)
2257 .last();
2258
2259 let index = index.map_or_else(
2260 || self.0.children_with_tokens().count(),
2261 |it| it.index() + 1,
2262 );
2263
2264 let mut builder = GreenNodeBuilder::new();
2265 builder.start_node(RECIPE.into());
2266 builder.token(INDENT.into(), "\t");
2267 builder.token(TEXT.into(), line);
2268 builder.token(NEWLINE.into(), "\n");
2269 builder.finish_node();
2270 let syntax = SyntaxNode::new_root_mut(builder.finish());
2271
2272 self.0.splice_children(index..index, vec![syntax.into()]);
2273 }
2274
2275 pub fn remove_command(&mut self, index: usize) -> bool {
2285 let recipes: Vec<_> = self
2286 .syntax()
2287 .children()
2288 .filter(|n| n.kind() == RECIPE)
2289 .collect();
2290
2291 if index >= recipes.len() {
2292 return false;
2293 }
2294
2295 let target_node = &recipes[index];
2296 let target_index = target_node.index();
2297
2298 self.0
2299 .splice_children(target_index..target_index + 1, vec![]);
2300 true
2301 }
2302
2303 pub fn insert_command(&mut self, index: usize, line: &str) -> bool {
2314 let recipes: Vec<_> = self
2315 .syntax()
2316 .children()
2317 .filter(|n| n.kind() == RECIPE)
2318 .collect();
2319
2320 if index > recipes.len() {
2321 return false;
2322 }
2323
2324 let target_index = if index == recipes.len() {
2325 recipes.last().map(|n| n.index() + 1).unwrap_or_else(|| {
2327 self.0.children_with_tokens().count()
2329 })
2330 } else {
2331 recipes[index].index()
2333 };
2334
2335 let mut builder = GreenNodeBuilder::new();
2336 builder.start_node(RECIPE.into());
2337 builder.token(INDENT.into(), "\t");
2338 builder.token(TEXT.into(), line);
2339 builder.token(NEWLINE.into(), "\n");
2340 builder.finish_node();
2341 let syntax = SyntaxNode::new_root_mut(builder.finish());
2342
2343 self.0
2344 .splice_children(target_index..target_index, vec![syntax.into()]);
2345 true
2346 }
2347
2348 pub fn recipe_count(&self) -> usize {
2357 self.syntax()
2358 .children()
2359 .filter(|n| n.kind() == RECIPE)
2360 .count()
2361 }
2362
2363 pub fn clear_commands(&mut self) {
2373 let recipes: Vec<_> = self
2374 .syntax()
2375 .children()
2376 .filter(|n| n.kind() == RECIPE)
2377 .collect();
2378
2379 if recipes.is_empty() {
2380 return;
2381 }
2382
2383 for recipe in recipes.iter().rev() {
2385 let index = recipe.index();
2386 self.0.splice_children(index..index + 1, vec![]);
2387 }
2388 }
2389
2390 pub fn remove_prerequisite(&mut self, target: &str) -> Result<bool, Error> {
2403 let mut found_operator = false;
2405 let mut prereqs_node = None;
2406
2407 for child in self.syntax().children_with_tokens() {
2408 if let Some(token) = child.as_token() {
2409 if token.kind() == OPERATOR {
2410 found_operator = true;
2411 }
2412 } else if let Some(node) = child.as_node() {
2413 if found_operator && node.kind() == PREREQUISITES {
2414 prereqs_node = Some(node.clone());
2415 break;
2416 }
2417 }
2418 }
2419
2420 let prereqs_node = match prereqs_node {
2421 Some(node) => node,
2422 None => return Ok(false), };
2424
2425 let current_prereqs: Vec<String> = self.prerequisites().collect();
2427
2428 if !current_prereqs.iter().any(|p| p == target) {
2430 return Ok(false);
2431 }
2432
2433 let new_prereqs: Vec<String> = current_prereqs
2435 .into_iter()
2436 .filter(|p| p != target)
2437 .collect();
2438
2439 let prereqs_index = prereqs_node.index();
2441 let new_prereqs_node = build_prerequisites_node(&new_prereqs);
2442
2443 self.0.splice_children(
2444 prereqs_index..prereqs_index + 1,
2445 vec![new_prereqs_node.into()],
2446 );
2447
2448 Ok(true)
2449 }
2450
2451 pub fn add_prerequisite(&mut self, target: &str) -> Result<(), Error> {
2461 let mut current_prereqs: Vec<String> = self.prerequisites().collect();
2462 current_prereqs.push(target.to_string());
2463 self.set_prerequisites(current_prereqs.iter().map(|s| s.as_str()).collect())
2464 }
2465
2466 pub fn set_prerequisites(&mut self, prereqs: Vec<&str>) -> Result<(), Error> {
2476 let mut prereqs_index = None;
2478 let mut operator_found = false;
2479
2480 for child in self.syntax().children_with_tokens() {
2481 if let Some(token) = child.as_token() {
2482 if token.kind() == OPERATOR {
2483 operator_found = true;
2484 }
2485 } else if let Some(node) = child.as_node() {
2486 if operator_found && node.kind() == PREREQUISITES {
2487 prereqs_index = Some((node.index(), true)); break;
2489 }
2490 }
2491 }
2492
2493 let new_prereqs =
2495 build_prerequisites_node(&prereqs.iter().map(|s| s.to_string()).collect::<Vec<_>>());
2496
2497 match prereqs_index {
2498 Some((idx, true)) => {
2499 self.0
2501 .splice_children(idx..idx + 1, vec![new_prereqs.into()]);
2502 }
2503 _ => {
2504 let insert_pos = self
2506 .syntax()
2507 .children_with_tokens()
2508 .position(|t| t.as_token().map(|t| t.kind() == OPERATOR).unwrap_or(false))
2509 .map(|p| p + 1)
2510 .ok_or_else(|| {
2511 Error::Parse(ParseError {
2512 errors: vec![ErrorInfo {
2513 message: "No operator found in rule".to_string(),
2514 line: 1,
2515 context: "set_prerequisites".to_string(),
2516 }],
2517 })
2518 })?;
2519
2520 self.0
2521 .splice_children(insert_pos..insert_pos, vec![new_prereqs.into()]);
2522 }
2523 }
2524
2525 Ok(())
2526 }
2527
2528 pub fn remove(self) -> Result<(), Error> {
2541 let parent = self.syntax().parent().ok_or_else(|| {
2542 Error::Parse(ParseError {
2543 errors: vec![ErrorInfo {
2544 message: "Rule has no parent".to_string(),
2545 line: 1,
2546 context: "remove".to_string(),
2547 }],
2548 })
2549 })?;
2550
2551 remove_with_preceding_comments(self.syntax(), &parent);
2552 Ok(())
2553 }
2554}
2555
2556impl Default for Makefile {
2557 fn default() -> Self {
2558 Self::new()
2559 }
2560}
2561
2562impl Include {
2563 pub fn path(&self) -> Option<String> {
2565 self.syntax()
2566 .children()
2567 .find(|it| it.kind() == EXPR)
2568 .map(|it| it.text().to_string().trim().to_string())
2569 }
2570
2571 pub fn is_optional(&self) -> bool {
2573 let text = self.syntax().text();
2574 text.to_string().starts_with("-include") || text.to_string().starts_with("sinclude")
2575 }
2576}
2577
2578#[cfg(test)]
2579mod tests {
2580 use super::*;
2581
2582 #[test]
2583 fn test_conditionals() {
2584 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
2588 let mut buf = code.as_bytes();
2589 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
2590 assert!(makefile.code().contains("DEBUG_FLAG"));
2591
2592 let code =
2594 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
2595 let mut buf = code.as_bytes();
2596 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
2597 assert!(makefile.code().contains("RESULT"));
2598 assert!(makefile.code().contains("windows"));
2599
2600 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
2602 let mut buf = code.as_bytes();
2603 let makefile = Makefile::read_relaxed(&mut buf)
2604 .expect("Failed to parse nested conditionals with else");
2605 assert!(makefile.code().contains("CFLAGS"));
2606 assert!(makefile.code().contains("VERBOSE"));
2607
2608 let code = "ifdef DEBUG\nendif\n";
2610 let mut buf = code.as_bytes();
2611 let makefile =
2612 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
2613 assert!(makefile.code().contains("ifdef DEBUG"));
2614
2615 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelif ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
2617 let mut buf = code.as_bytes();
2618 let makefile =
2619 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with elif");
2620 assert!(makefile.code().contains("EXT"));
2621
2622 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
2624 let mut buf = code.as_bytes();
2625 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
2626 assert!(makefile.code().contains("DEBUG"));
2627
2628 let code = "ifdef \nDEBUG := 1\nendif\n";
2630 let mut buf = code.as_bytes();
2631 let makefile = Makefile::read_relaxed(&mut buf)
2632 .expect("Failed to parse with recovery - missing condition");
2633 assert!(makefile.code().contains("DEBUG"));
2634 }
2635
2636 #[test]
2637 fn test_parse_simple() {
2638 const SIMPLE: &str = r#"VARIABLE = value
2639
2640rule: dependency
2641 command
2642"#;
2643 let parsed = parse(SIMPLE);
2644 assert!(parsed.errors.is_empty());
2645 let node = parsed.syntax();
2646 assert_eq!(
2647 format!("{:#?}", node),
2648 r#"ROOT@0..44
2649 VARIABLE@0..17
2650 IDENTIFIER@0..8 "VARIABLE"
2651 WHITESPACE@8..9 " "
2652 OPERATOR@9..10 "="
2653 WHITESPACE@10..11 " "
2654 EXPR@11..16
2655 IDENTIFIER@11..16 "value"
2656 NEWLINE@16..17 "\n"
2657 NEWLINE@17..18 "\n"
2658 RULE@18..44
2659 IDENTIFIER@18..22 "rule"
2660 OPERATOR@22..23 ":"
2661 WHITESPACE@23..24 " "
2662 PREREQUISITES@24..34
2663 PREREQUISITE@24..34
2664 IDENTIFIER@24..34 "dependency"
2665 NEWLINE@34..35 "\n"
2666 RECIPE@35..44
2667 INDENT@35..36 "\t"
2668 TEXT@36..43 "command"
2669 NEWLINE@43..44 "\n"
2670"#
2671 );
2672
2673 let root = parsed.root();
2674
2675 let mut rules = root.rules().collect::<Vec<_>>();
2676 assert_eq!(rules.len(), 1);
2677 let rule = rules.pop().unwrap();
2678 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2679 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
2680 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2681
2682 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2683 assert_eq!(variables.len(), 1);
2684 let variable = variables.pop().unwrap();
2685 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2686 assert_eq!(variable.raw_value(), Some("value".to_string()));
2687 }
2688
2689 #[test]
2690 fn test_parse_export_assign() {
2691 const EXPORT: &str = r#"export VARIABLE := value
2692"#;
2693 let parsed = parse(EXPORT);
2694 assert!(parsed.errors.is_empty());
2695 let node = parsed.syntax();
2696 assert_eq!(
2697 format!("{:#?}", node),
2698 r#"ROOT@0..25
2699 VARIABLE@0..25
2700 IDENTIFIER@0..6 "export"
2701 WHITESPACE@6..7 " "
2702 IDENTIFIER@7..15 "VARIABLE"
2703 WHITESPACE@15..16 " "
2704 OPERATOR@16..18 ":="
2705 WHITESPACE@18..19 " "
2706 EXPR@19..24
2707 IDENTIFIER@19..24 "value"
2708 NEWLINE@24..25 "\n"
2709"#
2710 );
2711
2712 let root = parsed.root();
2713
2714 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2715 assert_eq!(variables.len(), 1);
2716 let variable = variables.pop().unwrap();
2717 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2718 assert_eq!(variable.raw_value(), Some("value".to_string()));
2719 }
2720
2721 #[test]
2722 fn test_parse_multiple_prerequisites() {
2723 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
2724 command
2725
2726"#;
2727 let parsed = parse(MULTIPLE_PREREQUISITES);
2728 assert!(parsed.errors.is_empty());
2729 let node = parsed.syntax();
2730 assert_eq!(
2731 format!("{:#?}", node),
2732 r#"ROOT@0..40
2733 RULE@0..40
2734 IDENTIFIER@0..4 "rule"
2735 OPERATOR@4..5 ":"
2736 WHITESPACE@5..6 " "
2737 PREREQUISITES@6..29
2738 PREREQUISITE@6..17
2739 IDENTIFIER@6..17 "dependency1"
2740 WHITESPACE@17..18 " "
2741 PREREQUISITE@18..29
2742 IDENTIFIER@18..29 "dependency2"
2743 NEWLINE@29..30 "\n"
2744 RECIPE@30..39
2745 INDENT@30..31 "\t"
2746 TEXT@31..38 "command"
2747 NEWLINE@38..39 "\n"
2748 NEWLINE@39..40 "\n"
2749"#
2750 );
2751 let root = parsed.root();
2752
2753 let rule = root.rules().next().unwrap();
2754 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2755 assert_eq!(
2756 rule.prerequisites().collect::<Vec<_>>(),
2757 vec!["dependency1", "dependency2"]
2758 );
2759 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2760 }
2761
2762 #[test]
2763 fn test_add_rule() {
2764 let mut makefile = Makefile::new();
2765 let rule = makefile.add_rule("rule");
2766 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2767 assert_eq!(
2768 rule.prerequisites().collect::<Vec<_>>(),
2769 Vec::<String>::new()
2770 );
2771
2772 assert_eq!(makefile.to_string(), "rule:\n");
2773 }
2774
2775 #[test]
2776 fn test_push_command() {
2777 let mut makefile = Makefile::new();
2778 let mut rule = makefile.add_rule("rule");
2779
2780 rule.push_command("command");
2782 rule.push_command("command2");
2783
2784 assert_eq!(
2786 rule.recipes().collect::<Vec<_>>(),
2787 vec!["command", "command2"]
2788 );
2789
2790 rule.push_command("command3");
2792 assert_eq!(
2793 rule.recipes().collect::<Vec<_>>(),
2794 vec!["command", "command2", "command3"]
2795 );
2796
2797 assert_eq!(
2799 makefile.to_string(),
2800 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2801 );
2802
2803 assert_eq!(
2805 rule.to_string(),
2806 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2807 );
2808 }
2809
2810 #[test]
2811 fn test_replace_command() {
2812 let mut makefile = Makefile::new();
2813 let mut rule = makefile.add_rule("rule");
2814
2815 rule.push_command("command");
2817 rule.push_command("command2");
2818
2819 assert_eq!(
2821 rule.recipes().collect::<Vec<_>>(),
2822 vec!["command", "command2"]
2823 );
2824
2825 rule.replace_command(0, "new command");
2827 assert_eq!(
2828 rule.recipes().collect::<Vec<_>>(),
2829 vec!["new command", "command2"]
2830 );
2831
2832 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2834
2835 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2837 }
2838
2839 #[test]
2840 fn test_parse_rule_without_newline() {
2841 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
2842 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2843 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2844 let rule = "rule: dependency".parse::<Rule>().unwrap();
2845 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2846 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
2847 }
2848
2849 #[test]
2850 fn test_parse_makefile_without_newline() {
2851 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
2852 assert_eq!(makefile.rules().count(), 1);
2853 }
2854
2855 #[test]
2856 fn test_from_reader() {
2857 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
2858 assert_eq!(makefile.rules().count(), 1);
2859 }
2860
2861 #[test]
2862 fn test_parse_with_tab_after_last_newline() {
2863 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
2864 assert_eq!(makefile.rules().count(), 1);
2865 }
2866
2867 #[test]
2868 fn test_parse_with_space_after_last_newline() {
2869 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
2870 assert_eq!(makefile.rules().count(), 1);
2871 }
2872
2873 #[test]
2874 fn test_parse_with_comment_after_last_newline() {
2875 let makefile =
2876 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
2877 assert_eq!(makefile.rules().count(), 1);
2878 }
2879
2880 #[test]
2881 fn test_parse_with_variable_rule() {
2882 let makefile =
2883 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
2884 .unwrap();
2885
2886 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2888 assert_eq!(vars.len(), 1);
2889 assert_eq!(vars[0].name(), Some("RULE".to_string()));
2890 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
2891
2892 let rules = makefile.rules().collect::<Vec<_>>();
2894 assert_eq!(rules.len(), 1);
2895 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
2896 assert_eq!(
2897 rules[0].prerequisites().collect::<Vec<_>>(),
2898 vec!["dependency"]
2899 );
2900 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2901 }
2902
2903 #[test]
2904 fn test_parse_with_variable_dependency() {
2905 let makefile =
2906 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
2907
2908 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2910 assert_eq!(vars.len(), 1);
2911 assert_eq!(vars[0].name(), Some("DEP".to_string()));
2912 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
2913
2914 let rules = makefile.rules().collect::<Vec<_>>();
2916 assert_eq!(rules.len(), 1);
2917 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2918 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
2919 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2920 }
2921
2922 #[test]
2923 fn test_parse_with_variable_command() {
2924 let makefile =
2925 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
2926
2927 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2929 assert_eq!(vars.len(), 1);
2930 assert_eq!(vars[0].name(), Some("COM".to_string()));
2931 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
2932
2933 let rules = makefile.rules().collect::<Vec<_>>();
2935 assert_eq!(rules.len(), 1);
2936 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2937 assert_eq!(
2938 rules[0].prerequisites().collect::<Vec<_>>(),
2939 vec!["dependency"]
2940 );
2941 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
2942 }
2943
2944 #[test]
2945 fn test_regular_line_error_reporting() {
2946 let input = "rule target\n\tcommand";
2947
2948 let parsed = parse(input);
2950 let direct_error = &parsed.errors[0];
2951
2952 assert_eq!(direct_error.line, 2);
2954 assert!(
2955 direct_error.message.contains("expected"),
2956 "Error message should contain 'expected': {}",
2957 direct_error.message
2958 );
2959 assert_eq!(direct_error.context, "\tcommand");
2960
2961 let reader_result = Makefile::from_reader(input.as_bytes());
2963 let parse_error = match reader_result {
2964 Ok(_) => panic!("Expected Parse error from from_reader"),
2965 Err(err) => match err {
2966 self::Error::Parse(parse_err) => parse_err,
2967 _ => panic!("Expected Parse error"),
2968 },
2969 };
2970
2971 let error_text = parse_error.to_string();
2973 assert!(error_text.contains("Error at line 2:"));
2974 assert!(error_text.contains("2| \tcommand"));
2975 }
2976
2977 #[test]
2978 fn test_parsing_error_context_with_bad_syntax() {
2979 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
2981
2982 match Makefile::from_reader(input.as_bytes()) {
2984 Ok(makefile) => {
2985 assert_eq!(
2987 makefile.rules().count(),
2988 0,
2989 "Should not have found any rules"
2990 );
2991 }
2992 Err(err) => match err {
2993 self::Error::Parse(error) => {
2994 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
2996 assert!(
2997 !error.errors[0].context.is_empty(),
2998 "Error context should not be empty"
2999 );
3000 }
3001 _ => panic!("Unexpected error type"),
3002 },
3003 };
3004 }
3005
3006 #[test]
3007 fn test_error_message_format() {
3008 let parse_error = ParseError {
3010 errors: vec![ErrorInfo {
3011 message: "test error".to_string(),
3012 line: 42,
3013 context: "some problematic code".to_string(),
3014 }],
3015 };
3016
3017 let error_text = parse_error.to_string();
3018 assert!(error_text.contains("Error at line 42: test error"));
3019 assert!(error_text.contains("42| some problematic code"));
3020 }
3021
3022 #[test]
3023 fn test_line_number_calculation() {
3024 let test_cases = [
3026 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
3030
3031 for (input, expected_line) in test_cases {
3032 match input.parse::<Makefile>() {
3034 Ok(_) => {
3035 continue;
3038 }
3039 Err(err) => {
3040 if let Error::Parse(parse_err) = err {
3041 assert_eq!(
3043 parse_err.errors[0].line, expected_line,
3044 "Line number should match the expected line"
3045 );
3046
3047 if parse_err.errors[0].message.contains("indented") {
3049 assert!(
3050 parse_err.errors[0].context.starts_with('\t'),
3051 "Context for indentation errors should include the tab character"
3052 );
3053 }
3054 } else {
3055 panic!("Expected parse error, got: {:?}", err);
3056 }
3057 }
3058 }
3059 }
3060 }
3061
3062 #[test]
3063 fn test_conditional_features() {
3064 let code = r#"
3066# Set variables based on DEBUG flag
3067ifdef DEBUG
3068 CFLAGS += -g -DDEBUG
3069else
3070 CFLAGS = -O2
3071endif
3072
3073# Define a build rule
3074all: $(OBJS)
3075 $(CC) $(CFLAGS) -o $@ $^
3076"#;
3077
3078 let mut buf = code.as_bytes();
3079 let makefile =
3080 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
3081
3082 assert!(!makefile.code().is_empty(), "Makefile has content");
3085
3086 let rules = makefile.rules().collect::<Vec<_>>();
3088 assert!(!rules.is_empty(), "Should have found rules");
3089
3090 assert!(code.contains("ifdef DEBUG"));
3092 assert!(code.contains("endif"));
3093
3094 let code_with_var = r#"
3096# Define a variable first
3097CC = gcc
3098
3099ifdef DEBUG
3100 CFLAGS += -g -DDEBUG
3101else
3102 CFLAGS = -O2
3103endif
3104
3105all: $(OBJS)
3106 $(CC) $(CFLAGS) -o $@ $^
3107"#;
3108
3109 let mut buf = code_with_var.as_bytes();
3110 let makefile =
3111 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
3112
3113 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3115 assert!(
3116 !vars.is_empty(),
3117 "Should have found at least the CC variable definition"
3118 );
3119 }
3120
3121 #[test]
3122 fn test_include_directive() {
3123 let parsed = parse("include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n");
3124 assert!(parsed.errors.is_empty());
3125 let node = parsed.syntax();
3126 assert!(format!("{:#?}", node).contains("INCLUDE@"));
3127 }
3128
3129 #[test]
3130 fn test_export_variables() {
3131 let parsed = parse("export SHELL := /bin/bash\n");
3132 assert!(parsed.errors.is_empty());
3133 let makefile = parsed.root();
3134 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3135 assert_eq!(vars.len(), 1);
3136 let shell_var = vars
3137 .iter()
3138 .find(|v| v.name() == Some("SHELL".to_string()))
3139 .unwrap();
3140 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
3141 }
3142
3143 #[test]
3144 fn test_variable_scopes() {
3145 let parsed =
3146 parse("SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n");
3147 assert!(parsed.errors.is_empty());
3148 let makefile = parsed.root();
3149 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3150 assert_eq!(vars.len(), 4);
3151 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
3152 assert!(var_names.contains(&"SIMPLE".to_string()));
3153 assert!(var_names.contains(&"IMMEDIATE".to_string()));
3154 assert!(var_names.contains(&"CONDITIONAL".to_string()));
3155 assert!(var_names.contains(&"APPEND".to_string()));
3156 }
3157
3158 #[test]
3159 fn test_pattern_rule_parsing() {
3160 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n");
3161 assert!(parsed.errors.is_empty());
3162 let makefile = parsed.root();
3163 let rules = makefile.rules().collect::<Vec<_>>();
3164 assert_eq!(rules.len(), 1);
3165 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
3166 assert!(rules[0].recipes().next().unwrap().contains("$@"));
3167 }
3168
3169 #[test]
3170 fn test_include_variants() {
3171 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
3173 let parsed = parse(makefile_str);
3174 assert!(parsed.errors.is_empty());
3175
3176 let node = parsed.syntax();
3178 let debug_str = format!("{:#?}", node);
3179
3180 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
3182
3183 let makefile = parsed.root();
3185
3186 let include_count = makefile
3188 .syntax()
3189 .children()
3190 .filter(|child| child.kind() == INCLUDE)
3191 .count();
3192 assert_eq!(include_count, 4);
3193
3194 assert!(makefile
3196 .included_files()
3197 .any(|path| path.contains("$(VAR)")));
3198 }
3199
3200 #[test]
3201 fn test_include_api() {
3202 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
3204 let makefile: Makefile = makefile_str.parse().unwrap();
3205
3206 let includes: Vec<_> = makefile.includes().collect();
3208 assert_eq!(includes.len(), 3);
3209
3210 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
3217 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
3218
3219 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
3221 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
3222 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
3223 }
3224
3225 #[test]
3226 fn test_include_integration() {
3227 let phony_makefile = Makefile::from_reader(
3231 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3232 .as_bytes()
3233 ).unwrap();
3234
3235 assert_eq!(phony_makefile.rules().count(), 2);
3237
3238 let normal_rules_count = phony_makefile
3240 .rules()
3241 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
3242 .count();
3243 assert_eq!(normal_rules_count, 1);
3244
3245 assert_eq!(phony_makefile.includes().count(), 1);
3247 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
3248
3249 let simple_makefile = Makefile::from_reader(
3251 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3252 .as_bytes(),
3253 )
3254 .unwrap();
3255 assert_eq!(simple_makefile.rules().count(), 1);
3256 assert_eq!(simple_makefile.includes().count(), 1);
3257 }
3258
3259 #[test]
3260 fn test_real_conditional_directives() {
3261 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
3263 let mut buf = conditional.as_bytes();
3264 let makefile =
3265 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
3266 let code = makefile.code();
3267 assert!(code.contains("ifdef DEBUG"));
3268 assert!(code.contains("else"));
3269 assert!(code.contains("endif"));
3270
3271 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
3273 let mut buf = nested.as_bytes();
3274 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
3275 let code = makefile.code();
3276 assert!(code.contains("ifdef DEBUG"));
3277 assert!(code.contains("ifdef VERBOSE"));
3278
3279 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
3281 let mut buf = ifeq.as_bytes();
3282 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
3283 let code = makefile.code();
3284 assert!(code.contains("ifeq"));
3285 assert!(code.contains("Windows_NT"));
3286 }
3287
3288 #[test]
3289 fn test_indented_text_outside_rules() {
3290 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
3292 let parsed = parse(help_text);
3293 assert!(parsed.errors.is_empty());
3294
3295 let root = parsed.root();
3297 let rules = root.rules().collect::<Vec<_>>();
3298 assert_eq!(rules.len(), 1);
3299
3300 let help_rule = &rules[0];
3301 let recipes = help_rule.recipes().collect::<Vec<_>>();
3302 assert_eq!(recipes.len(), 2);
3303 assert!(recipes[0].contains("Available targets"));
3304 assert!(recipes[1].contains("help"));
3305 }
3306
3307 #[test]
3308 fn test_comment_handling_in_recipes() {
3309 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
3311
3312 let parsed = parse(recipe_comment);
3314
3315 assert!(
3317 parsed.errors.is_empty(),
3318 "Should parse recipe with comments without errors"
3319 );
3320
3321 let root = parsed.root();
3323 let rules = root.rules().collect::<Vec<_>>();
3324 assert_eq!(rules.len(), 1, "Should find exactly one rule");
3325
3326 let build_rule = &rules[0];
3328 assert_eq!(
3329 build_rule.targets().collect::<Vec<_>>(),
3330 vec!["build"],
3331 "Rule should have 'build' as target"
3332 );
3333
3334 let recipes = build_rule.recipes().collect::<Vec<_>>();
3338 assert_eq!(
3339 recipes.len(),
3340 1,
3341 "Should find exactly one recipe line (comment lines are filtered)"
3342 );
3343 assert!(
3344 recipes[0].contains("gcc -o app"),
3345 "Recipe should be the command line"
3346 );
3347 assert!(
3348 !recipes[0].contains("This is a comment"),
3349 "Comments should not be included in recipe lines"
3350 );
3351 }
3352
3353 #[test]
3354 fn test_multiline_variables() {
3355 let multiline = "SOURCES = main.c \\\n util.c\n";
3357
3358 let parsed = parse(multiline);
3360
3361 let root = parsed.root();
3363 let vars = root.variable_definitions().collect::<Vec<_>>();
3364 assert!(!vars.is_empty(), "Should find at least one variable");
3365
3366 let operators = "CFLAGS := -Wall \\\n -Werror\n";
3370 let parsed_operators = parse(operators);
3371
3372 let root = parsed_operators.root();
3374 let vars = root.variable_definitions().collect::<Vec<_>>();
3375 assert!(
3376 !vars.is_empty(),
3377 "Should find at least one variable with := operator"
3378 );
3379
3380 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
3382 let parsed_append = parse(append);
3383
3384 let root = parsed_append.root();
3386 let vars = root.variable_definitions().collect::<Vec<_>>();
3387 assert!(
3388 !vars.is_empty(),
3389 "Should find at least one variable with += operator"
3390 );
3391 }
3392
3393 #[test]
3394 fn test_whitespace_and_eof_handling() {
3395 let blank_lines = "VAR = value\n\n\n";
3397
3398 let parsed_blank = parse(blank_lines);
3399
3400 let root = parsed_blank.root();
3402 let vars = root.variable_definitions().collect::<Vec<_>>();
3403 assert_eq!(
3404 vars.len(),
3405 1,
3406 "Should find one variable in blank lines test"
3407 );
3408
3409 let trailing_space = "VAR = value \n";
3411
3412 let parsed_space = parse(trailing_space);
3413
3414 let root = parsed_space.root();
3416 let vars = root.variable_definitions().collect::<Vec<_>>();
3417 assert_eq!(
3418 vars.len(),
3419 1,
3420 "Should find one variable in trailing space test"
3421 );
3422
3423 let no_newline = "VAR = value";
3425
3426 let parsed_no_newline = parse(no_newline);
3427
3428 let root = parsed_no_newline.root();
3430 let vars = root.variable_definitions().collect::<Vec<_>>();
3431 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
3432 assert_eq!(
3433 vars[0].name(),
3434 Some("VAR".to_string()),
3435 "Variable name should be VAR"
3436 );
3437 }
3438
3439 #[test]
3440 fn test_complex_variable_references() {
3441 let wildcard = "SOURCES = $(wildcard *.c)\n";
3443 let parsed = parse(wildcard);
3444 assert!(parsed.errors.is_empty());
3445
3446 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3448 let parsed = parse(nested);
3449 assert!(parsed.errors.is_empty());
3450
3451 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3453 let parsed = parse(patsubst);
3454 assert!(parsed.errors.is_empty());
3455 }
3456
3457 #[test]
3458 fn test_complex_variable_references_minimal() {
3459 let wildcard = "SOURCES = $(wildcard *.c)\n";
3461 let parsed = parse(wildcard);
3462 assert!(parsed.errors.is_empty());
3463
3464 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3466 let parsed = parse(nested);
3467 assert!(parsed.errors.is_empty());
3468
3469 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3471 let parsed = parse(patsubst);
3472 assert!(parsed.errors.is_empty());
3473 }
3474
3475 #[test]
3476 fn test_multiline_variable_with_backslash() {
3477 let content = r#"
3478LONG_VAR = This is a long variable \
3479 that continues on the next line \
3480 and even one more line
3481"#;
3482
3483 let mut buf = content.as_bytes();
3485 let makefile =
3486 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
3487
3488 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3490 assert_eq!(
3491 vars.len(),
3492 1,
3493 "Expected 1 variable but found {}",
3494 vars.len()
3495 );
3496 let var_value = vars[0].raw_value();
3497 assert!(var_value.is_some(), "Variable value is None");
3498
3499 let value_str = var_value.unwrap();
3501 assert!(
3502 value_str.contains("long variable"),
3503 "Value doesn't contain expected content"
3504 );
3505 }
3506
3507 #[test]
3508 fn test_multiline_variable_with_mixed_operators() {
3509 let content = r#"
3510PREFIX ?= /usr/local
3511CFLAGS := -Wall -O2 \
3512 -I$(PREFIX)/include \
3513 -DDEBUG
3514"#;
3515 let mut buf = content.as_bytes();
3517 let makefile = Makefile::read_relaxed(&mut buf)
3518 .expect("Failed to parse multiline variable with operators");
3519
3520 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3522 assert!(
3523 vars.len() >= 1,
3524 "Expected at least 1 variable, found {}",
3525 vars.len()
3526 );
3527
3528 let prefix_var = vars
3530 .iter()
3531 .find(|v| v.name().unwrap_or_default() == "PREFIX");
3532 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
3533 assert!(
3534 prefix_var.unwrap().raw_value().is_some(),
3535 "PREFIX variable has no value"
3536 );
3537
3538 let cflags_var = vars
3540 .iter()
3541 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
3542 assert!(
3543 cflags_var.is_some(),
3544 "Expected to find CFLAGS variable (or part of it)"
3545 );
3546 }
3547
3548 #[test]
3549 fn test_indented_help_text() {
3550 let content = r#"
3551.PHONY: help
3552help:
3553 @echo "Available targets:"
3554 @echo " build - Build the project"
3555 @echo " test - Run tests"
3556 @echo " clean - Remove build artifacts"
3557"#;
3558 let mut buf = content.as_bytes();
3560 let makefile =
3561 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
3562
3563 let rules = makefile.rules().collect::<Vec<_>>();
3565 assert!(!rules.is_empty(), "Expected at least one rule");
3566
3567 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
3569 assert!(help_rule.is_some(), "Expected to find help rule");
3570
3571 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
3573 assert!(
3574 !recipes.is_empty(),
3575 "Expected at least one recipe line in help rule"
3576 );
3577 assert!(
3578 recipes.iter().any(|r| r.contains("Available targets")),
3579 "Expected to find 'Available targets' in recipes"
3580 );
3581 }
3582
3583 #[test]
3584 fn test_indented_lines_in_conditionals() {
3585 let content = r#"
3586ifdef DEBUG
3587 CFLAGS += -g -DDEBUG
3588 # This is a comment inside conditional
3589 ifdef VERBOSE
3590 CFLAGS += -v
3591 endif
3592endif
3593"#;
3594 let mut buf = content.as_bytes();
3596 let makefile = Makefile::read_relaxed(&mut buf)
3597 .expect("Failed to parse indented lines in conditionals");
3598
3599 let code = makefile.code();
3601 assert!(code.contains("ifdef DEBUG"));
3602 assert!(code.contains("ifdef VERBOSE"));
3603 assert!(code.contains("endif"));
3604 }
3605
3606 #[test]
3607 fn test_recipe_with_colon() {
3608 let content = r#"
3609build:
3610 @echo "Building at: $(shell date)"
3611 gcc -o program main.c
3612"#;
3613 let parsed = parse(content);
3614 assert!(
3615 parsed.errors.is_empty(),
3616 "Failed to parse recipe with colon: {:?}",
3617 parsed.errors
3618 );
3619 }
3620
3621 #[test]
3622 #[ignore]
3623 fn test_double_colon_rules() {
3624 let content = r#"
3627%.o :: %.c
3628 $(CC) -c $< -o $@
3629
3630# Double colon allows multiple rules for same target
3631all:: prerequisite1
3632 @echo "First rule for all"
3633
3634all:: prerequisite2
3635 @echo "Second rule for all"
3636"#;
3637 let mut buf = content.as_bytes();
3638 let makefile =
3639 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
3640
3641 let rules = makefile.rules().collect::<Vec<_>>();
3643 assert!(!rules.is_empty(), "Expected at least one rule");
3644
3645 let all_rules = rules
3647 .iter()
3648 .filter(|r| r.targets().any(|t| t.contains("all")));
3649 assert!(
3650 all_rules.count() > 0,
3651 "Expected to find at least one rule containing 'all'"
3652 );
3653 }
3654
3655 #[test]
3656 fn test_elif_directive() {
3657 let content = r#"
3658ifeq ($(OS),Windows_NT)
3659 TARGET = windows
3660elif ifeq ($(OS),Darwin)
3661 TARGET = macos
3662elif ifeq ($(OS),Linux)
3663 TARGET = linux
3664else
3665 TARGET = unknown
3666endif
3667"#;
3668 let mut buf = content.as_bytes();
3670 let _makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse elif directive");
3671
3672 }
3675
3676 #[test]
3677 fn test_ambiguous_assignment_vs_rule() {
3678 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
3680
3681 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
3682 let makefile =
3683 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
3684
3685 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3686 let rules = makefile.rules().collect::<Vec<_>>();
3687
3688 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
3689 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
3690
3691 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
3692
3693 const SIMPLE_RULE: &str = "target: dependency\n";
3695
3696 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
3697 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
3698
3699 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3700 let rules = makefile.rules().collect::<Vec<_>>();
3701
3702 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
3703 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
3704
3705 let rule = &rules[0];
3706 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
3707 }
3708
3709 #[test]
3710 fn test_nested_conditionals() {
3711 let content = r#"
3712ifdef RELEASE
3713 CFLAGS += -O3
3714 ifndef DEBUG
3715 ifneq ($(ARCH),arm)
3716 CFLAGS += -march=native
3717 else
3718 CFLAGS += -mcpu=cortex-a72
3719 endif
3720 endif
3721endif
3722"#;
3723 let mut buf = content.as_bytes();
3725 let makefile =
3726 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
3727
3728 let code = makefile.code();
3730 assert!(code.contains("ifdef RELEASE"));
3731 assert!(code.contains("ifndef DEBUG"));
3732 assert!(code.contains("ifneq"));
3733 }
3734
3735 #[test]
3736 fn test_space_indented_recipes() {
3737 let content = r#"
3740build:
3741 @echo "Building with spaces instead of tabs"
3742 gcc -o program main.c
3743"#;
3744 let mut buf = content.as_bytes();
3746 let makefile =
3747 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
3748
3749 let rules = makefile.rules().collect::<Vec<_>>();
3751 assert!(!rules.is_empty(), "Expected at least one rule");
3752
3753 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
3755 assert!(build_rule.is_some(), "Expected to find build rule");
3756 }
3757
3758 #[test]
3759 fn test_complex_variable_functions() {
3760 let content = r#"
3761FILES := $(shell find . -name "*.c")
3762OBJS := $(patsubst %.c,%.o,$(FILES))
3763NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
3764HEADERS := ${wildcard *.h}
3765"#;
3766 let parsed = parse(content);
3767 assert!(
3768 parsed.errors.is_empty(),
3769 "Failed to parse complex variable functions: {:?}",
3770 parsed.errors
3771 );
3772 }
3773
3774 #[test]
3775 fn test_nested_variable_expansions() {
3776 let content = r#"
3777VERSION = 1.0
3778PACKAGE = myapp
3779TARBALL = $(PACKAGE)-$(VERSION).tar.gz
3780INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
3781"#;
3782 let parsed = parse(content);
3783 assert!(
3784 parsed.errors.is_empty(),
3785 "Failed to parse nested variable expansions: {:?}",
3786 parsed.errors
3787 );
3788 }
3789
3790 #[test]
3791 fn test_special_directives() {
3792 let content = r#"
3793# Special makefile directives
3794.PHONY: all clean
3795.SUFFIXES: .c .o
3796.DEFAULT: all
3797
3798# Variable definition and export directive
3799export PATH := /usr/bin:/bin
3800"#;
3801 let mut buf = content.as_bytes();
3803 let makefile =
3804 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
3805
3806 let rules = makefile.rules().collect::<Vec<_>>();
3808
3809 let phony_rule = rules
3811 .iter()
3812 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
3813 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
3814
3815 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3817 assert!(!vars.is_empty(), "Expected to find at least one variable");
3818 }
3819
3820 #[test]
3823 fn test_comprehensive_real_world_makefile() {
3824 let content = r#"
3826# Basic variable assignment
3827VERSION = 1.0.0
3828
3829# Phony target
3830.PHONY: all clean
3831
3832# Simple rule
3833all:
3834 echo "Building version $(VERSION)"
3835
3836# Another rule with dependencies
3837clean:
3838 rm -f *.o
3839"#;
3840
3841 let parsed = parse(content);
3843
3844 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
3846
3847 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
3849 assert!(!variables.is_empty(), "Expected at least one variable");
3850 assert_eq!(
3851 variables[0].name(),
3852 Some("VERSION".to_string()),
3853 "Expected VERSION variable"
3854 );
3855
3856 let rules = parsed.root().rules().collect::<Vec<_>>();
3858 assert!(!rules.is_empty(), "Expected at least one rule");
3859
3860 let rule_targets: Vec<String> = rules
3862 .iter()
3863 .flat_map(|r| r.targets().collect::<Vec<_>>())
3864 .collect();
3865 assert!(
3866 rule_targets.contains(&".PHONY".to_string()),
3867 "Expected .PHONY rule"
3868 );
3869 assert!(
3870 rule_targets.contains(&"all".to_string()),
3871 "Expected 'all' rule"
3872 );
3873 assert!(
3874 rule_targets.contains(&"clean".to_string()),
3875 "Expected 'clean' rule"
3876 );
3877 }
3878
3879 #[test]
3880 fn test_indented_help_text_outside_rules() {
3881 let content = r#"
3883# Targets with help text
3884help:
3885 @echo "Available targets:"
3886 @echo " build build the project"
3887 @echo " test run tests"
3888 @echo " clean clean build artifacts"
3889
3890# Another target
3891clean:
3892 rm -rf build/
3893"#;
3894
3895 let parsed = parse(content);
3897
3898 assert!(
3900 parsed.errors.is_empty(),
3901 "Failed to parse indented help text"
3902 );
3903
3904 let rules = parsed.root().rules().collect::<Vec<_>>();
3906 assert_eq!(rules.len(), 2, "Expected to find two rules");
3907
3908 let help_rule = rules
3910 .iter()
3911 .find(|r| r.targets().any(|t| t == "help"))
3912 .expect("Expected to find help rule");
3913
3914 let clean_rule = rules
3915 .iter()
3916 .find(|r| r.targets().any(|t| t == "clean"))
3917 .expect("Expected to find clean rule");
3918
3919 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
3921 assert!(
3922 !help_recipes.is_empty(),
3923 "Help rule should have recipe lines"
3924 );
3925 assert!(
3926 help_recipes
3927 .iter()
3928 .any(|line| line.contains("Available targets")),
3929 "Help recipes should include 'Available targets' line"
3930 );
3931
3932 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
3934 assert!(
3935 !clean_recipes.is_empty(),
3936 "Clean rule should have recipe lines"
3937 );
3938 assert!(
3939 clean_recipes.iter().any(|line| line.contains("rm -rf")),
3940 "Clean recipes should include 'rm -rf' command"
3941 );
3942 }
3943
3944 #[test]
3945 fn test_makefile1_phony_pattern() {
3946 let content = "#line 2145\n.PHONY: $(PHONY)\n";
3948
3949 let result = parse(content);
3951
3952 assert!(
3954 result.errors.is_empty(),
3955 "Failed to parse .PHONY: $(PHONY) pattern"
3956 );
3957
3958 let rules = result.root().rules().collect::<Vec<_>>();
3960 assert_eq!(rules.len(), 1, "Expected 1 rule");
3961 assert_eq!(
3962 rules[0].targets().next().unwrap(),
3963 ".PHONY",
3964 "Expected .PHONY rule"
3965 );
3966
3967 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
3969 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
3970 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
3971 }
3972
3973 #[test]
3974 fn test_skip_until_newline_behavior() {
3975 let input = "text without newline";
3977 let parsed = parse(input);
3978 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3980
3981 let input_with_newline = "text\nafter newline";
3982 let parsed2 = parse(input_with_newline);
3983 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
3984 }
3985
3986 #[test]
3987 fn test_error_with_indent_token() {
3988 let input = "\tinvalid indented line";
3990 let parsed = parse(input);
3991 assert!(!parsed.errors.is_empty());
3993
3994 let error_msg = &parsed.errors[0].message;
3995 assert!(error_msg.contains("indented") || error_msg.contains("part of a rule"));
3996 }
3997
3998 #[test]
3999 fn test_conditional_token_handling() {
4000 let input = r#"
4002ifndef VAR
4003 CFLAGS = -DTEST
4004endif
4005"#;
4006 let parsed = parse(input);
4007 let makefile = parsed.root();
4009 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
4010 let nested = r#"
4014ifdef DEBUG
4015 ifndef RELEASE
4016 CFLAGS = -g
4017 endif
4018endif
4019"#;
4020 let parsed_nested = parse(nested);
4021 let _makefile = parsed_nested.root();
4023 }
4024
4025 #[test]
4026 fn test_include_vs_conditional_logic() {
4027 let input = r#"
4029include file.mk
4030ifdef VAR
4031 VALUE = 1
4032endif
4033"#;
4034 let parsed = parse(input);
4035 let makefile = parsed.root();
4037 let includes = makefile.includes().collect::<Vec<_>>();
4038 assert!(includes.len() >= 1 || parsed.errors.len() > 0);
4040
4041 let optional_include = r#"
4043-include optional.mk
4044ifndef VAR
4045 VALUE = default
4046endif
4047"#;
4048 let parsed2 = parse(optional_include);
4049 let _makefile = parsed2.root();
4051 }
4052
4053 #[test]
4054 fn test_balanced_parens_counting() {
4055 let input = r#"
4057VAR = $(call func,$(nested,arg),extra)
4058COMPLEX = $(if $(condition),$(then_val),$(else_val))
4059"#;
4060 let parsed = parse(input);
4061 assert!(parsed.errors.is_empty());
4062
4063 let makefile = parsed.root();
4064 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4065 assert_eq!(vars.len(), 2);
4066 }
4067
4068 #[test]
4069 fn test_documentation_lookahead() {
4070 let input = r#"
4072# Documentation comment
4073help:
4074 @echo "Usage instructions"
4075 @echo "More help text"
4076"#;
4077 let parsed = parse(input);
4078 assert!(parsed.errors.is_empty());
4079
4080 let makefile = parsed.root();
4081 let rules = makefile.rules().collect::<Vec<_>>();
4082 assert_eq!(rules.len(), 1);
4083 assert_eq!(rules[0].targets().next().unwrap(), "help");
4084 }
4085
4086 #[test]
4087 fn test_edge_case_empty_input() {
4088 let parsed = parse("");
4090 assert!(parsed.errors.is_empty());
4091
4092 let parsed2 = parse(" \n \n");
4094 let _makefile = parsed2.root();
4097 }
4098
4099 #[test]
4100 fn test_malformed_conditional_recovery() {
4101 let input = r#"
4103ifdef
4104 # Missing condition variable
4105endif
4106"#;
4107 let parsed = parse(input);
4108 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
4111 }
4112
4113 #[test]
4114 fn test_replace_rule() {
4115 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4116 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4117
4118 makefile.replace_rule(0, new_rule).unwrap();
4119
4120 let targets: Vec<_> = makefile
4121 .rules()
4122 .flat_map(|r| r.targets().collect::<Vec<_>>())
4123 .collect();
4124 assert_eq!(targets, vec!["new_rule", "rule2"]);
4125
4126 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
4127 assert_eq!(recipes, vec!["new_command"]);
4128 }
4129
4130 #[test]
4131 fn test_replace_rule_out_of_bounds() {
4132 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4133 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4134
4135 let result = makefile.replace_rule(5, new_rule);
4136 assert!(result.is_err());
4137 }
4138
4139 #[test]
4140 fn test_remove_rule() {
4141 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
4142 .parse()
4143 .unwrap();
4144
4145 let removed = makefile.remove_rule(1).unwrap();
4146 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
4147
4148 let remaining_targets: Vec<_> = makefile
4149 .rules()
4150 .flat_map(|r| r.targets().collect::<Vec<_>>())
4151 .collect();
4152 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
4153 assert_eq!(makefile.rules().count(), 2);
4154 }
4155
4156 #[test]
4157 fn test_remove_rule_out_of_bounds() {
4158 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4159
4160 let result = makefile.remove_rule(5);
4161 assert!(result.is_err());
4162 }
4163
4164 #[test]
4165 fn test_insert_rule() {
4166 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4167 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
4168
4169 makefile.insert_rule(1, new_rule).unwrap();
4170
4171 let targets: Vec<_> = makefile
4172 .rules()
4173 .flat_map(|r| r.targets().collect::<Vec<_>>())
4174 .collect();
4175 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
4176 assert_eq!(makefile.rules().count(), 3);
4177 }
4178
4179 #[test]
4180 fn test_insert_rule_at_end() {
4181 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4182 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
4183
4184 makefile.insert_rule(1, new_rule).unwrap();
4185
4186 let targets: Vec<_> = makefile
4187 .rules()
4188 .flat_map(|r| r.targets().collect::<Vec<_>>())
4189 .collect();
4190 assert_eq!(targets, vec!["rule1", "end_rule"]);
4191 }
4192
4193 #[test]
4194 fn test_insert_rule_out_of_bounds() {
4195 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4196 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4197
4198 let result = makefile.insert_rule(5, new_rule);
4199 assert!(result.is_err());
4200 }
4201
4202 #[test]
4203 fn test_remove_command() {
4204 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4205 .parse()
4206 .unwrap();
4207
4208 rule.remove_command(1);
4209 let recipes: Vec<_> = rule.recipes().collect();
4210 assert_eq!(recipes, vec!["command1", "command3"]);
4211 assert_eq!(rule.recipe_count(), 2);
4212 }
4213
4214 #[test]
4215 fn test_remove_command_out_of_bounds() {
4216 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4217
4218 let result = rule.remove_command(5);
4219 assert!(!result);
4220 }
4221
4222 #[test]
4223 fn test_insert_command() {
4224 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
4225
4226 rule.insert_command(1, "command2");
4227 let recipes: Vec<_> = rule.recipes().collect();
4228 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
4229 }
4230
4231 #[test]
4232 fn test_insert_command_at_end() {
4233 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4234
4235 rule.insert_command(1, "command2");
4236 let recipes: Vec<_> = rule.recipes().collect();
4237 assert_eq!(recipes, vec!["command1", "command2"]);
4238 }
4239
4240 #[test]
4241 fn test_insert_command_in_empty_rule() {
4242 let mut rule: Rule = "rule:\n".parse().unwrap();
4243
4244 rule.insert_command(0, "new_command");
4245 let recipes: Vec<_> = rule.recipes().collect();
4246 assert_eq!(recipes, vec!["new_command"]);
4247 }
4248
4249 #[test]
4250 fn test_recipe_count() {
4251 let rule1: Rule = "rule:\n".parse().unwrap();
4252 assert_eq!(rule1.recipe_count(), 0);
4253
4254 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
4255 assert_eq!(rule2.recipe_count(), 2);
4256 }
4257
4258 #[test]
4259 fn test_clear_commands() {
4260 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4261 .parse()
4262 .unwrap();
4263
4264 rule.clear_commands();
4265 assert_eq!(rule.recipe_count(), 0);
4266
4267 let recipes: Vec<_> = rule.recipes().collect();
4268 assert_eq!(recipes, Vec::<String>::new());
4269
4270 let targets: Vec<_> = rule.targets().collect();
4272 assert_eq!(targets, vec!["rule"]);
4273 }
4274
4275 #[test]
4276 fn test_clear_commands_empty_rule() {
4277 let mut rule: Rule = "rule:\n".parse().unwrap();
4278
4279 rule.clear_commands();
4280 assert_eq!(rule.recipe_count(), 0);
4281
4282 let targets: Vec<_> = rule.targets().collect();
4283 assert_eq!(targets, vec!["rule"]);
4284 }
4285
4286 #[test]
4287 fn test_rule_manipulation_preserves_structure() {
4288 let input = r#"# Comment
4290VAR = value
4291
4292rule1:
4293 command1
4294
4295# Another comment
4296rule2:
4297 command2
4298
4299VAR2 = value2
4300"#;
4301
4302 let mut makefile: Makefile = input.parse().unwrap();
4303 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4304
4305 makefile.insert_rule(1, new_rule).unwrap();
4307
4308 let targets: Vec<_> = makefile
4310 .rules()
4311 .flat_map(|r| r.targets().collect::<Vec<_>>())
4312 .collect();
4313 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
4314
4315 let vars: Vec<_> = makefile.variable_definitions().collect();
4317 assert_eq!(vars.len(), 2);
4318
4319 let output = makefile.code();
4321 assert!(output.contains("# Comment"));
4322 assert!(output.contains("VAR = value"));
4323 assert!(output.contains("# Another comment"));
4324 assert!(output.contains("VAR2 = value2"));
4325 }
4326
4327 #[test]
4328 fn test_replace_rule_with_multiple_targets() {
4329 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
4330 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
4331
4332 makefile.replace_rule(0, new_rule).unwrap();
4333
4334 let targets: Vec<_> = makefile
4335 .rules()
4336 .flat_map(|r| r.targets().collect::<Vec<_>>())
4337 .collect();
4338 assert_eq!(targets, vec!["new_target"]);
4339 }
4340
4341 #[test]
4342 fn test_empty_makefile_operations() {
4343 let mut makefile = Makefile::new();
4344
4345 assert!(makefile
4347 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
4348 .is_err());
4349 assert!(makefile.remove_rule(0).is_err());
4350
4351 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
4353 makefile.insert_rule(0, new_rule).unwrap();
4354 assert_eq!(makefile.rules().count(), 1);
4355 }
4356
4357 #[test]
4358 fn test_command_operations_preserve_indentation() {
4359 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
4360 .parse()
4361 .unwrap();
4362
4363 rule.insert_command(1, "middle_command");
4364 let recipes: Vec<_> = rule.recipes().collect();
4365 assert_eq!(
4366 recipes,
4367 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
4368 );
4369 }
4370
4371 #[test]
4372 fn test_rule_operations_with_variables_and_includes() {
4373 let input = r#"VAR1 = value1
4374include common.mk
4375
4376rule1:
4377 command1
4378
4379VAR2 = value2
4380include other.mk
4381
4382rule2:
4383 command2
4384"#;
4385
4386 let mut makefile: Makefile = input.parse().unwrap();
4387
4388 makefile.remove_rule(0).unwrap();
4390
4391 let output = makefile.code();
4393 assert!(output.contains("VAR1 = value1"));
4394 assert!(output.contains("include common.mk"));
4395 assert!(output.contains("VAR2 = value2"));
4396 assert!(output.contains("include other.mk"));
4397
4398 assert_eq!(makefile.rules().count(), 1);
4400 let remaining_targets: Vec<_> = makefile
4401 .rules()
4402 .flat_map(|r| r.targets().collect::<Vec<_>>())
4403 .collect();
4404 assert_eq!(remaining_targets, vec!["rule2"]);
4405 }
4406
4407 #[test]
4408 fn test_command_manipulation_edge_cases() {
4409 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
4411 assert_eq!(empty_rule.recipe_count(), 0);
4412
4413 empty_rule.insert_command(0, "first_command");
4414 assert_eq!(empty_rule.recipe_count(), 1);
4415
4416 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
4418 empty_rule2.clear_commands();
4419 assert_eq!(empty_rule2.recipe_count(), 0);
4420 }
4421
4422 #[test]
4423 fn test_archive_member_parsing() {
4424 let input = "libfoo.a(bar.o): bar.c\n\tgcc -c bar.c -o bar.o\n\tar r libfoo.a bar.o\n";
4426 let parsed = parse(input);
4427 assert!(
4428 parsed.errors.is_empty(),
4429 "Should parse archive member without errors"
4430 );
4431
4432 let makefile = parsed.root();
4433 let rules: Vec<_> = makefile.rules().collect();
4434 assert_eq!(rules.len(), 1);
4435
4436 let target_text = rules[0].targets().next().unwrap();
4438 assert_eq!(target_text, "libfoo.a(bar.o)");
4439 }
4440
4441 #[test]
4442 fn test_archive_member_multiple_members() {
4443 let input = "libfoo.a(bar.o baz.o): bar.c baz.c\n\tgcc -c bar.c baz.c\n\tar r libfoo.a bar.o baz.o\n";
4445 let parsed = parse(input);
4446 assert!(
4447 parsed.errors.is_empty(),
4448 "Should parse multiple archive members"
4449 );
4450
4451 let makefile = parsed.root();
4452 let rules: Vec<_> = makefile.rules().collect();
4453 assert_eq!(rules.len(), 1);
4454 }
4455
4456 #[test]
4457 fn test_archive_member_in_dependencies() {
4458 let input =
4460 "program: main.o libfoo.a(bar.o) libfoo.a(baz.o)\n\tgcc -o program main.o libfoo.a\n";
4461 let parsed = parse(input);
4462 assert!(
4463 parsed.errors.is_empty(),
4464 "Should parse archive members in dependencies"
4465 );
4466
4467 let makefile = parsed.root();
4468 let rules: Vec<_> = makefile.rules().collect();
4469 assert_eq!(rules.len(), 1);
4470 }
4471
4472 #[test]
4473 fn test_archive_member_with_variables() {
4474 let input = "$(LIB)($(OBJ)): $(SRC)\n\t$(CC) -c $(SRC)\n\t$(AR) r $(LIB) $(OBJ)\n";
4476 let parsed = parse(input);
4477 assert!(
4479 parsed.errors.is_empty(),
4480 "Should parse archive members with variables"
4481 );
4482 }
4483
4484 #[test]
4485 fn test_archive_member_ast_access() {
4486 let input = "libtest.a(foo.o bar.o): foo.c bar.c\n\tgcc -c foo.c bar.c\n";
4488 let parsed = parse(input);
4489 let makefile = parsed.root();
4490
4491 let archive_member_count = makefile
4493 .syntax()
4494 .descendants()
4495 .filter(|n| n.kind() == ARCHIVE_MEMBERS)
4496 .count();
4497
4498 assert!(
4499 archive_member_count > 0,
4500 "Should find ARCHIVE_MEMBERS nodes in AST"
4501 );
4502 }
4503
4504 #[test]
4505 fn test_large_makefile_performance() {
4506 let mut makefile = Makefile::new();
4508
4509 for i in 0..100 {
4511 let rule_name = format!("rule{}", i);
4512 let _rule = makefile
4513 .add_rule(&rule_name)
4514 .push_command(&format!("command{}", i));
4515 }
4516
4517 assert_eq!(makefile.rules().count(), 100);
4518
4519 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
4521 makefile.replace_rule(50, new_rule).unwrap();
4522
4523 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
4525 assert_eq!(rule_50_targets, vec!["middle_rule"]);
4526
4527 assert_eq!(makefile.rules().count(), 100); }
4529
4530 #[test]
4531 fn test_complex_recipe_manipulation() {
4532 let mut complex_rule: Rule = r#"complex:
4533 @echo "Starting build"
4534 $(CC) $(CFLAGS) -o $@ $<
4535 @echo "Build complete"
4536 chmod +x $@
4537"#
4538 .parse()
4539 .unwrap();
4540
4541 assert_eq!(complex_rule.recipe_count(), 4);
4542
4543 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
4548 assert_eq!(final_recipes.len(), 2);
4549 assert!(final_recipes[0].contains("$(CC)"));
4550 assert!(final_recipes[1].contains("chmod"));
4551 }
4552
4553 #[test]
4554 fn test_variable_definition_remove() {
4555 let makefile: Makefile = r#"VAR1 = value1
4556VAR2 = value2
4557VAR3 = value3
4558"#
4559 .parse()
4560 .unwrap();
4561
4562 assert_eq!(makefile.variable_definitions().count(), 3);
4564
4565 let mut var2 = makefile
4567 .variable_definitions()
4568 .nth(1)
4569 .expect("Should have second variable");
4570 assert_eq!(var2.name(), Some("VAR2".to_string()));
4571 var2.remove();
4572
4573 assert_eq!(makefile.variable_definitions().count(), 2);
4575 let var_names: Vec<_> = makefile
4576 .variable_definitions()
4577 .filter_map(|v| v.name())
4578 .collect();
4579 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
4580 }
4581
4582 #[test]
4583 fn test_variable_definition_set_value() {
4584 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
4585
4586 let mut var = makefile
4587 .variable_definitions()
4588 .next()
4589 .expect("Should have variable");
4590 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4591
4592 var.set_value("new_value");
4594
4595 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4597 assert!(makefile.code().contains("VAR = new_value"));
4598 }
4599
4600 #[test]
4601 fn test_variable_definition_set_value_preserves_format() {
4602 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
4603
4604 let mut var = makefile
4605 .variable_definitions()
4606 .next()
4607 .expect("Should have variable");
4608 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4609
4610 var.set_value("new_value");
4612
4613 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4615 let code = makefile.code();
4616 assert!(code.contains("export"), "Should preserve export prefix");
4617 assert!(code.contains(":="), "Should preserve := operator");
4618 assert!(code.contains("new_value"), "Should have new value");
4619 }
4620
4621 #[test]
4622 fn test_makefile_find_variable() {
4623 let makefile: Makefile = r#"VAR1 = value1
4624VAR2 = value2
4625VAR3 = value3
4626"#
4627 .parse()
4628 .unwrap();
4629
4630 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4632 assert_eq!(vars.len(), 1);
4633 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4634 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4635
4636 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
4638 }
4639
4640 #[test]
4641 fn test_makefile_find_variable_with_export() {
4642 let makefile: Makefile = r#"VAR1 = value1
4643export VAR2 := value2
4644VAR3 = value3
4645"#
4646 .parse()
4647 .unwrap();
4648
4649 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4651 assert_eq!(vars.len(), 1);
4652 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4653 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4654 }
4655
4656 #[test]
4657 fn test_makefile_find_variable_multiple() {
4658 let makefile: Makefile = r#"VAR1 = value1
4659VAR1 = value2
4660VAR2 = other
4661VAR1 = value3
4662"#
4663 .parse()
4664 .unwrap();
4665
4666 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
4668 assert_eq!(vars.len(), 3);
4669 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
4670 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
4671 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
4672
4673 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
4675 assert_eq!(var2s.len(), 1);
4676 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
4677 }
4678
4679 #[test]
4680 fn test_variable_remove_and_find() {
4681 let makefile: Makefile = r#"VAR1 = value1
4682VAR2 = value2
4683VAR3 = value3
4684"#
4685 .parse()
4686 .unwrap();
4687
4688 let mut var2 = makefile
4690 .find_variable("VAR2")
4691 .next()
4692 .expect("Should find VAR2");
4693 var2.remove();
4694
4695 assert_eq!(makefile.find_variable("VAR2").count(), 0);
4697
4698 assert_eq!(makefile.find_variable("VAR1").count(), 1);
4700 assert_eq!(makefile.find_variable("VAR3").count(), 1);
4701 }
4702
4703 #[test]
4704 fn test_variable_remove_with_comment() {
4705 let makefile: Makefile = r#"VAR1 = value1
4706# This is a comment about VAR2
4707VAR2 = value2
4708VAR3 = value3
4709"#
4710 .parse()
4711 .unwrap();
4712
4713 let mut var2 = makefile
4715 .variable_definitions()
4716 .nth(1)
4717 .expect("Should have second variable");
4718 assert_eq!(var2.name(), Some("VAR2".to_string()));
4719 var2.remove();
4720
4721 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4723 }
4724
4725 #[test]
4726 fn test_variable_remove_with_multiple_comments() {
4727 let makefile: Makefile = r#"VAR1 = value1
4728# Comment line 1
4729# Comment line 2
4730# Comment line 3
4731VAR2 = value2
4732VAR3 = value3
4733"#
4734 .parse()
4735 .unwrap();
4736
4737 let mut var2 = makefile
4739 .variable_definitions()
4740 .nth(1)
4741 .expect("Should have second variable");
4742 var2.remove();
4743
4744 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4746 }
4747
4748 #[test]
4749 fn test_variable_remove_with_empty_line() {
4750 let makefile: Makefile = r#"VAR1 = value1
4751
4752# Comment about VAR2
4753VAR2 = value2
4754VAR3 = value3
4755"#
4756 .parse()
4757 .unwrap();
4758
4759 let mut var2 = makefile
4761 .variable_definitions()
4762 .nth(1)
4763 .expect("Should have second variable");
4764 var2.remove();
4765
4766 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4769 }
4770
4771 #[test]
4772 fn test_variable_remove_with_multiple_empty_lines() {
4773 let makefile: Makefile = r#"VAR1 = value1
4774
4775
4776# Comment about VAR2
4777VAR2 = value2
4778VAR3 = value3
4779"#
4780 .parse()
4781 .unwrap();
4782
4783 let mut var2 = makefile
4785 .variable_definitions()
4786 .nth(1)
4787 .expect("Should have second variable");
4788 var2.remove();
4789
4790 assert_eq!(makefile.code(), "VAR1 = value1\n\nVAR3 = value3\n");
4793 }
4794
4795 #[test]
4796 fn test_rule_remove_with_comment() {
4797 let makefile: Makefile = r#"rule1:
4798 command1
4799
4800# Comment about rule2
4801rule2:
4802 command2
4803rule3:
4804 command3
4805"#
4806 .parse()
4807 .unwrap();
4808
4809 let rule2 = makefile.rules().nth(1).expect("Should have second rule");
4811 rule2.remove().unwrap();
4812
4813 assert_eq!(
4816 makefile.code(),
4817 "rule1:\n\tcommand1\n\nrule3:\n\tcommand3\n"
4818 );
4819 }
4820
4821 #[test]
4822 fn test_rule_add_prerequisite() {
4823 let mut rule: Rule = "target: dep1\n".parse().unwrap();
4824 rule.add_prerequisite("dep2").unwrap();
4825 assert_eq!(
4826 rule.prerequisites().collect::<Vec<_>>(),
4827 vec!["dep1", "dep2"]
4828 );
4829 }
4830
4831 #[test]
4832 fn test_rule_remove_prerequisite() {
4833 let mut rule: Rule = "target: dep1 dep2 dep3\n".parse().unwrap();
4834 assert!(rule.remove_prerequisite("dep2").unwrap());
4835 assert_eq!(
4836 rule.prerequisites().collect::<Vec<_>>(),
4837 vec!["dep1", "dep3"]
4838 );
4839 assert!(!rule.remove_prerequisite("nonexistent").unwrap());
4840 }
4841
4842 #[test]
4843 fn test_rule_set_prerequisites() {
4844 let mut rule: Rule = "target: old_dep\n".parse().unwrap();
4845 rule.set_prerequisites(vec!["new_dep1", "new_dep2"])
4846 .unwrap();
4847 assert_eq!(
4848 rule.prerequisites().collect::<Vec<_>>(),
4849 vec!["new_dep1", "new_dep2"]
4850 );
4851 }
4852
4853 #[test]
4854 fn test_rule_set_prerequisites_empty() {
4855 let mut rule: Rule = "target: dep1 dep2\n".parse().unwrap();
4856 rule.set_prerequisites(vec![]).unwrap();
4857 assert_eq!(rule.prerequisites().collect::<Vec<_>>().len(), 0);
4858 }
4859
4860 #[test]
4861 fn test_rule_remove() {
4862 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4863 let rule = makefile.find_rule_by_target("rule1").unwrap();
4864 rule.remove().unwrap();
4865 assert_eq!(makefile.rules().count(), 1);
4866 assert!(makefile.find_rule_by_target("rule1").is_none());
4867 assert!(makefile.find_rule_by_target("rule2").is_some());
4868 }
4869
4870 #[test]
4871 fn test_makefile_find_rule_by_target() {
4872 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4873 let rule = makefile.find_rule_by_target("rule2");
4874 assert!(rule.is_some());
4875 assert_eq!(rule.unwrap().targets().collect::<Vec<_>>(), vec!["rule2"]);
4876 assert!(makefile.find_rule_by_target("nonexistent").is_none());
4877 }
4878
4879 #[test]
4880 fn test_makefile_find_rules_by_target() {
4881 let makefile: Makefile = "rule1:\n\tcommand1\nrule1:\n\tcommand2\nrule2:\n\tcommand3\n"
4882 .parse()
4883 .unwrap();
4884 assert_eq!(makefile.find_rules_by_target("rule1").count(), 2);
4885 assert_eq!(makefile.find_rules_by_target("rule2").count(), 1);
4886 assert_eq!(makefile.find_rules_by_target("nonexistent").count(), 0);
4887 }
4888
4889 #[test]
4890 fn test_makefile_add_phony_target() {
4891 let mut makefile = Makefile::new();
4892 makefile.add_phony_target("clean").unwrap();
4893 assert!(makefile.is_phony("clean"));
4894 assert_eq!(makefile.phony_targets().collect::<Vec<_>>(), vec!["clean"]);
4895 }
4896
4897 #[test]
4898 fn test_makefile_add_phony_target_existing() {
4899 let mut makefile: Makefile = ".PHONY: test\n".parse().unwrap();
4900 makefile.add_phony_target("clean").unwrap();
4901 assert!(makefile.is_phony("test"));
4902 assert!(makefile.is_phony("clean"));
4903 let targets: Vec<_> = makefile.phony_targets().collect();
4904 assert!(targets.contains(&"test".to_string()));
4905 assert!(targets.contains(&"clean".to_string()));
4906 }
4907
4908 #[test]
4909 fn test_makefile_remove_phony_target() {
4910 let mut makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4911 assert!(makefile.remove_phony_target("clean").unwrap());
4912 assert!(!makefile.is_phony("clean"));
4913 assert!(makefile.is_phony("test"));
4914 assert!(!makefile.remove_phony_target("nonexistent").unwrap());
4915 }
4916
4917 #[test]
4918 fn test_makefile_remove_phony_target_last() {
4919 let mut makefile: Makefile = ".PHONY: clean\n".parse().unwrap();
4920 assert!(makefile.remove_phony_target("clean").unwrap());
4921 assert!(!makefile.is_phony("clean"));
4922 assert!(makefile.find_rule_by_target(".PHONY").is_none());
4924 }
4925
4926 #[test]
4927 fn test_makefile_is_phony() {
4928 let makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4929 assert!(makefile.is_phony("clean"));
4930 assert!(makefile.is_phony("test"));
4931 assert!(!makefile.is_phony("build"));
4932 }
4933
4934 #[test]
4935 fn test_makefile_phony_targets() {
4936 let makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
4937 let phony_targets: Vec<_> = makefile.phony_targets().collect();
4938 assert_eq!(phony_targets, vec!["clean", "test", "build"]);
4939 }
4940
4941 #[test]
4942 fn test_makefile_phony_targets_empty() {
4943 let makefile = Makefile::new();
4944 assert_eq!(makefile.phony_targets().count(), 0);
4945 }
4946}