Coverage for lintro / parsers / vue_tsc / vue_tsc_parser.py: 93%

57 statements  

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

1"""Parser for vue-tsc output. 

2 

3Vue-tsc outputs in the same format as tsc (TypeScript compiler), so this 

4parser uses the same pattern matching logic. 

5""" 

6 

7from __future__ import annotations 

8 

9import re 

10 

11from loguru import logger 

12 

13from lintro.parsers.base_parser import strip_ansi_codes 

14from lintro.parsers.vue_tsc.vue_tsc_issue import VueTscIssue 

15 

16# Error codes that indicate missing dependencies rather than actual type errors 

17# These are the same as tsc since vue-tsc uses the same error codes 

18DEPENDENCY_ERROR_CODES: frozenset[str] = frozenset( 

19 { 

20 "TS2307", # Cannot find module 'X' or its corresponding type declarations 

21 "TS2688", # Cannot find type definition file for 'X' 

22 "TS7016", # Could not find a declaration file for module 'X' 

23 }, 

24) 

25 

26# Pattern for vue-tsc output (same as tsc with --pretty false): 

27# file.vue(line,col): error TS1234: message 

28# file.vue(line,col): warning TS1234: message 

29VUE_TSC_ISSUE_PATTERN = re.compile( 

30 r"^(?P<file>.+?)\((?P<line>\d+),(?P<column>\d+)\):\s*" 

31 r"(?P<severity>error|warning)\s+(?P<code>TS\d+):\s*" 

32 r"(?P<message>.+)$", 

33) 

34 

35# Patterns for extracting module names from dependency error messages 

36_MODULE_PATTERN = re.compile(r"Cannot find module ['\"]([^'\"]+)['\"]") 

37_TYPEDEF_PATTERN = re.compile(r"type definition file for ['\"]([^'\"]+)['\"]") 

38_DECL_PATTERN = re.compile(r"declaration file for module ['\"]([^'\"]+)['\"]") 

39 

40 

41def _parse_line(line: str) -> VueTscIssue | None: 

42 """Parse a single vue-tsc output line into a VueTscIssue. 

43 

44 Args: 

45 line: A single line of vue-tsc output. 

46 

47 Returns: 

48 A VueTscIssue instance or None if the line doesn't match. 

49 """ 

50 line = line.strip() 

51 if not line: 

52 return None 

53 

54 match = VUE_TSC_ISSUE_PATTERN.match(line) 

55 if not match: 

56 return None 

57 

58 try: 

59 file_path = match.group("file") 

60 line_num = int(match.group("line")) 

61 column = int(match.group("column")) 

62 severity = match.group("severity") 

63 code = match.group("code") 

64 message = match.group("message").strip() 

65 

66 # Normalize Windows paths to forward slashes 

67 file_path = file_path.replace("\\", "/") 

68 

69 return VueTscIssue( 

70 file=file_path, 

71 line=line_num, 

72 column=column, 

73 code=code, 

74 message=message, 

75 severity=severity, 

76 ) 

77 except (ValueError, AttributeError) as e: 

78 logger.debug(f"Failed to parse vue-tsc line: {e}") 

79 return None 

80 

81 

82def parse_vue_tsc_output(output: str) -> list[VueTscIssue]: 

83 """Parse vue-tsc output into VueTscIssue objects. 

84 

85 Args: 

86 output: Raw stdout emitted by vue-tsc with --pretty false. 

87 

88 Returns: 

89 A list of VueTscIssue instances parsed from the output. 

90 

91 Examples: 

92 >>> output = "src/App.vue(10,5): error TS2322: Type error." 

93 >>> issues = parse_vue_tsc_output(output) 

94 >>> len(issues) 

95 1 

96 >>> issues[0].code 

97 'TS2322' 

98 """ 

99 if not output or not output.strip(): 

100 return [] 

101 

102 # Strip ANSI codes for consistent parsing 

103 output = strip_ansi_codes(output) 

104 

105 issues: list[VueTscIssue] = [] 

106 for line in output.splitlines(): 

107 parsed = _parse_line(line) 

108 if parsed: 

109 issues.append(parsed) 

110 

111 return issues 

112 

113 

114def categorize_vue_tsc_issues( 

115 issues: list[VueTscIssue], 

116) -> tuple[list[VueTscIssue], list[VueTscIssue]]: 

117 """Categorize vue-tsc issues into type errors and dependency errors. 

118 

119 Separates actual type errors from errors caused by missing dependencies 

120 (e.g., when node_modules is not installed). 

121 

122 Args: 

123 issues: List of VueTscIssue objects to categorize. 

124 

125 Returns: 

126 A tuple of (type_errors, dependency_errors). 

127 """ 

128 type_errors: list[VueTscIssue] = [] 

129 dependency_errors: list[VueTscIssue] = [] 

130 

131 for issue in issues: 

132 if issue.code and issue.code in DEPENDENCY_ERROR_CODES: 

133 dependency_errors.append(issue) 

134 else: 

135 type_errors.append(issue) 

136 

137 return type_errors, dependency_errors 

138 

139 

140def extract_missing_modules(dependency_errors: list[VueTscIssue]) -> list[str]: 

141 """Extract module names from dependency error messages. 

142 

143 Args: 

144 dependency_errors: List of VueTscIssue objects with dependency errors. 

145 

146 Returns: 

147 List of unique module names that are missing. 

148 """ 

149 modules: set[str] = set() 

150 

151 for error in dependency_errors: 

152 message = error.message or "" 

153 

154 for pattern in (_MODULE_PATTERN, _TYPEDEF_PATTERN, _DECL_PATTERN): 

155 match = pattern.search(message) 

156 if match: 

157 modules.add(match.group(1)) 

158 break 

159 

160 return sorted(modules)