class TestExpectationsChecker(object): """Processes TestExpectations lines for validating the syntax.""" categories = set(['test/expectations']) def _determine_port_from_expectations_path(self, host, expectations_path): # Pass a configuration to avoid calling default_configuration() when initializing the port (takes 0.5 seconds on a Mac Pro!). options = optparse.Values({'configuration': 'Release'}) for port_name in host.port_factory.all_port_names(): port = host.port_factory.get(port_name, options=options) for test_expectation_file in port.expectations_files(): if test_expectation_file.replace( port.path_from_webkit_base() + host.filesystem.sep, '') == expectations_path: return port return None def __init__(self, file_path, handle_style_error, host=None): self._file_path = file_path self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) # FIXME: host should be a required parameter, not an optional one. host = host or Host() host.initialize_scm() self._port_obj = self._determine_port_from_expectations_path( host, file_path) # Suppress error messages of test_expectations module since they will be reported later. log = logging.getLogger( "webkitpy.layout_tests.layout_package.test_expectations") log.setLevel(logging.CRITICAL) def _handle_error_message(self, lineno, message, confidence): pass def check_test_expectations(self, expectations_str, tests=None): parser = TestExpectationParser(self._port_obj, tests, allow_rebaseline_modifier=False) expectations = parser.parse('expectations', expectations_str) level = 5 for expectation_line in expectations: for warning in expectation_line.warnings: self._handle_style_error(expectation_line.line_number, 'test/expectations', level, warning) def check_tabs(self, lines): self._tab_checker.check(lines) def check(self, lines): expectations = '\n'.join(lines) if self._port_obj: self.check_test_expectations(expectations_str=expectations, tests=None) # Warn tabs in lines as well self.check_tabs(lines)
class TestExpectationsChecker(object): """Processes TestExpectations lines for validating the syntax.""" categories = set(['test/expectations']) def _determine_port_from_expectations_path(self, host, expectations_path): # Pass a configuration to avoid calling default_configuration() when initializing the port (takes 0.5 seconds on a Mac Pro!). options_wk1 = optparse.Values({'configuration': 'Release', 'webkit_test_runner': False}) options_wk2 = optparse.Values({'configuration': 'Release', 'webkit_test_runner': True}) for port_name in host.port_factory.all_port_names(): ports = [host.port_factory.get(port_name, options=options_wk1), host.port_factory.get(port_name, options=options_wk2)] for port in ports: for test_expectation_file in port.expectations_files(): if test_expectation_file.replace(port.path_from_webkit_base() + host.filesystem.sep, '') == expectations_path: return port return None def __init__(self, file_path, handle_style_error, host=None): self._file_path = file_path self._handle_style_error = handle_style_error self._handle_style_error.turn_off_line_filtering() self._tab_checker = TabChecker(file_path, handle_style_error) # FIXME: host should be a required parameter, not an optional one. host = host or Host() host._initialize_scm() self._port_obj = self._determine_port_from_expectations_path(host, file_path) # Suppress error messages of test_expectations module since they will be reported later. log = logging.getLogger("webkitpy.layout_tests.layout_package.test_expectations") log.setLevel(logging.CRITICAL) def _handle_error_message(self, lineno, message, confidence): pass def check_test_expectations(self, expectations_str, tests=None): parser = TestExpectationParser(self._port_obj, tests, False) expectations = parser.parse('expectations', expectations_str) level = 5 for expectation_line in expectations: for warning in expectation_line.warnings: self._handle_style_error(expectation_line.line_number, 'test/expectations', level, warning) def check_tabs(self, lines): self._tab_checker.check(lines) def check(self, lines): expectations = '\n'.join(lines) if self._port_obj: self.check_test_expectations(expectations_str=expectations, tests=None) else: self._handle_style_error(1, 'test/expectations', 5, 'No port uses path %s for test_expectations' % self._file_path) # Warn tabs in lines as well self.check_tabs(lines)
class TextChecker(object): """Processes text lines for checking style.""" def __init__(self, file_path, handle_style_error): self.file_path = file_path self.handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines)
class ChangeLogChecker(object): """Processes text lines for checking style.""" categories = set(['changelog/bugnumber', 'changelog/filechangedescriptionwhitespace']) def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if re.search("Unreviewed", line, re.IGNORECASE): break if re.search("build", line, re.IGNORECASE) and re.search("fix", line, re.IGNORECASE): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") # check file change descriptions for style violations line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 # filter file change descriptions if not re.match('\s*\*\s', line): continue if re.search(':\s*$', line) or re.search(':\s', line): continue self.handle_style_error(line_no, "changelog/filechangedescriptionwhitespace", 5, "Need whitespace between colon and description") def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines)
class ChangeLogChecker(object): """Processes text lines for checking style.""" categories = set( ['changelog/bugnumber', 'changelog/filechangedescriptionwhitespace']) def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if re.search("Unreviewed", line, re.IGNORECASE): break if re.search("build", line, re.IGNORECASE) and re.search( "fix", line, re.IGNORECASE): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") # check file change descriptions for style violations line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 # filter file change descriptions if not re.match('\s*\*\s', line): continue if re.search(':\s*$', line) or re.search(':\s', line): continue self.handle_style_error( line_no, "changelog/filechangedescriptionwhitespace", 5, "Need whitespace between colon and description") def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines)
class JSChecker(object): """Processes JavaScript lines for checking style.""" # FIXME: plug in a JavaScript parser to find syntax errors. categories = set(('js/syntax',)) def __init__(self, file_path, handle_style_error): self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines)
class JSChecker(object): """Processes JavaScript lines for checking style.""" # FIXME: plug in a JavaScript parser to find syntax errors. categories = set(('js/syntax', )) def __init__(self, file_path, handle_style_error): self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines)
def assert_tab(self, input_lines, error_lines): """Assert when the given lines contain tabs.""" self._error_lines = [] def style_error_handler(line_number, category, confidence, message): self.assertEqual(category, 'whitespace/tab') self.assertEqual(confidence, 5) self.assertEqual(message, 'Line contains tab character.') self._error_lines.append(line_number) checker = TabChecker('', style_error_handler) checker.check(input_lines) self.assertEquals(self._error_lines, error_lines)
class TestExpectationsChecker(object): """Processes TestExpectations lines for validating the syntax.""" categories = set(['test/expectations']) def __init__(self, file_path, handle_style_error, host=None): self._file_path = file_path self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) # FIXME: host should be a required parameter, not an optional one. host = host or Host() host.initialize_scm() self._port_obj = host.port_factory.get() # Suppress error messages of test_expectations module since they will be reported later. log = logging.getLogger( "webkitpy.layout_tests.layout_package.test_expectations") log.setLevel(logging.CRITICAL) def _handle_error_message(self, lineno, message, confidence): pass def check_test_expectations(self, expectations_str, tests=None): parser = TestExpectationParser(self._port_obj, tests, is_lint_mode=True) expectations = parser.parse('expectations', expectations_str) level = 5 for expectation_line in expectations: for warning in expectation_line.warnings: self._handle_style_error(expectation_line.line_numbers, 'test/expectations', level, warning) def check_tabs(self, lines): self._tab_checker.check(lines) def check(self, lines): expectations = '\n'.join(lines) if self._port_obj: self.check_test_expectations(expectations_str=expectations, tests=None) # Warn tabs in lines as well self.check_tabs(lines)
class MessagesInChecker(object): """Processes .messages.in lines for checking style.""" def __init__(self, file_path, handle_style_error): self.file_path = file_path self.handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines) self.check_WTF_prefix(lines) def check_WTF_prefix(self, lines): comment = re.compile('^\s*#') for line_number, line in enumerate(lines): if not comment.match(line) and 'WTF::' in line: self.handle_style_error(line_number + 1, 'build/messagesin/wtf', 5, 'Line contains WTF:: prefix.')
class MessagesInChecker(object): """Processes .messages.in lines for checking style.""" def __init__(self, file_path, handle_style_error): self.file_path = file_path self.handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines) self.check_WTF_prefix(lines) def check_WTF_prefix(self, lines): comment = re.compile("^\s*#") for line_number, line in enumerate(lines): if not comment.match(line) and "WTF::" in line: self.handle_style_error(line_number + 1, "build/messagesin/wtf", 5, "Line contains WTF:: prefix.")
class TestExpectationsChecker(object): """Processes TestExpectations lines for validating the syntax.""" categories = set(["test/expectations"]) def __init__(self, file_path, handle_style_error, host=None): self._file_path = file_path self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) # FIXME: host should be a required parameter, not an optional one. host = host or Host() host.initialize_scm() self._port_obj = host.port_factory.get() # Suppress error messages of test_expectations module since they will be reported later. log = logging.getLogger("webkitpy.layout_tests.layout_package.test_expectations") log.setLevel(logging.CRITICAL) def _handle_error_message(self, lineno, message, confidence): pass def check_test_expectations(self, expectations_str, tests=None): parser = TestExpectationParser(self._port_obj, tests, is_lint_mode=True) expectations = parser.parse("expectations", expectations_str) level = 5 for expectation_line in expectations: for warning in expectation_line.warnings: self._handle_style_error(expectation_line.line_numbers, "test/expectations", level, warning) def check_tabs(self, lines): self._tab_checker.check(lines) def check(self, lines): expectations = "\n".join(lines) if self._port_obj: self.check_test_expectations(expectations_str=expectations, tests=None) # Warn tabs in lines as well self.check_tabs(lines)
class ChangeLogChecker(object): """Processes text lines for checking style.""" def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if re.search("Unreviewed", line, re.IGNORECASE): break if re.search("build", line, re.IGNORECASE) and re.search("fix", line, re.IGNORECASE): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines)
class ChangeLogChecker(object): """Processes text lines for checking style.""" def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if re.search("Unreviewed", line, re.IGNORECASE): break if re.search("build", line, re.IGNORECASE) and re.search( "fix", line, re.IGNORECASE): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines)
class TestExpectationsChecker(object): """Processes TestExpectations lines for validating the syntax.""" categories = set(['test/expectations']) def _determine_port_from_expectations_path(self, host, expectations_path): # Pass a configuration to avoid calling default_configuration() when initializing the port (takes 0.5 seconds on a Mac Pro!). options_wk1 = optparse.Values({'configuration': 'Release', 'webkit_test_runner': False}) options_wk2 = optparse.Values({'configuration': 'Release', 'webkit_test_runner': True}) for port_name in host.port_factory.all_port_names(): ports = [host.port_factory.get(port_name, options=options_wk1), host.port_factory.get(port_name, options=options_wk2)] for port in ports: for test_expectation_file in port.expectations_files(): if test_expectation_file.replace(port.path_from_webkit_base() + host.filesystem.sep, '') == expectations_path: return port return None def __init__(self, file_path, handle_style_error, host=None): self._file_path = file_path self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) # FIXME: host should be a required parameter, not an optional one. host = host or Host() host.initialize_scm() self._port_obj = self._determine_port_from_expectations_path(host, file_path) # Suppress error messages of test_expectations module since they will be reported later. log = logging.getLogger("webkitpy.layout_tests.layout_package.test_expectations") log.setLevel(logging.CRITICAL) def _handle_error_message(self, lineno, message, confidence): pass def check_test_expectations(self, expectations_str, tests=None): parser = test_expectations.TestExpectationParser(self._port_obj, tests, allow_rebaseline_modifier=False) expectations = parser.parse('expectations', expectations_str) level = 5 for expectation_line in expectations: for warning in expectation_line.warnings: self._handle_style_error(expectation_line.line_number, 'test/expectations', level, warning) def check_tabs(self, lines): self._tab_checker.check(lines) def check(self, lines): expectations = '\n'.join(lines) if self._port_obj: self.check_test_expectations(expectations_str=expectations, tests=None) # Warn tabs in lines as well self.check_tabs(lines) @staticmethod def _should_log_linter_warning(warning, files, cwd, host): abs_filename = host.filesystem.join(cwd, warning.filename) # Case 1, the line the warning was tied to is in our patch. if abs_filename in files and files[abs_filename] and warning.line_number in files[abs_filename]: return True for file, lines in warning.related_files.iteritems(): abs_filename = host.filesystem.join(cwd, file) if abs_filename in files: # Case 2, a file associated with the warning is in our patch # Note that this will really only happen if you delete a test. if lines is None: return True # Case 3, a line associated with the warning is in our patch. for line in lines: if files[abs_filename] and line in files[abs_filename]: return True return False @staticmethod def lint_test_expectations(files, configuration, cwd, increment_error_count=lambda: 0, line_numbers=None, host=Host()): error_count = 0 files_linted = set() ports_to_lint = [host.port_factory.get(name) for name in host.port_factory.all_port_names()] for port in ports_to_lint: for expectations_file in port.expectations_dict().keys(): style_error_handler = DefaultStyleErrorHandler(expectations_file, configuration, increment_error_count, line_numbers) try: if expectations_file in files_linted: continue expectations = test_expectations.TestExpectations( port, expectations_to_lint={expectations_file: port.expectations_dict()[expectations_file]}) expectations.parse_all_expectations() except test_expectations.ParseError as e: for warning in e.warnings: if TestExpectationsChecker._should_log_linter_warning(warning, files, cwd, host): style_error_handler(warning.line_number, 'test/expectations', 5, warning.error) error_count += 1 files_linted.add(expectations_file) return error_count
class CMakeChecker(object): """Processes CMake lines for checking style.""" # NO_SPACE_CMDS list are based on commands section of CMake document. # Now it is generated from # http://www.cmake.org/cmake/help/v2.8.10/cmake.html#section_Commands. # Some commands are from default CMake modules such as pkg_check_modules. # Please keep list in alphabet order. # # For commands in this list, spaces should not be added it and its # parentheses. For eg, message("testing"), not message ("testing") # # The conditional commands like if, else, endif, foreach, endforeach, # while, endwhile and break are listed in ONE_SPACE_CMDS NO_SPACE_CMDS = [ 'add_custom_command', 'add_custom_target', 'add_definitions', 'add_dependencies', 'add_executable', 'add_library', 'add_subdirectory', 'add_test', 'aux_source_directory', 'build_command', 'cmake_minimum_required', 'cmake_policy', 'configure_file', 'create_test_sourcelist', 'define_property', 'enable_language', 'enable_testing', 'endfunction', 'endmacro', 'execute_process', 'export', 'file', 'find_file', 'find_library', 'find_package', 'find_path', 'find_program', 'fltk_wrap_ui', 'function', 'get_cmake_property', 'get_directory_property', 'get_filename_component', 'get_property', 'get_source_file_property', 'get_target_property', 'get_test_property', 'include', 'include_directories', 'include_external_msproject', 'include_regular_expression', 'install', 'link_directories', 'list', 'load_cache', 'load_command', 'macro', 'mark_as_advanced', 'math', 'message', 'option', #From FindPkgConfig.cmake 'pkg_check_modules', 'project', 'remove_definitions', 'return', 'separate_arguments', 'set', 'set_directory_properties', 'set_property', 'set_source_files_properties', 'set_target_properties', 'set_tests_properties', 'site_name', 'source_group', 'string', 'target_link_libraries', 'try_compile', 'try_run', 'unset', 'variable_watch', ] # CMake conditional commands, require one space between command and # its parentheses, such as "if (", "foreach (", etc. ONE_SPACE_CMDS = [ 'if', 'else', 'elseif', 'endif', 'foreach', 'endforeach', 'while', 'endwhile', 'break', ] def __init__(self, file_path, handle_style_error): self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines) for line_number, line in enumerate(lines, start=1): self._process_line(line_number, line) self._check_list_order(lines) def _process_line(self, line_number, line_content): if match('(^|\ +)#', line_content): # ignore comment line return l = line_content.expandtabs(4) # check command like message( "testing") if search('\(\ +', l): self._handle_style_error(line_number, 'whitespace/parentheses', 5, 'No space after "("') # check command like message("testing" ) if search('\ +\)', l) and not search('^\ +\)$', l): self._handle_style_error(line_number, 'whitespace/parentheses', 5, 'No space before ")"') self._check_trailing_whitespace(line_number, l) self._check_no_space_cmds(line_number, l) self._check_one_space_cmds(line_number, l) self._check_indent(line_number, line_content) def _check_trailing_whitespace(self, line_number, line_content): line_content = line_content.rstrip('\n') # chr(10), newline line_content = line_content.rstrip('\r') # chr(13), carriage return line_content = line_content.rstrip('\x0c') # chr(12), form feed, ^L stripped = line_content.rstrip() if line_content != stripped: self._handle_style_error(line_number, 'whitespace/trailing', 5, 'No trailing spaces') def _check_no_space_cmds(self, line_number, line_content): # check command like "SET (" or "Set(" for t in self.NO_SPACE_CMDS: self._check_non_lowercase_cmd(line_number, line_content, t) if search('(^|\ +)' + t.lower() + '\ +\(', line_content): msg = 'No space between command "' + t.lower( ) + '" and its parentheses, should be "' + t + '("' self._handle_style_error(line_number, 'whitespace/parentheses', 5, msg) def _check_one_space_cmds(self, line_number, line_content): # check command like "IF (" or "if(" or "if (" or "If ()" for t in self.ONE_SPACE_CMDS: self._check_non_lowercase_cmd(line_number, line_content, t) if search('(^|\ +)' + t.lower() + '(\(|\ \ +\()', line_content): msg = 'One space between command "' + t.lower( ) + '" and its parentheses, should be "' + t + ' ("' self._handle_style_error(line_number, 'whitespace/parentheses', 5, msg) def _check_non_lowercase_cmd(self, line_number, line_content, cmd): if searchIgnorecase('(^|\ +)' + cmd + '\ *\(', line_content) and \ (not search('(^|\ +)' + cmd.lower() + '\ *\(', line_content)): msg = 'Use lowercase command "' + cmd.lower() + '"' self._handle_style_error(line_number, 'command/lowercase', 5, msg) def _check_indent(self, line_number, line_content): #TODO (halton): add indent checking pass def _check_list_order(self, lines): last_line = None for line_number, line in enumerate(lines, start=1): matched = search('\$\{.*\}', line) if matched: continue line = line.strip() if last_line == None: matched = match( '(set\(|list\((APPEND|REMOVE_ITEM) )(?P<name>\w+)(?P<item>\s+\w+)?$', line) if matched: # FIXME: Add handling for include directories. if 'INCLUDE_DIRECTORIES' in matched.group('name'): continue empty_lines_count = 0 last_line = '' if matched.group('item'): msg = 'First listitem "%s" should be in a new line.' % matched.group( 'item').strip() self._handle_style_error(line_number, 'list/parentheses', 5, msg) else: matched = match('(?P<item>.+)?\)$', line) if matched: last_line = None if matched.group('item'): msg = 'The parentheses after the last listitem "%s" should be in a new line.' % matched.group( 'item').strip() self._handle_style_error(line_number, 'list/parentheses', 5, msg) elif line == '': empty_lines_count += 1 else: last_line_path = self._list_item_path(last_line) line_path = self._list_item_path(line) if line == last_line: msg = 'The item "%s" should be added only once to the list.' % line self._handle_style_error(line_number, 'list/duplicate', 5, msg) elif line_path < last_line_path or line_path == last_line_path and line < last_line: msg = 'Alphabetical sorting problem. "%s" should be before "%s".' % ( line, last_line) self._handle_style_error(line_number, 'list/order', 5, msg) elif last_line != '': if line_path != last_line_path: if empty_lines_count != 1: msg = 'There should be exactly one empty line instead of %d between "%s" and "%s".' % ( empty_lines_count, last_line, line) self._handle_style_error( line_number, 'list/emptyline', 5, msg) elif empty_lines_count != 0: msg = 'There should be no empty line between "%s" and "%s".' % ( last_line, line) self._handle_style_error(line_number, 'list/emptyline', 5, msg) last_line = line empty_lines_count = 0 def _list_item_path(self, item): token = item.split('/') if len(token) < 2: return '' return '/'.join(token[:-1])
class ChangeLogChecker(object): """Processes text lines for checking style.""" categories = set(['changelog/bugnumber', 'changelog/filechangedescriptionwhitespace']) def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if searchIgnorecase("Unreviewed", line): break if searchIgnorecase("build", line) and searchIgnorecase("fix", line): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") # check file change descriptions for style violations line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 # filter file change descriptions if not match('\s*\*\s', line): continue if search(':\s*$', line) or search(':\s', line): continue self.handle_style_error(line_no, "changelog/filechangedescriptionwhitespace", 5, "Need whitespace between colon and description") # check for a lingering "No new tests. (OOPS!)" left over from prepare-changeLog. line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 if match('\s*No new tests. \(OOPS!\)$', line): self.handle_style_error(line_no, "changelog/nonewtests", 5, "You should remove the 'No new tests' and either add and list tests, or explain why no new tests were possible.") def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines)
class CMakeChecker(object): """Processes CMake lines for checking style.""" # NO_SPACE_CMDS list are based on commands section of CMake document. # Now it is generated from # http://www.cmake.org/cmake/help/v2.8.10/cmake.html#section_Commands. # Some commands are from default CMake modules such as pkg_check_modules. # Please keep list in alphabet order. # # For commands in this list, spaces should not be added it and its # parentheses. For eg, message("testing"), not message ("testing") # # The conditional commands like if, else, endif, foreach, endforeach, # while, endwhile and break are listed in ONE_SPACE_CMDS NO_SPACE_CMDS = [ 'add_custom_command', 'add_custom_target', 'add_definitions', 'add_dependencies', 'add_executable', 'add_library', 'add_subdirectory', 'add_test', 'aux_source_directory', 'build_command', 'cmake_minimum_required', 'cmake_policy', 'configure_file', 'create_test_sourcelist', 'define_property', 'enable_language', 'enable_testing', 'endfunction', 'endmacro', 'execute_process', 'export', 'file', 'find_file', 'find_library', 'find_package', 'find_path', 'find_program', 'fltk_wrap_ui', 'function', 'get_cmake_property', 'get_directory_property', 'get_filename_component', 'get_property', 'get_source_file_property', 'get_target_property', 'get_test_property', 'include', 'include_directories', 'include_external_msproject', 'include_regular_expression', 'install', 'link_directories', 'list', 'load_cache', 'load_command', 'macro', 'mark_as_advanced', 'math', 'message', 'option', #From FindPkgConfig.cmake 'pkg_check_modules', 'project', 'qt_wrap_cpp', 'qt_wrap_ui', 'remove_definitions', 'return', 'separate_arguments', 'set', 'set_directory_properties', 'set_property', 'set_source_files_properties', 'set_target_properties', 'set_tests_properties', 'site_name', 'source_group', 'string', 'target_link_libraries', 'try_compile', 'try_run', 'unset', 'variable_watch', ] # CMake conditional commands, require one space between command and # its parentheses, such as "if (", "foreach (", etc. ONE_SPACE_CMDS = [ 'if', 'else', 'elseif', 'endif', 'foreach', 'endforeach', 'while', 'endwhile', 'break', ] def __init__(self, file_path, handle_style_error): self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines) self._num_lines = len(lines) for l in xrange(self._num_lines): self._process_line(l + 1, lines[l]) def _process_line(self, line_number, line_content): if re.match('(^|\ +)#', line_content): # ignore comment line return l = line_content.expandtabs(4) # check command like message( "testing") if re.search('\(\ +', l): self._handle_style_error(line_number, 'whitespace/parentheses', 5, 'No space after "("') # check command like message("testing" ) if re.search('\ +\)', l) and not re.search('^\ +\)$', l): self._handle_style_error(line_number, 'whitespace/parentheses', 5, 'No space before ")"') self._check_trailing_whitespace(line_number, l) self._check_no_space_cmds(line_number, l) self._check_one_space_cmds(line_number, l) self._check_indent(line_number, line_content) def _check_trailing_whitespace(self, line_number, line_content): line_content = line_content.rstrip('\n') # chr(10), newline line_content = line_content.rstrip('\r') # chr(13), carriage return line_content = line_content.rstrip('\x0c') # chr(12), form feed, ^L stripped = line_content.rstrip() if line_content != stripped: self._handle_style_error(line_number, 'whitespace/trailing', 5, 'No trailing spaces') def _check_no_space_cmds(self, line_number, line_content): # check command like "SET (" or "Set(" for t in self.NO_SPACE_CMDS: self._check_non_lowercase_cmd(line_number, line_content, t) if re.search('(^|\ +)' + t.lower() + '\ +\(', line_content): msg = 'No space between command "' + t.lower() + '" and its parentheses, should be "' + t + '("' self._handle_style_error(line_number, 'whitespace/parentheses', 5, msg) def _check_one_space_cmds(self, line_number, line_content): # check command like "IF (" or "if(" or "if (" or "If ()" for t in self.ONE_SPACE_CMDS: self._check_non_lowercase_cmd(line_number, line_content, t) if re.search('(^|\ +)' + t.lower() + '(\(|\ \ +\()', line_content): msg = 'One space between command "' + t.lower() + '" and its parentheses, should be "' + t + ' ("' self._handle_style_error(line_number, 'whitespace/parentheses', 5, msg) def _check_non_lowercase_cmd(self, line_number, line_content, cmd): if re.search('(^|\ +)' + cmd + '\ *\(', line_content, flags=re.IGNORECASE) and \ (not re.search('(^|\ +)' + cmd.lower() + '\ *\(', line_content)): msg = 'Use lowercase command "' + cmd.lower() + '"' self._handle_style_error(line_number, 'command/lowercase', 5, msg) def _check_indent(self, line_number, line_content): #TODO (halton): add indent checking pass
class ChangeLogChecker(object): """Processes text lines for checking style.""" categories = set(['changelog/bugnumber', 'changelog/filechangedescriptionwhitespace']) def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if searchIgnorecase("Unreviewed", line): break if searchIgnorecase("build", line) and searchIgnorecase("fix", line): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") # check file change descriptions for style violations line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 # filter file change descriptions if not match('\s*\*\s', line): continue if search(':\s*$', line) or search(':\s', line): continue self.handle_style_error(line_no, "changelog/filechangedescriptionwhitespace", 5, "Need whitespace between colon and description") # check for a lingering "No new tests (OOPS!)." left over from prepare-changeLog. line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 if match('\s*No new tests \(OOPS!\)\.$', line): self.handle_style_error(line_no, "changelog/nonewtests", 5, "You should remove the 'No new tests' and either add and list tests, or explain why no new tests were possible.") self.check_for_unwanted_security_phrases(first_line_checked, entry_lines) def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines) def contains_phrase_in_first_line_or_across_two_lines(self, phrase, line1, line2): return searchIgnorecase(phrase, line1) or ((not searchIgnorecase(phrase, line2)) and searchIgnorecase(phrase, line1 + " " + line2)) def check_for_unwanted_security_phrases(self, first_line_checked, lines): unwanted_security_phrases = [ "arbitrary code execution", "buffer overflow", "buffer overrun", "buffer underrun", "dangling pointer", "double free", "fuzzer", "fuzzing", "fuzz test", "invalid cast", "jsfunfuzz", "malicious", "memory corruption", "security bug", "security flaw", "use after free", "use-after-free", "UXSS", "WTFCrashWithSecurityImplication", "spoof", # Captures spoof, spoofed, spoofing "vulnerab", # Captures vulnerable, vulnerability, vulnerabilities ] lines_with_single_spaces = [] for line in lines: lines_with_single_spaces.append(" ".join(line.split())) found_unwanted_security_phrases = [] last_index = len(lines_with_single_spaces) - 1 first_line_number_with_unwanted_phrase = maxsize for unwanted_phrase in unwanted_security_phrases: for line_index, line in enumerate(lines_with_single_spaces): next_line = "" if line_index >= last_index else lines_with_single_spaces[line_index + 1] if self.contains_phrase_in_first_line_or_across_two_lines(unwanted_phrase, line, next_line): found_unwanted_security_phrases.append(unwanted_phrase) first_line_number_with_unwanted_phrase = min(first_line_number_with_unwanted_phrase, first_line_checked + line_index) if len(found_unwanted_security_phrases) > 0: self.handle_style_error(first_line_number_with_unwanted_phrase, "changelog/unwantedsecurityterms", 3, "Please consider whether the use of security-sensitive phrasing could help someone exploit WebKit: {}".format(", ".join(found_unwanted_security_phrases)))
class ChangeLogChecker(object): """Processes text lines for checking style.""" categories = set( ['changelog/bugnumber', 'changelog/filechangedescriptionwhitespace']) def __init__(self, file_path, handle_style_error, should_line_be_checked): self.file_path = file_path self.handle_style_error = handle_style_error self.should_line_be_checked = should_line_be_checked self._tab_checker = TabChecker(file_path, handle_style_error) def check_entry(self, first_line_checked, entry_lines): if not entry_lines: return for line in entry_lines: if parse_bug_id_from_changelog(line): break if searchIgnorecase("Unreviewed", line): break if searchIgnorecase("build", line) and searchIgnorecase( "fix", line): break else: self.handle_style_error(first_line_checked, "changelog/bugnumber", 5, "ChangeLog entry has no bug number") # check file change descriptions for style violations line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 # filter file change descriptions if not match('\s*\*\s', line): continue if search(':\s*$', line) or search(':\s', line): continue self.handle_style_error( line_no, "changelog/filechangedescriptionwhitespace", 5, "Need whitespace between colon and description") # check for a lingering "No new tests (OOPS!)." left over from prepare-changeLog. line_no = first_line_checked - 1 for line in entry_lines: line_no = line_no + 1 if match('\s*No new tests \(OOPS!\)\.$', line): self.handle_style_error( line_no, "changelog/nonewtests", 5, "You should remove the 'No new tests' and either add and list tests, or explain why no new tests were possible." ) self.check_for_unwanted_security_phrases(first_line_checked, entry_lines) def check(self, lines): self._tab_checker.check(lines) first_line_checked = 0 entry_lines = [] for line_index, line in enumerate(lines): if not self.should_line_be_checked(line_index + 1): # If we transitioned from finding changed lines to # unchanged lines, then we are done. if first_line_checked: break continue if not first_line_checked: first_line_checked = line_index + 1 entry_lines.append(line) self.check_entry(first_line_checked, entry_lines) def contains_phrase_in_first_line_or_across_two_lines( self, phrase, line1, line2): return searchIgnorecase(phrase, line1) or ( (not searchIgnorecase(phrase, line2)) and searchIgnorecase(phrase, line1 + " " + line2)) def check_for_unwanted_security_phrases(self, first_line_checked, lines): unwanted_security_phrases = [ "arbitrary code execution", "buffer overflow", "buffer overrun", "buffer underrun", "dangling pointer", "double free", "fuzzer", "fuzzing", "fuzz test", "invalid cast", "jsfunfuzz", "malicious", "memory corruption", "security bug", "security flaw", "use after free", "use-after-free", "UXSS", "WTFCrashWithSecurityImplication", "spoof", # Captures spoof, spoofed, spoofing "vulnerab", # Captures vulnerable, vulnerability, vulnerabilities ] lines_with_single_spaces = [] for line in lines: lines_with_single_spaces.append(" ".join(line.split())) found_unwanted_security_phrases = [] last_index = len(lines_with_single_spaces) - 1 first_line_number_with_unwanted_phrase = maxsize for unwanted_phrase in unwanted_security_phrases: for line_index, line in enumerate(lines_with_single_spaces): next_line = "" if line_index >= last_index else lines_with_single_spaces[ line_index + 1] if self.contains_phrase_in_first_line_or_across_two_lines( unwanted_phrase, line, next_line): found_unwanted_security_phrases.append(unwanted_phrase) first_line_number_with_unwanted_phrase = min( first_line_number_with_unwanted_phrase, first_line_checked + line_index) if len(found_unwanted_security_phrases) > 0: self.handle_style_error( first_line_number_with_unwanted_phrase, "changelog/unwantedsecurityterms", 3, "Please consider whether the use of security-sensitive phrasing could help someone exploit WebKit: {}" .format(", ".join(found_unwanted_security_phrases)))
class CMakeChecker(object): """Processes CMake lines for checking style.""" # NO_SPACE_CMDS list are based on commands section of CMake document. # Now it is generated from # http://www.cmake.org/cmake/help/v2.8.10/cmake.html#section_Commands. # Some commands are from default CMake modules such as pkg_check_modules. # Please keep list in alphabet order. # # For commands in this list, spaces should not be added it and its # parentheses. For eg, message("testing"), not message ("testing") # # The conditional commands like if, else, endif, foreach, endforeach, # while, endwhile and break are listed in ONE_SPACE_CMDS NO_SPACE_CMDS = [ 'add_custom_command', 'add_custom_target', 'add_definitions', 'add_dependencies', 'add_executable', 'add_library', 'add_subdirectory', 'add_test', 'aux_source_directory', 'build_command', 'cmake_minimum_required', 'cmake_policy', 'configure_file', 'create_test_sourcelist', 'define_property', 'enable_language', 'enable_testing', 'endfunction', 'endmacro', 'execute_process', 'export', 'file', 'find_file', 'find_library', 'find_package', 'find_path', 'find_program', 'fltk_wrap_ui', 'function', 'get_cmake_property', 'get_directory_property', 'get_filename_component', 'get_property', 'get_source_file_property', 'get_target_property', 'get_test_property', 'include', 'include_directories', 'include_external_msproject', 'include_regular_expression', 'install', 'link_directories', 'list', 'load_cache', 'load_command', 'macro', 'mark_as_advanced', 'math', 'message', 'option', #From FindPkgConfig.cmake 'pkg_check_modules', 'project', 'remove_definitions', 'return', 'separate_arguments', 'set', 'set_directory_properties', 'set_property', 'set_source_files_properties', 'set_target_properties', 'set_tests_properties', 'site_name', 'source_group', 'string', 'target_link_libraries', 'try_compile', 'try_run', 'unset', 'variable_watch', ] # CMake conditional commands, require one space between command and # its parentheses, such as "if (", "foreach (", etc. ONE_SPACE_CMDS = [ 'if', 'else', 'elseif', 'endif', 'foreach', 'endforeach', 'while', 'endwhile', 'break', ] def __init__(self, file_path, handle_style_error): self._handle_style_error = handle_style_error self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): self._tab_checker.check(lines) for line_number, line in enumerate(lines, start=1): self._process_line(line_number, line) self._check_list_order(lines) def _process_line(self, line_number, line_content): if match('(^|\ +)#', line_content): # ignore comment line return l = line_content.expandtabs(4) # check command like message( "testing") if search('\(\ +', l): self._handle_style_error(line_number, 'whitespace/parentheses', 5, 'No space after "("') # check command like message("testing" ) if search('\ +\)', l) and not search('^\ +\)$', l): self._handle_style_error(line_number, 'whitespace/parentheses', 5, 'No space before ")"') self._check_trailing_whitespace(line_number, l) self._check_no_space_cmds(line_number, l) self._check_one_space_cmds(line_number, l) self._check_indent(line_number, line_content) def _check_trailing_whitespace(self, line_number, line_content): line_content = line_content.rstrip('\n') # chr(10), newline line_content = line_content.rstrip('\r') # chr(13), carriage return line_content = line_content.rstrip('\x0c') # chr(12), form feed, ^L stripped = line_content.rstrip() if line_content != stripped: self._handle_style_error(line_number, 'whitespace/trailing', 5, 'No trailing spaces') def _check_no_space_cmds(self, line_number, line_content): # check command like "SET (" or "Set(" for t in self.NO_SPACE_CMDS: self._check_non_lowercase_cmd(line_number, line_content, t) if search('(^|\ +)' + t.lower() + '\ +\(', line_content): msg = 'No space between command "' + t.lower() + '" and its parentheses, should be "' + t + '("' self._handle_style_error(line_number, 'whitespace/parentheses', 5, msg) def _check_one_space_cmds(self, line_number, line_content): # check command like "IF (" or "if(" or "if (" or "If ()" for t in self.ONE_SPACE_CMDS: self._check_non_lowercase_cmd(line_number, line_content, t) if search('(^|\ +)' + t.lower() + '(\(|\ \ +\()', line_content): msg = 'One space between command "' + t.lower() + '" and its parentheses, should be "' + t + ' ("' self._handle_style_error(line_number, 'whitespace/parentheses', 5, msg) def _check_non_lowercase_cmd(self, line_number, line_content, cmd): if searchIgnorecase('(^|\ +)' + cmd + '\ *\(', line_content) and \ (not search('(^|\ +)' + cmd.lower() + '\ *\(', line_content)): msg = 'Use lowercase command "' + cmd.lower() + '"' self._handle_style_error(line_number, 'command/lowercase', 5, msg) def _check_indent(self, line_number, line_content): #TODO (halton): add indent checking pass def _check_list_order(self, lines): last_line = None for line_number, line in enumerate(lines, start=1): matched = search('\$\{.*\}', line) if matched: continue line = line.strip() if last_line == None: matched = match('(set\(|list\((APPEND|REMOVE_ITEM) )(?P<name>\w+)(?P<item>\s+\w+)?$', line) if matched: # FIXME: Add handling for include directories. if 'INCLUDE_DIRECTORIES' in matched.group('name'): continue empty_lines_count = 0 last_line = '' if matched.group('item'): msg = 'First listitem "%s" should be in a new line.' % matched.group('item').strip() self._handle_style_error(line_number, 'list/parentheses', 5, msg) else: matched = match('(?P<item>.+)?\)$', line) if matched: last_line = None if matched.group('item'): msg = 'The parentheses after the last listitem "%s" should be in a new line.' % matched.group('item').strip() self._handle_style_error(line_number, 'list/parentheses', 5, msg) elif line == '': empty_lines_count += 1 else: last_line_path = self._list_item_path(last_line) line_path = self._list_item_path(line) if line == last_line: msg = 'The item "%s" should be added only once to the list.' % line self._handle_style_error(line_number, 'list/duplicate', 5, msg) elif line_path < last_line_path or line_path == last_line_path and line < last_line: msg = 'Alphabetical sorting problem. "%s" should be before "%s".' % (line, last_line) self._handle_style_error(line_number, 'list/order', 5, msg) elif last_line != '': if line_path != last_line_path: if empty_lines_count != 1: msg = 'There should be exactly one empty line instead of %d between "%s" and "%s".' % (empty_lines_count, last_line, line) self._handle_style_error(line_number, 'list/emptyline', 5, msg) elif empty_lines_count != 0: msg = 'There should be no empty line between "%s" and "%s".' % (last_line, line) self._handle_style_error(line_number, 'list/emptyline', 5, msg) last_line = line empty_lines_count = 0 def _list_item_path(self, item): token = item.split('/') if len(token) < 2: return '' return '/'.join(token[:-1])