Coverage for lintro / parsers / rustfmt / rustfmt_parser.py: 94%

34 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Parser for rustfmt output. 

2 

3Rustfmt with --check outputs diff-style format when files need formatting: 

4- "Diff in /path/to/file.rs at line N:" followed by diff content 

5- Or simply lists files that would be reformatted 

6 

7We parse this output to extract file paths and create RustfmtIssue objects. 

8""" 

9 

10from __future__ import annotations 

11 

12import re 

13 

14from loguru import logger 

15 

16from lintro.parsers.rustfmt.rustfmt_issue import RustfmtIssue 

17 

18# Pattern to match "Diff in <file>:<line>:" format (actual rustfmt output) 

19_DIFF_IN_RE = re.compile(r"^Diff in (?P<file>.+?):(?P<line>\d+):?$") 

20 

21# Pattern to match file paths that would be reformatted 

22# cargo fmt -- --check may output just the file path when using certain options 

23_FILE_PATH_RE = re.compile(r"^(?P<file>.+\.rs)$") 

24 

25 

26def parse_rustfmt_output(output: str | None) -> list[RustfmtIssue]: 

27 """Parse rustfmt output into issues. 

28 

29 Args: 

30 output: Raw stdout/stderr from rustfmt/cargo fmt --check. 

31 

32 Returns: 

33 List of parsed issues, one per file that needs formatting. 

34 """ 

35 if not output: 

36 return [] 

37 

38 issues: list[RustfmtIssue] = [] 

39 seen_files: set[str] = set() 

40 

41 try: 

42 for line in output.splitlines(): 

43 line = line.strip() 

44 if not line: 

45 continue 

46 

47 # Try to match "Diff in <file> at line <n>:" format 

48 m = _DIFF_IN_RE.match(line) 

49 if m: 

50 file_path = m.group("file") 

51 line_num = int(m.group("line")) 

52 

53 # Only add first occurrence per file to avoid duplicates 

54 if file_path not in seen_files: 

55 seen_files.add(file_path) 

56 issues.append( 

57 RustfmtIssue( 

58 file=file_path, 

59 line=line_num, 

60 column=0, 

61 message="File needs formatting", 

62 fixable=True, 

63 ), 

64 ) 

65 continue 

66 

67 # Try to match standalone file paths (some rustfmt output modes) 

68 m = _FILE_PATH_RE.match(line) 

69 if m: 

70 file_path = m.group("file") 

71 if file_path not in seen_files: 

72 seen_files.add(file_path) 

73 issues.append( 

74 RustfmtIssue( 

75 file=file_path, 

76 line=0, 

77 column=0, 

78 message="File needs formatting", 

79 fixable=True, 

80 ), 

81 ) 

82 continue 

83 

84 except (ValueError, TypeError, AttributeError) as e: 

85 logger.debug(f"Error parsing rustfmt output: {e}") 

86 

87 return issues