- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / PRESUBMIT.py
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Chromium presubmit script for src/chrome/browser/extensions.
6
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details on the presubmit API built into gcl.
9 """
10
11 def GetPreferredTrySlaves():
12   return ['linux_chromeos']
13
14 class HistogramValueChecker(object):
15   """Verify that changes to "extension_function_histogram_value.h" are valid.
16
17   See comments at the top of the "extension_function_histogram_value.h" file
18   for what are considered valid changes. There are situations where this script
19   gives false positive warnings, i.e. it warns even though the edit is
20   legitimate. Since the script warns using prompt warnings, the user can always
21   choose to continue. The main point is to attract the attention to all
22   (potentially or not) invalid edits.
23
24   """
25
26   # The name of the file we want to check against
27   LOCAL_PATH = "chrome/browser/extensions/extension_function_histogram_value.h"
28
29   # The markers we look for in the above source file as delimiters of the enum
30   # definition.
31   ENUM_START_MARKER = "enum HistogramValue {"
32   ENUM_END_MARKER = "  ENUM_BOUNDARY"
33
34   def __init__(self, input_api, output_api):
35     self.input_api = input_api
36     self.output_api = output_api
37     self.results = []
38
39   class EnumRange(object):
40     """Represents a range of line numbers (1-based)"""
41     def __init__(self, first_line, last_line):
42       self.first_line = first_line
43       self.last_line = last_line
44
45     def Count(self):
46       return self.last_line - self.first_line + 1
47
48     def Contains(self, line_num):
49       return self.first_line <= line_num and line_num <= self.last_line
50
51   def LogInfo(self, message):
52     self.input_api.logging.info(message)
53     return
54
55   def LogDebug(self, message):
56     self.input_api.logging.debug(message)
57     return
58
59   def ComputeEnumRangeInContents(self, contents):
60     """Returns an |EnumRange| object representing the line extent of the
61     HistogramValue enum members in |contents|. The line numbers are 1-based,
62     compatible with line numbers returned by AffectedFile.ChangeContents().
63     |contents| is a list of strings reprenting the lines of a text file.
64
65     If either ENUM_START_MARKER or ENUM_END_MARKER cannot be found in
66     |contents|, returns None and emits detailed warnings about the problem.
67
68     """
69     first_enum_line = 0
70     last_enum_line = 0
71     line_num = 1  # Line numbers are 1-based
72     for line in contents:
73       if line.startswith(self.ENUM_START_MARKER):
74         first_enum_line = line_num + 1
75       elif line.startswith(self.ENUM_END_MARKER):
76         last_enum_line = line_num
77       line_num += 1
78
79     if first_enum_line == 0:
80       self.EmitWarning("The presubmit script could not find the start of the "
81                        "enum definition (\"%s\"). Did the enum definition "
82                        "change?" % self.ENUM_START_MARKER)
83       return None
84
85     if last_enum_line == 0:
86       self.EmitWarning("The presubmit script could not find the end of the "
87                        "enum definition (\"%s\"). Did the enum definition "
88                        "change?" % self.ENUM_END_MARKER)
89       return None
90
91     if first_enum_line >= last_enum_line:
92       self.EmitWarning("The presubmit script located the start of the enum "
93                        "definition (\"%s\" at line %d) *after* its end "
94                        "(\"%s\" at line %d). Something is not quite right."
95                        % (self.ENUM_START_MARKER, first_enum_line,
96                           self.ENUM_END_MARKER, last_enum_line))
97       return None
98
99     self.LogInfo("Line extent of |HistogramValue| enum definition: "
100                  "first_line=%d, last_line=%d."
101                  % (first_enum_line, last_enum_line))
102     return self.EnumRange(first_enum_line, last_enum_line)
103
104   def ComputeEnumRangeInNewFile(self, affected_file):
105     return self.ComputeEnumRangeInContents(affected_file.NewContents())
106
107   def GetLongMessage(self):
108     return str("The file \"%s\" contains the definition of the "
109                "|HistogramValue| enum which should be edited in specific ways "
110                "only - *** read the comments at the top of the header file ***"
111                ". There are changes to the file that may be incorrect and "
112                "warrant manual confirmation after review. Note that this "
113                "presubmit script can not reliably report the nature of all "
114                "types of invalid changes, especially when the diffs are "
115                "complex. For example, an invalid deletion may be reported "
116                "whereas the change contains a valid rename."
117                % self.LOCAL_PATH)
118
119   def EmitWarning(self, message, line_number=None, line_text=None):
120     """Emits a presubmit prompt warning containing the short message
121     |message|. |item| is |LOCAL_PATH| with optional |line_number| and
122     |line_text|.
123
124     """
125     if line_number is not None and line_text is not None:
126       item = "%s(%d): %s" % (self.LOCAL_PATH, line_number, line_text)
127     elif line_number is not None:
128       item = "%s(%d)" % (self.LOCAL_PATH, line_number)
129     else:
130       item = self.LOCAL_PATH
131     long_message = self.GetLongMessage()
132     self.LogInfo(message)
133     self.results.append(
134       self.output_api.PresubmitPromptWarning(message, [item], long_message))
135
136   def CollectRangesInsideEnumDefinition(self, affected_file,
137                                         first_line, last_line):
138     """Returns a list of triplet (line_start, line_end, line_text) of ranges of
139     edits changes. The |line_text| part is the text at line |line_start|.
140     Since it used only for reporting purposes, we do not need all the text
141     lines in the range.
142
143     """
144     results = []
145     previous_line_number = 0
146     previous_range_start_line_number = 0
147     previous_range_start_text = ""
148
149     def addRange():
150       tuple = (previous_range_start_line_number,
151                previous_line_number,
152                previous_range_start_text)
153       results.append(tuple)
154
155     for line_number, line_text in affected_file.ChangedContents():
156       if first_line <= line_number and line_number <= last_line:
157         self.LogDebug("Line change at line number " + str(line_number) + ": " +
158                       line_text)
159         # Start a new interval if none started
160         if previous_range_start_line_number == 0:
161           previous_range_start_line_number = line_number
162           previous_range_start_text = line_text
163         # Add new interval if we reached past the previous one
164         elif line_number != previous_line_number + 1:
165           addRange()
166           previous_range_start_line_number = line_number
167           previous_range_start_text = line_text
168         previous_line_number = line_number
169
170     # Add a last interval if needed
171     if previous_range_start_line_number != 0:
172         addRange()
173     return results
174
175   def CheckForFileDeletion(self, affected_file):
176     """Emits a warning notification if file has been deleted """
177     if not affected_file.NewContents():
178       self.EmitWarning("The file seems to be deleted in the changelist. If "
179                        "your intent is to really delete the file, the code in "
180                        "PRESUBMIT.py should be updated to remove the "
181                        "|HistogramValueChecker| class.");
182       return False
183     return True
184
185   def GetDeletedLinesFromScmDiff(self, affected_file):
186     """Return a list of of line numbers (1-based) corresponding to lines
187     deleted from the new source file (if they had been present in it). Note
188     that if multiple contiguous lines have been deleted, the returned list will
189     contain contiguous line number entries. To prevent false positives, we
190     return deleted line numbers *only* from diff chunks which decrease the size
191     of the new file.
192
193     Note: We need this method because we have access to neither the old file
194     content nor the list of "delete" changes from the current presubmit script
195     API.
196
197     """
198     results = []
199     line_num = 0
200     deleting_lines = False
201     for line in affected_file.GenerateScmDiff().splitlines():
202       # Parse the unified diff chunk optional section heading, which looks like
203       # @@ -l,s +l,s @@ optional section heading
204       m = self.input_api.re.match(
205         r'^@@ \-([0-9]+)\,([0-9]+) \+([0-9]+)\,([0-9]+) @@', line)
206       if m:
207         old_line_num = int(m.group(1))
208         old_size = int(m.group(2))
209         new_line_num = int(m.group(3))
210         new_size = int(m.group(4))
211         line_num = new_line_num
212         # Return line numbers only from diff chunks decreasing the size of the
213         # new file
214         deleting_lines = old_size > new_size
215         continue
216       if not line.startswith('-'):
217         line_num += 1
218       if deleting_lines and line.startswith('-') and not line.startswith('--'):
219         results.append(line_num)
220     return results
221
222   def CheckForEnumEntryDeletions(self, affected_file):
223     """Look for deletions inside the enum definition. We currently use a
224     simple heuristics (not 100% accurate): if there are deleted lines inside
225     the enum definition, this might be a deletion.
226
227     """
228     range_new = self.ComputeEnumRangeInNewFile(affected_file)
229     if not range_new:
230       return False
231
232     is_ok = True
233     for line_num in self.GetDeletedLinesFromScmDiff(affected_file):
234       if range_new.Contains(line_num):
235         self.EmitWarning("It looks like you are deleting line(s) from the "
236                          "enum definition. This should never happen.",
237                          line_num)
238         is_ok = False
239     return is_ok
240
241   def CheckForEnumEntryInsertions(self, affected_file):
242     range = self.ComputeEnumRangeInNewFile(affected_file)
243     if not range:
244       return False
245
246     first_line = range.first_line
247     last_line = range.last_line
248
249     # Collect the range of changes inside the enum definition range.
250     is_ok = True
251     for line_start, line_end, line_text in \
252           self.CollectRangesInsideEnumDefinition(affected_file,
253                                                  first_line,
254                                                  last_line):
255       # The only edit we consider valid is adding 1 or more entries *exactly*
256       # at the end of the enum definition. Every other edit inside the enum
257       # definition will result in a "warning confirmation" message.
258       #
259       # TODO(rpaquay): We currently cannot detect "renames" of existing entries
260       # vs invalid insertions, so we sometimes will warn for valid edits.
261       is_valid_edit = (line_end == last_line - 1)
262
263       self.LogDebug("Edit range in new file at starting at line number %d and "
264                     "ending at line number %d: valid=%s"
265                     % (line_start, line_end, is_valid_edit))
266
267       if not is_valid_edit:
268         self.EmitWarning("The change starting at line %d and ending at line "
269                          "%d is *not* located *exactly* at the end of the "
270                          "enum definition. Unless you are renaming an "
271                          "existing entry, this is not a valid changes, as new "
272                          "entries should *always* be added at the end of the "
273                          "enum definition, right before the 'ENUM_BOUNDARY' "
274                          "entry." % (line_start, line_end),
275                          line_start,
276                          line_text)
277         is_ok = False
278     return is_ok
279
280   def PerformChecks(self, affected_file):
281     if not self.CheckForFileDeletion(affected_file):
282       return
283     if not self.CheckForEnumEntryDeletions(affected_file):
284       return
285     if not self.CheckForEnumEntryInsertions(affected_file):
286       return
287
288   def ProcessHistogramValueFile(self, affected_file):
289     self.LogInfo("Start processing file \"%s\"" % affected_file.LocalPath())
290     self.PerformChecks(affected_file)
291     self.LogInfo("Done processing file \"%s\"" % affected_file.LocalPath())
292
293   def Run(self):
294     for file in self.input_api.AffectedFiles(include_deletes=True):
295       if file.LocalPath() == self.LOCAL_PATH:
296         self.ProcessHistogramValueFile(file)
297     return self.results
298
299 def CheckChangeOnUpload(input_api, output_api):
300     results = []
301     results += HistogramValueChecker(input_api, output_api).Run()
302     return results
303