| # Copyright (C) 2014 Google Inc. All rights reserved. | 
 | # | 
 | # Redistribution and use in source and binary forms, with or without | 
 | # modification, are permitted provided that the following conditions are | 
 | # met: | 
 | # | 
 | #     * Redistributions of source code must retain the above copyright | 
 | # notice, this list of conditions and the following disclaimer. | 
 | #     * Redistributions in binary form must reproduce the above | 
 | # copyright notice, this list of conditions and the following disclaimer | 
 | # in the documentation and/or other materials provided with the | 
 | # distribution. | 
 | #     * Neither the name of Google Inc. nor the names of its | 
 | # contributors may be used to endorse or promote products derived from | 
 | # this software without specific prior written permission. | 
 | # | 
 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 | """ | 
 | DevTools presubmit script | 
 |  | 
 | See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts | 
 | for more details about the presubmit API built into gcl. | 
 | """ | 
 |  | 
 | # pylint: skip-file | 
 |  | 
 | import sys | 
 |  | 
 | EXCLUSIVE_CHANGE_DIRECTORIES = [ | 
 |     [ 'third_party', 'v8' ], | 
 |     [ 'node_modules' ], | 
 |     [ 'OWNERS' ], | 
 | ] | 
 |  | 
 | def _CheckChangesAreExclusiveToDirectory(input_api, output_api): | 
 |     if input_api.change.DISABLE_THIRD_PARTY_CHECK != None: | 
 |         return [] | 
 |  | 
 |     def IsParentDir(file, dir): | 
 |         while file != '': | 
 |             if file == dir: | 
 |                 return True | 
 |             file = input_api.os_path.dirname(file) | 
 |         return False | 
 |  | 
 |     def FileIsInDir(file, dirs): | 
 |         for dir in dirs: | 
 |             if IsParentDir(file, dir): | 
 |                 return True | 
 |  | 
 |     affected_files = input_api.LocalPaths() | 
 |     num_affected = len(affected_files) | 
 |     for dirs in EXCLUSIVE_CHANGE_DIRECTORIES: | 
 |         dir_list = ', '.join(dirs) | 
 |         affected_in_dir = filter(lambda f: FileIsInDir(f, dirs), affected_files) | 
 |         num_in_dir = len(affected_in_dir) | 
 |         if num_in_dir == 0: | 
 |             continue | 
 |         # Addition of new third_party folders must have a new entry in `.gitignore` | 
 |         if '.gitignore' in affected_files: | 
 |             num_in_dir = num_in_dir + 1 | 
 |         if num_in_dir < num_affected: | 
 |             return [ | 
 |                 output_api | 
 |                 .PresubmitError(('CLs that affect files in "%s" should be limited to these files/directories.' % dir_list) + | 
 |                                 ' You can disable this check by adding DISABLE_THIRD_PARTY_CHECK=<reason> to your commit message') | 
 |             ] | 
 |     return [] | 
 |  | 
 |  | 
 | def _CheckBuildGN(input_api, output_api): | 
 |     script_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 'scripts', 'check_gn.js') | 
 |     return _checkWithNodeScript(input_api, output_api, script_path) | 
 |  | 
 |  | 
 | def _CheckFormat(input_api, output_api): | 
 |  | 
 |     def popen(args): | 
 |         return input_api.subprocess.Popen(args=args, stdout=input_api.subprocess.PIPE, stderr=input_api.subprocess.STDOUT) | 
 |  | 
 |     affected_files = _getAffectedJSFiles(input_api) | 
 |     if len(affected_files) == 0: | 
 |         return [] | 
 |     original_sys_path = sys.path | 
 |     try: | 
 |         sys.path = sys.path + [input_api.os_path.join(input_api.PresubmitLocalPath(), 'scripts')] | 
 |         import devtools_paths | 
 |     finally: | 
 |         sys.path = original_sys_path | 
 |  | 
 |     ignore_files = [] | 
 |     eslint_ignore_path = input_api.os_path.join(input_api.PresubmitLocalPath(), '.eslintignore') | 
 |     with open(eslint_ignore_path, 'r') as ignore_manifest: | 
 |         for line in ignore_manifest: | 
 |             ignore_files.append(input_api.os_path.normpath(line.strip())) | 
 |     formattable_files = [ | 
 |         affected_file for affected_file in affected_files if all(ignore_file not in affected_file for ignore_file in ignore_files) | 
 |     ] | 
 |     if len(formattable_files) == 0: | 
 |         return [] | 
 |  | 
 |     check_formatting_process = popen(['git', 'cl', 'format', '--js', '--dry-run'] + formattable_files) | 
 |     check_formatting_process.communicate() | 
 |     if check_formatting_process.returncode == 0: | 
 |         return [] | 
 |  | 
 |     format_args = ['git', 'cl', 'format', '--js'] + formattable_files | 
 |     format_process = popen(format_args) | 
 |     format_out, _ = format_process.communicate() | 
 |     if format_process.returncode != 0: | 
 |         return [output_api.PresubmitError(format_out)] | 
 |  | 
 |     # Use eslint to autofix the braces. | 
 |     # Also fix semicolon to avoid confusing clang-format. | 
 |     eslint_process = popen( | 
 |         [devtools_paths.node_path(), devtools_paths.eslint_path(), '--config', '.eslintrc.js', '--fix'] + affected_files) | 
 |     eslint_process.communicate() | 
 |  | 
 |     # Need to run clang-format again to align the braces | 
 |     popen(format_args).communicate() | 
 |  | 
 |     return [ | 
 |         output_api.PresubmitError('ERROR: Found formatting violations.\n' | 
 |                                   'Ran clang-format on diff\n' | 
 |                                   'Use git status to check the formatting changes'), | 
 |         output_api.PresubmitError(format_out), | 
 |     ] | 
 |  | 
 |  | 
 | def _CheckDevtoolsLocalization(input_api, output_api, check_all_files=False):  # pylint: disable=invalid-name | 
 |     devtools_root = input_api.PresubmitLocalPath() | 
 |     script_path = input_api.os_path.join(devtools_root, 'scripts', 'test', 'run_localization_check.py') | 
 |     if check_all_files == True: | 
 |         # Scan all files and fix any errors | 
 |         args = ['--autofix', '--all'] | 
 |     else: | 
 |         devtools_front_end = input_api.os_path.join(devtools_root, 'front_end') | 
 |         affected_front_end_files = _getAffectedFiles(input_api, [devtools_front_end], ['D'], | 
 |                                                      ['.js', '.grdp', '.grd', 'module.json']) | 
 |  | 
 |         if len(affected_front_end_files) == 0: | 
 |             return [] | 
 |         # Scan only added or modified files with specific extensions. | 
 |         args = [ | 
 |             '--autofix', | 
 |             '--files', | 
 |         ] + affected_front_end_files | 
 |     process = input_api.subprocess.Popen( | 
 |         [input_api.python_executable, script_path] + args, stdout=input_api.subprocess.PIPE, stderr=input_api.subprocess.STDOUT) | 
 |     out, _ = process.communicate() | 
 |     if process.returncode != 0: | 
 |         return [output_api.PresubmitError(out)] | 
 |     return [output_api.PresubmitNotifyResult(out)] | 
 |  | 
 |  | 
 | def _CheckDevtoolsStyle(input_api, output_api): | 
 |     lint_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 'scripts', 'test', 'run_lint_check.py') | 
 |     process = input_api.subprocess.Popen([input_api.python_executable, lint_path], | 
 |                                          stdout=input_api.subprocess.PIPE, | 
 |                                          stderr=input_api.subprocess.STDOUT) | 
 |     out, _ = process.communicate() | 
 |     if process.returncode != 0: | 
 |         return [output_api.PresubmitError(out)] | 
 |     return [output_api.PresubmitNotifyResult(out)] | 
 |  | 
 |  | 
 | def _CheckOptimizeSVGHashes(input_api, output_api): | 
 |     if not input_api.platform.startswith('linux'): | 
 |         return [] | 
 |  | 
 |     original_sys_path = sys.path | 
 |     try: | 
 |         sys.path = sys.path + [input_api.os_path.join(input_api.PresubmitLocalPath(), 'scripts', 'build')] | 
 |         import devtools_file_hashes | 
 |     finally: | 
 |         sys.path = original_sys_path | 
 |  | 
 |     absolute_local_paths = [af.AbsoluteLocalPath() for af in input_api.AffectedFiles(include_deletes=False)] | 
 |     images_src_path = input_api.os_path.join('devtools', 'front_end', 'Images', 'src') | 
 |     image_source_file_paths = [path for path in absolute_local_paths if images_src_path in path and path.endswith('.svg')] | 
 |     image_sources_path = input_api.os_path.join(input_api.PresubmitLocalPath(), 'front_end', 'Images', 'src') | 
 |     hashes_file_name = 'optimize_svg.hashes' | 
 |     hashes_file_path = input_api.os_path.join(image_sources_path, hashes_file_name) | 
 |     invalid_hash_file_paths = devtools_file_hashes.files_with_invalid_hashes(hashes_file_path, image_source_file_paths) | 
 |     if len(invalid_hash_file_paths) == 0: | 
 |         return [] | 
 |     invalid_hash_file_names = [input_api.os_path.basename(file_path) for file_path in invalid_hash_file_paths] | 
 |     file_paths_str = ', '.join(invalid_hash_file_names) | 
 |     error_message = 'The following SVG files should be optimized using optimize_svg_images script before uploading: \n  - %s' % file_paths_str | 
 |     return [output_api.PresubmitError(error_message)] | 
 |  | 
 |  | 
 | def _CheckCSSViolations(input_api, output_api): | 
 |     results = [] | 
 |     for f in input_api.AffectedFiles(include_deletes=False): | 
 |         if not f.LocalPath().endswith('.css'): | 
 |             continue | 
 |         for line_number, line in f.ChangedContents(): | 
 |             if '/deep/' in line: | 
 |                 results.append(output_api.PresubmitError(('%s:%d uses /deep/ selector') % (f.LocalPath(), line_number))) | 
 |             if '::shadow' in line: | 
 |                 results.append(output_api.PresubmitError(('%s:%d uses ::shadow selector') % (f.LocalPath(), line_number))) | 
 |     return results | 
 |  | 
 |  | 
 | def _CommonChecks(input_api, output_api): | 
 |     """Checks common to both upload and commit.""" | 
 |     results = [] | 
 |     # Cobalt changes don't need DevTools owners to be involved | 
 |     # results.extend(input_api.canned_checks.CheckOwnersFormat(input_api, | 
 |     #                                                          output_api)) | 
 |     # results.extend(input_api.canned_checks.CheckOwners(input_api, output_api)) | 
 |     results.extend(input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api)) | 
 |     results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(input_api, output_api)) | 
 |     # Cobalt's depot_tools doesn't include CheckGenderNeutral | 
 |     # results.extend(input_api.canned_checks.CheckGenderNeutral(input_api, | 
 |     #                                                           output_api)) | 
 |     return results | 
 |  | 
 |  | 
 | def CheckChangeOnUpload(input_api, output_api): | 
 |     results = [] | 
 |     results.extend(_CommonChecks(input_api, output_api)) | 
 |  | 
 |     # Cobalt doesn't use GN to build. | 
 |     # results.extend(_CheckBuildGN(input_api, output_api)) | 
 |  | 
 |     # Formatting & style checks use eslint from a Node install, which isn't | 
 |     # part of the Cobalt dev environment. | 
 |     # results.extend(_CheckFormat(input_api, output_api)) | 
 |     # results.extend(_CheckDevtoolsStyle(input_api, output_api)) | 
 |  | 
 |     results.extend(_CheckOptimizeSVGHashes(input_api, output_api)) | 
 |     results.extend(_CheckCSSViolations(input_api, output_api)) | 
 |     #results.extend(_CheckDevtoolsLocalization(input_api, output_api)) | 
 |     results.extend(_CheckChangesAreExclusiveToDirectory(input_api, output_api)) | 
 |     return results | 
 |  | 
 |  | 
 | def CheckChangeOnCommit(input_api, output_api): | 
 |     results = [] | 
 |     results.extend(_CommonChecks(input_api, output_api)) | 
 |     #results.extend(_CheckDevtoolsLocalization(input_api, output_api, True)) | 
 |     results.extend(_CheckChangesAreExclusiveToDirectory(input_api, output_api)) | 
 |     results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api)) | 
 |     return results | 
 |  | 
 |  | 
 | def _getAffectedFiles(input_api, parent_directories, excluded_actions, accepted_endings):  # pylint: disable=invalid-name | 
 |     """Return absolute file paths of affected files (not due to an excluded action) | 
 |        under a parent directory with an accepted file ending. | 
 |     """ | 
 |     local_paths = [ | 
 |         f.AbsoluteLocalPath() for f in input_api.AffectedFiles() if all(f.Action() != action for action in excluded_actions) | 
 |     ] | 
 |     affected_files = [ | 
 |         file_name for file_name in local_paths | 
 |         if any(parent_directory in file_name for parent_directory in parent_directories) and any( | 
 |             file_name.endswith(accepted_ending) for accepted_ending in accepted_endings) | 
 |     ] | 
 |     return affected_files | 
 |  | 
 |  | 
 | def _getAffectedFrontEndFiles(input_api): | 
 |     devtools_root = input_api.PresubmitLocalPath() | 
 |     devtools_front_end = input_api.os_path.join(devtools_root, 'front_end') | 
 |     affected_front_end_files = _getAffectedFiles(input_api, [devtools_front_end], ['D'], ['.js']) | 
 |     return [input_api.os_path.relpath(file_name, devtools_root) for file_name in affected_front_end_files] | 
 |  | 
 |  | 
 | def _getAffectedJSFiles(input_api): | 
 |     devtools_root = input_api.PresubmitLocalPath() | 
 |     devtools_front_end = input_api.os_path.join(devtools_root, 'front_end') | 
 |     devtools_scripts = input_api.os_path.join(devtools_root, 'scripts') | 
 |     affected_js_files = _getAffectedFiles(input_api, [devtools_front_end, devtools_scripts], ['D'], ['.js']) | 
 |     return [input_api.os_path.relpath(file_name, devtools_root) for file_name in affected_js_files] | 
 |  | 
 |  | 
 | def _checkWithNodeScript(input_api, output_api, script_path, script_arguments=None):  # pylint: disable=invalid-name | 
 |     original_sys_path = sys.path | 
 |     try: | 
 |         sys.path = sys.path + [input_api.os_path.join(input_api.PresubmitLocalPath(), 'scripts')] | 
 |         import devtools_paths | 
 |     finally: | 
 |         sys.path = original_sys_path | 
 |  | 
 |     node_path = devtools_paths.node_path() | 
 |  | 
 |     if script_arguments is None: | 
 |         script_arguments = [] | 
 |  | 
 |     process = input_api.subprocess.Popen( | 
 |         [node_path, script_path] + script_arguments, stdout=input_api.subprocess.PIPE, stderr=input_api.subprocess.STDOUT) | 
 |     out, _ = process.communicate() | 
 |  | 
 |     if process.returncode != 0: | 
 |         return [output_api.PresubmitError(out)] | 
 |     return [output_api.PresubmitNotifyResult(out)] |