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