PRESUBMIT.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. # Copyright 2017 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Presubmit script for ios.
  5. See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
  6. for more details about the presubmit API built into depot_tools.
  7. """
  8. import os
  9. USE_PYTHON3 = True
  10. NULLABILITY_PATTERN = r'(nonnull|nullable|_Nullable|_Nonnull)'
  11. TODO_PATTERN = r'TO[D]O\(([^\)]*)\)'
  12. CRBUG_PATTERN = r'crbug\.com/\d+$'
  13. INCLUDE_PATTERN = r'^#include'
  14. IOS_PACKAGE_PATTERN = r'^ios'
  15. ARC_COMPILE_GUARD = [
  16. '#if !defined(__has_feature) || !__has_feature(objc_arc)',
  17. '#error "This file requires ARC support."',
  18. '#endif',
  19. ]
  20. def IsSubListOf(needle, hay):
  21. """Returns whether there is a slice of |hay| equal to |needle|."""
  22. for i, line in enumerate(hay):
  23. if line == needle[0]:
  24. if needle == hay[i:i+len(needle)]:
  25. return True
  26. return False
  27. def _CheckARCCompilationGuard(input_api, output_api):
  28. """ Checks whether new objc files have proper ARC compile guards."""
  29. files_without_headers = []
  30. for f in input_api.AffectedFiles():
  31. if f.Action() != 'A':
  32. continue
  33. _, ext = os.path.splitext(f.LocalPath())
  34. if ext not in ('.m', '.mm'):
  35. continue
  36. if not IsSubListOf(ARC_COMPILE_GUARD, f.NewContents()):
  37. files_without_headers.append(f.LocalPath())
  38. if not files_without_headers:
  39. return []
  40. plural_suffix = '' if len(files_without_headers) == 1 else 's'
  41. error_message = '\n'.join([
  42. 'Found new Objective-C implementation file%(plural)s without compile'
  43. ' guard%(plural)s. Please use the following compile guard'
  44. ':' % {'plural': plural_suffix}
  45. ] + ARC_COMPILE_GUARD + files_without_headers) + '\n'
  46. return [output_api.PresubmitError(error_message)]
  47. def _CheckNullabilityAnnotations(input_api, output_api):
  48. """ Checks whether there are nullability annotations in ios code."""
  49. nullability_regex = input_api.re.compile(NULLABILITY_PATTERN)
  50. errors = []
  51. for f in input_api.AffectedFiles():
  52. for line_num, line in f.ChangedContents():
  53. if nullability_regex.search(line):
  54. errors.append('%s:%s' % (f.LocalPath(), line_num))
  55. if not errors:
  56. return []
  57. plural_suffix = '' if len(errors) == 1 else 's'
  58. error_message = ('Found Nullability annotation%(plural)s. '
  59. 'Prefer DCHECKs in ios code to check for nullness:'
  60. % {'plural': plural_suffix})
  61. return [output_api.PresubmitPromptWarning(error_message, items=errors)]
  62. def _CheckBugInToDo(input_api, output_api):
  63. """ Checks whether TODOs in ios code are identified by a bug number."""
  64. errors = []
  65. for f in input_api.AffectedFiles():
  66. for line_num, line in f.ChangedContents():
  67. if _HasToDoWithNoBug(input_api, line):
  68. errors.append('%s:%s' % (f.LocalPath(), line_num))
  69. if not errors:
  70. return []
  71. plural_suffix = '' if len(errors) == 1 else 's'
  72. error_message = '\n'.join([
  73. 'Found TO''DO%(plural)s without bug number%(plural)s (expected format is '
  74. '\"TO''DO(crbug.com/######)\":' % {'plural': plural_suffix}
  75. ] + errors) + '\n'
  76. return [output_api.PresubmitError(error_message)]
  77. def _CheckHasNoIncludeDirectives(input_api, output_api):
  78. """ Checks that #include preprocessor directives are not present."""
  79. errors = []
  80. for f in input_api.AffectedFiles():
  81. if not _IsInIosPackage(input_api, f.LocalPath()):
  82. continue
  83. _, ext = os.path.splitext(f.LocalPath())
  84. if ext != '.mm':
  85. continue
  86. for line_num, line in f.ChangedContents():
  87. if _HasIncludeDirective(input_api, line):
  88. errors.append('%s:%s' % (f.LocalPath(), line_num))
  89. if not errors:
  90. return []
  91. singular_plural = 'it' if len(errors) == 1 else 'them'
  92. plural_suffix = '' if len(errors) == 1 else 's'
  93. error_message = '\n'.join([
  94. 'Found usage of `#include` preprocessor directive%(plural)s! Please, '
  95. 'replace %(singular_plural)s with `#import` preprocessor '
  96. 'directive%(plural)s instead. '
  97. 'Consider replacing all existing `#include` with `#import` (if any) in '
  98. 'this file for the code clean up. See '
  99. 'https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main'
  100. '/styleguide/objective-c/objective-c.md#import-and-include-in-the-directory'
  101. ' for more details. '
  102. '\n\nAffected file%(plural)s:' % {'plural': plural_suffix,
  103. 'singular_plural': singular_plural }
  104. ] + errors) + '\n'
  105. return [output_api.PresubmitError(error_message)]
  106. def _IsInIosPackage(input_api, path):
  107. """ Returns True if path is within ios package"""
  108. ios_package_regex = input_api.re.compile(IOS_PACKAGE_PATTERN)
  109. return ios_package_regex.search(path)
  110. def _HasIncludeDirective(input_api, line):
  111. """ Returns True if #include is found in the line"""
  112. include_regex = input_api.re.compile(INCLUDE_PATTERN)
  113. return include_regex.search(line)
  114. def _HasToDoWithNoBug(input_api, line):
  115. """ Returns True if TODO is not identified by a bug number."""
  116. todo_regex = input_api.re.compile(TODO_PATTERN)
  117. crbug_regex = input_api.re.compile(CRBUG_PATTERN)
  118. todo_match = todo_regex.search(line)
  119. if not todo_match:
  120. return False
  121. crbug_match = crbug_regex.match(todo_match.group(1))
  122. return not crbug_match
  123. def CheckChangeOnUpload(input_api, output_api):
  124. results = []
  125. results.extend(_CheckBugInToDo(input_api, output_api))
  126. results.extend(_CheckNullabilityAnnotations(input_api, output_api))
  127. results.extend(_CheckARCCompilationGuard(input_api, output_api))
  128. results.extend(_CheckHasNoIncludeDirectives(input_api, output_api))
  129. return results