~ibmcharmers/charms/xenial/ibm-cinder-storwize-svc/trunk

« back to all changes in this revision

Viewing changes to .tox/py35/lib/python3.5/site-packages/flake8/style_guide.py

  • Committer: Ankammarao
  • Date: 2017-03-06 05:11:42 UTC
  • Revision ID: achittet@in.ibm.com-20170306051142-dpg27z4es1k56hfn
Marked tests folder executable

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Implementation of the StyleGuide used by Flake8."""
 
2
import collections
 
3
import contextlib
 
4
import enum
 
5
import linecache
 
6
import logging
 
7
 
 
8
from flake8 import defaults
 
9
from flake8 import statistics
 
10
from flake8 import utils
 
11
 
 
12
__all__ = (
 
13
    'StyleGuide',
 
14
)
 
15
 
 
16
LOG = logging.getLogger(__name__)
 
17
 
 
18
 
 
19
# TODO(sigmavirus24): Determine if we need to use enum/enum34
 
20
class Selected(enum.Enum):
 
21
    """Enum representing an explicitly or implicitly selected code."""
 
22
 
 
23
    Explicitly = 'explicitly selected'
 
24
    Implicitly = 'implicitly selected'
 
25
 
 
26
 
 
27
class Ignored(enum.Enum):
 
28
    """Enum representing an explicitly or implicitly ignored code."""
 
29
 
 
30
    Explicitly = 'explicitly ignored'
 
31
    Implicitly = 'implicitly ignored'
 
32
 
 
33
 
 
34
class Decision(enum.Enum):
 
35
    """Enum representing whether a code should be ignored or selected."""
 
36
 
 
37
    Ignored = 'ignored error'
 
38
    Selected = 'selected error'
 
39
 
 
40
 
 
41
Error = collections.namedtuple(
 
42
    'Error',
 
43
    [
 
44
        'code',
 
45
        'filename',
 
46
        'line_number',
 
47
        'column_number',
 
48
        'text',
 
49
        'physical_line',
 
50
    ],
 
51
)
 
52
 
 
53
 
 
54
class StyleGuide(object):
 
55
    """Manage a Flake8 user's style guide."""
 
56
 
 
57
    def __init__(self, options, listener_trie, formatter):
 
58
        """Initialize our StyleGuide.
 
59
 
 
60
        .. todo:: Add parameter documentation.
 
61
        """
 
62
        self.options = options
 
63
        self.listener = listener_trie
 
64
        self.formatter = formatter
 
65
        self.stats = statistics.Statistics()
 
66
        self._selected = tuple(options.select)
 
67
        self._extended_selected = tuple(sorted(
 
68
            options.extended_default_select,
 
69
            reverse=True,
 
70
        ))
 
71
        self._enabled_extensions = tuple(options.enable_extensions)
 
72
        self._all_selected = tuple(sorted(
 
73
            self._selected + self._enabled_extensions,
 
74
            reverse=True,
 
75
        ))
 
76
        self._ignored = tuple(sorted(options.ignore, reverse=True))
 
77
        self._decision_cache = {}
 
78
        self._parsed_diff = {}
 
79
 
 
80
    def is_user_selected(self, code):
 
81
        # type: (str) -> Union[Selected, Ignored]
 
82
        """Determine if the code has been selected by the user.
 
83
 
 
84
        :param str code:
 
85
            The code for the check that has been run.
 
86
        :returns:
 
87
            Selected.Implicitly if the selected list is empty,
 
88
            Selected.Explicitly if the selected list is not empty and a match
 
89
            was found,
 
90
            Ignored.Implicitly if the selected list is not empty but no match
 
91
            was found.
 
92
        """
 
93
        if self._all_selected and code.startswith(self._all_selected):
 
94
            return Selected.Explicitly
 
95
 
 
96
        if (not self._all_selected and
 
97
            (self._extended_selected and
 
98
                code.startswith(self._extended_selected))):
 
99
            # If it was not explicitly selected, it may have been implicitly
 
100
            # selected because the check comes from a plugin that is enabled by
 
101
            # default
 
102
            return Selected.Implicitly
 
103
 
 
104
        return Ignored.Implicitly
 
105
 
 
106
    def is_user_ignored(self, code):
 
107
        # type: (str) -> Union[Selected, Ignored]
 
108
        """Determine if the code has been ignored by the user.
 
109
 
 
110
        :param str code:
 
111
            The code for the check that has been run.
 
112
        :returns:
 
113
            Selected.Implicitly if the ignored list is empty,
 
114
            Ignored.Explicitly if the ignored list is not empty and a match was
 
115
            found,
 
116
            Selected.Implicitly if the ignored list is not empty but no match
 
117
            was found.
 
118
        """
 
