blob: 53fb2bb0b885a6941a242d96fcba7bd6ce642f99 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2022 The ChromiumOS Authors
Ryan Beltran1f2dd082022-04-25 18:42:32 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This script emerges packages and retrieves their lints.
6
7Currently support is provided for both general and differential linting of C++
8with Clang Tidy and Rust with Cargo Clippy for all packages within platform2.
9"""
10
Ryan Beltran179d6bb2023-09-14 23:37:33 +000011import collections
Ryan Beltran1f2dd082022-04-25 18:42:32 +000012import json
Ryan Beltran179d6bb2023-09-14 23:37:33 +000013import logging
Ryan Beltran378934c2022-11-23 00:44:26 +000014import os
15from pathlib import Path
Ryan Beltran1f2dd082022-04-25 18:42:32 +000016import sys
Ryan Beltran179d6bb2023-09-14 23:37:33 +000017from typing import DefaultDict, Dict, Iterable, List, Optional, Text, Tuple
Ryan Beltran1f2dd082022-04-25 18:42:32 +000018
19from chromite.lib import build_target_lib
20from chromite.lib import commandline
Ryan Beltran4706f342023-08-29 01:16:22 +000021from chromite.lib import constants
Ryan Beltran1f2dd082022-04-25 18:42:32 +000022from chromite.lib import cros_build_lib
Ryan Beltranb2175862022-04-28 19:55:57 +000023from chromite.lib import portage_util
Ryan Beltrance85d0f2022-08-09 21:36:39 +000024from chromite.lib import terminal
Ryan Beltran5514eab2022-04-28 21:40:24 +000025from chromite.lib import workon_helper
Ryan Beltran1f2dd082022-04-25 18:42:32 +000026from chromite.lib.parser import package_info
27from chromite.service import toolchain
28from chromite.utils import file_util
29
30
Ryan Beltran179d6bb2023-09-14 23:37:33 +000031PLATFORM2_PATH = constants.CHROOT_SOURCE_ROOT / "src/platform2"
32
33
Alex Klein1699fab2022-09-08 08:46:06 -060034def parse_packages(
35 build_target: build_target_lib.BuildTarget, packages: List[str]
36) -> List[package_info.PackageInfo]:
37 """Parse packages and insert the category if none is given.
Ryan Beltranb2175862022-04-28 19:55:57 +000038
Alex Klein1699fab2022-09-08 08:46:06 -060039 Args:
Alex Klein68b270c2023-04-14 14:42:50 -060040 build_target: build_target to find ebuild for
41 packages: user input package names to parse
Ryan Beltranb2175862022-04-28 19:55:57 +000042
Alex Klein1699fab2022-09-08 08:46:06 -060043 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -060044 A list of parsed PackageInfo objects
Alex Klein1699fab2022-09-08 08:46:06 -060045 """
46 package_infos: List[package_info.PackageInfo] = []
47 for package in packages:
48 parsed = package_info.parse(package)
49 if not parsed.category:
Alex Klein68b270c2023-04-14 14:42:50 -060050 # If a category is not specified, get it from the ebuild path.
Alex Klein1699fab2022-09-08 08:46:06 -060051 if build_target.is_host():
52 ebuild_path = portage_util.FindEbuildForPackage(
53 package, build_target.root
54 )
55 else:
56 ebuild_path = portage_util.FindEbuildForBoardPackage(
57 package, build_target.name, build_target.root
58 )
59 ebuild_data = portage_util.EBuild(ebuild_path)
60 parsed = package_info.parse(ebuild_data.package)
61 package_infos.append(parsed)
62 return package_infos
Ryan Beltranb2175862022-04-28 19:55:57 +000063
64
Ryan Beltran4706f342023-08-29 01:16:22 +000065def make_relative_to_cros(file_path: str) -> Path:
66 """removes /mnt/host/source from file_paths if present."""
67 path = Path(file_path)
68 try:
69 return path.relative_to(constants.CHROOT_SOURCE_ROOT)
70 except ValueError:
71 return path
72
73
Ryan Beltran179d6bb2023-09-14 23:37:33 +000074def process_fixes_by_file(
Ryan Beltrane522fe32023-09-26 23:06:16 +000075 lint: toolchain.LinterFinding,
76 file_lengths: Dict[Path, int],
77 allowed_subdirs: Optional[List[Text]],
Ryan Beltran179d6bb2023-09-14 23:37:33 +000078) -> Optional[DefaultDict[Path, List[toolchain.SuggestedFix]]]:
79 """Get fixes grouped by file if all the fixes apply to valid files.
80
81 If any fixes modify invalid files this returns None.
Ryan Beltranf83a3972023-09-21 00:22:25 +000082
83 Args:
84 lint: LinterFinding to get fixes from
85 file_lengths: dictionary of previously determined file lengths which
86 may be modified with additional entries
Ryan Beltrane522fe32023-09-26 23:06:16 +000087 allowed_subdirs: subdirectories in platform2 that we can modify or none
Ryan Beltran179d6bb2023-09-14 23:37:33 +000088 """
89 if not lint.suggested_fixes:
90 return None
91
92 new_fixes_by_file: DefaultDict[
93 Path, List[toolchain.SuggestedFix]
94 ] = collections.defaultdict(list)
95 for fix in lint.suggested_fixes:
96 filepath = Path(fix.location.filepath)
97 # These are files that we locate, and are usually generated files.
98 if filepath.is_absolute():
99 logging.warning(
100 "Skipped applying fix due to invalid path: %s", filepath
101 )
102 return None
Ryan Beltrane522fe32023-09-26 23:06:16 +0000103 # Check if file is in an allowed subdirectory.
104 if allowed_subdirs and filepath.parts[0] not in allowed_subdirs:
105 logging.warning(
106 "Skipped applying fix due to invalid path: %s", filepath
107 )
108 return None
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000109 # Make sure this file exists in platform2
110 file_in_platform2 = PLATFORM2_PATH / filepath
111 if not file_in_platform2.exists():
112 logging.warning(
113 "Skipped applying fix due to invalid path: %s", filepath
114 )
115 return None
116 if file_in_platform2 not in file_lengths:
117 file_lengths[file_in_platform2] = len(
118 file_in_platform2.read_text(encoding="utf-8")
119 )
120 if fix.location.end_offset > file_lengths[file_in_platform2]:
121 logging.warning(
122 "Skipped applying fix due to out of bounds change to: %s",
123 filepath,
124 )
125 return None
126 new_fixes_by_file[file_in_platform2].append(fix)
127
128 return new_fixes_by_file
129
130
131def get_noconflict_fixes(
132 lints: List[toolchain.LinterFinding],
Ryan Beltrane522fe32023-09-26 23:06:16 +0000133 allowed_subdirs: Optional[List[Text]],
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000134) -> Tuple[
135 DefaultDict[Path, List[toolchain.SuggestedFix]],
136 List[toolchain.LinterFinding],
137]:
138 """Get a conflict free set of replacements to apply for each file.
139
140 Fixes will not be included in results if they:
141 A) include a replacement to a path which does not exist
142 B) include a replacement to a path outside of platform2
143 C) include a replacement to file location that exceeds the file size
144 D) overlap a previous replacement.
145
146 Args:
147 lints: List of lints to aggregate suggested fixes from.
Ryan Beltrane522fe32023-09-26 23:06:16 +0000148 allowed_subdirs: subdirectories in platform2 that we can modify or none
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000149
150 Returns:
151 A tuple including:
152 0) the mapping of paths to a list of their suggested fixes
153 1) the list of lints which were fixed
154 """
155 fixes_by_file: DefaultDict[
156 Path, List[toolchain.SuggestedFix]
157 ] = collections.defaultdict(list)
158 lints_fixed = []
159 file_lengths = {}
160 for lint in lints:
Ryan Beltrane522fe32023-09-26 23:06:16 +0000161 new_fixes_by_file = process_fixes_by_file(
162 lint, file_lengths, allowed_subdirs
163 )
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000164 if not new_fixes_by_file:
165 continue
166 files_with_overlap = set(
167 filepath
168 for filepath, new_fixes in new_fixes_by_file.items()
169 if has_overlap(fixes_by_file[filepath], new_fixes)
170 )
171 if files_with_overlap:
172 logging.warning(
173 "Skipped applying fix for %s due to conflicts in:\n\t%s.",
174 lint.name,
175 "\n\t".join(files_with_overlap),
176 )
177 else:
178 for filepath, new_fixes in new_fixes_by_file.items():
179 fixes_by_file[filepath].extend(new_fixes)
180 lints_fixed.append(lint)
181
182 return fixes_by_file, lints_fixed
183
184
185def has_overlap(
186 prior_fixes: List[toolchain.SuggestedFix],
187 new_fixes: List[toolchain.SuggestedFix],
188) -> bool:
189 """Check if new fixes have overlapping ranges with a prior replacement.
190
191 Note: this implementation is n^2, but the amount of lints in a single file
192 is experimentally pretty small, so optimizing this is probably not a large
193 concern.
194 """
195 for new in new_fixes:
196 for old in prior_fixes:
197 if (
198 (old.location.start_offset <= new.location.start_offset)
199 and (new.location.start_offset <= old.location.end_offset)
200 ) or (
201 (old.location.start_offset <= new.location.end_offset)
202 and (new.location.end_offset <= old.location.end_offset)
203 ):
204 return True
205 return False
206
207
208def apply_edits(content: Text, fixes: List[toolchain.SuggestedFix]) -> Text:
209 """Modify a file by applying a list of fixes."""
210
211 # We need to be able to apply fixes in reverse order within a file to
212 # preserve code locations.
213 def fix_sort_key(fix: toolchain.SuggestedFix) -> int:
214 return fix.location.start_offset
215
216 pieces = []
217 content_end = len(content)
218 for fix in sorted(fixes, key=fix_sort_key, reverse=True):
219 pieces += [
220 content[fix.location.end_offset : content_end],
221 fix.replacement,
222 ]
223 content_end = fix.location.start_offset
224 pieces.append(content[:content_end])
225
226 return "".join(reversed(pieces))
227
228
229def apply_fixes(
230 lints: List[toolchain.LinterFinding],
Ryan Beltrane522fe32023-09-26 23:06:16 +0000231 allowed_subdirs: Optional[List[Text]],
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000232) -> Tuple[List[toolchain.LinterFinding], Iterable[Path]]:
233 """Modify files in Platform2 to apply suggested fixes from linter findings.
234
235 Some fixes which cannot be applied cleanly will be discarded (see the
236 `get_noconflict_fixes_by_file` description for more details).
237
238 Args:
239 lints: LinterFindings to apply potential fixes from.
Ryan Beltrane522fe32023-09-26 23:06:16 +0000240 allowed_subdirs: subdirectories in platform2 that we can modify or none
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000241
242 Returns:
243 A tuple including:
244 0) The list of lints which were fixed
245 1) The list of files which were modified
246 """
247
Ryan Beltrane522fe32023-09-26 23:06:16 +0000248 fixes_by_file, lints_fixed = get_noconflict_fixes(lints, allowed_subdirs)
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000249
250 for filepath, fixes in fixes_by_file.items():
251 file_content = filepath.read_text(encoding="utf-8")
252 rewrite = apply_edits(file_content, fixes)
253 filepath.write_text(rewrite, encoding="utf-8")
254
255 return lints_fixed, fixes_by_file.keys()
256
257
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000258def format_lint(lint: toolchain.LinterFinding) -> Text:
Alex Klein68b270c2023-04-14 14:42:50 -0600259 """Formats a lint for human-readable printing.
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 Example output:
262 [ClangTidy] In 'path/to/file.c' on line 36:
263 Also in 'path/to/file.c' on line 40:
264 Also in 'path/to/file.c' on lines 50-53:
265 You did something bad, don't do it.
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 Args:
Alex Klein68b270c2023-04-14 14:42:50 -0600268 lint: A linter finding from the toolchain service.
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 Returns:
Alex Klein68b270c2023-04-14 14:42:50 -0600271 A correctly formatted string ready to be displayed to the user.
Alex Klein1699fab2022-09-08 08:46:06 -0600272 """
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 color = terminal.Color(True)
275 lines = []
276 linter_prefix = color.Color(
277 terminal.Color.YELLOW,
278 f"[{lint.linter}]",
279 background_color=terminal.Color.BLACK,
280 )
281 for loc in lint.locations:
Ryan Beltran4706f342023-08-29 01:16:22 +0000282 filepath = make_relative_to_cros(loc.filepath)
Alex Klein1699fab2022-09-08 08:46:06 -0600283 if not lines:
284 location_prefix = f"\n{linter_prefix} In"
285 else:
286 location_prefix = " and in"
287 if loc.line_start != loc.line_end:
288 lines.append(
Ryan Beltran4706f342023-08-29 01:16:22 +0000289 f"{location_prefix} '{filepath}' "
Alex Klein1699fab2022-09-08 08:46:06 -0600290 f"lines {loc.line_start}-{loc.line_end}:"
291 )
292 else:
293 lines.append(
Ryan Beltran4706f342023-08-29 01:16:22 +0000294 f"{location_prefix} '{filepath}' line {loc.line_start}:"
Alex Klein1699fab2022-09-08 08:46:06 -0600295 )
296 message_lines = lint.message.split("\n")
297 for line in message_lines:
298 lines.append(f" {line}")
299 lines.append("")
300 return "\n".join(lines)
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000301
302
Ryan Beltrana32a1a12022-09-28 06:03:45 +0000303def json_format_lint(lint: toolchain.LinterFinding) -> Text:
304 """Formats a lint in json for machine parsing.
305
306 Args:
307 lint: A linter finding from the toolchain service.
308
309 Returns:
310 A correctly formatted json string ready to be displayed to the user.
311 """
312
313 def _dictify(original):
314 """Turns namedtuple's to dictionaries recursively."""
315 # Handle namedtuples
316 if isinstance(original, tuple) and hasattr(original, "_asdict"):
317 return _dictify(original._asdict())
318 # Handle collection types
319 elif hasattr(original, "__iter__"):
320 # Handle strings
321 if isinstance(original, (str, bytes)):
322 return original
323 # Handle dictionaries
324 elif isinstance(original, dict):
325 return {k: _dictify(v) for k, v in original.items()}
326 # Handle lists, sets, etc.
327 else:
328 return [_dictify(x) for x in original]
Ryan Beltranc37fb392023-05-11 18:24:40 +0000329 # Handle PackageInfo objects
330 elif isinstance(original, package_info.PackageInfo):
331 return original.atom
Ryan Beltrana32a1a12022-09-28 06:03:45 +0000332 # Handle everything else
333 return original
334
335 return json.dumps(_dictify(lint))
336
337
Ryan Beltran378934c2022-11-23 00:44:26 +0000338def get_all_sysroots() -> List[Text]:
339 """Gets all available sysroots for both host and boards."""
340 host_root = Path(build_target_lib.BuildTarget(None).root)
341 roots = [str(host_root)]
342 build_dir = host_root / "build"
343 for board in os.listdir(build_dir):
344 if board != "bin":
345 board_root = build_dir / board
346 if board_root.is_dir():
347 roots.append(str(board_root))
348 return roots
349
350
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000351def get_arg_parser() -> commandline.ArgumentParser:
Alex Klein1699fab2022-09-08 08:46:06 -0600352 """Creates an argument parser for this script."""
353 default_board = cros_build_lib.GetDefaultBoard()
354 parser = commandline.ArgumentParser(description=__doc__)
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000355
Ryan Beltran378934c2022-11-23 00:44:26 +0000356 board_group = parser.add_mutually_exclusive_group()
Alex Klein1699fab2022-09-08 08:46:06 -0600357 board_group.add_argument(
358 "-b",
359 "--board",
360 "--build-target",
361 dest="board",
362 default=default_board,
363 help="The board to emerge packages for",
364 )
365 board_group.add_argument(
366 "--host", action="store_true", help="emerge for host instead of board."
367 )
Ryan Beltran378934c2022-11-23 00:44:26 +0000368 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600369 "--fetch-only",
370 action="store_true",
Alex Klein68b270c2023-04-14 14:42:50 -0600371 help="Fetch lints from previous run without resetting or calling "
372 "emerge.",
Alex Klein1699fab2022-09-08 08:46:06 -0600373 )
Alex Klein1699fab2022-09-08 08:46:06 -0600374 parser.add_argument(
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000375 "--apply-fixes",
376 action="store_true",
377 help="Apply suggested fixes from linters.",
378 )
379 parser.add_argument(
Ryan Beltranf83a3972023-09-21 00:22:25 +0000380 "--filter-names",
381 help="Only keep lints if the name contains one of the provided filters",
382 action="append",
383 )
384 parser.add_argument(
Ryan Beltrane522fe32023-09-26 23:06:16 +0000385 "--restrict-fix-subdirs",
386 help="Only fix lints if all fixes are in the given directories",
387 action="append",
388 )
389 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600390 "--differential",
391 action="store_true",
392 help="only lint lines touched by the last commit",
393 )
394 parser.add_argument(
395 "-o",
396 "--output",
397 default=sys.stdout,
398 help="File to use instead of stdout.",
399 )
400 parser.add_argument(
401 "--json", action="store_true", help="Output lints in JSON format."
402 )
403 parser.add_argument(
404 "--no-clippy",
405 dest="clippy",
406 action="store_false",
407 help="Disable cargo clippy linter.",
408 )
409 parser.add_argument(
410 "--no-tidy",
411 dest="tidy",
412 action="store_false",
413 help="Disable clang tidy linter.",
414 )
415 parser.add_argument(
416 "--no-golint",
417 dest="golint",
418 action="store_false",
419 help="Disable golint linter.",
420 )
421 parser.add_argument(
Ryan Beltran378934c2022-11-23 00:44:26 +0000422 "--iwyu",
423 action="store_true",
424 help="Enable include-what-you-use linter.",
425 )
426 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600427 "packages",
428 nargs="*",
429 help="package(s) to emerge and retrieve lints for",
430 )
Ryan Beltrane522fe32023-09-26 23:06:16 +0000431
Alex Klein1699fab2022-09-08 08:46:06 -0600432 return parser
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000433
434
435def parse_args(argv: List[str]):
Alex Klein1699fab2022-09-08 08:46:06 -0600436 """Parses arguments in argv and returns the options."""
437 parser = get_arg_parser()
438 opts = parser.parse_args(argv)
439 opts.Freeze()
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000440
Alex Klein1699fab2022-09-08 08:46:06 -0600441 # A package must be specified unless we are in fetch-only mode
442 if not (opts.fetch_only or opts.packages):
Ryan Beltran378934c2022-11-23 00:44:26 +0000443 parser.error("Emerge mode requires specified package(s).")
Alex Klein1699fab2022-09-08 08:46:06 -0600444 if opts.fetch_only and opts.packages:
445 parser.error("Cannot specify packages for fetch-only mode.")
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000446
Ryan Beltran378934c2022-11-23 00:44:26 +0000447 # A board must be specified unless we are in fetch-only mode
448 if not (opts.fetch_only or opts.board or opts.host):
449 parser.error("Emerge mode requires either --board or --host.")
450
Ryan Beltrane522fe32023-09-26 23:06:16 +0000451 # Require apply_fix for flags that only affect this mode
452 if opts.restrict_fix_subdirs and not opts.apply_fixes:
453 parser.error(
454 "--restrict-fix-subdirs is meaningless if fixes aren't applied"
455 )
456
Alex Klein1699fab2022-09-08 08:46:06 -0600457 return opts
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000458
459
Ryan Beltranf83a3972023-09-21 00:22:25 +0000460def filter_lints(
461 lints: List[toolchain.LinterFinding], names_filters: List[Text]
462) -> List[toolchain.LinterFinding]:
463 """Filter linter finding by name."""
464 return [l for l in lints if any(f in l.name for f in names_filters)]
465
466
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000467def main(argv: List[str]) -> None:
Alex Klein1699fab2022-09-08 08:46:06 -0600468 cros_build_lib.AssertInsideChroot()
469 opts = parse_args(argv)
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000470
Alex Klein1699fab2022-09-08 08:46:06 -0600471 if opts.host:
472 # BuildTarget interprets None as host target
473 build_target = build_target_lib.BuildTarget(None)
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000474 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600475 build_target = build_target_lib.BuildTarget(opts.board)
476 packages = parse_packages(build_target, opts.packages)
477 package_atoms = [x.atom for x in packages]
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000478
Alex Klein1699fab2022-09-08 08:46:06 -0600479 with workon_helper.WorkonScope(build_target, package_atoms):
480 build_linter = toolchain.BuildLinter(
481 packages, build_target.root, opts.differential
482 )
483 if opts.fetch_only:
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000484 if opts.apply_fixes:
485 logging.warning(
486 "Apply fixes with fetch_only may lead to fixes being"
487 " applied incorrectly if source files have changed!"
488 )
Ryan Beltran378934c2022-11-23 00:44:26 +0000489 if opts.host or opts.board:
490 roots = [build_target.root]
491 else:
492 roots = get_all_sysroots()
493 lints = []
494 for root in roots:
495 build_linter.sysroot = root
496 lints.extend(
497 build_linter.fetch_findings(
498 use_clippy=opts.clippy,
499 use_tidy=opts.tidy,
500 use_golint=opts.golint,
501 use_iwyu=opts.iwyu,
502 )
503 )
Alex Klein1699fab2022-09-08 08:46:06 -0600504 else:
505 lints = build_linter.emerge_with_linting(
506 use_clippy=opts.clippy,
507 use_tidy=opts.tidy,
508 use_golint=opts.golint,
Ryan Beltran378934c2022-11-23 00:44:26 +0000509 use_iwyu=opts.iwyu,
Alex Klein1699fab2022-09-08 08:46:06 -0600510 )
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000511
Ryan Beltranf83a3972023-09-21 00:22:25 +0000512 if opts.filter_names:
513 lints = filter_lints(lints, opts.filter_names)
514
Alex Klein1699fab2022-09-08 08:46:06 -0600515 if opts.json:
Ryan Beltrana32a1a12022-09-28 06:03:45 +0000516 formatted_output_inner = ",\n".join(json_format_lint(l) for l in lints)
517 formatted_output = f"[{formatted_output_inner}]"
Alex Klein1699fab2022-09-08 08:46:06 -0600518 else:
Ryan Beltrana32a1a12022-09-28 06:03:45 +0000519 formatted_output = "\n".join(format_lint(l) for l in lints)
Alex Klein1699fab2022-09-08 08:46:06 -0600520
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000521 if opts.apply_fixes:
Ryan Beltrane522fe32023-09-26 23:06:16 +0000522 fixed_lints, modified_files = apply_fixes(
523 lints, opts.restrict_fix_subdirs
524 )
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000525 if opts.json:
526 formatted_fixes_inner = ",\n".join(
527 json_format_lint(l) for l in lints
528 )
529 formatted_fixes = f"[{formatted_fixes_inner}]"
530 else:
531 formatted_fixes = "\n".join(format_lint(l) for l in fixed_lints)
532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 with file_util.Open(opts.output, "w") as output_file:
534 output_file.write(formatted_output)
535 if not opts.json:
536 output_file.write(f"\nFound {len(lints)} lints.")
Ryan Beltran179d6bb2023-09-14 23:37:33 +0000537 if opts.apply_fixes:
538 output_file.write("\n\n\n--------- Fixed Problems ---------\n\n")
539 output_file.write(formatted_fixes)
540 if not opts.json:
541 output_file.write(
542 f"\nFixed {len(fixed_lints)}/{len(lints)} lints."
543 )
544 output_file.write("\n\n\n--------- Modified Files ---------\n\n")
Ryan Beltranf83a3972023-09-21 00:22:25 +0000545 output_file.write("\n".join(str(f) for f in sorted(modified_files)))
Alex Klein1699fab2022-09-08 08:46:06 -0600546 output_file.write("\n")