1# Copyright (c) 2014 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 5import os 6import re 7 8from tracing import tracing_project 9 10 11class JSChecker(object): 12 13 def __init__(self, input_api, file_filter=None): 14 self.input_api = input_api 15 if file_filter: 16 self.file_filter = file_filter 17 else: 18 self.file_filter = lambda x: True 19 20 def RegexCheck(self, line_number, line, regex, message): 21 """Searches for |regex| in |line| to check for a particular style 22 violation, returning a message like the one below if the regex matches. 23 The |regex| must have exactly one capturing group so that the relevant 24 part of |line| can be highlighted. If more groups are needed, use 25 "(?:...)" to make a non-capturing group. Sample message: 26 27 line 6: Use var instead of const. 28 const foo = bar(); 29 ^^^^^ 30 """ 31 match = re.search(regex, line) 32 if match: 33 assert len(match.groups()) == 1 34 start = match.start(1) 35 length = match.end(1) - start 36 return ' line %d: %s\n%s\n%s' % ( 37 line_number, 38 message, 39 line, 40 self.error_highlight(start, length)) 41 return '' 42 43 def ConstCheck(self, i, line): 44 """Check for use of the 'const' keyword.""" 45 if re.search(r'\*\s+@const', line): 46 # Probably a JsDoc line 47 return '' 48 49 return self.RegexCheck(i, line, r'(?:^|\s|\()(const)\s', 50 'Use var instead of const.') 51 52 def error_highlight(self, start, length): 53 """Takes a start position and a length, and produces a row of '^'s to 54 highlight the corresponding part of a string. 55 """ 56 return start * ' ' + length * '^' 57 58 def _makeErrorOrWarning(self, error_text, filename): 59 return error_text 60 61 def RunChecks(self): 62 """Check for violations of the Chromium JavaScript style guide. See 63 http://chromium.org/developers/web-development-style-guide#TOC-JavaScript 64 """ 65 66 import sys 67 import warnings 68 old_path = sys.path 69 old_filters = warnings.filters 70 71 try: 72 base_path = os.path.abspath(os.path.join( 73 os.path.dirname(__file__), '..', '..')) 74 closure_linter_path = os.path.join( 75 base_path, 'tracing', 'third_party', 'closure_linter') 76 gflags_path = os.path.join( 77 base_path, 'tracing', 'third_party', 'python_gflags') 78 sys.path.insert(0, closure_linter_path) 79 sys.path.insert(0, gflags_path) 80 81 warnings.filterwarnings('ignore', category=DeprecationWarning) 82 83 from closure_linter import checker, errors 84 from closure_linter.common import errorhandler 85 86 finally: 87 sys.path = old_path 88 warnings.filters = old_filters 89 90 class ErrorHandlerImpl(errorhandler.ErrorHandler): 91 """Filters out errors that don't apply to Chromium JavaScript code.""" 92 93 def __init__(self): 94 self._errors = [] 95 96 def HandleFile(self, filename, first_token): 97 self._filename = filename 98 99 def HandleError(self, error): 100 if (self._valid(error)): 101 error.filename = self._filename 102 self._errors.append(error) 103 104 def GetErrors(self): 105 return self._errors 106 107 def HasErrors(self): 108 return bool(self._errors) 109 110 def _valid(self, error): 111 """Check whether an error is valid. Most errors are valid, with a few 112 exceptions which are listed here. 113 """ 114 115 is_grit_statement = bool( 116 re.search("</?(include|if)", error.token.line)) 117 118 return not is_grit_statement and error.code not in [ 119 errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE, 120 errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER, 121 errors.MISSING_JSDOC_TAG_THIS, 122 ] 123 124 results = [] 125 126 try: 127 affected_files = self.input_api.AffectedFiles( 128 file_filter=self.file_filter, 129 include_deletes=False) 130 except: 131 affected_files = [] 132 133 def ShouldCheck(f): 134 if tracing_project.TracingProject.IsIgnoredFile(f): 135 return False 136 if f.LocalPath().endswith('.js'): 137 return True 138 if f.LocalPath().endswith('.html'): 139 return True 140 return False 141 142 affected_js_files = filter(ShouldCheck, affected_files) 143 for f in affected_js_files: 144 error_lines = [] 145 146 for i, line in enumerate(f.NewContents(), start=1): 147 error_lines += filter(None, [ 148 self.ConstCheck(i, line), 149 ]) 150 151 # Use closure_linter to check for several different errors 152 import gflags as flags 153 flags.FLAGS.strict = True 154 error_handler = ErrorHandlerImpl() 155 js_checker = checker.JavaScriptStyleChecker(error_handler) 156 js_checker.Check(f.AbsoluteLocalPath()) 157 158 for error in error_handler.GetErrors(): 159 highlight = self.error_highlight( 160 error.token.start_index, error.token.length) 161 error_msg = ' line %d: E%04d: %s\n%s\n%s' % ( 162 error.token.line_number, 163 error.code, 164 error.message, 165 error.token.line.rstrip(), 166 highlight) 167 error_lines.append(error_msg) 168 169 if error_lines: 170 error_lines = [ 171 'Found JavaScript style violations in %s:' % 172 f.LocalPath()] + error_lines 173 results.append(self._makeErrorOrWarning( 174 '\n'.join(error_lines), f.LocalPath())) 175 176 return results 177 178 179def RunChecks(input_api): 180 return JSChecker(input_api).RunChecks() 181