NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / extensions / PRESUBMIT.py
blobc8c16b5d2987e6c2559c5680eed813189ab0bbe5
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.
5 """Chromium presubmit script for src/chrome/browser/extensions.
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details on the presubmit API built into gcl.
9 """
11 def GetPreferredTrySlaves():
12 return ['linux_chromeos']
14 class HistogramValueChecker(object):
15 """Verify that changes to "extension_function_histogram_value.h" are valid.
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.
24 """
26 # The name of the file we want to check against
27 LOCAL_PATH = "chrome/browser/extensions/extension_function_histogram_value.h"
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"
34 def __init__(self, input_api, output_api):
35 self.input_api = input_api
36 self.output_api = output_api
37 self.results = []
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
45 def Count(self):
46 return self.last_line - self.first_line + 1
48 def Contains(self, line_num):
49 return self.first_line <= line_num and line_num <= self.last_line
51 def LogInfo(self, message):
52 self.input_api.logging.info(message)
53 return
55 def LogDebug(self, message):
56 self.input_api.logging.debug(message)
57 return
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.
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.
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
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
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
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
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)
104 def ComputeEnumRangeInNewFile(self, affected_file):
105 return self.ComputeEnumRangeInContents(affected_file.NewContents())
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)
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|.
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))
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.
144 results = []
145 previous_line_number = 0
146 previous_range_start_line_number = 0
147 previous_range_start_text = ""
149 def addRange():
150 tuple = (previous_range_start_line_number,
151 previous_line_number,
152 previous_range_start_text)
153 results.append(tuple)
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
170 # Add a last interval if needed
171 if previous_range_start_line_number != 0:
172 addRange()
173 return results
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
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.
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.
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
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.
228 range_new = self.ComputeEnumRangeInNewFile(affected_file)
229 if not range_new:
230 return False
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
241 def CheckForEnumEntryInsertions(self, affected_file):
242 range = self.ComputeEnumRangeInNewFile(affected_file)
243 if not range:
244 return False
246 first_line = range.first_line
247 last_line = range.last_line
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.
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)
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))
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
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
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())
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
299 def CheckChangeOnUpload(input_api, output_api):
300 results = []
301 results += HistogramValueChecker(input_api, output_api).Run()
302 results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
303 return results