Bug 1279812 0 Update wpt lint for mozlint compatibility, r=Ms2ger draft
authorJames Graham <james@hoppipolla.co.uk>
Sat, 11 Jun 2016 09:26:50 +0100
changeset 377831 efa1e3b0ea59fe1736e99b9d5432b509b977ace3
parent 377830 ccf6b0b4af50f5577e3ffa64873e47d94c87ac1b
child 377832 08d030bd2d9de1500ab2fa0d22fc5d023713168b
push id20863
push userbmo:james@hoppipolla.co.uk
push dateSun, 12 Jun 2016 19:03:01 +0000
reviewersMs2ger
bugs1279812
milestone50.0a1
Bug 1279812 0 Update wpt lint for mozlint compatibility, r=Ms2ger MozReview-Commit-ID: 6UJj8FSs5Ot
testing/web-platform/tests/tools/lint/lint.py
--- a/testing/web-platform/tests/tools/lint/lint.py
+++ b/testing/web-platform/tests/tools/lint/lint.py
@@ -1,12 +1,13 @@
 from __future__ import print_function, unicode_literals
 
 import argparse
 import fnmatch
+import json
 import os
 import re
 import subprocess
 import sys
 
 from collections import defaultdict
 
 from .. import localpaths
@@ -73,17 +74,17 @@ def filter_whitelist_errors(data, path, 
     """
     Filter out those errors that are whitelisted in `data`.
     """
 
     whitelisted = [False for item in range(len(errors))]
 
     for file_match, whitelist_errors in iteritems(data):
         if fnmatch.fnmatch(path, file_match):
-            for i, (error_type, msg, line) in enumerate(errors):
+            for i, (error_type, msg, path, line) in enumerate(errors):
                 if "*" in whitelist_errors:
                     whitelisted[i] = True
                 elif error_type in whitelist_errors:
                     allowed_lines = whitelist_errors[error_type]
                     if None in allowed_lines or line in allowed_lines:
                         whitelisted[i] = True
 
     return [item for i, item in enumerate(errors) if not whitelisted[i]]
@@ -102,42 +103,49 @@ class Regexp(object):
                 os.path.splitext(path)[1] in self.file_extensions)
 
     def search(self, line):
         return self._re.search(line)
 
 class TrailingWhitespaceRegexp(Regexp):
     pattern = b"[ \t\f\v]$"
     error = "TRAILING WHITESPACE"
+    description = "Whitespace at EOL"
 
 class TabsRegexp(Regexp):
     pattern = b"^\t"
     error = "INDENT TABS"
+    description = "Tabs used for indentation"
 
 class CRRegexp(Regexp):
     pattern = b"\r$"
     error = "CR AT EOL"
+    description = "CR character in line separator"
 
 class W3CTestOrgRegexp(Regexp):
     pattern = b"w3c\-test\.org"
     error = "W3C-TEST.ORG"
+    description = "External w3c-test.org domain used"
 
 class Webidl2Regexp(Regexp):
     pattern = b"webidl2\.js"
     error = "WEBIDL2.JS"
+    description = "Legacy webidl2.js script used"
 
 class ConsoleRegexp(Regexp):
     pattern = b"console\.[a-zA-Z]+\s*\("
     error = "CONSOLE"
     file_extensions = [".html", ".htm", ".js", ".xht", ".html", ".svg"]
+    description = "Console logging API used"
 
 class PrintRegexp(Regexp):
     pattern = b"print(?:\s|\s*\()"
     error = "PRINT STATEMENT"
     file_extensions = [".py"]
