|
12 | 12 | import sys
|
13 | 13 | import tokenize
|
14 | 14 | import traceback
|
| 15 | +import warnings |
15 | 16 | from collections import defaultdict
|
16 | 17 | from collections.abc import Callable, Iterable, Iterator, Sequence
|
17 | 18 | from io import TextIOWrapper
|
|
48 | 49 | report_total_messages_stats,
|
49 | 50 | )
|
50 | 51 | from pylint.lint.utils import (
|
| 52 | + _is_env_set_and_non_empty, |
51 | 53 | augmented_sys_path,
|
52 | 54 | get_fatal_error_message,
|
53 | 55 | prepare_crash_report,
|
54 | 56 | )
|
55 | 57 | from pylint.message import Message, MessageDefinition, MessageDefinitionStore
|
| 58 | +from pylint.reporters import ReporterWarning |
56 | 59 | from pylint.reporters.base_reporter import BaseReporter
|
57 |
| -from pylint.reporters.text import TextReporter |
| 60 | +from pylint.reporters.text import ColorizedTextReporter, TextReporter |
58 | 61 | from pylint.reporters.ureports import nodes as report_nodes
|
59 | 62 | from pylint.typing import (
|
60 | 63 | DirectoryNamespaceDict,
|
|
69 | 72 |
|
70 | 73 | MANAGER = astroid.MANAGER
|
71 | 74 |
|
| 75 | +NO_COLOR = "NO_COLOR" |
| 76 | +FORCE_COLOR = "FORCE_COLOR" |
| 77 | +PY_COLORS = "PY_COLORS" |
| 78 | + |
| 79 | +WARN_FORCE_COLOR_SET = "FORCE_COLOR is set; ignoring `text` at stdout" |
| 80 | +WARN_NO_COLOR_SET = "NO_COLOR is set; ignoring `colorized` at stdout" |
| 81 | +WARN_BOTH_COLOR_SET = "Both NO_COLOR and FORCE_COLOR are set! (disabling colors)" |
| 82 | + |
72 | 83 |
|
73 | 84 | class GetAstProtocol(Protocol):
|
74 | 85 | def __call__(
|
@@ -250,6 +261,68 @@ def _load_reporter_by_class(reporter_class: str) -> type[BaseReporter]:
|
250 | 261 | }
|
251 | 262 |
|
252 | 263 |
|
| 264 | +def _handle_force_color_no_color(reporter: list[reporters.BaseReporter]) -> None: |
| 265 | + """ |
| 266 | + Check ``NO_COLOR``, ``FORCE_COLOR``, ``PY_COLOR`` and modify the reporter list |
| 267 | + accordingly. |
| 268 | +
|
| 269 | + Rules are presented in this table: |
| 270 | + +--------------+---------------+-----------------+------------------------------------------------------------+ |
| 271 | + | `NO_COLOR` | `FORCE_COLOR` | `output-format` | Behavior | |
| 272 | + +==============+===============+=================+============================================================+ |
| 273 | + | `bool: True` | `bool: True` | colorized | not colorized + warnings (override + inconsistent env var) | |
| 274 | + | `bool: True` | `bool: True` | / | not colorized + warnings (inconsistent env var) | |
| 275 | + | unset | `bool: True` | colorized | colorized | |
| 276 | + | unset | `bool: True` | / | colorized + warnings (override) | |
| 277 | + | `bool: True` | unset | colorized | not colorized + warnings (override) | |
| 278 | + | `bool: True` | unset | / | not colorized | |
| 279 | + | unset | unset | colorized | colorized | |
| 280 | + | unset | unset | / | not colorized | |
| 281 | + +--------------+---------------+-----------------+------------------------------------------------------------+ |
| 282 | + """ |
| 283 | + no_color = _is_env_set_and_non_empty(NO_COLOR) |
| 284 | + force_color = _is_env_set_and_non_empty(FORCE_COLOR) or _is_env_set_and_non_empty( |
| 285 | + PY_COLORS |
| 286 | + ) |
| 287 | + |
| 288 | + if no_color and force_color: |
| 289 | + warnings.warn( |
| 290 | + WARN_BOTH_COLOR_SET, |
| 291 | + ReporterWarning, |
| 292 | + stacklevel=2, |
| 293 | + ) |
| 294 | + force_color = False |
| 295 | + |
| 296 | + if no_color: |
| 297 | + for idx, rep in enumerate(list(reporter)): |
| 298 | + if not isinstance(rep, ColorizedTextReporter): |
| 299 | + continue |
| 300 | + |
| 301 | + if rep.out.buffer is sys.stdout.buffer: |
| 302 | + warnings.warn( |
| 303 | + WARN_NO_COLOR_SET, |
| 304 | + ReporterWarning, |
| 305 | + stacklevel=2, |
| 306 | + ) |
| 307 | + reporter.pop(idx) |
| 308 | + reporter.append(TextReporter()) |
| 309 | + |
| 310 | + elif force_color: |
| 311 | + for idx, rep in enumerate(list(reporter)): |
| 312 | + # pylint: disable=unidiomatic-typecheck # Want explicit type check |
| 313 | + if type(rep) is not TextReporter: |
| 314 | + continue |
| 315 | + |
| 316 | + if rep.out.buffer is sys.stdout.buffer: |
| 317 | + warnings.warn( |
| 318 | + WARN_FORCE_COLOR_SET, |
| 319 | + ReporterWarning, |
| 320 | + stacklevel=2, |
| 321 | + ) |
| 322 | + reporter.pop(idx) |
| 323 | + reporter.append(ColorizedTextReporter()) |
| 324 | + |
| 325 | + |
253 | 326 | # pylint: disable=too-many-instance-attributes,too-many-public-methods
|
254 | 327 | class PyLinter(
|
255 | 328 | _ArgumentsManager,
|
@@ -435,6 +508,8 @@ def _load_reporters(self, reporter_names: str) -> None:
|
435 | 508 | # Extend the lifetime of all opened output files
|
436 | 509 | close_output_files = stack.pop_all().close
|
437 | 510 |
|
| 511 | + _handle_force_color_no_color(sub_reporters) |
| 512 | + |
438 | 513 | if len(sub_reporters) > 1 or output_files:
|
439 | 514 | self.set_reporter(
|
440 | 515 | reporters.MultiReporter(
|
|
0 commit comments