Rename WebKitTools to Tools
[WebKit-https.git] / Tools / Scripts / webkitpy / style / error_handlers.py
1 # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
16 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 """Defines style error handler classes.
24
25 A style error handler is a function to call when a style error is
26 found. Style error handlers can also have state. A class that represents
27 a style error handler should implement the following methods.
28
29 Methods:
30
31   __call__(self, line_number, category, confidence, message):
32
33     Handle the occurrence of a style error.
34
35     Check whether the error is reportable. If so, increment the total
36     error count and report the details. Note that error reporting can
37     be suppressed after reaching a certain number of reports.
38
39     Args:
40       line_number: The integer line number of the line containing the error.
41       category: The name of the category of the error, for example
42                 "whitespace/newline".
43       confidence: An integer between 1 and 5 inclusive that represents the
44                   application's level of confidence in the error. The value
45                   5 means that we are certain of the problem, and the
46                   value 1 means that it could be a legitimate construct.
47       message: The error message to report.
48
49 """
50
51
52 import sys
53
54
55 class DefaultStyleErrorHandler(object):
56
57     """The default style error handler."""
58
59     def __init__(self, file_path, configuration, increment_error_count,
60                  line_numbers=None):
61         """Create a default style error handler.
62
63         Args:
64           file_path: The path to the file containing the error. This
65                      is used for reporting to the user.
66           configuration: A StyleProcessorConfiguration instance.
67           increment_error_count: A function that takes no arguments and
68                                  increments the total count of reportable
69                                  errors.
70           line_numbers: An array of line numbers of the lines for which
71                         style errors should be reported, or None if errors
72                         for all lines should be reported.  When it is not
73                         None, this array normally contains the line numbers
74                         corresponding to the modified lines of a patch.
75
76         """
77         if line_numbers is not None:
78             line_numbers = set(line_numbers)
79
80         self._file_path = file_path
81         self._configuration = configuration
82         self._increment_error_count = increment_error_count
83         self._line_numbers = line_numbers
84
85         # A string to integer dictionary cache of the number of reportable
86         # errors per category passed to this instance.
87         self._category_totals = {}
88
89     # Useful for unit testing.
90     def __eq__(self, other):
91         """Return whether this instance is equal to another."""
92         if self._configuration != other._configuration:
93             return False
94         if self._file_path != other._file_path:
95             return False
96         if self._increment_error_count != other._increment_error_count:
97             return False
98         if self._line_numbers != other._line_numbers:
99             return False
100
101         return True
102
103     # Useful for unit testing.
104     def __ne__(self, other):
105         # Python does not automatically deduce __ne__ from __eq__.
106         return not self.__eq__(other)
107
108     def _add_reportable_error(self, category):
109         """Increment the error count and return the new category total."""
110         self._increment_error_count() # Increment the total.
111
112         # Increment the category total.
113         if not category in self._category_totals:
114             self._category_totals[category] = 1
115         else:
116             self._category_totals[category] += 1
117
118         return self._category_totals[category]
119
120     def _max_reports(self, category):
121         """Return the maximum number of errors to report."""
122         if not category in self._configuration.max_reports_per_category:
123             return None
124         return self._configuration.max_reports_per_category[category]
125
126     def __call__(self, line_number, category, confidence, message):
127         """Handle the occurrence of a style error.
128
129         See the docstring of this module for more information.
130
131         """
132         if (self._line_numbers is not None and
133             line_number not in self._line_numbers):
134             # Then the error occurred in a line that was not modified, so
135             # the error is not reportable.
136             return
137
138         if not self._configuration.is_reportable(category=category,
139                                                  confidence_in_error=confidence,
140                                                  file_path=self._file_path):
141             return
142
143         category_total = self._add_reportable_error(category)
144
145         max_reports = self._max_reports(category)
146
147         if (max_reports is not None) and (category_total > max_reports):
148             # Then suppress displaying the error.
149             return
150
151         self._configuration.write_style_error(category=category,
152                                               confidence_in_error=confidence,
153                                               file_path=self._file_path,
154                                               line_number=line_number,
155                                               message=message)
156
157         if category_total == max_reports:
158             self._configuration.stderr_write("Suppressing further [%s] reports "
159                                              "for this file.\n" % category)