119
        if self._ignored and code.startswith(self._ignored):
 
120
            return Ignored.Explicitly
 
121
 
 
122
        return Selected.Implicitly
 
123
 
 
124
    @contextlib.contextmanager
 
125
    def processing_file(self, filename):
 
126
        """Record the fact that we're processing the file's results."""
 
127
        self.formatter.beginning(filename)
 
128
        yield self
 
129
        self.formatter.finished(filename)
 
130
 
 
131
    def _decision_for(self, code):
 
132
        # type: (Error) -> Decision
 
133
        select = find_first_match(code, self._all_selected)
 
134
        extra_select = find_first_match(code, self._extended_selected)
 
135
        ignore = find_first_match(code, self._ignored)
 
136
 
 
137
        if select and ignore:
 
138
            return find_more_specific(select, ignore)
 
139
        if extra_select and ignore:
 
140
            return find_more_specific(extra_select, ignore)
 
141
        if select or (extra_select and self._selected == defaults.SELECT):
 
142
            return Decision.Selected
 
143
        if select is None and extra_select is None and ignore is not None:
 
144
            return Decision.Ignored
 
145
        if self._selected != defaults.SELECT and select is None:
 
146
            return Decision.Ignored
 
147
        return Decision.Selected
 
148
 
 
149
    def should_report_error(self, code):
 
150
        # type: (str) -> Decision
 
151
        """Determine if the error code should be reported or ignored.
 
152
 
 
153
        This method only cares about the select and ignore rules as specified
 
154
        by the user in their configuration files and command-line flags.
 
155
 
 
156
        This method does not look at whether the specific line is being
 
157
        ignored in the file itself.
 
158
 
 
159
        :param str code:
 
160
            The code for the check that has been run.
 
161
        """
 
162
        decision = self._decision_cache.get(code)
 
163
        if decision is None:
 
164
            LOG.debug('Deciding if "%s" should be reported', code)
 
165
            selected = self.is_user_selected(code)
 
166
            ignored = self.is_user_ignored(code)
 
167
            LOG.debug('The user configured "%s" to be "%s", "%s"',
 
168
                      code, selected, ignored)
 
169
 
 
170
            if ((selected is Selected.Explicitly or
 
171
                 selected is Selected.Implicitly) and
 
172
                    ignored is Selected.Implicitly):
 
173
                decision = Decision.Selected
 
174
            elif ((selected is Selected.Explicitly and
 
175
                  ignored is Ignored.Explicitly) or
 
176
                  (selected is Ignored.Implicitly and
 
177
                   ignored is Selected.Implicitly)):
 
178
                decision = self._decision_for(code)
 
179
            elif (selected is Ignored.Implicitly or
 
180
                  ignored is Ignored.Explicitly):
 
181
                decision = Decision.Ignored  # pylint: disable=R0204
 
182
 
 
183
            self._decision_cache[code] = decision
 
184
            LOG.debug('"%s" will be "%s"', code, decision)
 
185
        return decision
 
186
 
 
187
    def is_inline_ignored(self, error):
 
188
        # type: (Error) -> bool
 
189
        """Determine if an comment has been added to ignore this line."""
 
190
        physical_line = error.physical_line
 
191
        # TODO(sigmavirus24): Determine how to handle stdin with linecache
 
192
        if self.options.disable_noqa:
 
193
            return False
 
194
 
 
195
        if physical_line is None:
 
196
            physical_line = linecache.getline(error.filename,
 
197
                                              error.line_number)
 
198
        noqa_match = defaults.NOQA_INLINE_REGEXP.search(physical_line)
 
199
        if noqa_match is None:
 
200
            LOG.debug('%r is not inline ignored', error)
 
201
            return False
 
202
 
 
203
        codes_str = noqa_match.groupdict()['codes']
 
204
        if codes_str is None:
 
205
            LOG.debug('%r is ignored by a blanket ``# noqa``', error)
 
206
            return True
 
207
 
 
208
        codes = set(utils.parse_comma_separated_list(codes_str))
 
209
        if error.code in codes or error.code.startswith(tuple(codes)):
 
210
            LOG.debug('%r is ignored specifically inline with ``# noqa: %s``',
 
211
                      error, codes_str)
 
212
            return True
 
213
 
 
214
        LOG.debug('%r is not ignored inline with ``# noqa: %s``',
 
215
                  error, codes_str)
 
