summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/style/checkers/cpp.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/style/checkers/cpp.py')
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/cpp.py266
1 files changed, 260 insertions, 6 deletions
diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp.py b/Tools/Scripts/webkitpy/style/checkers/cpp.py
index 94e5bdd..4ea7d69 100644
--- a/Tools/Scripts/webkitpy/style/checkers/cpp.py
+++ b/Tools/Scripts/webkitpy/style/checkers/cpp.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2009 Google Inc. All rights reserved.
+# Copyright (C) 2009, 2010 Google Inc. All rights reserved.
# Copyright (C) 2009 Torch Mobile Inc.
# Copyright (C) 2009 Apple Inc. All rights reserved.
# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
@@ -47,6 +47,8 @@ import string
import sys
import unicodedata
+from webkitpy.common.memoized import memoized
+
# The key to use to provide a class to fake loading a header file.
INCLUDE_IO_INJECTION_KEY = 'include_header_io'
@@ -192,6 +194,34 @@ def iteratively_replace_matches_with_char(pattern, char_replacement, s):
s = s[:start_match_index] + char_replacement * match_length + s[end_match_index:]
+def _convert_to_lower_with_underscores(text):
+ """Converts all text strings in camelCase or PascalCase to lowers with underscores."""
+
+ # First add underscores before any capital letter followed by a lower case letter
+ # as long as it is in a word.
+ # (This put an underscore before Password but not P and A in WPAPassword).
+ text = sub(r'(?<=[A-Za-z0-9])([A-Z])(?=[a-z])', r'_\1', text)
+
+ # Next add underscores before capitals at the end of words if it was
+ # preceeded by lower case letter or number.
+ # (This puts an underscore before A in isA but not A in CBA).
+ text = sub(r'(?<=[a-z0-9])([A-Z])(?=\b)', r'_\1', text)
+
+ # Next add underscores when you have a captial letter which is followed by a capital letter
+ # but is not proceeded by one. (This puts an underscore before A in 'WordADay').
+ text = sub(r'(?<=[a-z0-9])([A-Z][A-Z_])', r'_\1', text)
+
+ return text.lower()
+
+
+
+def _create_acronym(text):
+ """Creates an acronym for the given text."""
+ # Removes all lower case letters except those starting words.
+ text = sub(r'(?<!\b)[a-z]', '', text)
+ return text.upper()
+
+
def up_to_unmatched_closing_paren(s):
"""Splits a string into two parts up to first unmatched ')'.
@@ -308,6 +338,138 @@ class _IncludeState(dict):
return error_message
+class Position(object):
+ """Holds the position of something."""
+ def __init__(self, row, column):
+ self.row = row
+ self.column = column
+
+
+class Parameter(object):
+ """Information about one function parameter."""
+ def __init__(self, parameter, parameter_name_index, row):
+ self.type = parameter[:parameter_name_index].strip()
+ # Remove any initializers from the parameter name (e.g. int i = 5).
+ self.name = sub(r'=.*', '', parameter[parameter_name_index:]).strip()
+ self.row = row
+
+ @memoized
+ def lower_with_underscores_name(self):
+ """Returns the parameter name in the lower with underscores format."""
+ return _convert_to_lower_with_underscores(self.name)
+
+
+class SingleLineView(object):
+ """Converts multiple lines into a single line (with line breaks replaced by a
+ space) to allow for easier searching."""
+ def __init__(self, lines, start_position, end_position):
+ """Create a SingleLineView instance.
+
+ Args:
+ lines: a list of multiple lines to combine into a single line.
+ start_position: offset within lines of where to start the single line.
+ end_position: just after where to end (like a slice operation).
+ """
+ # Get the rows of interest.
+ trimmed_lines = lines[start_position.row:end_position.row + 1]
+
+ # Remove the columns on the last line that aren't included.
+ trimmed_lines[-1] = trimmed_lines[-1][:end_position.column]
+
+ # Remove the columns on the first line that aren't included.
+ trimmed_lines[0] = trimmed_lines[0][start_position.column:]
+
+ # Create a single line with all of the parameters.
+ self.single_line = ' '.join(trimmed_lines)
+
+ # Keep the row lengths, so we can calculate the original row number
+ # given a column in the single line (adding 1 due to the space added
+ # during the join).
+ self._row_lengths = [len(line) + 1 for line in trimmed_lines]
+ self._starting_row = start_position.row
+
+ def convert_column_to_row(self, single_line_column_number):
+ """Convert the column number from the single line into the original
+ line number.
+
+ Special cases:
+ * Columns in the added spaces are considered part of the previous line.
+ * Columns beyond the end of the line are consider part the last line
+ in the view."""
+ total_columns = 0
+ row_offset = 0
+ while row_offset < len(self._row_lengths) - 1 and single_line_column_number >= total_columns + self._row_lengths[row_offset]:
+ total_columns += self._row_lengths[row_offset]
+ row_offset += 1
+ return self._starting_row + row_offset
+
+
+def create_skeleton_parameters(all_parameters):
+ """Converts a parameter list to a skeleton version.
+
+ The skeleton only has one word for the parameter name, one word for the type,
+ and commas after each parameter and only there. Everything in the skeleton
+ remains in the same columns as the original."""
+ all_simplifications = (
+ # Remove template parameters, function declaration parameters, etc.
+ r'(<[^<>]*?>)|(\([^\(\)]*?\))|(\{[^\{\}]*?\})',
+ # Remove all initializers.
+ r'=[^,]*',
+ # Remove :: and everything before it.
+ r'[^,]*::',
+ # Remove modifiers like &, *.
+ r'[&*]',
+ # Remove const modifiers.
+ r'\bconst\s+(?=[A-Za-z])',
+ # Remove numerical modifiers like long.
+ r'\b(unsigned|long|short)\s+(?=unsigned|long|short|int|char|double|float)')
+
+ skeleton_parameters = all_parameters
+ for simplification in all_simplifications:
+ skeleton_parameters = iteratively_replace_matches_with_char(simplification, ' ', skeleton_parameters)
+ # If there are any parameters, then add a , after the last one to
+ # make a regular pattern of a , following every parameter.
+ if skeleton_parameters.strip():
+ skeleton_parameters += ','
+ return skeleton_parameters
+
+
+def find_parameter_name_index(skeleton_parameter):
+ """Determines where the parametere name starts given the skeleton parameter."""
+ # The first space from the right in the simplified parameter is where the parameter
+ # name starts unless the first space is before any content in the simplified parameter.
+ before_name_index = skeleton_parameter.rstrip().rfind(' ')
+ if before_name_index != -1 and skeleton_parameter[:before_name_index].strip():
+ return before_name_index + 1
+ return len(skeleton_parameter)
+
+
+def parameter_list(elided_lines, start_position, end_position):
+ """Generator for a function's parameters."""
+ # Create new positions that omit the outer parenthesis of the parameters.
+ start_position = Position(row=start_position.row, column=start_position.column + 1)
+ end_position = Position(row=end_position.row, column=end_position.column - 1)
+ single_line_view = SingleLineView(elided_lines, start_position, end_position)
+ skeleton_parameters = create_skeleton_parameters(single_line_view.single_line)
+ end_index = -1
+
+ while True:
+ # Find the end of the next parameter.
+ start_index = end_index + 1
+ end_index = skeleton_parameters.find(',', start_index)
+
+ # No comma means that all parameters have been parsed.
+ if end_index == -1:
+ return
+ row = single_line_view.convert_column_to_row(end_index)
+
+ # Parse the parameter into a type and parameter name.
+ skeleton_parameter = skeleton_parameters[start_index:end_index]
+ name_offset = find_parameter_name_index(skeleton_parameter)
+ parameter = single_line_view.single_line[start_index:end_index]
+ yield Parameter(parameter, name_offset, row)
+
+
class _FunctionState(object):
"""Tracks current function name and the number of lines in its body.
@@ -329,7 +491,8 @@ class _FunctionState(object):
self.body_start_line_number = -1000
self.ending_line_number = -1000
- def begin(self, function_name, body_start_line_number, ending_line_number, is_declaration):
+ def begin(self, function_name, body_start_line_number, ending_line_number, is_declaration,
+ parameter_start_position, parameter_end_position, clean_lines):
"""Start analyzing function body.
Args:
@@ -337,6 +500,9 @@ class _FunctionState(object):
body_start_line_number: The line number of the { or the ; for a protoype.
ending_line_number: The line number where the function ends.
is_declaration: True if this is a prototype.
+ parameter_start_position: position in elided of the '(' for the parameters.
+ parameter_end_position: position in elided of the ')' for the parameters.
+ clean_lines: A CleansedLines instance containing the file.
"""
self.in_a_function = True
self.lines_in_function = -1 # Don't count the open brace line.
@@ -344,6 +510,17 @@ class _FunctionState(object):
self.body_start_line_number = body_start_line_number
self.ending_line_number = ending_line_number
self.is_declaration = is_declaration
+ self.parameter_start_position = parameter_start_position
+ self.parameter_end_position = parameter_end_position
+ self._clean_lines = clean_lines
+ self._parameter_list = None
+
+ def parameter_list(self):
+ if not self._parameter_list:
+ # Store the final result as a tuple since that is immutable.
+ self._parameter_list = tuple(parameter_list(self._clean_lines.elided, self.parameter_start_position, self.parameter_end_position))
+
+ return self._parameter_list
def count(self, line_number):
"""Count line in current function body."""
@@ -1161,7 +1338,7 @@ def check_spacing_for_function_call(line, line_number, error):
error(line_number, 'whitespace/parens', 2,
'Extra space after (')
if (search(r'\w\s+\(', function_call)
- and not search(r'#\s*define|typedef', function_call)):
+ and not match(r'\s*(#|typedef)', function_call)):
error(line_number, 'whitespace/parens', 4,
'Extra space before ( in function call')
# If the ) is followed only by a newline or a { + newline, assume it's
@@ -1213,7 +1390,11 @@ def detect_functions(clean_lines, line_number, function_state, error):
raw = clean_lines.raw_lines
raw_line = raw[line_number]
- regexp = r'\s*(\w(\w|::|\*|\&|\s|<|>|,|~)*)\(' # decls * & space::name( ...
+ # Lines ending with a \ indicate a macro. Don't try to check them.
+ if raw_line.endswith('\\'):
+ return
+
+ regexp = r'\s*(\w(\w|::|\*|\&|\s|<|>|,|~|(operator\s*(/|-|=|!|\+)+))*)\(' # decls * & space::name( ...
match_result = match(regexp, line)
if not match_result:
return
@@ -1232,7 +1413,7 @@ def detect_functions(clean_lines, line_number, function_state, error):
# Replace template constructs with _ so that no spaces remain in the function name,
# while keeping the column numbers of other characters the same as "line".
line_with_no_templates = iteratively_replace_matches_with_char(r'<[^<>]*>', '_', line)
- match_function = search(r'((\w|:|<|>|,|~)*)\(', line_with_no_templates)
+ match_function = search(r'((\w|:|<|>|,|~|(operator\s*(/|-|=|!|\+)+))*)\(', line_with_no_templates)
if not match_function:
return # The '(' must have been inside of a template.
@@ -1246,13 +1427,22 @@ def detect_functions(clean_lines, line_number, function_state, error):
function += parameter_regexp.group(1)
else:
function += '()'
+
+ parameter_start_position = Position(line_number, match_function.end(1))
+ close_result = close_expression(clean_lines, line_number, parameter_start_position.column)
+ if close_result[1] == len(clean_lines.elided):
+ # No end was found.
+ return
+ parameter_end_position = Position(close_result[1], close_result[2])
+
is_declaration = bool(search(r'^[^{]*;', start_line))
if is_declaration:
ending_line_number = start_line_number
else:
open_brace_index = start_line.find('{')
ending_line_number = close_expression(clean_lines, start_line_number, open_brace_index)[1]
- function_state.begin(function, start_line_number, ending_line_number, is_declaration)
+ function_state.begin(function, start_line_number, ending_line_number, is_declaration,
+ parameter_start_position, parameter_end_position, clean_lines)
return
# No body for the function (or evidence of a non-function) was found.
@@ -1288,6 +1478,64 @@ def check_for_function_lengths(clean_lines, line_number, function_state, error):
function_state.count(line_number) # Count non-blank/non-comment lines.
+def _check_parameter_name_against_text(parameter, text, error):
+ """Checks to see if the parameter name is contained within the text.
+
+ Return false if the check failed (i.e. an error was produced).
+ """
+
+ # Treat 'lower with underscores' as a canonical form because it is
+ # case insensitive while still retaining word breaks. (This ensures that
+ # 'elate' doesn't look like it is duplicating of 'NateLate'.)
+ canonical_parameter_name = parameter.lower_with_underscores_name()
+
+ # Appends "object" to all text to catch variables that did the same (but only
+ # do this when the parameter name is more than a single character to avoid
+ # flagging 'b' which may be an ok variable when used in an rgba function).
+ if len(canonical_parameter_name) > 1:
+ text = sub(r'(\w)\b', r'\1Object', text)
+ canonical_text = _convert_to_lower_with_underscores(text)
+
+ # Used to detect cases like ec for ExceptionCode.
+ acronym = _create_acronym(text).lower()
+ if canonical_text.find(canonical_parameter_name) != -1 or acronym.find(canonical_parameter_name) != -1:
+ error(parameter.row, 'readability/parameter_name', 5,
+ 'The parameter name "%s" adds no information, so it should be removed.' % parameter.name)
+ return False
+ return True
+
+
+def check_function_definition(clean_lines, line_number, function_state, error):
+ """Check that function definitions for style issues.
+
+ Specifically, check that parameter names in declarations add information.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ line_number: The number of the line to check.
+ function_state: Current function name and lines in body so far.
+ error: The function to call with any errors found.
+ """
+ # Only do checks when we have a function declaration.
+ if line_number != function_state.body_start_line_number or not function_state.is_declaration:
+ return
+
+ parameter_list = function_state.parameter_list()
+ for parameter in parameter_list:
+ if not parameter.name:
+ continue
+
+ # Check the parameter name against the function name for single parameter set functions.
+ if len(parameter_list) == 1 and match('set[A-Z]', function_state.current_function):
+ trimmed_function_name = function_state.current_function[len('set'):]
+ if not _check_parameter_name_against_text(parameter, trimmed_function_name, error):
+ continue # Since an error was noted for this name, move to the next parameter.
+
+ # Check the parameter name against the type.
+ if not _check_parameter_name_against_text(parameter, parameter.type, error):
+ continue # Since an error was noted for this name, move to the next parameter.
+
+
def check_pass_ptr_usage(clean_lines, line_number, function_state, error):
"""Check for proper usage of Pass*Ptr.
@@ -2028,6 +2276,10 @@ def check_for_null(clean_lines, line_number, file_state, error):
if search(r'\bgdk_pixbuf_save_to\w+\b', line):
return
+ # Don't warn about NULL usage in gtk_widget_style_get(). See Bug 51758.
+ if search(r'\bgtk_widget_style_get\(\w+\b', line):
+ return
+
if search(r'\bNULL\b', line):
error(line_number, 'readability/null', 5, 'Use 0 instead of NULL.')
return
@@ -3001,6 +3253,7 @@ def process_line(filename, file_extension,
check_for_function_lengths(clean_lines, line, function_state, error)
if search(r'\bNOLINT\b', raw_lines[line]): # ignore nolint lines
return
+ check_function_definition(clean_lines, line, function_state, error)
check_pass_ptr_usage(clean_lines, line, function_state, error)
check_for_multiline_comments_and_strings(clean_lines, line, error)
check_style(clean_lines, line, file_extension, class_state, file_state, error)
@@ -3084,6 +3337,7 @@ class CppChecker(object):
'readability/function',
'readability/multiline_comment',
'readability/multiline_string',
+ 'readability/parameter_name',
'readability/naming',
'readability/null',
'readability/pass_ptr',