1 # Copyright 2017 The Chromium Authors
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Presubmit script for ios.
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details about the presubmit API built into depot_tools.
15 NULLABILITY_PATTERN = r'(nonnull|nullable|_Nullable|_Nonnull)'
16 TODO_PATTERN = r'TO[D]O\(([^\)]*)\)'
17 CRBUG_PATTERN = r'crbug\.com/\d+$'
18 INCLUDE_PATTERN = r'^#include'
19 PIPE_IN_COMMENT_PATTERN = r'//.*[^|]\|(?!\|)'
20 IOS_PACKAGE_PATTERN = r'^ios'
22 '#if !defined(__has_feature) || !__has_feature(objc_arc)',
23 '#error "This file requires ARC support."',
27 def IsSubListOf(needle, hay):
28 """Returns whether there is a slice of |hay| equal to |needle|."""
29 for i, line in enumerate(hay):
31 if needle == hay[i:i + len(needle)]:
36 def _CheckARCCompilationGuard(input_api, output_api):
37 """ Checks whether new objc files have proper ARC compile guards."""
38 files_without_headers = []
39 for f in input_api.AffectedFiles():
43 _, ext = os.path.splitext(f.LocalPath())
44 if ext not in ('.m', '.mm'):
47 if not IsSubListOf(ARC_COMPILE_GUARD, f.NewContents()):
48 files_without_headers.append(f.LocalPath())
50 if not files_without_headers:
53 plural_suffix = '' if len(files_without_headers) == 1 else 's'
54 error_message = '\n'.join([
55 'Found new Objective-C implementation file%(plural)s without compile'
56 ' guard%(plural)s. Please use the following compile guard'
58 'plural': plural_suffix
60 ] + ARC_COMPILE_GUARD + files_without_headers) + '\n'
62 return [output_api.PresubmitError(error_message)]
65 def _CheckNullabilityAnnotations(input_api, output_api):
66 """ Checks whether there are nullability annotations in ios code."""
67 nullability_regex = input_api.re.compile(NULLABILITY_PATTERN)
70 for f in input_api.AffectedFiles():
71 for line_num, line in f.ChangedContents():
72 if nullability_regex.search(line):
73 errors.append('%s:%s' % (f.LocalPath(), line_num))
77 plural_suffix = '' if len(errors) == 1 else 's'
78 error_message = ('Found Nullability annotation%(plural)s. '
79 'Prefer DCHECKs in ios code to check for nullness:' % {
80 'plural': plural_suffix
83 return [output_api.PresubmitPromptWarning(error_message, items=errors)]
86 def _CheckBugInToDo(input_api, output_api):
87 """ Checks whether TODOs in ios code are identified by a bug number."""
89 for f in input_api.AffectedFiles():
90 for line_num, line in f.ChangedContents():
91 if _HasToDoWithNoBug(input_api, line):
92 errors.append('%s:%s' % (f.LocalPath(), line_num))
96 plural_suffix = '' if len(errors) == 1 else 's'
97 error_message = '\n'.join([
99 'DO%(plural)s without bug number%(plural)s (expected format '
101 'DO(crbug.com/######)\":' % {
102 'plural': plural_suffix
106 return [output_api.PresubmitError(error_message)]
109 def _CheckHasNoIncludeDirectives(input_api, output_api):
110 """ Checks that #include preprocessor directives are not present."""
112 for f in input_api.AffectedFiles():
113 if not _IsInIosPackage(input_api, f.LocalPath()):
115 _, ext = os.path.splitext(f.LocalPath())
118 for line_num, line in f.ChangedContents():
119 if _HasIncludeDirective(input_api, line):
120 errors.append('%s:%s' % (f.LocalPath(), line_num))
124 singular_plural = 'it' if len(errors) == 1 else 'them'
125 plural_suffix = '' if len(errors) == 1 else 's'
126 error_message = '\n'.join([
127 'Found usage of `#include` preprocessor directive%(plural)s! Please, '
128 'replace %(singular_plural)s with `#import` preprocessor '
129 'directive%(plural)s instead. '
130 'Consider replacing all existing `#include` with `#import` (if any) in '
131 'this file for the code clean up. See '
132 'https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main'
133 '/styleguide/objective-c/objective-c.md'
134 '#import-and-include-in-the-directory for more details. '
135 '\n\nAffected file%(plural)s:' % {
136 'plural': plural_suffix,
137 'singular_plural': singular_plural
141 return [output_api.PresubmitError(error_message)]
144 def _CheckHasNoPipeInComment(input_api, output_api):
145 """ Checks that comments don't contain pipes."""
146 pipe_regex = input_api.re.compile(PIPE_IN_COMMENT_PATTERN)
149 for f in input_api.AffectedFiles():
150 if not _IsInIosPackage(input_api, f.LocalPath()):
152 for line_num, line in f.ChangedContents():
153 if pipe_regex.search(line):
154 errors.append('%s:%s' % (f.LocalPath(), line_num))
157 error_message = '\n'.join([
158 'Please use backticks "`" instead of pipes "|" if you need to quote'
159 ' variable names and symbols in comments.\n'
160 'Found potential uses of pipes in:'
163 return [output_api.PresubmitPromptWarning(error_message)]
166 def _IsInIosPackage(input_api, path):
167 """ Returns True if path is within ios package"""
168 ios_package_regex = input_api.re.compile(IOS_PACKAGE_PATTERN)
170 return ios_package_regex.search(path)
173 def _HasIncludeDirective(input_api, line):
174 """ Returns True if #include is found in the line"""
175 include_regex = input_api.re.compile(INCLUDE_PATTERN)
177 return include_regex.search(line)
180 def _HasToDoWithNoBug(input_api, line):
181 """ Returns True if TODO is not identified by a bug number."""
182 todo_regex = input_api.re.compile(TODO_PATTERN)
183 crbug_regex = input_api.re.compile(CRBUG_PATTERN)
185 todo_match = todo_regex.search(line)
188 crbug_match = crbug_regex.match(todo_match.group(1))
189 return not crbug_match
192 def CheckChangeOnUpload(input_api, output_api):
194 results.extend(_CheckBugInToDo(input_api, output_api))
195 results.extend(_CheckNullabilityAnnotations(input_api, output_api))
196 results.extend(_CheckARCCompilationGuard(input_api, output_api))
197 results.extend(_CheckHasNoIncludeDirectives(input_api, output_api))
198 results.extend(_CheckHasNoPipeInComment(input_api, output_api))