Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------

import json
import logging
import os

from typing import Dict, List

from codechecker_report_converter.report import File, get_or_create_file, \
Report

from ..analyzer_result import AnalyzerResultBase


LOG = logging.getLogger('report-converter')


class AnalyzerResult(AnalyzerResultBase):
""" Transform analyzer result in json format of the ruff analyzer. """

TOOL_NAME = 'ruff'
NAME = 'ruff'
URL = 'https://docs.astral.sh/ruff/'

def get_reports(self, file_path: str) -> List[Report]:
""" Get reports from the given analyzer result. """
reports: List[Report] = []

if not os.path.exists(file_path):
LOG.error("Report file does not exist: %s", file_path)
return reports

try:
with open(file_path, 'r',
encoding="utf-8", errors="ignore") as f:
bugs = json.load(f)
except (IOError, json.decoder.JSONDecodeError):
LOG.error("Failed to parse the given analyzer result '%s'. Please "
"give a valid json file generated by ruff.",
file_path)
return reports

file_cache: Dict[str, File] = {}
for bug in bugs:
fp = bug.get('filename')
if not os.path.exists(fp):
LOG.warning("Source file does not exists: %s", fp)
continue

reports.append(Report(
get_or_create_file(os.path.abspath(fp), file_cache),
int(bug['location']['row']),
int(bug['location']['column']),
bug['message'],
bug['code']))

return reports
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# copied from pylint, could
all:
ruff check --exit-zero --output-format=json --output-file=./simple.json files/simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python3

import json


def foo(x):
return 1


foo(0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>diagnostics</key>
<array>
<dict>
<key>category</key>
<string>unknown</string>
<key>check_name</key>
<string>F401</string>
<key>description</key>
<string>`json` imported but unused</string>
<key>issue_hash_content_of_line_in_context</key>
<string>faa5e0e305cb745ebab5a254dfd0ce66</string>
<key>location</key>
<dict>
<key>col</key>
<integer>8</integer>
<key>file</key>
<integer>0</integer>
<key>line</key>
<integer>3</integer>
</dict>
<key>path</key>
<array>
<dict>
<key>depth</key>
<integer>0</integer>
<key>kind</key>
<string>event</string>
<key>location</key>
<dict>
<key>col</key>
<integer>8</integer>
<key>file</key>
<integer>0</integer>
<key>line</key>
<integer>3</integer>
</dict>
<key>message</key>
<string>`json` imported but unused</string>
</dict>
</array>
<key>type</key>
<string>ruff</string>
</dict>
</array>
<key>files</key>
<array>
<string>files/simple.py</string>
</array>
<key>metadata</key>
<dict>
<key>analyzer</key>
<dict>
<key>name</key>
<string>ruff</string>
</dict>
<key>generated_by</key>
<dict>
<key>name</key>
<string>report-converter</string>
<key>version</key>
<string>x.y.z</string>
</dict>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[
{
"cell": null,
"code": "F401",
"end_location": {
"column": 12,
"row": 3
},
"filename": "/localdata1/zoel_ml/codechecker/tools/report-converter/tests/unit/analyzers/ruff_output_test_files/files/simple.py",
"fix": {
"applicability": "safe",
"edits": [
{
"content": "",
"end_location": {
"column": 1,
"row": 4
},
"location": {
"column": 1,
"row": 3
}
}
],
"message": "Remove unused import: `json`"
},
"location": {
"column": 8,
"row": 3
},
"message": "`json` imported but unused",
"noqa_row": 3,
"url": "https://docs.astral.sh/ruff/rules/unused-import"
}
]
86 changes: 86 additions & 0 deletions tools/report-converter/tests/unit/analyzers/test_ruff_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------

"""
This module tests the correctness of the RuffAnalyzerResult, which
used in sequence transform ruff output to a plist file.
"""


import os
import plistlib
import shutil
import tempfile
import unittest

from codechecker_report_converter.analyzers.ruff import analyzer_result
from codechecker_report_converter.report.parser import plist


class RuffAnalyzerResultTestCase(unittest.TestCase):
""" Test the output of the RuffAnalyzerResult. """

def setUp(self):
""" Setup the test. """
self.analyzer_result = analyzer_result.AnalyzerResult()
self.cc_result_dir = tempfile.mkdtemp()
self.test_files = os.path.join(os.path.dirname(__file__),
'ruff_output_test_files')

def tearDown(self):
""" Clean temporary directory. """
shutil.rmtree(self.cc_result_dir)

def test_no_json_file(self):
""" Test transforming single plist file. """
analyzer_result = os.path.join(self.test_files, 'files',
'simple.py')

ret = self.analyzer_result.transform(
[analyzer_result], self.cc_result_dir, plist.EXTENSION,
file_name="{source_file}_{analyzer}")
self.assertFalse(ret)

def test_transform_dir(self):
""" Test transforming single plist file. """
analyzer_result = os.path.join(self.test_files)

ret = self.analyzer_result.transform(
[analyzer_result], self.cc_result_dir, plist.EXTENSION,
file_name="{source_file}_{analyzer}")
self.assertFalse(ret)

def test_transform_single_file(self):
""" Test transforming single json file. """
analyzer_result = os.path.join(self.test_files, 'simple.json')
self.analyzer_result.transform(
[analyzer_result], self.cc_result_dir, plist.EXTENSION,
file_name="{source_file}_{analyzer}")

plist_file = os.path.join(self.cc_result_dir,
'simple.py_ruff.plist')

with open(plist_file, mode='rb') as pfile:
res = plistlib.load(pfile)

# Use relative path for this test.
res['files'][0] = os.path.join('files', 'simple.py')

self.assertTrue(res['metadata']['generated_by']['version'])
res['metadata']['generated_by']['version'] = "x.y.z"

plist_file = os.path.join(self.test_files,
'simple.expected.plist')
with open(plist_file, mode='rb') as pfile:
exp = plistlib.load(pfile)

self.assertEqual(res, exp)


if __name__ == '__main__':
unittest.main()
Loading