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 is_export(&self) -> bool {
1453 self.syntax()
1454 .children_with_tokens()
1455 .any(|it| it.as_token().is_some_and(|token| token.text() == "export"))
1456 }
1457
1458 pub fn raw_value(&self) -> Option<String> {
1460 self.syntax()
1461 .children()
1462 .find(|it| it.kind() == EXPR)
1463 .map(|it| it.text().into())
1464 }
1465
1466 pub fn remove(&mut self) {
1479 if let Some(parent) = self.syntax().parent() {
1480 remove_with_preceding_comments(self.syntax(), &parent);
1481 }
1482 }
1483
1484 pub fn set_value(&mut self, new_value: &str) {
1497 let expr_index = self
1499 .syntax()
1500 .children()
1501 .find(|it| it.kind() == EXPR)
1502 .map(|it| it.index());
1503
1504 if let Some(expr_idx) = expr_index {
1505 let mut builder = GreenNodeBuilder::new();
1507 builder.start_node(EXPR.into());
1508 builder.token(IDENTIFIER.into(), new_value);
1509 builder.finish_node();
1510
1511 let new_expr = SyntaxNode::new_root_mut(builder.finish());
1512
1513 self.0
1515 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
1516 }
1517 }
1518}
1519
1520impl Makefile {
1521 pub fn new() -> Makefile {
1523 let mut builder = GreenNodeBuilder::new();
1524
1525 builder.start_node(ROOT.into());
1526 builder.finish_node();
1527
1528 let syntax = SyntaxNode::new_root_mut(builder.finish());
1529 Makefile(syntax)
1530 }
1531
1532 pub fn parse(text: &str) -> crate::Parse<Makefile> {
1534 crate::Parse::<Makefile>::parse_makefile(text)
1535 }
1536
1537 pub fn code(&self) -> String {
1539 self.syntax().text().to_string()
1540 }
1541
1542 pub fn is_root(&self) -> bool {
1544 self.syntax().kind() == ROOT
1545 }
1546
1547 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1549 let mut buf = String::new();
1550 r.read_to_string(&mut buf)?;
1551 buf.parse()
1552 }
1553
1554 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1556 let mut buf = String::new();
1557 r.read_to_string(&mut buf)?;
1558
1559 let parsed = parse(&buf);
1560 Ok(parsed.root())
1561 }
1562
1563 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
1572 self.syntax().children().filter_map(Rule::cast)
1573 }
1574
1575 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1577 self.rules()
1578 .filter(move |rule| rule.targets().any(|t| t == target))
1579 }
1580
1581 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
1583 self.syntax()
1584 .children()
1585 .filter_map(VariableDefinition::cast)
1586 }
1587
1588 pub fn find_variable<'a>(
1603 &'a self,
1604 name: &'a str,
1605 ) -> impl Iterator<Item = VariableDefinition> + 'a {
1606 self.variable_definitions()
1607 .filter(move |var| var.name().as_deref() == Some(name))
1608 }
1609
1610 pub fn add_rule(&mut self, target: &str) -> Rule {
1620 let mut builder = GreenNodeBuilder::new();
1621 builder.start_node(RULE.into());
1622 builder.token(IDENTIFIER.into(), target);
1623 builder.token(OPERATOR.into(), ":");
1624 builder.token(NEWLINE.into(), "\n");
1625 builder.finish_node();
1626
1627 let syntax = SyntaxNode::new_root_mut(builder.finish());
1628 let pos = self.0.children_with_tokens().count();
1629 self.0.splice_children(pos..pos, vec![syntax.into()]);
1630 Rule(self.0.children().nth(pos).unwrap())
1631 }
1632
1633 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1635 let mut buf = String::new();
1636 r.read_to_string(&mut buf)?;
1637
1638 let parsed = parse(&buf);
1639 if !parsed.errors.is_empty() {
1640 Err(Error::Parse(ParseError {
1641 errors: parsed.errors,
1642 }))
1643 } else {
1644 Ok(parsed.root())
1645 }
1646 }
1647
1648 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1659 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1660
1661 if rules.is_empty() {
1662 return Err(Error::Parse(ParseError {
1663 errors: vec![ErrorInfo {
1664 message: "Cannot replace rule in empty makefile".to_string(),
1665 line: 1,
1666 context: "replace_rule".to_string(),
1667 }],
1668 }));
1669 }
1670
1671 if index >= rules.len() {
1672 return Err(Error::Parse(ParseError {
1673 errors: vec![ErrorInfo {
1674 message: format!(
1675 "Rule index {} out of bounds (max {})",
1676 index,
1677 rules.len() - 1
1678 ),
1679 line: 1,
1680 context: "replace_rule".to_string(),
1681 }],
1682 }));
1683 }
1684
1685 let target_node = &rules[index];
1686 let target_index = target_node.index();
1687
1688 self.0.splice_children(
1690 target_index..target_index + 1,
1691 vec![new_rule.0.clone().into()],
1692 );
1693 Ok(())
1694 }
1695
1696 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
1707 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1708
1709 if rules.is_empty() {
1710 return Err(Error::Parse(ParseError {
1711 errors: vec![ErrorInfo {
1712 message: "Cannot remove rule from empty makefile".to_string(),
1713 line: 1,
1714 context: "remove_rule".to_string(),
1715 }],
1716 }));
1717 }
1718
1719 if index >= rules.len() {
1720 return Err(Error::Parse(ParseError {
1721 errors: vec![ErrorInfo {
1722 message: format!(
1723 "Rule index {} out of bounds (max {})",
1724 index,
1725 rules.len() - 1
1726 ),
1727 line: 1,
1728 context: "remove_rule".to_string(),
1729 }],
1730 }));
1731 }
1732
1733 let target_node = rules[index].clone();
1734 let target_index = target_node.index();
1735
1736 self.0
1738 .splice_children(target_index..target_index + 1, vec![]);
1739 Ok(Rule(target_node))
1740 }
1741
1742 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1754 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1755
1756 if index > rules.len() {
1757 return Err(Error::Parse(ParseError {
1758 errors: vec![ErrorInfo {
1759 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1760 line: 1,
1761 context: "insert_rule".to_string(),
1762 }],
1763 }));
1764 }
1765
1766 let target_index = if index == rules.len() {
1767 self.0.children_with_tokens().count()
1769 } else {
1770 rules[index].index()
1772 };
1773
1774 self.0
1776 .splice_children(target_index..target_index, vec![new_rule.0.clone().into()]);
1777 Ok(())
1778 }
1779
1780 pub fn includes(&self) -> impl Iterator<Item = Include> {
1790 self.syntax().children().filter_map(Include::cast)
1791 }
1792
1793 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1803 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1806 let mut includes = Vec::new();
1807
1808 if let Some(include) = Include::cast(node.clone()) {
1810 includes.push(include);
1811 }
1812
1813 for child in node.children() {
1815 includes.extend(collect_includes(&child));
1816 }
1817
1818 includes
1819 }
1820
1821 let includes = collect_includes(self.syntax());
1823
1824 includes.into_iter().map(|include| {
1826 include
1827 .syntax()
1828 .children()
1829 .find(|node| node.kind() == EXPR)
1830 .map(|expr| expr.text().to_string().trim().to_string())
1831 .unwrap_or_default()
1832 })
1833 }
1834
1835 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1846 self.rules()
1847 .find(|rule| rule.targets().any(|t| t == target))
1848 }
1849
1850 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1860 self.rules_by_target(target)
1861 }
1862
1863 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1873 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1875 if !phony_rule.prerequisites().any(|p| p == target) {
1877 phony_rule.add_prerequisite(target)?;
1878 }
1879 } else {
1880 let mut phony_rule = self.add_rule(".PHONY");
1882 phony_rule.add_prerequisite(target)?;
1883 }
1884 Ok(())
1885 }
1886
1887 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1901 let mut phony_rule = None;
1903 for rule in self.rules_by_target(".PHONY") {
1904 if rule.prerequisites().any(|p| p == target) {
1905 phony_rule = Some(rule);
1906 break;
1907 }
1908 }
1909
1910 let mut phony_rule = match phony_rule {
1911 Some(rule) => rule,
1912 None => return Ok(false),
1913 };
1914
1915 let prereq_count = phony_rule.prerequisites().count();
1917
1918 phony_rule.remove_prerequisite(target)?;
1920
1921 if prereq_count == 1 {
1923 phony_rule.remove()?;
1925 }
1926
1927 Ok(true)
1928 }
1929
1930 pub fn is_phony(&self, target: &str) -> bool {
1941 self.rules_by_target(".PHONY")
1943 .any(|rule| rule.prerequisites().any(|p| p == target))
1944 }
1945
1946 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1956 self.rules_by_target(".PHONY")
1958 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1959 }
1960}
1961
1962impl FromStr for Rule {
1963 type Err = crate::Error;
1964
1965 fn from_str(s: &str) -> Result<Self, Self::Err> {
1966 Rule::parse(s).to_rule_result()
1967 }
1968}
1969
1970impl FromStr for Makefile {
1971 type Err = crate::Error;
1972
1973 fn from_str(s: &str) -> Result<Self, Self::Err> {
1974 Makefile::parse(s).to_result()
1975 }
1976}
1977
1978fn build_prerequisites_node(prereqs: &[String]) -> SyntaxNode {
1980 let mut builder = GreenNodeBuilder::new();
1981 builder.start_node(PREREQUISITES.into());
1982
1983 for (i, prereq) in prereqs.iter().enumerate() {
1984 if i > 0 {
1985 builder.token(WHITESPACE.into(), " ");
1986 }
1987
1988 builder.start_node(PREREQUISITE.into());
1990 builder.token(IDENTIFIER.into(), prereq);
1991 builder.finish_node();
1992 }
1993
1994 builder.finish_node();
1995 SyntaxNode::new_root_mut(builder.finish())
1996}
1997
1998impl Rule {
1999 pub fn parse(text: &str) -> crate::Parse<Rule> {
2001 crate::Parse::<Rule>::parse_rule(text)
2002 }
2003
2004 fn collect_variable_reference(
2006 &self,
2007 tokens: &mut std::iter::Peekable<impl Iterator<Item = SyntaxElement>>,
2008 ) -> Option<String> {
2009 let mut var_ref = String::new();
2010
2011 if let Some(token) = tokens.next() {
2013 if let Some(t) = token.as_token() {
2014 if t.kind() == DOLLAR {
2015 var_ref.push_str(t.text());
2016
2017 if let Some(next) = tokens.peek() {
2019 if let Some(nt) = next.as_token() {
2020 if nt.kind() == LPAREN {
2021 var_ref.push_str(nt.text());
2023 tokens.next();
2024
2025 let mut paren_count = 1;
2027
2028 for next_token in tokens.by_ref() {
2030 if let Some(nt) = next_token.as_token() {
2031 var_ref.push_str(nt.text());
2032
2033 if nt.kind() == LPAREN {
2034 paren_count += 1;
2035 } else if nt.kind() == RPAREN {
2036 paren_count -= 1;
2037 if paren_count == 0 {
2038 break;
2039 }
2040 }
2041 }
2042 }
2043
2044 return Some(var_ref);
2045 }
2046 }
2047 }
2048
2049 for next_token in tokens.by_ref() {
2051 if let Some(nt) = next_token.as_token() {
2052 var_ref.push_str(nt.text());
2053 if nt.kind() == RPAREN {
2054 break;
2055 }
2056 }
2057 }
2058 return Some(var_ref);
2059 }
2060 }
2061 }
2062
2063 None
2064 }
2065
2066 pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
2076 let mut result = Vec::new();
2077 let mut tokens = self
2078 .syntax()
2079 .children_with_tokens()
2080 .take_while(|it| it.as_token().map(|t| t.kind() != OPERATOR).unwrap_or(true))
2081 .peekable();
2082
2083 while let Some(token) = tokens.peek().cloned() {
2084 if let Some(node) = token.as_node() {
2085 tokens.next(); if node.kind() == EXPR {
2087 let mut var_content = String::new();
2089 for child in node.children_with_tokens() {
2090 if let Some(t) = child.as_token() {
2091 var_content.push_str(t.text());
2092 }
2093 }
2094 if !var_content.is_empty() {
2095 result.push(var_content);
2096 }
2097 }
2098 } else if let Some(t) = token.as_token() {
2099 if t.kind() == DOLLAR {
2100 if let Some(var_ref) = self.collect_variable_reference(&mut tokens) {
2101 result.push(var_ref);
2102 }
2103 } else if t.kind() == IDENTIFIER {
2104 let ident_text = t.text().to_string();
2106 tokens.next(); if let Some(next) = tokens.peek() {
2110 if let Some(next_token) = next.as_token() {
2111 if next_token.kind() == LPAREN {
2112 let mut archive_target = ident_text;
2114 archive_target.push_str(next_token.text()); tokens.next(); while let Some(token) = tokens.peek() {
2119 if let Some(node) = token.as_node() {
2120 if node.kind() == ARCHIVE_MEMBERS {
2121 archive_target.push_str(&node.text().to_string());
2122 tokens.next();
2123 } else {
2124 tokens.next();
2125 }
2126 } else if let Some(t) = token.as_token() {
2127 if t.kind() == RPAREN {
2128 archive_target.push_str(t.text());
2129 tokens.next();
2130 break;
2131 } else {
2132 tokens.next();
2133 }
2134 } else {
2135 break;
2136 }
2137 }
2138 result.push(archive_target);
2139 } else {
2140 result.push(ident_text);
2142 }
2143 } else {
2144 result.push(ident_text);
2146 }
2147 } else {
2148 result.push(ident_text);
2150 }
2151 } else {
2152 tokens.next(); }
2154 }
2155 }
2156 result.into_iter()
2157 }
2158
2159 pub fn prerequisites(&self) -> impl Iterator<Item = String> + '_ {
2168 let mut found_operator = false;
2170 let mut prerequisites_node = None;
2171
2172 for element in self.syntax().children_with_tokens() {
2173 if let Some(token) = element.as_token() {
2174 if token.kind() == OPERATOR {
2175 found_operator = true;
2176 }
2177 } else if let Some(node) = element.as_node() {
2178 if found_operator && node.kind() == PREREQUISITES {
2179 prerequisites_node = Some(node.clone());
2180 break;
2181 }
2182 }
2183 }
2184
2185 let result: Vec<String> = if let Some(prereqs) = prerequisites_node {
2186 prereqs
2188 .children()
2189 .filter(|child| child.kind() == PREREQUISITE)
2190 .map(|child| child.text().to_string().trim().to_string())
2191 .collect()
2192 } else {
2193 Vec::new()
2194 };
2195
2196 result.into_iter()
2197 }
2198
2199 pub fn recipes(&self) -> impl Iterator<Item = String> {
2208 self.syntax()
2209 .children()
2210 .filter(|it| it.kind() == RECIPE)
2211 .flat_map(|it| {
2212 it.children_with_tokens().filter_map(|it| {
2213 it.as_token().and_then(|t| {
2214 if t.kind() == TEXT {
2215 Some(t.text().to_string())
2216 } else {
2217 None
2218 }
2219 })
2220 })
2221 })
2222 }
2223
2224 pub fn replace_command(&mut self, i: usize, line: &str) -> bool {
2234 let index = self
2236 .syntax()
2237 .children()
2238 .filter(|it| it.kind() == RECIPE)
2239 .nth(i);
2240
2241 let index = match index {
2242 Some(node) => node.index(),
2243 None => return false,
2244 };
2245
2246 let mut builder = GreenNodeBuilder::new();
2247 builder.start_node(RECIPE.into());
2248 builder.token(INDENT.into(), "\t");
2249 builder.token(TEXT.into(), line);
2250 builder.token(NEWLINE.into(), "\n");
2251 builder.finish_node();
2252
2253 let syntax = SyntaxNode::new_root_mut(builder.finish());
2254
2255 self.0
2256 .splice_children(index..index + 1, vec![syntax.into()]);
2257
2258 true
2259 }
2260
2261 pub fn push_command(&mut self, line: &str) {
2271 let index = self
2273 .0
2274 .children_with_tokens()
2275 .filter(|it| it.kind() == RECIPE)
2276 .last();
2277
2278 let index = index.map_or_else(
2279 || self.0.children_with_tokens().count(),
2280 |it| it.index() + 1,
2281 );
2282
2283 let mut builder = GreenNodeBuilder::new();
2284 builder.start_node(RECIPE.into());
2285 builder.token(INDENT.into(), "\t");
2286 builder.token(TEXT.into(), line);
2287 builder.token(NEWLINE.into(), "\n");
2288 builder.finish_node();
2289 let syntax = SyntaxNode::new_root_mut(builder.finish());
2290
2291 self.0.splice_children(index..index, vec![syntax.into()]);
2292 }
2293
2294 pub fn remove_command(&mut self, index: usize) -> bool {
2304 let recipes: Vec<_> = self
2305 .syntax()
2306 .children()
2307 .filter(|n| n.kind() == RECIPE)
2308 .collect();
2309
2310 if index >= recipes.len() {
2311 return false;
2312 }
2313
2314 let target_node = &recipes[index];
2315 let target_index = target_node.index();
2316
2317 self.0
2318 .splice_children(target_index..target_index + 1, vec![]);
2319 true
2320 }
2321
2322 pub fn insert_command(&mut self, index: usize, line: &str) -> bool {
2333 let recipes: Vec<_> = self
2334 .syntax()
2335 .children()
2336 .filter(|n| n.kind() == RECIPE)
2337 .collect();
2338
2339 if index > recipes.len() {
2340 return false;
2341 }
2342
2343 let target_index = if index == recipes.len() {
2344 recipes.last().map(|n| n.index() + 1).unwrap_or_else(|| {
2346 self.0.children_with_tokens().count()
2348 })
2349 } else {
2350 recipes[index].index()
2352 };
2353
2354 let mut builder = GreenNodeBuilder::new();
2355 builder.start_node(RECIPE.into());
2356 builder.token(INDENT.into(), "\t");
2357 builder.token(TEXT.into(), line);
2358 builder.token(NEWLINE.into(), "\n");
2359 builder.finish_node();
2360 let syntax = SyntaxNode::new_root_mut(builder.finish());
2361
2362 self.0
2363 .splice_children(target_index..target_index, vec![syntax.into()]);
2364 true
2365 }
2366
2367 pub fn recipe_count(&self) -> usize {
2376 self.syntax()
2377 .children()
2378 .filter(|n| n.kind() == RECIPE)
2379 .count()
2380 }
2381
2382 pub fn clear_commands(&mut self) {
2392 let recipes: Vec<_> = self
2393 .syntax()
2394 .children()
2395 .filter(|n| n.kind() == RECIPE)
2396 .collect();
2397
2398 if recipes.is_empty() {
2399 return;
2400 }
2401
2402 for recipe in recipes.iter().rev() {
2404 let index = recipe.index();
2405 self.0.splice_children(index..index + 1, vec![]);
2406 }
2407 }
2408
2409 pub fn remove_prerequisite(&mut self, target: &str) -> Result<bool, Error> {
2422 let mut found_operator = false;
2424 let mut prereqs_node = None;
2425
2426 for child in self.syntax().children_with_tokens() {
2427 if let Some(token) = child.as_token() {
2428 if token.kind() == OPERATOR {
2429 found_operator = true;
2430 }
2431 } else if let Some(node) = child.as_node() {
2432 if found_operator && node.kind() == PREREQUISITES {
2433 prereqs_node = Some(node.clone());
2434 break;
2435 }
2436 }
2437 }
2438
2439 let prereqs_node = match prereqs_node {
2440 Some(node) => node,
2441 None => return Ok(false), };
2443
2444 let current_prereqs: Vec<String> = self.prerequisites().collect();
2446
2447 if !current_prereqs.iter().any(|p| p == target) {
2449 return Ok(false);
2450 }
2451
2452 let new_prereqs: Vec<String> = current_prereqs
2454 .into_iter()
2455 .filter(|p| p != target)
2456 .collect();
2457
2458 let prereqs_index = prereqs_node.index();
2460 let new_prereqs_node = build_prerequisites_node(&new_prereqs);
2461
2462 self.0.splice_children(
2463 prereqs_index..prereqs_index + 1,
2464 vec![new_prereqs_node.into()],
2465 );
2466
2467 Ok(true)
2468 }
2469
2470 pub fn add_prerequisite(&mut self, target: &str) -> Result<(), Error> {
2480 let mut current_prereqs: Vec<String> = self.prerequisites().collect();
2481 current_prereqs.push(target.to_string());
2482 self.set_prerequisites(current_prereqs.iter().map(|s| s.as_str()).collect())
2483 }
2484
2485 pub fn set_prerequisites(&mut self, prereqs: Vec<&str>) -> Result<(), Error> {
2495 let mut prereqs_index = None;
2497 let mut operator_found = false;
2498
2499 for child in self.syntax().children_with_tokens() {
2500 if let Some(token) = child.as_token() {
2501 if token.kind() == OPERATOR {
2502 operator_found = true;
2503 }
2504 } else if let Some(node) = child.as_node() {
2505 if operator_found && node.kind() == PREREQUISITES {
2506 prereqs_index = Some((node.index(), true)); break;
2508 }
2509 }
2510 }
2511
2512 let new_prereqs =
2514 build_prerequisites_node(&prereqs.iter().map(|s| s.to_string()).collect::<Vec<_>>());
2515
2516 match prereqs_index {
2517 Some((idx, true)) => {
2518 self.0
2520 .splice_children(idx..idx + 1, vec![new_prereqs.into()]);
2521 }
2522 _ => {
2523 let insert_pos = self
2525 .syntax()
2526 .children_with_tokens()
2527 .position(|t| t.as_token().map(|t| t.kind() == OPERATOR).unwrap_or(false))
2528 .map(|p| p + 1)
2529 .ok_or_else(|| {
2530 Error::Parse(ParseError {
2531 errors: vec![ErrorInfo {
2532 message: "No operator found in rule".to_string(),
2533 line: 1,
2534 context: "set_prerequisites".to_string(),
2535 }],
2536 })
2537 })?;
2538
2539 self.0
2540 .splice_children(insert_pos..insert_pos, vec![new_prereqs.into()]);
2541 }
2542 }
2543
2544 Ok(())
2545 }
2546
2547 pub fn remove(self) -> Result<(), Error> {
2560 let parent = self.syntax().parent().ok_or_else(|| {
2561 Error::Parse(ParseError {
2562 errors: vec![ErrorInfo {
2563 message: "Rule has no parent".to_string(),
2564 line: 1,
2565 context: "remove".to_string(),
2566 }],
2567 })
2568 })?;
2569
2570 remove_with_preceding_comments(self.syntax(), &parent);
2571 Ok(())
2572 }
2573}
2574
2575impl Default for Makefile {
2576 fn default() -> Self {
2577 Self::new()
2578 }
2579}
2580
2581impl Include {
2582 pub fn path(&self) -> Option<String> {
2584 self.syntax()
2585 .children()
2586 .find(|it| it.kind() == EXPR)
2587 .map(|it| it.text().to_string().trim().to_string())
2588 }
2589
2590 pub fn is_optional(&self) -> bool {
2592 let text = self.syntax().text();
2593 text.to_string().starts_with("-include") || text.to_string().starts_with("sinclude")
2594 }
2595}
2596
2597#[cfg(test)]
2598mod tests {
2599 use super::*;
2600
2601 #[test]
2602 fn test_conditionals() {
2603 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
2607 let mut buf = code.as_bytes();
2608 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
2609 assert!(makefile.code().contains("DEBUG_FLAG"));
2610
2611 let code =
2613 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
2614 let mut buf = code.as_bytes();
2615 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
2616 assert!(makefile.code().contains("RESULT"));
2617 assert!(makefile.code().contains("windows"));
2618
2619 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
2621 let mut buf = code.as_bytes();
2622 let makefile = Makefile::read_relaxed(&mut buf)
2623 .expect("Failed to parse nested conditionals with else");
2624 assert!(makefile.code().contains("CFLAGS"));
2625 assert!(makefile.code().contains("VERBOSE"));
2626
2627 let code = "ifdef DEBUG\nendif\n";
2629 let mut buf = code.as_bytes();
2630 let makefile =
2631 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
2632 assert!(makefile.code().contains("ifdef DEBUG"));
2633
2634 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelif ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
2636 let mut buf = code.as_bytes();
2637 let makefile =
2638 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with elif");
2639 assert!(makefile.code().contains("EXT"));
2640
2641 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
2643 let mut buf = code.as_bytes();
2644 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
2645 assert!(makefile.code().contains("DEBUG"));
2646
2647 let code = "ifdef \nDEBUG := 1\nendif\n";
2649 let mut buf = code.as_bytes();
2650 let makefile = Makefile::read_relaxed(&mut buf)
2651 .expect("Failed to parse with recovery - missing condition");
2652 assert!(makefile.code().contains("DEBUG"));
2653 }
2654
2655 #[test]
2656 fn test_parse_simple() {
2657 const SIMPLE: &str = r#"VARIABLE = value
2658
2659rule: dependency
2660 command
2661"#;
2662 let parsed = parse(SIMPLE);
2663 assert!(parsed.errors.is_empty());
2664 let node = parsed.syntax();
2665 assert_eq!(
2666 format!("{:#?}", node),
2667 r#"ROOT@0..44
2668 VARIABLE@0..17
2669 IDENTIFIER@0..8 "VARIABLE"
2670 WHITESPACE@8..9 " "
2671 OPERATOR@9..10 "="
2672 WHITESPACE@10..11 " "
2673 EXPR@11..16
2674 IDENTIFIER@11..16 "value"
2675 NEWLINE@16..17 "\n"
2676 NEWLINE@17..18 "\n"
2677 RULE@18..44
2678 IDENTIFIER@18..22 "rule"
2679 OPERATOR@22..23 ":"
2680 WHITESPACE@23..24 " "
2681 PREREQUISITES@24..34
2682 PREREQUISITE@24..34
2683 IDENTIFIER@24..34 "dependency"
2684 NEWLINE@34..35 "\n"
2685 RECIPE@35..44
2686 INDENT@35..36 "\t"
2687 TEXT@36..43 "command"
2688 NEWLINE@43..44 "\n"
2689"#
2690 );
2691
2692 let root = parsed.root();
2693
2694 let mut rules = root.rules().collect::<Vec<_>>();
2695 assert_eq!(rules.len(), 1);
2696 let rule = rules.pop().unwrap();
2697 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2698 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
2699 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2700
2701 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2702 assert_eq!(variables.len(), 1);
2703 let variable = variables.pop().unwrap();
2704 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2705 assert_eq!(variable.raw_value(), Some("value".to_string()));
2706 }
2707
2708 #[test]
2709 fn test_parse_export_assign() {
2710 const EXPORT: &str = r#"export VARIABLE := value
2711"#;
2712 let parsed = parse(EXPORT);
2713 assert!(parsed.errors.is_empty());
2714 let node = parsed.syntax();
2715 assert_eq!(
2716 format!("{:#?}", node),
2717 r#"ROOT@0..25
2718 VARIABLE@0..25
2719 IDENTIFIER@0..6 "export"
2720 WHITESPACE@6..7 " "
2721 IDENTIFIER@7..15 "VARIABLE"
2722 WHITESPACE@15..16 " "
2723 OPERATOR@16..18 ":="
2724 WHITESPACE@18..19 " "
2725 EXPR@19..24
2726 IDENTIFIER@19..24 "value"
2727 NEWLINE@24..25 "\n"
2728"#
2729 );
2730
2731 let root = parsed.root();
2732
2733 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2734 assert_eq!(variables.len(), 1);
2735 let variable = variables.pop().unwrap();
2736 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2737 assert_eq!(variable.raw_value(), Some("value".to_string()));
2738 }
2739
2740 #[test]
2741 fn test_parse_multiple_prerequisites() {
2742 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
2743 command
2744
2745"#;
2746 let parsed = parse(MULTIPLE_PREREQUISITES);
2747 assert!(parsed.errors.is_empty());
2748 let node = parsed.syntax();
2749 assert_eq!(
2750 format!("{:#?}", node),
2751 r#"ROOT@0..40
2752 RULE@0..40
2753 IDENTIFIER@0..4 "rule"
2754 OPERATOR@4..5 ":"
2755 WHITESPACE@5..6 " "
2756 PREREQUISITES@6..29
2757 PREREQUISITE@6..17
2758 IDENTIFIER@6..17 "dependency1"
2759 WHITESPACE@17..18 " "
2760 PREREQUISITE@18..29
2761 IDENTIFIER@18..29 "dependency2"
2762 NEWLINE@29..30 "\n"
2763 RECIPE@30..39
2764 INDENT@30..31 "\t"
2765 TEXT@31..38 "command"
2766 NEWLINE@38..39 "\n"
2767 NEWLINE@39..40 "\n"
2768"#
2769 );
2770 let root = parsed.root();
2771
2772 let rule = root.rules().next().unwrap();
2773 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2774 assert_eq!(
2775 rule.prerequisites().collect::<Vec<_>>(),
2776 vec!["dependency1", "dependency2"]
2777 );
2778 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2779 }
2780
2781 #[test]
2782 fn test_add_rule() {
2783 let mut makefile = Makefile::new();
2784 let rule = makefile.add_rule("rule");
2785 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2786 assert_eq!(
2787 rule.prerequisites().collect::<Vec<_>>(),
2788 Vec::<String>::new()
2789 );
2790
2791 assert_eq!(makefile.to_string(), "rule:\n");
2792 }
2793
2794 #[test]
2795 fn test_push_command() {
2796 let mut makefile = Makefile::new();
2797 let mut rule = makefile.add_rule("rule");
2798
2799 rule.push_command("command");
2801 rule.push_command("command2");
2802
2803 assert_eq!(
2805 rule.recipes().collect::<Vec<_>>(),
2806 vec!["command", "command2"]
2807 );
2808
2809 rule.push_command("command3");
2811 assert_eq!(
2812 rule.recipes().collect::<Vec<_>>(),
2813 vec!["command", "command2", "command3"]
2814 );
2815
2816 assert_eq!(
2818 makefile.to_string(),
2819 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2820 );
2821
2822 assert_eq!(
2824 rule.to_string(),
2825 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2826 );
2827 }
2828
2829 #[test]
2830 fn test_replace_command() {
2831 let mut makefile = Makefile::new();
2832 let mut rule = makefile.add_rule("rule");
2833
2834 rule.push_command("command");
2836 rule.push_command("command2");
2837
2838 assert_eq!(
2840 rule.recipes().collect::<Vec<_>>(),
2841 vec!["command", "command2"]
2842 );
2843
2844 rule.replace_command(0, "new command");
2846 assert_eq!(
2847 rule.recipes().collect::<Vec<_>>(),
2848 vec!["new command", "command2"]
2849 );
2850
2851 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2853
2854 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2856 }
2857
2858 #[test]
2859 fn test_parse_rule_without_newline() {
2860 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
2861 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2862 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2863 let rule = "rule: dependency".parse::<Rule>().unwrap();
2864 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2865 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
2866 }
2867
2868 #[test]
2869 fn test_parse_makefile_without_newline() {
2870 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
2871 assert_eq!(makefile.rules().count(), 1);
2872 }
2873
2874 #[test]
2875 fn test_from_reader() {
2876 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
2877 assert_eq!(makefile.rules().count(), 1);
2878 }
2879
2880 #[test]
2881 fn test_parse_with_tab_after_last_newline() {
2882 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
2883 assert_eq!(makefile.rules().count(), 1);
2884 }
2885
2886 #[test]
2887 fn test_parse_with_space_after_last_newline() {
2888 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
2889 assert_eq!(makefile.rules().count(), 1);
2890 }
2891
2892 #[test]
2893 fn test_parse_with_comment_after_last_newline() {
2894 let makefile =
2895 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
2896 assert_eq!(makefile.rules().count(), 1);
2897 }
2898
2899 #[test]
2900 fn test_parse_with_variable_rule() {
2901 let makefile =
2902 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
2903 .unwrap();
2904
2905 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2907 assert_eq!(vars.len(), 1);
2908 assert_eq!(vars[0].name(), Some("RULE".to_string()));
2909 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
2910
2911 let rules = makefile.rules().collect::<Vec<_>>();
2913 assert_eq!(rules.len(), 1);
2914 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
2915 assert_eq!(
2916 rules[0].prerequisites().collect::<Vec<_>>(),
2917 vec!["dependency"]
2918 );
2919 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2920 }
2921
2922 #[test]
2923 fn test_parse_with_variable_dependency() {
2924 let makefile =
2925 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
2926
2927 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2929 assert_eq!(vars.len(), 1);
2930 assert_eq!(vars[0].name(), Some("DEP".to_string()));
2931 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
2932
2933 let rules = makefile.rules().collect::<Vec<_>>();
2935 assert_eq!(rules.len(), 1);
2936 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2937 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
2938 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2939 }
2940
2941 #[test]
2942 fn test_parse_with_variable_command() {
2943 let makefile =
2944 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
2945
2946 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2948 assert_eq!(vars.len(), 1);
2949 assert_eq!(vars[0].name(), Some("COM".to_string()));
2950 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
2951
2952 let rules = makefile.rules().collect::<Vec<_>>();
2954 assert_eq!(rules.len(), 1);
2955 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2956 assert_eq!(
2957 rules[0].prerequisites().collect::<Vec<_>>(),
2958 vec!["dependency"]
2959 );
2960 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
2961 }
2962
2963 #[test]
2964 fn test_regular_line_error_reporting() {
2965 let input = "rule target\n\tcommand";
2966
2967 let parsed = parse(input);
2969 let direct_error = &parsed.errors[0];
2970
2971 assert_eq!(direct_error.line, 2);
2973 assert!(
2974 direct_error.message.contains("expected"),
2975 "Error message should contain 'expected': {}",
2976 direct_error.message
2977 );
2978 assert_eq!(direct_error.context, "\tcommand");
2979
2980 let reader_result = Makefile::from_reader(input.as_bytes());
2982 let parse_error = match reader_result {
2983 Ok(_) => panic!("Expected Parse error from from_reader"),
2984 Err(err) => match err {
2985 self::Error::Parse(parse_err) => parse_err,
2986 _ => panic!("Expected Parse error"),
2987 },
2988 };
2989
2990 let error_text = parse_error.to_string();
2992 assert!(error_text.contains("Error at line 2:"));
2993 assert!(error_text.contains("2| \tcommand"));
2994 }
2995
2996 #[test]
2997 fn test_parsing_error_context_with_bad_syntax() {
2998 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
3000
3001 match Makefile::from_reader(input.as_bytes()) {
3003 Ok(makefile) => {
3004 assert_eq!(
3006 makefile.rules().count(),
3007 0,
3008 "Should not have found any rules"
3009 );
3010 }
3011 Err(err) => match err {
3012 self::Error::Parse(error) => {
3013 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
3015 assert!(
3016 !error.errors[0].context.is_empty(),
3017 "Error context should not be empty"
3018 );
3019 }
3020 _ => panic!("Unexpected error type"),
3021 },
3022 };
3023 }
3024
3025 #[test]
3026 fn test_error_message_format() {
3027 let parse_error = ParseError {
3029 errors: vec![ErrorInfo {
3030 message: "test error".to_string(),
3031 line: 42,
3032 context: "some problematic code".to_string(),
3033 }],
3034 };
3035
3036 let error_text = parse_error.to_string();
3037 assert!(error_text.contains("Error at line 42: test error"));
3038 assert!(error_text.contains("42| some problematic code"));
3039 }
3040
3041 #[test]
3042 fn test_line_number_calculation() {
3043 let test_cases = [
3045 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
3049
3050 for (input, expected_line) in test_cases {
3051 match input.parse::<Makefile>() {
3053 Ok(_) => {
3054 continue;
3057 }
3058 Err(err) => {
3059 if let Error::Parse(parse_err) = err {
3060 assert_eq!(
3062 parse_err.errors[0].line, expected_line,
3063 "Line number should match the expected line"
3064 );
3065
3066 if parse_err.errors[0].message.contains("indented") {
3068 assert!(
3069 parse_err.errors[0].context.starts_with('\t'),
3070 "Context for indentation errors should include the tab character"
3071 );
3072 }
3073 } else {
3074 panic!("Expected parse error, got: {:?}", err);
3075 }
3076 }
3077 }
3078 }
3079 }
3080
3081 #[test]
3082 fn test_conditional_features() {
3083 let code = r#"
3085# Set variables based on DEBUG flag
3086ifdef DEBUG
3087 CFLAGS += -g -DDEBUG
3088else
3089 CFLAGS = -O2
3090endif
3091
3092# Define a build rule
3093all: $(OBJS)
3094 $(CC) $(CFLAGS) -o $@ $^
3095"#;
3096
3097 let mut buf = code.as_bytes();
3098 let makefile =
3099 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
3100
3101 assert!(!makefile.code().is_empty(), "Makefile has content");
3104
3105 let rules = makefile.rules().collect::<Vec<_>>();
3107 assert!(!rules.is_empty(), "Should have found rules");
3108
3109 assert!(code.contains("ifdef DEBUG"));
3111 assert!(code.contains("endif"));
3112
3113 let code_with_var = r#"
3115# Define a variable first
3116CC = gcc
3117
3118ifdef DEBUG
3119 CFLAGS += -g -DDEBUG
3120else
3121 CFLAGS = -O2
3122endif
3123
3124all: $(OBJS)
3125 $(CC) $(CFLAGS) -o $@ $^
3126"#;
3127
3128 let mut buf = code_with_var.as_bytes();
3129 let makefile =
3130 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
3131
3132 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3134 assert!(
3135 !vars.is_empty(),
3136 "Should have found at least the CC variable definition"
3137 );
3138 }
3139
3140 #[test]
3141 fn test_include_directive() {
3142 let parsed = parse("include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n");
3143 assert!(parsed.errors.is_empty());
3144 let node = parsed.syntax();
3145 assert!(format!("{:#?}", node).contains("INCLUDE@"));
3146 }
3147
3148 #[test]
3149 fn test_export_variables() {
3150 let parsed = parse("export SHELL := /bin/bash\n");
3151 assert!(parsed.errors.is_empty());
3152 let makefile = parsed.root();
3153 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3154 assert_eq!(vars.len(), 1);
3155 let shell_var = vars
3156 .iter()
3157 .find(|v| v.name() == Some("SHELL".to_string()))
3158 .unwrap();
3159 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
3160 }
3161
3162 #[test]
3163 fn test_variable_scopes() {
3164 let parsed =
3165 parse("SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n");
3166 assert!(parsed.errors.is_empty());
3167 let makefile = parsed.root();
3168 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3169 assert_eq!(vars.len(), 4);
3170 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
3171 assert!(var_names.contains(&"SIMPLE".to_string()));
3172 assert!(var_names.contains(&"IMMEDIATE".to_string()));
3173 assert!(var_names.contains(&"CONDITIONAL".to_string()));
3174 assert!(var_names.contains(&"APPEND".to_string()));
3175 }
3176
3177 #[test]
3178 fn test_pattern_rule_parsing() {
3179 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n");
3180 assert!(parsed.errors.is_empty());
3181 let makefile = parsed.root();
3182 let rules = makefile.rules().collect::<Vec<_>>();
3183 assert_eq!(rules.len(), 1);
3184 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
3185 assert!(rules[0].recipes().next().unwrap().contains("$@"));
3186 }
3187
3188 #[test]
3189 fn test_include_variants() {
3190 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
3192 let parsed = parse(makefile_str);
3193 assert!(parsed.errors.is_empty());
3194
3195 let node = parsed.syntax();
3197 let debug_str = format!("{:#?}", node);
3198
3199 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
3201
3202 let makefile = parsed.root();
3204
3205 let include_count = makefile
3207 .syntax()
3208 .children()
3209 .filter(|child| child.kind() == INCLUDE)
3210 .count();
3211 assert_eq!(include_count, 4);
3212
3213 assert!(makefile
3215 .included_files()
3216 .any(|path| path.contains("$(VAR)")));
3217 }
3218
3219 #[test]
3220 fn test_include_api() {
3221 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
3223 let makefile: Makefile = makefile_str.parse().unwrap();
3224
3225 let includes: Vec<_> = makefile.includes().collect();
3227 assert_eq!(includes.len(), 3);
3228
3229 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
3236 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
3237
3238 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
3240 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
3241 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
3242 }
3243
3244 #[test]
3245 fn test_include_integration() {
3246 let phony_makefile = Makefile::from_reader(
3250 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3251 .as_bytes()
3252 ).unwrap();
3253
3254 assert_eq!(phony_makefile.rules().count(), 2);
3256
3257 let normal_rules_count = phony_makefile
3259 .rules()
3260 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
3261 .count();
3262 assert_eq!(normal_rules_count, 1);
3263
3264 assert_eq!(phony_makefile.includes().count(), 1);
3266 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
3267
3268 let simple_makefile = Makefile::from_reader(
3270 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3271 .as_bytes(),
3272 )
3273 .unwrap();
3274 assert_eq!(simple_makefile.rules().count(), 1);
3275 assert_eq!(simple_makefile.includes().count(), 1);
3276 }
3277
3278 #[test]
3279 fn test_real_conditional_directives() {
3280 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
3282 let mut buf = conditional.as_bytes();
3283 let makefile =
3284 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
3285 let code = makefile.code();
3286 assert!(code.contains("ifdef DEBUG"));
3287 assert!(code.contains("else"));
3288 assert!(code.contains("endif"));
3289
3290 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
3292 let mut buf = nested.as_bytes();
3293 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
3294 let code = makefile.code();
3295 assert!(code.contains("ifdef DEBUG"));
3296 assert!(code.contains("ifdef VERBOSE"));
3297
3298 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
3300 let mut buf = ifeq.as_bytes();
3301 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
3302 let code = makefile.code();
3303 assert!(code.contains("ifeq"));
3304 assert!(code.contains("Windows_NT"));
3305 }
3306
3307 #[test]
3308 fn test_indented_text_outside_rules() {
3309 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
3311 let parsed = parse(help_text);
3312 assert!(parsed.errors.is_empty());
3313
3314 let root = parsed.root();
3316 let rules = root.rules().collect::<Vec<_>>();
3317 assert_eq!(rules.len(), 1);
3318
3319 let help_rule = &rules[0];
3320 let recipes = help_rule.recipes().collect::<Vec<_>>();
3321 assert_eq!(recipes.len(), 2);
3322 assert!(recipes[0].contains("Available targets"));
3323 assert!(recipes[1].contains("help"));
3324 }
3325
3326 #[test]
3327 fn test_comment_handling_in_recipes() {
3328 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
3330
3331 let parsed = parse(recipe_comment);
3333
3334 assert!(
3336 parsed.errors.is_empty(),
3337 "Should parse recipe with comments without errors"
3338 );
3339
3340 let root = parsed.root();
3342 let rules = root.rules().collect::<Vec<_>>();
3343 assert_eq!(rules.len(), 1, "Should find exactly one rule");
3344
3345 let build_rule = &rules[0];
3347 assert_eq!(
3348 build_rule.targets().collect::<Vec<_>>(),
3349 vec!["build"],
3350 "Rule should have 'build' as target"
3351 );
3352
3353 let recipes = build_rule.recipes().collect::<Vec<_>>();
3357 assert_eq!(
3358 recipes.len(),
3359 1,
3360 "Should find exactly one recipe line (comment lines are filtered)"
3361 );
3362 assert!(
3363 recipes[0].contains("gcc -o app"),
3364 "Recipe should be the command line"
3365 );
3366 assert!(
3367 !recipes[0].contains("This is a comment"),
3368 "Comments should not be included in recipe lines"
3369 );
3370 }
3371
3372 #[test]
3373 fn test_multiline_variables() {
3374 let multiline = "SOURCES = main.c \\\n util.c\n";
3376
3377 let parsed = parse(multiline);
3379
3380 let root = parsed.root();
3382 let vars = root.variable_definitions().collect::<Vec<_>>();
3383 assert!(!vars.is_empty(), "Should find at least one variable");
3384
3385 let operators = "CFLAGS := -Wall \\\n -Werror\n";
3389 let parsed_operators = parse(operators);
3390
3391 let root = parsed_operators.root();
3393 let vars = root.variable_definitions().collect::<Vec<_>>();
3394 assert!(
3395 !vars.is_empty(),
3396 "Should find at least one variable with := operator"
3397 );
3398
3399 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
3401 let parsed_append = parse(append);
3402
3403 let root = parsed_append.root();
3405 let vars = root.variable_definitions().collect::<Vec<_>>();
3406 assert!(
3407 !vars.is_empty(),
3408 "Should find at least one variable with += operator"
3409 );
3410 }
3411
3412 #[test]
3413 fn test_whitespace_and_eof_handling() {
3414 let blank_lines = "VAR = value\n\n\n";
3416
3417 let parsed_blank = parse(blank_lines);
3418
3419 let root = parsed_blank.root();
3421 let vars = root.variable_definitions().collect::<Vec<_>>();
3422 assert_eq!(
3423 vars.len(),
3424 1,
3425 "Should find one variable in blank lines test"
3426 );
3427
3428 let trailing_space = "VAR = value \n";
3430
3431 let parsed_space = parse(trailing_space);
3432
3433 let root = parsed_space.root();
3435 let vars = root.variable_definitions().collect::<Vec<_>>();
3436 assert_eq!(
3437 vars.len(),
3438 1,
3439 "Should find one variable in trailing space test"
3440 );
3441
3442 let no_newline = "VAR = value";
3444
3445 let parsed_no_newline = parse(no_newline);
3446
3447 let root = parsed_no_newline.root();
3449 let vars = root.variable_definitions().collect::<Vec<_>>();
3450 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
3451 assert_eq!(
3452 vars[0].name(),
3453 Some("VAR".to_string()),
3454 "Variable name should be VAR"
3455 );
3456 }
3457
3458 #[test]
3459 fn test_complex_variable_references() {
3460 let wildcard = "SOURCES = $(wildcard *.c)\n";
3462 let parsed = parse(wildcard);
3463 assert!(parsed.errors.is_empty());
3464
3465 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3467 let parsed = parse(nested);
3468 assert!(parsed.errors.is_empty());
3469
3470 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3472 let parsed = parse(patsubst);
3473 assert!(parsed.errors.is_empty());
3474 }
3475
3476 #[test]
3477 fn test_complex_variable_references_minimal() {
3478 let wildcard = "SOURCES = $(wildcard *.c)\n";
3480 let parsed = parse(wildcard);
3481 assert!(parsed.errors.is_empty());
3482
3483 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3485 let parsed = parse(nested);
3486 assert!(parsed.errors.is_empty());
3487
3488 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3490 let parsed = parse(patsubst);
3491 assert!(parsed.errors.is_empty());
3492 }
3493
3494 #[test]
3495 fn test_multiline_variable_with_backslash() {
3496 let content = r#"
3497LONG_VAR = This is a long variable \
3498 that continues on the next line \
3499 and even one more line
3500"#;
3501
3502 let mut buf = content.as_bytes();
3504 let makefile =
3505 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
3506
3507 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3509 assert_eq!(
3510 vars.len(),
3511 1,
3512 "Expected 1 variable but found {}",
3513 vars.len()
3514 );
3515 let var_value = vars[0].raw_value();
3516 assert!(var_value.is_some(), "Variable value is None");
3517
3518 let value_str = var_value.unwrap();
3520 assert!(
3521 value_str.contains("long variable"),
3522 "Value doesn't contain expected content"
3523 );
3524 }
3525
3526 #[test]
3527 fn test_multiline_variable_with_mixed_operators() {
3528 let content = r#"
3529PREFIX ?= /usr/local
3530CFLAGS := -Wall -O2 \
3531 -I$(PREFIX)/include \
3532 -DDEBUG
3533"#;
3534 let mut buf = content.as_bytes();
3536 let makefile = Makefile::read_relaxed(&mut buf)
3537 .expect("Failed to parse multiline variable with operators");
3538
3539 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3541 assert!(
3542 vars.len() >= 1,
3543 "Expected at least 1 variable, found {}",
3544 vars.len()
3545 );
3546
3547 let prefix_var = vars
3549 .iter()
3550 .find(|v| v.name().unwrap_or_default() == "PREFIX");
3551 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
3552 assert!(
3553 prefix_var.unwrap().raw_value().is_some(),
3554 "PREFIX variable has no value"
3555 );
3556
3557 let cflags_var = vars
3559 .iter()
3560 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
3561 assert!(
3562 cflags_var.is_some(),
3563 "Expected to find CFLAGS variable (or part of it)"
3564 );
3565 }
3566
3567 #[test]
3568 fn test_indented_help_text() {
3569 let content = r#"
3570.PHONY: help
3571help:
3572 @echo "Available targets:"
3573 @echo " build - Build the project"
3574 @echo " test - Run tests"
3575 @echo " clean - Remove build artifacts"
3576"#;
3577 let mut buf = content.as_bytes();
3579 let makefile =
3580 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
3581
3582 let rules = makefile.rules().collect::<Vec<_>>();
3584 assert!(!rules.is_empty(), "Expected at least one rule");
3585
3586 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
3588 assert!(help_rule.is_some(), "Expected to find help rule");
3589
3590 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
3592 assert!(
3593 !recipes.is_empty(),
3594 "Expected at least one recipe line in help rule"
3595 );
3596 assert!(
3597 recipes.iter().any(|r| r.contains("Available targets")),
3598 "Expected to find 'Available targets' in recipes"
3599 );
3600 }
3601
3602 #[test]
3603 fn test_indented_lines_in_conditionals() {
3604 let content = r#"
3605ifdef DEBUG
3606 CFLAGS += -g -DDEBUG
3607 # This is a comment inside conditional
3608 ifdef VERBOSE
3609 CFLAGS += -v
3610 endif
3611endif
3612"#;
3613 let mut buf = content.as_bytes();
3615 let makefile = Makefile::read_relaxed(&mut buf)
3616 .expect("Failed to parse indented lines in conditionals");
3617
3618 let code = makefile.code();
3620 assert!(code.contains("ifdef DEBUG"));
3621 assert!(code.contains("ifdef VERBOSE"));
3622 assert!(code.contains("endif"));
3623 }
3624
3625 #[test]
3626 fn test_recipe_with_colon() {
3627 let content = r#"
3628build:
3629 @echo "Building at: $(shell date)"
3630 gcc -o program main.c
3631"#;
3632 let parsed = parse(content);
3633 assert!(
3634 parsed.errors.is_empty(),
3635 "Failed to parse recipe with colon: {:?}",
3636 parsed.errors
3637 );
3638 }
3639
3640 #[test]
3641 #[ignore]
3642 fn test_double_colon_rules() {
3643 let content = r#"
3646%.o :: %.c
3647 $(CC) -c $< -o $@
3648
3649# Double colon allows multiple rules for same target
3650all:: prerequisite1
3651 @echo "First rule for all"
3652
3653all:: prerequisite2
3654 @echo "Second rule for all"
3655"#;
3656 let mut buf = content.as_bytes();
3657 let makefile =
3658 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
3659
3660 let rules = makefile.rules().collect::<Vec<_>>();
3662 assert!(!rules.is_empty(), "Expected at least one rule");
3663
3664 let all_rules = rules
3666 .iter()
3667 .filter(|r| r.targets().any(|t| t.contains("all")));
3668 assert!(
3669 all_rules.count() > 0,
3670 "Expected to find at least one rule containing 'all'"
3671 );
3672 }
3673
3674 #[test]
3675 fn test_elif_directive() {
3676 let content = r#"
3677ifeq ($(OS),Windows_NT)
3678 TARGET = windows
3679elif ifeq ($(OS),Darwin)
3680 TARGET = macos
3681elif ifeq ($(OS),Linux)
3682 TARGET = linux
3683else
3684 TARGET = unknown
3685endif
3686"#;
3687 let mut buf = content.as_bytes();
3689 let _makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse elif directive");
3690
3691 }
3694
3695 #[test]
3696 fn test_ambiguous_assignment_vs_rule() {
3697 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
3699
3700 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
3701 let makefile =
3702 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
3703
3704 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3705 let rules = makefile.rules().collect::<Vec<_>>();
3706
3707 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
3708 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
3709
3710 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
3711
3712 const SIMPLE_RULE: &str = "target: dependency\n";
3714
3715 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
3716 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
3717
3718 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3719 let rules = makefile.rules().collect::<Vec<_>>();
3720
3721 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
3722 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
3723
3724 let rule = &rules[0];
3725 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
3726 }
3727
3728 #[test]
3729 fn test_nested_conditionals() {
3730 let content = r#"
3731ifdef RELEASE
3732 CFLAGS += -O3
3733 ifndef DEBUG
3734 ifneq ($(ARCH),arm)
3735 CFLAGS += -march=native
3736 else
3737 CFLAGS += -mcpu=cortex-a72
3738 endif
3739 endif
3740endif
3741"#;
3742 let mut buf = content.as_bytes();
3744 let makefile =
3745 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
3746
3747 let code = makefile.code();
3749 assert!(code.contains("ifdef RELEASE"));
3750 assert!(code.contains("ifndef DEBUG"));
3751 assert!(code.contains("ifneq"));
3752 }
3753
3754 #[test]
3755 fn test_space_indented_recipes() {
3756 let content = r#"
3759build:
3760 @echo "Building with spaces instead of tabs"
3761 gcc -o program main.c
3762"#;
3763 let mut buf = content.as_bytes();
3765 let makefile =
3766 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
3767
3768 let rules = makefile.rules().collect::<Vec<_>>();
3770 assert!(!rules.is_empty(), "Expected at least one rule");
3771
3772 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
3774 assert!(build_rule.is_some(), "Expected to find build rule");
3775 }
3776
3777 #[test]
3778 fn test_complex_variable_functions() {
3779 let content = r#"
3780FILES := $(shell find . -name "*.c")
3781OBJS := $(patsubst %.c,%.o,$(FILES))
3782NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
3783HEADERS := ${wildcard *.h}
3784"#;
3785 let parsed = parse(content);
3786 assert!(
3787 parsed.errors.is_empty(),
3788 "Failed to parse complex variable functions: {:?}",
3789 parsed.errors
3790 );
3791 }
3792
3793 #[test]
3794 fn test_nested_variable_expansions() {
3795 let content = r#"
3796VERSION = 1.0
3797PACKAGE = myapp
3798TARBALL = $(PACKAGE)-$(VERSION).tar.gz
3799INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
3800"#;
3801 let parsed = parse(content);
3802 assert!(
3803 parsed.errors.is_empty(),
3804 "Failed to parse nested variable expansions: {:?}",
3805 parsed.errors
3806 );
3807 }
3808
3809 #[test]
3810 fn test_special_directives() {
3811 let content = r#"
3812# Special makefile directives
3813.PHONY: all clean
3814.SUFFIXES: .c .o
3815.DEFAULT: all
3816
3817# Variable definition and export directive
3818export PATH := /usr/bin:/bin
3819"#;
3820 let mut buf = content.as_bytes();
3822 let makefile =
3823 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
3824
3825 let rules = makefile.rules().collect::<Vec<_>>();
3827
3828 let phony_rule = rules
3830 .iter()
3831 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
3832 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
3833
3834 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3836 assert!(!vars.is_empty(), "Expected to find at least one variable");
3837 }
3838
3839 #[test]
3842 fn test_comprehensive_real_world_makefile() {
3843 let content = r#"
3845# Basic variable assignment
3846VERSION = 1.0.0
3847
3848# Phony target
3849.PHONY: all clean
3850
3851# Simple rule
3852all:
3853 echo "Building version $(VERSION)"
3854
3855# Another rule with dependencies
3856clean:
3857 rm -f *.o
3858"#;
3859
3860 let parsed = parse(content);
3862
3863 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
3865
3866 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
3868 assert!(!variables.is_empty(), "Expected at least one variable");
3869 assert_eq!(
3870 variables[0].name(),
3871 Some("VERSION".to_string()),
3872 "Expected VERSION variable"
3873 );
3874
3875 let rules = parsed.root().rules().collect::<Vec<_>>();
3877 assert!(!rules.is_empty(), "Expected at least one rule");
3878
3879 let rule_targets: Vec<String> = rules
3881 .iter()
3882 .flat_map(|r| r.targets().collect::<Vec<_>>())
3883 .collect();
3884 assert!(
3885 rule_targets.contains(&".PHONY".to_string()),
3886 "Expected .PHONY rule"
3887 );
3888 assert!(
3889 rule_targets.contains(&"all".to_string()),
3890 "Expected 'all' rule"
3891 );
3892 assert!(
3893 rule_targets.contains(&"clean".to_string()),
3894 "Expected 'clean' rule"
3895 );
3896 }
3897
3898 #[test]
3899 fn test_indented_help_text_outside_rules() {
3900 let content = r#"
3902# Targets with help text
3903help:
3904 @echo "Available targets:"
3905 @echo " build build the project"
3906 @echo " test run tests"
3907 @echo " clean clean build artifacts"
3908
3909# Another target
3910clean:
3911 rm -rf build/
3912"#;
3913
3914 let parsed = parse(content);
3916
3917 assert!(
3919 parsed.errors.is_empty(),
3920 "Failed to parse indented help text"
3921 );
3922
3923 let rules = parsed.root().rules().collect::<Vec<_>>();
3925 assert_eq!(rules.len(), 2, "Expected to find two rules");
3926
3927 let help_rule = rules
3929 .iter()
3930 .find(|r| r.targets().any(|t| t == "help"))
3931 .expect("Expected to find help rule");
3932
3933 let clean_rule = rules
3934 .iter()
3935 .find(|r| r.targets().any(|t| t == "clean"))
3936 .expect("Expected to find clean rule");
3937
3938 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
3940 assert!(
3941 !help_recipes.is_empty(),
3942 "Help rule should have recipe lines"
3943 );
3944 assert!(
3945 help_recipes
3946 .iter()
3947 .any(|line| line.contains("Available targets")),
3948 "Help recipes should include 'Available targets' line"
3949 );
3950
3951 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
3953 assert!(
3954 !clean_recipes.is_empty(),
3955 "Clean rule should have recipe lines"
3956 );
3957 assert!(
3958 clean_recipes.iter().any(|line| line.contains("rm -rf")),
3959 "Clean recipes should include 'rm -rf' command"
3960 );
3961 }
3962
3963 #[test]
3964 fn test_makefile1_phony_pattern() {
3965 let content = "#line 2145\n.PHONY: $(PHONY)\n";
3967
3968 let result = parse(content);
3970
3971 assert!(
3973 result.errors.is_empty(),
3974 "Failed to parse .PHONY: $(PHONY) pattern"
3975 );
3976
3977 let rules = result.root().rules().collect::<Vec<_>>();
3979 assert_eq!(rules.len(), 1, "Expected 1 rule");
3980 assert_eq!(
3981 rules[0].targets().next().unwrap(),
3982 ".PHONY",
3983 "Expected .PHONY rule"
3984 );
3985
3986 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
3988 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
3989 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
3990 }
3991
3992 #[test]
3993 fn test_skip_until_newline_behavior() {
3994 let input = "text without newline";
3996 let parsed = parse(input);
3997 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3999
4000 let input_with_newline = "text\nafter newline";
4001 let parsed2 = parse(input_with_newline);
4002 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
4003 }
4004
4005 #[test]
4006 fn test_error_with_indent_token() {
4007 let input = "\tinvalid indented line";
4009 let parsed = parse(input);
4010 assert!(!parsed.errors.is_empty());
4012
4013 let error_msg = &parsed.errors[0].message;
4014 assert!(error_msg.contains("indented") || error_msg.contains("part of a rule"));
4015 }
4016
4017 #[test]
4018 fn test_conditional_token_handling() {
4019 let input = r#"
4021ifndef VAR
4022 CFLAGS = -DTEST
4023endif
4024"#;
4025 let parsed = parse(input);
4026 let makefile = parsed.root();
4028 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
4029 let nested = r#"
4033ifdef DEBUG
4034 ifndef RELEASE
4035 CFLAGS = -g
4036 endif
4037endif
4038"#;
4039 let parsed_nested = parse(nested);
4040 let _makefile = parsed_nested.root();
4042 }
4043
4044 #[test]
4045 fn test_include_vs_conditional_logic() {
4046 let input = r#"
4048include file.mk
4049ifdef VAR
4050 VALUE = 1
4051endif
4052"#;
4053 let parsed = parse(input);
4054 let makefile = parsed.root();
4056 let includes = makefile.includes().collect::<Vec<_>>();
4057 assert!(includes.len() >= 1 || parsed.errors.len() > 0);
4059
4060 let optional_include = r#"
4062-include optional.mk
4063ifndef VAR
4064 VALUE = default
4065endif
4066"#;
4067 let parsed2 = parse(optional_include);
4068 let _makefile = parsed2.root();
4070 }
4071
4072 #[test]
4073 fn test_balanced_parens_counting() {
4074 let input = r#"
4076VAR = $(call func,$(nested,arg),extra)
4077COMPLEX = $(if $(condition),$(then_val),$(else_val))
4078"#;
4079 let parsed = parse(input);
4080 assert!(parsed.errors.is_empty());
4081
4082 let makefile = parsed.root();
4083 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4084 assert_eq!(vars.len(), 2);
4085 }
4086
4087 #[test]
4088 fn test_documentation_lookahead() {
4089 let input = r#"
4091# Documentation comment
4092help:
4093 @echo "Usage instructions"
4094 @echo "More help text"
4095"#;
4096 let parsed = parse(input);
4097 assert!(parsed.errors.is_empty());
4098
4099 let makefile = parsed.root();
4100 let rules = makefile.rules().collect::<Vec<_>>();
4101 assert_eq!(rules.len(), 1);
4102 assert_eq!(rules[0].targets().next().unwrap(), "help");
4103 }
4104
4105 #[test]
4106 fn test_edge_case_empty_input() {
4107 let parsed = parse("");
4109 assert!(parsed.errors.is_empty());
4110
4111 let parsed2 = parse(" \n \n");
4113 let _makefile = parsed2.root();
4116 }
4117
4118 #[test]
4119 fn test_malformed_conditional_recovery() {
4120 let input = r#"
4122ifdef
4123 # Missing condition variable
4124endif
4125"#;
4126 let parsed = parse(input);
4127 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
4130 }
4131
4132 #[test]
4133 fn test_replace_rule() {
4134 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4135 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4136
4137 makefile.replace_rule(0, new_rule).unwrap();
4138
4139 let targets: Vec<_> = makefile
4140 .rules()
4141 .flat_map(|r| r.targets().collect::<Vec<_>>())
4142 .collect();
4143 assert_eq!(targets, vec!["new_rule", "rule2"]);
4144
4145 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
4146 assert_eq!(recipes, vec!["new_command"]);
4147 }
4148
4149 #[test]
4150 fn test_replace_rule_out_of_bounds() {
4151 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4152 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4153
4154 let result = makefile.replace_rule(5, new_rule);
4155 assert!(result.is_err());
4156 }
4157
4158 #[test]
4159 fn test_remove_rule() {
4160 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
4161 .parse()
4162 .unwrap();
4163
4164 let removed = makefile.remove_rule(1).unwrap();
4165 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
4166
4167 let remaining_targets: Vec<_> = makefile
4168 .rules()
4169 .flat_map(|r| r.targets().collect::<Vec<_>>())
4170 .collect();
4171 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
4172 assert_eq!(makefile.rules().count(), 2);
4173 }
4174
4175 #[test]
4176 fn test_remove_rule_out_of_bounds() {
4177 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4178
4179 let result = makefile.remove_rule(5);
4180 assert!(result.is_err());
4181 }
4182
4183 #[test]
4184 fn test_insert_rule() {
4185 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4186 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
4187
4188 makefile.insert_rule(1, new_rule).unwrap();
4189
4190 let targets: Vec<_> = makefile
4191 .rules()
4192 .flat_map(|r| r.targets().collect::<Vec<_>>())
4193 .collect();
4194 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
4195 assert_eq!(makefile.rules().count(), 3);
4196 }
4197
4198 #[test]
4199 fn test_insert_rule_at_end() {
4200 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4201 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
4202
4203 makefile.insert_rule(1, new_rule).unwrap();
4204
4205 let targets: Vec<_> = makefile
4206 .rules()
4207 .flat_map(|r| r.targets().collect::<Vec<_>>())
4208 .collect();
4209 assert_eq!(targets, vec!["rule1", "end_rule"]);
4210 }
4211
4212 #[test]
4213 fn test_insert_rule_out_of_bounds() {
4214 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4215 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4216
4217 let result = makefile.insert_rule(5, new_rule);
4218 assert!(result.is_err());
4219 }
4220
4221 #[test]
4222 fn test_remove_command() {
4223 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4224 .parse()
4225 .unwrap();
4226
4227 rule.remove_command(1);
4228 let recipes: Vec<_> = rule.recipes().collect();
4229 assert_eq!(recipes, vec!["command1", "command3"]);
4230 assert_eq!(rule.recipe_count(), 2);
4231 }
4232
4233 #[test]
4234 fn test_remove_command_out_of_bounds() {
4235 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4236
4237 let result = rule.remove_command(5);
4238 assert!(!result);
4239 }
4240
4241 #[test]
4242 fn test_insert_command() {
4243 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
4244
4245 rule.insert_command(1, "command2");
4246 let recipes: Vec<_> = rule.recipes().collect();
4247 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
4248 }
4249
4250 #[test]
4251 fn test_insert_command_at_end() {
4252 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4253
4254 rule.insert_command(1, "command2");
4255 let recipes: Vec<_> = rule.recipes().collect();
4256 assert_eq!(recipes, vec!["command1", "command2"]);
4257 }
4258
4259 #[test]
4260 fn test_insert_command_in_empty_rule() {
4261 let mut rule: Rule = "rule:\n".parse().unwrap();
4262
4263 rule.insert_command(0, "new_command");
4264 let recipes: Vec<_> = rule.recipes().collect();
4265 assert_eq!(recipes, vec!["new_command"]);
4266 }
4267
4268 #[test]
4269 fn test_recipe_count() {
4270 let rule1: Rule = "rule:\n".parse().unwrap();
4271 assert_eq!(rule1.recipe_count(), 0);
4272
4273 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
4274 assert_eq!(rule2.recipe_count(), 2);
4275 }
4276
4277 #[test]
4278 fn test_clear_commands() {
4279 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4280 .parse()
4281 .unwrap();
4282
4283 rule.clear_commands();
4284 assert_eq!(rule.recipe_count(), 0);
4285
4286 let recipes: Vec<_> = rule.recipes().collect();
4287 assert_eq!(recipes, Vec::<String>::new());
4288
4289 let targets: Vec<_> = rule.targets().collect();
4291 assert_eq!(targets, vec!["rule"]);
4292 }
4293
4294 #[test]
4295 fn test_clear_commands_empty_rule() {
4296 let mut rule: Rule = "rule:\n".parse().unwrap();
4297
4298 rule.clear_commands();
4299 assert_eq!(rule.recipe_count(), 0);
4300
4301 let targets: Vec<_> = rule.targets().collect();
4302 assert_eq!(targets, vec!["rule"]);
4303 }
4304
4305 #[test]
4306 fn test_rule_manipulation_preserves_structure() {
4307 let input = r#"# Comment
4309VAR = value
4310
4311rule1:
4312 command1
4313
4314# Another comment
4315rule2:
4316 command2
4317
4318VAR2 = value2
4319"#;
4320
4321 let mut makefile: Makefile = input.parse().unwrap();
4322 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4323
4324 makefile.insert_rule(1, new_rule).unwrap();
4326
4327 let targets: Vec<_> = makefile
4329 .rules()
4330 .flat_map(|r| r.targets().collect::<Vec<_>>())
4331 .collect();
4332 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
4333
4334 let vars: Vec<_> = makefile.variable_definitions().collect();
4336 assert_eq!(vars.len(), 2);
4337
4338 let output = makefile.code();
4340 assert!(output.contains("# Comment"));
4341 assert!(output.contains("VAR = value"));
4342 assert!(output.contains("# Another comment"));
4343 assert!(output.contains("VAR2 = value2"));
4344 }
4345
4346 #[test]
4347 fn test_replace_rule_with_multiple_targets() {
4348 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
4349 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
4350
4351 makefile.replace_rule(0, new_rule).unwrap();
4352
4353 let targets: Vec<_> = makefile
4354 .rules()
4355 .flat_map(|r| r.targets().collect::<Vec<_>>())
4356 .collect();
4357 assert_eq!(targets, vec!["new_target"]);
4358 }
4359
4360 #[test]
4361 fn test_empty_makefile_operations() {
4362 let mut makefile = Makefile::new();
4363
4364 assert!(makefile
4366 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
4367 .is_err());
4368 assert!(makefile.remove_rule(0).is_err());
4369
4370 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
4372 makefile.insert_rule(0, new_rule).unwrap();
4373 assert_eq!(makefile.rules().count(), 1);
4374 }
4375
4376 #[test]
4377 fn test_command_operations_preserve_indentation() {
4378 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
4379 .parse()
4380 .unwrap();
4381
4382 rule.insert_command(1, "middle_command");
4383 let recipes: Vec<_> = rule.recipes().collect();
4384 assert_eq!(
4385 recipes,
4386 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
4387 );
4388 }
4389
4390 #[test]
4391 fn test_rule_operations_with_variables_and_includes() {
4392 let input = r#"VAR1 = value1
4393include common.mk
4394
4395rule1:
4396 command1
4397
4398VAR2 = value2
4399include other.mk
4400
4401rule2:
4402 command2
4403"#;
4404
4405 let mut makefile: Makefile = input.parse().unwrap();
4406
4407 makefile.remove_rule(0).unwrap();
4409
4410 let output = makefile.code();
4412 assert!(output.contains("VAR1 = value1"));
4413 assert!(output.contains("include common.mk"));
4414 assert!(output.contains("VAR2 = value2"));
4415 assert!(output.contains("include other.mk"));
4416
4417 assert_eq!(makefile.rules().count(), 1);
4419 let remaining_targets: Vec<_> = makefile
4420 .rules()
4421 .flat_map(|r| r.targets().collect::<Vec<_>>())
4422 .collect();
4423 assert_eq!(remaining_targets, vec!["rule2"]);
4424 }
4425
4426 #[test]
4427 fn test_command_manipulation_edge_cases() {
4428 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
4430 assert_eq!(empty_rule.recipe_count(), 0);
4431
4432 empty_rule.insert_command(0, "first_command");
4433 assert_eq!(empty_rule.recipe_count(), 1);
4434
4435 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
4437 empty_rule2.clear_commands();
4438 assert_eq!(empty_rule2.recipe_count(), 0);
4439 }
4440
4441 #[test]
4442 fn test_archive_member_parsing() {
4443 let input = "libfoo.a(bar.o): bar.c\n\tgcc -c bar.c -o bar.o\n\tar r libfoo.a bar.o\n";
4445 let parsed = parse(input);
4446 assert!(
4447 parsed.errors.is_empty(),
4448 "Should parse archive member without errors"
4449 );
4450
4451 let makefile = parsed.root();
4452 let rules: Vec<_> = makefile.rules().collect();
4453 assert_eq!(rules.len(), 1);
4454
4455 let target_text = rules[0].targets().next().unwrap();
4457 assert_eq!(target_text, "libfoo.a(bar.o)");
4458 }
4459
4460 #[test]
4461 fn test_archive_member_multiple_members() {
4462 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";
4464 let parsed = parse(input);
4465 assert!(
4466 parsed.errors.is_empty(),
4467 "Should parse multiple archive members"
4468 );
4469
4470 let makefile = parsed.root();
4471 let rules: Vec<_> = makefile.rules().collect();
4472 assert_eq!(rules.len(), 1);
4473 }
4474
4475 #[test]
4476 fn test_archive_member_in_dependencies() {
4477 let input =
4479 "program: main.o libfoo.a(bar.o) libfoo.a(baz.o)\n\tgcc -o program main.o libfoo.a\n";
4480 let parsed = parse(input);
4481 assert!(
4482 parsed.errors.is_empty(),
4483 "Should parse archive members in dependencies"
4484 );
4485
4486 let makefile = parsed.root();
4487 let rules: Vec<_> = makefile.rules().collect();
4488 assert_eq!(rules.len(), 1);
4489 }
4490
4491 #[test]
4492 fn test_archive_member_with_variables() {
4493 let input = "$(LIB)($(OBJ)): $(SRC)\n\t$(CC) -c $(SRC)\n\t$(AR) r $(LIB) $(OBJ)\n";
4495 let parsed = parse(input);
4496 assert!(
4498 parsed.errors.is_empty(),
4499 "Should parse archive members with variables"
4500 );
4501 }
4502
4503 #[test]
4504 fn test_archive_member_ast_access() {
4505 let input = "libtest.a(foo.o bar.o): foo.c bar.c\n\tgcc -c foo.c bar.c\n";
4507 let parsed = parse(input);
4508 let makefile = parsed.root();
4509
4510 let archive_member_count = makefile
4512 .syntax()
4513 .descendants()
4514 .filter(|n| n.kind() == ARCHIVE_MEMBERS)
4515 .count();
4516
4517 assert!(
4518 archive_member_count > 0,
4519 "Should find ARCHIVE_MEMBERS nodes in AST"
4520 );
4521 }
4522
4523 #[test]
4524 fn test_large_makefile_performance() {
4525 let mut makefile = Makefile::new();
4527
4528 for i in 0..100 {
4530 let rule_name = format!("rule{}", i);
4531 let _rule = makefile
4532 .add_rule(&rule_name)
4533 .push_command(&format!("command{}", i));
4534 }
4535
4536 assert_eq!(makefile.rules().count(), 100);
4537
4538 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
4540 makefile.replace_rule(50, new_rule).unwrap();
4541
4542 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
4544 assert_eq!(rule_50_targets, vec!["middle_rule"]);
4545
4546 assert_eq!(makefile.rules().count(), 100); }
4548
4549 #[test]
4550 fn test_complex_recipe_manipulation() {
4551 let mut complex_rule: Rule = r#"complex:
4552 @echo "Starting build"
4553 $(CC) $(CFLAGS) -o $@ $<
4554 @echo "Build complete"
4555 chmod +x $@
4556"#
4557 .parse()
4558 .unwrap();
4559
4560 assert_eq!(complex_rule.recipe_count(), 4);
4561
4562 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
4567 assert_eq!(final_recipes.len(), 2);
4568 assert!(final_recipes[0].contains("$(CC)"));
4569 assert!(final_recipes[1].contains("chmod"));
4570 }
4571
4572 #[test]
4573 fn test_variable_definition_remove() {
4574 let makefile: Makefile = r#"VAR1 = value1
4575VAR2 = value2
4576VAR3 = value3
4577"#
4578 .parse()
4579 .unwrap();
4580
4581 assert_eq!(makefile.variable_definitions().count(), 3);
4583
4584 let mut var2 = makefile
4586 .variable_definitions()
4587 .nth(1)
4588 .expect("Should have second variable");
4589 assert_eq!(var2.name(), Some("VAR2".to_string()));
4590 var2.remove();
4591
4592 assert_eq!(makefile.variable_definitions().count(), 2);
4594 let var_names: Vec<_> = makefile
4595 .variable_definitions()
4596 .filter_map(|v| v.name())
4597 .collect();
4598 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
4599 }
4600
4601 #[test]
4602 fn test_variable_definition_set_value() {
4603 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
4604
4605 let mut var = makefile
4606 .variable_definitions()
4607 .next()
4608 .expect("Should have variable");
4609 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4610
4611 var.set_value("new_value");
4613
4614 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4616 assert!(makefile.code().contains("VAR = new_value"));
4617 }
4618
4619 #[test]
4620 fn test_variable_definition_set_value_preserves_format() {
4621 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
4622
4623 let mut var = makefile
4624 .variable_definitions()
4625 .next()
4626 .expect("Should have variable");
4627 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4628
4629 var.set_value("new_value");
4631
4632 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4634 let code = makefile.code();
4635 assert!(code.contains("export"), "Should preserve export prefix");
4636 assert!(code.contains(":="), "Should preserve := operator");
4637 assert!(code.contains("new_value"), "Should have new value");
4638 }
4639
4640 #[test]
4641 fn test_makefile_find_variable() {
4642 let makefile: Makefile = r#"VAR1 = value1
4643VAR2 = value2
4644VAR3 = value3
4645"#
4646 .parse()
4647 .unwrap();
4648
4649 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4651 assert_eq!(vars.len(), 1);
4652 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4653 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4654
4655 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
4657 }
4658
4659 #[test]
4660 fn test_makefile_find_variable_with_export() {
4661 let makefile: Makefile = r#"VAR1 = value1
4662export VAR2 := value2
4663VAR3 = value3
4664"#
4665 .parse()
4666 .unwrap();
4667
4668 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4670 assert_eq!(vars.len(), 1);
4671 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4672 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4673 }
4674
4675 #[test]
4676 fn test_variable_definition_is_export() {
4677 let makefile: Makefile = r#"VAR1 = value1
4678export VAR2 := value2
4679export VAR3 = value3
4680VAR4 := value4
4681"#
4682 .parse()
4683 .unwrap();
4684
4685 let vars: Vec<_> = makefile.variable_definitions().collect();
4686 assert_eq!(vars.len(), 4);
4687
4688 assert_eq!(vars[0].is_export(), false);
4689 assert_eq!(vars[1].is_export(), true);
4690 assert_eq!(vars[2].is_export(), true);
4691 assert_eq!(vars[3].is_export(), false);
4692 }
4693
4694 #[test]
4695 fn test_makefile_find_variable_multiple() {
4696 let makefile: Makefile = r#"VAR1 = value1
4697VAR1 = value2
4698VAR2 = other
4699VAR1 = value3
4700"#
4701 .parse()
4702 .unwrap();
4703
4704 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
4706 assert_eq!(vars.len(), 3);
4707 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
4708 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
4709 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
4710
4711 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
4713 assert_eq!(var2s.len(), 1);
4714 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
4715 }
4716
4717 #[test]
4718 fn test_variable_remove_and_find() {
4719 let makefile: Makefile = r#"VAR1 = value1
4720VAR2 = value2
4721VAR3 = value3
4722"#
4723 .parse()
4724 .unwrap();
4725
4726 let mut var2 = makefile
4728 .find_variable("VAR2")
4729 .next()
4730 .expect("Should find VAR2");
4731 var2.remove();
4732
4733 assert_eq!(makefile.find_variable("VAR2").count(), 0);
4735
4736 assert_eq!(makefile.find_variable("VAR1").count(), 1);
4738 assert_eq!(makefile.find_variable("VAR3").count(), 1);
4739 }
4740
4741 #[test]
4742 fn test_variable_remove_with_comment() {
4743 let makefile: Makefile = r#"VAR1 = value1
4744# This is a comment about VAR2
4745VAR2 = value2
4746VAR3 = value3
4747"#
4748 .parse()
4749 .unwrap();
4750
4751 let mut var2 = makefile
4753 .variable_definitions()
4754 .nth(1)
4755 .expect("Should have second variable");
4756 assert_eq!(var2.name(), Some("VAR2".to_string()));
4757 var2.remove();
4758
4759 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4761 }
4762
4763 #[test]
4764 fn test_variable_remove_with_multiple_comments() {
4765 let makefile: Makefile = r#"VAR1 = value1
4766# Comment line 1
4767# Comment line 2
4768# Comment line 3
4769VAR2 = value2
4770VAR3 = value3
4771"#
4772 .parse()
4773 .unwrap();
4774
4775 let mut var2 = makefile
4777 .variable_definitions()
4778 .nth(1)
4779 .expect("Should have second variable");
4780 var2.remove();
4781
4782 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4784 }
4785
4786 #[test]
4787 fn test_variable_remove_with_empty_line() {
4788 let makefile: Makefile = r#"VAR1 = value1
4789
4790# Comment about VAR2
4791VAR2 = value2
4792VAR3 = value3
4793"#
4794 .parse()
4795 .unwrap();
4796
4797 let mut var2 = makefile
4799 .variable_definitions()
4800 .nth(1)
4801 .expect("Should have second variable");
4802 var2.remove();
4803
4804 assert_eq!(makefile.code(), "VAR1 = value1\nVAR3 = value3\n");
4807 }
4808
4809 #[test]
4810 fn test_variable_remove_with_multiple_empty_lines() {
4811 let makefile: Makefile = r#"VAR1 = value1
4812
4813
4814# Comment about VAR2
4815VAR2 = value2
4816VAR3 = value3
4817"#
4818 .parse()
4819 .unwrap();
4820
4821 let mut var2 = makefile
4823 .variable_definitions()
4824 .nth(1)
4825 .expect("Should have second variable");
4826 var2.remove();
4827
4828 assert_eq!(makefile.code(), "VAR1 = value1\n\nVAR3 = value3\n");
4831 }
4832
4833 #[test]
4834 fn test_rule_remove_with_comment() {
4835 let makefile: Makefile = r#"rule1:
4836 command1
4837
4838# Comment about rule2
4839rule2:
4840 command2
4841rule3:
4842 command3
4843"#
4844 .parse()
4845 .unwrap();
4846
4847 let rule2 = makefile.rules().nth(1).expect("Should have second rule");
4849 rule2.remove().unwrap();
4850
4851 assert_eq!(
4854 makefile.code(),
4855 "rule1:\n\tcommand1\n\nrule3:\n\tcommand3\n"
4856 );
4857 }
4858
4859 #[test]
4860 fn test_variable_remove_preserves_shebang() {
4861 let makefile: Makefile = r#"#!/usr/bin/make -f
4862# This is a regular comment
4863VAR1 = value1
4864VAR2 = value2
4865"#
4866 .parse()
4867 .unwrap();
4868
4869 let mut var1 = makefile.variable_definitions().next().unwrap();
4871 var1.remove();
4872
4873 let code = makefile.code();
4875 assert!(code.starts_with("#!/usr/bin/make -f"));
4876 assert!(!code.contains("regular comment"));
4877 assert!(!code.contains("VAR1"));
4878 assert!(code.contains("VAR2"));
4879 }
4880
4881 #[test]
4882 fn test_variable_remove_preserves_subsequent_comments() {
4883 let makefile: Makefile = r#"VAR1 = value1
4884# Comment about VAR2
4885VAR2 = value2
4886
4887# Comment about VAR3
4888VAR3 = value3
4889"#
4890 .parse()
4891 .unwrap();
4892
4893 let mut var2 = makefile
4895 .variable_definitions()
4896 .nth(1)
4897 .expect("Should have second variable");
4898 var2.remove();
4899
4900 let code = makefile.code();
4902 assert_eq!(
4903 code,
4904 "VAR1 = value1\n\n# Comment about VAR3\nVAR3 = value3\n"
4905 );
4906 }
4907
4908 #[test]
4909 fn test_variable_remove_after_shebang_preserves_empty_line() {
4910 let makefile: Makefile = r#"#!/usr/bin/make -f
4911export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
4912
4913%:
4914 dh $@
4915"#
4916 .parse()
4917 .unwrap();
4918
4919 let mut var = makefile.variable_definitions().next().unwrap();
4921 var.remove();
4922
4923 assert_eq!(makefile.code(), "#!/usr/bin/make -f\n\n%:\n\tdh $@\n");
4925 }
4926
4927 #[test]
4928 fn test_rule_add_prerequisite() {
4929 let mut rule: Rule = "target: dep1\n".parse().unwrap();
4930 rule.add_prerequisite("dep2").unwrap();
4931 assert_eq!(
4932 rule.prerequisites().collect::<Vec<_>>(),
4933 vec!["dep1", "dep2"]
4934 );
4935 }
4936
4937 #[test]
4938 fn test_rule_remove_prerequisite() {
4939 let mut rule: Rule = "target: dep1 dep2 dep3\n".parse().unwrap();
4940 assert!(rule.remove_prerequisite("dep2").unwrap());
4941 assert_eq!(
4942 rule.prerequisites().collect::<Vec<_>>(),
4943 vec!["dep1", "dep3"]
4944 );
4945 assert!(!rule.remove_prerequisite("nonexistent").unwrap());
4946 }
4947
4948 #[test]
4949 fn test_rule_set_prerequisites() {
4950 let mut rule: Rule = "target: old_dep\n".parse().unwrap();
4951 rule.set_prerequisites(vec!["new_dep1", "new_dep2"])
4952 .unwrap();
4953 assert_eq!(
4954 rule.prerequisites().collect::<Vec<_>>(),
4955 vec!["new_dep1", "new_dep2"]
4956 );
4957 }
4958
4959 #[test]
4960 fn test_rule_set_prerequisites_empty() {
4961 let mut rule: Rule = "target: dep1 dep2\n".parse().unwrap();
4962 rule.set_prerequisites(vec![]).unwrap();
4963 assert_eq!(rule.prerequisites().collect::<Vec<_>>().len(), 0);
4964 }
4965
4966 #[test]
4967 fn test_rule_remove() {
4968 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4969 let rule = makefile.find_rule_by_target("rule1").unwrap();
4970 rule.remove().unwrap();
4971 assert_eq!(makefile.rules().count(), 1);
4972 assert!(makefile.find_rule_by_target("rule1").is_none());
4973 assert!(makefile.find_rule_by_target("rule2").is_some());
4974 }
4975
4976 #[test]
4977 fn test_makefile_find_rule_by_target() {
4978 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4979 let rule = makefile.find_rule_by_target("rule2");
4980 assert!(rule.is_some());
4981 assert_eq!(rule.unwrap().targets().collect::<Vec<_>>(), vec!["rule2"]);
4982 assert!(makefile.find_rule_by_target("nonexistent").is_none());
4983 }
4984
4985 #[test]
4986 fn test_makefile_find_rules_by_target() {
4987 let makefile: Makefile = "rule1:\n\tcommand1\nrule1:\n\tcommand2\nrule2:\n\tcommand3\n"
4988 .parse()
4989 .unwrap();
4990 assert_eq!(makefile.find_rules_by_target("rule1").count(), 2);
4991 assert_eq!(makefile.find_rules_by_target("rule2").count(), 1);
4992 assert_eq!(makefile.find_rules_by_target("nonexistent").count(), 0);
4993 }
4994
4995 #[test]
4996 fn test_makefile_add_phony_target() {
4997 let mut makefile = Makefile::new();
4998 makefile.add_phony_target("clean").unwrap();
4999 assert!(makefile.is_phony("clean"));
5000 assert_eq!(makefile.phony_targets().collect::<Vec<_>>(), vec!["clean"]);
5001 }
5002
5003 #[test]
5004 fn test_makefile_add_phony_target_existing() {
5005 let mut makefile: Makefile = ".PHONY: test\n".parse().unwrap();
5006 makefile.add_phony_target("clean").unwrap();
5007 assert!(makefile.is_phony("test"));
5008 assert!(makefile.is_phony("clean"));
5009 let targets: Vec<_> = makefile.phony_targets().collect();
5010 assert!(targets.contains(&"test".to_string()));
5011 assert!(targets.contains(&"clean".to_string()));
5012 }
5013
5014 #[test]
5015 fn test_makefile_remove_phony_target() {
5016 let mut makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
5017 assert!(makefile.remove_phony_target("clean").unwrap());
5018 assert!(!makefile.is_phony("clean"));
5019 assert!(makefile.is_phony("test"));
5020 assert!(!makefile.remove_phony_target("nonexistent").unwrap());
5021 }
5022
5023 #[test]
5024 fn test_makefile_remove_phony_target_last() {
5025 let mut makefile: Makefile = ".PHONY: clean\n".parse().unwrap();
5026 assert!(makefile.remove_phony_target("clean").unwrap());
5027 assert!(!makefile.is_phony("clean"));
5028 assert!(makefile.find_rule_by_target(".PHONY").is_none());
5030 }
5031
5032 #[test]
5033 fn test_makefile_is_phony() {
5034 let makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
5035 assert!(makefile.is_phony("clean"));
5036 assert!(makefile.is_phony("test"));
5037 assert!(!makefile.is_phony("build"));
5038 }
5039
5040 #[test]
5041 fn test_makefile_phony_targets() {
5042 let makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
5043 let phony_targets: Vec<_> = makefile.phony_targets().collect();
5044 assert_eq!(phony_targets, vec!["clean", "test", "build"]);
5045 }
5046
5047 #[test]
5048 fn test_makefile_phony_targets_empty() {
5049 let makefile = Makefile::new();
5050 assert_eq!(makefile.phony_targets().count(), 0);
5051 }
5052}