Skip to content

Commit b5b91c1

Browse files
committed
Create pylinter#_handle_force_color_no_color
Use the `_is_env_set_and_non_empty`, and the logic decided in the issue, to modify the reporter(s) list accordingly. At most, one reporter should be modified - the one going to `stdout`. Hooking before the `self.set_reporter` was deemed as the smallest incision. For no particular reason, it was decided that `_handle_force_color_no_color` would modify its input; so there would be no need for re-assigning the input we want to modify anyway. Signed-off-by: Stavros Ntentos <[email protected]>
1 parent aec5efb commit b5b91c1

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

pylint/lint/pylinter.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sys
1313
import tokenize
1414
import traceback
15+
import warnings
1516
from collections import defaultdict
1617
from collections.abc import Callable, Iterable, Iterator, Sequence
1718
from io import TextIOWrapper
@@ -48,13 +49,15 @@
4849
report_total_messages_stats,
4950
)
5051
from pylint.lint.utils import (
52+
_is_env_set_and_non_empty,
5153
augmented_sys_path,
5254
get_fatal_error_message,
5355
prepare_crash_report,
5456
)
5557
from pylint.message import Message, MessageDefinition, MessageDefinitionStore
58+
from pylint.reporters import ReporterWarning
5659
from pylint.reporters.base_reporter import BaseReporter
57-
from pylint.reporters.text import TextReporter
60+
from pylint.reporters.text import ColorizedTextReporter, TextReporter
5861
from pylint.reporters.ureports import nodes as report_nodes
5962
from pylint.typing import (
6063
DirectoryNamespaceDict,
@@ -69,6 +72,14 @@
6972

7073
MANAGER = astroid.MANAGER
7174

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+
7283

7384
class GetAstProtocol(Protocol):
7485
def __call__(
@@ -250,6 +261,68 @@ def _load_reporter_by_class(reporter_class: str) -> type[BaseReporter]:
250261
}
251262

252263

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+
253326
# pylint: disable=too-many-instance-attributes,too-many-public-methods
254327
class PyLinter(
255328
_ArgumentsManager,
@@ -435,6 +508,8 @@ def _load_reporters(self, reporter_names: str) -> None:
435508
# Extend the lifetime of all opened output files
436509
close_output_files = stack.pop_all().close
437510

511+
_handle_force_color_no_color(sub_reporters)
512+
438513
if len(sub_reporters) > 1 or output_files:
439514
self.set_reporter(
440515
reporters.MultiReporter(

pylint/reporters/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
from pylint.lint.pylinter import PyLinter
2020

2121

22+
class ReporterWarning(Warning):
23+
"""Warning class for reporters."""
24+
25+
2226
def initialize(linter: PyLinter) -> None:
2327
"""Initialize linter with reporters in this package."""
2428
utils.register_plugins(linter, __path__[0])

0 commit comments

Comments
 (0)