| # Copyright 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Presubmit script for ios. |
| |
| See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts |
| for more details about the presubmit API built into depot_tools. |
| """ |
| |
| import os |
| |
| USE_PYTHON3 = True |
| |
| NULLABILITY_PATTERN = r'(nonnull|nullable|_Nullable|_Nonnull)' |
| TODO_PATTERN = r'TO[D]O\(([^\)]*)\)' |
| CRBUG_PATTERN = r'crbug\.com/\d+$' |
| ARC_COMPILE_GUARD = [ |
| '#if !defined(__has_feature) || !__has_feature(objc_arc)', |
| '#error "This file requires ARC support."', |
| '#endif', |
| ] |
| |
| def IsSubListOf(needle, hay): |
| """Returns whether there is a slice of |hay| equal to |needle|.""" |
| for i, line in enumerate(hay): |
| if line == needle[0]: |
| if needle == hay[i:i+len(needle)]: |
| return True |
| return False |
| |
| def _CheckARCCompilationGuard(input_api, output_api): |
| """ Checks whether new objc files have proper ARC compile guards.""" |
| files_without_headers = [] |
| for f in input_api.AffectedFiles(): |
| if f.Action() != 'A': |
| continue |
| |
| _, ext = os.path.splitext(f.LocalPath()) |
| if ext not in ('.m', '.mm'): |
| continue |
| |
| if not IsSubListOf(ARC_COMPILE_GUARD, f.NewContents()): |
| files_without_headers.append(f.LocalPath()) |
| |
| if not files_without_headers: |
| return [] |
| |
| plural_suffix = '' if len(files_without_headers) == 1 else 's' |
| error_message = '\n'.join([ |
| 'Found new Objective-C implementation file%(plural)s without compile' |
| ' guard%(plural)s. Please use the following compile guard' |
| ':' % {'plural': plural_suffix} |
| ] + ARC_COMPILE_GUARD + files_without_headers) + '\n' |
| |
| return [output_api.PresubmitError(error_message)] |
| |
| |
| def _CheckNullabilityAnnotations(input_api, output_api): |
| """ Checks whether there are nullability annotations in ios code.""" |
| nullability_regex = input_api.re.compile(NULLABILITY_PATTERN) |
| |
| errors = [] |
| for f in input_api.AffectedFiles(): |
| for line_num, line in f.ChangedContents(): |
| if nullability_regex.search(line): |
| errors.append('%s:%s' % (f.LocalPath(), line_num)) |
| if not errors: |
| return [] |
| |
| plural_suffix = '' if len(errors) == 1 else 's' |
| error_message = ('Found Nullability annotation%(plural)s. ' |
| 'Prefer DCHECKs in ios code to check for nullness:' |
| % {'plural': plural_suffix}) |
| |
| return [output_api.PresubmitPromptWarning(error_message, items=errors)] |
| |
| |
| def _CheckBugInToDo(input_api, output_api): |
| """ Checks whether TODOs in ios code are identified by a bug number.""" |
| errors = [] |
| for f in input_api.AffectedFiles(): |
| for line_num, line in f.ChangedContents(): |
| if _HasToDoWithNoBug(input_api, line): |
| errors.append('%s:%s' % (f.LocalPath(), line_num)) |
| if not errors: |
| return [] |
| |
| plural_suffix = '' if len(errors) == 1 else 's' |
| error_message = '\n'.join([ |
| 'Found TO''DO%(plural)s without bug number%(plural)s (expected format is ' |
| '\"TO''DO(crbug.com/######)\":' % {'plural': plural_suffix} |
| ] + errors) + '\n' |
| |
| return [output_api.PresubmitError(error_message)] |
| |
| |
| def _HasToDoWithNoBug(input_api, line): |
| """ Returns True if TODO is not identified by a bug number.""" |
| todo_regex = input_api.re.compile(TODO_PATTERN) |
| crbug_regex = input_api.re.compile(CRBUG_PATTERN) |
| |
| todo_match = todo_regex.search(line) |
| if not todo_match: |
| return False |
| crbug_match = crbug_regex.match(todo_match.group(1)) |
| return not crbug_match |
| |
| |
| def CheckChangeOnUpload(input_api, output_api): |
| results = [] |
| results.extend(_CheckBugInToDo(input_api, output_api)) |
| results.extend(_CheckNullabilityAnnotations(input_api, output_api)) |
| results.extend(_CheckARCCompilationGuard(input_api, output_api)) |
| return results |