PageRenderTime 75ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/hooks/webkitpy/style/checkers/cpp.py

https://github.com/hwti/LunaSysMgr
Python | 3580 lines | 2922 code | 212 blank | 446 comment | 298 complexity | 265650f2e883796370c6cad485c6051a MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Copyright (C) 2009, 2010 Google Inc. All rights reserved.
  5. # Copyright (C) 2009 Torch Mobile Inc.
  6. # Copyright (C) 2009 Apple Inc. All rights reserved.
  7. # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions are
  11. # met:
  12. #
  13. # * Redistributions of source code must retain the above copyright
  14. # notice, this list of conditions and the following disclaimer.
  15. # * Redistributions in binary form must reproduce the above
  16. # copyright notice, this list of conditions and the following disclaimer
  17. # in the documentation and/or other materials provided with the
  18. # distribution.
  19. # * Neither the name of Google Inc. nor the names of its
  20. # contributors may be used to endorse or promote products derived from
  21. # this software without specific prior written permission.
  22. #
  23. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  24. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  25. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  26. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  27. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  28. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  29. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  30. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  31. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  32. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  33. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34. # This is the modified version of Google's cpplint. The original code is
  35. # http://google-styleguide.googlecode.com/svn/trunk/cpplint/cpplint.py
  36. """Support for check-webkit-style."""
  37. import codecs
  38. import math # for log
  39. import os
  40. import os.path
  41. import re
  42. import sre_compile
  43. import string
  44. import sys
  45. import unicodedata
  46. from webkitpy.common.memoized import memoized
  47. # The key to use to provide a class to fake loading a header file.
  48. INCLUDE_IO_INJECTION_KEY = 'include_header_io'
  49. # Headers that we consider STL headers.
  50. _STL_HEADERS = frozenset([
  51. 'algobase.h', 'algorithm', 'alloc.h', 'bitset', 'deque', 'exception',
  52. 'function.h', 'functional', 'hash_map', 'hash_map.h', 'hash_set',
  53. 'hash_set.h', 'iterator', 'list', 'list.h', 'map', 'memory', 'pair.h',
  54. 'pthread_alloc', 'queue', 'set', 'set.h', 'sstream', 'stack',
  55. 'stl_alloc.h', 'stl_relops.h', 'type_traits.h',
  56. 'utility', 'vector', 'vector.h',
  57. ])
  58. # Non-STL C++ system headers.
  59. _CPP_HEADERS = frozenset([
  60. 'algo.h', 'builtinbuf.h', 'bvector.h', 'cassert', 'cctype',
  61. 'cerrno', 'cfloat', 'ciso646', 'climits', 'clocale', 'cmath',
  62. 'complex', 'complex.h', 'csetjmp', 'csignal', 'cstdarg', 'cstddef',
  63. 'cstdio', 'cstdlib', 'cstring', 'ctime', 'cwchar', 'cwctype',
  64. 'defalloc.h', 'deque.h', 'editbuf.h', 'exception', 'fstream',
  65. 'fstream.h', 'hashtable.h', 'heap.h', 'indstream.h', 'iomanip',
  66. 'iomanip.h', 'ios', 'iosfwd', 'iostream', 'iostream.h', 'istream.h',
  67. 'iterator.h', 'limits', 'map.h', 'multimap.h', 'multiset.h',
  68. 'numeric', 'ostream.h', 'parsestream.h', 'pfstream.h', 'PlotFile.h',
  69. 'procbuf.h', 'pthread_alloc.h', 'rope', 'rope.h', 'ropeimpl.h',
  70. 'SFile.h', 'slist', 'slist.h', 'stack.h', 'stdexcept',
  71. 'stdiostream.h', 'streambuf.h', 'stream.h', 'strfile.h', 'string',
  72. 'strstream', 'strstream.h', 'tempbuf.h', 'tree.h', 'typeinfo', 'valarray',
  73. ])
  74. # Assertion macros. These are defined in base/logging.h and
  75. # testing/base/gunit.h. Note that the _M versions need to come first
  76. # for substring matching to work.
  77. _CHECK_MACROS = [
  78. 'DCHECK', 'CHECK',
  79. 'EXPECT_TRUE_M', 'EXPECT_TRUE',
  80. 'ASSERT_TRUE_M', 'ASSERT_TRUE',
  81. 'EXPECT_FALSE_M', 'EXPECT_FALSE',
  82. 'ASSERT_FALSE_M', 'ASSERT_FALSE',
  83. ]
  84. # Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE
  85. _CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])
  86. for op, replacement in [('==', 'EQ'), ('!=', 'NE'),
  87. ('>=', 'GE'), ('>', 'GT'),
  88. ('<=', 'LE'), ('<', 'LT')]:
  89. _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement
  90. _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement
  91. _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement
  92. _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement
  93. _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement
  94. _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement
  95. for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'),
  96. ('>=', 'LT'), ('>', 'LE'),
  97. ('<=', 'GT'), ('<', 'GE')]:
  98. _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement
  99. _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement
  100. _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement
  101. _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement
  102. # These constants define types of headers for use with
  103. # _IncludeState.check_next_include_order().
  104. _CONFIG_HEADER = 0
  105. _PRIMARY_HEADER = 1
  106. _OTHER_HEADER = 2
  107. _MOC_HEADER = 3
  108. # A dictionary of items customize behavior for unit test. For example,
  109. # INCLUDE_IO_INJECTION_KEY allows providing a custom io class which allows
  110. # for faking a header file.
  111. _unit_test_config = {}
  112. # The regexp compilation caching is inlined in all regexp functions for
  113. # performance reasons; factoring it out into a separate function turns out
  114. # to be noticeably expensive.
  115. _regexp_compile_cache = {}
  116. def match(pattern, s):
  117. """Matches the string with the pattern, caching the compiled regexp."""
  118. if not pattern in _regexp_compile_cache:
  119. _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
  120. return _regexp_compile_cache[pattern].match(s)
  121. def search(pattern, s):
  122. """Searches the string for the pattern, caching the compiled regexp."""
  123. if not pattern in _regexp_compile_cache:
  124. _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
  125. return _regexp_compile_cache[pattern].search(s)
  126. def sub(pattern, replacement, s):
  127. """Substitutes occurrences of a pattern, caching the compiled regexp."""
  128. if not pattern in _regexp_compile_cache:
  129. _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
  130. return _regexp_compile_cache[pattern].sub(replacement, s)
  131. def subn(pattern, replacement, s):
  132. """Substitutes occurrences of a pattern, caching the compiled regexp."""
  133. if not pattern in _regexp_compile_cache:
  134. _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
  135. return _regexp_compile_cache[pattern].subn(replacement, s)
  136. def iteratively_replace_matches_with_char(pattern, char_replacement, s):
  137. """Returns the string with replacement done.
  138. Every character in the match is replaced with char.
  139. Due to the iterative nature, pattern should not match char or
  140. there will be an infinite loop.
  141. Example:
  142. pattern = r'<[^>]>' # template parameters
  143. char_replacement = '_'
  144. s = 'A<B<C, D>>'
  145. Returns 'A_________'
  146. Args:
  147. pattern: The regex to match.
  148. char_replacement: The character to put in place of every
  149. character of the match.
  150. s: The string on which to do the replacements.
  151. Returns:
  152. True, if the given line is blank.
  153. """
  154. while True:
  155. matched = search(pattern, s)
  156. if not matched:
  157. return s
  158. start_match_index = matched.start(0)
  159. end_match_index = matched.end(0)
  160. match_length = end_match_index - start_match_index
  161. s = s[:start_match_index] + char_replacement * match_length + s[end_match_index:]
  162. def _rfind_in_lines(regex, lines, start_position, not_found_position):
  163. """Does a reverse find starting at start position and going backwards until
  164. a match is found.
  165. Returns the position where the regex ended.
  166. """
  167. # Put the regex in a group and proceed it with a greedy expression that
  168. # matches anything to ensure that we get the last possible match in a line.
  169. last_in_line_regex = r'.*(' + regex + ')'
  170. current_row = start_position.row
  171. # Start with the given row and trim off everything past what may be matched.
  172. current_line = lines[start_position.row][:start_position.column]
  173. while True:
  174. found_match = match(last_in_line_regex, current_line)
  175. if found_match:
  176. return Position(current_row, found_match.end(1))
  177. # A match was not found so continue backward.
  178. current_row -= 1
  179. if current_row < 0:
  180. return not_found_position
  181. current_line = lines[current_row]
  182. def _convert_to_lower_with_underscores(text):
  183. """Converts all text strings in camelCase or PascalCase to lowers with underscores."""
  184. # First add underscores before any capital letter followed by a lower case letter
  185. # as long as it is in a word.
  186. # (This put an underscore before Password but not P and A in WPAPassword).
  187. text = sub(r'(?<=[A-Za-z0-9])([A-Z])(?=[a-z])', r'_\1', text)
  188. # Next add underscores before capitals at the end of words if it was
  189. # preceeded by lower case letter or number.
  190. # (This puts an underscore before A in isA but not A in CBA).
  191. text = sub(r'(?<=[a-z0-9])([A-Z])(?=\b)', r'_\1', text)
  192. # Next add underscores when you have a captial letter which is followed by a capital letter
  193. # but is not proceeded by one. (This puts an underscore before A in 'WordADay').
  194. text = sub(r'(?<=[a-z0-9])([A-Z][A-Z_])', r'_\1', text)
  195. return text.lower()
  196. def _create_acronym(text):
  197. """Creates an acronym for the given text."""
  198. # Removes all lower case letters except those starting words.
  199. text = sub(r'(?<!\b)[a-z]', '', text)
  200. return text.upper()
  201. def up_to_unmatched_closing_paren(s):
  202. """Splits a string into two parts up to first unmatched ')'.
  203. Args:
  204. s: a string which is a substring of line after '('
  205. (e.g., "a == (b + c))").
  206. Returns:
  207. A pair of strings (prefix before first unmatched ')',
  208. remainder of s after first unmatched ')'), e.g.,
  209. up_to_unmatched_closing_paren("a == (b + c)) { ")
  210. returns "a == (b + c)", " {".
  211. Returns None, None if there is no unmatched ')'
  212. """
  213. i = 1
  214. for pos, c in enumerate(s):
  215. if c == '(':
  216. i += 1
  217. elif c == ')':
  218. i -= 1
  219. if i == 0:
  220. return s[:pos], s[pos + 1:]
  221. return None, None
  222. class _IncludeState(dict):
  223. """Tracks line numbers for includes, and the order in which includes appear.
  224. As a dict, an _IncludeState object serves as a mapping between include
  225. filename and line number on which that file was included.
  226. Call check_next_include_order() once for each header in the file, passing
  227. in the type constants defined above. Calls in an illegal order will
  228. raise an _IncludeError with an appropriate error message.
  229. """
  230. # self._section will move monotonically through this set. If it ever
  231. # needs to move backwards, check_next_include_order will raise an error.
  232. _INITIAL_SECTION = 0
  233. _CONFIG_SECTION = 1
  234. _PRIMARY_SECTION = 2
  235. _OTHER_SECTION = 3
  236. _TYPE_NAMES = {
  237. _CONFIG_HEADER: '',
  238. _PRIMARY_HEADER: 'header this file implements',
  239. _OTHER_HEADER: 'other header',
  240. _MOC_HEADER: 'moc file',
  241. }
  242. _SECTION_NAMES = {
  243. _INITIAL_SECTION: "... nothing.",
  244. _CONFIG_SECTION: "WebCore config.h.",
  245. _PRIMARY_SECTION: 'a header this file implements.',
  246. _OTHER_SECTION: 'other header.',
  247. }
  248. def __init__(self):
  249. dict.__init__(self)
  250. self._section = self._INITIAL_SECTION
  251. self._visited_primary_section = False
  252. self.header_types = dict();
  253. def visited_primary_section(self):
  254. return self._visited_primary_section
  255. def check_next_include_order(self, header_type, file_is_header, primary_header_exists):
  256. """Returns a non-empty error message if the next header is out of order.
  257. This function also updates the internal state to be ready to check
  258. the next include.
  259. Args:
  260. header_type: One of the _XXX_HEADER constants defined above.
  261. file_is_header: Whether the file that owns this _IncludeState is itself a header
  262. Returns:
  263. The empty string if the header is in the right order, or an
  264. error message describing what's wrong.
  265. """
  266. if header_type == _CONFIG_HEADER and file_is_header:
  267. return 'Header file should not contain WebCore config.h.'
  268. if header_type == _PRIMARY_HEADER and file_is_header:
  269. return 'Header file should not contain itself.'
  270. if header_type == _MOC_HEADER:
  271. return ''
  272. error_message = ''
  273. if self._section != self._OTHER_SECTION:
  274. before_error_message = ('Found %s before %s' %
  275. (self._TYPE_NAMES[header_type],
  276. self._SECTION_NAMES[self._section + 1]))
  277. after_error_message = ('Found %s after %s' %
  278. (self._TYPE_NAMES[header_type],
  279. self._SECTION_NAMES[self._section]))
  280. if header_type == _CONFIG_HEADER:
  281. if self._section >= self._CONFIG_SECTION:
  282. error_message = after_error_message
  283. self._section = self._CONFIG_SECTION
  284. elif header_type == _PRIMARY_HEADER:
  285. if self._section >= self._PRIMARY_SECTION:
  286. error_message = after_error_message
  287. # elif self._section < self._CONFIG_SECTION:
  288. # error_message = before_error_message
  289. self._section = self._PRIMARY_SECTION
  290. self._visited_primary_section = True
  291. else:
  292. assert header_type == _OTHER_HEADER
  293. if not file_is_header and self._section < self._PRIMARY_SECTION:
  294. if primary_header_exists:
  295. error_message = before_error_message
  296. self._section = self._OTHER_SECTION
  297. return error_message
  298. class Position(object):
  299. """Holds the position of something."""
  300. def __init__(self, row, column):
  301. self.row = row
  302. self.column = column
  303. def __str__(self):
  304. return '(%s, %s)' % (self.row, self.column)
  305. def __cmp__(self, other):
  306. return self.row.__cmp__(other.row) or self.column.__cmp__(other.column)
  307. class Parameter(object):
  308. """Information about one function parameter."""
  309. def __init__(self, parameter, parameter_name_index, row):
  310. self.type = parameter[:parameter_name_index].strip()
  311. # Remove any initializers from the parameter name (e.g. int i = 5).
  312. self.name = sub(r'=.*', '', parameter[parameter_name_index:]).strip()
  313. self.row = row
  314. @memoized
  315. def lower_with_underscores_name(self):
  316. """Returns the parameter name in the lower with underscores format."""
  317. return _convert_to_lower_with_underscores(self.name)
  318. class SingleLineView(object):
  319. """Converts multiple lines into a single line (with line breaks replaced by a
  320. space) to allow for easier searching."""
  321. def __init__(self, lines, start_position, end_position):
  322. """Create a SingleLineView instance.
  323. Args:
  324. lines: a list of multiple lines to combine into a single line.
  325. start_position: offset within lines of where to start the single line.
  326. end_position: just after where to end (like a slice operation).
  327. """
  328. # Get the rows of interest.
  329. trimmed_lines = lines[start_position.row:end_position.row + 1]
  330. # Remove the columns on the last line that aren't included.
  331. trimmed_lines[-1] = trimmed_lines[-1][:end_position.column]
  332. # Remove the columns on the first line that aren't included.
  333. trimmed_lines[0] = trimmed_lines[0][start_position.column:]
  334. # Create a single line with all of the parameters.
  335. self.single_line = ' '.join(trimmed_lines)
  336. # Keep the row lengths, so we can calculate the original row number
  337. # given a column in the single line (adding 1 due to the space added
  338. # during the join).
  339. self._row_lengths = [len(line) + 1 for line in trimmed_lines]
  340. self._starting_row = start_position.row
  341. def convert_column_to_row(self, single_line_column_number):
  342. """Convert the column number from the single line into the original
  343. line number.
  344. Special cases:
  345. * Columns in the added spaces are considered part of the previous line.
  346. * Columns beyond the end of the line are consider part the last line
  347. in the view."""
  348. total_columns = 0
  349. row_offset = 0
  350. while row_offset < len(self._row_lengths) - 1 and single_line_column_number >= total_columns + self._row_lengths[row_offset]:
  351. total_columns += self._row_lengths[row_offset]
  352. row_offset += 1
  353. return self._starting_row + row_offset
  354. def create_skeleton_parameters(all_parameters):
  355. """Converts a parameter list to a skeleton version.
  356. The skeleton only has one word for the parameter name, one word for the type,
  357. and commas after each parameter and only there. Everything in the skeleton
  358. remains in the same columns as the original."""
  359. all_simplifications = (
  360. # Remove template parameters, function declaration parameters, etc.
  361. r'(<[^<>]*?>)|(\([^\(\)]*?\))|(\{[^\{\}]*?\})',
  362. # Remove all initializers.
  363. r'=[^,]*',
  364. # Remove :: and everything before it.
  365. r'[^,]*::',
  366. # Remove modifiers like &, *.
  367. r'[&*]',
  368. # Remove const modifiers.
  369. r'\bconst\s+(?=[A-Za-z])',
  370. # Remove numerical modifiers like long.
  371. r'\b(unsigned|long|short)\s+(?=unsigned|long|short|int|char|double|float)')
  372. skeleton_parameters = all_parameters
  373. for simplification in all_simplifications:
  374. skeleton_parameters = iteratively_replace_matches_with_char(simplification, ' ', skeleton_parameters)
  375. # If there are any parameters, then add a , after the last one to
  376. # make a regular pattern of a , following every parameter.
  377. if skeleton_parameters.strip():
  378. skeleton_parameters += ','
  379. return skeleton_parameters
  380. def find_parameter_name_index(skeleton_parameter):
  381. """Determines where the parametere name starts given the skeleton parameter."""
  382. # The first space from the right in the simplified parameter is where the parameter
  383. # name starts unless the first space is before any content in the simplified parameter.
  384. before_name_index = skeleton_parameter.rstrip().rfind(' ')
  385. if before_name_index != -1 and skeleton_parameter[:before_name_index].strip():
  386. return before_name_index + 1
  387. return len(skeleton_parameter)
  388. def parameter_list(elided_lines, start_position, end_position):
  389. """Generator for a function's parameters."""
  390. # Create new positions that omit the outer parenthesis of the parameters.
  391. start_position = Position(row=start_position.row, column=start_position.column + 1)
  392. end_position = Position(row=end_position.row, column=end_position.column - 1)
  393. single_line_view = SingleLineView(elided_lines, start_position, end_position)
  394. skeleton_parameters = create_skeleton_parameters(single_line_view.single_line)
  395. end_index = -1
  396. while True:
  397. # Find the end of the next parameter.
  398. start_index = end_index + 1
  399. end_index = skeleton_parameters.find(',', start_index)
  400. # No comma means that all parameters have been parsed.
  401. if end_index == -1:
  402. return
  403. row = single_line_view.convert_column_to_row(end_index)
  404. # Parse the parameter into a type and parameter name.
  405. skeleton_parameter = skeleton_parameters[start_index:end_index]
  406. name_offset = find_parameter_name_index(skeleton_parameter)
  407. parameter = single_line_view.single_line[start_index:end_index]
  408. yield Parameter(parameter, name_offset, row)
  409. class _FunctionState(object):
  410. """Tracks current function name and the number of lines in its body.
  411. Attributes:
  412. min_confidence: The minimum confidence level to use while checking style.
  413. """
  414. _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc.
  415. _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER.
  416. def __init__(self, min_confidence):
  417. self.min_confidence = min_confidence
  418. self.current_function = ''
  419. self.in_a_function = False
  420. self.lines_in_function = 0
  421. # Make sure these will not be mistaken for real positions (even when a
  422. # small amount is added to them).
  423. self.body_start_position = Position(-1000, 0)
  424. self.end_position = Position(-1000, 0)
  425. def begin(self, function_name, function_name_start_position, body_start_position, end_position,
  426. parameter_start_position, parameter_end_position, clean_lines):
  427. """Start analyzing function body.
  428. Args:
  429. function_name: The name of the function being tracked.
  430. function_name_start_position: Position in elided where the function name starts.
  431. body_start_position: Position in elided of the { or the ; for a prototype.
  432. end_position: Position in elided just after the final } (or ; is.
  433. parameter_start_position: Position in elided of the '(' for the parameters.
  434. parameter_end_position: Position in elided just after the ')' for the parameters.
  435. clean_lines: A CleansedLines instance containing the file.
  436. """
  437. self.in_a_function = True
  438. self.lines_in_function = -1 # Don't count the open brace line.
  439. self.current_function = function_name
  440. self.function_name_start_position = function_name_start_position
  441. self.body_start_position = body_start_position
  442. self.end_position = end_position
  443. self.is_declaration = clean_lines.elided[body_start_position.row][body_start_position.column] == ';'
  444. self.parameter_start_position = parameter_start_position
  445. self.parameter_end_position = parameter_end_position
  446. self.is_pure = False
  447. if self.is_declaration:
  448. characters_after_parameters = SingleLineView(clean_lines.elided, parameter_end_position, body_start_position).single_line
  449. self.is_pure = bool(match(r'\s*=\s*0\s*', characters_after_parameters))
  450. self._clean_lines = clean_lines
  451. self._parameter_list = None
  452. def modifiers_and_return_type(self):
  453. """Returns the modifiers and the return type."""
  454. # Go backwards from where the function name is until we encounter one of several things:
  455. # ';' or '{' or '}' or 'private:', etc. or '#' or return Position(0, 0)
  456. elided = self._clean_lines.elided
  457. start_modifiers = _rfind_in_lines(r';|\{|\}|((private|public|protected):)|(#.*)',
  458. elided, self.parameter_start_position, Position(0, 0))
  459. return SingleLineView(elided, start_modifiers, self.function_name_start_position).single_line.strip()
  460. def parameter_list(self):
  461. if not self._parameter_list:
  462. # Store the final result as a tuple since that is immutable.
  463. self._parameter_list = tuple(parameter_list(self._clean_lines.elided, self.parameter_start_position, self.parameter_end_position))
  464. return self._parameter_list
  465. def count(self, line_number):
  466. """Count line in current function body."""
  467. if self.in_a_function and line_number >= self.body_start_position.row:
  468. self.lines_in_function += 1
  469. def check(self, error, line_number):
  470. """Report if too many lines in function body.
  471. Args:
  472. error: The function to call with any errors found.
  473. line_number: The number of the line to check.
  474. """
  475. if match(r'T(EST|est)', self.current_function):
  476. base_trigger = self._TEST_TRIGGER
  477. else:
  478. base_trigger = self._NORMAL_TRIGGER
  479. trigger = base_trigger * 2 ** self.min_confidence
  480. if self.lines_in_function > trigger:
  481. error_level = int(math.log(self.lines_in_function / base_trigger, 2))
  482. # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ...
  483. if error_level > 5:
  484. error_level = 5
  485. error(line_number, 'readability/fn_size', error_level,
  486. 'Small and focused functions are preferred:'
  487. ' %s has %d non-comment lines'
  488. ' (error triggered by exceeding %d lines).' % (
  489. self.current_function, self.lines_in_function, trigger))
  490. def end(self):
  491. """Stop analyzing function body."""
  492. self.in_a_function = False
  493. class _IncludeError(Exception):
  494. """Indicates a problem with the include order in a file."""
  495. pass
  496. class FileInfo:
  497. """Provides utility functions for filenames.
  498. FileInfo provides easy access to the components of a file's path
  499. relative to the project root.
  500. """
  501. def __init__(self, filename):
  502. self._filename = filename
  503. def full_name(self):
  504. """Make Windows paths like Unix."""
  505. return os.path.abspath(self._filename).replace('\\', '/')
  506. def repository_name(self):
  507. """Full name after removing the local path to the repository.
  508. If we have a real absolute path name here we can try to do something smart:
  509. detecting the root of the checkout and truncating /path/to/checkout from
  510. the name so that we get header guards that don't include things like
  511. "C:\Documents and Settings\..." or "/home/username/..." in them and thus
  512. people on different computers who have checked the source out to different
  513. locations won't see bogus errors.
  514. """
  515. fullname = self.full_name()
  516. if os.path.exists(fullname):
  517. project_dir = os.path.dirname(fullname)
  518. if os.path.exists(os.path.join(project_dir, ".svn")):
  519. # If there's a .svn file in the current directory, we
  520. # recursively look up the directory tree for the top
  521. # of the SVN checkout
  522. root_dir = project_dir
  523. one_up_dir = os.path.dirname(root_dir)
  524. while os.path.exists(os.path.join(one_up_dir, ".svn")):
  525. root_dir = os.path.dirname(root_dir)
  526. one_up_dir = os.path.dirname(one_up_dir)
  527. prefix = os.path.commonprefix([root_dir, project_dir])
  528. return fullname[len(prefix) + 1:]
  529. # Not SVN? Try to find a git top level directory by
  530. # searching up from the current path.
  531. root_dir = os.path.dirname(fullname)
  532. while (root_dir != os.path.dirname(root_dir)
  533. and not os.path.exists(os.path.join(root_dir, ".git"))):
  534. root_dir = os.path.dirname(root_dir)
  535. if os.path.exists(os.path.join(root_dir, ".git")):
  536. prefix = os.path.commonprefix([root_dir, project_dir])
  537. return fullname[len(prefix) + 1:]
  538. # Don't know what to do; header guard warnings may be wrong...
  539. return fullname
  540. def split(self):
  541. """Splits the file into the directory, basename, and extension.
  542. For 'chrome/browser/browser.cpp', Split() would
  543. return ('chrome/browser', 'browser', '.cpp')
  544. Returns:
  545. A tuple of (directory, basename, extension).
  546. """
  547. googlename = self.repository_name()
  548. project, rest = os.path.split(googlename)
  549. return (project,) + os.path.splitext(rest)
  550. def base_name(self):
  551. """File base name - text after the final slash, before the final period."""
  552. return self.split()[1]
  553. def extension(self):
  554. """File extension - text following the final period."""
  555. return self.split()[2]
  556. def no_extension(self):
  557. """File has no source file extension."""
  558. return '/'.join(self.split()[0:2])
  559. def is_source(self):
  560. """File has a source file extension."""
  561. return self.extension()[1:] in ('c', 'cc', 'cpp', 'cxx')
  562. # Matches standard C++ escape esequences per 2.13.2.3 of the C++ standard.
  563. _RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(
  564. r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)')
  565. # Matches strings. Escape codes should already be removed by ESCAPES.
  566. _RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES = re.compile(r'"[^"]*"')
  567. # Matches characters. Escape codes should already be removed by ESCAPES.
  568. _RE_PATTERN_CLEANSE_LINE_SINGLE_QUOTES = re.compile(r"'.'")
  569. # Matches multi-line C++ comments.
  570. # This RE is a little bit more complicated than one might expect, because we
  571. # have to take care of space removals tools so we can handle comments inside
  572. # statements better.
  573. # The current rule is: We only clear spaces from both sides when we're at the
  574. # end of the line. Otherwise, we try to remove spaces from the right side,
  575. # if this doesn't work we try on left side but only if there's a non-character
  576. # on the right.
  577. _RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile(
  578. r"""(\s*/\*.*\*/\s*$|
  579. /\*.*\*/\s+|
  580. \s+/\*.*\*/(?=\W)|
  581. /\*.*\*/)""", re.VERBOSE)
  582. def is_cpp_string(line):
  583. """Does line terminate so, that the next symbol is in string constant.
  584. This function does not consider single-line nor multi-line comments.
  585. Args:
  586. line: is a partial line of code starting from the 0..n.
  587. Returns:
  588. True, if next character appended to 'line' is inside a
  589. string constant.
  590. """
  591. line = line.replace(r'\\', 'XX') # after this, \\" does not match to \"
  592. return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1
  593. def find_next_multi_line_comment_start(lines, line_index):
  594. """Find the beginning marker for a multiline comment."""
  595. while line_index < len(lines):
  596. if lines[line_index].strip().startswith('/*'):
  597. # Only return this marker if the comment goes beyond this line
  598. if lines[line_index].strip().find('*/', 2) < 0:
  599. return line_index
  600. line_index += 1
  601. return len(lines)
  602. def find_next_multi_line_comment_end(lines, line_index):
  603. """We are inside a comment, find the end marker."""
  604. while line_index < len(lines):
  605. if lines[line_index].strip().endswith('*/'):
  606. return line_index
  607. line_index += 1
  608. return len(lines)
  609. def remove_multi_line_comments_from_range(lines, begin, end):
  610. """Clears a range of lines for multi-line comments."""
  611. # Having // dummy comments makes the lines non-empty, so we will not get
  612. # unnecessary blank line warnings later in the code.
  613. for i in range(begin, end):
  614. lines[i] = '// dummy'
  615. def remove_multi_line_comments(lines, error):
  616. """Removes multiline (c-style) comments from lines."""
  617. line_index = 0
  618. while line_index < len(lines):
  619. line_index_begin = find_next_multi_line_comment_start(lines, line_index)
  620. if line_index_begin >= len(lines):
  621. return
  622. line_index_end = find_next_multi_line_comment_end(lines, line_index_begin)
  623. if line_index_end >= len(lines):
  624. error(line_index_begin + 1, 'readability/multiline_comment', 5,
  625. 'Could not find end of multi-line comment')
  626. return
  627. remove_multi_line_comments_from_range(lines, line_index_begin, line_index_end + 1)
  628. line_index = line_index_end + 1
  629. def cleanse_comments(line):
  630. """Removes //-comments and single-line C-style /* */ comments.
  631. Args:
  632. line: A line of C++ source.
  633. Returns:
  634. The line with single-line comments removed.
  635. """
  636. comment_position = line.find('//')
  637. if comment_position != -1 and not is_cpp_string(line[:comment_position]):
  638. line = line[:comment_position]
  639. # get rid of /* ... */
  640. return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line)
  641. class CleansedLines(object):
  642. """Holds 3 copies of all lines with different preprocessing applied to them.
  643. 1) elided member contains lines without strings and comments,
  644. 2) lines member contains lines without comments, and
  645. 3) raw member contains all the lines without processing.
  646. All these three members are of <type 'list'>, and of the same length.
  647. """
  648. def __init__(self, lines):
  649. self.elided = []
  650. self.lines = []
  651. self.raw_lines = lines
  652. self._num_lines = len(lines)
  653. for line_number in range(len(lines)):
  654. self.lines.append(cleanse_comments(lines[line_number]))
  655. elided = self.collapse_strings(lines[line_number])
  656. self.elided.append(cleanse_comments(elided))
  657. def num_lines(self):
  658. """Returns the number of lines represented."""
  659. return self._num_lines
  660. @staticmethod
  661. def collapse_strings(elided):
  662. """Collapses strings and chars on a line to simple "" or '' blocks.
  663. We nix strings first so we're not fooled by text like '"http://"'
  664. Args:
  665. elided: The line being processed.
  666. Returns:
  667. The line with collapsed strings.
  668. """
  669. if not _RE_PATTERN_INCLUDE.match(elided):
  670. # Remove escaped characters first to make quote/single quote collapsing
  671. # basic. Things that look like escaped characters shouldn't occur
  672. # outside of strings and chars.
  673. elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided)
  674. elided = _RE_PATTERN_CLEANSE_LINE_SINGLE_QUOTES.sub("''", elided)
  675. elided = _RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES.sub('""', elided)
  676. return elided
  677. def close_expression(elided, position):
  678. """If input points to ( or { or [, finds the position that closes it.
  679. If elided[position.row][position.column] points to a '(' or '{' or '[',
  680. finds the line_number/pos that correspond to the closing of the expression.
  681. Args:
  682. elided: A CleansedLines.elided instance containing the file.
  683. position: The position of the opening item.
  684. Returns:
  685. The Position *past* the closing brace, or Position(len(elided), -1)
  686. if we never find a close. Note we ignore strings and comments when matching.
  687. """
  688. line = elided[position.row]
  689. start_character = line[position.column]
  690. if start_character == '(':
  691. enclosing_character_regex = r'[\(\)]'
  692. elif start_character == '[':
  693. enclosing_character_regex = r'[\[\]]'
  694. elif start_character == '{':
  695. enclosing_character_regex = r'[\{\}]'
  696. else:
  697. return Position(len(elided), -1)
  698. current_column = position.column + 1
  699. line_number = position.row
  700. net_open = 1
  701. for line in elided[position.row:]:
  702. line = line[current_column:]
  703. # Search the current line for opening and closing characters.
  704. while True:
  705. next_enclosing_character = search(enclosing_character_regex, line)
  706. # No more on this line.
  707. if not next_enclosing_character:
  708. break
  709. current_column += next_enclosing_character.end(0)
  710. line = line[next_enclosing_character.end(0):]
  711. if next_enclosing_character.group(0) == start_character:
  712. net_open += 1
  713. else:
  714. net_open -= 1
  715. if not net_open:
  716. return Position(line_number, current_column)
  717. # Proceed to the next line.
  718. line_number += 1
  719. current_column = 0
  720. # The given item was not closed.
  721. return Position(len(elided), -1)
  722. def check_for_copyright(lines, error):
  723. """Logs an error if no Copyright message appears at the top of the file."""
  724. # We'll say it should occur by line 10. Don't forget there's a
  725. # dummy line at the front.
  726. for line in xrange(1, min(len(lines), 11)):
  727. if re.search(r'Copyright', lines[line], re.I):
  728. break
  729. else: # means no copyright line was found
  730. error(0, 'legal/copyright', 5,
  731. 'No copyright message found. '
  732. 'You should have a line: "Copyright [year] <Copyright Owner>"')
  733. def get_header_guard_cpp_variable(filename):
  734. """Returns the CPP variable that should be used as a header guard.
  735. Args:
  736. filename: The name of a C++ header file.
  737. Returns:
  738. The CPP variable that should be used as a header guard in the
  739. named file.
  740. """
  741. # Restores original filename in case that style checker is invoked from Emacs's
  742. # flymake.
  743. filename = re.sub(r'_flymake\.h$', '.h', filename)
  744. standard_name = sub(r'[-.\s]', '_', os.path.basename(filename))
  745. # Files under WTF typically have header guards that start with WTF_.
  746. if '/wtf/' in filename:
  747. special_name = "WTF_" + standard_name
  748. else:
  749. special_name = standard_name
  750. return (special_name, standard_name)
  751. def check_for_header_guard(filename, lines, error):
  752. """Checks that the file contains a header guard.
  753. Logs an error if no #ifndef header guard is present. For other
  754. headers, checks that the full pathname is used.
  755. Args:
  756. filename: The name of the C++ header file.
  757. lines: An array of strings, each representing a line of the file.
  758. error: The function to call with any errors found.
  759. """
  760. cppvar = get_header_guard_cpp_variable(filename)
  761. ifndef = None
  762. ifndef_line_number = 0
  763. define = None
  764. for line_number, line in enumerate(lines):
  765. line_split = line.split()
  766. if len(line_split) >= 2:
  767. # find the first occurrence of #ifndef and #define, save arg
  768. if not ifndef and line_split[0] == '#ifndef':
  769. # set ifndef to the header guard presented on the #ifndef line.
  770. ifndef = line_split[1]
  771. ifndef_line_number = line_number
  772. if not define and line_split[0] == '#define':
  773. define = line_split[1]
  774. if define and ifndef:
  775. break
  776. if not ifndef or not define or ifndef != define:
  777. error(0, 'build/header_guard', 5,
  778. 'No #ifndef header guard found, suggested CPP variable is: %s' %
  779. cppvar[0])
  780. return
  781. # The guard should be File_h.
  782. if ifndef not in cppvar:
  783. error(ifndef_line_number, 'build/header_guard', 5,
  784. '#ifndef header guard has wrong style, please use: %s' % cppvar[0])
  785. def check_for_unicode_replacement_characters(lines, error):
  786. """Logs an error for each line containing Unicode replacement characters.
  787. These indicate that either the file contained invalid UTF-8 (likely)
  788. or Unicode replacement characters (which it shouldn't). Note that
  789. it's possible for this to throw off line numbering if the invalid
  790. UTF-8 occurred adjacent to a newline.
  791. Args:
  792. lines: An array of strings, each representing a line of the file.
  793. error: The function to call with any errors found.
  794. """
  795. for line_number, line in enumerate(lines):
  796. if u'\ufffd' in line:
  797. error(line_number, 'readability/utf8', 5,
  798. 'Line contains invalid UTF-8 (or Unicode replacement character).')
  799. def check_for_new_line_at_eof(lines, error):
  800. """Logs an error if there is no newline char at the end of the file.
  801. Args:
  802. lines: An array of strings, each representing a line of the file.
  803. error: The function to call with any errors found.
  804. """
  805. # The array lines() was created by adding two newlines to the
  806. # original file (go figure), then splitting on \n.
  807. # To verify that the file ends in \n, we just have to make sure the
  808. # last-but-two element of lines() exists and is empty.
  809. if len(lines) < 3 or lines[-2]:
  810. error(len(lines) - 2, 'whitespace/ending_newline', 5,
  811. 'Could not find a newline character at the end of the file.')
  812. def check_for_multiline_comments_and_strings(clean_lines, line_number, error):
  813. """Logs an error if we see /* ... */ or "..." that extend past one line.
  814. /* ... */ comments are legit inside macros, for one line.
  815. Otherwise, we prefer // comments, so it's ok to warn about the
  816. other. Likewise, it's ok for strings to extend across multiple
  817. lines, as long as a line continuation character (backslash)
  818. terminates each line. Although not currently prohibited by the C++
  819. style guide, it's ugly and unnecessary. We don't do well with either
  820. in this lint program, so we warn about both.
  821. Args:
  822. clean_lines: A CleansedLines instance containing the file.
  823. line_number: The number of the line to check.
  824. error: The function to call with any errors found.
  825. """
  826. line = clean_lines.elided[line_number]
  827. # Remove all \\ (escaped backslashes) from the line. They are OK, and the
  828. # second (escaped) slash may trigger later \" detection erroneously.
  829. line = line.replace('\\\\', '')
  830. if line.count('/*') > line.count('*/'):
  831. error(line_number, 'readability/multiline_comment', 5,
  832. 'Complex multi-line /*...*/-style comment found. '
  833. 'Lint may give bogus warnings. '
  834. 'Consider replacing these with //-style comments, '
  835. 'with #if 0...#endif, '
  836. 'or with more clearly structured multi-line comments.')
  837. if (line.count('"') - line.count('\\"')) % 2:
  838. error(line_number, 'readability/multiline_string', 5,
  839. 'Multi-line string ("...") found. This lint script doesn\'t '
  840. 'do well with such strings, and may give bogus warnings. They\'re '
  841. 'ugly and unnecessary, and you should use concatenation instead".')
  842. _THREADING_LIST = (
  843. ('asctime(', 'asctime_r('),
  844. ('ctime(', 'ctime_r('),
  845. ('getgrgid(', 'getgrgid_r('),
  846. ('getgrnam(', 'getgrnam_r('),
  847. ('getlogin(', 'getlogin_r('),
  848. ('getpwnam(', 'getpwnam_r('),
  849. ('getpwuid(', 'getpwuid_r('),
  850. ('gmtime(', 'gmtime_r('),
  851. ('localtime(', 'localtime_r('),
  852. ('rand(', 'rand_r('),
  853. ('readdir(', 'readdir_r('),
  854. ('strtok(', 'strtok_r('),
  855. ('ttyname(', 'ttyname_r('),
  856. )
  857. def check_posix_threading(clean_lines, line_number, error):
  858. """Checks for calls to thread-unsafe functions.
  859. Much code has been originally written without consideration of
  860. multi-threading. Also, engineers are relying on their old experience;
  861. they have learned posix before threading extensions were added. These
  862. tests guide the engineers to use thread-safe functions (when using
  863. posix directly).
  864. Args:
  865. clean_lines: A CleansedLines instance containing the file.
  866. line_number: The number of the line to check.
  867. error: The function to call with any errors found.
  868. """
  869. line = clean_lines.elided[line_number]
  870. for single_thread_function, multithread_safe_function in _THREADING_LIST:
  871. index = line.find(single_thread_function)
  872. # Comparisons made explicit for clarity -- pylint: disable-msg=C6403
  873. if index >= 0 and (index == 0 or (not line[index - 1].isalnum()
  874. and line[index - 1] not in ('_', '.', '>'))):
  875. error(line_number, 'runtime/threadsafe_fn', 2,
  876. 'Consider using ' + multithread_safe_function +
  877. '...) instead of ' + single_thread_function +
  878. '...) for improved thread safety.')
  879. # Matches invalid increment: *count++, which moves pointer instead of
  880. # incrementing a value.
  881. _RE_PATTERN_INVALID_INCREMENT = re.compile(
  882. r'^\s*\*\w+(\+\+|--);')
  883. def check_invalid_increment(clean_lines, line_number, error):
  884. """Checks for invalid increment *count++.
  885. For example following function:
  886. void increment_counter(int* count) {
  887. *count++;
  888. }
  889. is invalid, because it effectively does count++, moving pointer, and should
  890. be replaced with ++*count, (*count)++ or *count += 1.
  891. Args:
  892. clean_lines: A CleansedLines instance containing the file.
  893. line_number: The number of the line to check.
  894. error: The function to call with any errors found.
  895. """
  896. line = clean_lines.elided[line_number]
  897. if _RE_PATTERN_INVALID_INCREMENT.match(line):
  898. error(line_number, 'runtime/invalid_increment', 5,
  899. 'Changing pointer instead of value (or unused value of operator*).')
  900. class _ClassInfo(object):
  901. """Stores information about a class."""
  902. def __init__(self, name, line_number):
  903. self.name = name
  904. self.line_number = line_number
  905. self.seen_open_brace = False
  906. self.is_derived = False
  907. self.virtual_method_line_number = None
  908. self.has_virtual_destructor = False
  909. self.brace_depth = 0
  910. class _ClassState(object):
  911. """Holds the current state of the parse relating to class declarations.
  912. It maintains a stack of _ClassInfos representing the parser's guess
  913. as to the current nesting of class declarations. The innermost class
  914. is at the top (back) of the stack. Typically, the stack will either
  915. be empty or have exactly one entry.
  916. """
  917. def __init__(self):
  918. self.classinfo_stack = []
  919. def check_finished(self, error):
  920. """Checks that all classes have been completely parsed.
  921. Call this when all lines in a file have been processed.
  922. Args:
  923. error: The function to call with any errors found.
  924. """
  925. if self.classinfo_stack:
  926. # Note: This test can result in false positives if #ifdef constructs
  927. # get in the way of brace matching. See the testBuildClass test in
  928. # cpp_style_unittest.py for an example of this.
  929. error(self.classinfo_stack[0].line_number, 'build/class', 5,
  930. 'Failed to find complete declaration of class %s' %
  931. self.classinfo_stack[0].name)
  932. class _FileState(object):
  933. def __init__(self, clean_lines, file_extension):
  934. self._did_inside_namespace_indent_warning = False
  935. self._clean_lines = clean_lines
  936. if file_extension in ['m', 'mm']:
  937. self._is_objective_c = True
  938. elif file_extension == 'h':
  939. # In the case of header files, it is unknown if the file
  940. # is objective c or not, so set this value to None and then
  941. # if it is requested, use heuristics to guess the value.
  942. self._is_objective_c = None
  943. else:
  944. self._is_objective_c = False
  945. self._is_c = file_extension == 'c'
  946. def set_did_inside_namespace_indent_warning(self):
  947. self._did_inside_namespace_indent_warning = True
  948. def did_inside_namespace_indent_warning(self):
  949. return self._did_inside_namespace_indent_warning
  950. def is_objective_c(self):
  951. if self._is_objective_c is None:
  952. for line in self._clean_lines.elided:
  953. # Starting with @ or #import seem like the best indications
  954. # that we have an Objective C file.
  955. if line.startswith("@") or line.startswith("#import"):
  956. self._is_objective_c = True
  957. break
  958. else:
  959. self._is_objective_c = False
  960. return self._is_objective_c
  961. def is_c_or_objective_c(self):
  962. """Return whether the file extension corresponds to C or Objective-C."""
  963. return self._is_c or self.is_objective_c()
  964. def check_for_non_standard_constructs(clean_lines, line_number,
  965. class_state, error):
  966. """Logs an error if we see certain non-ANSI constructs ignored by gcc-2.
  967. Complain about several constructs which gcc-2 accepts, but which are
  968. not standard C++. Warning about these in lint is one way to ease the
  969. transition to new compilers.
  970. - put storage class first (e.g. "static const" instead of "const static").
  971. - "%lld" instead of %qd" in printf-type functions.
  972. - "%1$d" is non-standard in printf-type functions.
  973. - "\%" is an undefined character escape sequence.
  974. - text after #endif is not allowed.
  975. - invalid inner-style forward declaration.
  976. - >? and <? operators, and their >?= and <?= cousins.
  977. - classes with virtual methods need virtual destructors (compiler warning
  978. available, but not turned on yet.)
  979. Additionally, check for constructor/destructor style violations as it
  980. is very convenient to do so while checking for gcc-2 compliance.
  981. Args:
  982. clean_lines: A CleansedLines instance containing the file.
  983. line_number: The number of the line to check.
  984. class_state: A _ClassState instance which maintains information about
  985. the current stack of nested class declarations being parsed.
  986. error: A callable to which errors are reported, which takes parameters:
  987. line number, error level, and message
  988. """
  989. # Remove comments from the line, but leave in strings for now.
  990. line = clean_lines.lines[line_number]
  991. if search(r'printf\s*\(.*".*%[-+ ]?\d*q', line):
  992. error(line_number, 'runtime/printf_format', 3,
  993. '%q in format strings is deprecated. Use %ll instead.')
  994. if search(r'printf\s*\(.*".*%\d+\$', line):
  995. error(line_number, 'runtime/printf_format', 2,
  996. '%N$ formats are unconventional. Try rewriting to avoid them.')
  997. # Remove escaped backslashes before looking for undefined escapes.
  998. line = line.replace('\\\\', '')
  999. if search(r'("|\').*\\(%|\[|\(|{)', line):
  1000. error(line_number, 'build/printf_format', 3,
  1001. '%, [, (, and { are undefined character escapes. Unescape them.')
  1002. # For the rest, work with both comments and strings removed.
  1003. line = clean_lines.elided[line_number]
  1004. i

Large files files are truncated, but you can click here to view the full file