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
1372impl VariableDefinition {
1373 pub fn name(&self) -> Option<String> {
1375 self.syntax().children_with_tokens().find_map(|it| {
1376 it.as_token().and_then(|it| {
1377 if it.kind() == IDENTIFIER && it.text() != "export" {
1378 Some(it.text().to_string())
1379 } else {
1380 None
1381 }
1382 })
1383 })
1384 }
1385
1386 pub fn raw_value(&self) -> Option<String> {
1388 self.syntax()
1389 .children()
1390 .find(|it| it.kind() == EXPR)
1391 .map(|it| it.text().into())
1392 }
1393
1394 pub fn remove(&mut self) {
1405 let index = self.syntax().index();
1406 if let Some(parent) = self.syntax().parent() {
1407 parent.splice_children(index..index + 1, vec![]);
1408 }
1409 }
1410
1411 pub fn set_value(&mut self, new_value: &str) {
1424 let expr_index = self
1426 .syntax()
1427 .children()
1428 .find(|it| it.kind() == EXPR)
1429 .map(|it| it.index());
1430
1431 if let Some(expr_idx) = expr_index {
1432 let mut builder = GreenNodeBuilder::new();
1434 builder.start_node(EXPR.into());
1435 builder.token(IDENTIFIER.into(), new_value);
1436 builder.finish_node();
1437
1438 let new_expr = SyntaxNode::new_root_mut(builder.finish());
1439
1440 self.0
1442 .splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
1443 }
1444 }
1445}
1446
1447impl Makefile {
1448 pub fn new() -> Makefile {
1450 let mut builder = GreenNodeBuilder::new();
1451
1452 builder.start_node(ROOT.into());
1453 builder.finish_node();
1454
1455 let syntax = SyntaxNode::new_root_mut(builder.finish());
1456 Makefile(syntax)
1457 }
1458
1459 pub fn parse(text: &str) -> crate::Parse<Makefile> {
1461 crate::Parse::<Makefile>::parse_makefile(text)
1462 }
1463
1464 pub fn code(&self) -> String {
1466 self.syntax().text().to_string()
1467 }
1468
1469 pub fn is_root(&self) -> bool {
1471 self.syntax().kind() == ROOT
1472 }
1473
1474 pub fn read<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1476 let mut buf = String::new();
1477 r.read_to_string(&mut buf)?;
1478 buf.parse()
1479 }
1480
1481 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1483 let mut buf = String::new();
1484 r.read_to_string(&mut buf)?;
1485
1486 let parsed = parse(&buf);
1487 Ok(parsed.root())
1488 }
1489
1490 pub fn rules(&self) -> impl Iterator<Item = Rule> + '_ {
1499 self.syntax().children().filter_map(Rule::cast)
1500 }
1501
1502 pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1504 self.rules()
1505 .filter(move |rule| rule.targets().any(|t| t == target))
1506 }
1507
1508 pub fn variable_definitions(&self) -> impl Iterator<Item = VariableDefinition> {
1510 self.syntax()
1511 .children()
1512 .filter_map(VariableDefinition::cast)
1513 }
1514
1515 pub fn find_variable<'a>(
1530 &'a self,
1531 name: &'a str,
1532 ) -> impl Iterator<Item = VariableDefinition> + 'a {
1533 self.variable_definitions()
1534 .filter(move |var| var.name().as_deref() == Some(name))
1535 }
1536
1537 pub fn add_rule(&mut self, target: &str) -> Rule {
1547 let mut builder = GreenNodeBuilder::new();
1548 builder.start_node(RULE.into());
1549 builder.token(IDENTIFIER.into(), target);
1550 builder.token(OPERATOR.into(), ":");
1551 builder.token(NEWLINE.into(), "\n");
1552 builder.finish_node();
1553
1554 let syntax = SyntaxNode::new_root_mut(builder.finish());
1555 let pos = self.0.children_with_tokens().count();
1556 self.0.splice_children(pos..pos, vec![syntax.into()]);
1557 Rule(self.0.children().nth(pos).unwrap())
1558 }
1559
1560 pub fn from_reader<R: std::io::Read>(mut r: R) -> Result<Makefile, Error> {
1562 let mut buf = String::new();
1563 r.read_to_string(&mut buf)?;
1564
1565 let parsed = parse(&buf);
1566 if !parsed.errors.is_empty() {
1567 Err(Error::Parse(ParseError {
1568 errors: parsed.errors,
1569 }))
1570 } else {
1571 Ok(parsed.root())
1572 }
1573 }
1574
1575 pub fn replace_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1586 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1587
1588 if rules.is_empty() {
1589 return Err(Error::Parse(ParseError {
1590 errors: vec![ErrorInfo {
1591 message: "Cannot replace rule in empty makefile".to_string(),
1592 line: 1,
1593 context: "replace_rule".to_string(),
1594 }],
1595 }));
1596 }
1597
1598 if index >= rules.len() {
1599 return Err(Error::Parse(ParseError {
1600 errors: vec![ErrorInfo {
1601 message: format!(
1602 "Rule index {} out of bounds (max {})",
1603 index,
1604 rules.len() - 1
1605 ),
1606 line: 1,
1607 context: "replace_rule".to_string(),
1608 }],
1609 }));
1610 }
1611
1612 let target_node = &rules[index];
1613 let target_index = target_node.index();
1614
1615 self.0.splice_children(
1617 target_index..target_index + 1,
1618 vec![new_rule.0.clone().into()],
1619 );
1620 Ok(())
1621 }
1622
1623 pub fn remove_rule(&mut self, index: usize) -> Result<Rule, Error> {
1634 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1635
1636 if rules.is_empty() {
1637 return Err(Error::Parse(ParseError {
1638 errors: vec![ErrorInfo {
1639 message: "Cannot remove rule from empty makefile".to_string(),
1640 line: 1,
1641 context: "remove_rule".to_string(),
1642 }],
1643 }));
1644 }
1645
1646 if index >= rules.len() {
1647 return Err(Error::Parse(ParseError {
1648 errors: vec![ErrorInfo {
1649 message: format!(
1650 "Rule index {} out of bounds (max {})",
1651 index,
1652 rules.len() - 1
1653 ),
1654 line: 1,
1655 context: "remove_rule".to_string(),
1656 }],
1657 }));
1658 }
1659
1660 let target_node = rules[index].clone();
1661 let target_index = target_node.index();
1662
1663 self.0
1665 .splice_children(target_index..target_index + 1, vec![]);
1666 Ok(Rule(target_node))
1667 }
1668
1669 pub fn insert_rule(&mut self, index: usize, new_rule: Rule) -> Result<(), Error> {
1681 let rules: Vec<_> = self.0.children().filter(|n| n.kind() == RULE).collect();
1682
1683 if index > rules.len() {
1684 return Err(Error::Parse(ParseError {
1685 errors: vec![ErrorInfo {
1686 message: format!("Rule index {} out of bounds (max {})", index, rules.len()),
1687 line: 1,
1688 context: "insert_rule".to_string(),
1689 }],
1690 }));
1691 }
1692
1693 let target_index = if index == rules.len() {
1694 self.0.children_with_tokens().count()
1696 } else {
1697 rules[index].index()
1699 };
1700
1701 self.0
1703 .splice_children(target_index..target_index, vec![new_rule.0.clone().into()]);
1704 Ok(())
1705 }
1706
1707 pub fn includes(&self) -> impl Iterator<Item = Include> {
1717 self.syntax().children().filter_map(Include::cast)
1718 }
1719
1720 pub fn included_files(&self) -> impl Iterator<Item = String> + '_ {
1730 fn collect_includes(node: &SyntaxNode) -> Vec<Include> {
1733 let mut includes = Vec::new();
1734
1735 if let Some(include) = Include::cast(node.clone()) {
1737 includes.push(include);
1738 }
1739
1740 for child in node.children() {
1742 includes.extend(collect_includes(&child));
1743 }
1744
1745 includes
1746 }
1747
1748 let includes = collect_includes(self.syntax());
1750
1751 includes.into_iter().map(|include| {
1753 include
1754 .syntax()
1755 .children()
1756 .find(|node| node.kind() == EXPR)
1757 .map(|expr| expr.text().to_string().trim().to_string())
1758 .unwrap_or_default()
1759 })
1760 }
1761
1762 pub fn find_rule_by_target(&self, target: &str) -> Option<Rule> {
1773 self.rules()
1774 .find(|rule| rule.targets().any(|t| t == target))
1775 }
1776
1777 pub fn find_rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator<Item = Rule> + 'a {
1787 self.rules_by_target(target)
1788 }
1789
1790 pub fn add_phony_target(&mut self, target: &str) -> Result<(), Error> {
1800 if let Some(mut phony_rule) = self.find_rule_by_target(".PHONY") {
1802 if !phony_rule.prerequisites().any(|p| p == target) {
1804 phony_rule.add_prerequisite(target)?;
1805 }
1806 } else {
1807 let mut phony_rule = self.add_rule(".PHONY");
1809 phony_rule.add_prerequisite(target)?;
1810 }
1811 Ok(())
1812 }
1813
1814 pub fn remove_phony_target(&mut self, target: &str) -> Result<bool, Error> {
1828 let mut phony_rule = None;
1830 for rule in self.rules_by_target(".PHONY") {
1831 if rule.prerequisites().any(|p| p == target) {
1832 phony_rule = Some(rule);
1833 break;
1834 }
1835 }
1836
1837 let mut phony_rule = match phony_rule {
1838 Some(rule) => rule,
1839 None => return Ok(false),
1840 };
1841
1842 let prereq_count = phony_rule.prerequisites().count();
1844
1845 phony_rule.remove_prerequisite(target)?;
1847
1848 if prereq_count == 1 {
1850 phony_rule.remove()?;
1852 }
1853
1854 Ok(true)
1855 }
1856
1857 pub fn is_phony(&self, target: &str) -> bool {
1868 self.rules_by_target(".PHONY")
1870 .any(|rule| rule.prerequisites().any(|p| p == target))
1871 }
1872
1873 pub fn phony_targets(&self) -> impl Iterator<Item = String> + '_ {
1883 self.rules_by_target(".PHONY")
1885 .flat_map(|rule| rule.prerequisites().collect::<Vec<_>>())
1886 }
1887}
1888
1889impl FromStr for Rule {
1890 type Err = crate::Error;
1891
1892 fn from_str(s: &str) -> Result<Self, Self::Err> {
1893 Rule::parse(s).to_rule_result()
1894 }
1895}
1896
1897impl FromStr for Makefile {
1898 type Err = crate::Error;
1899
1900 fn from_str(s: &str) -> Result<Self, Self::Err> {
1901 Makefile::parse(s).to_result()
1902 }
1903}
1904
1905fn build_prerequisites_node(prereqs: &[String]) -> SyntaxNode {
1907 let mut builder = GreenNodeBuilder::new();
1908 builder.start_node(PREREQUISITES.into());
1909
1910 for (i, prereq) in prereqs.iter().enumerate() {
1911 if i > 0 {
1912 builder.token(WHITESPACE.into(), " ");
1913 }
1914
1915 builder.start_node(PREREQUISITE.into());
1917 builder.token(IDENTIFIER.into(), prereq);
1918 builder.finish_node();
1919 }
1920
1921 builder.finish_node();
1922 SyntaxNode::new_root_mut(builder.finish())
1923}
1924
1925impl Rule {
1926 pub fn parse(text: &str) -> crate::Parse<Rule> {
1928 crate::Parse::<Rule>::parse_rule(text)
1929 }
1930
1931 fn collect_variable_reference(
1933 &self,
1934 tokens: &mut std::iter::Peekable<impl Iterator<Item = SyntaxElement>>,
1935 ) -> Option<String> {
1936 let mut var_ref = String::new();
1937
1938 if let Some(token) = tokens.next() {
1940 if let Some(t) = token.as_token() {
1941 if t.kind() == DOLLAR {
1942 var_ref.push_str(t.text());
1943
1944 if let Some(next) = tokens.peek() {
1946 if let Some(nt) = next.as_token() {
1947 if nt.kind() == LPAREN {
1948 var_ref.push_str(nt.text());
1950 tokens.next();
1951
1952 let mut paren_count = 1;
1954
1955 for next_token in tokens.by_ref() {
1957 if let Some(nt) = next_token.as_token() {
1958 var_ref.push_str(nt.text());
1959
1960 if nt.kind() == LPAREN {
1961 paren_count += 1;
1962 } else if nt.kind() == RPAREN {
1963 paren_count -= 1;
1964 if paren_count == 0 {
1965 break;
1966 }
1967 }
1968 }
1969 }
1970
1971 return Some(var_ref);
1972 }
1973 }
1974 }
1975
1976 for next_token in tokens.by_ref() {
1978 if let Some(nt) = next_token.as_token() {
1979 var_ref.push_str(nt.text());
1980 if nt.kind() == RPAREN {
1981 break;
1982 }
1983 }
1984 }
1985 return Some(var_ref);
1986 }
1987 }
1988 }
1989
1990 None
1991 }
1992
1993 pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
2003 let mut result = Vec::new();
2004 let mut tokens = self
2005 .syntax()
2006 .children_with_tokens()
2007 .take_while(|it| it.as_token().map(|t| t.kind() != OPERATOR).unwrap_or(true))
2008 .peekable();
2009
2010 while let Some(token) = tokens.peek().cloned() {
2011 if let Some(node) = token.as_node() {
2012 tokens.next(); if node.kind() == EXPR {
2014 let mut var_content = String::new();
2016 for child in node.children_with_tokens() {
2017 if let Some(t) = child.as_token() {
2018 var_content.push_str(t.text());
2019 }
2020 }
2021 if !var_content.is_empty() {
2022 result.push(var_content);
2023 }
2024 }
2025 } else if let Some(t) = token.as_token() {
2026 if t.kind() == DOLLAR {
2027 if let Some(var_ref) = self.collect_variable_reference(&mut tokens) {
2028 result.push(var_ref);
2029 }
2030 } else if t.kind() == IDENTIFIER {
2031 let ident_text = t.text().to_string();
2033 tokens.next(); if let Some(next) = tokens.peek() {
2037 if let Some(next_token) = next.as_token() {
2038 if next_token.kind() == LPAREN {
2039 let mut archive_target = ident_text;
2041 archive_target.push_str(next_token.text()); tokens.next(); while let Some(token) = tokens.peek() {
2046 if let Some(node) = token.as_node() {
2047 if node.kind() == ARCHIVE_MEMBERS {
2048 archive_target.push_str(&node.text().to_string());
2049 tokens.next();
2050 } else {
2051 tokens.next();
2052 }
2053 } else if let Some(t) = token.as_token() {
2054 if t.kind() == RPAREN {
2055 archive_target.push_str(t.text());
2056 tokens.next();
2057 break;
2058 } else {
2059 tokens.next();
2060 }
2061 } else {
2062 break;
2063 }
2064 }
2065 result.push(archive_target);
2066 } else {
2067 result.push(ident_text);
2069 }
2070 } else {
2071 result.push(ident_text);
2073 }
2074 } else {
2075 result.push(ident_text);
2077 }
2078 } else {
2079 tokens.next(); }
2081 }
2082 }
2083 result.into_iter()
2084 }
2085
2086 pub fn prerequisites(&self) -> impl Iterator<Item = String> + '_ {
2095 let mut found_operator = false;
2097 let mut prerequisites_node = None;
2098
2099 for element in self.syntax().children_with_tokens() {
2100 if let Some(token) = element.as_token() {
2101 if token.kind() == OPERATOR {
2102 found_operator = true;
2103 }
2104 } else if let Some(node) = element.as_node() {
2105 if found_operator && node.kind() == PREREQUISITES {
2106 prerequisites_node = Some(node.clone());
2107 break;
2108 }
2109 }
2110 }
2111
2112 let result: Vec<String> = if let Some(prereqs) = prerequisites_node {
2113 prereqs
2115 .children()
2116 .filter(|child| child.kind() == PREREQUISITE)
2117 .map(|child| child.text().to_string().trim().to_string())
2118 .collect()
2119 } else {
2120 Vec::new()
2121 };
2122
2123 result.into_iter()
2124 }
2125
2126 pub fn recipes(&self) -> impl Iterator<Item = String> {
2135 self.syntax()
2136 .children()
2137 .filter(|it| it.kind() == RECIPE)
2138 .flat_map(|it| {
2139 it.children_with_tokens().filter_map(|it| {
2140 it.as_token().and_then(|t| {
2141 if t.kind() == TEXT {
2142 Some(t.text().to_string())
2143 } else {
2144 None
2145 }
2146 })
2147 })
2148 })
2149 }
2150
2151 pub fn replace_command(&mut self, i: usize, line: &str) -> bool {
2161 let index = self
2163 .syntax()
2164 .children()
2165 .filter(|it| it.kind() == RECIPE)
2166 .nth(i);
2167
2168 let index = match index {
2169 Some(node) => node.index(),
2170 None => return false,
2171 };
2172
2173 let mut builder = GreenNodeBuilder::new();
2174 builder.start_node(RECIPE.into());
2175 builder.token(INDENT.into(), "\t");
2176 builder.token(TEXT.into(), line);
2177 builder.token(NEWLINE.into(), "\n");
2178 builder.finish_node();
2179
2180 let syntax = SyntaxNode::new_root_mut(builder.finish());
2181
2182 self.0
2183 .splice_children(index..index + 1, vec![syntax.into()]);
2184
2185 true
2186 }
2187
2188 pub fn push_command(&mut self, line: &str) {
2198 let index = self
2200 .0
2201 .children_with_tokens()
2202 .filter(|it| it.kind() == RECIPE)
2203 .last();
2204
2205 let index = index.map_or_else(
2206 || self.0.children_with_tokens().count(),
2207 |it| it.index() + 1,
2208 );
2209
2210 let mut builder = GreenNodeBuilder::new();
2211 builder.start_node(RECIPE.into());
2212 builder.token(INDENT.into(), "\t");
2213 builder.token(TEXT.into(), line);
2214 builder.token(NEWLINE.into(), "\n");
2215 builder.finish_node();
2216 let syntax = SyntaxNode::new_root_mut(builder.finish());
2217
2218 self.0.splice_children(index..index, vec![syntax.into()]);
2219 }
2220
2221 pub fn remove_command(&mut self, index: usize) -> bool {
2231 let recipes: Vec<_> = self
2232 .syntax()
2233 .children()
2234 .filter(|n| n.kind() == RECIPE)
2235 .collect();
2236
2237 if index >= recipes.len() {
2238 return false;
2239 }
2240
2241 let target_node = &recipes[index];
2242 let target_index = target_node.index();
2243
2244 self.0
2245 .splice_children(target_index..target_index + 1, vec![]);
2246 true
2247 }
2248
2249 pub fn insert_command(&mut self, index: usize, line: &str) -> bool {
2260 let recipes: Vec<_> = self
2261 .syntax()
2262 .children()
2263 .filter(|n| n.kind() == RECIPE)
2264 .collect();
2265
2266 if index > recipes.len() {
2267 return false;
2268 }
2269
2270 let target_index = if index == recipes.len() {
2271 recipes.last().map(|n| n.index() + 1).unwrap_or_else(|| {
2273 self.0.children_with_tokens().count()
2275 })
2276 } else {
2277 recipes[index].index()
2279 };
2280
2281 let mut builder = GreenNodeBuilder::new();
2282 builder.start_node(RECIPE.into());
2283 builder.token(INDENT.into(), "\t");
2284 builder.token(TEXT.into(), line);
2285 builder.token(NEWLINE.into(), "\n");
2286 builder.finish_node();
2287 let syntax = SyntaxNode::new_root_mut(builder.finish());
2288
2289 self.0
2290 .splice_children(target_index..target_index, vec![syntax.into()]);
2291 true
2292 }
2293
2294 pub fn recipe_count(&self) -> usize {
2303 self.syntax()
2304 .children()
2305 .filter(|n| n.kind() == RECIPE)
2306 .count()
2307 }
2308
2309 pub fn clear_commands(&mut self) {
2319 let recipes: Vec<_> = self
2320 .syntax()
2321 .children()
2322 .filter(|n| n.kind() == RECIPE)
2323 .collect();
2324
2325 if recipes.is_empty() {
2326 return;
2327 }
2328
2329 for recipe in recipes.iter().rev() {
2331 let index = recipe.index();
2332 self.0.splice_children(index..index + 1, vec![]);
2333 }
2334 }
2335
2336 pub fn remove_prerequisite(&mut self, target: &str) -> Result<bool, Error> {
2349 let mut found_operator = false;
2351 let mut prereqs_node = None;
2352
2353 for child in self.syntax().children_with_tokens() {
2354 if let Some(token) = child.as_token() {
2355 if token.kind() == OPERATOR {
2356 found_operator = true;
2357 }
2358 } else if let Some(node) = child.as_node() {
2359 if found_operator && node.kind() == PREREQUISITES {
2360 prereqs_node = Some(node.clone());
2361 break;
2362 }
2363 }
2364 }
2365
2366 let prereqs_node = match prereqs_node {
2367 Some(node) => node,
2368 None => return Ok(false), };
2370
2371 let current_prereqs: Vec<String> = self.prerequisites().collect();
2373
2374 if !current_prereqs.iter().any(|p| p == target) {
2376 return Ok(false);
2377 }
2378
2379 let new_prereqs: Vec<String> = current_prereqs
2381 .into_iter()
2382 .filter(|p| p != target)
2383 .collect();
2384
2385 let prereqs_index = prereqs_node.index();
2387 let new_prereqs_node = build_prerequisites_node(&new_prereqs);
2388
2389 self.0.splice_children(
2390 prereqs_index..prereqs_index + 1,
2391 vec![new_prereqs_node.into()],
2392 );
2393
2394 Ok(true)
2395 }
2396
2397 pub fn add_prerequisite(&mut self, target: &str) -> Result<(), Error> {
2407 let mut current_prereqs: Vec<String> = self.prerequisites().collect();
2408 current_prereqs.push(target.to_string());
2409 self.set_prerequisites(current_prereqs.iter().map(|s| s.as_str()).collect())
2410 }
2411
2412 pub fn set_prerequisites(&mut self, prereqs: Vec<&str>) -> Result<(), Error> {
2422 let mut prereqs_index = None;
2424 let mut operator_found = false;
2425
2426 for child in self.syntax().children_with_tokens() {
2427 if let Some(token) = child.as_token() {
2428 if token.kind() == OPERATOR {
2429 operator_found = true;
2430 }
2431 } else if let Some(node) = child.as_node() {
2432 if operator_found && node.kind() == PREREQUISITES {
2433 prereqs_index = Some((node.index(), true)); break;
2435 }
2436 }
2437 }
2438
2439 let new_prereqs =
2441 build_prerequisites_node(&prereqs.iter().map(|s| s.to_string()).collect::<Vec<_>>());
2442
2443 match prereqs_index {
2444 Some((idx, true)) => {
2445 self.0
2447 .splice_children(idx..idx + 1, vec![new_prereqs.into()]);
2448 }
2449 _ => {
2450 let insert_pos = self
2452 .syntax()
2453 .children_with_tokens()
2454 .position(|t| t.as_token().map(|t| t.kind() == OPERATOR).unwrap_or(false))
2455 .map(|p| p + 1)
2456 .ok_or_else(|| {
2457 Error::Parse(ParseError {
2458 errors: vec![ErrorInfo {
2459 message: "No operator found in rule".to_string(),
2460 line: 1,
2461 context: "set_prerequisites".to_string(),
2462 }],
2463 })
2464 })?;
2465
2466 self.0
2467 .splice_children(insert_pos..insert_pos, vec![new_prereqs.into()]);
2468 }
2469 }
2470
2471 Ok(())
2472 }
2473
2474 pub fn remove(self) -> Result<(), Error> {
2485 let parent = self.syntax().parent().ok_or_else(|| {
2486 Error::Parse(ParseError {
2487 errors: vec![ErrorInfo {
2488 message: "Rule has no parent".to_string(),
2489 line: 1,
2490 context: "remove".to_string(),
2491 }],
2492 })
2493 })?;
2494
2495 let index = self.syntax().index();
2496 parent.splice_children(index..index + 1, vec![]);
2497 Ok(())
2498 }
2499}
2500
2501impl Default for Makefile {
2502 fn default() -> Self {
2503 Self::new()
2504 }
2505}
2506
2507impl Include {
2508 pub fn path(&self) -> Option<String> {
2510 self.syntax()
2511 .children()
2512 .find(|it| it.kind() == EXPR)
2513 .map(|it| it.text().to_string().trim().to_string())
2514 }
2515
2516 pub fn is_optional(&self) -> bool {
2518 let text = self.syntax().text();
2519 text.to_string().starts_with("-include") || text.to_string().starts_with("sinclude")
2520 }
2521}
2522
2523#[cfg(test)]
2524mod tests {
2525 use super::*;
2526
2527 #[test]
2528 fn test_conditionals() {
2529 let code = "ifdef DEBUG\n DEBUG_FLAG := 1\nendif\n";
2533 let mut buf = code.as_bytes();
2534 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse basic ifdef");
2535 assert!(makefile.code().contains("DEBUG_FLAG"));
2536
2537 let code =
2539 "ifeq ($(OS),Windows_NT)\n RESULT := windows\nelse\n RESULT := unix\nendif\n";
2540 let mut buf = code.as_bytes();
2541 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq/ifneq");
2542 assert!(makefile.code().contains("RESULT"));
2543 assert!(makefile.code().contains("windows"));
2544
2545 let code = "ifdef DEBUG\n CFLAGS += -g\n ifdef VERBOSE\n CFLAGS += -v\n endif\nelse\n CFLAGS += -O2\nendif\n";
2547 let mut buf = code.as_bytes();
2548 let makefile = Makefile::read_relaxed(&mut buf)
2549 .expect("Failed to parse nested conditionals with else");
2550 assert!(makefile.code().contains("CFLAGS"));
2551 assert!(makefile.code().contains("VERBOSE"));
2552
2553 let code = "ifdef DEBUG\nendif\n";
2555 let mut buf = code.as_bytes();
2556 let makefile =
2557 Makefile::read_relaxed(&mut buf).expect("Failed to parse empty conditionals");
2558 assert!(makefile.code().contains("ifdef DEBUG"));
2559
2560 let code = "ifeq ($(OS),Windows)\n EXT := .exe\nelif ifeq ($(OS),Linux)\n EXT := .bin\nelse\n EXT := .out\nendif\n";
2562 let mut buf = code.as_bytes();
2563 let makefile =
2564 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditionals with elif");
2565 assert!(makefile.code().contains("EXT"));
2566
2567 let code = "ifXYZ DEBUG\nDEBUG := 1\nendif\n";
2569 let mut buf = code.as_bytes();
2570 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse with recovery");
2571 assert!(makefile.code().contains("DEBUG"));
2572
2573 let code = "ifdef \nDEBUG := 1\nendif\n";
2575 let mut buf = code.as_bytes();
2576 let makefile = Makefile::read_relaxed(&mut buf)
2577 .expect("Failed to parse with recovery - missing condition");
2578 assert!(makefile.code().contains("DEBUG"));
2579 }
2580
2581 #[test]
2582 fn test_parse_simple() {
2583 const SIMPLE: &str = r#"VARIABLE = value
2584
2585rule: dependency
2586 command
2587"#;
2588 let parsed = parse(SIMPLE);
2589 assert!(parsed.errors.is_empty());
2590 let node = parsed.syntax();
2591 assert_eq!(
2592 format!("{:#?}", node),
2593 r#"ROOT@0..44
2594 VARIABLE@0..17
2595 IDENTIFIER@0..8 "VARIABLE"
2596 WHITESPACE@8..9 " "
2597 OPERATOR@9..10 "="
2598 WHITESPACE@10..11 " "
2599 EXPR@11..16
2600 IDENTIFIER@11..16 "value"
2601 NEWLINE@16..17 "\n"
2602 NEWLINE@17..18 "\n"
2603 RULE@18..44
2604 IDENTIFIER@18..22 "rule"
2605 OPERATOR@22..23 ":"
2606 WHITESPACE@23..24 " "
2607 PREREQUISITES@24..34
2608 PREREQUISITE@24..34
2609 IDENTIFIER@24..34 "dependency"
2610 NEWLINE@34..35 "\n"
2611 RECIPE@35..44
2612 INDENT@35..36 "\t"
2613 TEXT@36..43 "command"
2614 NEWLINE@43..44 "\n"
2615"#
2616 );
2617
2618 let root = parsed.root();
2619
2620 let mut rules = root.rules().collect::<Vec<_>>();
2621 assert_eq!(rules.len(), 1);
2622 let rule = rules.pop().unwrap();
2623 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2624 assert_eq!(rule.prerequisites().collect::<Vec<_>>(), vec!["dependency"]);
2625 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2626
2627 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2628 assert_eq!(variables.len(), 1);
2629 let variable = variables.pop().unwrap();
2630 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2631 assert_eq!(variable.raw_value(), Some("value".to_string()));
2632 }
2633
2634 #[test]
2635 fn test_parse_export_assign() {
2636 const EXPORT: &str = r#"export VARIABLE := value
2637"#;
2638 let parsed = parse(EXPORT);
2639 assert!(parsed.errors.is_empty());
2640 let node = parsed.syntax();
2641 assert_eq!(
2642 format!("{:#?}", node),
2643 r#"ROOT@0..25
2644 VARIABLE@0..25
2645 IDENTIFIER@0..6 "export"
2646 WHITESPACE@6..7 " "
2647 IDENTIFIER@7..15 "VARIABLE"
2648 WHITESPACE@15..16 " "
2649 OPERATOR@16..18 ":="
2650 WHITESPACE@18..19 " "
2651 EXPR@19..24
2652 IDENTIFIER@19..24 "value"
2653 NEWLINE@24..25 "\n"
2654"#
2655 );
2656
2657 let root = parsed.root();
2658
2659 let mut variables = root.variable_definitions().collect::<Vec<_>>();
2660 assert_eq!(variables.len(), 1);
2661 let variable = variables.pop().unwrap();
2662 assert_eq!(variable.name(), Some("VARIABLE".to_string()));
2663 assert_eq!(variable.raw_value(), Some("value".to_string()));
2664 }
2665
2666 #[test]
2667 fn test_parse_multiple_prerequisites() {
2668 const MULTIPLE_PREREQUISITES: &str = r#"rule: dependency1 dependency2
2669 command
2670
2671"#;
2672 let parsed = parse(MULTIPLE_PREREQUISITES);
2673 assert!(parsed.errors.is_empty());
2674 let node = parsed.syntax();
2675 assert_eq!(
2676 format!("{:#?}", node),
2677 r#"ROOT@0..40
2678 RULE@0..40
2679 IDENTIFIER@0..4 "rule"
2680 OPERATOR@4..5 ":"
2681 WHITESPACE@5..6 " "
2682 PREREQUISITES@6..29
2683 PREREQUISITE@6..17
2684 IDENTIFIER@6..17 "dependency1"
2685 WHITESPACE@17..18 " "
2686 PREREQUISITE@18..29
2687 IDENTIFIER@18..29 "dependency2"
2688 NEWLINE@29..30 "\n"
2689 RECIPE@30..39
2690 INDENT@30..31 "\t"
2691 TEXT@31..38 "command"
2692 NEWLINE@38..39 "\n"
2693 NEWLINE@39..40 "\n"
2694"#
2695 );
2696 let root = parsed.root();
2697
2698 let rule = root.rules().next().unwrap();
2699 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2700 assert_eq!(
2701 rule.prerequisites().collect::<Vec<_>>(),
2702 vec!["dependency1", "dependency2"]
2703 );
2704 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2705 }
2706
2707 #[test]
2708 fn test_add_rule() {
2709 let mut makefile = Makefile::new();
2710 let rule = makefile.add_rule("rule");
2711 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2712 assert_eq!(
2713 rule.prerequisites().collect::<Vec<_>>(),
2714 Vec::<String>::new()
2715 );
2716
2717 assert_eq!(makefile.to_string(), "rule:\n");
2718 }
2719
2720 #[test]
2721 fn test_push_command() {
2722 let mut makefile = Makefile::new();
2723 let mut rule = makefile.add_rule("rule");
2724
2725 rule.push_command("command");
2727 rule.push_command("command2");
2728
2729 assert_eq!(
2731 rule.recipes().collect::<Vec<_>>(),
2732 vec!["command", "command2"]
2733 );
2734
2735 rule.push_command("command3");
2737 assert_eq!(
2738 rule.recipes().collect::<Vec<_>>(),
2739 vec!["command", "command2", "command3"]
2740 );
2741
2742 assert_eq!(
2744 makefile.to_string(),
2745 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2746 );
2747
2748 assert_eq!(
2750 rule.to_string(),
2751 "rule:\n\tcommand\n\tcommand2\n\tcommand3\n"
2752 );
2753 }
2754
2755 #[test]
2756 fn test_replace_command() {
2757 let mut makefile = Makefile::new();
2758 let mut rule = makefile.add_rule("rule");
2759
2760 rule.push_command("command");
2762 rule.push_command("command2");
2763
2764 assert_eq!(
2766 rule.recipes().collect::<Vec<_>>(),
2767 vec!["command", "command2"]
2768 );
2769
2770 rule.replace_command(0, "new command");
2772 assert_eq!(
2773 rule.recipes().collect::<Vec<_>>(),
2774 vec!["new command", "command2"]
2775 );
2776
2777 assert_eq!(makefile.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2779
2780 assert_eq!(rule.to_string(), "rule:\n\tnew command\n\tcommand2\n");
2782 }
2783
2784 #[test]
2785 fn test_parse_rule_without_newline() {
2786 let rule = "rule: dependency\n\tcommand".parse::<Rule>().unwrap();
2787 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2788 assert_eq!(rule.recipes().collect::<Vec<_>>(), vec!["command"]);
2789 let rule = "rule: dependency".parse::<Rule>().unwrap();
2790 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["rule"]);
2791 assert_eq!(rule.recipes().collect::<Vec<_>>(), Vec::<String>::new());
2792 }
2793
2794 #[test]
2795 fn test_parse_makefile_without_newline() {
2796 let makefile = "rule: dependency\n\tcommand".parse::<Makefile>().unwrap();
2797 assert_eq!(makefile.rules().count(), 1);
2798 }
2799
2800 #[test]
2801 fn test_from_reader() {
2802 let makefile = Makefile::from_reader("rule: dependency\n\tcommand".as_bytes()).unwrap();
2803 assert_eq!(makefile.rules().count(), 1);
2804 }
2805
2806 #[test]
2807 fn test_parse_with_tab_after_last_newline() {
2808 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n\t".as_bytes()).unwrap();
2809 assert_eq!(makefile.rules().count(), 1);
2810 }
2811
2812 #[test]
2813 fn test_parse_with_space_after_last_newline() {
2814 let makefile = Makefile::from_reader("rule: dependency\n\tcommand\n ".as_bytes()).unwrap();
2815 assert_eq!(makefile.rules().count(), 1);
2816 }
2817
2818 #[test]
2819 fn test_parse_with_comment_after_last_newline() {
2820 let makefile =
2821 Makefile::from_reader("rule: dependency\n\tcommand\n#comment".as_bytes()).unwrap();
2822 assert_eq!(makefile.rules().count(), 1);
2823 }
2824
2825 #[test]
2826 fn test_parse_with_variable_rule() {
2827 let makefile =
2828 Makefile::from_reader("RULE := rule\n$(RULE): dependency\n\tcommand".as_bytes())
2829 .unwrap();
2830
2831 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2833 assert_eq!(vars.len(), 1);
2834 assert_eq!(vars[0].name(), Some("RULE".to_string()));
2835 assert_eq!(vars[0].raw_value(), Some("rule".to_string()));
2836
2837 let rules = makefile.rules().collect::<Vec<_>>();
2839 assert_eq!(rules.len(), 1);
2840 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["$(RULE)"]);
2841 assert_eq!(
2842 rules[0].prerequisites().collect::<Vec<_>>(),
2843 vec!["dependency"]
2844 );
2845 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2846 }
2847
2848 #[test]
2849 fn test_parse_with_variable_dependency() {
2850 let makefile =
2851 Makefile::from_reader("DEP := dependency\nrule: $(DEP)\n\tcommand".as_bytes()).unwrap();
2852
2853 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2855 assert_eq!(vars.len(), 1);
2856 assert_eq!(vars[0].name(), Some("DEP".to_string()));
2857 assert_eq!(vars[0].raw_value(), Some("dependency".to_string()));
2858
2859 let rules = makefile.rules().collect::<Vec<_>>();
2861 assert_eq!(rules.len(), 1);
2862 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2863 assert_eq!(rules[0].prerequisites().collect::<Vec<_>>(), vec!["$(DEP)"]);
2864 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["command"]);
2865 }
2866
2867 #[test]
2868 fn test_parse_with_variable_command() {
2869 let makefile =
2870 Makefile::from_reader("COM := command\nrule: dependency\n\t$(COM)".as_bytes()).unwrap();
2871
2872 let vars = makefile.variable_definitions().collect::<Vec<_>>();
2874 assert_eq!(vars.len(), 1);
2875 assert_eq!(vars[0].name(), Some("COM".to_string()));
2876 assert_eq!(vars[0].raw_value(), Some("command".to_string()));
2877
2878 let rules = makefile.rules().collect::<Vec<_>>();
2880 assert_eq!(rules.len(), 1);
2881 assert_eq!(rules[0].targets().collect::<Vec<_>>(), vec!["rule"]);
2882 assert_eq!(
2883 rules[0].prerequisites().collect::<Vec<_>>(),
2884 vec!["dependency"]
2885 );
2886 assert_eq!(rules[0].recipes().collect::<Vec<_>>(), vec!["$(COM)"]);
2887 }
2888
2889 #[test]
2890 fn test_regular_line_error_reporting() {
2891 let input = "rule target\n\tcommand";
2892
2893 let parsed = parse(input);
2895 let direct_error = &parsed.errors[0];
2896
2897 assert_eq!(direct_error.line, 2);
2899 assert!(
2900 direct_error.message.contains("expected"),
2901 "Error message should contain 'expected': {}",
2902 direct_error.message
2903 );
2904 assert_eq!(direct_error.context, "\tcommand");
2905
2906 let reader_result = Makefile::from_reader(input.as_bytes());
2908 let parse_error = match reader_result {
2909 Ok(_) => panic!("Expected Parse error from from_reader"),
2910 Err(err) => match err {
2911 self::Error::Parse(parse_err) => parse_err,
2912 _ => panic!("Expected Parse error"),
2913 },
2914 };
2915
2916 let error_text = parse_error.to_string();
2918 assert!(error_text.contains("Error at line 2:"));
2919 assert!(error_text.contains("2| \tcommand"));
2920 }
2921
2922 #[test]
2923 fn test_parsing_error_context_with_bad_syntax() {
2924 let input = "#begin comment\n\t(╯°□°)╯︵ ┻━┻\n#end comment";
2926
2927 match Makefile::from_reader(input.as_bytes()) {
2929 Ok(makefile) => {
2930 assert_eq!(
2932 makefile.rules().count(),
2933 0,
2934 "Should not have found any rules"
2935 );
2936 }
2937 Err(err) => match err {
2938 self::Error::Parse(error) => {
2939 assert!(error.errors[0].line >= 2, "Error line should be at least 2");
2941 assert!(
2942 !error.errors[0].context.is_empty(),
2943 "Error context should not be empty"
2944 );
2945 }
2946 _ => panic!("Unexpected error type"),
2947 },
2948 };
2949 }
2950
2951 #[test]
2952 fn test_error_message_format() {
2953 let parse_error = ParseError {
2955 errors: vec![ErrorInfo {
2956 message: "test error".to_string(),
2957 line: 42,
2958 context: "some problematic code".to_string(),
2959 }],
2960 };
2961
2962 let error_text = parse_error.to_string();
2963 assert!(error_text.contains("Error at line 42: test error"));
2964 assert!(error_text.contains("42| some problematic code"));
2965 }
2966
2967 #[test]
2968 fn test_line_number_calculation() {
2969 let test_cases = [
2971 ("rule dependency\n\tcommand", 2), ("#comment\n\t(╯°□°)╯︵ ┻━┻", 2), ("var = value\n#comment\n\tindented line", 3), ];
2975
2976 for (input, expected_line) in test_cases {
2977 match input.parse::<Makefile>() {
2979 Ok(_) => {
2980 continue;
2983 }
2984 Err(err) => {
2985 if let Error::Parse(parse_err) = err {
2986 assert_eq!(
2988 parse_err.errors[0].line, expected_line,
2989 "Line number should match the expected line"
2990 );
2991
2992 if parse_err.errors[0].message.contains("indented") {
2994 assert!(
2995 parse_err.errors[0].context.starts_with('\t'),
2996 "Context for indentation errors should include the tab character"
2997 );
2998 }
2999 } else {
3000 panic!("Expected parse error, got: {:?}", err);
3001 }
3002 }
3003 }
3004 }
3005 }
3006
3007 #[test]
3008 fn test_conditional_features() {
3009 let code = r#"
3011# Set variables based on DEBUG flag
3012ifdef DEBUG
3013 CFLAGS += -g -DDEBUG
3014else
3015 CFLAGS = -O2
3016endif
3017
3018# Define a build rule
3019all: $(OBJS)
3020 $(CC) $(CFLAGS) -o $@ $^
3021"#;
3022
3023 let mut buf = code.as_bytes();
3024 let makefile =
3025 Makefile::read_relaxed(&mut buf).expect("Failed to parse conditional features");
3026
3027 assert!(!makefile.code().is_empty(), "Makefile has content");
3030
3031 let rules = makefile.rules().collect::<Vec<_>>();
3033 assert!(!rules.is_empty(), "Should have found rules");
3034
3035 assert!(code.contains("ifdef DEBUG"));
3037 assert!(code.contains("endif"));
3038
3039 let code_with_var = r#"
3041# Define a variable first
3042CC = gcc
3043
3044ifdef DEBUG
3045 CFLAGS += -g -DDEBUG
3046else
3047 CFLAGS = -O2
3048endif
3049
3050all: $(OBJS)
3051 $(CC) $(CFLAGS) -o $@ $^
3052"#;
3053
3054 let mut buf = code_with_var.as_bytes();
3055 let makefile =
3056 Makefile::read_relaxed(&mut buf).expect("Failed to parse with explicit variable");
3057
3058 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3060 assert!(
3061 !vars.is_empty(),
3062 "Should have found at least the CC variable definition"
3063 );
3064 }
3065
3066 #[test]
3067 fn test_include_directive() {
3068 let parsed = parse("include config.mk\ninclude $(TOPDIR)/rules.mk\ninclude *.mk\n");
3069 assert!(parsed.errors.is_empty());
3070 let node = parsed.syntax();
3071 assert!(format!("{:#?}", node).contains("INCLUDE@"));
3072 }
3073
3074 #[test]
3075 fn test_export_variables() {
3076 let parsed = parse("export SHELL := /bin/bash\n");
3077 assert!(parsed.errors.is_empty());
3078 let makefile = parsed.root();
3079 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3080 assert_eq!(vars.len(), 1);
3081 let shell_var = vars
3082 .iter()
3083 .find(|v| v.name() == Some("SHELL".to_string()))
3084 .unwrap();
3085 assert!(shell_var.raw_value().unwrap().contains("bin/bash"));
3086 }
3087
3088 #[test]
3089 fn test_variable_scopes() {
3090 let parsed =
3091 parse("SIMPLE = value\nIMMEDIATE := value\nCONDITIONAL ?= value\nAPPEND += value\n");
3092 assert!(parsed.errors.is_empty());
3093 let makefile = parsed.root();
3094 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3095 assert_eq!(vars.len(), 4);
3096 let var_names: Vec<_> = vars.iter().filter_map(|v| v.name()).collect();
3097 assert!(var_names.contains(&"SIMPLE".to_string()));
3098 assert!(var_names.contains(&"IMMEDIATE".to_string()));
3099 assert!(var_names.contains(&"CONDITIONAL".to_string()));
3100 assert!(var_names.contains(&"APPEND".to_string()));
3101 }
3102
3103 #[test]
3104 fn test_pattern_rule_parsing() {
3105 let parsed = parse("%.o: %.c\n\t$(CC) -c -o $@ $<\n");
3106 assert!(parsed.errors.is_empty());
3107 let makefile = parsed.root();
3108 let rules = makefile.rules().collect::<Vec<_>>();
3109 assert_eq!(rules.len(), 1);
3110 assert_eq!(rules[0].targets().next().unwrap(), "%.o");
3111 assert!(rules[0].recipes().next().unwrap().contains("$@"));
3112 }
3113
3114 #[test]
3115 fn test_include_variants() {
3116 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\ninclude $(VAR)/generated.mk\n";
3118 let parsed = parse(makefile_str);
3119 assert!(parsed.errors.is_empty());
3120
3121 let node = parsed.syntax();
3123 let debug_str = format!("{:#?}", node);
3124
3125 assert_eq!(debug_str.matches("INCLUDE@").count(), 4);
3127
3128 let makefile = parsed.root();
3130
3131 let include_count = makefile
3133 .syntax()
3134 .children()
3135 .filter(|child| child.kind() == INCLUDE)
3136 .count();
3137 assert_eq!(include_count, 4);
3138
3139 assert!(makefile
3141 .included_files()
3142 .any(|path| path.contains("$(VAR)")));
3143 }
3144
3145 #[test]
3146 fn test_include_api() {
3147 let makefile_str = "include simple.mk\n-include optional.mk\nsinclude synonym.mk\n";
3149 let makefile: Makefile = makefile_str.parse().unwrap();
3150
3151 let includes: Vec<_> = makefile.includes().collect();
3153 assert_eq!(includes.len(), 3);
3154
3155 assert!(!includes[0].is_optional()); assert!(includes[1].is_optional()); assert!(includes[2].is_optional()); let files: Vec<_> = makefile.included_files().collect();
3162 assert_eq!(files, vec!["simple.mk", "optional.mk", "synonym.mk"]);
3163
3164 assert_eq!(includes[0].path(), Some("simple.mk".to_string()));
3166 assert_eq!(includes[1].path(), Some("optional.mk".to_string()));
3167 assert_eq!(includes[2].path(), Some("synonym.mk".to_string()));
3168 }
3169
3170 #[test]
3171 fn test_include_integration() {
3172 let phony_makefile = Makefile::from_reader(
3176 ".PHONY: build\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3177 .as_bytes()
3178 ).unwrap();
3179
3180 assert_eq!(phony_makefile.rules().count(), 2);
3182
3183 let normal_rules_count = phony_makefile
3185 .rules()
3186 .filter(|r| !r.targets().any(|t| t.starts_with('.')))
3187 .count();
3188 assert_eq!(normal_rules_count, 1);
3189
3190 assert_eq!(phony_makefile.includes().count(), 1);
3192 assert_eq!(phony_makefile.included_files().next().unwrap(), ".env");
3193
3194 let simple_makefile = Makefile::from_reader(
3196 "\n\nVERBOSE ?= 0\n\n# comment\n-include .env\n\nrule: dependency\n\tcommand"
3197 .as_bytes(),
3198 )
3199 .unwrap();
3200 assert_eq!(simple_makefile.rules().count(), 1);
3201 assert_eq!(simple_makefile.includes().count(), 1);
3202 }
3203
3204 #[test]
3205 fn test_real_conditional_directives() {
3206 let conditional = "ifdef DEBUG\nCFLAGS = -g\nelse\nCFLAGS = -O2\nendif\n";
3208 let mut buf = conditional.as_bytes();
3209 let makefile =
3210 Makefile::read_relaxed(&mut buf).expect("Failed to parse basic if/else conditional");
3211 let code = makefile.code();
3212 assert!(code.contains("ifdef DEBUG"));
3213 assert!(code.contains("else"));
3214 assert!(code.contains("endif"));
3215
3216 let nested = "ifdef DEBUG\nCFLAGS = -g\nifdef VERBOSE\nCFLAGS += -v\nendif\nendif\n";
3218 let mut buf = nested.as_bytes();
3219 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse nested ifdef");
3220 let code = makefile.code();
3221 assert!(code.contains("ifdef DEBUG"));
3222 assert!(code.contains("ifdef VERBOSE"));
3223
3224 let ifeq = "ifeq ($(OS),Windows_NT)\nTARGET = app.exe\nelse\nTARGET = app\nendif\n";
3226 let mut buf = ifeq.as_bytes();
3227 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse ifeq form");
3228 let code = makefile.code();
3229 assert!(code.contains("ifeq"));
3230 assert!(code.contains("Windows_NT"));
3231 }
3232
3233 #[test]
3234 fn test_indented_text_outside_rules() {
3235 let help_text = "help:\n\t@echo \"Available targets:\"\n\t@echo \" help show help\"\n";
3237 let parsed = parse(help_text);
3238 assert!(parsed.errors.is_empty());
3239
3240 let root = parsed.root();
3242 let rules = root.rules().collect::<Vec<_>>();
3243 assert_eq!(rules.len(), 1);
3244
3245 let help_rule = &rules[0];
3246 let recipes = help_rule.recipes().collect::<Vec<_>>();
3247 assert_eq!(recipes.len(), 2);
3248 assert!(recipes[0].contains("Available targets"));
3249 assert!(recipes[1].contains("help"));
3250 }
3251
3252 #[test]
3253 fn test_comment_handling_in_recipes() {
3254 let recipe_comment = "build:\n\t# This is a comment\n\tgcc -o app main.c\n";
3256
3257 let parsed = parse(recipe_comment);
3259
3260 assert!(
3262 parsed.errors.is_empty(),
3263 "Should parse recipe with comments without errors"
3264 );
3265
3266 let root = parsed.root();
3268 let rules = root.rules().collect::<Vec<_>>();
3269 assert_eq!(rules.len(), 1, "Should find exactly one rule");
3270
3271 let build_rule = &rules[0];
3273 assert_eq!(
3274 build_rule.targets().collect::<Vec<_>>(),
3275 vec!["build"],
3276 "Rule should have 'build' as target"
3277 );
3278
3279 let recipes = build_rule.recipes().collect::<Vec<_>>();
3283 assert_eq!(
3284 recipes.len(),
3285 1,
3286 "Should find exactly one recipe line (comment lines are filtered)"
3287 );
3288 assert!(
3289 recipes[0].contains("gcc -o app"),
3290 "Recipe should be the command line"
3291 );
3292 assert!(
3293 !recipes[0].contains("This is a comment"),
3294 "Comments should not be included in recipe lines"
3295 );
3296 }
3297
3298 #[test]
3299 fn test_multiline_variables() {
3300 let multiline = "SOURCES = main.c \\\n util.c\n";
3302
3303 let parsed = parse(multiline);
3305
3306 let root = parsed.root();
3308 let vars = root.variable_definitions().collect::<Vec<_>>();
3309 assert!(!vars.is_empty(), "Should find at least one variable");
3310
3311 let operators = "CFLAGS := -Wall \\\n -Werror\n";
3315 let parsed_operators = parse(operators);
3316
3317 let root = parsed_operators.root();
3319 let vars = root.variable_definitions().collect::<Vec<_>>();
3320 assert!(
3321 !vars.is_empty(),
3322 "Should find at least one variable with := operator"
3323 );
3324
3325 let append = "LDFLAGS += -L/usr/lib \\\n -lm\n";
3327 let parsed_append = parse(append);
3328
3329 let root = parsed_append.root();
3331 let vars = root.variable_definitions().collect::<Vec<_>>();
3332 assert!(
3333 !vars.is_empty(),
3334 "Should find at least one variable with += operator"
3335 );
3336 }
3337
3338 #[test]
3339 fn test_whitespace_and_eof_handling() {
3340 let blank_lines = "VAR = value\n\n\n";
3342
3343 let parsed_blank = parse(blank_lines);
3344
3345 let root = parsed_blank.root();
3347 let vars = root.variable_definitions().collect::<Vec<_>>();
3348 assert_eq!(
3349 vars.len(),
3350 1,
3351 "Should find one variable in blank lines test"
3352 );
3353
3354 let trailing_space = "VAR = value \n";
3356
3357 let parsed_space = parse(trailing_space);
3358
3359 let root = parsed_space.root();
3361 let vars = root.variable_definitions().collect::<Vec<_>>();
3362 assert_eq!(
3363 vars.len(),
3364 1,
3365 "Should find one variable in trailing space test"
3366 );
3367
3368 let no_newline = "VAR = value";
3370
3371 let parsed_no_newline = parse(no_newline);
3372
3373 let root = parsed_no_newline.root();
3375 let vars = root.variable_definitions().collect::<Vec<_>>();
3376 assert_eq!(vars.len(), 1, "Should find one variable in no newline test");
3377 assert_eq!(
3378 vars[0].name(),
3379 Some("VAR".to_string()),
3380 "Variable name should be VAR"
3381 );
3382 }
3383
3384 #[test]
3385 fn test_complex_variable_references() {
3386 let wildcard = "SOURCES = $(wildcard *.c)\n";
3388 let parsed = parse(wildcard);
3389 assert!(parsed.errors.is_empty());
3390
3391 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3393 let parsed = parse(nested);
3394 assert!(parsed.errors.is_empty());
3395
3396 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3398 let parsed = parse(patsubst);
3399 assert!(parsed.errors.is_empty());
3400 }
3401
3402 #[test]
3403 fn test_complex_variable_references_minimal() {
3404 let wildcard = "SOURCES = $(wildcard *.c)\n";
3406 let parsed = parse(wildcard);
3407 assert!(parsed.errors.is_empty());
3408
3409 let nested = "PREFIX = /usr\nBINDIR = $(PREFIX)/bin\n";
3411 let parsed = parse(nested);
3412 assert!(parsed.errors.is_empty());
3413
3414 let patsubst = "OBJECTS = $(patsubst %.c,%.o,$(SOURCES))\n";
3416 let parsed = parse(patsubst);
3417 assert!(parsed.errors.is_empty());
3418 }
3419
3420 #[test]
3421 fn test_multiline_variable_with_backslash() {
3422 let content = r#"
3423LONG_VAR = This is a long variable \
3424 that continues on the next line \
3425 and even one more line
3426"#;
3427
3428 let mut buf = content.as_bytes();
3430 let makefile =
3431 Makefile::read_relaxed(&mut buf).expect("Failed to parse multiline variable");
3432
3433 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3435 assert_eq!(
3436 vars.len(),
3437 1,
3438 "Expected 1 variable but found {}",
3439 vars.len()
3440 );
3441 let var_value = vars[0].raw_value();
3442 assert!(var_value.is_some(), "Variable value is None");
3443
3444 let value_str = var_value.unwrap();
3446 assert!(
3447 value_str.contains("long variable"),
3448 "Value doesn't contain expected content"
3449 );
3450 }
3451
3452 #[test]
3453 fn test_multiline_variable_with_mixed_operators() {
3454 let content = r#"
3455PREFIX ?= /usr/local
3456CFLAGS := -Wall -O2 \
3457 -I$(PREFIX)/include \
3458 -DDEBUG
3459"#;
3460 let mut buf = content.as_bytes();
3462 let makefile = Makefile::read_relaxed(&mut buf)
3463 .expect("Failed to parse multiline variable with operators");
3464
3465 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3467 assert!(
3468 vars.len() >= 1,
3469 "Expected at least 1 variable, found {}",
3470 vars.len()
3471 );
3472
3473 let prefix_var = vars
3475 .iter()
3476 .find(|v| v.name().unwrap_or_default() == "PREFIX");
3477 assert!(prefix_var.is_some(), "Expected to find PREFIX variable");
3478 assert!(
3479 prefix_var.unwrap().raw_value().is_some(),
3480 "PREFIX variable has no value"
3481 );
3482
3483 let cflags_var = vars
3485 .iter()
3486 .find(|v| v.name().unwrap_or_default().contains("CFLAGS"));
3487 assert!(
3488 cflags_var.is_some(),
3489 "Expected to find CFLAGS variable (or part of it)"
3490 );
3491 }
3492
3493 #[test]
3494 fn test_indented_help_text() {
3495 let content = r#"
3496.PHONY: help
3497help:
3498 @echo "Available targets:"
3499 @echo " build - Build the project"
3500 @echo " test - Run tests"
3501 @echo " clean - Remove build artifacts"
3502"#;
3503 let mut buf = content.as_bytes();
3505 let makefile =
3506 Makefile::read_relaxed(&mut buf).expect("Failed to parse indented help text");
3507
3508 let rules = makefile.rules().collect::<Vec<_>>();
3510 assert!(!rules.is_empty(), "Expected at least one rule");
3511
3512 let help_rule = rules.iter().find(|r| r.targets().any(|t| t == "help"));
3514 assert!(help_rule.is_some(), "Expected to find help rule");
3515
3516 let recipes = help_rule.unwrap().recipes().collect::<Vec<_>>();
3518 assert!(
3519 !recipes.is_empty(),
3520 "Expected at least one recipe line in help rule"
3521 );
3522 assert!(
3523 recipes.iter().any(|r| r.contains("Available targets")),
3524 "Expected to find 'Available targets' in recipes"
3525 );
3526 }
3527
3528 #[test]
3529 fn test_indented_lines_in_conditionals() {
3530 let content = r#"
3531ifdef DEBUG
3532 CFLAGS += -g -DDEBUG
3533 # This is a comment inside conditional
3534 ifdef VERBOSE
3535 CFLAGS += -v
3536 endif
3537endif
3538"#;
3539 let mut buf = content.as_bytes();
3541 let makefile = Makefile::read_relaxed(&mut buf)
3542 .expect("Failed to parse indented lines in conditionals");
3543
3544 let code = makefile.code();
3546 assert!(code.contains("ifdef DEBUG"));
3547 assert!(code.contains("ifdef VERBOSE"));
3548 assert!(code.contains("endif"));
3549 }
3550
3551 #[test]
3552 fn test_recipe_with_colon() {
3553 let content = r#"
3554build:
3555 @echo "Building at: $(shell date)"
3556 gcc -o program main.c
3557"#;
3558 let parsed = parse(content);
3559 assert!(
3560 parsed.errors.is_empty(),
3561 "Failed to parse recipe with colon: {:?}",
3562 parsed.errors
3563 );
3564 }
3565
3566 #[test]
3567 #[ignore]
3568 fn test_double_colon_rules() {
3569 let content = r#"
3572%.o :: %.c
3573 $(CC) -c $< -o $@
3574
3575# Double colon allows multiple rules for same target
3576all:: prerequisite1
3577 @echo "First rule for all"
3578
3579all:: prerequisite2
3580 @echo "Second rule for all"
3581"#;
3582 let mut buf = content.as_bytes();
3583 let makefile =
3584 Makefile::read_relaxed(&mut buf).expect("Failed to parse double colon rules");
3585
3586 let rules = makefile.rules().collect::<Vec<_>>();
3588 assert!(!rules.is_empty(), "Expected at least one rule");
3589
3590 let all_rules = rules
3592 .iter()
3593 .filter(|r| r.targets().any(|t| t.contains("all")));
3594 assert!(
3595 all_rules.count() > 0,
3596 "Expected to find at least one rule containing 'all'"
3597 );
3598 }
3599
3600 #[test]
3601 fn test_elif_directive() {
3602 let content = r#"
3603ifeq ($(OS),Windows_NT)
3604 TARGET = windows
3605elif ifeq ($(OS),Darwin)
3606 TARGET = macos
3607elif ifeq ($(OS),Linux)
3608 TARGET = linux
3609else
3610 TARGET = unknown
3611endif
3612"#;
3613 let mut buf = content.as_bytes();
3615 let _makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse elif directive");
3616
3617 }
3620
3621 #[test]
3622 fn test_ambiguous_assignment_vs_rule() {
3623 const VAR_ASSIGNMENT: &str = "VARIABLE = value\n";
3625
3626 let mut buf = std::io::Cursor::new(VAR_ASSIGNMENT);
3627 let makefile =
3628 Makefile::read_relaxed(&mut buf).expect("Failed to parse variable assignment");
3629
3630 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3631 let rules = makefile.rules().collect::<Vec<_>>();
3632
3633 assert_eq!(vars.len(), 1, "Expected 1 variable, found {}", vars.len());
3634 assert_eq!(rules.len(), 0, "Expected 0 rules, found {}", rules.len());
3635
3636 assert_eq!(vars[0].name(), Some("VARIABLE".to_string()));
3637
3638 const SIMPLE_RULE: &str = "target: dependency\n";
3640
3641 let mut buf = std::io::Cursor::new(SIMPLE_RULE);
3642 let makefile = Makefile::read_relaxed(&mut buf).expect("Failed to parse simple rule");
3643
3644 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3645 let rules = makefile.rules().collect::<Vec<_>>();
3646
3647 assert_eq!(vars.len(), 0, "Expected 0 variables, found {}", vars.len());
3648 assert_eq!(rules.len(), 1, "Expected 1 rule, found {}", rules.len());
3649
3650 let rule = &rules[0];
3651 assert_eq!(rule.targets().collect::<Vec<_>>(), vec!["target"]);
3652 }
3653
3654 #[test]
3655 fn test_nested_conditionals() {
3656 let content = r#"
3657ifdef RELEASE
3658 CFLAGS += -O3
3659 ifndef DEBUG
3660 ifneq ($(ARCH),arm)
3661 CFLAGS += -march=native
3662 else
3663 CFLAGS += -mcpu=cortex-a72
3664 endif
3665 endif
3666endif
3667"#;
3668 let mut buf = content.as_bytes();
3670 let makefile =
3671 Makefile::read_relaxed(&mut buf).expect("Failed to parse nested conditionals");
3672
3673 let code = makefile.code();
3675 assert!(code.contains("ifdef RELEASE"));
3676 assert!(code.contains("ifndef DEBUG"));
3677 assert!(code.contains("ifneq"));
3678 }
3679
3680 #[test]
3681 fn test_space_indented_recipes() {
3682 let content = r#"
3685build:
3686 @echo "Building with spaces instead of tabs"
3687 gcc -o program main.c
3688"#;
3689 let mut buf = content.as_bytes();
3691 let makefile =
3692 Makefile::read_relaxed(&mut buf).expect("Failed to parse space-indented recipes");
3693
3694 let rules = makefile.rules().collect::<Vec<_>>();
3696 assert!(!rules.is_empty(), "Expected at least one rule");
3697
3698 let build_rule = rules.iter().find(|r| r.targets().any(|t| t == "build"));
3700 assert!(build_rule.is_some(), "Expected to find build rule");
3701 }
3702
3703 #[test]
3704 fn test_complex_variable_functions() {
3705 let content = r#"
3706FILES := $(shell find . -name "*.c")
3707OBJS := $(patsubst %.c,%.o,$(FILES))
3708NAME := $(if $(PROGRAM),$(PROGRAM),a.out)
3709HEADERS := ${wildcard *.h}
3710"#;
3711 let parsed = parse(content);
3712 assert!(
3713 parsed.errors.is_empty(),
3714 "Failed to parse complex variable functions: {:?}",
3715 parsed.errors
3716 );
3717 }
3718
3719 #[test]
3720 fn test_nested_variable_expansions() {
3721 let content = r#"
3722VERSION = 1.0
3723PACKAGE = myapp
3724TARBALL = $(PACKAGE)-$(VERSION).tar.gz
3725INSTALL_PATH = $(shell echo $(PREFIX) | sed 's/\/$//')
3726"#;
3727 let parsed = parse(content);
3728 assert!(
3729 parsed.errors.is_empty(),
3730 "Failed to parse nested variable expansions: {:?}",
3731 parsed.errors
3732 );
3733 }
3734
3735 #[test]
3736 fn test_special_directives() {
3737 let content = r#"
3738# Special makefile directives
3739.PHONY: all clean
3740.SUFFIXES: .c .o
3741.DEFAULT: all
3742
3743# Variable definition and export directive
3744export PATH := /usr/bin:/bin
3745"#;
3746 let mut buf = content.as_bytes();
3748 let makefile =
3749 Makefile::read_relaxed(&mut buf).expect("Failed to parse special directives");
3750
3751 let rules = makefile.rules().collect::<Vec<_>>();
3753
3754 let phony_rule = rules
3756 .iter()
3757 .find(|r| r.targets().any(|t| t.contains(".PHONY")));
3758 assert!(phony_rule.is_some(), "Expected to find .PHONY rule");
3759
3760 let vars = makefile.variable_definitions().collect::<Vec<_>>();
3762 assert!(!vars.is_empty(), "Expected to find at least one variable");
3763 }
3764
3765 #[test]
3768 fn test_comprehensive_real_world_makefile() {
3769 let content = r#"
3771# Basic variable assignment
3772VERSION = 1.0.0
3773
3774# Phony target
3775.PHONY: all clean
3776
3777# Simple rule
3778all:
3779 echo "Building version $(VERSION)"
3780
3781# Another rule with dependencies
3782clean:
3783 rm -f *.o
3784"#;
3785
3786 let parsed = parse(content);
3788
3789 assert!(parsed.errors.is_empty(), "Expected no parsing errors");
3791
3792 let variables = parsed.root().variable_definitions().collect::<Vec<_>>();
3794 assert!(!variables.is_empty(), "Expected at least one variable");
3795 assert_eq!(
3796 variables[0].name(),
3797 Some("VERSION".to_string()),
3798 "Expected VERSION variable"
3799 );
3800
3801 let rules = parsed.root().rules().collect::<Vec<_>>();
3803 assert!(!rules.is_empty(), "Expected at least one rule");
3804
3805 let rule_targets: Vec<String> = rules
3807 .iter()
3808 .flat_map(|r| r.targets().collect::<Vec<_>>())
3809 .collect();
3810 assert!(
3811 rule_targets.contains(&".PHONY".to_string()),
3812 "Expected .PHONY rule"
3813 );
3814 assert!(
3815 rule_targets.contains(&"all".to_string()),
3816 "Expected 'all' rule"
3817 );
3818 assert!(
3819 rule_targets.contains(&"clean".to_string()),
3820 "Expected 'clean' rule"
3821 );
3822 }
3823
3824 #[test]
3825 fn test_indented_help_text_outside_rules() {
3826 let content = r#"
3828# Targets with help text
3829help:
3830 @echo "Available targets:"
3831 @echo " build build the project"
3832 @echo " test run tests"
3833 @echo " clean clean build artifacts"
3834
3835# Another target
3836clean:
3837 rm -rf build/
3838"#;
3839
3840 let parsed = parse(content);
3842
3843 assert!(
3845 parsed.errors.is_empty(),
3846 "Failed to parse indented help text"
3847 );
3848
3849 let rules = parsed.root().rules().collect::<Vec<_>>();
3851 assert_eq!(rules.len(), 2, "Expected to find two rules");
3852
3853 let help_rule = rules
3855 .iter()
3856 .find(|r| r.targets().any(|t| t == "help"))
3857 .expect("Expected to find help rule");
3858
3859 let clean_rule = rules
3860 .iter()
3861 .find(|r| r.targets().any(|t| t == "clean"))
3862 .expect("Expected to find clean rule");
3863
3864 let help_recipes = help_rule.recipes().collect::<Vec<_>>();
3866 assert!(
3867 !help_recipes.is_empty(),
3868 "Help rule should have recipe lines"
3869 );
3870 assert!(
3871 help_recipes
3872 .iter()
3873 .any(|line| line.contains("Available targets")),
3874 "Help recipes should include 'Available targets' line"
3875 );
3876
3877 let clean_recipes = clean_rule.recipes().collect::<Vec<_>>();
3879 assert!(
3880 !clean_recipes.is_empty(),
3881 "Clean rule should have recipe lines"
3882 );
3883 assert!(
3884 clean_recipes.iter().any(|line| line.contains("rm -rf")),
3885 "Clean recipes should include 'rm -rf' command"
3886 );
3887 }
3888
3889 #[test]
3890 fn test_makefile1_phony_pattern() {
3891 let content = "#line 2145\n.PHONY: $(PHONY)\n";
3893
3894 let result = parse(content);
3896
3897 assert!(
3899 result.errors.is_empty(),
3900 "Failed to parse .PHONY: $(PHONY) pattern"
3901 );
3902
3903 let rules = result.root().rules().collect::<Vec<_>>();
3905 assert_eq!(rules.len(), 1, "Expected 1 rule");
3906 assert_eq!(
3907 rules[0].targets().next().unwrap(),
3908 ".PHONY",
3909 "Expected .PHONY rule"
3910 );
3911
3912 let prereqs = rules[0].prerequisites().collect::<Vec<_>>();
3914 assert_eq!(prereqs.len(), 1, "Expected 1 prerequisite");
3915 assert_eq!(prereqs[0], "$(PHONY)", "Expected $(PHONY) prerequisite");
3916 }
3917
3918 #[test]
3919 fn test_skip_until_newline_behavior() {
3920 let input = "text without newline";
3922 let parsed = parse(input);
3923 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
3925
3926 let input_with_newline = "text\nafter newline";
3927 let parsed2 = parse(input_with_newline);
3928 assert!(parsed2.errors.is_empty() || !parsed2.errors.is_empty());
3929 }
3930
3931 #[test]
3932 fn test_error_with_indent_token() {
3933 let input = "\tinvalid indented line";
3935 let parsed = parse(input);
3936 assert!(!parsed.errors.is_empty());
3938
3939 let error_msg = &parsed.errors[0].message;
3940 assert!(error_msg.contains("indented") || error_msg.contains("part of a rule"));
3941 }
3942
3943 #[test]
3944 fn test_conditional_token_handling() {
3945 let input = r#"
3947ifndef VAR
3948 CFLAGS = -DTEST
3949endif
3950"#;
3951 let parsed = parse(input);
3952 let makefile = parsed.root();
3954 let _vars = makefile.variable_definitions().collect::<Vec<_>>();
3955 let nested = r#"
3959ifdef DEBUG
3960 ifndef RELEASE
3961 CFLAGS = -g
3962 endif
3963endif
3964"#;
3965 let parsed_nested = parse(nested);
3966 let _makefile = parsed_nested.root();
3968 }
3969
3970 #[test]
3971 fn test_include_vs_conditional_logic() {
3972 let input = r#"
3974include file.mk
3975ifdef VAR
3976 VALUE = 1
3977endif
3978"#;
3979 let parsed = parse(input);
3980 let makefile = parsed.root();
3982 let includes = makefile.includes().collect::<Vec<_>>();
3983 assert!(includes.len() >= 1 || parsed.errors.len() > 0);
3985
3986 let optional_include = r#"
3988-include optional.mk
3989ifndef VAR
3990 VALUE = default
3991endif
3992"#;
3993 let parsed2 = parse(optional_include);
3994 let _makefile = parsed2.root();
3996 }
3997
3998 #[test]
3999 fn test_balanced_parens_counting() {
4000 let input = r#"
4002VAR = $(call func,$(nested,arg),extra)
4003COMPLEX = $(if $(condition),$(then_val),$(else_val))
4004"#;
4005 let parsed = parse(input);
4006 assert!(parsed.errors.is_empty());
4007
4008 let makefile = parsed.root();
4009 let vars = makefile.variable_definitions().collect::<Vec<_>>();
4010 assert_eq!(vars.len(), 2);
4011 }
4012
4013 #[test]
4014 fn test_documentation_lookahead() {
4015 let input = r#"
4017# Documentation comment
4018help:
4019 @echo "Usage instructions"
4020 @echo "More help text"
4021"#;
4022 let parsed = parse(input);
4023 assert!(parsed.errors.is_empty());
4024
4025 let makefile = parsed.root();
4026 let rules = makefile.rules().collect::<Vec<_>>();
4027 assert_eq!(rules.len(), 1);
4028 assert_eq!(rules[0].targets().next().unwrap(), "help");
4029 }
4030
4031 #[test]
4032 fn test_edge_case_empty_input() {
4033 let parsed = parse("");
4035 assert!(parsed.errors.is_empty());
4036
4037 let parsed2 = parse(" \n \n");
4039 let _makefile = parsed2.root();
4042 }
4043
4044 #[test]
4045 fn test_malformed_conditional_recovery() {
4046 let input = r#"
4048ifdef
4049 # Missing condition variable
4050endif
4051"#;
4052 let parsed = parse(input);
4053 assert!(parsed.errors.is_empty() || !parsed.errors.is_empty());
4056 }
4057
4058 #[test]
4059 fn test_replace_rule() {
4060 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4061 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4062
4063 makefile.replace_rule(0, new_rule).unwrap();
4064
4065 let targets: Vec<_> = makefile
4066 .rules()
4067 .flat_map(|r| r.targets().collect::<Vec<_>>())
4068 .collect();
4069 assert_eq!(targets, vec!["new_rule", "rule2"]);
4070
4071 let recipes: Vec<_> = makefile.rules().next().unwrap().recipes().collect();
4072 assert_eq!(recipes, vec!["new_command"]);
4073 }
4074
4075 #[test]
4076 fn test_replace_rule_out_of_bounds() {
4077 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4078 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4079
4080 let result = makefile.replace_rule(5, new_rule);
4081 assert!(result.is_err());
4082 }
4083
4084 #[test]
4085 fn test_remove_rule() {
4086 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\nrule3:\n\tcommand3\n"
4087 .parse()
4088 .unwrap();
4089
4090 let removed = makefile.remove_rule(1).unwrap();
4091 assert_eq!(removed.targets().collect::<Vec<_>>(), vec!["rule2"]);
4092
4093 let remaining_targets: Vec<_> = makefile
4094 .rules()
4095 .flat_map(|r| r.targets().collect::<Vec<_>>())
4096 .collect();
4097 assert_eq!(remaining_targets, vec!["rule1", "rule3"]);
4098 assert_eq!(makefile.rules().count(), 2);
4099 }
4100
4101 #[test]
4102 fn test_remove_rule_out_of_bounds() {
4103 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4104
4105 let result = makefile.remove_rule(5);
4106 assert!(result.is_err());
4107 }
4108
4109 #[test]
4110 fn test_insert_rule() {
4111 let mut makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4112 let new_rule: Rule = "inserted_rule:\n\tinserted_command\n".parse().unwrap();
4113
4114 makefile.insert_rule(1, new_rule).unwrap();
4115
4116 let targets: Vec<_> = makefile
4117 .rules()
4118 .flat_map(|r| r.targets().collect::<Vec<_>>())
4119 .collect();
4120 assert_eq!(targets, vec!["rule1", "inserted_rule", "rule2"]);
4121 assert_eq!(makefile.rules().count(), 3);
4122 }
4123
4124 #[test]
4125 fn test_insert_rule_at_end() {
4126 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4127 let new_rule: Rule = "end_rule:\n\tend_command\n".parse().unwrap();
4128
4129 makefile.insert_rule(1, new_rule).unwrap();
4130
4131 let targets: Vec<_> = makefile
4132 .rules()
4133 .flat_map(|r| r.targets().collect::<Vec<_>>())
4134 .collect();
4135 assert_eq!(targets, vec!["rule1", "end_rule"]);
4136 }
4137
4138 #[test]
4139 fn test_insert_rule_out_of_bounds() {
4140 let mut makefile: Makefile = "rule1:\n\tcommand1\n".parse().unwrap();
4141 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4142
4143 let result = makefile.insert_rule(5, new_rule);
4144 assert!(result.is_err());
4145 }
4146
4147 #[test]
4148 fn test_remove_command() {
4149 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4150 .parse()
4151 .unwrap();
4152
4153 rule.remove_command(1);
4154 let recipes: Vec<_> = rule.recipes().collect();
4155 assert_eq!(recipes, vec!["command1", "command3"]);
4156 assert_eq!(rule.recipe_count(), 2);
4157 }
4158
4159 #[test]
4160 fn test_remove_command_out_of_bounds() {
4161 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4162
4163 let result = rule.remove_command(5);
4164 assert!(!result);
4165 }
4166
4167 #[test]
4168 fn test_insert_command() {
4169 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand3\n".parse().unwrap();
4170
4171 rule.insert_command(1, "command2");
4172 let recipes: Vec<_> = rule.recipes().collect();
4173 assert_eq!(recipes, vec!["command1", "command2", "command3"]);
4174 }
4175
4176 #[test]
4177 fn test_insert_command_at_end() {
4178 let mut rule: Rule = "rule:\n\tcommand1\n".parse().unwrap();
4179
4180 rule.insert_command(1, "command2");
4181 let recipes: Vec<_> = rule.recipes().collect();
4182 assert_eq!(recipes, vec!["command1", "command2"]);
4183 }
4184
4185 #[test]
4186 fn test_insert_command_in_empty_rule() {
4187 let mut rule: Rule = "rule:\n".parse().unwrap();
4188
4189 rule.insert_command(0, "new_command");
4190 let recipes: Vec<_> = rule.recipes().collect();
4191 assert_eq!(recipes, vec!["new_command"]);
4192 }
4193
4194 #[test]
4195 fn test_recipe_count() {
4196 let rule1: Rule = "rule:\n".parse().unwrap();
4197 assert_eq!(rule1.recipe_count(), 0);
4198
4199 let rule2: Rule = "rule:\n\tcommand1\n\tcommand2\n".parse().unwrap();
4200 assert_eq!(rule2.recipe_count(), 2);
4201 }
4202
4203 #[test]
4204 fn test_clear_commands() {
4205 let mut rule: Rule = "rule:\n\tcommand1\n\tcommand2\n\tcommand3\n"
4206 .parse()
4207 .unwrap();
4208
4209 rule.clear_commands();
4210 assert_eq!(rule.recipe_count(), 0);
4211
4212 let recipes: Vec<_> = rule.recipes().collect();
4213 assert_eq!(recipes, Vec::<String>::new());
4214
4215 let targets: Vec<_> = rule.targets().collect();
4217 assert_eq!(targets, vec!["rule"]);
4218 }
4219
4220 #[test]
4221 fn test_clear_commands_empty_rule() {
4222 let mut rule: Rule = "rule:\n".parse().unwrap();
4223
4224 rule.clear_commands();
4225 assert_eq!(rule.recipe_count(), 0);
4226
4227 let targets: Vec<_> = rule.targets().collect();
4228 assert_eq!(targets, vec!["rule"]);
4229 }
4230
4231 #[test]
4232 fn test_rule_manipulation_preserves_structure() {
4233 let input = r#"# Comment
4235VAR = value
4236
4237rule1:
4238 command1
4239
4240# Another comment
4241rule2:
4242 command2
4243
4244VAR2 = value2
4245"#;
4246
4247 let mut makefile: Makefile = input.parse().unwrap();
4248 let new_rule: Rule = "new_rule:\n\tnew_command\n".parse().unwrap();
4249
4250 makefile.insert_rule(1, new_rule).unwrap();
4252
4253 let targets: Vec<_> = makefile
4255 .rules()
4256 .flat_map(|r| r.targets().collect::<Vec<_>>())
4257 .collect();
4258 assert_eq!(targets, vec!["rule1", "new_rule", "rule2"]);
4259
4260 let vars: Vec<_> = makefile.variable_definitions().collect();
4262 assert_eq!(vars.len(), 2);
4263
4264 let output = makefile.code();
4266 assert!(output.contains("# Comment"));
4267 assert!(output.contains("VAR = value"));
4268 assert!(output.contains("# Another comment"));
4269 assert!(output.contains("VAR2 = value2"));
4270 }
4271
4272 #[test]
4273 fn test_replace_rule_with_multiple_targets() {
4274 let mut makefile: Makefile = "target1 target2: dep\n\tcommand\n".parse().unwrap();
4275 let new_rule: Rule = "new_target: new_dep\n\tnew_command\n".parse().unwrap();
4276
4277 makefile.replace_rule(0, new_rule).unwrap();
4278
4279 let targets: Vec<_> = makefile
4280 .rules()
4281 .flat_map(|r| r.targets().collect::<Vec<_>>())
4282 .collect();
4283 assert_eq!(targets, vec!["new_target"]);
4284 }
4285
4286 #[test]
4287 fn test_empty_makefile_operations() {
4288 let mut makefile = Makefile::new();
4289
4290 assert!(makefile
4292 .replace_rule(0, "rule:\n\tcommand\n".parse().unwrap())
4293 .is_err());
4294 assert!(makefile.remove_rule(0).is_err());
4295
4296 let new_rule: Rule = "first_rule:\n\tcommand\n".parse().unwrap();
4298 makefile.insert_rule(0, new_rule).unwrap();
4299 assert_eq!(makefile.rules().count(), 1);
4300 }
4301
4302 #[test]
4303 fn test_command_operations_preserve_indentation() {
4304 let mut rule: Rule = "rule:\n\t\tdeep_indent\n\tshallow_indent\n"
4305 .parse()
4306 .unwrap();
4307
4308 rule.insert_command(1, "middle_command");
4309 let recipes: Vec<_> = rule.recipes().collect();
4310 assert_eq!(
4311 recipes,
4312 vec!["\tdeep_indent", "middle_command", "shallow_indent"]
4313 );
4314 }
4315
4316 #[test]
4317 fn test_rule_operations_with_variables_and_includes() {
4318 let input = r#"VAR1 = value1
4319include common.mk
4320
4321rule1:
4322 command1
4323
4324VAR2 = value2
4325include other.mk
4326
4327rule2:
4328 command2
4329"#;
4330
4331 let mut makefile: Makefile = input.parse().unwrap();
4332
4333 makefile.remove_rule(0).unwrap();
4335
4336 let output = makefile.code();
4338 assert!(output.contains("VAR1 = value1"));
4339 assert!(output.contains("include common.mk"));
4340 assert!(output.contains("VAR2 = value2"));
4341 assert!(output.contains("include other.mk"));
4342
4343 assert_eq!(makefile.rules().count(), 1);
4345 let remaining_targets: Vec<_> = makefile
4346 .rules()
4347 .flat_map(|r| r.targets().collect::<Vec<_>>())
4348 .collect();
4349 assert_eq!(remaining_targets, vec!["rule2"]);
4350 }
4351
4352 #[test]
4353 fn test_command_manipulation_edge_cases() {
4354 let mut empty_rule: Rule = "empty:\n".parse().unwrap();
4356 assert_eq!(empty_rule.recipe_count(), 0);
4357
4358 empty_rule.insert_command(0, "first_command");
4359 assert_eq!(empty_rule.recipe_count(), 1);
4360
4361 let mut empty_rule2: Rule = "empty:\n".parse().unwrap();
4363 empty_rule2.clear_commands();
4364 assert_eq!(empty_rule2.recipe_count(), 0);
4365 }
4366
4367 #[test]
4368 fn test_archive_member_parsing() {
4369 let input = "libfoo.a(bar.o): bar.c\n\tgcc -c bar.c -o bar.o\n\tar r libfoo.a bar.o\n";
4371 let parsed = parse(input);
4372 assert!(
4373 parsed.errors.is_empty(),
4374 "Should parse archive member without errors"
4375 );
4376
4377 let makefile = parsed.root();
4378 let rules: Vec<_> = makefile.rules().collect();
4379 assert_eq!(rules.len(), 1);
4380
4381 let target_text = rules[0].targets().next().unwrap();
4383 assert_eq!(target_text, "libfoo.a(bar.o)");
4384 }
4385
4386 #[test]
4387 fn test_archive_member_multiple_members() {
4388 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";
4390 let parsed = parse(input);
4391 assert!(
4392 parsed.errors.is_empty(),
4393 "Should parse multiple archive members"
4394 );
4395
4396 let makefile = parsed.root();
4397 let rules: Vec<_> = makefile.rules().collect();
4398 assert_eq!(rules.len(), 1);
4399 }
4400
4401 #[test]
4402 fn test_archive_member_in_dependencies() {
4403 let input =
4405 "program: main.o libfoo.a(bar.o) libfoo.a(baz.o)\n\tgcc -o program main.o libfoo.a\n";
4406 let parsed = parse(input);
4407 assert!(
4408 parsed.errors.is_empty(),
4409 "Should parse archive members in dependencies"
4410 );
4411
4412 let makefile = parsed.root();
4413 let rules: Vec<_> = makefile.rules().collect();
4414 assert_eq!(rules.len(), 1);
4415 }
4416
4417 #[test]
4418 fn test_archive_member_with_variables() {
4419 let input = "$(LIB)($(OBJ)): $(SRC)\n\t$(CC) -c $(SRC)\n\t$(AR) r $(LIB) $(OBJ)\n";
4421 let parsed = parse(input);
4422 assert!(
4424 parsed.errors.is_empty(),
4425 "Should parse archive members with variables"
4426 );
4427 }
4428
4429 #[test]
4430 fn test_archive_member_ast_access() {
4431 let input = "libtest.a(foo.o bar.o): foo.c bar.c\n\tgcc -c foo.c bar.c\n";
4433 let parsed = parse(input);
4434 let makefile = parsed.root();
4435
4436 let archive_member_count = makefile
4438 .syntax()
4439 .descendants()
4440 .filter(|n| n.kind() == ARCHIVE_MEMBERS)
4441 .count();
4442
4443 assert!(
4444 archive_member_count > 0,
4445 "Should find ARCHIVE_MEMBERS nodes in AST"
4446 );
4447 }
4448
4449 #[test]
4450 fn test_large_makefile_performance() {
4451 let mut makefile = Makefile::new();
4453
4454 for i in 0..100 {
4456 let rule_name = format!("rule{}", i);
4457 let _rule = makefile
4458 .add_rule(&rule_name)
4459 .push_command(&format!("command{}", i));
4460 }
4461
4462 assert_eq!(makefile.rules().count(), 100);
4463
4464 let new_rule: Rule = "middle_rule:\n\tmiddle_command\n".parse().unwrap();
4466 makefile.replace_rule(50, new_rule).unwrap();
4467
4468 let rule_50_targets: Vec<_> = makefile.rules().nth(50).unwrap().targets().collect();
4470 assert_eq!(rule_50_targets, vec!["middle_rule"]);
4471
4472 assert_eq!(makefile.rules().count(), 100); }
4474
4475 #[test]
4476 fn test_complex_recipe_manipulation() {
4477 let mut complex_rule: Rule = r#"complex:
4478 @echo "Starting build"
4479 $(CC) $(CFLAGS) -o $@ $<
4480 @echo "Build complete"
4481 chmod +x $@
4482"#
4483 .parse()
4484 .unwrap();
4485
4486 assert_eq!(complex_rule.recipe_count(), 4);
4487
4488 complex_rule.remove_command(0); complex_rule.remove_command(1); let final_recipes: Vec<_> = complex_rule.recipes().collect();
4493 assert_eq!(final_recipes.len(), 2);
4494 assert!(final_recipes[0].contains("$(CC)"));
4495 assert!(final_recipes[1].contains("chmod"));
4496 }
4497
4498 #[test]
4499 fn test_variable_definition_remove() {
4500 let makefile: Makefile = r#"VAR1 = value1
4501VAR2 = value2
4502VAR3 = value3
4503"#
4504 .parse()
4505 .unwrap();
4506
4507 assert_eq!(makefile.variable_definitions().count(), 3);
4509
4510 let mut var2 = makefile
4512 .variable_definitions()
4513 .nth(1)
4514 .expect("Should have second variable");
4515 assert_eq!(var2.name(), Some("VAR2".to_string()));
4516 var2.remove();
4517
4518 assert_eq!(makefile.variable_definitions().count(), 2);
4520 let var_names: Vec<_> = makefile
4521 .variable_definitions()
4522 .filter_map(|v| v.name())
4523 .collect();
4524 assert_eq!(var_names, vec!["VAR1", "VAR3"]);
4525 }
4526
4527 #[test]
4528 fn test_variable_definition_set_value() {
4529 let makefile: Makefile = "VAR = old_value\n".parse().unwrap();
4530
4531 let mut var = makefile
4532 .variable_definitions()
4533 .next()
4534 .expect("Should have variable");
4535 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4536
4537 var.set_value("new_value");
4539
4540 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4542 assert!(makefile.code().contains("VAR = new_value"));
4543 }
4544
4545 #[test]
4546 fn test_variable_definition_set_value_preserves_format() {
4547 let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
4548
4549 let mut var = makefile
4550 .variable_definitions()
4551 .next()
4552 .expect("Should have variable");
4553 assert_eq!(var.raw_value(), Some("old_value".to_string()));
4554
4555 var.set_value("new_value");
4557
4558 assert_eq!(var.raw_value(), Some("new_value".to_string()));
4560 let code = makefile.code();
4561 assert!(code.contains("export"), "Should preserve export prefix");
4562 assert!(code.contains(":="), "Should preserve := operator");
4563 assert!(code.contains("new_value"), "Should have new value");
4564 }
4565
4566 #[test]
4567 fn test_makefile_find_variable() {
4568 let makefile: Makefile = r#"VAR1 = value1
4569VAR2 = value2
4570VAR3 = value3
4571"#
4572 .parse()
4573 .unwrap();
4574
4575 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4577 assert_eq!(vars.len(), 1);
4578 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4579 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4580
4581 assert_eq!(makefile.find_variable("NONEXISTENT").count(), 0);
4583 }
4584
4585 #[test]
4586 fn test_makefile_find_variable_with_export() {
4587 let makefile: Makefile = r#"VAR1 = value1
4588export VAR2 := value2
4589VAR3 = value3
4590"#
4591 .parse()
4592 .unwrap();
4593
4594 let vars: Vec<_> = makefile.find_variable("VAR2").collect();
4596 assert_eq!(vars.len(), 1);
4597 assert_eq!(vars[0].name(), Some("VAR2".to_string()));
4598 assert_eq!(vars[0].raw_value(), Some("value2".to_string()));
4599 }
4600
4601 #[test]
4602 fn test_makefile_find_variable_multiple() {
4603 let makefile: Makefile = r#"VAR1 = value1
4604VAR1 = value2
4605VAR2 = other
4606VAR1 = value3
4607"#
4608 .parse()
4609 .unwrap();
4610
4611 let vars: Vec<_> = makefile.find_variable("VAR1").collect();
4613 assert_eq!(vars.len(), 3);
4614 assert_eq!(vars[0].raw_value(), Some("value1".to_string()));
4615 assert_eq!(vars[1].raw_value(), Some("value2".to_string()));
4616 assert_eq!(vars[2].raw_value(), Some("value3".to_string()));
4617
4618 let var2s: Vec<_> = makefile.find_variable("VAR2").collect();
4620 assert_eq!(var2s.len(), 1);
4621 assert_eq!(var2s[0].raw_value(), Some("other".to_string()));
4622 }
4623
4624 #[test]
4625 fn test_variable_remove_and_find() {
4626 let makefile: Makefile = r#"VAR1 = value1
4627VAR2 = value2
4628VAR3 = value3
4629"#
4630 .parse()
4631 .unwrap();
4632
4633 let mut var2 = makefile
4635 .find_variable("VAR2")
4636 .next()
4637 .expect("Should find VAR2");
4638 var2.remove();
4639
4640 assert_eq!(makefile.find_variable("VAR2").count(), 0);
4642
4643 assert_eq!(makefile.find_variable("VAR1").count(), 1);
4645 assert_eq!(makefile.find_variable("VAR3").count(), 1);
4646 }
4647
4648 #[test]
4649 fn test_rule_add_prerequisite() {
4650 let mut rule: Rule = "target: dep1\n".parse().unwrap();
4651 rule.add_prerequisite("dep2").unwrap();
4652 assert_eq!(
4653 rule.prerequisites().collect::<Vec<_>>(),
4654 vec!["dep1", "dep2"]
4655 );
4656 }
4657
4658 #[test]
4659 fn test_rule_remove_prerequisite() {
4660 let mut rule: Rule = "target: dep1 dep2 dep3\n".parse().unwrap();
4661 assert!(rule.remove_prerequisite("dep2").unwrap());
4662 assert_eq!(
4663 rule.prerequisites().collect::<Vec<_>>(),
4664 vec!["dep1", "dep3"]
4665 );
4666 assert!(!rule.remove_prerequisite("nonexistent").unwrap());
4667 }
4668
4669 #[test]
4670 fn test_rule_set_prerequisites() {
4671 let mut rule: Rule = "target: old_dep\n".parse().unwrap();
4672 rule.set_prerequisites(vec!["new_dep1", "new_dep2"])
4673 .unwrap();
4674 assert_eq!(
4675 rule.prerequisites().collect::<Vec<_>>(),
4676 vec!["new_dep1", "new_dep2"]
4677 );
4678 }
4679
4680 #[test]
4681 fn test_rule_set_prerequisites_empty() {
4682 let mut rule: Rule = "target: dep1 dep2\n".parse().unwrap();
4683 rule.set_prerequisites(vec![]).unwrap();
4684 assert_eq!(rule.prerequisites().collect::<Vec<_>>().len(), 0);
4685 }
4686
4687 #[test]
4688 fn test_rule_remove() {
4689 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4690 let rule = makefile.find_rule_by_target("rule1").unwrap();
4691 rule.remove().unwrap();
4692 assert_eq!(makefile.rules().count(), 1);
4693 assert!(makefile.find_rule_by_target("rule1").is_none());
4694 assert!(makefile.find_rule_by_target("rule2").is_some());
4695 }
4696
4697 #[test]
4698 fn test_makefile_find_rule_by_target() {
4699 let makefile: Makefile = "rule1:\n\tcommand1\nrule2:\n\tcommand2\n".parse().unwrap();
4700 let rule = makefile.find_rule_by_target("rule2");
4701 assert!(rule.is_some());
4702 assert_eq!(rule.unwrap().targets().collect::<Vec<_>>(), vec!["rule2"]);
4703 assert!(makefile.find_rule_by_target("nonexistent").is_none());
4704 }
4705
4706 #[test]
4707 fn test_makefile_find_rules_by_target() {
4708 let makefile: Makefile = "rule1:\n\tcommand1\nrule1:\n\tcommand2\nrule2:\n\tcommand3\n"
4709 .parse()
4710 .unwrap();
4711 assert_eq!(makefile.find_rules_by_target("rule1").count(), 2);
4712 assert_eq!(makefile.find_rules_by_target("rule2").count(), 1);
4713 assert_eq!(makefile.find_rules_by_target("nonexistent").count(), 0);
4714 }
4715
4716 #[test]
4717 fn test_makefile_add_phony_target() {
4718 let mut makefile = Makefile::new();
4719 makefile.add_phony_target("clean").unwrap();
4720 assert!(makefile.is_phony("clean"));
4721 assert_eq!(makefile.phony_targets().collect::<Vec<_>>(), vec!["clean"]);
4722 }
4723
4724 #[test]
4725 fn test_makefile_add_phony_target_existing() {
4726 let mut makefile: Makefile = ".PHONY: test\n".parse().unwrap();
4727 makefile.add_phony_target("clean").unwrap();
4728 assert!(makefile.is_phony("test"));
4729 assert!(makefile.is_phony("clean"));
4730 let targets: Vec<_> = makefile.phony_targets().collect();
4731 assert!(targets.contains(&"test".to_string()));
4732 assert!(targets.contains(&"clean".to_string()));
4733 }
4734
4735 #[test]
4736 fn test_makefile_remove_phony_target() {
4737 let mut makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4738 assert!(makefile.remove_phony_target("clean").unwrap());
4739 assert!(!makefile.is_phony("clean"));
4740 assert!(makefile.is_phony("test"));
4741 assert!(!makefile.remove_phony_target("nonexistent").unwrap());
4742 }
4743
4744 #[test]
4745 fn test_makefile_remove_phony_target_last() {
4746 let mut makefile: Makefile = ".PHONY: clean\n".parse().unwrap();
4747 assert!(makefile.remove_phony_target("clean").unwrap());
4748 assert!(!makefile.is_phony("clean"));
4749 assert!(makefile.find_rule_by_target(".PHONY").is_none());
4751 }
4752
4753 #[test]
4754 fn test_makefile_is_phony() {
4755 let makefile: Makefile = ".PHONY: clean test\n".parse().unwrap();
4756 assert!(makefile.is_phony("clean"));
4757 assert!(makefile.is_phony("test"));
4758 assert!(!makefile.is_phony("build"));
4759 }
4760
4761 #[test]
4762 fn test_makefile_phony_targets() {
4763 let makefile: Makefile = ".PHONY: clean test build\n".parse().unwrap();
4764 let phony_targets: Vec<_> = makefile.phony_targets().collect();
4765 assert_eq!(phony_targets, vec!["clean", "test", "build"]);
4766 }
4767
4768 #[test]
4769 fn test_makefile_phony_targets_empty() {
4770 let makefile = Makefile::new();
4771 assert_eq!(makefile.phony_targets().count(), 0);
4772 }
4773}