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