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

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: Update directory name 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 (c) 2012 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/chrome/browser/extensions. 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 GetPreferredTrySlaves(): 12 changes. There are situations where this class gives false positive warnings,
12 return ['linux_chromeos'] 13 i.e. it warns even though the edit is legitimate. Since the class warns using
13 14 prompt warnings, the user can always choose to continue. The main point is to
14 class HistogramValueChecker(object): 15 attract the attention to all (potentially or not) invalid edits.
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 16
24 """ 17 """
25 18 def __init__(self, input_api, output_api, start_marker, end_marker, path):
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 = " // Last entry:"
33
34 def __init__(self, input_api, output_api):
35 self.input_api = input_api 19 self.input_api = input_api
36 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
37 self.results = [] 24 self.results = []
38 25
39 class EnumRange(object): 26 class EnumRange(object):
40 """Represents a range of line numbers (1-based)""" 27 """Represents a range of line numbers (1-based)"""
41 def __init__(self, first_line, last_line): 28 def __init__(self, first_line, last_line):
42 self.first_line = first_line 29 self.first_line = first_line
43 self.last_line = last_line 30 self.last_line = last_line
44 31
45 def Count(self): 32 def Count(self):
46 return self.last_line - self.first_line + 1 33 return self.last_line - self.first_line + 1
47 34
48 def Contains(self, line_num): 35 def Contains(self, line_num):
49 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
50 37
51 def LogInfo(self, message): 38 def LogInfo(self, message):
52 self.input_api.logging.info(message) 39 self.input_api.logging.info(message)
53 return 40 return
54 41
55 def LogDebug(self, message): 42 def LogDebug(self, message):
56 self.input_api.logging.debug(message) 43 self.input_api.logging.debug(message)
57 return 44 return
58 45
59 def ComputeEnumRangeInContents(self, contents): 46 def ComputeEnumRangeInContents(self, contents):
60 """Returns an |EnumRange| object representing the line extent of the 47 """Returns an |EnumRange| object representing the line extent of the
61 HistogramValue enum members in |contents|. The line numbers are 1-based, 48 enum members in |contents|. The line numbers are 1-based,
62 compatible with line numbers returned by AffectedFile.ChangeContents(). 49 compatible with line numbers returned by AffectedFile.ChangeContents().
63 |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.
64 51
65 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
66 |contents|, returns None and emits detailed warnings about the problem. 53 |contents|, returns None and emits detailed warnings about the problem.
67 54
68 """ 55 """
69 first_enum_line = 0 56 first_enum_line = 0
70 last_enum_line = 0 57 last_enum_line = 0
71 line_num = 1 # Line numbers are 1-based 58 line_num = 1 # Line numbers are 1-based
72 for line in contents: 59 for line in contents:
73 if line.startswith(self.ENUM_START_MARKER): 60 if line.startswith(self.start_marker):
74 first_enum_line = line_num + 1 61 first_enum_line = line_num + 1
75 elif line.startswith(self.ENUM_END_MARKER): 62 elif line.startswith(self.end_marker):
76 last_enum_line = line_num 63 last_enum_line = line_num
77 line_num += 1 64 line_num += 1
78 65
79 if first_enum_line == 0: 66 if first_enum_line == 0:
80 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 "
81 "enum definition (\"%s\"). Did the enum definition " 68 "enum definition (\"%s\"). Did the enum definition "
82 "change?" % self.ENUM_START_MARKER) 69 "change?" % self.start_marker)
83 return None 70 return None
84 71
85 if last_enum_line == 0: 72 if last_enum_line == 0:
86 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 "
87 "enum definition (\"%s\"). Did the enum definition " 74 "enum definition (\"%s\"). Did the enum definition "
88 "change?" % self.ENUM_END_MARKER) 75 "change?" % self.end_marker)
89 return None 76 return None
90 77
91 if first_enum_line >= last_enum_line: 78 if first_enum_line >= last_enum_line:
92 self.EmitWarning("The presubmit script located the start of the enum " 79 self.EmitWarning("The presubmit script located the start of the enum "
93 "definition (\"%s\" at line %d) *after* its end " 80 "definition (\"%s\" at line %d) *after* its end "
94 "(\"%s\" at line %d). Something is not quite right." 81 "(\"%s\" at line %d). Something is not quite right."
95 % (self.ENUM_START_MARKER, first_enum_line, 82 % (self.start_marker, first_enum_line,
96 self.ENUM_END_MARKER, last_enum_line)) 83 self.end_marker, last_enum_line))
97 return None 84 return None
98 85
99 self.LogInfo("Line extent of |HistogramValue| enum definition: " 86 self.LogInfo("Line extent of (\"%s\") enum definition: "
100 "first_line=%d, last_line=%d." 87 "first_line=%d, last_line=%d."
101 % (first_enum_line, last_enum_line)) 88 % (self.start_marker, first_enum_line, last_enum_line))
102 return self.EnumRange(first_enum_line, last_enum_line) 89 return self.EnumRange(first_enum_line, last_enum_line)
103 90
104 def ComputeEnumRangeInNewFile(self, affected_file): 91 def ComputeEnumRangeInNewFile(self, affected_file):
105 return self.ComputeEnumRangeInContents(affected_file.NewContents()) 92 return self.ComputeEnumRangeInContents(affected_file.NewContents())
106 93
107 def GetLongMessage(self): 94 def GetLongMessage(self, local_path):
108 return str("The file \"%s\" contains the definition of the " 95 return str("The file \"%s\" contains the definition of the "
109 "|HistogramValue| enum which should be edited in specific ways " 96 "(\"%s\") enum which should be edited in specific ways "
110 "only - *** read the comments at the top of the header file ***" 97 "only - *** read the comments at the top of the header file ***"
111 ". There are changes to the file that may be incorrect and " 98 ". There are changes to the file that may be incorrect and "
112 "warrant manual confirmation after review. Note that this " 99 "warrant manual confirmation after review. Note that this "
113 "presubmit script can not reliably report the nature of all " 100 "presubmit script can not reliably report the nature of all "
114 "types of invalid changes, especially when the diffs are " 101 "types of invalid changes, especially when the diffs are "
115 "complex. For example, an invalid deletion may be reported " 102 "complex. For example, an invalid deletion may be reported "
116 "whereas the change contains a valid rename." 103 "whereas the change contains a valid rename."
117 % self.LOCAL_PATH) 104 % local_path, self.start_marker)
118 105
119 def EmitWarning(self, message, line_number=None, line_text=None): 106 def EmitWarning(self, message, line_number=None, line_text=None):
120 """Emits a presubmit prompt warning containing the short message 107 """Emits a presubmit prompt warning containing the short message
121 |message|. |item| is |LOCAL_PATH| with optional |line_number| and 108 |message|. |item| is |LOCAL_PATH| with optional |line_number| and
122 |line_text|. 109 |line_text|.
123 110
124 """ 111 """
125 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:
126 item = "%s(%d): %s" % (self.LOCAL_PATH, line_number, line_text) 113 item = "%s(%d): %s" % (self.path, line_number, line_text)
127 elif line_number is not None: 114 elif line_number is not None:
128 item = "%s(%d)" % (self.LOCAL_PATH, line_number) 115 item = "%s(%d)" % (self.path, line_number)
129 else: 116 else:
130 item = self.LOCAL_PATH 117 item = self.path
131 long_message = self.GetLongMessage() 118 long_message = self.GetLongMessage(self.path)
132 self.LogInfo(message) 119 self.LogInfo(message)
133 self.results.append( 120 self.results.append(
134 self.output_api.PresubmitPromptWarning(message, [item], long_message)) 121 self.output_api.PresubmitPromptWarning(message, [item], long_message))
135 122
136 def CollectRangesInsideEnumDefinition(self, affected_file, 123 def CollectRangesInsideEnumDefinition(self, affected_file,
137 first_line, last_line): 124 first_line, last_line):
138 """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
139 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|.
140 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
141 lines in the range. 128 lines in the range.
(...skipping 29 matching lines...) Expand all
171 if previous_range_start_line_number != 0: 158 if previous_range_start_line_number != 0:
172 addRange() 159 addRange()
173 return results 160 return results
174 161
175 def CheckForFileDeletion(self, affected_file): 162 def CheckForFileDeletion(self, affected_file):
176 """Emits a warning notification if file has been deleted """ 163 """Emits a warning notification if file has been deleted """
177 if not affected_file.NewContents(): 164 if not affected_file.NewContents():
178 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 "
179 "your intent is to really delete the file, the code in " 166 "your intent is to really delete the file, the code in "
180 "PRESUBMIT.py should be updated to remove the " 167 "PRESUBMIT.py should be updated to remove the "
181 "|HistogramValueChecker| class."); 168 "|StrictEnumValueChecker| class.");
182 return False 169 return False
183 return True 170 return True
184 171
185 def GetDeletedLinesFromScmDiff(self, affected_file): 172 def GetDeletedLinesFromScmDiff(self, affected_file):
186 """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
187 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
188 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
189 contain contiguous line number entries. To prevent false positives, we 176 contain contiguous line number entries. To prevent false positives, we
190 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
191 of the new file. 178 of the new file.
192 179
193 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
194 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
195 API. 182 API.
196 183
197 """ 184 """
198 results = [] 185 results = []
199 line_num = 0 186 line_num = 0
200 deleting_lines = False 187 deleting_lines = False
201 for line in affected_file.GenerateScmDiff().splitlines(): 188 for line in affected_file.GenerateScmDiff().splitlines():
202 # Parse the unified diff chunk optional section heading, which looks like 189 # Parse the unified diff chunk optional section heading, which looks like
203 # @@ -l,s +l,s @@ optional section heading 190 # @@ -l,s +l,s @@ optional section heading
204 m = self.input_api.re.match( 191 m = self.input_api.re.match(
205 r'^@@ \-([0-9]+)\,([0-9]+) \+([0-9]+)\,([0-9]+) @@', line) 192 r"^@@ \-([0-9]+)\,([0-9]+) \+([0-9]+)\,([0-9]+) @@", line)
206 if m: 193 if m:
207 old_line_num = int(m.group(1)) 194 old_line_num = int(m.group(1))
208 old_size = int(m.group(2)) 195 old_size = int(m.group(2))
209 new_line_num = int(m.group(3)) 196 new_line_num = int(m.group(3))
210 new_size = int(m.group(4)) 197 new_size = int(m.group(4))
211 line_num = new_line_num 198 line_num = new_line_num
212 # 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
213 # new file 200 # new file
214 deleting_lines = old_size > new_size 201 deleting_lines = old_size > new_size
215 continue 202 continue
216 if not line.startswith('-'): 203 if not line.startswith("-"):
217 line_num += 1 204 line_num += 1
218 if deleting_lines and line.startswith('-') and not line.startswith('--'): 205 if deleting_lines and line.startswith("-") and not line.startswith("--"):
219 results.append(line_num) 206 results.append(line_num)
220 return results 207 return results
221 208
222 def CheckForEnumEntryDeletions(self, affected_file): 209 def CheckForEnumEntryDeletions(self, affected_file):
223 """Look for deletions inside the enum definition. We currently use a 210 """Look for deletions inside the enum definition. We currently use a
224 simple heuristics (not 100% accurate): if there are deleted lines inside 211 simple heuristics (not 100% accurate): if there are deleted lines inside
225 the enum definition, this might be a deletion. 212 the enum definition, this might be a deletion.
226 213
227 """ 214 """
228 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
261 is_valid_edit = (line_end == last_line - 1) 248 is_valid_edit = (line_end == last_line - 1)
262 249
263 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 "
264 "ending at line number %d: valid=%s" 251 "ending at line number %d: valid=%s"
265 % (line_start, line_end, is_valid_edit)) 252 % (line_start, line_end, is_valid_edit))
266 253
267 if not is_valid_edit: 254 if not is_valid_edit:
268 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 "
269 "%d is *not* located *exactly* at the end of the " 256 "%d is *not* located *exactly* at the end of the "
270 "enum definition. Unless you are renaming an " 257 "enum definition. Unless you are renaming an "
271 "existing entry, this is not a valid changes, as new " 258 "existing entry, this is not a valid change, as new "
272 "entries should *always* be added at the end of the " 259 "entries should *always* be added at the end of the "
273 "enum definition, right before the 'ENUM_BOUNDARY' " 260 "enum definition, right before the \"%s\" "
274 "entry." % (line_start, line_end), 261 "entry." % (line_start, line_end, self.end_marker),
275 line_start, 262 line_start,
276 line_text) 263 line_text)
277 is_ok = False 264 is_ok = False
278 return is_ok 265 return is_ok
279 266
280 def PerformChecks(self, affected_file): 267 def PerformChecks(self, affected_file):
281 if not self.CheckForFileDeletion(affected_file): 268 if not self.CheckForFileDeletion(affected_file):
282 return 269 return
283 if not self.CheckForEnumEntryDeletions(affected_file): 270 if not self.CheckForEnumEntryDeletions(affected_file):
284 return 271 return
285 if not self.CheckForEnumEntryInsertions(affected_file): 272 if not self.CheckForEnumEntryInsertions(affected_file):
286 return 273 return
287 274
288 def ProcessHistogramValueFile(self, affected_file): 275 def ProcessHistogramValueFile(self, affected_file):
289 self.LogInfo("Start processing file \"%s\"" % affected_file.LocalPath()) 276 self.LogInfo("Start processing file \"%s\"" % affected_file.LocalPath())
290 self.PerformChecks(affected_file) 277 self.PerformChecks(affected_file)
291 self.LogInfo("Done processing file \"%s\"" % affected_file.LocalPath()) 278 self.LogInfo("Done processing file \"%s\"" % affected_file.LocalPath())
292 279
293 def Run(self): 280 def Run(self):
294 for file in self.input_api.AffectedFiles(include_deletes=True): 281 for file in self.input_api.AffectedFiles(include_deletes=True):
295 if file.LocalPath() == self.LOCAL_PATH: 282 if file.LocalPath() == self.path:
296 self.ProcessHistogramValueFile(file) 283 self.ProcessHistogramValueFile(file)
297 return self.results 284 return self.results
298
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
304
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698