Index: PRESUBMIT.py |
diff --git a/PRESUBMIT.py b/PRESUBMIT.py |
index 686ae511773be4835973f9e2dac4ab7d9776bd2b..a87d017275ec7c8cf0e74943e58c4af2bcb26eed 100644 |
--- a/PRESUBMIT.py |
+++ b/PRESUBMIT.py |
@@ -58,6 +58,11 @@ _TEST_ONLY_WARNING = ( |
'not perfect. The commit queue will not block on this warning.') |
+_INCLUDE_IMPORT_WARNING = ( |
+ 'You are including ObjC headers or importing C++ headers. Remember to use ' |
+ 'the right #include/#import directive.') |
+ |
+ |
_INCLUDE_ORDER_WARNING = ( |
'Your #include order seems to be broken. Remember to use the right ' |
'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/' |
@@ -790,6 +795,133 @@ def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api): |
return results |
+def _CheckHeaderIsObjectiveC(input_api, included_file, included_file_content): |
+ """Checks if f is a C++ or an Objective-C header. |
+ |
+ An Objective-C header is a header with a line starting with |
+ - #import |
+ - @class |
+ - @interface |
+ - @implementation |
+ - @protocol |
+ - @end |
+ |
+ or with the two lines |
+ #if !defined(__OBJC__) |
+ #error ... |
+ |
+ Returns a pair of booleans. |
+ - First is true if the determination of Objective-C could be done without |
+ error |
+ - Second is true if the file has been determined as Objective-C header. |
+ """ |
+ file_path_ios_mac_pattern = input_api.re.compile( |
+ r'(?:.*[_./])?(?:ios|mac)[_./].*') |
+ if not file_path_ios_mac_pattern.match(included_file): |
+ return (False, False) |
+ guard_noobjc_pattern = input_api.re.compile( |
+ r'\s*#if !defined\(__OBJC__\).*') |
+ guard_objc_pattern = input_api.re.compile( |
+ r'\s*#if defined\(__OBJC__\).*') |
+ error_pattern = input_api.re.compile( |
+ r'\s*#error .*') |
+ block_pattern = input_api.re.compile( |
+ r'\s*typedef.*\(\^.*') |
+ objc_pattern = input_api.re.compile( |
+ r'\s*(@class|@interface|@end|@implementation|@protocol|#import).*') |
+ lines = included_file_content.NewContents() |
+ previous_was_noobjc_define = False |
+ for included_line in lines: |
+ if guard_noobjc_pattern.match(included_line): |
+ previous_was_noobjc_define = True |
+ continue |
+ if previous_was_noobjc_define: |
+ # Found No Objc Guard. The next line must be check to see if it is |
+ # throwing an error. |
+ if error_pattern.match(included_line): |
+ # There is an error thrown if the file is compiled without ObjC. |
+ # Header is Objective-C. |
+ return (True, True) |
+ else: |
+ # There are ifdefs about Objective-C, file can likely be imported or |
+ # included. |
+ return (False, False) |
+ previous_was_noobjc_define = False |
+ if guard_objc_pattern.match(included_line): |
+ # Found ObjcGuard. This file can be included or imported. |
+ return (False, False) |
+ if objc_pattern.match(included_line) or block_pattern.match(included_line): |
+ # Found an Objective-C keyword. File is an Objective-C header. |
+ return (True, True) |
+ # No Objective-C clue, file must be C++. |
+ return (True, False) |
+ |
+ |
+def _CheckIncludeImportInFile(input_api, f): |
+ """Checks the include/import coherence in file f.""" |
+ custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"') |
+ custom_import_pattern = input_api.re.compile(r'\s*#import "(?P<FILE>.*)"') |
+ contents = f.NewContents() |
+ warnings = [] |
+ for (line_num, line) in enumerate(contents): |
+ is_imported = False |
+ included_file = "" |
+ match_cpp = custom_include_pattern.match(line) |
+ if match_cpp: |
+ match_dict = match_cpp.groupdict() |
+ included_file = match_dict['FILE'] |
+ match_objc = custom_import_pattern.match(line) |
+ if match_objc: |
+ match_dict = match_objc.groupdict() |
+ included_file = match_dict['FILE'] |
+ is_imported = True |
+ if included_file == "": |
+ continue |
+ included_absolute_path = input_api.os_path.join( |
+ input_api.change.RepositoryRoot(), included_file) |
+ if not input_api.os_path.isfile(included_absolute_path): |
+ # Included file may need a custom -I flag. |
+ # Ignore it. |
+ continue |
+ included_file_content = input_api.ReadFile(included_absolute_path) |
+ (detection_ok, has_objc) = \ |
+ _CheckHeaderIsObjectiveC(input_api, |
+ included_file, |
+ included_file_content) |
+ if not detection_ok: |
+ # There is an Objective-C guard, the file is internded to be included or |
+ # imported. |
+ continue |
+ if is_imported and f.LocalPath().endswith('.cc'): |
+ warnings.append('%s:%d %s' % (f.LocalPath(), line_num, line)) |
+ warnings.append('Header is imported in a C++ file.') |
+ if has_objc and not is_imported: |
+ warnings.append('%s:%d %s' % (f.LocalPath(), line_num, line)) |
+ warnings.append('Header is included but has Objective-C instructions.') |
+ if not has_objc and is_imported: |
+ warnings.append('%s:%d %s' % (f.LocalPath(), line_num, line)) |
+ warnings.append('Header is imported but has no Objective-C instructions.') |
+ return warnings |
+ |
+ |
+def _CheckIncludeImport(input_api, output_api): |
+ """Checks that C++ headers are included, Objective-C headers imported. |
+ """ |
+ def FileFilterIncludeImport(affected_file): |
+ black_list = (_EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST) |
+ return input_api.FilterSourceFile(affected_file, black_list=black_list) |
+ |
+ warnings = [] |
+ for f in input_api.AffectedFiles(file_filter=FileFilterIncludeImport): |
+ warnings.extend(_CheckIncludeImportInFile(input_api, f)) |
+ |
+ results = [] |
+ if warnings: |
+ results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_IMPORT_WARNING, |
+ warnings)) |
+ return results |
+ |
+ |
def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums): |
"""Checks that the lines in scope occur in the right order. |
@@ -2088,6 +2220,7 @@ def _CommonChecks(input_api, output_api): |
results.extend(_CheckFilePermissions(input_api, output_api)) |
results.extend(_CheckTeamTags(input_api, output_api)) |
results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api)) |
+ results.extend(_CheckIncludeImport(input_api, output_api)) |
results.extend(_CheckIncludeOrder(input_api, output_api)) |
results.extend(_CheckForVersionControlConflicts(input_api, output_api)) |
results.extend(_CheckPatchFiles(input_api, output_api)) |