193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# Copyright (c) 2014 The Chromium Authors. All rights reserved. 22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Use of this source code is governed by a BSD-style license that can be 32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# found in the LICENSE file. 4b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik 593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport os 693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport re 72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 8b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom tracing import tracing_project 9b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik 10b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik 112da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass JSChecker(object): 12b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik 1393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik def __init__(self, input_api, file_filter=None): 142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.input_api = input_api 1593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik if file_filter: 1693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik self.file_filter = file_filter 1793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik else: 1893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik self.file_filter = lambda x: True 192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def RegexCheck(self, line_number, line, regex, message): 212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Searches for |regex| in |line| to check for a particular style 222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis violation, returning a message like the one below if the regex matches. 232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The |regex| must have exactly one capturing group so that the relevant 242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis part of |line| can be highlighted. If more groups are needed, use 252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis "(?:...)" to make a non-capturing group. Sample message: 262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis line 6: Use var instead of const. 282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis const foo = bar(); 292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ^^^^^ 302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik match = re.search(regex, line) 322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if match: 332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis assert len(match.groups()) == 1 342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis start = match.start(1) 352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis length = match.end(1) - start 362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return ' line %d: %s\n%s\n%s' % ( 372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis line_number, 382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis message, 392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis line, 402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.error_highlight(start, length)) 412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return '' 422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def ConstCheck(self, i, line): 442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Check for use of the 'const' keyword.""" 4593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik if re.search(r'\*\s+@const', line): 462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Probably a JsDoc line 472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return '' 482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return self.RegexCheck(i, line, r'(?:^|\s|\()(const)\s', 50b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik 'Use var instead of const.') 512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def error_highlight(self, start, length): 532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Takes a start position and a length, and produces a row of '^'s to 542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis highlight the corresponding part of a string. 552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return start * ' ' + length * '^' 572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _makeErrorOrWarning(self, error_text, filename): 5993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik return error_text 602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def RunChecks(self): 622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Check for violations of the Chromium JavaScript style guide. See 632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis http://chromium.org/developers/web-development-style-guide#TOC-JavaScript 642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis import sys 672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis import warnings 682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis old_path = sys.path 692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis old_filters = warnings.filters 702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 7293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik base_path = os.path.abspath(os.path.join( 73b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik os.path.dirname(__file__), '..', '..')) 7493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik closure_linter_path = os.path.join( 75b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik base_path, 'tracing', 'third_party', 'closure_linter') 7693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik gflags_path = os.path.join( 77b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik base_path, 'tracing', 'third_party', 'python_gflags') 782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis sys.path.insert(0, closure_linter_path) 792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis sys.path.insert(0, gflags_path) 802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis warnings.filterwarnings('ignore', category=DeprecationWarning) 822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis from closure_linter import checker, errors 842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis from closure_linter.common import errorhandler 852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis finally: 872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis sys.path = old_path 882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis warnings.filters = old_filters 892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis class ErrorHandlerImpl(errorhandler.ErrorHandler): 912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Filters out errors that don't apply to Chromium JavaScript code.""" 922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 9393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik def __init__(self): 942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._errors = [] 952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def HandleFile(self, filename, first_token): 972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._filename = filename 982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def HandleError(self, error): 1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (self._valid(error)): 1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error.filename = self._filename 1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._errors.append(error) 1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def GetErrors(self): 1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return self._errors 1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def HasErrors(self): 1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return bool(self._errors) 1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _valid(self, error): 1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Check whether an error is valid. Most errors are valid, with a few 1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis exceptions which are listed here. 1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_grit_statement = bool( 11693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik re.search("</?(include|if)", error.token.line)) 1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return not is_grit_statement and error.code not in [ 1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE, 1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER, 1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis errors.MISSING_JSDOC_TAG_THIS, 1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ] 1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis results = [] 1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 12666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis try: 12793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik affected_files = self.input_api.AffectedFiles( 12866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis file_filter=self.file_filter, 12966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis include_deletes=False) 13066a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis except: 13166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis affected_files = [] 13266a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis 13393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik def ShouldCheck(f): 134b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik if tracing_project.TracingProject.IsIgnoredFile(f): 13524385dbd2e4e893fe6ed411b3dbec947d956a599Chris Craik return False 136b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik if f.LocalPath().endswith('.js'): 13793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik return True 138b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik if f.LocalPath().endswith('.html'): 13993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik return True 14093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik return False 14193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik 14293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik affected_js_files = filter(ShouldCheck, affected_files) 1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for f in affected_js_files: 1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error_lines = [] 1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 146b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik for i, line in enumerate(f.NewContents(), start=1): 1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error_lines += filter(None, [ 1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.ConstCheck(i, line), 1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ]) 1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Use closure_linter to check for several different errors 15266a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis import gflags as flags 15366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis flags.FLAGS.strict = True 15493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik error_handler = ErrorHandlerImpl() 1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis js_checker = checker.JavaScriptStyleChecker(error_handler) 156b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik js_checker.Check(f.AbsoluteLocalPath()) 1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for error in error_handler.GetErrors(): 1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis highlight = self.error_highlight( 1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error.token.start_index, error.token.length) 1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error_msg = ' line %d: E%04d: %s\n%s\n%s' % ( 1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error.token.line_number, 1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error.code, 1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error.message, 1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error.token.line.rstrip(), 1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis highlight) 1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error_lines.append(error_msg) 1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if error_lines: 1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis error_lines = [ 1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Found JavaScript style violations in %s:' % 172b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik f.LocalPath()] + error_lines 1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis results.append(self._makeErrorOrWarning( 174b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik '\n'.join(error_lines), f.LocalPath())) 1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return results 17793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik 17893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik 17993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikdef RunChecks(input_api): 18093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik return JSChecker(input_api).RunChecks() 181