Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(712)

Side by Side Diff: tools/strict_enum_value_checker/strict_enum_value_checker.py

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

Powered by Google App Engine
This is Rietveld 408576698