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