Skip to content

Commit b100251

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 e11caaf commit b100251

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

pylint/lint/pylinter.py

Lines changed: 77 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,14 +49,16 @@
4849
report_total_messages_stats,
4950
)
5051
from pylint.lint.utils import (
52+
_is_env_set_and_non_empty,
5153
_is_relative_to,
5254
augmented_sys_path,
5355
get_fatal_error_message,
5456
prepare_crash_report,
5557
)
5658
from pylint.message import Message, MessageDefinition, MessageDefinitionStore
59+
from pylint.reporters import ReporterWarning
5760
from pylint.reporters.base_reporter import BaseReporter
58-
from pylint.reporters.text import TextReporter
61+
from pylint.reporters.text import ColorizedTextReporter, TextReporter
5962
from pylint.reporters.ureports import nodes as report_nodes
6063
from pylint.typing import (
6164
DirectoryNamespaceDict,
@@ -70,6 +73,14 @@
7073

7174
MANAGER = astroid.MANAGER
7275

76+
NO_COLOR = "NO_COLOR"
77+
FORCE_COLOR = "FORCE_COLOR"
78+
PY_COLORS = "PY_COLORS"
79+
80+
WARN_FORCE_COLOR_SET = "FORCE_COLOR is set; ignoring `text` at stdout"
81+
WARN_NO_COLOR_SET = "NO_COLOR is set; ignoring `colorized` at stdout"
82+
WARN_BOTH_COLOR_SET = "Both NO_COLOR and FORCE_COLOR are set! (disabling colors)"
83+
7384

7485
class GetAstProtocol(Protocol):
7586
def __call__(
@@ -252,6 +263,69 @@ def _load_reporter_by_class(reporter_class: str) -> type[BaseReporter]:
252263
}
253264

254265

266+
def _handle_force_color_no_color(reporter: list[reporters.BaseReporter]) -> None:
267+
"""
268+
Check ``NO_COLOR``, ``FORCE_COLOR``, ``PY_COLOR`` and modify the reporter list
269+
accordingly.
270+
271+
Rules are presented in this table:
272+
+--------------+---------------+-----------------+------------------------------------------------------------+
273+
| `NO_COLOR` | `FORCE_COLOR` | `output-format` | Behavior |
274+
+==============+===============+=================+============================================================+
275+
| `bool: True` | `bool: True` | colorized | not colorized + warnings (override + inconsistent env var) |
276+
| `bool: True` | `bool: True` | / | not colorized + warnings (inconsistent env var) |
277+
| unset | `bool: True` | colorized | colorized |
278+
| unset | `bool: True` | / | colorized + warnings (override) |
279+
| `bool: True` | unset | colorized | not colorized + warnings (override) |
280+
| `bool: True` | unset | / | not colorized |
281+
| unset | unset | colorized | colorized |
282+
| unset | unset | / | not colorized |
283+
+--------------+---------------+-----------------+------------------------------------------------------------+
284+
"""
285+
286+
no_color = _is_env_set_and_non_empty(NO_COLOR)
287+
force_color = _is_env_set_and_non_empty(FORCE_COLOR) or _is_env_set_and_non_empty(
288+
PY_COLORS
289+
)
290+
291+
if no_color and force_color:
292+
warnings.warn(
293+
WARN_BOTH_COLOR_SET,
294+
ReporterWarning,
295+
stacklevel=2,
296+
)
297+
force_color = False
298+
299+
if no_color:
300+
for idx, rep in enumerate(list(reporter)):
301+
if not isinstance(rep, ColorizedTextReporter):
302+
continue
303+
304+
if rep.out.buffer is sys.stdout.buffer:
305+
warnings.warn(
306+
WARN_NO_COLOR_SET,
307+
ReporterWarning,
308+
stacklevel=2,
309+
)
310+
reporter.pop(idx)
311+
reporter.append(TextReporter())
312+
313+
elif force_color:
314+
for idx, rep in enumerate(list(reporter)):
315+
# pylint: disable=unidiomatic-typecheck # Want explicit type check
316+
if type(rep) is not TextReporter:
317+
continue
318+
319+
if rep.out.buffer is sys.stdout.buffer:
320+
warnings.warn(
321+
WARN_FORCE_COLOR_SET,
322+
ReporterWarning,
323+
stacklevel=2,
324+
)
325+
reporter.pop(idx)
326+
reporter.append(ColorizedTextReporter())
327+
328+
255329
# pylint: disable=too-many-instance-attributes,too-many-public-methods
256330
class PyLinter(
257331
_ArgumentsManager,
@@ -437,6 +511,8 @@ def _load_reporters(self, reporter_names: str) -> None:
437511
# Extend the lifetime of all opened output files
438512
close_output_files = stack.pop_all().close
439513

514+
_handle_force_color_no_color(sub_reporters)
515+
440516
if len(sub_reporters) > 1 or output_files:
441517
self.set_reporter(
442518
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)