1# Copyright 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 5""" 6Presubmit for Chromium HTML resources. See chrome/browser/PRESUBMIT.py. 7""" 8 9import regex_check 10 11 12class HtmlChecker(object): 13 def __init__(self, input_api, output_api, file_filter=None): 14 self.input_api = input_api 15 self.output_api = output_api 16 self.file_filter = file_filter 17 18 def ClassesUseDashFormCheck(self, line_number, line): 19 msg = "Classes should use dash-form." 20 re = self.input_api.re 21 class_regex = re.compile(""" 22 (?:^|\s) # start of line or whitespace 23 (class="[^"]*[A-Z_][^"]*") # class contains caps or '_' 24 """, 25 re.VERBOSE) 26 27 # $i18n{...} messes with highlighting. Special path for this. 28 if "$i18n{" in line: 29 match = re.search(class_regex, re.sub("\$i18n{[^}]+}", "", line)) 30 return " line %d: %s" % (line_number, msg) if match else "" 31 32 return regex_check.RegexCheck(re, line_number, line, class_regex, msg) 33 34 def DoNotCloseSingleTagsCheck(self, line_number, line): 35 regex = r"(/>)" 36 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 37 "Do not close single tags.") 38 39 def DoNotUseBrElementCheck(self, line_number, line): 40 regex = r"(<br\b)" 41 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 42 "Do not use <br>; place blocking elements (<div>) as appropriate.") 43 44 def DoNotUseInputTypeButtonCheck(self, line_number, line): 45 regex = self.input_api.re.compile(""" 46 (<input [^>]* # "<input " followed by anything but ">" 47 type="button" # type="button" 48 [^>]*>) # anything but ">" then ">" 49 """, 50 self.input_api.re.VERBOSE) 51 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 52 'Use the button element instead of <input type="button">') 53 54 def DoNotUseSingleQuotesCheck(self, line_number, line): 55 regex = self.input_api.re.compile(""" 56 <\S+ # The tag name. 57 (?:\s+\S+\$?="[^"]*"|\s+\S+)* # Correctly quoted or non-value props. 58 \s+(\S+\$?='[^']*') # Find incorrectly quoted (foo='bar'). 59 [^>]*> # To the end of the tag. 60 """, 61 self.input_api.re.MULTILINE | self.input_api.re.VERBOSE) 62 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 63 'Use double quotes rather than single quotes in HTML properties') 64 65 def I18nContentJavaScriptCaseCheck(self, line_number, line): 66 regex = self.input_api.re.compile(""" 67 (?:^|\s) # start of line or whitespace 68 i18n-content=" # i18n-content=" 69 ([A-Z][^"]*|[^"]*[-_][^"]*)" # starts with caps or contains '-' or '_' 70 """, 71 self.input_api.re.VERBOSE) 72 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 73 "For i18n-content use javaScriptCase.") 74 75 def ImportCorrectPolymerHtml(self, line_number, line): 76 bad_import_url = r"(chrome://resources/polymer/v1_0/polymer/polymer.html)" 77 regex = self.input_api.re.compile(bad_import_url) 78 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 79 "Please import chrome://resources/html/polymer.html instead " + 80 "(to ensure your Polymer config is set up correctly)"); 81 82 def LabelCheck(self, line_number, line): 83 regex = self.input_api.re.compile(""" 84 (?:^|\s) # start of line or whitespace 85 <label[^>]+? # <label tag 86 (for=) # for= 87 """, 88 self.input_api.re.VERBOSE) 89 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 90 "Avoid 'for' attribute on <label>. Place the input within the <label>, " 91 "or use aria-labelledby for <select>.") 92 93 def QuotePolymerBindings(self, line_number, line): 94 regex = self.input_api.re.compile(r"=(\[\[|\{\{)") 95 return regex_check.RegexCheck(self.input_api.re, line_number, line, regex, 96 'Please use quotes around Polymer bindings (i.e. attr="[[prop]]")') 97 98 def RunChecks(self): 99 """Check for violations of the Chromium web development style guide. See 100 https://chromium.googlesource.com/chromium/src/+/master/styleguide/web/web.md 101 """ 102 results = [] 103 104 affected_files = self.input_api.AffectedFiles(file_filter=self.file_filter, 105 include_deletes=False) 106 107 for f in affected_files: 108 if not f.LocalPath().endswith('.html'): 109 continue 110 111 errors = [] 112 113 for line_number, line in f.ChangedContents(): 114 errors.extend(filter(None, [ 115 self.ClassesUseDashFormCheck(line_number, line), 116 self.DoNotCloseSingleTagsCheck(line_number, line), 117 self.DoNotUseBrElementCheck(line_number, line), 118 self.DoNotUseInputTypeButtonCheck(line_number, line), 119 self.I18nContentJavaScriptCaseCheck(line_number, line), 120 self.ImportCorrectPolymerHtml(line_number, line), 121 self.LabelCheck(line_number, line), 122 self.QuotePolymerBindings(line_number, line), 123 ])) 124 125 if errors: 126 abs_local_path = f.AbsoluteLocalPath() 127 file_indicator = 'Found HTML style issues in %s' % abs_local_path 128 prompt_msg = file_indicator + '\n\n' + '\n'.join(errors) + '\n' 129 results.append(self.output_api.PresubmitPromptWarning(prompt_msg)) 130 131 return results 132