Mike Frysinger | f1ba7ad | 2022-09-12 05:42:57 -0400 | [diff] [blame] | 1 | # Copyright 2021 The ChromiumOS Authors |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Runs cargo clippy across the given files, dumping diagnostics to a JSON file. |
| 6 | |
| 7 | This script is intended specifically for use with Tricium (go/tricium). |
| 8 | """ |
| 9 | |
| 10 | import json |
Chris McDonald | 59650c3 | 2021-07-20 15:29:28 -0600 | [diff] [blame] | 11 | import logging |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 12 | import os |
| 13 | from pathlib import Path |
Ryan Beltran | a4b45a3 | 2021-08-11 08:26:38 +0000 | [diff] [blame] | 14 | import re |
Chris McDonald | 59650c3 | 2021-07-20 15:29:28 -0600 | [diff] [blame] | 15 | from typing import Any, Dict, Iterable, List, NamedTuple, Text |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 16 | |
| 17 | from chromite.lib import commandline |
| 18 | from chromite.lib import cros_build_lib |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 19 | |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 20 | |
| 21 | class Error(Exception): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 22 | """Base error class for tricium-cargo-clippy.""" |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 23 | |
| 24 | |
Ryan Beltran | c0fa16a | 2021-08-05 20:45:14 +0000 | [diff] [blame] | 25 | class CargoClippyPackagePathError(Error): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 26 | """Raised when no Package Path is provided.""" |
Ryan Beltran | c0fa16a | 2021-08-05 20:45:14 +0000 | [diff] [blame] | 27 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 28 | def __init__(self, source: Text): |
| 29 | super().__init__(f"{source} does not start with a package path") |
| 30 | self.source = source |
| 31 | |
Ryan Beltran | c0fa16a | 2021-08-05 20:45:14 +0000 | [diff] [blame] | 32 | |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 33 | class CargoClippyJSONError(Error): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 34 | """Raised when cargo-clippy parsing jobs are not proper JSON.""" |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 35 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 36 | def __init__(self, source: Text, line_num: int): |
| 37 | super().__init__(f"{source}:{line_num}: is not valid JSON") |
| 38 | self.source = source |
| 39 | self.line_num = line_num |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 40 | |
| 41 | |
| 42 | class CargoClippyReasonError(Error): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 43 | """Raised when cargo-clippy parsing jobs don't provide a "reason" field.""" |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 44 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 45 | def __init__(self, source: Text, line_num: int): |
| 46 | super().__init__(f"{source}:{line_num}: is missing its reason") |
| 47 | self.source = source |
| 48 | self.line_num = line_num |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 49 | |
| 50 | |
| 51 | class CargoClippyFieldError(Error): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 52 | """Raised when cargo-clippy parsing jobs fail to determine a field.""" |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 53 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 54 | def __init__(self, source: Text, line_num: int, field: Text): |
| 55 | super().__init__( |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 56 | f"{source}:{line_num}: {field} could not be parsed from original" |
| 57 | " json" |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 58 | ) |
| 59 | self.source = source |
| 60 | self.line_num = line_num |
| 61 | self.field = field |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 62 | |
| 63 | |
| 64 | def resolve_path(file_path: Text) -> Text: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 65 | return str(Path(file_path).resolve()) |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 66 | |
| 67 | |
| 68 | class CodeLocation(NamedTuple): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 69 | """Holds the location a ClippyDiagnostic Finding.""" |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 70 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 71 | file_path: Text |
| 72 | line_start: int |
| 73 | line_end: int |
| 74 | column_start: int |
| 75 | column_end: int |
| 76 | |
| 77 | def to_dict(self): |
| 78 | return {**self._asdict(), "file_path": self.file_path} |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 79 | |
| 80 | |
| 81 | class ClippyDiagnostic(NamedTuple): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 82 | """Holds information about a compiler message from Clippy.""" |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 83 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 84 | locations: Iterable["CodeLocation"] |
| 85 | level: Text |
| 86 | message: Text |
| 87 | |
| 88 | def as_json(self): |
| 89 | return json.dumps( |
| 90 | { |
| 91 | **self._asdict(), |
| 92 | "locations": [loc.to_dict() for loc in self.locations], |
| 93 | } |
| 94 | ) |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 95 | |
| 96 | |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 97 | def parse_locations( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 98 | orig_json: Dict[Text, Any], package_path: Text, git_repo: Text |
| 99 | ) -> Iterable["CodeLocation"]: |
| 100 | """The code locations associated with this diagnostic as an iter. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 101 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 102 | The relevant code location can appear in either the messages[spans] field, |
| 103 | which will be used if present, or else child messages each have their own |
| 104 | locations specified. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 105 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 106 | Args: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 107 | orig_json: An iterable of clippy entries in original json. |
| 108 | package_path: A resolved path to the rust package. |
| 109 | git_repo: Base directory for git repo to strip out in diagnostics. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 110 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 111 | Yields: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 112 | A CodeLocation object associated with a relevant span. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 113 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 114 | Raises: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 115 | CargoClippyFieldError: Parsing failed to determine any code locations. |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 116 | """ |
| 117 | spans = orig_json.get("message", {}).get("spans", []) |
| 118 | children = orig_json.get("message", {}).get("children", []) |
| 119 | for child in children: |
| 120 | spans = spans + child.get("spans", []) |
| 121 | locations = set() |
| 122 | for span in spans: |
| 123 | file_path = os.path.join(package_path, span.get("file_name")) |
| 124 | if git_repo and file_path.startswith(f"{git_repo}/"): |
| 125 | file_path = file_path[len(git_repo) + 1 :] |
| 126 | else: |
| 127 | # Remove ebuild work directories from prefix |
| 128 | # Such as: "**/<package>-9999/work/<package>-9999/" |
| 129 | # or: "**/<package>-0.24.52-r9/work/<package>-0.24.52/" |
| 130 | file_path = re.sub( |
| 131 | r"(.*/)?([^/]+)-[^/]+/work/[^/]+/+", "", file_path |
| 132 | ) |
| 133 | location = CodeLocation( |
| 134 | file_path=file_path, |
| 135 | line_start=span.get("line_start"), |
| 136 | line_end=span.get("line_end"), |
| 137 | column_start=span.get("column_start"), |
| 138 | column_end=span.get("column_end"), |
| 139 | ) |
| 140 | if location not in locations: |
| 141 | locations.add(location) |
| 142 | yield location |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 143 | |
| 144 | |
| 145 | def parse_level(src: Text, src_line: int, orig_json: Dict[Text, Any]) -> Text: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 146 | """The level (error or warning) associated with this diagnostic. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 147 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 148 | Args: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 149 | src: Name of the file orig_json was found in. |
| 150 | src_line: Line number where orig_json was found. |
| 151 | orig_json: An iterable of clippy entries in original json. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 152 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 153 | Returns: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 154 | The level of the diagnostic as a string (either error or warning). |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 155 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 156 | Raises: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 157 | CargoClippyFieldError: Parsing failed to determine the level. |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 158 | """ |
| 159 | level = orig_json.get("level") |
| 160 | if not level: |
| 161 | level = orig_json.get("message", {}).get("level") |
| 162 | if not level: |
| 163 | raise CargoClippyFieldError(src, src_line, "level") |
| 164 | return level |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 165 | |
| 166 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 167 | def parse_message(src: Text, src_line: int, orig_json: Dict[Text, Any]) -> Text: |
| 168 | """The formatted linter message for this diagnostic. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 169 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 170 | Args: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 171 | src: Name of the file orig_json was found in. |
| 172 | src_line: Line number where orig_json was found. |
| 173 | orig_json: An iterable of clippy entries in original json. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 174 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 175 | Returns: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 176 | The rendered message of the diagnostic. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 177 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 178 | Raises: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 179 | CargoClippyFieldError: Parsing failed to determine the message. |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 180 | """ |
| 181 | message = orig_json.get("message", {}).get("rendered") |
| 182 | if message is None: |
| 183 | raise CargoClippyFieldError(src, src_line, "message") |
| 184 | return message |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 185 | |
| 186 | |
| 187 | def parse_diagnostics( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 188 | src: Text, orig_jsons: Iterable[Text], git_repo: Text |
| 189 | ) -> ClippyDiagnostic: |
| 190 | """Parses original JSON to find the fields of a Clippy Diagnostic. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 191 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 192 | Args: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 193 | src: Name of the file orig_json was found in. |
| 194 | orig_jsons: An iterable of clippy entries in original json. |
| 195 | git_repo: Base directory for git repo to strip out in diagnostics. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 196 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 197 | Yields: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 198 | A ClippyDiagnostic for orig_json. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 199 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 200 | Raises: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 201 | CargoClippyJSONError: if a diagnostic is not valid JSON. |
| 202 | CargoClippyReasonError: if a diagnostic is missing a "reason" field. |
| 203 | CargoClippyFieldError: if a field cannot be determined while parsing. |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 204 | """ |
| 205 | for src_line, orig_json in enumerate(orig_jsons): |
| 206 | try: |
| 207 | line_json = json.loads(orig_json) |
| 208 | except json.decoder.JSONDecodeError: |
| 209 | json_error = CargoClippyJSONError(src, src_line) |
| 210 | logging.error(json_error) |
| 211 | raise json_error |
Ryan Beltran | c0fa16a | 2021-08-05 20:45:14 +0000 | [diff] [blame] | 212 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 213 | # We pass the path to the package in a special JSON on the first line |
| 214 | if src_line == 0: |
| 215 | package_path = line_json.get("package_path") |
| 216 | if not package_path: |
| 217 | raise CargoClippyPackagePathError(src) |
| 218 | package_path = resolve_path(package_path) |
| 219 | continue |
Ryan Beltran | c0fa16a | 2021-08-05 20:45:14 +0000 | [diff] [blame] | 220 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 221 | # Clippy outputs several types of logs, as distinguished by the "reason" |
| 222 | # field, but we only want to process "compiler-message" logs. |
| 223 | reason = line_json.get("reason") |
| 224 | if reason is None: |
| 225 | reason_error = CargoClippyReasonError(src, src_line) |
| 226 | logging.error(reason_error) |
| 227 | raise reason_error |
| 228 | if reason != "compiler-message": |
| 229 | continue |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 230 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 231 | locations = parse_locations(line_json, package_path, git_repo) |
| 232 | level = parse_level(src, src_line, line_json) |
| 233 | message = parse_message(src, src_line, line_json) |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 234 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 235 | # TODO(ryanbeltran): Export suggested replacements |
| 236 | yield ClippyDiagnostic(locations, level, message) |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 237 | |
| 238 | |
Ryan Beltran | 923a131 | 2021-07-30 00:28:13 +0000 | [diff] [blame] | 239 | def parse_files(input_dir: Text, git_repo: Text) -> Iterable[ClippyDiagnostic]: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 240 | """Gets all compiler-message lints from all the input files in input_dir. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 241 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 242 | Args: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 243 | input_dir: path to directory to scan for files |
| 244 | git_repo: Base directory for git repo to strip out in diagnostics. |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 245 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 246 | Yields: |
Trent Apted | c20bb6d | 2023-05-10 15:00:03 +1000 | [diff] [blame^] | 247 | Clippy Diagnostics objects found in files in the input directory |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 248 | """ |
| 249 | for root_path, _, file_names in os.walk(input_dir): |
| 250 | for file_name in file_names: |
| 251 | file_path = os.path.join(root_path, file_name) |
| 252 | with open(file_path, encoding="utf-8") as clippy_file: |
| 253 | yield from parse_diagnostics(file_path, clippy_file, git_repo) |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 254 | |
| 255 | |
| 256 | def filter_diagnostics( |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 257 | diags: Iterable[ClippyDiagnostic], |
| 258 | ) -> Iterable[ClippyDiagnostic]: |
| 259 | """Filters diagnostics and validates schemas.""" |
| 260 | for diag in diags: |
| 261 | # ignore redundant messages: "aborting due to previous error..." |
| 262 | if "aborting due to previous error" in diag.message: |
| 263 | continue |
| 264 | # findings with no location are never useful |
| 265 | if not diag.locations: |
| 266 | continue |
| 267 | yield diag |
Ryan Beltran | 43a0066 | 2021-05-17 16:55:24 +0000 | [diff] [blame] | 268 | |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 269 | |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 270 | def get_arg_parser() -> commandline.ArgumentParser: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 271 | """Creates an argument parser for this script.""" |
| 272 | parser = commandline.ArgumentParser(description=__doc__) |
| 273 | parser.add_argument( |
| 274 | "--output", required=True, type="path", help="File to write results to." |
| 275 | ) |
| 276 | parser.add_argument( |
| 277 | "--clippy-json-dir", |
| 278 | type="path", |
| 279 | help="Directory where clippy outputs were previously written to.", |
| 280 | ) |
| 281 | parser.add_argument( |
| 282 | "--git-repo-path", |
| 283 | type="path", |
| 284 | default="", |
| 285 | help="Base directory for git repo to strip out in diagnostics.", |
| 286 | ) |
| 287 | return parser |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 288 | |
| 289 | |
| 290 | def main(argv: List[str]) -> None: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 291 | cros_build_lib.AssertInsideChroot() |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 292 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 293 | logging.basicConfig() |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 294 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 295 | parser = get_arg_parser() |
| 296 | opts = parser.parse_args(argv) |
| 297 | opts.Freeze() |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 298 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 299 | input_dir = resolve_path(opts.clippy_json_dir) |
| 300 | output_path = resolve_path(opts.output) |
| 301 | git_repo = opts.git_repo_path |
Ryan Beltran | cfc5c36 | 2021-03-02 18:36:18 +0000 | [diff] [blame] | 302 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 303 | diagnostics = filter_diagnostics(parse_files(input_dir, git_repo)) |
| 304 | with open(output_path, "w", encoding="utf-8") as output_file: |
| 305 | output_file.writelines(f"{diag}\n" for diag in diagnostics) |