Failed Conditions
Pull Request — master (#1990)
by Mischa
01:43
created

coalib.bearlib.abstractions.LinterBase   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 395
Duplicated Lines 0 %
Metric Value
dl 0
loc 395
rs 8.4864
wmc 48

How to fix   Complexity   

Complex Class

Complex classes like coalib.bearlib.abstractions.LinterBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
from contextlib import contextmanager
2
import inspect
3
import re
4
import shutil
5
from subprocess import check_call, CalledProcessError, DEVNULL
6
from types import MappingProxyType
7
8
from coalib.bears.LocalBear import LocalBear
9
from coalib.misc.ContextManagers import make_temp
10
from coalib.misc.Decorators import assert_right_type, enforce_signature
11
from coalib.misc.Future import partialmethod
12
from coalib.misc.Shell import run_shell_command
13
from coalib.results.Diff import Diff
14
from coalib.results.Result import Result
15
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
16
from coalib.settings.FunctionMetadata import FunctionMetadata
17
18
19
@enforce_signature
20
def Linter(executable: str,
21
           use_stdin: bool=False,
22
           output_stream: str="stdout",
23
           config_suffix: str="",
24
           prerequisite_check_command: tuple=(),
25
           output_format: (str, None)=None,
26
           **options):
27
    """
28
    Decorator that creates a ``LocalBear`` that is able to process results from
29
    an external linter tool.
30
31
    The main functionality is achieved through the ``create_arguments()``
32
    function that constructs the command-line-arguments that get parsed to your
33
    executable.
34
35
    >>> @Linter("xlint", output_format="regex", output_regex="...")
36
    ... class XLintBear:
37
    ...     @staticmethod
38
    ...     def create_arguments(filename, file, config_file):
39
    ...         return "--lint", filename
40
41
    Requiring settings is possible like in ``Bear.run()`` with supplying
42
    additional keyword arguments (and if needed with defaults).
43
44
    >>> @Linter("xlint", output_format="regex", output_regex="...")
45
    ... class XLintBear:
46
    ...     @staticmethod
47
    ...     def create_arguments(filename,
48
    ...                          file,
49
    ...                          config_file,
50
    ...                          lintmode: str,
51
    ...                          enable_aggressive_lints: bool=False):
52
    ...         arguments = ("--lint", filename, "--mode=" + lintmode)
53
    ...         if enable_aggressive_lints:
54
    ...             arguments += ("--aggressive",)
55
    ...         return arguments
56
57
    Sometimes your tool requires an actual file that contains configuration.
58
    ``Linter`` allows you to just define the contents the configuration shall
59
    contain via ``generate_config()`` and handles everything else for you.
60
61
    >>> @Linter("xlint", output_format="regex", output_regex="...")
62
    ... class XLintBear:
63
    ...     @staticmethod
64
    ...     def generate_config(filename,
65
    ...                         file,
66
    ...                         lintmode,
67
    ...                         enable_aggressive_lints):
68
    ...         modestring = ("aggressive"
69
    ...                       if enable_aggressive_lints else
70
    ...                       "non-aggressive")
71
    ...         contents = ("<xlint>",
72
    ...                     "    <mode>" + lintmode + "</mode>",
73
    ...                     "    <aggressive>" + modestring + "</aggressive>",
74
    ...                     "</xlint>")
75
    ...         return "\\n".join(contents)
76
    ...
77
    ...     @staticmethod
78
    ...     def create_arguments(filename,
79
    ...                          file,
80
    ...                          config_file):
81
    ...         return "--lint", filename, "--config", config_file
82
83
    As you can see you don't need to copy additional keyword-arguments you
84
    introduced from ``create_arguments()`` to ``generate_config()`` and
85
    vice-versa. ``Linter`` takes care of forwarding the right arguments to the
86
    right place, so you are able to avoid signature duplication.
87
88
    If you override ``process_output``, you have the same feature like above
89
    (auto-forwarding of the right arguments defined in your function
90
    signature).
91
92
    Documentation:
93
    Bear description shall be provided at class level.
94
    If you document your additional parameters inside ``create_arguments``,
95
    ``generate_config`` and ``process_output``, beware that conflicting
96
    documentation between them may be overridden. Document duplicated
97
    parameters inside ``create_arguments`` first, then in ``generate_config``
98
    and after that inside ``process_output``.
99
100
    For the tutorial see:
101
    http://coala.readthedocs.org/en/latest/Users/Tutorials/Linter_Bears.html
102
103
    :param executable:
104
        The linter tool.
105
    :param use_stdin:
106
        Whether the input file is sent via stdin instead of passing it over the
107
        command-line-interface.
108
    :param output_stream:
109
        The output streams to grab from the executable. Possible values are
110
        ``stdout``, ``stderr`` or ``stdout+stderr`` (or ``stderr+stdout``).
111
        Providing an unknown value raises a ``ValueError``.
112
113
        Note when overriding ``process_output``: Providing a single output
114
        stream puts the according string attained from the stream into
115
        parameter ``output``, providing more than one output stream inputs
116
        a tuple in the same order like specified for this argument.
117
    :param config_suffix:
118
        The suffix-string to append to the filename of the configuration file
119
        created when ``generate_config`` is supplied. Useful if your executable
120
        expects getting a specific file-type with specific file-ending for the
121
        configuration file.
122
    :param prerequisite_check_command:
123
        A custom command to check for when ``check_prerequisites`` gets
124
        invoked (via ``subprocess.check_call()``). Must be an ``Iterable``.
125
    :param prerequisite_check_fail_message:
126
        A custom command to check for when ``check_prerequisites`` gets
127
        invoked. Must be provided only together with
128
        ``prerequisite_check_command``.
129
    :param output_format:
130
        The output format of the underlying executable. Valid values are
131
132
        - ``None``: Define your own format by overriding ``process_output``.
133
          Overriding ``process_output`` is then mandatory, not specifying it
134
          raises a ``ValueError``.
135
        - ``'regex'``: Parse output using a regex. See parameter
136
          ``output_regex``.
137
        - ``'corrected'``: The output is the corrected of the given file. Diffs
138
          are then generated to supply patches for results.
139
140
        Passing something else raises a ``ValueError``.
141
    :param output_regex:
142
        The regex expression as a string that is used to parse the output
143
        generated by the underlying executable. It should use as many of the
144
        following named groups (via ``(?P<name>...)``) to provide a good
145
        result:
146
147
        - line - The line where the issue starts.
148
        - column - The column where the issue starts.
149
        - end_line - The line where the issue ends.
150
        - end_column - The column where the issue ends.
151
        - severity - The severity of the issue.
152
        - message - The message of the result.
153
        - origin - The origin of the issue.
154
155
        The groups ``line``, ``column``, ``end_line`` and ``end_column`` don't
156
        have to match numbers only, they can also match nothing, the generated
157
        ``Result`` is filled automatically with ``None`` then for the
158
        appropriate properties.
159
160
        Needs to be provided if ``output_format`` is ``'regex'``.
161
    :param severity_map:
162
        A dict used to map a severity string (captured from the
163
        ``output_regex`` with the named group ``severity``) to an actual
164
        ``coalib.results.RESULT_SEVERITY`` for a result.
165
166
        - ``RESULT_SEVERITY.MAJOR``
167
          Mapped by ``error``, ``Error`` or ``ERROR``.
168
        - ``RESULT_SEVERITY.NORMAL``
169
          Mapped by ``warning``, ``Warning``, ``WARNING``, ``warn``, ``Warn``
170
          or ``WARN``.
171
        - ``RESULT_SEVERITY.MINOR``
172
          Mapped by ``info``, ``Info`` or ``INFO``.
173
174
        A ``ValueError`` is raised when the named group ``severity`` is not
175
        used inside ``output_regex`` and this parameter is given.
176
    :param diff_severity:
177
        The severity to use for all results if ``output_format`` is
178
        ``'corrected'``. By default this value is
179
        ``coalib.results.RESULT_SEVERITY.NORMAL``. The given value needs to be
180
        defined inside ``coalib.results.RESULT_SEVERITY``.
181
    :param diff_message:
182
        The message-string to use for all results if ``output_format`` is
183
        ``'corrected'``. By default this value is ``"Inconsistency found."``.
184
    :raises ValueError:
185
        Raised when invalid options are supplied.
186
    :raises TypeError:
187
        Raised when incompatible types are supplied.
188
        See parameter documentations for allowed types.
189
    :return:
190
        A ``LocalBear`` derivation that lints code using an external tool.
191
    """
192
    options["executable"] = executable
193
    options["output_format"] = output_format
194
    options["use_stdin"] = use_stdin
195
    options["output_stream"] = output_stream
196
    options["config_suffix"] = config_suffix
197
    options["prerequisite_check_command"] = prerequisite_check_command
198
199
    allowed_options = {"executable",
200
                       "output_format",
201
                       "use_stdin",
202
                       "output_stream",
203
                       "config_suffix",
204
                       "prerequisite_check_command"}
205
206
    output_stream_to_index_map = {"stdout": 0, "stderr": 1}
207
    try:
208
        stream_indices = []
209
        for stream in options["output_stream"].split("+"):
210
            stream_indices.append(output_stream_to_index_map[stream])
211
        options["output_stream"] = tuple(stream_indices)
212
    except KeyError:
213
        raise ValueError("Invalid output stream: " + repr(stream))
214
215
    if options["output_format"] == "corrected":
216
        if "diff_severity" in options:
217
            if options["diff_severity"] not in RESULT_SEVERITY.reverse:
218
                raise TypeError("Invalid value for `diff_severity`: " +
219
                                repr(options["diff_severity"]))
220
221
        if "diff_message" in options:
222
            assert_right_type(options["diff_message"], str, "diff_message")
223
224
        allowed_options |= {"diff_severity", "diff_message"}
225
    elif options["output_format"] == "regex":
226
        if "output_regex" not in options:
227
            raise ValueError("No `output_regex` specified.")
228
229
        options["output_regex"] = re.compile(options["output_regex"])
230
231
        # Don't setup severity_map if one is provided by user or if it's not
232
        # used inside the output_regex. If one is manually provided but not
233
        # used in the output_regex, throw an exception.
234
        if "severity_map" in options:
235
            if "severity" not in options["output_regex"].groupindex:
236
                raise ValueError("Provided `severity_map` but named group "
237
                                 "`severity` is not used in `output_regex`.")
238
            assert_right_type(options["severity_map"], dict, "severity_map")
239
240
            for key, value in options["severity_map"].items():
241
                try:
242
                    assert_right_type(key, str, "<severity_map dict-key>")
243
                except TypeError:
244
                    raise TypeError("The key " + repr(key) + " inside given "
245
                                    "severity-map is no string.")
246
247
                try:
248
                    assert_right_type(value, int, "<severity_map dict-value>")
249
                except TypeError:
250
                    raise TypeError(
251
                        "The value {} for key {} inside given severity-map is "
252
                        "no valid severity value.".format(repr(value),
253
                                                          repr(key)))
254
255
                if value not in RESULT_SEVERITY.reverse:
256
                    raise TypeError(
257
                        "Invalid severity value {} for key {} inside given "
258
                        "severity-map.".format(repr(value), repr(key)))
259
260
            # Copy the severity map, so users can't change it afterwards.
261
            options["severity_map"] = dict(options["severity_map"])
262
263
        allowed_options |= {"output_regex", "severity_map"}
264
    elif options["output_format"] is not None:
265
        raise ValueError("Invalid `output_format` specified.")
266
267
    if options["prerequisite_check_command"]:
268
        if "prerequisite_check_fail_message" in options:
269
            assert_right_type(options["prerequisite_check_fail_message"],
270
                              str,
271
                              "prerequisite_check_fail_message")
272
        else:
273
            options["prerequisite_check_fail_message"] = (
274
                "Prerequisite check failed.")
275
276
        allowed_options.add("prerequisite_check_fail_message")
277
278
    # Check for illegal superfluous options.
279
    superfluous_options = options.keys() - allowed_options
280
    if superfluous_options:
281
        raise ValueError(
282
            "Invalid keyword arguments provided: " +
283
            ", ".join(repr(s) for s in sorted(superfluous_options)))
284
285
    def create_linter(klass):
286
        class LinterBase(LocalBear):
287
288
            @staticmethod
289
            def generate_config(filename, file):
290
                """
291
                Generates the content of a config-file the linter-tool might
292
                need.
293
294
                The contents generated from this function are written to a
295
                temporary file and the path is provided inside
296
                ``create_arguments()``.
297
298
                By default no configuration is generated.
299
300
                You can provide additional keyword arguments and defaults.
301
                These will be interpreted as required settings that need to be
302
                provided through a coafile-section.
303
304
                :param filename:
305
                    The name of the file currently processed.
306
                :param file:
307
                    The contents of the file currently processed.
308
                :return:
309
                    The config-file-contents as a string or ``None``.
310
                """
311
                return None
312
313
            @staticmethod
314
            def create_arguments(filename, file, config_file):
315
                """
316
                Creates the arguments for the linter.
317
318
                You can provide additional keyword arguments and defaults.
319
                These will be interpreted as required settings that need to be
320
                provided through a coafile-section.
321
322
                :param filename:
323
                    The name of the file the linter-tool shall process.
324
                :param file:
325
                    The contents of the file.
326
                :param config_file:
327
                    The path of the config-file if used. ``None`` if unused.
328
                :return:
329
                    A sequence of arguments to feed the linter-tool with.
330
                """
331
                raise NotImplementedError
332
333
            @staticmethod
334
            def get_executable():
335
                """
336
                Returns the executable of this class.
337
338
                :return:
339
                    The executable name.
340
                """
341
                return options["executable"]
342
343
            @classmethod
344
            def check_prerequisites(cls):
345
                """
346
                Checks whether the linter-tool the bear uses is operational.
347
348
                :return:
349
                    True if available, otherwise a string containing more info.
350
                """
351
                if shutil.which(cls.get_executable()) is None:
352
                    return repr(cls.get_executable()) + " is not installed."
353
                else:
354
                    if options["prerequisite_check_command"]:
355
                        try:
356
                            check_call(options["prerequisite_check_command"],
357
                                       stdout=DEVNULL,
358
                                       stderr=DEVNULL)
359
                            return True
360
                        except (OSError, CalledProcessError):
361
                            return options["prerequisite_check_fail_message"]
362
                    return True
363
364
            @classmethod
365
            def _get_create_arguments_metadata(cls):
366
                return FunctionMetadata.from_function(
367
                    cls.create_arguments,
368
                    omit={"filename", "file", "config_file"})
369
370
            @classmethod
371
            def _get_generate_config_metadata(cls):
372
                return FunctionMetadata.from_function(
373
                    cls.generate_config,
374
                    omit={"filename", "file"})
375
376
            @classmethod
377
            def _get_process_output_metadata(cls):
378
                return FunctionMetadata.from_function(
379
                    cls.process_output,
380
                    omit={"self", "output", "filename", "file"})
381
382
            @classmethod
383
            def get_non_optional_settings(cls):
384
                return cls.get_metadata().non_optional_params
385
386
            @classmethod
387
            def get_metadata(cls):
388
                merged_metadata = FunctionMetadata.merge(
389
                    cls._get_process_output_metadata(),
390
                    cls._get_generate_config_metadata(),
391
                    cls._get_create_arguments_metadata())
392
                merged_metadata.desc = inspect.getdoc(cls)
393
                return merged_metadata
394
395
            @classmethod
396
            def _execute_command(cls, args, stdin=None):
397
                """
398
                Executes the underlying tool with the given arguments.
399
400
                :param args:
401
                    The argument sequence to pass to the executable.
402
                :param stdin:
403
                    Input to send to the opened process as stdin.
404
                :return:
405
                    A tuple with ``(stdout, stderr)``.
406
                """
407
                return run_shell_command(
408
                    (cls.get_executable(),) + tuple(args),
409
                    stdin=stdin)
410
411
            def _convert_output_regex_match_to_result(self,
412
                                                      match,
413
                                                      filename,
414
                                                      severity_map):
415
                """
416
                Converts the matched named-groups of ``output_regex`` to an
417
                actual ``Result``.
418
419
                :param match:
420
                    The regex match object.
421
                :param filename:
422
                    The name of the file this match belongs to.
423
                :param severity_map:
424
                    The dict to use to map the severity-match to an actual
425
                    ``RESULT_SEVERITY``.
426
                """
427
                # Pre process the groups
428
                groups = match.groupdict()
429
430
                try:
431
                    groups["severity"] = severity_map[groups["severity"]]
432
                except KeyError:
433
                    self.warn(
434
                        "No correspondence for " + repr(groups["severity"]) +
435
                        " found in given severity map. Assuming "
436
                        "`RESULT_SEVERITY.NORMAL`.")
437
                    groups["severity"] = RESULT_SEVERITY.NORMAL
438
439
                for variable in ("line", "column", "end_line", "end_column"):
440
                    groups[variable] = (None
441
                                        if groups.get(variable, "") == "" else
442
                                        int(groups[variable]))
443
444
                if "origin" in groups:
445
                    groups["origin"] = "{} ({})".format(
446
                        str(klass.__name__),
447
                        str(groups["origin"]))
448
449
                # Construct the result.
450
                return Result.from_values(
451
                    origin=groups.get("origin", self),
452
                    message=groups.get("message", ""),
453
                    file=filename,
454
                    severity=int(groups.get("severity",
455
                                            RESULT_SEVERITY.NORMAL)),
456
                    line=groups["line"],
457
                    column=groups["column"],
458
                    end_line=groups["end_line"],
459
                    end_column=groups["end_column"])
460
461
            def process_output_corrected(self,
462
                                         output,
463
                                         filename,
464
                                         file,
465
                                         diff_severity=RESULT_SEVERITY.NORMAL,
466
                                         diff_message="Inconsistency found."):
467
                """
468
                Processes the executable's output as a corrected file.
469
470
                :param output:
471
                    The output of the program. This can be either a single
472
                    string or a sequence of strings.
473
                :param filename:
474
                    The filename of the file currently being corrected.
475
                :param file:
476
                    The contents of the file currently being corrected.
477
                :param diff_severity:
478
                    The severity to use for generating results.
479
                :param diff_message:
480
                    The message to use for generating results.
481
                :return:
482
                    An iterator returning results containing patches for the
483
                    file to correct.
484
                """
485
                if isinstance(output, str):
486
                    output = (output,)
487
488
                for string in output:
489
                    for diff in Diff.from_string_arrays(
490
                                file,
491
                                string.splitlines(keepends=True)).split_diff():
492
                        yield Result(self,
493
                                     diff_message,
494
                                     affected_code=(diff.range(filename),),
495
                                     diffs={filename: diff},
496
                                     severity=diff_severity)
497
498
            def process_output_regex(
499
                    self,
500
                    output,
501
                    filename,
502
                    file,
503
                    output_regex,
504
                    severity_map=MappingProxyType({
505
                        "error": RESULT_SEVERITY.MAJOR,
506
                        "Error": RESULT_SEVERITY.MAJOR,
507
                        "ERROR": RESULT_SEVERITY.MAJOR,
508
                        "warning": RESULT_SEVERITY.NORMAL,
509
                        "Warning": RESULT_SEVERITY.NORMAL,
510
                        "WARNING": RESULT_SEVERITY.NORMAL,
511
                        "warn": RESULT_SEVERITY.NORMAL,
512
                        "Warn": RESULT_SEVERITY.NORMAL,
513
                        "WARN": RESULT_SEVERITY.NORMAL,
514
                        "info": RESULT_SEVERITY.INFO,
515
                        "Info": RESULT_SEVERITY.INFO,
516
                        "INFO": RESULT_SEVERITY.INFO})):
517
                """
518
                Processes the executable's output using a regex.
519
520
                :param output:
521
                    The output of the program. This can be either a single
522
                    string or a sequence of strings.
523
                :param filename:
524
                    The filename of the file currently being corrected.
525
                :param file:
526
                    The contents of the file currently being corrected.
527
                :param output_regex:
528
                    The regex to parse the output with. It should use as many
529
                    of the following named groups (via ``(?P<name>...)``) to
530
                    provide a good result:
531
532
                    - line - The line where the issue starts.
533
                    - column - The column where the issue starts.
534
                    - end_line - The line where the issue ends.
535
                    - end_column - The column where the issue ends.
536
                    - severity - The severity of the issue.
537
                    - message - The message of the result.
538
                    - origin - The origin of the issue.
539
540
                    The groups ``line``, ``column``, ``end_line`` and
541
                    ``end_column`` don't have to match numbers only, they can
542
                    also match nothing, the generated ``Result`` is filled
543
                    automatically with ``None`` then for the appropriate
544
                    properties.
545
                :param severity_map:
546
                    A dict used to map a severity string (captured from the
547
                    ``output_regex`` with the named group ``severity``) to an
548
                    actual ``coalib.results.RESULT_SEVERITY`` for a result.
549
                :return:
550
                    An iterator returning results.
551
                """
552
                if isinstance(output, str):
553
                    output = (output,)
554
555
                for string in output:
556
                    for match in re.finditer(output_regex, string):
557
                        yield self._convert_output_regex_match_to_result(
558
                            match, filename, severity_map=severity_map)
559
560
            if options["output_format"] is None:
561
                # Check if user supplied a `process_output` override.
562
                if not (hasattr(klass, "process_output") and
563
                        callable(klass.process_output)):
564
                    raise ValueError("`process_output` not provided by given "
565
                                     "class.")
566
                # No need to assign to `process_output` here, the class mixing
567
                # below automatically does that.
568
            else:
569
                # Prevent people from accidentally defining `process_output`
570
                # manually, as this would implicitly override the internally
571
                # set-up `process_output`.
572
                if hasattr(klass, "process_output"):
573
                    raise ValueError("`process_output` is used by given class,"
574
                                     " but " + repr(options["output_format"]) +
575
                                     " output format was specified.")
576
577
                if options["output_format"] == "corrected":
578
                    process_output_args = {}
579
                    if "diff_severity" in options:
580
                        process_output_args["diff_severity"] = (
581
                            options["diff_severity"])
582
                    if "diff_message" in options:
583
                        process_output_args["diff_message"] = (
584
                            options["diff_message"])
585
586
                    process_output = partialmethod(
587
                        process_output_corrected, **process_output_args)
588
589
                elif options["output_format"] == "regex":
590
                    process_output_args = {
591
                        "output_regex": options["output_regex"]}
592
                    if "severity_map" in options:
593
                        process_output_args["severity_map"] = (
594
                            options["severity_map"])
595
596
                    process_output = partialmethod(
597
                        process_output_regex, **process_output_args)
598
599
                else:  # pragma: no cover
600
                    # This statement is never reached.
601
                    # Due to a bug in coverage we can't use `pass` here, as
602
                    # the ignore-pragma doesn't take up this else-clause then.
603
                    # https://bitbucket.org/ned/coveragepy/issues/483/partial-
604
                    # branch-coverage-pragma-no-cover
605
                    assert False
606
607
            @classmethod
608
            @contextmanager
609
            def _create_config(cls, filename, file, **kwargs):
610
                """
611
                Provides a context-manager that creates the config file if the
612
                user provides one and cleans it up when done with linting.
613
614
                :param filename:
615
                    The filename of the file.
616
                :param file:
617
                    The file contents.
618
                :param kwargs:
619
                    Section settings passed from ``run()``.
620
                :return:
621
                    A context-manager handling the config-file.
622
                """
623
                content = cls.generate_config(filename, file, **kwargs)
624
                if content is None:
625
                    yield None
626
                else:
627
                    tmp_suffix = options["config_suffix"]
628
                    with make_temp(suffix=tmp_suffix) as config_file:
629
                        with open(config_file, mode="w") as fl:
630
                            fl.write(content)
631
                        yield config_file
632
633
            @staticmethod
634
            def _filter_kwargs(metadata, kwargs):
635
                """
636
                Filter out kwargs using the given metadata. Means only
637
                parameters contained in the metadata specification are taken
638
                from kwargs and returned.
639
640
                :param metadata:
641
                    The signature specification.
642
                :param kwargs:
643
                    The kwargs to filter.
644
                :return:
645
                    The filtered kwargs.
646
                """
647
                return {key: kwargs[key]
648
                        for key in metadata.non_optional_params.keys() |
649
                        metadata.optional_params.keys()
650
                        if key in kwargs}
651
652
            def run(self, filename, file, **kwargs):
653
                # Get the **kwargs params to forward to `generate_config()`
654
                # (from `_create_config()`).
655
                generate_config_kwargs = self._filter_kwargs(
656
                    self._get_generate_config_metadata(), kwargs)
657
658
                with self._create_config(
659
                        filename,
660
                        file,
661
                        **generate_config_kwargs) as config_file:
662
663
                    # And now retrieve the **kwargs for `create_arguments()`.
664
                    create_arguments_kwargs = self._filter_kwargs(
665
                        self._get_create_arguments_metadata(), kwargs)
666
667
                    output = self._execute_command(
668
                        self.create_arguments(filename,
669
                                              file,
670
                                              config_file,
671
                                              **create_arguments_kwargs),
672
                        stdin="".join(file) if options["use_stdin"] else None)
673
                    output = tuple(output[i] for i in options["output_stream"])
674
                    if len(output) == 1:
675
                        output = output[0]
676
677
                    process_output_kwargs = self._filter_kwargs(
678
                        self._get_process_output_metadata(), kwargs)
679
                    return self.process_output(output, filename, file,
680
                                               **process_output_kwargs)
681
682
        # Mixin the linter into the user-defined interface, otherwise
683
        # `create_arguments` and other methods would be overridden by the
684
        # default version.
685
        class Linter(klass, LinterBase):
0 ignored issues
show
Comprehensibility Bug introduced by
Linter is re-defining a name which is already available in the outer-scope (previously defined on line 20).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
686
            pass
687
688
        return Linter
689
690
    return create_linter
691