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 collected_elements = vec![];
1381 let mut found_comment = false;
1382
1383 let mut current = node.prev_sibling_or_token();
1385 while let Some(element) = current {
1386 match &element {
1387 rowan::NodeOrToken::Token(token) => match token.kind() {
1388 COMMENT => {
1389 if token.text().starts_with("#!") {
1390 break; }
1392 found_comment = true;
1393 collected_elements.push(element.clone());
1394 }
1395 NEWLINE | WHITESPACE => {
1396 collected_elements.push(element.clone());
1397 }
1398 _ => break, },
1400 rowan::NodeOrToken::Node(_) => break, }
1402 current = element.prev_sibling_or_token();
1403 }
1404
1405 let node_index = node.index();
1407 parent.splice_children(node_index..node_index + 1, vec![]);
1408
1409 if found_comment {
1411 let mut consecutive_newlines = 0;
1412 for element in collected_elements.iter().rev() {
1413 let should_remove = match element {
1414 rowan::NodeOrToken::Token(token) => match token.kind() {
1415 COMMENT => {
1416 consecutive_newlines = 0;
1417 true
1418 }
1419 NEWLINE => {
1420 consecutive_newlines += 1;
1421 consecutive_newlines <= 1
1422 }
1423 WHITESPACE => true,
1424 _ => false,
1425 },
1426 _ => false,
1427 };
1428
1429 if should_remove {
1430 let idx = element.index();
1431 parent.splice_children(idx..idx + 1, vec![]);
1432 }
1433 }
1434 }
1435}
1436
1437impl VariableDefinition {
1438 pub fn name(&self) -> Option<String> {
1440 self.syntax().children_with_tokens().find_map(|it| {
1441 it.as_token().and_then(|it| {
1442 if it.kind() == IDENTIFIER && it.text() != "export" {
1443 Some(it.text().to_string())
1444 } else {
1445 None
1446 }
1447 })
1448 })
1449 }
1450
1451 pub fn raw_value(&self) -> Option<String> {
1453 self.syntax()
1454 .children()
1455 .find(|it| it.kind() == EXPR)
1456 .map(|it| it.text().into())
1457 }
1458
1459 pub fn remove(&mut self) {
1472 if let Some(parent) = self.syntax().parent() {
1473 remove_with_preceding_comments(self.syntax(), &parent);
1474 }
1475 }
1476
1477 pub fn set_value(&mut self, new_value: &str) {
1490 let expr_index = self
1492 .syntax()
1493 .children()
1494 .find(|it| it.kind() == EXPR)
1495 .map(|it| it.index());
1496
1497 if let Some(expr_idx) = expr_index {
1498 let mut builder = GreenNodeBuilder::new();
1500 builder.start_node(EXPR.into());
1501 builder.token(IDENTIFIER.into(), new_value);
1502 builder.finish_node();
1503
1504 let new_expr = SyntaxNode::new_root_mut(builder.finish());
1505
1506 self.0
1508 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
1509 }
1510 }
1511}
1512
1513impl Makefile {
1514 pub fn new() -> Makefile {
1516 let mut builder = GreenNodeBuilder::new();
1517
1518 builder.start_node(ROOT.into());
1519 builder.finish_node();
1520
1521 let syntax = SyntaxNode::new_root_mut(builder.finish());
1522 Makefile(syntax)
1523 }
1524
1525 pub fn parse(text: &str) -> crate::Parse<Makefile> {
1527 crate::Parse::<Makefile>::parse_makefile(text)
1528 }
1529
1530 pub fn code(&self) -> String {
1532 self.syntax().text().to_string()
1533 }
1534
1535 pub fn is_root(&self) -> bool {
1537 self.syntax().kind() == ROOT
1538 }
1539
1540 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1542 let mut buf = String::new();
1543 r.read_to_string(&mut buf)?;
1544 buf.parse()
1545 }
1546
1547 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1549 let mut buf = String::new();
1550 r.read_to_string(&mut buf)?;
1551
1552 let parsed = parse(&buf);
1553 Ok(parsed.root())
1554 }
1555
1556 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
1565 self.syntax().children().filter_map(Rule::cast)
1566 }
1567
1568 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1570 self.rules()
1571 .filter(move |rule| rule.targets().any(|t| t == target))
1572 }
1573
1574 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
1576 self.syntax()
1577 .children()
1578 .filter_map(VariableDefinition::cast)
1579 }
1580
1581 pub fn find_variable<'a>(
1596 &'a self,
1597 name: &'a str,
1598 ) -> impl Iterator<Item = VariableDefinition> + 'a {
1599 self.variable_definitions()
1600 .filter(move |var| var.name().as_deref() == Some(name))
1601 }
1602
1603 pub fn add_rule(&mut self, target: &str) -> Rule {
1613 let mut builder = GreenNodeBuilder::new();
1614 builder.start_node(RULE.into());
1615 builder.token(IDENTIFIER.into(), target);
1616 builder.token(OPERATOR.into(), ":");
1617 builder.token(NEWLINE.into(), "\n");
1618 builder.finish_node();
1619
1620 let syntax = SyntaxNode::new_root_mut(builder.finish());
1621 let pos = self.0.children_with_tokens().count();
1622 self.0.splice_children(pos..pos, vec![syntax.into()]);
1623 Rule(self.0.children().nth(pos).unwrap())
1624 }
1625
1626 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1628 let mut buf = String::new();
1629 r.read_to_string(&mut buf)?;
1630
1631 let parsed = parse(&buf);
1632 if !parsed.errors.is_empty() {
1633 Err(Error::Parse(ParseError {
1634 errors: parsed.errors,
1635 }))
1636 } else {
1637 Ok(parsed.root())
1638 }
1639 }
1640
1641 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1652 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1653
1654 if rules.is_empty() {
1655 return Err(Error::Parse(ParseError {
1656 errors: vec![ErrorInfo {
1657 message: "Cannot replace rule in empty makefile".to_string(),
1658 line: 1,
1659 context: "replace_rule".to_string(),
1660 }],
1661 }));
1662 }
1663
1664 if index >= rules.len() {
1665 return Err(Error::Parse(ParseError {
1666 errors: vec![ErrorInfo {
1667 message: format!(
1668 "Rule index {} out of bounds (max {})",
1669 index,
1670 rules.len() - 1
1671 ),
1672 line: 1,
1673 context: "replace_rule".to_string(),
1674 }],
1675 }));
1676 }
1677
1678 let target_node = &rules[index];
1679 let target_index = target_node.index();
1680
1681 self.0.splice_children(
1683 target_index..target_index + 1,
1684 vec![new_rule.0.clone().into()],
1685 );
1686 Ok(())
1687 }
1688
1689 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
1700 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1701
1702 if rules.is_empty() {
1703 return Err(Error::Parse(ParseError {
1704 errors: vec![ErrorInfo {
1705 message: "Cannot remove rule from empty makefile".to_string(),
1706 line: 1,
1707 context: "remove_rule".to_string(),
1708 }],
1709 }));
1710 }
1711
1712 if index >= rules.len() {
1713 return Err(Error::Parse(ParseError {
1714 errors: vec![ErrorInfo {
1715 message: format!(
1716 "Rule index {} out of bounds (max {})",
1717 index,
1718 rules.len() - 1
1719 ),
1720 line: 1,
1721 context: "remove_rule".to_string(),
1722 }],
1723 }));
1724 }
1725
1726 let target_node = rules[index].clone();
1727 let target_index = target_node.index();
1728
1729 self.0
1731 .splice_children(target_index..target_index + 1, vec![]);
1732 Ok(Rule(target_node))
1733 }
1734
1735 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1747 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1748
1749 if index > rules.len() {
1750 return Err(Error::Parse(ParseError {
1751 errors: vec![ErrorInfo {
1752 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1753 line: 1,
1754 context: "insert_rule".to_string(),
1755 }],
1756 }));
1757 }
1758
1759 let target_index = if index == rules.len() {
1760 self.0.children_with_tokens().count()
1762 } else {
1763 rules[index].index()
1765 };
1766
1767 self.0
1769 .splice_children(target_index..target_index, vec![new_rule.0.clone().into()]);
1770 Ok(())
1771 }
1772
1773 pub fn includes(&self) -> impl Iterator<Item = Include> {
1783 self.syntax().children().filter_map(Include::cast)
1784 }
1785
1786 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1796 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1799 let mut includes = Vec::new();
1800
1801 if let Some(include) = Include::cast(node.clone()) {
1803 includes.push(include);
1804 }
1805
1806 for child in node.children() {
1808 includes.extend(collect_includes(&child));
1809 }
1810
1811 includes
1812 }
1813
1814 let includes = collect_includes(self.syntax());
1816
1817 includes.into_iter().map(|include| {
1819 include
1820 .syntax()
1821 .children()
1822 .find(|node| node.kind() == EXPR)
1823 .map(|expr| expr.text().to_string().trim().to_string())
1824 .unwrap_or_default()
1825 })
1826 }
1827
1828 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1839 self.rules()
1840 .find(|rule| rule.targets().any(|t| t == target))
1841 }
1842
1843 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1853 self.rules_by_target(target)
1854 }
1855
1856 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1866 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1868 if !phony_rule.prerequisites().any(|p| p == target) {
1870 phony_rule.add_prerequisite(target)?;
1871 }
1872 } else {
1873 let mut phony_rule = self.add_rule(".PHONY");
1875 phony_rule.add_prerequisite(target)?;
1876 }
1877 Ok(())
1878 }
1879
1880 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1894 let mut phony_rule = None;
1896 for rule in self.rules_by_target(".PHONY") {
1897 if rule.prerequisites().any(|p| p == target) {
1898 phony_rule = Some(rule);
1899 break;
1900 }
1901 }
1902
1903 let mut phony_rule = match phony_rule {
1904 Some(rule) => rule,
1905 None => return Ok(false),
1906 };
1907
1908 let prereq_count = phony_rule.prerequisites().count();
1910
1911 phony_rule.remove_prerequisite(target)?;
1913
1914 if prereq_count == 1 {
1916 phony_rule.remove()?;
1918 }
1919
1920 Ok(true)
1921 }
1922
1923 pub fn is_phony(&self, target: &str) -> bool {
1934 self.rules_by_target(".PHONY")
1936 .any(|rule| rule.prerequisites().any(|p| p == target))
1937 }
1938
1939 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1949 self.rules_by_target(".PHONY")
1951 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1952 }
1953}
1954
1955impl FromStr for Rule {
1956 type Err = crate::Error;
1957
1958 fn from_str(s: &str) -> Result<Self, Self::Err> {
1959 Rule::parse(s).to_rule_result()
1960 }
1961}
1962
1963impl FromStr for Makefile {
1964 type Err = crate::Error;
1965
1966 fn from_str(s: &str) -> Result<Self, Self::Err> {
1967 Makefile::parse(s).to_result()
1968 }
1969}
1970
1971fn build_prerequisites_node(prereqs: &[String]) -> SyntaxNode {
1973 let mut builder = GreenNodeBuilder::new();
1974 builder.start_node(PREREQUISITES.into());
1975
1976 for (i, prereq) in prereqs.iter().enumerate() {
1977 if i > 0 {
1978 builder.token(WHITESPACE.into(), " ");
1979 }
1980
1981 builder.start_node(PREREQUISITE.into());
1983 builder.token(IDENTIFIER.into(), prereq);
1984 builder.finish_node();
1985 }
1986
1987 builder.finish_node();
1988 SyntaxNode::new_root_mut(builder.finish())
1989}
1990
1991impl Rule {
1992 pub fn parse(text: &str) -> crate::Parse<Rule> {
1994 crate::Parse::<Rule>::parse_rule(text)
1995 }
1996
1997 fn collect_variable_reference(
1999 &self,
2000 tokens: &mut std::iter::Peekable<impl Iterator<Item = SyntaxElement>>,
2001 ) -> Option<String> {
2002 let mut var_ref = String::new();
2003
2004 if let Some(token) = tokens.next() {
2006 if let Some(t) = token.as_token() {
2007 if t.kind() == DOLLAR {
2008 var_ref.push_str(t.text());
2009
2010 if let Some(next) = tokens.peek() {
2012 if let Some(nt) = next.as_token() {
2013 if nt.kind() == LPAREN {
2014 var_ref.push_str(nt.text());
2016 tokens.next();
2017
2018 let mut paren_count = 1;
2020
2021 for next_token in tokens.by_ref() {
2023 if let Some(nt) = next_token.as_token() {
2024 var_ref.push_str(nt.text());
2025
2026 if nt.kind() == LPAREN {
2027 paren_count += 1;
2028 } else if nt.kind() == RPAREN {
2029 paren_count -= 1;
2030 if paren_count == 0 {
2031 break;
2032 }
2033 }
2034 }
2035 }
2036
2037 return Some(var_ref);
2038 }
2039 }
2040 }
2041
2042 for next_token in tokens.by_ref() {
2044 if let Some(nt) = next_token.as_token() {
2045 var_ref.push_str(nt.text());
2046 if nt.kind() == RPAREN {
2047 break;
2048 }
2049 }
2050 }
2051 return Some(var_ref);
2052 }
2053 }
2054 }
2055
2056 None
2057 }
2058
2059 pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
2069 let mut result = Vec::new();
2070 let mut tokens = self
2071 .syntax()
2072 .children_with_tokens()
2073 .take_while(|it| it.as_token().map(|t| t.kind() != OPERATOR).unwrap_or(true))
2074 .peekable();
2075
2076 while let Some(token) = tokens.peek().cloned() {
2077 if let Some(node) = token.as_node() {
2078 tokens.next(); if node.kind() == EXPR {
2080 let mut var_content = String::new();
2082 for child in node.children_with_tokens() {
2083 if let Some(t) = child.as_token() {
2084 var_content.push_str(t.text());
2085 }
2086 }
2087 if !var_content.is_empty() {
2088 result.push(var_content);
2089 }
2090 }
2091 } else if let Some(t) = token.as_token() {
2092 if t.kind() == DOLLAR {
2093 if let Some(var_ref) = self.collect_variable_reference(&mut tokens) {
2094 result.push(var_ref);
2095 }
2096 } else if t.kind() == IDENTIFIER {
2097 let ident_text = t.text().to_string();
2099 tokens.next(); if let Some(next) = tokens.peek() {
2103 if let Some(next_token) = next.as_token() {
2104 if next_token.kind() == LPAREN {
2105 let mut archive_target = ident_text;
2107 archive_target.push_str(next_token.text()); tokens.next(); while let Some(token) = tokens.peek() {
2112 if let Some(node) = token.as_node() {
2113 if node.kind() == ARCHIVE_MEMBERS {
2114 archive_target.push_str(&node.text().to_string());
2115 tokens.next();
2116 } else {
2117 tokens.next();
2118 }
2119 } else if let Some(t) = token.as_token() {
2120 if t.kind() == RPAREN {
2121 archive_target.push_str(t.text());
2122 tokens.next();
2123 break;
2124 } else {
2125 tokens.next();
2126 }
2127 } else {
2128 break;
2129 }
2130 }
2131 result.push(archive_target);
2132 } else {
2133 result.push(ident_text);
2135 }
2136 } else {
2137 result.push(ident_text);
2139 }
2140 } else {
2141 result.push(ident_text);
2143 }
2144 } else {
2145 tokens.next(); }
2147 }
2148 }
2149 result.into_iter()
2150 }
2151
2152 pub fn prerequisites(&self) -> impl Iterator<Item = String> + '_ {
2161 let mut found_operator = false;
2163 let mut prerequisites_node = None;
2164
2165 for element in self.syntax().children_with_tokens() {
2166 if let Some(token) = element.as_token() {
2167 if token.kind() == OPERATOR {
2168 found_operator = true;
2169 }
2170 } else if let Some(node) = element.as_node() {
2171 if found_operator && node.kind() == PREREQUISITES {
2172 prerequisites_node = Some(node.clone());
2173 break;
2174 }
2175 }
2176 }
2177
2178 let result: Vec<String> = if let Some(prereqs) = prerequisites_node {
2179 prereqs
2181 .children()
2182 .filter(|child| child.kind() == PREREQUISITE)
2183 .map(|child| child.text().to_string().trim().to_string())
2184 .collect()
2185 } else {
2186 Vec::new()
2187 };
2188
2189 result.into_iter()
2190 }
2191
2192 pub fn recipes(&self) -> impl Iterator<Item = String> {
2201 self.syntax()
2202 .children()
2203 .filter(|it| it.kind() == RECIPE)
2204 .flat_map(|it| {
2205 it.children_with_tokens().filter_map(|it| {
2206 it.as_token().and_then(|t| {
2207 if t.kind() == TEXT {
2208 Some(t.text().to_string())
2209 } else {
2210 None
2211 }
2212 })
2213 })
2214 })
2215 }
2216
2217 pub fn replace_command(&mut self, i: usize, line: &str) -> bool {
2227 let index = self
2229 .syntax()
2230 .children()
2231 .filter(|it| it.kind() == RECIPE)
2232 .nth(i);
2233
2234 let index = match index {
2235 Some(node) => node.index(),
2236 None => return false,
2237 };
2238
2239 let mut builder = GreenNodeBuilder::new();
2240 builder.start_node(RECIPE.into());
2241 builder.token(INDENT.into(), "\t");
2242 builder.token(TEXT.into(), line);
2243 builder.token(NEWLINE.into(), "\n");
2244 builder.finish_node();
2245
2246 let syntax = SyntaxNode::new_root_mut(builder.finish());
2247
2248 self.0
2249 .splice_children(index..index + 1, vec![syntax.into()]);
2250
2251 true
2252 }
2253
2254 pub fn push_command(&mut self, line: &str) {
2264 let index = self
2266 .0
2267 .children_with_tokens()
2268 .filter(|it| it.kind() == RECIPE)
2269 .last();
2270
2271 let index = index.map_or_else(
2272 || self.0.children_with_tokens().count(),
2273 |it| it.index() + 1,
2274 );
2275
2276 let mut builder = GreenNodeBuilder::new();
2277 builder.start_node(RECIPE.into());
2278 builder.token(INDENT.into(), "\t");
2279 builder.token(TEXT.into(), line);
2280 builder.token(NEWLINE.into(), "\n");
2281 builder.finish_node();
2282 let syntax = SyntaxNode::new_root_mut(builder.finish());
2283
2284 self.0.splice_children(index..index, vec![syntax.into()]);
2285 }
2286
2287 pub fn remove_command(&mut self, index: usize) -> bool {
2297 let recipes: Vec<_> = self
2298 .syntax()
2299 .children()
2300 .filter(|n| n.kind() == RECIPE)
2301 .collect();
2302
2303 if index >= recipes.len() {
2304 return false;
2305 }
2306
2307 let target_node = &recipes[index];
2308 let target_index = target_node.index();
2309
2310 self.0
2311 .splice_children(target_index..target_index + 1, vec![]);
2312 true
2313 }
2314
2315 pub fn insert_command(&mut self, index: usize, line: &str) -> bool {
2326 let recipes: Vec<_> = self
2327 .syntax()
2328 .children()
2329 .filter(|n| n.kind() == RECIPE)
2330 .collect();
2331
2332 if index > recipes.len() {
2333 return false;
2334 }
2335
2336 let target_index = if index == recipes.len() {
2337 recipes.last().map(|n| n.index() + 1).unwrap_or_else(|| {
2339 self.0.children_with_tokens().count()
2341 })
2342 } else {
2343 recipes[index].index()
2345 };
2346
2347 let mut builder = GreenNodeBuilder::new();
2348 builder.start_node(RECIPE.into());
2349 builder.token(INDENT.into(), "\t");
2350 builder.token(TEXT.into(), line);
2351 builder.token(NEWLINE.into(), "\n");
2352 builder.finish_node();
2353 let syntax = SyntaxNode::new_root_mut(builder.finish());
2354
2355 self.0
2356 .splice_children(target_index..target_index, vec![syntax.into()]);
2357 true
2358 }
2359
2360 pub fn recipe_count(&self) -> usize {
2369 self.syntax()
2370 .children()
2371 .filter(|n| n.kind() == RECIPE)
2372 .count()
2373 }
2374
2375 pub fn clear_commands(&mut self) {
2385 let recipes: Vec<_> = self
2386 .syntax()
2387 .children()
2388 .filter(|n| n.kind() == RECIPE)
2389 .collect();
2390
2391 if recipes.is_empty() {
2392 return;
2393 }
2394
2395 for recipe in recipes.iter().rev() {
2397 let index = recipe.index();
2398 self.0.splice_children(index..index + 1, vec![]);
2399 }
2400 }
2401
2402 pub fn remove_prerequisite(&mut self, target: &str) -> Result<bool, Error> {
2415 let mut found_operator = false;
2417 let mut prereqs_node = None;
2418
2419 for child in self.syntax().children_with_tokens() {
2420 if let Some(token) = child.as_token() {
2421 if token.kind() == OPERATOR {
2422 found_operator = true;
2423 }
2424 } else if let Some(node) = child.as_node() {
2425 if found_operator && node.kind() == PREREQUISITES {
2426 prereqs_node = Some(node.clone());
2427 break;
2428 }
2429 }
2430 }
2431
2432 let prereqs_node = match prereqs_node {
2433 Some(node) => node,
2434 None => return Ok(false), };
2436
2437 let current_prereqs: Vec<String> = self.prerequisites().collect();
2439
2440 if !current_prereqs.iter().any(|p| p == target) {
2442 return Ok(false);
2443 }
2444
2445 let new_prereqs: Vec<String> = current_prereqs
2447 .into_iter()
2448 .filter(|p| p != target)
2449 .collect();
2450
2451 let prereqs_index = prereqs_node.index();
2453 let new_prereqs_node = build_prerequisites_node(&new_prereqs);
2454
2455 self.0.splice_children(
2456 prereqs_index..prereqs_index + 1,
2457 vec![new_prereqs_node.into()],
2458 );
2459
2460 Ok(true)
2461 }
2462
2463 pub fn add_prerequisite(&mut self, target: &str) -> Result<(), Error> {
2473 let mut current_prereqs: Vec<String> = self.prerequisites().collect();
2474 current_prereqs.push(target.to_string());
2475 self.set_prerequisites(current_prereqs.iter().map(|s| s.as_str()).collect())
2476 }
2477
2478 pub fn set_prerequisites(&mut self, prereqs: Vec<&str>) -> Result<(), Error> {
2488 let mut prereqs_index = None;
2490 let mut operator_found = false;
2491
2492 for child in self.syntax().children_with_tokens() {
2493 if let Some(token) = child.as_token() {
2494 if token.kind() == OPERATOR {
2495 operator_found = true;
2496 }
2497 } else if let Some(node) = child.as_node() {
2498 if operator_found && node.kind() == PREREQUISITES {
2499 prereqs_index = Some((node.index(), true)); break;
2501 }
2502 }
2503 }
2504
2505 let new_prereqs =
2507 build_prerequisites_node(&prereqs.iter().map(|s| s.to_string()).collect::<Vec<_>>());
2508
2509 match prereqs_index {
2510 Some((idx, true)) => {
2511 self.0
2513 .splice_children(idx..idx + 1, vec![new_prereqs.into()]);
2514 }
2515 _ => {
2516 let insert_pos = self
2518 .syntax()
2519 .children_with_tokens()
2520 .position(|t| t.as_token().map(|t| t.kind() == OPERATOR).unwrap_or(false))
2521 .map(|p| p + 1)
2522 .ok_or_else(|| {
2523 Error::Parse(ParseError {
2524 errors: vec![ErrorInfo {
2525 message: "No operator found in rule".to_string(),
2526 line: 1,
2527 context: "set_prerequisites".to_string(),
2528 }],
2529 })
2530 })?;
2531
2532 self.0
2533 .splice_children(insert_pos..insert_pos, vec![new_prereqs.into()]);
2534 }
2535 }
2536
2537 Ok(())
2538 }
2539
2540 pub fn remove(self) -> Result<(), Error> {
2553 let parent = self.syntax().parent().ok_or_else(|| {
2554 Error::Parse(ParseError {
2555 errors: vec![ErrorInfo {
2556 message: "Rule has no parent".to_string(),
2557 line: 1,
2558 context: "remove".to_string(),
2559 }],
2560 })
2561 })?;
2562
2563 remove_with_preceding_comments(self.syntax(), &parent);
2564 Ok(())
2565 }
2566}
2567
2568impl Default for Makefile {
2569 fn default() -> Self {
2570 Self::new()
2571 }
2572}
2573
2574impl Include {
2575 pub fn path(&self) -> Option<String> {
2577 self.syntax()
2578 .children()
2579 .find(|it| it.kind() == EXPR)
2580 .map(|it| it.text().to_string().trim().to_string())
2581 }
2582
2583 pub fn is_optional(&self) -> bool {
2585 let text = self.syntax().text();
2586 text.to_string().starts_with("-include") || text.to_string().starts_with("sinclude")
2587 }
2588}
2589
2590#[cfg(test)]
2591mod tests {
2592 use super::*;
2593
2594 #[test]
2595 fn test_conditionals() {
2596 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
2600 let mut buf = code.as_bytes();
2601 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
2602 assert!(makefile.code().contains("DEBUG_FLAG"));
2603
2604 let code =
2606 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
2607 let mut buf = code.as_bytes();
2608 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
2609 assert!(makefile.code().contains("RESULT"));
2610 assert!(makefile.code().contains("windows"));
2611
2612 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
2614 let mut buf = code.as_bytes();
2615 let makefile = Makefile::read_relaxed(&mut buf)
2616 .expect("Failed to parse nested conditionals with else");
2617 assert!(makefile.code().contains("CFLAGS"));
2618 assert!(makefile.code().contains("VERBOSE"));
2619
2620 let code = "ifdef DEBUG\nendif\n";
2622 let mut buf = code.as_bytes();
2623 let makefile =
2624 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
2625 assert!(makefile.code().contains("ifdef DEBUG"));
2626
2627 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelif ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
2629 let mut buf = code.as_bytes();
2630 let makefile =
2631 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with elif");
2632 assert!(makefile.code().contains("EXT"));
2633
2634 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
2636 let mut buf = code.as_bytes();
2637 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
2638 assert!(makefile.code().contains("DEBUG"));
2639
2640 let code = "ifdef \nDEBUG := 1\nendif\n";
2642 let mut buf = code.as_bytes();
2643 let makefile = Makefile::read_relaxed(&mut buf)
2644 .expect("Failed to parse with recovery - missing condition");
2645 assert!(makefile.code().contains("DEBUG"));
2646 }
2647
2648 #[test]
2649 fn test_parse_simple() {
2650 const SIMPLE: &str = r#"VARIABLE = value
2651
2652rule: dependency
2653 command
2654"#;
2655 let parsed = parse(SIMPLE);
2656 assert!(parsed.errors.is_empty());
2657 let node = parsed.syntax();
2658 assert_eq!(
2659 format!("{:#?}", node),
2660 r#"ROOT@0..44
2661 VARIABLE@0..17
2662 IDENTIFIER@0..8 "VARIABLE"
2663 WHITESPACE@8..9 " "
2664 OPERATOR@9..10 "="
2665 WHITESPACE@10..11 " "
2666 EXPR@11..16
2667 IDENTIFIER@11..16 "value"
2668 NEWLINE@16..17 "\n"
2669 NEWLINE@17..18 "\n"
2670 RULE@18..44
2671 IDENTIFIER@18..22 "rule"
2672 OPERATOR@22..23 ":"
2673 WHITESPACE@23..24 " "
2674 PREREQUISITES@24..34
2675 PREREQUISITE@24..34
2676 IDENTIFIER@24..34 "dependency"
2677 NEWLINE@34..35 "\n"
2678 RECIPE@35..44
2679 INDENT@35..36 "\t"
2680 TEXT@36..43 "command"
2681 NEWLINE@43..44 "\n"
2682"#
2683 );
2684
2685 let root = parsed.root();
2686
2687 let mut rules = root.rules().collect::<Vec<_>>();
2688 assert_eq!(rules.len(), 1);
2689 let rule = rules.pop().unwrap();
2690 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2691 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
2692 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2693
2694 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2695 assert_eq!(variables.len(), 1);
2696 let variable = variables.pop().unwrap();
2697 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2698 assert_eq!(variable.raw_value(), Some("value".to_string()));
2699 }
2700
2701 #[test]
2702 fn test_parse_export_assign() {
2703 const EXPORT: &str = r#"export VARIABLE := value
2704"#;
2705 let parsed = parse(EXPORT);
2706 assert!(parsed.errors.is_empty());
2707 let node = parsed.syntax();
2708 assert_eq!(
2709 format!("{:#?}", node),
2710 r#"ROOT@0..25
2711 VARIABLE@0..25
2712 IDENTIFIER@0..6 "export"
2713 WHITESPACE@6..7 " "
2714 IDENTIFIER@7..15 "VARIABLE"
2715 WHITESPACE@15..16 " "
2716 OPERATOR@16..18 ":="
2717 WHITESPACE@18..19 " "
2718 EXPR@19..24
2719 IDENTIFIER@19..24 "value"
2720 NEWLINE@24..25 "\n"
2721"#
2722 );
2723
2724 let root = parsed.root();
2725
2726 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2727 assert_eq!(variables.len(), 1);
2728 let variable = variables.pop().unwrap();
2729 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2730 assert_eq!(variable.raw_value(), Some("value".to_string()));
2731 }
2732
2733 #[test]
2734 fn test_parse_multiple_prerequisites() {
2735 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
2736 command
2737
2738"#;
2739 let parsed = parse(MULTIPLE_PREREQUISITES);
2740 assert!(parsed.errors.is_empty());
2741 let node = parsed.syntax();
2742 assert_eq!(
2743 format!("{:#?}", node),
2744 r#"ROOT@0..40
2745 RULE@0..40
2746 IDENTIFIER@0..4 "rule"
2747 OPERATOR@4..5 ":"
2748 WHITESPACE@5..6 " "
2749 PREREQUISITES@6..29
2750 PREREQUISITE@6..17
2751 IDENTIFIER@6..17 "dependency1"
2752 WHITESPACE@17..18 " "
2753 PREREQUISITE@18..29
2754 IDENTIFIER@18..29 "dependency2"
2755 NEWLINE@29..30 "\n"
2756 RECIPE@30..39
2757 INDENT@30..31 "\t"
2758 TEXT@31..38 "command"
2759 NEWLINE@38..39 "\n"
2760 NEWLINE@39..40 "\n"
2761"#
2762 );
2763 let root = parsed.root();
2764
2765 let rule = root.rules().next().unwrap();
2766 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2767 assert_eq!(
2768 rule.prerequisites().collect::<Vec<_>>(),
2769 vec!["dependency1", "dependency2"]
2770 );
2771 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2772 }
2773
2774 #[test]
2775 fn test_add_rule() {
2776 let mut makefile = Makefile::new();
2777 let rule = makefile.add_rule("rule");
2778 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2779 assert_eq!(
2780 rule.prerequisites().collect::<Vec<_>>(),
2781 Vec::<String>::new()
2782 );
2783
2784 assert_eq!(makefile.to_string(), "rule:\n");
2785 }
2786
2787 #[test]
2788 fn test_push_command() {
2789 let mut makefile = Makefile::new();
2790 let mut rule = makefile.add_rule("rule");
2791
2792 rule.push_command("command");
2794 rule.push_command("command2");
2795
2796 assert_eq!(
2798 rule.recipes().collect::<Vec<_>>(),
2799 vec!["command", "command2"]
2800 );
2801
2802 rule.push_command("command3");
2804 assert_eq!(
2805 rule.recipes().collect::<Vec<_>>(),
2806 vec!["command", "command2", "command3"]
2807 );
2808
2809 assert_eq!(
2811 makefile.to_string(),
2812 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2813 );
2814
2815 assert_eq!(
2817 rule.to_string(),
2818 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2819 );
2820 }
2821
2822 #[test]
2823 fn test_replace_command() {
2824 let mut makefile = Makefile::new();
2825 let mut rule = makefile.add_rule("rule");
2826
2827 rule.push_command("command");
2829 rule.push_command("command2");
2830
2831 assert_eq!(
2833 rule.recipes().collect::<Vec<_>>(),
2834 vec!["command", "command2"]
2835 );
2836
2837 rule.replace_command(0, "new command");
2839 assert_eq!(
2840 rule.recipes().collect::<Vec<_>>(),
2841 vec!["new command", "command2"]
2842 );
2843
2844 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2846
2847 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2849 }
2850
2851 #[test]
2852 fn test_parse_rule_without_newline() {
2853 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
2854 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2855 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2856 let rule = "rule: dependency".parse::<Rule>().unwrap();
2857 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2858 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
2859 }
2860
2861 #[test]
2862 fn test_parse_makefile_without_newline() {
2863 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
2864 assert_eq!(makefile.rules().count(), 1);
2865 }
2866
2867 #[test]
2868 fn test_from_reader() {
2869 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
2870 assert_eq!(makefile.rules().count(), 1);
2871 }
2872
2873 #[test]
2874 fn test_parse_with_tab_after_last_newline() {
2875 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
2876 assert_eq!(makefile.rules().count(), 1);
2877 }
2878
2879 #[test]
2880 fn test_parse_with_space_after_last_newline() {
2881 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
2882 assert_eq!(makefile.rules().count(), 1);
2883 }
2884
2885 #[test]
2886 fn test_parse_with_comment_after_last_newline() {
2887 let makefile =
2888 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
2889 assert_eq!(makefile.rules().count(), 1);
2890 }
2891
2892 #[test]
2893 fn test_parse_with_variable_rule() {
2894 let makefile =
2895 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
2896 .unwrap();
2897
2898 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2900 assert_eq!(vars.len(), 1);
2901 assert_eq!(vars[0].name(), Some("RULE".to_string()));
2902 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
2903
2904 let rules = makefile.rules().collect::<Vec<_>>();
2906 assert_eq!(rules.len(), 1);
2907 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
2908 assert_eq!(
2909 rules[0].prerequisites().collect::<Vec<_>>(),
2910 vec!["dependency"]
2911 );
2912 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2913 }
2914
2915 #[test]
2916 fn test_parse_with_variable_dependency() {
2917 let makefile =
2918 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
2919
2920 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2922 assert_eq!(vars.len(), 1);
2923 assert_eq!(vars[0].name(), Some("DEP".to_string()));
2924 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
2925
2926 let rules = makefile.rules().collect::<Vec<_>>();
2928 assert_eq!(rules.len(), 1);
2929 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2930 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
2931 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2932 }
2933
2934 #[test]
2935 fn test_parse_with_variable_command() {
2936 let makefile =
2937 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
2938
2939 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2941 assert_eq!(vars.len(), 1);
2942 assert_eq!(vars[0].name(), Some("COM".to_string()));
2943 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
2944
2945 let rules = makefile.rules().collect::<Vec<_>>();
2947 assert_eq!(rules.len(), 1);
2948 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2949 assert_eq!(
2950 rules[0].prerequisites().collect::<Vec<_>>(),
2951 vec!["dependency"]
2952 );
2953 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
2954 }
2955
2956 #[test]
2957 fn test_regular_line_error_reporting() {
2958 let input = "rule target\n\tcommand";
2959
2960 let parsed = parse(input);
2962 let direct_error = &parsed.errors[0];
2963
2964 assert_eq!(direct_error.line, 2);
2966 assert!(
2967 direct_error.message.contains("expected"),
2968 "Error message should contain 'expected': {}",
2969 direct_error.message
2970 );
2971 assert_eq!(direct_error.context, "\tcommand");
2972
2973 let reader_result = Makefile::from_reader(input.as_bytes());
2975 let parse_error = match reader_result {
2976 Ok(_) => panic!("Expected Parse error from from_reader"),
2977 Err(err) => match err {
2978 self::Error::Parse(parse_err) => parse_err,
2979 _ => panic!("Expected Parse error"),
2980 },
2981 };
2982
2983 let error_text = parse_error.to_string();
2985 assert!(error_text.contains("Error at line 2:"));
2986 assert!(error_text.contains("2| \tcommand"));
2987 }
2988
2989 #[test]
2990 fn test_parsing_error_context_with_bad_syntax() {
2991 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
2993
2994 match Makefile::from_reader(input.as_bytes()) {
2996 Ok(makefile) => {
2997 assert_eq!(
2999 makefile.rules().count(),
3000 0,
3001 "Should not have found any rules"
3002 );
3003 }
3004 Err(err) => match err {
3005 self::Error::Parse(error) => {
3006 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
3008 assert!(
3009 !error.errors[0].context.is_empty(),
3010 "Error context should not be empty"
3011 );
3012 }
3013 _ => panic!("Unexpected error type"),
3014 },
3015 };
3016 }
3017
3018 #[test]
3019 fn test_error_message_format() {
3020 let parse_error = ParseError {
3022 errors: vec![ErrorInfo {
3023 message: "test error".to_string(),
3024 line: 42,
3025 context: "some problematic code".to_string(),
3026 }],
3027 };
3028
3029 let error_text = parse_error.to_string();
3030 assert!(error_text.contains("Error at line 42: test error"));
3031 assert!(error_text.contains("42| some problematic code"));
3032 }
3033
3034 #[test]
3035 fn test_line_number_calculation() {
3036 let test_cases = [
3038 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
3042
3043 for (input, expected_line) in test_cases {
3044 match input.parse::<Makefile>() {
3046 Ok(_) => {
3047 continue;
3050 }
3051 Err(err) => {
3052 if let Error::Parse(parse_err) = err {
3053 assert_eq!(
3055 parse_err.errors[0].line, expected_line,
3056 "Line number should match the expected line"
3057 );
3058
3059 if parse_err.errors[0].message.contains("indented") {
3061 assert!(
3062 parse_err.errors[0].context.starts_with('\t'),
3063 "Context for indentation errors should include the tab character"
3064 );
3065 }
3066 } else {
3067 panic!("Expected parse error, got: {:?}", err);
3068 }
3069 }
3070 }
3071 }
3072 }
3073
3074 #[test]
3075 fn test_conditional_features() {
3076 let code = r#"
3078# Set variables based on DEBUG flag
3079ifdef DEBUG
3080 CFLAGS += -g -DDEBUG
3081else
3082 CFLAGS = -O2
3083endif
3084
3085# Define a build rule
3086all: $(OBJS)
3087 $(CC) $(CFLAGS) -o $@ $^
3088"#;
3089
3090 let mut buf = code.as_bytes();
3091 let makefile =
3092 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
3093
3094 assert!(!makefile.code().is_empty(), "Makefile has content");
3097
3098 let rules = makefile.rules().collect::<Vec<_>>();
3100 assert!(!rules.is_empty(), "Should have found rules");
3101
3102 assert!(code.contains("ifdef DEBUG"));
3104 assert!(code.contains("endif"));
3105
3106 let code_with_var = r#"
3108# Define a variable first
3109CC = gcc
3110
3111ifdef DEBUG
3112 CFLAGS += -g -DDEBUG
3113else
3114 CFLAGS = -O2
3115endif
3116
3117all: $(OBJS)
3118 $(CC) $(CFLAGS) -o $@ $^
3119"#;
3120
3121 let mut buf = code_with_var.as_bytes();
3122 let makefile =
3123 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
3124
3125 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3127 assert!(
3128 !vars.is_empty(),
3129 "Should have found at least the CC variable definition"
3130 );
3131 }
3132
3133 #[test]
3134 fn test_include_directive() {
3135 let parsed = parse("include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n");
3136 assert!(parsed.errors.is_empty());
3137 let node = parsed.syntax();
3138 assert!(format!("{:#?}", node).contains("INCLUDE@"));
3139 }
3140
3141 #[test]
3142 fn test_export_variables() {
3143 let parsed = parse("export SHELL := /bin/bash\n");
3144 assert!(parsed.errors.is_empty());
3145 let makefile = parsed.root();
3146 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3147 assert_eq!(vars.len(), 1);
3148 let shell_var = vars
3149 .iter()
3150 .find(|v| v.name() == Some("SHELL".to_string()))
3151 .unwrap();
3152 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
3153 }
3154
3155 #[test]
3156 fn test_variable_scopes() {
3157 let parsed =
3158 parse("SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n");
3159 assert!(parsed.errors.is_empty());
3160 let makefile = parsed.root();
3161 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3162 assert_eq!(vars.len(), 4);
3163 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
3164 assert!(var_names.contains(&"SIMPLE".to_string()));
3165 assert!(var_names.contains(&"IMMEDIATE".to_string()));
3166 assert!(var_names.contains(&"CONDITIONAL".to_string()));
3167 assert!(var_names.contains(&"APPEND".to_string()));
3168 }
3169
3170 #[test]
3171 fn test_pattern_rule_parsing() {
3172 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n");
3173 assert!(parsed.errors.is_empty());
3174 let makefile = parsed.root();
3175 let rules = makefile.rules().collect::<Vec<_>>();
3176 assert_eq!(rules.len(), 1);
3177 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
3178 assert!(rules[0].recipes().next().unwrap().contains("$@"));
3179 }
3180
3181 #[test]
3182 fn test_include_variants() {
3183 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
3185 let parsed = parse(makefile_str);
3186 assert!(parsed.errors.is_empty());
3187
3188 let node = parsed.syntax();
3190 let debug_str = format!("{:#?}", node);
3191
3192 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
3194
3195 let makefile = parsed.root();
3197
3198 let include_count = makefile
3200 .syntax()
3201 .children()
3202 .filter(|child| child.kind() == INCLUDE)
3203 .count();
3204 assert_eq!(include_count, 4);
3205
3206 assert!(makefile
3208 .included_files()
3209 .any(|path| path.contains("$(VAR)")));
3210 }
3211
3212 #[test]
3213 fn test_include_api() {
3214 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
3216 let makefile: Makefile = makefile_str.parse().unwrap();
3217
3218 let includes: Vec<_> = makefile.includes().collect();
3220 assert_eq!(includes.len(), 3);
3221
3222 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
3229 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
3230
3231 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
3233 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
3234 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
3235 }
3236
3237 #[test]
3238 fn test_include_integration() {
3239 let phony_makefile = Makefile::from_reader(
3243 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3244 .as_bytes()
3245 ).unwrap();
3246
3247 assert_eq!(phony_makefile.rules().count(), 2);
3249
3250 let normal_rules_count = phony_makefile
3252 .rules()
3253 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
3254 .count();
3255 assert_eq!(normal_rules_count, 1);
3256
3257 assert_eq!(phony_makefile.includes().count(), 1);
3259 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
3260
3261 let simple_makefile = Makefile::from_reader(
3263 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3264 .as_bytes(),
3265 )
3266 .unwrap();
3267 assert_eq!(simple_makefile.rules().count(), 1);
3268 assert_eq!(simple_makefile.includes().count(), 1);
3269 }
3270
3271 #[test]
3272 fn test_real_conditional_directives() {
3273 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
3275 let mut buf = conditional.as_bytes();
3276 let makefile =
3277 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
3278 let code = makefile.code();
3279 assert!(code.contains("ifdef DEBUG"));
3280 assert!(code.contains("else"));
3281 assert!(code.contains("endif"));
3282
3283 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
3285 let mut buf = nested.as_bytes();
3286 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
3287 let code = makefile.code();
3288 assert!(code.contains("ifdef DEBUG"));
3289 assert!(code.contains("ifdef VERBOSE"));
3290
3291 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
3293 let mut buf = ifeq.as_bytes();
3294 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
3295 let code = makefile.code();
3296 assert!(code.contains("ifeq"));
3297 assert!(code.contains("Windows_NT"));
3298 }
3299
3300 #[test]
3301 fn test_indented_text_outside_rules() {
3302 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
3304 let parsed = parse(help_text);
3305 assert!(parsed.errors.is_empty());
3306
3307 let root = parsed.root();
3309 let rules = root.rules().collect::<Vec<_>>();
3310 assert_eq!(rules.len(), 1);
3311
3312 let help_rule = &rules[0];
3313 let recipes = help_rule.recipes().collect::<Vec<_>>();
3314 assert_eq!(recipes.len(), 2);
3315 assert!(recipes[0].contains("Available targets"));
3316 assert!(recipes[1].contains("help"));
3317 }
3318
3319 #[test]
3320 fn test_comment_handling_in_recipes() {
3321 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
3323
3324 let parsed = parse(recipe_comment);
3326
3327 assert!(
3329 parsed.errors.is_empty(),
3330 "Should parse recipe with comments without errors"
3331 );
3332
3333 let root = parsed.root();
3335 let rules = root.rules().collect::<Vec<_>>();
3336 assert_eq!(rules.len(), 1, "Should find exactly one rule");
3337
3338 let build_rule = &rules[0];
3340 assert_eq!(
3341 build_rule.targets().collect::<Vec<_>>(),
3342 vec!["build"],
3343 "Rule should have 'build' as target"
3344 );
3345
3346 let recipes = build_rule.recipes().collect::<Vec<_>>();
3350 assert_eq!(
3351 recipes.len(),
3352 1,
3353 "Should find exactly one recipe line (comment lines are filtered)"
3354 );
3355 assert!(
3356 recipes[0].contains("gcc -o app"),
3357 "Recipe should be the command line"
3358 );
3359 assert!(
3360 !recipes[0].contains("This is a comment"),
3361 "Comments should not be included in recipe lines"
3362 );
3363 }
3364
3365 #[test]
3366 fn test_multiline_variables() {
3367 let multiline = "SOURCES = main.c \\\n util.c\n";
3369
3370 let parsed = parse(multiline);
3372
3373 let root = parsed.root();
3375 let vars = root.variable_definitions().collect::<Vec<_>>();
3376 assert!(!vars.is_empty(), "Should find at least one variable");
3377
3378 let operators = "CFLAGS := -Wall \\\n -Werror\n";
3382 let parsed_operators = parse(operators);
3383
3384 let root = parsed_operators.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 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
3394 let parsed_append = parse(append);
3395
3396 let root = parsed_append.root();
3398 let vars = root.variable_definitions().collect::<Vec<_>>();
3399 assert!(
3400 !vars.is_empty(),
3401 "Should find at least one variable with += operator"
3402 );
3403 }
3404
3405 #[test]
3406 fn test_whitespace_and_eof_handling() {
3407 let blank_lines = "VAR = value\n\n\n";
3409
3410 let parsed_blank = parse(blank_lines);
3411
3412 let root = parsed_blank.root();
3414 let vars = root.variable_definitions().collect::<Vec<_>>();
3415 assert_eq!(
3416 vars.len(),
3417 1,
3418 "Should find one variable in blank lines test"
3419 );
3420
3421 let trailing_space = "VAR = value \n";
3423
3424 let parsed_space = parse(trailing_space);
3425
3426 let root = parsed_space.root();
3428 let vars = root.variable_definitions().collect::<Vec<_>>();
3429 assert_eq!(
3430 vars.len(),
3431 1,
3432 "Should find one variable in trailing space test"
3433 );
3434
3435 let no_newline = "VAR = value";
3437
3438 let parsed_no_newline = parse(no_newline);
3439
3440 let root = parsed_no_newline.root();
3442 let vars = root.variable_definitions().collect::<Vec<_>>();
3443 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
3444 assert_eq!(
3445 vars[0].name(),
3446 Some("VAR".to_string()),
3447 "Variable name should be VAR"
3448 );
3449 }
3450
3451 #[test]
3452 fn test_complex_variable_references() {
3453 let wildcard = "SOURCES = $(wildcard *.c)\n";
3455 let parsed = parse(wildcard);
3456 assert!(parsed.errors.is_empty());
3457
3458 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3460 let parsed = parse(nested);
3461 assert!(parsed.errors.is_empty());
3462
3463 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3465 let parsed = parse(patsubst);
3466 assert!(parsed.errors.is_empty());
3467 }
3468
3469 #[test]
3470 fn test_complex_variable_references_minimal() {
3471 let wildcard = "SOURCES = $(wildcard *.c)\n";
3473 let parsed = parse(wildcard);
3474 assert!(parsed.errors.is_empty());
3475
3476 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3478 let parsed = parse(nested);
3479 assert!(parsed.errors.is_empty());
3480
3481 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3483 let parsed = parse(patsubst);
3484 assert!(parsed.errors.is_empty());
3485 }
3486
3487 #[test]
3488 fn test_multiline_variable_with_backslash() {
3489 let content = r#"
3490LONG_VAR = This is a long variable \
3491 that continues on the next line \
3492 and even one more line
3493"#;
3494
3495 let mut buf = content.as_bytes();
3497 let makefile =
3498 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
3499
3500 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3502 assert_eq!(
3503 vars.len(),
3504 1,
3505 "Expected 1 variable but found {}",
3506 vars.len()
3507 );
3508 let var_value = vars[0].raw_value();
3509 assert!(var_value.is_some(), "Variable value is None");
3510
3511 let value_str = var_value.unwrap();
3513 assert!(
3514 value_str.contains("long variable"),
3515 "Value doesn't contain expected content"
3516 );
3517 }
3518
3519 #[test]
3520 fn test_multiline_variable_with_mixed_operators() {
3521 let content = r#"
3522PREFIX ?= /usr/local
3523CFLAGS := -Wall -O2 \
3524 -I$(PREFIX)/include \
3525 -DDEBUG
3526"#;
3527 let mut buf = content.as_bytes();
3529 let makefile = Makefile::read_relaxed(&mut buf)
3530 .expect("Failed to parse multiline variable with operators");
3531
3532 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3534 assert!(
3535 vars.len() >= 1,
3536 "Expected at least 1 variable, found {}",
3537 vars.len()
3538 );
3539
3540 let prefix_var = vars
3542 .iter()
3543 .find(|v| v.name().unwrap_or_default() == "PREFIX");
3544 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
3545 assert!(
3546 prefix_var.unwrap().raw_value().is_some(),
3547 "PREFIX variable has no value"
3548 );
3549
3550 let cflags_var = vars
3552 .iter()
3553 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
3554 assert!(
3555 cflags_var.is_some(),
3556 "Expected to find CFLAGS variable (or part of it)"
3557 );
3558 }
3559
3560 #[test]
3561 fn test_indented_help_text() {
3562 let content = r#"
3563.PHONY: help
3564help:
3565 @echo "Available targets:"
3566 @echo " build - Build the project"
3567 @echo " test - Run tests"
3568 @echo " clean - Remove build artifacts"
3569"#;
3570 let mut buf = content.as_bytes();
3572 let makefile =
3573 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
3574
3575 let rules = makefile.rules().collect::<Vec<_>>();
3577 assert!(!rules.is_empty(), "Expected at least one rule");
3578
3579 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
3581 assert!(help_rule.is_some(), "Expected to find help rule");
3582
3583 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
3585 assert!(
3586 !recipes.is_empty(),
3587 "Expected at least one recipe line in help rule"
3588 );
3589 assert!(
3590 recipes.iter().any(|r| r.contains("Available targets")),
3591 "Expected to find 'Available targets' in recipes"
3592 );
3593 }
3594
3595 #[test]
3596 fn test_indented_lines_in_conditionals() {
3597 let content = r#"
3598ifdef DEBUG
3599 CFLAGS += -g -DDEBUG
3600 # This is a comment inside conditional
3601 ifdef VERBOSE
3602 CFLAGS += -v
3603 endif
3604endif
3605"#;
3606 let mut buf = content.as_bytes();
3608 let makefile = Makefile::read_relaxed(&mut buf)
3609 .expect("Failed to parse indented lines in conditionals");
3610
3611 let code = makefile.code();
3613 assert!(code.contains("ifdef DEBUG"));
3614 assert!(code.contains("ifdef VERBOSE"));
3615 assert!(code.contains("endif"));
3616 }
3617
3618 #[test]
3619 fn test_recipe_with_colon() {
3620 let content = r#"
3621build:
3622 @echo "Building at: $(shell date)"
3623 gcc -o program main.c
3624"#;
3625 let parsed = parse(content);
3626 assert!(
3627 parsed.errors.is_empty(),
3628 "Failed to parse recipe with colon: {:?}",
3629 parsed.errors
3630 );
3631 }
3632
3633 #[test]
3634 #[ignore]
3635 fn test_double_colon_rules() {
3636 let content = r#"
3639%.o :: %.c
3640 $(CC) -c $< -o $@
3641
3642# Double colon allows multiple rules for same target
3643all:: prerequisite1
3644 @echo "First rule for all"
3645
3646all:: prerequisite2
3647 @echo "Second rule for all"
3648"#;
3649 let mut buf = content.as_bytes();
3650 let makefile =
3651 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
3652
3653 let rules = makefile.rules().collect::<Vec<_>>();
3655 assert!(!rules.is_empty(), "Expected at least one rule");
3656
3657 let all_rules = rules
3659 .iter()
3660 .filter(|r| r.targets().any(|t| t.contains("all")));
3661 assert!(
3662 all_rules.count() > 0,
3663 "Expected to find at least one rule containing 'all'"
3664 );
3665 }
3666
3667 #[test]
3668 fn test_elif_directive() {
3669 let content = r#"
3670ifeq ($(OS),Windows_NT)
3671 TARGET = windows
3672elif ifeq ($(OS),Darwin)
3673 TARGET = macos
3674elif ifeq ($(OS),Linux)
3675 TARGET = linux
3676else
3677 TARGET = unknown
3678endif
3679"#;
3680 let mut buf = content.as_bytes();
3682 let _makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse elif directive");
3683
3684 }
3687
3688 #[test]
3689 fn test_ambiguous_assignment_vs_rule() {
3690 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
3692
3693 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
3694 let makefile =
3695 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
3696
3697 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3698 let rules = makefile.rules().collect::<Vec<_>>();
3699
3700 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
3701 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
3702
3703 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
3704
3705 const SIMPLE_RULE: &str = "target: dependency\n";
3707
3708 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
3709 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
3710
3711 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3712 let rules = makefile.rules().collect::<Vec<_>>();
3713
3714 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
3715 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
3716
3717 let rule = &rules[0];
3718 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
3719 }
3720
3721 #[test]
3722 fn test_nested_conditionals() {
3723 let content = r#"
3724ifdef RELEASE
3725 CFLAGS += -O3
3726 ifndef DEBUG
3727 ifneq ($(ARCH),arm)
3728 CFLAGS += -march=native
3729 else
3730 CFLAGS += -mcpu=cortex-a72
3731 endif
3732 endif
3733endif
3734"#;
3735 let mut buf = content.as_bytes();
3737 let makefile =
3738 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
3739
3740 let code = makefile.code();
3742 assert!(code.contains("ifdef RELEASE"));
3743 assert!(code.contains("ifndef DEBUG"));
3744 assert!(code.contains("ifneq"));
3745 }
3746
3747 #[test]
3748 fn test_space_indented_recipes() {
3749 let content = r#"
3752build:
3753 @echo "Building with spaces instead of tabs"
3754 gcc -o program main.c
3755"#;
3756 let mut buf = content.as_bytes();
3758 let makefile =
3759 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
3760
3761 let rules = makefile.rules().collect::<Vec<_>>();
3763 assert!(!rules.is_empty(), "Expected at least one rule");
3764
3765 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
3767 assert!(build_rule.is_some(), "Expected to find build rule");
3768 }
3769
3770 #[test]
3771 fn test_complex_variable_functions() {
3772 let content = r#"
3773FILES := $(shell find . -name "*.c")
3774OBJS := $(patsubst %.c,%.o,$(FILES))
3775NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
3776HEADERS := ${wildcard *.h}
3777"#;
3778 let parsed = parse(content);
3779 assert!(
3780 parsed.errors.is_empty(),
3781 "Failed to parse complex variable functions: {:?}",
3782 parsed.errors
3783 );
3784 }
3785
3786 #[test]
3787 fn test_nested_variable_expansions() {
3788 let content = r#"
3789VERSION = 1.0
3790PACKAGE = myapp
3791TARBALL = $(PACKAGE)-$(VERSION).tar.gz
3792INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
3793"#;
3794 let parsed = parse(content);
3795 assert!(
3796 parsed.errors.is_empty(),
3797 "Failed to parse nested variable expansions: {:?}",
3798 parsed.errors
3799 );
3800 }
3801
3802 #[test]
3803 fn test_special_directives() {
3804 let content = r#"
3805# Special makefile directives
3806.PHONY: all clean
3807.SUFFIXES: .c .o
3808.DEFAULT: all
3809
3810# Variable definition and export directive
3811export PATH := /usr/bin:/bin
3812"#;
3813 let mut buf = content.as_bytes();
3815 let makefile =
3816 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
3817
3818 let rules = makefile.rules().collect::<Vec<_>>();
3820
3821 let phony_rule = rules
3823 .iter()
3824 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
3825 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
3826
3827 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3829 assert!(!vars.is_empty(), "Expected to find at least one variable");
3830 }
3831
3832 #[test]
3835 fn test_comprehensive_real_world_makefile() {
3836 let content = r#"
3838# Basic variable assignment
3839VERSION = 1.0.0
3840
3841# Phony target
3842.PHONY: all clean
3843
3844# Simple rule
3845all:
3846 echo "Building version $(VERSION)"
3847
3848# Another rule with dependencies
3849clean:
3850 rm -f *.o
3851"#;
3852
3853 let parsed = parse(content);
3855
3856 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
3858
3859 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
3861 assert!(!variables.is_empty(), "Expected at least one variable");
3862 assert_eq!(
3863 variables[0].name(),
3864 Some("VERSION".to_string()),
3865 "Expected VERSION variable"
3866 );
3867
3868 let rules = parsed.root().rules().collect::<Vec<_>>();
3870 assert!(!rules.is_empty(), "Expected at least one rule");
3871
3872 let rule_targets: Vec<String> = rules
3874 .iter()
3875 .flat_map(|r| r.targets().collect::<Vec<_>>())
3876 .collect();
3877 assert!(
3878 rule_targets.contains(&".PHONY".to_string()),
3879 "Expected .PHONY rule"
3880 );
3881 assert!(
3882 rule_targets.contains(&"all".to_string()),
3883 "Expected 'all' rule"
3884 );
3885 assert!(
3886 rule_targets.contains(&"clean".to_string()),
3887 "Expected 'clean' rule"
3888 );
3889 }
3890
3891 #[test]
3892 fn test_indented_help_text_outside_rules() {
3893 let content = r#"
3895# Targets with help text
3896help:
3897 @echo "Available targets:"
3898 @echo " build build the project"
3899 @echo " test run tests"
3900 @echo " clean clean build artifacts"
3901
3902# Another target
3903clean:
3904 rm -rf build/
3905"#;
3906
3907 let parsed = parse(content);
3909
3910 assert!(
3912 parsed.errors.is_empty(),
3913 "Failed to parse indented help text"
3914 );
3915
3916 let rules = parsed.root().rules().collect::<Vec<_>>();
3918 assert_eq!(rules.len(), 2, "Expected to find two rules");
3919
3920 let help_rule = rules
3922 .iter()
3923 .find(|r| r.targets().any(|t| t == "help"))
3924 .expect("Expected to find help rule");
3925
3926 let clean_rule = rules
3927 .iter()
3928 .find(|r| r.targets().any(|t| t == "clean"))
3929 .expect("Expected to find clean rule");
3930
3931 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
3933 assert!(
3934 !help_recipes.is_empty(),
3935 "Help rule should have recipe lines"
3936 );
3937 assert!(
3938 help_recipes
3939 .iter()
3940 .any(|line| line.contains("Available targets")),
3941 "Help recipes should include 'Available targets' line"
3942 );
3943
3944 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
3946 assert!(
3947 !clean_recipes.is_empty(),
3948 "Clean rule should have recipe lines"
3949 );
3950 assert!(
3951 clean_recipes.iter().any(|line| line.contains("rm -rf")),
3952 "Clean recipes should include 'rm -rf' command"
3953 );
3954 }
3955
3956 #[test]
3957 fn test_makefile1_phony_pattern() {
3958 let content = "#line 2145\n.PHONY: $(PHONY)\n";
3960
3961 let result = parse(content);
3963
3964 assert!(
3966 result.errors.is_empty(),
3967 "Failed to parse .PHONY: $(PHONY) pattern"
3968 );
3969
3970 let rules = result.root().rules().collect::<Vec<_>>();
3972 assert_eq!(rules.len(), 1, "Expected 1 rule");
3973 assert_eq!(
3974 rules[0].targets().next().unwrap(),
3975 ".PHONY",
3976 "Expected .PHONY rule"
3977 );
3978
3979 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
3981 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
3982 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
3983 }
3984
3985 #[test]
3986 fn test_skip_until_newline_behavior() {
3987 let input = "text without newline";
3989 let parsed = parse(input);
3990 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3992
3993 let input_with_newline = "text\nafter newline";
3994 let parsed2 = parse(input_with_newline);
3995 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
3996 }
3997
3998 #[test]
3999 fn test_error_with_indent_token() {
4000 let input = "\tinvalid indented line";
4002 let parsed = parse(input);
4003 assert!(!parsed.errors.is_empty());
4005
4006 let error_msg = &parsed.errors[0].message;
4007 assert!(error_msg.contains("indented") || error_msg.contains("part of a rule"));
4008 }
4009
4010 #[test]
4011 fn test_conditional_token_handling() {
4012 let input = r#"
4014ifndef VAR
4015 CFLAGS = -DTEST
4016endif
4017"#;
4018 let parsed = parse(input);
4019 let makefile = parsed.root();
4021 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
4022 let nested = r#"
4026ifdef DEBUG
4027 ifndef RELEASE
4028 CFLAGS = -g
4029 endif
4030endif
4031"#;
4032 let parsed_nested = parse(nested);
4033 let _makefile = parsed_nested.root();
4035 }
4036
4037 #[test]
4038 fn test_include_vs_conditional_logic() {
4039 let input = r#"
4041include file.mk
4042ifdef VAR
4043 VALUE = 1
4044endif
4045"#;
4046 let parsed = parse(input);
4047 let makefile = parsed.root();
4049 let includes = makefile.includes().collect::<Vec<_>>();
4050 assert!(includes.len() >= 1 || parsed.errors.len() > 0);
4052
4053 let optional_include = r#"
4055-include optional.mk
4056ifndef VAR
4057 VALUE = default
4058endif
4059"#;
4060 let parsed2 = parse(optional_include);
4061 let _makefile = parsed2.root();
4063 }
4064
4065 #[test]
4066 fn test_balanced_parens_counting() {
4067 let input = r#"
4069VAR = $(call func,$(nested,arg),extra)
4070COMPLEX = $(if $(condition),$(then_val),$(else_val))
4071"#;
4072 let parsed = parse(input);
4073 assert!(parsed.errors.is_empty());
4074
4075 let makefile = parsed.root();
4076 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4077 assert_eq!(vars.len(), 2);
4078 }
4079
4080 #[test]
4081 fn test_documentation_lookahead() {
4082 let input = r#"
4084# Documentation comment
4085help:
4086 @echo "Usage instructions"
4087 @echo "More help text"
4088"#;
4089 let parsed = parse(input);
4090 assert!(parsed.errors.is_empty());
4091
4092 let makefile = parsed.root();
4093 let rules = makefile.rules().collect::<Vec<_>>();
4094 assert_eq!(rules.len(), 1);
4095 assert_eq!(rules[0].targets().next().unwrap(), "help");
4096 }
4097
4098 #[test]
4099 fn test_edge_case_empty_input() {
4100 let parsed = parse("");
4102 assert!(parsed.errors.is_empty());
4103
4104 let parsed2 = parse(" \n \n");
4106 let _makefile = parsed2.root();
4109 }
4110
4111 #[test]
4112 fn test_malformed_conditional_recovery() {
4113 let input = r#"
4115ifdef
4116 # Missing condition variable
4117endif
4118"#;
4119 let parsed = parse(input);
4120 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
4123 }
4124
4125 #[test]
4126 fn test_replace_rule() {
4127 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4128 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4129
4130 makefile.replace_rule(0, new_rule).unwrap();
4131
4132 let targets: Vec<_> = makefile
4133 .rules()
4134 .flat_map(|r| r.targets().collect::<Vec<_>>())
4135 .collect();
4136 assert_eq!(targets, vec!["new_rule", "rule2"]);
4137
4138 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
4139 assert_eq!(recipes, vec!["new_command"]);
4140 }
4141
4142 #[test]
4143 fn test_replace_rule_out_of_bounds() {
4144 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4145 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4146
4147 let result = makefile.replace_rule(5, new_rule);
4148 assert!(result.is_err());
4149 }
4150
4151 #[test]
4152 fn test_remove_rule() {
4153 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
4154 .parse()
4155 .unwrap();
4156
4157 let removed = makefile.remove_rule(1).unwrap();
4158 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
4159
4160 let remaining_targets: Vec<_> = makefile
4161 .rules()
4162 .flat_map(|r| r.targets().collect::<Vec<_>>())
4163 .collect();
4164 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
4165 assert_eq!(makefile.rules().count(), 2);
4166 }
4167
4168 #[test]
4169 fn test_remove_rule_out_of_bounds() {
4170 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4171
4172 let result = makefile.remove_rule(5);
4173 assert!(result.is_err());
4174 }
4175
4176 #[test]
4177 fn test_insert_rule() {
4178 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4179 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
4180
4181 makefile.insert_rule(1, new_rule).unwrap();
4182
4183 let targets: Vec<_> = makefile
4184 .rules()
4185 .flat_map(|r| r.targets().collect::<Vec<_>>())
4186 .collect();
4187 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
4188 assert_eq!(makefile.rules().count(), 3);
4189 }
4190
4191 #[test]
4192 fn test_insert_rule_at_end() {
4193 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4194 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
4195
4196 makefile.insert_rule(1, new_rule).unwrap();
4197
4198 let targets: Vec<_> = makefile
4199 .rules()
4200 .flat_map(|r| r.targets().collect::<Vec<_>>())
4201 .collect();
4202 assert_eq!(targets, vec!["rule1", "end_rule"]);
4203 }
4204
4205 #[test]
4206 fn test_insert_rule_out_of_bounds() {
4207 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4208 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4209
4210 let result = makefile.insert_rule(5, new_rule);
4211 assert!(result.is_err());
4212 }
4213
4214 #[test]
4215 fn test_remove_command() {
4216 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4217 .parse()
4218 .unwrap();
4219
4220 rule.remove_command(1);
4221 let recipes: Vec<_> = rule.recipes().collect();
4222 assert_eq!(recipes, vec!["command1", "command3"]);
4223 assert_eq!(rule.recipe_count(), 2);
4224 }
4225
4226 #[test]
4227 fn test_remove_command_out_of_bounds() {
4228 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4229
4230 let result = rule.remove_command(5);
4231 assert!(!result);
4232 }
4233
4234 #[test]
4235 fn test_insert_command() {
4236 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
4237
4238 rule.insert_command(1, "command2");
4239 let recipes: Vec<_> = rule.recipes().collect();
4240 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
4241 }
4242
4243 #[test]
4244 fn test_insert_command_at_end() {
4245 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4246
4247 rule.insert_command(1, "command2");
4248 let recipes: Vec<_> = rule.recipes().collect();
4249 assert_eq!(recipes, vec!["command1", "command2"]);
4250 }
4251
4252 #[test]
4253 fn test_insert_command_in_empty_rule() {
4254 let mut rule: Rule = "rule:\n".parse().unwrap();
4255
4256 rule.insert_command(0, "new_command");
4257 let recipes: Vec<_> = rule.recipes().collect();
4258 assert_eq!(recipes, vec!["new_command"]);
4259 }
4260
4261 #[test]
4262 fn test_recipe_count() {
4263 let rule1: Rule = "rule:\n".parse().unwrap();
4264 assert_eq!(rule1.recipe_count(), 0);
4265
4266 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
4267 assert_eq!(rule2.recipe_count(), 2);
4268 }
4269
4270 #[test]
4271 fn test_clear_commands() {
4272 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4273 .parse()
4274 .unwrap();
4275
4276 rule.clear_commands();
4277 assert_eq!(rule.recipe_count(), 0);
4278
4279 let recipes: Vec<_> = rule.recipes().collect();
4280 assert_eq!(recipes, Vec::<String>::new());
4281
4282 let targets: Vec<_> = rule.targets().collect();
4284 assert_eq!(targets, vec!["rule"]);
4285 }
4286
4287 #[test]
4288 fn test_clear_commands_empty_rule() {
4289 let mut rule: Rule = "rule:\n".parse().unwrap();
4290
4291 rule.clear_commands();
4292 assert_eq!(rule.recipe_count(), 0);
4293
4294 let targets: Vec<_> = rule.targets().collect();
4295 assert_eq!(targets, vec!["rule"]);
4296 }
4297
4298 #[test]
4299 fn test_rule_manipulation_preserves_structure() {
4300 let input = r#"# Comment
4302VAR = value
4303
4304rule1:
4305 command1
4306
4307# Another comment
4308rule2:
4309 command2
4310
4311VAR2 = value2
4312"#;
4313
4314 let mut makefile: Makefile = input.parse().unwrap();
4315 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4316
4317 makefile.insert_rule(1, new_rule).unwrap();
4319
4320 let targets: Vec<_> = makefile
4322 .rules()
4323 .flat_map(|r| r.targets().collect::<Vec<_>>())
4324 .collect();
4325 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
4326
4327 let vars: Vec<_> = makefile.variable_definitions().collect();
4329 assert_eq!(vars.len(), 2);
4330
4331 let output = makefile.code();
4333 assert!(output.contains("# Comment"));
4334 assert!(output.contains("VAR = value"));
4335 assert!(output.contains("# Another comment"));
4336 assert!(output.contains("VAR2 = value2"));
4337 }
4338
4339 #[test]
4340 fn test_replace_rule_with_multiple_targets() {
4341 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
4342 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
4343
4344 makefile.replace_rule(0, new_rule).unwrap();
4345
4346 let targets: Vec<_> = makefile
4347 .rules()
4348 .flat_map(|r| r.targets().collect::<Vec<_>>())
4349 .collect();
4350 assert_eq!(targets, vec!["new_target"]);
4351 }
4352
4353 #[test]
4354 fn test_empty_makefile_operations() {
4355 let mut makefile = Makefile::new();
4356
4357 assert!(makefile
4359 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
4360 .is_err());
4361 assert!(makefile.remove_rule(0).is_err());
4362
4363 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
4365 makefile.insert_rule(0, new_rule).unwrap();
4366 assert_eq!(makefile.rules().count(), 1);
4367 }
4368
4369 #[test]
4370 fn test_command_operations_preserve_indentation() {
4371 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
4372 .parse()
4373 .unwrap();
4374
4375 rule.insert_command(1, "middle_command");
4376 let recipes: Vec<_> = rule.recipes().collect();
4377 assert_eq!(
4378 recipes,
4379 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
4380 );
4381 }
4382
4383 #[test]
4384 fn test_rule_operations_with_variables_and_includes() {
4385 let input = r#"VAR1 = value1
4386include common.mk
4387
4388rule1:
4389 command1
4390
4391VAR2 = value2
4392include other.mk
4393
4394rule2:
4395 command2
4396"#;
4397
4398 let mut makefile: Makefile = input.parse().unwrap();
4399
4400 makefile.remove_rule(0).unwrap();
4402
4403 let output = makefile.code();
4405 assert!(output.contains("VAR1 = value1"));
4406 assert!(output.contains("include common.mk"));
4407 assert!(output.contains("VAR2 = value2"));
4408 assert!(output.contains("include other.mk"));
4409
4410 assert_eq!(makefile.rules().count(), 1);
4412 let remaining_targets: Vec<_> = makefile
4413 .rules()
4414 .flat_map(|r| r.targets().collect::<Vec<_>>())
4415 .collect();
4416 assert_eq!(remaining_targets, vec!["rule2"]);
4417 }
4418
4419 #[test]
4420 fn test_command_manipulation_edge_cases() {
4421 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
4423 assert_eq!(empty_rule.recipe_count(), 0);
4424
4425 empty_rule.insert_command(0, "first_command");
4426 assert_eq!(empty_rule.recipe_count(), 1);
4427
4428 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
4430 empty_rule2.clear_commands();
4431 assert_eq!(empty_rule2.recipe_count(), 0);
4432 }
4433
4434 #[test]
4435 fn test_archive_member_parsing() {
4436 let input = "libfoo.a(bar.o): bar.c\n\tgcc -c bar.c -o bar.o\n\tar r libfoo.a bar.o\n";
4438 let parsed = parse(input);
4439 assert!(
4440 parsed.errors.is_empty(),
4441 "Should parse archive member without errors"
4442 );
4443
4444 let makefile = parsed.root();
4445 let rules: Vec<_> = makefile.rules().collect();
4446 assert_eq!(rules.len(), 1);
4447
4448 let target_text = rules[0].targets().next().unwrap();
4450 assert_eq!(target_text, "libfoo.a(bar.o)");
4451 }
4452
4453 #[test]
4454 fn test_archive_member_multiple_members() {
4455 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";
4457 let parsed = parse(input);
4458 assert!(
4459 parsed.errors.is_empty(),
4460 "Should parse multiple archive members"
4461 );
4462
4463 let makefile = parsed.root();
4464 let rules: Vec<_> = makefile.rules().collect();
4465 assert_eq!(rules.len(), 1);
4466 }
4467
4468 #[test]
4469 fn test_archive_member_in_dependencies() {
4470 let input =
4472 "program: main.o libfoo.a(bar.o) libfoo.a(baz.o)\n\tgcc -o program main.o libfoo.a\n";
4473 let parsed = parse(input);
4474 assert!(
4475 parsed.errors.is_empty(),
4476 "Should parse archive members in dependencies"
4477 );
4478
4479 let makefile = parsed.root();
4480 let rules: Vec<_> = makefile.rules().collect();
4481 assert_eq!(rules.len(), 1);
4482 }
4483
4484 #[test]
4485 fn test_archive_member_with_variables() {
4486 let input = "$(LIB)($(OBJ)): $(SRC)\n\t$(CC) -c $(SRC)\n\t$(AR) r $(LIB) $(OBJ)\n";
4488 let parsed = parse(input);
4489 assert!(
4491 parsed.errors.is_empty(),
4492 "Should parse archive members with variables"
4493 );
4494 }
4495
4496 #[test]
4497 fn test_archive_member_ast_access() {
4498 let input = "libtest.a(foo.o bar.o): foo.c bar.c\n\tgcc -c foo.c bar.c\n";
4500 let parsed = parse(input);
4501 let makefile = parsed.root();
4502
4503 let archive_member_count = makefile
4505 .syntax()
4506 .descendants()
4507 .filter(|n| n.kind() == ARCHIVE_MEMBERS)
4508 .count();
4509
4510 assert!(
4511 archive_member_count > 0,
4512 "Should find ARCHIVE_MEMBERS nodes in AST"
4513 );
4514 }
4515
4516 #[test]
4517 fn test_large_makefile_performance() {
4518 let mut makefile = Makefile::new();
4520
4521 for i in 0..100 {
4523 let rule_name = format!("rule{}", i);
4524 let _rule = makefile
4525 .add_rule(&rule_name)
4526 .push_command(&format!("command{}", i));
4527 }
4528
4529 assert_eq!(makefile.rules().count(), 100);
4530
4531 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
4533 makefile.replace_rule(50, new_rule).unwrap();
4534
4535 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
4537 assert_eq!(rule_50_targets, vec!["middle_rule"]);
4538
4539 assert_eq!(makefile.rules().count(), 100); }
4541
4542 #[test]
4543 fn test_complex_recipe_manipulation() {
4544 let mut complex_rule: Rule = r#"complex:
4545 @echo "Starting build"
4546 $(CC) $(CFLAGS) -o $@ $<
4547 @echo "Build complete"
4548 chmod +x $@
4549"#
4550 .parse()
4551 .unwrap();
4552
4553 assert_eq!(complex_rule.recipe_count(), 4);
4554
4555 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
4560 assert_eq!(final_recipes.len(), 2);
4561 assert!(final_recipes[0].contains("$(CC)"));
4562 assert!(final_recipes[1].contains("chmod"));
4563 }
4564
4565 #[test]
4566 fn test_variable_definition_remove() {
4567 let makefile: Makefile = r#"VAR1 = value1
4568VAR2 = value2
4569VAR3 = value3
4570"#
4571 .parse()
4572 .unwrap();
4573
4574 assert_eq!(makefile.variable_definitions().count(), 3);
4576
4577 let mut var2 = makefile
4579 .variable_definitions()
4580 .nth(1)
4581 .expect("Should have second variable");
4582 assert_eq!(var2.name(), Some("VAR2".to_string()));
4583 var2.remove();
4584
4585 assert_eq!(makefile.variable_definitions().count(), 2);
4587 let var_names: Vec<_> = makefile
4588 .variable_definitions()
4589 .filter_map(|v| v.name())
4590 .collect();
4591 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
4592 }
4593
4594 #[test]
4595 fn test_variable_definition_set_value() {
4596 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
4597
4598 let mut var = makefile
4599 .variable_definitions()
4600 .next()
4601 .expect("Should have variable");
4602 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4603
4604 var.set_value("new_value");
4606
4607 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4609 assert!(makefile.code().contains("VAR = new_value"));
4610 }
4611
4612 #[test]
4613 fn test_variable_definition_set_value_preserves_format() {
4614 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
4615
4616 let mut var = makefile
4617 .variable_definitions()
4618 .next()
4619 .expect("Should have variable");
4620 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4621
4622 var.set_value("new_value");
4624
4625 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4627 let code = makefile.code();
4628 assert!(code.contains("export"), "Should preserve export prefix");
4629 assert!(code.contains(":="), "Should preserve := operator");
4630 assert!(code.contains("new_value"), "Should have new value");
4631 }
4632
4633 #[test]
4634 fn test_makefile_find_variable() {
4635 let makefile: Makefile = r#"VAR1 = value1
4636VAR2 = value2
4637VAR3 = value3
4638"#
4639 .parse()
4640 .unwrap();
4641
4642 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4644 assert_eq!(vars.len(), 1);
4645 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4646 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4647
4648 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
4650 }
4651
4652 #[test]
4653 fn test_makefile_find_variable_with_export() {
4654 let makefile: Makefile = r#"VAR1 = value1
4655export VAR2 := value2
4656VAR3 = value3
4657"#
4658 .parse()
4659 .unwrap();
4660
4661 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4663 assert_eq!(vars.len(), 1);
4664 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4665 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4666 }
4667
4668 #[test]
4669 fn test_makefile_find_variable_multiple() {
4670 let makefile: Makefile = r#"VAR1 = value1
4671VAR1 = value2
4672VAR2 = other
4673VAR1 = value3
4674"#
4675 .parse()
4676 .unwrap();
4677
4678 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
4680 assert_eq!(vars.len(), 3);
4681 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
4682 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
4683 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
4684
4685 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
4687 assert_eq!(var2s.len(), 1);
4688 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
4689 }
4690
4691 #[test]
4692 fn test_variable_remove_and_find() {
4693 let makefile: Makefile = r#"VAR1 = value1
4694VAR2 = value2
4695VAR3 = value3
4696"#
4697 .parse()
4698 .unwrap();
4699
4700 let mut var2 = makefile
4702 .find_variable("VAR2")
4703 .next()
4704 .expect("Should find VAR2");
4705 var2.remove();
4706
4707 assert_eq!(makefile.find_variable("VAR2").count(), 0);
4709
4710 assert_eq!(makefile.find_variable("VAR1").count(), 1);
4712 assert_eq!(makefile.find_variable("VAR3").count(), 1);
4713 }
4714
4715 #[test]
4716 fn test_variable_remove_with_comment() {
4717 let makefile: Makefile = r#"VAR1 = value1
4718# This is a comment about VAR2
4719VAR2 = value2
4720VAR3 = value3
4721"#
4722 .parse()
4723 .unwrap();
4724
4725 let mut var2 = makefile
4727 .variable_definitions()
4728 .nth(1)
4729 .expect("Should have second variable");
4730 assert_eq!(var2.name(), Some("VAR2".to_string()));
4731 var2.remove();
4732
4733 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4735 }
4736
4737 #[test]
4738 fn test_variable_remove_with_multiple_comments() {
4739 let makefile: Makefile = r#"VAR1 = value1
4740# Comment line 1
4741# Comment line 2
4742# Comment line 3
4743VAR2 = value2
4744VAR3 = value3
4745"#
4746 .parse()
4747 .unwrap();
4748
4749 let mut var2 = makefile
4751 .variable_definitions()
4752 .nth(1)
4753 .expect("Should have second variable");
4754 var2.remove();
4755
4756 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4758 }
4759
4760 #[test]
4761 fn test_variable_remove_with_empty_line() {
4762 let makefile: Makefile = r#"VAR1 = value1
4763
4764# Comment about VAR2
4765VAR2 = value2
4766VAR3 = value3
4767"#
4768 .parse()
4769 .unwrap();
4770
4771 let mut var2 = makefile
4773 .variable_definitions()
4774 .nth(1)
4775 .expect("Should have second variable");
4776 var2.remove();
4777
4778 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4781 }
4782
4783 #[test]
4784 fn test_variable_remove_with_multiple_empty_lines() {
4785 let makefile: Makefile = r#"VAR1 = value1
4786
4787
4788# Comment about VAR2
4789VAR2 = value2
4790VAR3 = value3
4791"#
4792 .parse()
4793 .unwrap();
4794
4795 let mut var2 = makefile
4797 .variable_definitions()
4798 .nth(1)
4799 .expect("Should have second variable");
4800 var2.remove();
4801
4802 assert_eq!(makefile.code(), "VAR1 = value1\n\nVAR3 = value3\n");
4805 }
4806
4807 #[test]
4808 fn test_rule_remove_with_comment() {
4809 let makefile: Makefile = r#"rule1:
4810 command1
4811
4812# Comment about rule2
4813rule2:
4814 command2
4815rule3:
4816 command3
4817"#
4818 .parse()
4819 .unwrap();
4820
4821 let rule2 = makefile.rules().nth(1).expect("Should have second rule");
4823 rule2.remove().unwrap();
4824
4825 assert_eq!(
4828 makefile.code(),
4829 "rule1:\n\tcommand1\n\nrule3:\n\tcommand3\n"
4830 );
4831 }
4832
4833 #[test]
4834 fn test_variable_remove_preserves_shebang() {
4835 let makefile: Makefile = r#"#!/usr/bin/make -f
4836# This is a regular comment
4837VAR1 = value1
4838VAR2 = value2
4839"#
4840 .parse()
4841 .unwrap();
4842
4843 let mut var1 = makefile.variable_definitions().next().unwrap();
4845 var1.remove();
4846
4847 let code = makefile.code();
4849 assert!(code.starts_with("#!/usr/bin/make -f"));
4850 assert!(!code.contains("regular comment"));
4851 assert!(!code.contains("VAR1"));
4852 assert!(code.contains("VAR2"));
4853 }
4854
4855 #[test]
4856 fn test_variable_remove_preserves_subsequent_comments() {
4857 let makefile: Makefile = r#"VAR1 = value1
4858# Comment about VAR2
4859VAR2 = value2
4860
4861# Comment about VAR3
4862VAR3 = value3
4863"#
4864 .parse()
4865 .unwrap();
4866
4867 let mut var2 = makefile
4869 .variable_definitions()
4870 .nth(1)
4871 .expect("Should have second variable");
4872 var2.remove();
4873
4874 let code = makefile.code();
4876 assert_eq!(
4877 code,
4878 "VAR1 = value1\n\n# Comment about VAR3\nVAR3 = value3\n"
4879 );
4880 }
4881
4882 #[test]
4883 fn test_variable_remove_after_shebang_preserves_empty_line() {
4884 let makefile: Makefile = r#"#!/usr/bin/make -f
4885export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
4886
4887%:
4888 dh $@
4889"#
4890 .parse()
4891 .unwrap();
4892
4893 let mut var = makefile.variable_definitions().next().unwrap();
4895 var.remove();
4896
4897 assert_eq!(makefile.code(), "#!/usr/bin/make -f\n\n%:\n\tdh $@\n");
4899 }
4900
4901 #[test]
4902 fn test_rule_add_prerequisite() {
4903 let mut rule: Rule = "target: dep1\n".parse().unwrap();
4904 rule.add_prerequisite("dep2").unwrap();
4905 assert_eq!(
4906 rule.prerequisites().collect::<Vec<_>>(),
4907 vec!["dep1", "dep2"]
4908 );
4909 }
4910
4911 #[test]
4912 fn test_rule_remove_prerequisite() {
4913 let mut rule: Rule = "target: dep1 dep2 dep3\n".parse().unwrap();
4914 assert!(rule.remove_prerequisite("dep2").unwrap());
4915 assert_eq!(
4916 rule.prerequisites().collect::<Vec<_>>(),
4917 vec!["dep1", "dep3"]
4918 );
4919 assert!(!rule.remove_prerequisite("nonexistent").unwrap());
4920 }
4921
4922 #[test]
4923 fn test_rule_set_prerequisites() {
4924 let mut rule: Rule = "target: old_dep\n".parse().unwrap();
4925 rule.set_prerequisites(vec!["new_dep1", "new_dep2"])
4926 .unwrap();
4927 assert_eq!(
4928 rule.prerequisites().collect::<Vec<_>>(),
4929 vec!["new_dep1", "new_dep2"]
4930 );
4931 }
4932
4933 #[test]
4934 fn test_rule_set_prerequisites_empty() {
4935 let mut rule: Rule = "target: dep1 dep2\n".parse().unwrap();
4936 rule.set_prerequisites(vec![]).unwrap();
4937 assert_eq!(rule.prerequisites().collect::<Vec<_>>().len(), 0);
4938 }
4939
4940 #[test]
4941 fn test_rule_remove() {
4942 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4943 let rule = makefile.find_rule_by_target("rule1").unwrap();
4944 rule.remove().unwrap();
4945 assert_eq!(makefile.rules().count(), 1);
4946 assert!(makefile.find_rule_by_target("rule1").is_none());
4947 assert!(makefile.find_rule_by_target("rule2").is_some());
4948 }
4949
4950 #[test]
4951 fn test_makefile_find_rule_by_target() {
4952 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4953 let rule = makefile.find_rule_by_target("rule2");
4954 assert!(rule.is_some());
4955 assert_eq!(rule.unwrap().targets().collect::<Vec<_>>(), vec!["rule2"]);
4956 assert!(makefile.find_rule_by_target("nonexistent").is_none());
4957 }
4958
4959 #[test]
4960 fn test_makefile_find_rules_by_target() {
4961 let makefile: Makefile = "rule1:\n\tcommand1\nrule1:\n\tcommand2\nrule2:\n\tcommand3\n"
4962 .parse()
4963 .unwrap();
4964 assert_eq!(makefile.find_rules_by_target("rule1").count(), 2);
4965 assert_eq!(makefile.find_rules_by_target("rule2").count(), 1);
4966 assert_eq!(makefile.find_rules_by_target("nonexistent").count(), 0);
4967 }
4968
4969 #[test]
4970 fn test_makefile_add_phony_target() {
4971 let mut makefile = Makefile::new();
4972 makefile.add_phony_target("clean").unwrap();
4973 assert!(makefile.is_phony("clean"));
4974 assert_eq!(makefile.phony_targets().collect::<Vec<_>>(), vec!["clean"]);
4975 }
4976
4977 #[test]
4978 fn test_makefile_add_phony_target_existing() {
4979 let mut makefile: Makefile = ".PHONY: test\n".parse().unwrap();
4980 makefile.add_phony_target("clean").unwrap();
4981 assert!(makefile.is_phony("test"));
4982 assert!(makefile.is_phony("clean"));
4983 let targets: Vec<_> = makefile.phony_targets().collect();
4984 assert!(targets.contains(&"test".to_string()));
4985 assert!(targets.contains(&"clean".to_string()));
4986 }
4987
4988 #[test]
4989 fn test_makefile_remove_phony_target() {
4990 let mut makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4991 assert!(makefile.remove_phony_target("clean").unwrap());
4992 assert!(!makefile.is_phony("clean"));
4993 assert!(makefile.is_phony("test"));
4994 assert!(!makefile.remove_phony_target("nonexistent").unwrap());
4995 }
4996
4997 #[test]
4998 fn test_makefile_remove_phony_target_last() {
4999 let mut makefile: Makefile = ".PHONY: clean\n".parse().unwrap();
5000 assert!(makefile.remove_phony_target("clean").unwrap());
5001 assert!(!makefile.is_phony("clean"));
5002 assert!(makefile.find_rule_by_target(".PHONY").is_none());
5004 }
5005
5006 #[test]
5007 fn test_makefile_is_phony() {
5008 let makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
5009 assert!(makefile.is_phony("clean"));
5010 assert!(makefile.is_phony("test"));
5011 assert!(!makefile.is_phony("build"));
5012 }
5013
5014 #[test]
5015 fn test_makefile_phony_targets() {
5016 let makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
5017 let phony_targets: Vec<_> = makefile.phony_targets().collect();
5018 assert_eq!(phony_targets, vec!["clean", "test", "build"]);
5019 }
5020
5021 #[test]
5022 fn test_makefile_phony_targets_empty() {
5023 let makefile = Makefile::new();
5024 assert_eq!(makefile.phony_targets().count(), 0);
5025 }
5026}