1# Copyright (c) 2012 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 5"""Presubmit script for Chromium JS resources. 6 7See chrome/browser/PRESUBMIT.py 8""" 9 10class JSChecker(object): 11 def __init__(self, input_api, output_api, file_filter=None): 12 self.input_api = input_api 13 self.output_api = output_api 14 self.file_filter = file_filter 15 16 def RegexCheck(self, line_number, line, regex, message): 17 """Searches for |regex| in |line| to check for a particular style 18 violation, returning a message like the one below if the regex matches. 19 The |regex| must have exactly one capturing group so that the relevant 20 part of |line| can be highlighted. If more groups are needed, use 21 "(?:...)" to make a non-capturing group. Sample message: 22 23 line 6: Use var instead of const. 24 const foo = bar(); 25 ^^^^^ 26 """ 27 match = self.input_api.re.search(regex, line) 28 if match: 29 assert len(match.groups()) == 1 30 start = match.start(1) 31 length = match.end(1) - start 32 return ' line %d: %s\n%s\n%s' % ( 33 line_number, 34 message, 35 line, 36 self.error_highlight(start, length)) 37 return '' 38 39 def ChromeSendCheck(self, i, line): 40 """Checks for a particular misuse of 'chrome.send'.""" 41 return self.RegexCheck(i, line, r"chrome\.send\('[^']+'\s*(, \[\])\)", 42 'Passing an empty array to chrome.send is unnecessary') 43 44 def ConstCheck(self, i, line): 45 """Check for use of the 'const' keyword.""" 46 if self.input_api.re.search(r'\*\s+@const', line): 47 # Probably a JsDoc line 48 return '' 49 50 return self.RegexCheck(i, line, r'(?:^|\s|\()(const)\s', 51 'Use /** @const */ var varName; instead of const varName;') 52 53 def EndJsDocCommentCheck(self, i, line): 54 msg = 'End JSDoc comments with */ instead of **/' 55 def _check(regex): 56 return self.RegexCheck(i, line, regex, msg) 57 return _check(r'^\s*(\*\*/)\s*$') or _check(r'/\*\* @[a-zA-Z]+.* (\*\*/)') 58 59 def GetElementByIdCheck(self, i, line): 60 """Checks for use of 'document.getElementById' instead of '$'.""" 61 return self.RegexCheck(i, line, r"(document\.getElementById)\('", 62 "Use $('id'), from chrome://resources/js/util.js, instead of " 63 "document.getElementById('id')") 64 65 def InheritDocCheck(self, i, line): 66 """Checks for use of '@inheritDoc' instead of '@override'.""" 67 return self.RegexCheck(i, line, r"\* (@inheritDoc)", 68 "@inheritDoc is deprecated, use @override instead") 69 70 def WrapperTypeCheck(self, i, line): 71 """Check for wrappers (new String()) instead of builtins (string).""" 72 return self.RegexCheck(i, line, 73 r"(?:/\*)?\*.*?@(?:param|return|type) ?" # /** @param/@return/@type 74 r"{[^}]*\b(String|Boolean|Number)\b[^}]*}", # {(Boolean|Number|String)} 75 "Don't use wrapper types (i.e. new String() or @type {String})") 76 77 def VarNameCheck(self, i, line): 78 """See the style guide. http://goo.gl/uKir6""" 79 return self.RegexCheck(i, line, 80 r"var (?!g_\w+)([a-z]*[_$][\w_$]*)(?<! \$)", 81 "Please use var namesLikeThis <http://goo.gl/uKir6>") 82 83 def error_highlight(self, start, length): 84 """Takes a start position and a length, and produces a row of '^'s to 85 highlight the corresponding part of a string. 86 """ 87 return start * ' ' + length * '^' 88 89 def _makeErrorOrWarning(self, error_text, filename): 90 """Takes a few lines of text indicating a style violation and turns it into 91 a PresubmitError (if |filename| is in a directory where we've already 92 taken out all the style guide violations) or a PresubmitPromptWarning 93 (if it's in a directory where we haven't done that yet). 94 """ 95 # TODO(tbreisacher): Once we've cleaned up the style nits in all of 96 # resources/ we can get rid of this function. 97 path = self.input_api.os_path 98 resources = path.join(self.input_api.PresubmitLocalPath(), 'resources') 99 dirs = ( 100 path.join(resources, 'bookmark_manager'), 101 path.join(resources, 'extensions'), 102 path.join(resources, 'file_manager'), 103 path.join(resources, 'help'), 104 path.join(resources, 'history'), 105 path.join(resources, 'memory_internals'), 106 path.join(resources, 'net_export'), 107 path.join(resources, 'net_internals'), 108 path.join(resources, 'network_action_predictor'), 109 path.join(resources, 'ntp4'), 110 path.join(resources, 'options'), 111 path.join(resources, 'password_manager_internals'), 112 path.join(resources, 'print_preview'), 113 path.join(resources, 'profiler'), 114 path.join(resources, 'sync_promo'), 115 path.join(resources, 'tracing'), 116 path.join(resources, 'uber'), 117 ) 118 if filename.startswith(dirs): 119 return self.output_api.PresubmitError(error_text) 120 else: 121 return self.output_api.PresubmitPromptWarning(error_text) 122 123 def RunChecks(self): 124 """Check for violations of the Chromium JavaScript style guide. See 125 http://chromium.org/developers/web-development-style-guide#TOC-JavaScript 126 """ 127 128 import sys 129 import warnings 130 old_path = sys.path 131 old_filters = warnings.filters 132 133 try: 134 closure_linter_path = self.input_api.os_path.join( 135 self.input_api.change.RepositoryRoot(), 136 "third_party", 137 "closure_linter") 138 gflags_path = self.input_api.os_path.join( 139 self.input_api.change.RepositoryRoot(), 140 "third_party", 141 "python_gflags") 142 143 sys.path.insert(0, closure_linter_path) 144 sys.path.insert(0, gflags_path) 145 146 warnings.filterwarnings('ignore', category=DeprecationWarning) 147 148 from closure_linter import checker, errors 149 from closure_linter.common import errorhandler 150 151 finally: 152 sys.path = old_path 153 warnings.filters = old_filters 154 155 class ErrorHandlerImpl(errorhandler.ErrorHandler): 156 """Filters out errors that don't apply to Chromium JavaScript code.""" 157 158 def __init__(self, re): 159 self._errors = [] 160 self.re = re 161 162 def HandleFile(self, filename, first_token): 163 self._filename = filename 164 165 def HandleError(self, error): 166 if (self._valid(error)): 167 error.filename = self._filename 168 self._errors.append(error) 169 170 def GetErrors(self): 171 return self._errors 172 173 def HasErrors(self): 174 return bool(self._errors) 175 176 def _valid(self, error): 177 """Check whether an error is valid. Most errors are valid, with a few 178 exceptions which are listed here. 179 """ 180 181 is_grit_statement = bool( 182 self.re.search("</?(include|if)", error.token.line)) 183 184 # Ignore missing spaces before "(" until Promise#catch issue is solved. 185 # http://crbug.com/338301 186 if (error.code == errors.MISSING_SPACE and error.token.string == '(' and 187 'catch(' in error.token.line): 188 return False 189 190 return not is_grit_statement and error.code not in [ 191 errors.COMMA_AT_END_OF_LITERAL, 192 errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE, 193 errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER, 194 errors.LINE_TOO_LONG, 195 errors.MISSING_JSDOC_TAG_THIS, 196 ] 197 198 results = [] 199 200 affected_files = self.input_api.change.AffectedFiles( 201 file_filter=self.file_filter, 202 include_deletes=False) 203 affected_js_files = filter(lambda f: f.LocalPath().endswith('.js'), 204 affected_files) 205 for f in affected_js_files: 206 error_lines = [] 207 208 # Check for the following: 209 # * document.getElementById() 210 # * the 'const' keyword 211 # * Passing an empty array to 'chrome.send()' 212 for i, line in enumerate(f.NewContents(), start=1): 213 error_lines += filter(None, [ 214 self.ChromeSendCheck(i, line), 215 self.ConstCheck(i, line), 216 self.GetElementByIdCheck(i, line), 217 self.InheritDocCheck(i, line), 218 self.WrapperTypeCheck(i, line), 219 self.VarNameCheck(i, line), 220 ]) 221 222 # Use closure_linter to check for several different errors 223 error_handler = ErrorHandlerImpl(self.input_api.re) 224 js_checker = checker.JavaScriptStyleChecker(error_handler) 225 js_checker.Check(self.input_api.os_path.join( 226 self.input_api.change.RepositoryRoot(), 227 f.LocalPath())) 228 229 for error in error_handler.GetErrors(): 230 highlight = self.error_highlight( 231 error.token.start_index, error.token.length) 232 error_msg = ' line %d: E%04d: %s\n%s\n%s' % ( 233 error.token.line_number, 234 error.code, 235 error.message, 236 error.token.line.rstrip(), 237 highlight) 238 error_lines.append(error_msg) 239 240 if error_lines: 241 error_lines = [ 242 'Found JavaScript style violations in %s:' % 243 f.LocalPath()] + error_lines 244 results.append(self._makeErrorOrWarning( 245 '\n'.join(error_lines), f.AbsoluteLocalPath())) 246 247 if results: 248 results.append(self.output_api.PresubmitNotifyResult( 249 'See the JavaScript style guide at ' 250 'http://www.chromium.org/developers/web-development-style-guide' 251 '#TOC-JavaScript and if you have any feedback about the JavaScript ' 252 'PRESUBMIT check, contact tbreisacher@chromium.org or ' 253 'dbeam@chromium.org')) 254 255 return results 256