blob: beb002d23c8217750ef62aebc33b9305e098cf13 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# 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] %s: %s' % (
SEVERITY_TO_HEADER[message['severity']],
message['message'],
)
# For syntax errors, this will be omitted.
ruleId = message.get('ruleId')
if ruleId:
msg += '\n%s%s' % (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:]))