+    description = "Print function used"
 
 regexps = [item() for item in
            [TrailingWhitespaceRegexp,
             TabsRegexp,
             CRRegexp,
             W3CTestOrgRegexp,
             Webidl2Regexp,
             ConsoleRegexp,
@@ -146,60 +154,60 @@ regexps = [item() for item in
 def check_regexp_line(repo_root, path, f):
     errors = []
 
     applicable_regexps = [regexp for regexp in regexps if regexp.applies(path)]
 
     for i, line in enumerate(f):
         for regexp in applicable_regexps:
             if regexp.search(line):
-                errors.append((regexp.error, "%s line %i" % (path, i+1), i+1))
+                errors.append((regexp.error, regexp.description, path, i+1))
 
     return errors
 
 def check_parsed(repo_root, path, f):
     source_file = SourceFile(repo_root, path, "/")
 
     errors = []
 
     if source_file.name_is_non_test or source_file.name_is_manual:
         return []
 
     if source_file.markup_type is None:
         return []
 
     if source_file.root is None:
-        return [("PARSE-FAILED", "Unable to parse file %s" % path, None)]
+        return [("PARSE-FAILED", "Unable to parse file", path, None)]
 
     if len(source_file.timeout_nodes) > 1:
-        errors.append(("MULTIPLE-TIMEOUT", "%s more than one meta name='timeout'" % path, None))
+        errors.append(("MULTIPLE-TIMEOUT", "More than one meta name='timeout'", path, None))
 
     for timeout_node in source_file.timeout_nodes:
         timeout_value = timeout_node.attrib.get("content", "").lower()
         if timeout_value != "long":
-            errors.append(("INVALID-TIMEOUT", "%s invalid timeout value %s" % (path, timeout_value), None))
+            errors.append(("INVALID-TIMEOUT", "Invalid timeout value %s" % timeout_value, path, None))
 
     if source_file.testharness_nodes:
         if len(source_file.testharness_nodes) > 1:
             errors.append(("MULTIPLE-TESTHARNESS",
-                           "%s more than one <script src='/resources/testharness.js'>" % path, None))
+                           "More than one <script src='/resources/testharness.js'>", path, None))
 
         testharnessreport_nodes = source_file.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src='/resources/testharnessreport.js']")
         if not testharnessreport_nodes:
             errors.append(("MISSING-TESTHARNESSREPORT",
-                           "%s missing <script src='/resources/testharnessreport.js'>" % path, None))
+                           "Missing <script src='/resources/testharnessreport.js'>", path, None))
         else:
             if len(testharnessreport_nodes) > 1:
                 errors.append(("MULTIPLE-TESTHARNESSREPORT",
-                               "%s more than one <script src='/resources/testharnessreport.js'>" % path, None))
+                               "More than one <script src='/resources/testharnessreport.js'>", path, None))
 
         for element in source_file.variant_nodes:
             if "content" not in element.attrib:
                 errors.append(("VARIANT-MISSING",
-                               "%s has <meta name=variant> missing 'content' attribute" % path, None))
+                               "<meta name=variant> missing 'content' attribute", path, None))
             else:
                 variant = element.attrib["content"]
                 if variant != "" and variant[0] not in ("?", "#"):
                     errors.append(("MALFORMED-VARIANT",
                                "%s <meta name=variant> 'content' attribute must be the empty string or start with '?' or '#'" % path, None))
 
         seen_elements = {"timeout": False,
                          "testharness": False,
@@ -209,91 +217,107 @@ def check_parsed(repo_root, path, f):
                                                     "timeout": len(source_file.timeout_nodes) > 0}.items()
                              if value]
 
         for elem in source_file.root.iter():
             if source_file.timeout_nodes and elem == source_file.timeout_nodes[0]:
                 seen_elements["timeout"] = True
                 if seen_elements["testharness"]:
                     errors.append(("LATE-TIMEOUT",
-                                   "%s <meta name=timeout> seen after testharness.js script" % path, None))
+                                   "<meta name=timeout> seen after testharness.js script", path, None))
 
             elif elem == source_file.testharness_nodes[0]:
                 seen_elements["testharness"] = True
 
             elif testharnessreport_nodes and elem == testharnessreport_nodes[0]:
                 seen_elements["testharnessreport"] = True
                 if not seen_elements["testharness"]:
                     errors.append(("EARLY-TESTHARNESSREPORT",
-                                   "%s testharnessreport.js script seen before testharness.js script" % path, None))
+                                   "testharnessreport.js script seen before testharness.js script", path, None))
 
             if all(seen_elements[name] for name in required_elements):
                 break
 
     return errors
 
-def output_errors(errors):
-    for error_type, error, line_number in errors:
-        print("%s: %s" % (error_type, error))
+def output_errors_text(errors):
+    for error_type, description, path, line_number in errors:
+        pos_string = path
+        if line_number:
+            pos_string += " %s" % line_number
+        print("%s: %s %s" % (error_type, pos_string, description))
+
+def output_errors_json(errors):
+    for error_type, error, path, line_number in errors:
+        print(json.dumps({"path": path, "lineno": line_number,
+                          "rule": error_type, "message": error}))
 
 def output_error_count(error_count):
     if not error_count:
         return
 
     by_type = " ".join("%s: %d" % item for item in error_count.items())
     count = sum(error_count.values())
     if count == 1:
         print("There was 1 error (%s)" % (by_type,))
     else:
         print("There were %d errors (%s)" % (count, by_type))
 
 def parse_args():
     parser = argparse.ArgumentParser()
     parser.add_argument("paths", nargs="*",
                         help="List of paths to lint")
+    parser.add_argument("--json", action="store_true",
+                        help="Output machine-readable JSON format")
     return parser.parse_args()
 
 def main():
     repo_root = localpaths.repo_root
     args = parse_args()
     paths = args.paths if args.paths else all_git_paths(repo_root)
-    return lint(repo_root, paths)
+    return lint(repo_root, paths, args.json)
 
-def lint(repo_root, paths):
+def lint(repo_root, paths, output_json):
     error_count = defaultdict(int)
     last = None
 
     whitelist = parse_whitelist_file(os.path.join(repo_root, "lint.whitelist"))
 
+    if output_json:
+        output_errors = output_errors_json
+    else:
+        output_errors = output_errors_text
+
     def run_lint(path, fn, last, *args):
         errors = filter_whitelist_errors(whitelist, path, fn(repo_root, path, *args))
         if errors:
             last = (errors[-1][0], path)
 
         output_errors(errors)
-        for error_type, error, line in errors:
+        for error_type, error, path, line in errors:
             error_count[error_type] += 1
         return last
 
     for path in paths:
         abs_path = os.path.join(repo_root, path)
         if not os.path.exists(abs_path):
             continue
         for path_fn in path_lints:
             last = run_lint(path, path_fn, last)
 
         if not os.path.isdir(abs_path):
             with open(abs_path) as f:
                 for file_fn in file_lints:
                     last = run_lint(path, file_fn, last, f)
                     f.seek(0)
 
-    output_error_count(error_count)
-    if error_count:
-        print(ERROR_MSG % (last[0], last[1], last[0], last[1]))
+    if not output_json:
+        output_error_count(error_count)
+        if error_count:
+            print(ERROR_MSG % (last[0], last[1], last[0], last[1]))
     return sum(error_count.itervalues())
 
 path_lints = [check_path_length]
 file_lints = [check_regexp_line, check_parsed]
 
 if __name__ == "__main__":
     error_count = main()
     if error_count > 0: