Coverage for lintro / parsers / oxlint / oxlint_parser.py: 81%

59 statements  

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

1"""Parser for Oxlint JSON output. 

2 

3Handles Oxlint JSON format output from --format json flag. 

4""" 

5 

6import json 

7from typing import Any 

8 

9from loguru import logger 

10 

11from lintro.parsers.oxlint.oxlint_issue import OxlintIssue 

12 

13 

14def parse_oxlint_output(output: str) -> list[OxlintIssue]: 

15 """Parse Oxlint JSON output into a list of OxlintIssue objects. 

16 

17 Args: 

18 output: The raw JSON output from Oxlint. 

19 

20 Returns: 

21 List of OxlintIssue objects. 

22 """ 

23 issues: list[OxlintIssue] = [] 

24 

25 if not output: 

26 return issues 

27 

28 try: 

29 # Oxlint JSON format is a single object with diagnostics array 

30 # Extract JSON from output (Oxlint may add extra text after JSON) 

31 json_start = output.find("{") 

32 json_end = output.rfind("}") + 1 

33 if json_start == -1 or json_end == 0: 

34 return issues 

35 json_content = output[json_start:json_end] 

36 oxlint_data: dict[str, Any] = json.loads(json_content) 

37 except json.JSONDecodeError as e: 

38 logger.debug(f"Failed to parse Oxlint JSON output: {e}") 

39 return issues 

40 except (ValueError, TypeError) as e: 

41 logger.debug(f"Error processing Oxlint output: {e}") 

42 return issues 

43 

44 if not isinstance(oxlint_data, dict): 

45 logger.debug("Oxlint output is not a dictionary") 

46 return issues 

47 

48 diagnostics = oxlint_data.get("diagnostics", []) 

49 if not isinstance(diagnostics, list): 

50 logger.debug("Oxlint diagnostics is not a list") 

51 return issues 

52 

53 for diagnostic in diagnostics: 

54 if not isinstance(diagnostic, dict): 

55 continue 

56 try: 

57 issue = _parse_diagnostic(diagnostic) 

58 if issue is not None: 

59 issues.append(issue) 

60 except (KeyError, TypeError, ValueError) as e: 

61 logger.debug(f"Failed to parse Oxlint diagnostic: {e}") 

62 continue 

63 

64 return issues 

65 

66 

67def _parse_diagnostic(diagnostic: dict[str, Any]) -> OxlintIssue | None: 

68 """Parse a single Oxlint diagnostic into an OxlintIssue. 

69 

70 Args: 

71 diagnostic: A single diagnostic dictionary from Oxlint output. 

72 

73 Returns: 

74 OxlintIssue if parsing succeeds, None otherwise. 

75 """ 

76 # Extract filename 

77 file_path = diagnostic.get("filename", "") 

78 if not file_path: 

79 return None 

80 

81 # Extract message and code 

82 message = diagnostic.get("message", "") 

83 code = diagnostic.get("code", "") 

84 severity = diagnostic.get("severity", "warning") 

85 help_text = diagnostic.get("help") 

86 

87 # Extract line and column from labels array 

88 # labels[0].span contains {offset, length, line, column} 

89 # Default to 1 for 1-based line/column numbering 

90 line = 1 

91 column = 1 

92 labels = diagnostic.get("labels", []) 

93 if isinstance(labels, list) and len(labels) > 0: 

94 first_label = labels[0] 

95 if isinstance(first_label, dict): 

96 span = first_label.get("span", {}) 

97 if isinstance(span, dict): 

98 line = span.get("line", 1) 

99 column = span.get("column", 1) 

100 

101 # Oxlint does not currently indicate fixable issues in JSON output 

102 # Default to False 

103 fixable = False 

104 

105 return OxlintIssue( 

106 file=file_path, 

107 line=line, 

108 column=column, 

109 message=message, 

110 code=code, 

111 severity=severity, 

112 fixable=fixable, 

113 help=help_text, 

114 )