blob: 82763d0654664df1eb57e91f16e63bf2ac4fd7f5 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Run eslint with our settings."""
import json
import os
import sys
import libdot
def convert_to_kokoro(data):
"""Take eslint JSON output and convert it to kokoro comment format.
The |data| input will look like:
[
{
"errorCount": <total number of errors>,
"filePath": ".../libapps/libdot/js/lib_f.js",
"fixableErrorCount": 1,
"fixableWarningCount": 0,
"messages": [ // This contains errors & warnings.
{
"column": 2,
"endColumn": 57,
"endLine": 1,
"line": 1,
"message": "Missing semicolon.",
"nodeType": "ExpressionStatement",
"ruleId": "semi",
"severity": <1=warning 2=error>
}
],
"source": "...the entire source file...",
"warningCount": <total number of warnings>
},
{
"errorCount": <total number of errors>,
"filePath": ".../libapps/libdot/js/lib_fs.js",
"fixableErrorCount": 2,
"fixableWarningCount": 0,
"messages": [ // This contains errors & warnings.
{
"column": 2,
"fix": {
"range": [8180, 8180],
"text": ";"
},
"line": 262,
"message": "Missing semicolon.",
"nodeType": "ExpressionStatement",
"ruleId": "semi",
"severity": <1=warning 2=error>
},
{
"column": 10,
"fix": {
"range": [8436, 8436],
"text": ";"
},
"line": 271,
"message": "Missing semicolon.",
"nodeType": "VariableDeclaration",
"ruleId": "semi",
"severity": <1=warning 2=error>
}
],
"source": "...the entire source file...",
"warningCount": <total number of warnings>
}
]
We want to return a list of results like:
[
{
path: "relative/path/to/source/file",
message: "Content of the inline comment."
startLine: <integer line number>,
startCharacter: <integer column number>,
endLine: <integer line number>,
endCharacter: <integer column number>
},
{ ... more results above ... }
]
"""
RULES_URI_BASE = "https://eslint.org/docs/rules/"
SEVERITY_TO_HEADER = {1: "Warning", 2: "Error"}
for file_results in data:
# Ignore generated files not in git.
if libdot.lint.is_generated_path(file_results["filePath"]):
continue
for message in file_results["messages"]:
msg = (
"[eslint] "
+ SEVERITY_TO_HEADER[message["severity"]]
+ ": "
+ message["message"]
)
# For syntax errors, this will be omitted.
ruleId = message.get("ruleId")
if ruleId:
msg += "\n" + RULES_URI_BASE + ruleId
# Gerrit comments appear to count columns from 0 while eslint counts
# from 1. Adjust them down by one to handle.
yield {
"path": os.path.relpath(
file_results["filePath"], libdot.LIBAPPS_DIR
),
"message": msg,
"startLine": message["line"],
"endLine": message.get("endLine", message["line"]),
"startCharacter": message["column"] - 1,
"endCharacter": message.get("endColumn", message["column"]),
}
def setup():
"""Initialize the tool settings."""
libdot.node_and_npm_setup()
def run(argv=(), **kwargs):
"""Run the tool directly."""
setup()
return libdot.node.run(["eslint"] + list(argv), **kwargs)
def perform(argv=(), paths=(), fix=False, gerrit_comments_file=None):
"""Run high level tool logic."""
ret = True
argv = list(argv)
paths = list(paths)
comments_path = libdot.lint.kokoro_comments_path(
gerrit_comments_file, "eslint"
)
if comments_path:
argv += ["--max-warnings=0"]
if fix:
argv += ["--fix"]
result = run(argv + paths, check=False)
if result.returncode:
ret = False
# Rerun for Gerrit.
if comments_path:
# Handle relative paths like "foo.json".
dirname = os.path.dirname(comments_path)
if dirname:
os.makedirs(dirname, exist_ok=True)
argv += ["--format=json"]
result = run(argv + paths, check=False, capture_output=True)
# Save a copy for debugging later.
with open(comments_path + ".in", "wb") as fp:
fp.write(result.stdout)
data = json.loads(result.stdout.decode("utf-8"))
comments = list(convert_to_kokoro(data))
with open(comments_path, "w", encoding="utf-8") as fp:
json.dump(comments, fp, sort_keys=True)
elif comments_path:
# If there were no failures, clear the files to avoid leaving previous
# results laying around & confuse devs.
libdot.unlink(comments_path)
libdot.unlink(comments_path + ".in")
return ret
def get_parser():
"""Get a command line parser."""
parser = libdot.ArgumentParser(description=__doc__, short_options=False)
parser.add_argument(
"--gerrit-comments-file",
help="Save errors for posting files to Gerrit.",
)
parser.add_argument("paths", nargs="*", help="Paths to lint.")
return parser
def main(argv):
"""The main func!"""
parser = get_parser()
opts, args = parser.parse_known_args(argv)
return (
0
if perform(
argv=args,
paths=opts.paths,
gerrit_comments_file=opts.gerrit_comments_file,
)
else 1
)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))