Coverage for C:\Repos\ekr-pylint\pylint\reporters\text.py: 39%

143 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html 

2# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE 

3# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt 

4 

5"""Plain text reporters:. 

6 

7:text: the default one grouping messages by module 

8:colorized: an ANSI colorized text reporter 

9""" 

10 

11from __future__ import annotations 

12 

13import os 

14import re 

15import sys 

16import warnings 

17from dataclasses import asdict, fields 

18from typing import TYPE_CHECKING, Dict, NamedTuple, Optional, TextIO, cast, overload 

19 

20from pylint.message import Message 

21from pylint.reporters import BaseReporter 

22from pylint.reporters.ureports.text_writer import TextWriter 

23from pylint.utils import _splitstrip 

24 

25if TYPE_CHECKING: 

26 from pylint.lint import PyLinter 

27 from pylint.reporters.ureports.nodes import Section 

28 

29 

30class MessageStyle(NamedTuple): 

31 """Styling of a message.""" 

32 

33 color: str | None 

34 """The color name (see `ANSI_COLORS` for available values) 

35 or the color number when 256 colors are available. 

36 """ 

37 style: tuple[str, ...] = () 

38 """Tuple of style strings (see `ANSI_COLORS` for available values).""" 

39 

40 

41ColorMappingDict = Dict[str, MessageStyle] 

42 

43TITLE_UNDERLINES = ["", "=", "-", "."] 

44 

45ANSI_PREFIX = "\033[" 

46ANSI_END = "m" 

47ANSI_RESET = "\033[0m" 

48ANSI_STYLES = { 

49 "reset": "0", 

50 "bold": "1", 

51 "italic": "3", 

52 "underline": "4", 

53 "blink": "5", 

54 "inverse": "7", 

55 "strike": "9", 

56} 

57ANSI_COLORS = { 

58 "reset": "0", 

59 "black": "30", 

60 "red": "31", 

61 "green": "32", 

62 "yellow": "33", 

63 "blue": "34", 

64 "magenta": "35", 

65 "cyan": "36", 

66 "white": "37", 

67} 

68 

69MESSAGE_FIELDS = {i.name for i in fields(Message)} 

70"""All fields of the Message class.""" 

71 

72 

73def _get_ansi_code(msg_style: MessageStyle) -> str: 

74 """Return ANSI escape code corresponding to color and style. 

75 

76 :param msg_style: the message style 

77 

78 :raise KeyError: if a nonexistent color or style identifier is given 

79 

80 :return: the built escape code 

81 """ 

82 ansi_code = [ANSI_STYLES[effect] for effect in msg_style.style] 

83 if msg_style.color: 

84 if msg_style.color.isdigit(): 

85 ansi_code.extend(["38", "5"]) 

86 ansi_code.append(msg_style.color) 

87 else: 

88 ansi_code.append(ANSI_COLORS[msg_style.color]) 

89 if ansi_code: 

90 return ANSI_PREFIX + ";".join(ansi_code) + ANSI_END 

91 return "" 

92 

93 

94@overload 

95def colorize_ansi( 

96 msg: str, 

97 msg_style: MessageStyle | None = ..., 

98) -> str: 

99 ... 

100 

101 

102@overload 

103def colorize_ansi( 

104 msg: str, 

105 msg_style: str | None = ..., 

106 style: str = ..., 

107 *, 

108 color: str | None = ..., 

109) -> str: 

110 # Remove for pylint 3.0 

111 ... 

112 

113 

114def colorize_ansi( 

115 msg: str, 

116 msg_style: MessageStyle | str | None = None, 

117 style: str = "", 

118 **kwargs: str | None, 

119) -> str: 

120 r"""colorize message by wrapping it with ANSI escape codes 

121 

122 :param msg: the message string to colorize 

123 

124 :param msg_style: the message style 

125 or color (for backwards compatibility): the color of the message style 

126 

127 :param style: the message's style elements, this will be deprecated 

128 

129 :param \**kwargs: used to accept `color` parameter while it is being deprecated 

130 

131 :return: the ANSI escaped string 

132 """ 

133 # TODO: 3.0: Remove deprecated typing and only accept MessageStyle as parameter 

134 if not isinstance(msg_style, MessageStyle): 

135 warnings.warn( 

136 "In pylint 3.0, the colorize_ansi function of Text reporters will only accept a MessageStyle parameter", 

137 DeprecationWarning, 

138 ) 

139 color = kwargs.get("color") 

140 style_attrs = tuple(_splitstrip(style)) 

141 msg_style = MessageStyle(color or msg_style, style_attrs) 

142 # If both color and style are not defined, then leave the text as is 

143 if msg_style.color is None and len(msg_style.style) == 0: 

144 return msg 

145 escape_code = _get_ansi_code(msg_style) 

146 # If invalid (or unknown) color, don't wrap msg with ANSI codes 

147 if escape_code: 

148 return f"{escape_code}{msg}{ANSI_RESET}" 

149 return msg 

150 

151 

152class TextReporter(BaseReporter): 

153 """Reports messages and layouts in plain text.""" 

154 

155 name = "text" 

156 extension = "txt" 

157 line_format = "{path}:{line}:{column}: {msg_id}: {msg} ({symbol})" 

158 

159 def __init__(self, output: TextIO | None = None) -> None: 

160 super().__init__(output) 

161 self._modules: set[str] = set() 

162 self._template = self.line_format 

163 self._fixed_template = self.line_format 

164 """The output format template with any unrecognized arguments removed.""" 

165 

166 def on_set_current_module(self, module: str, filepath: str | None) -> None: 

167 """Set the format template to be used and check for unrecognized arguments.""" 

168 template = str(self.linter.config.msg_template or self._template) 

169 

170 # Return early if the template is the same as the previous one 

171 if template == self._template: 

172 return 

173 

174 # Set template to the currently selected template 

175 self._template = template 

176 

177 # Check to see if all parameters in the template are attributes of the Message 

178 arguments = re.findall(r"\{(.+?)(:.*)?\}", template) 

179 for argument in arguments: 

180 if argument[0] not in MESSAGE_FIELDS: 

181 warnings.warn( 

182 f"Don't recognize the argument '{argument[0]}' in the --msg-template. " 

183 "Are you sure it is supported on the current version of pylint?" 

184 ) 

185 template = re.sub(r"\{" + argument[0] + r"(:.*?)?\}", "", template) 

186 self._fixed_template = template 

187 

188 def write_message(self, msg: Message) -> None: 

189 """Convenience method to write a formatted message with class default template.""" 

190 self_dict = asdict(msg) 

191 for key in ("end_line", "end_column"): 

192 self_dict[key] = self_dict[key] or "" 

193 

194 self.writeln(self._fixed_template.format(**self_dict)) 

195 

196 def handle_message(self, msg: Message) -> None: 

197 """Manage message of different type and in the context of path.""" 

198 if msg.module not in self._modules: 

199 if msg.module: 

200 self.writeln(f"************* Module {msg.module}") 

201 self._modules.add(msg.module) 

202 else: 

203 self.writeln("************* ") 

204 self.write_message(msg) 

205 

206 def _display(self, layout: Section) -> None: 

207 """Launch layouts display.""" 

208 print(file=self.out) 

209 TextWriter().format(layout, self.out) 

210 

211 

212class ParseableTextReporter(TextReporter): 

213 """A reporter very similar to TextReporter, but display messages in a form 

214 recognized by most text editors : 

215 

216 <filename>:<linenum>:<msg> 

217 """ 

218 

219 name = "parseable" 

220 line_format = "{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" 

221 

222 def __init__(self, output: TextIO | None = None) -> None: 

223 warnings.warn( 

224 f"{self.name} output format is deprecated. This is equivalent to --msg-template={self.line_format}", 

225 DeprecationWarning, 

226 ) 

227 super().__init__(output) 

228 

229 

230class VSTextReporter(ParseableTextReporter): 

231 """Visual studio text reporter.""" 

232 

233 name = "msvs" 

234 line_format = "{path}({line}): [{msg_id}({symbol}){obj}] {msg}" 

235 

236 

237class ColorizedTextReporter(TextReporter): 

238 """Simple TextReporter that colorizes text output.""" 

239 

240 name = "colorized" 

241 COLOR_MAPPING: ColorMappingDict = { 

242 "I": MessageStyle("green"), 

243 "C": MessageStyle(None, ("bold",)), 

244 "R": MessageStyle("magenta", ("bold", "italic")), 

245 "W": MessageStyle("magenta"), 

246 "E": MessageStyle("red", ("bold",)), 

247 "F": MessageStyle("red", ("bold", "underline")), 

248 "S": MessageStyle("yellow", ("inverse",)), # S stands for module Separator 

249 } 

250 

251 def __init__( 

252 self, 

253 output: TextIO | None = None, 

254 color_mapping: ( 

255 ColorMappingDict | dict[str, tuple[str | None, str]] | None 

256 ) = None, 

257 ) -> None: 

258 super().__init__(output) 

259 # TODO: 3.0: Remove deprecated typing and only accept ColorMappingDict as color_mapping parameter 

260 if color_mapping and not isinstance( 

261 list(color_mapping.values())[0], MessageStyle 

262 ): 

263 warnings.warn( 

264 "In pylint 3.0, the ColorizedTextReporter will only accept ColorMappingDict as color_mapping parameter", 

265 DeprecationWarning, 

266 ) 

267 temp_color_mapping: ColorMappingDict = {} 

268 for key, value in color_mapping.items(): 

269 color = value[0] 

270 style_attrs = tuple(_splitstrip(value[1])) # type: ignore[arg-type] 

271 temp_color_mapping[key] = MessageStyle(color, style_attrs) 

272 color_mapping = temp_color_mapping 

273 else: 

274 color_mapping = cast(Optional[ColorMappingDict], color_mapping) 

275 self.color_mapping = color_mapping or ColorizedTextReporter.COLOR_MAPPING 

276 ansi_terms = ["xterm-16color", "xterm-256color"] 

277 if os.environ.get("TERM") not in ansi_terms: 

278 if sys.platform == "win32": 

279 # pylint: disable=import-outside-toplevel 

280 import colorama 

281 

282 self.out = colorama.AnsiToWin32(self.out) 

283 

284 def _get_decoration(self, msg_id: str) -> MessageStyle: 

285 """Returns the message style as defined in self.color_mapping.""" 

286 return self.color_mapping.get(msg_id[0]) or MessageStyle(None) 

287 

288 def handle_message(self, msg: Message) -> None: 

289 """Manage message of different types, and colorize output 

290 using ANSI escape codes. 

291 """ 

292 if msg.module not in self._modules: 

293 msg_style = self._get_decoration("S") 

294 if msg.module: 

295 modsep = colorize_ansi(f"************* Module {msg.module}", msg_style) 

296 else: 

297 modsep = colorize_ansi(f"************* {msg.module}", msg_style) 

298 self.writeln(modsep) 

299 self._modules.add(msg.module) 

300 msg_style = self._get_decoration(msg.C) 

301 

302 msg.msg = colorize_ansi(msg.msg, msg_style) 

303 msg.symbol = colorize_ansi(msg.symbol, msg_style) 

304 msg.category = colorize_ansi(msg.category, msg_style) 

305 msg.C = colorize_ansi(msg.C, msg_style) 

306 self.write_message(msg) 

307 

308 

309def register(linter: PyLinter) -> None: 

310 linter.register_reporter(TextReporter) 

311 linter.register_reporter(ParseableTextReporter) 

312 linter.register_reporter(VSTextReporter) 

313 linter.register_reporter(ColorizedTextReporter)