216
        return False
 
217
 
 
218
    def is_in_diff(self, error):
 
219
        # type: (Error) -> bool
 
220
        """Determine if an error is included in a diff's line ranges.
 
221
 
 
222
        This function relies on the parsed data added via
 
223
        :meth:`~StyleGuide.add_diff_ranges`. If that has not been called and
 
224
        we are not evaluating files in a diff, then this will always return
 
225
        True. If there are diff ranges, then this will return True if the
 
226
        line number in the error falls inside one of the ranges for the file
 
227
        (and assuming the file is part of the diff data). If there are diff
 
228
        ranges, this will return False if the file is not part of the diff
 
229
        data or the line number of the error is not in any of the ranges of
 
230
        the diff.
 
231
 
 
232
        :returns:
 
233
            True if there is no diff or if the error is in the diff's line
 
234
            number ranges. False if the error's line number falls outside
 
235
            the diff's line number ranges.
 
236
        :rtype:
 
237
            bool
 
238
        """
 
239
        if not self._parsed_diff:
 
240
            return True
 
241
 
 
242
        # NOTE(sigmavirus24): The parsed diff will be a defaultdict with
 
243
        # a set as the default value (if we have received it from
 
244
        # flake8.utils.parse_unified_diff). In that case ranges below
 
245
        # could be an empty set (which is False-y) or if someone else
 
246
        # is using this API, it could be None. If we could guarantee one
 
247
        # or the other, we would check for it more explicitly.
 
248
        line_numbers = self._parsed_diff.get(error.filename)
 
249
        if not line_numbers:
 
250
            return False
 
251
 
 
252
        return error.line_number in line_numbers
 
253
 
 
254
    def handle_error(self, code, filename, line_number, column_number, text,
 
255
                     physical_line=None):
 
256
        # type: (str, str, int, int, str) -> int
 
257
        """Handle an error reported by a check.
 
258
 
 
259
        :param str code:
 
260
            The error code found, e.g., E123.
 
261
        :param str filename:
 
262
            The file in which the error was found.
 
263
        :param int line_number:
 
264
            The line number (where counting starts at 1) at which the error
 
265
            occurs.
 
266
        :param int column_number:
 
267
            The column number (where counting starts at 1) at which the error
 
268
            occurs.
 
269
        :param str text:
 
270
            The text of the error message.
 
271
        :param str physical_line:
 
272
            The actual physical line causing the error.
 
273
        :returns:
 
274
            1 if the error was reported. 0 if it was ignored. This is to allow
 
275
            for counting of the number of errors found that were not ignored.
 
276
        :rtype:
 
277
            int
 
278
        """
 
279
        # NOTE(sigmavirus24): Apparently we're provided with 0-indexed column
 
280
        # numbers so we have to offset that here. Also, if a SyntaxError is
 
281
        # caught, column_number may be None.
 
282
        if not column_number:
 
283
            column_number = 0
 
284
        error = Error(code, filename, line_number, column_number + 1, text,
 
285
                      physical_line)
 
286
        error_is_selected = (self.should_report_error(error.code) is
 
287
                             Decision.Selected)
 
288
        is_not_inline_ignored = self.is_inline_ignored(error) is False
 
289
        is_included_in_diff = self.is_in_diff(error)
 
290
        if (error_is_selected and is_not_inline_ignored and
 
291
                is_included_in_diff):
 
292
            self.formatter.handle(error)
 
293
            self.stats.record(error)
 
294
            self.listener.notify(error.code, error)
 
295
            return 1
 
296
        return 0
 
297
 
 
298
    def add_diff_ranges(self, diffinfo):
 
299
        """Update the StyleGuide to filter out information not in the diff.
 
300
 
 
301
        This provides information to the StyleGuide so that only the errors
 
302
        in the line number ranges are reported.
 
303
 
 
304
        :param dict diffinfo:
 
305
            Dictionary mapping filenames to sets of line number ranges.
 
306
        """
 
307
        self._parsed_diff = diffinfo
 
308
 
 
309
 
 
310
def find_more_specific(selected, ignored):
 
311
    if selected.startswith(ignored) and selected != ignored:
 
312
        return Decision.Selected
 
313
    return Decision.Ignored
 
314
 
 
315
 
 
316
def find_first_match(error_code, code_list):
 
317
    startswith = error_code.startswith
 
318
    for code in code_list:
 
319
        if startswith(code):
 
320
            break
 
321
    else:
 
322
        return None
 
323
    return code