1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
#!/usr/bin/env python3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Samples clang-tidy results from a JSON file.
Provides information about number of checks triggered and a summary of some of
the checks with links back to code search.
Usage:
tools/sample_clang_tidy_results.py out/all_findings.json
"""
import argparse
import collections
import functools
import json
import logging
import os
import random
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List
@functools.lru_cache(maxsize=None)
def get_src_path() -> str:
src_path = Path(__file__).parent.parent.resolve()
if not src_path:
raise NotFoundError(
'Could not find checkout in any parent of the current path.')
return src_path
@functools.lru_cache(maxsize=None)
def git_rev_parse_head(path: Path):
if (path / '.git').exists():
return subprocess.check_output(['git', 'rev-parse', 'HEAD'],
encoding='utf-8',
cwd=path).strip()
return git_rev_parse_head(path.parent)
def convert_diag_to_cs(diag: Dict[str, Any]) -> str:
path = diag['file_path']
line = diag['line_number']
name = diag['diag_name']
replacement = '\n'.join(x['new_text'] for x in diag['replacements'])
sha = git_rev_parse_head(get_src_path() / path)
# https://source.chromium.org/chromium/chromium/src/+/main:apps/app_restore_service.cc
sha_and_path = f'{sha}:{path}'
return {
'name':
name,
'path': ('https://source.chromium.org/chromium/chromium/src/+/'
f'{sha}:{path};l={line}'),
'replacement':
replacement
}
@functools.lru_cache(maxsize=None)
def is_first_party_path(path: Path) -> bool:
if path == get_src_path():
return True
if path == '/':
return False
if (path / '.git').exists() or (path / '.gclient').exists():
return False
return is_first_party_path(path.parent)
def is_first_party_diag(diag: Dict[str, Any]) -> bool:
path = diag['file_path']
if path.startswith('out/') or path.startswith('/'):
return False
return is_first_party_path(get_src_path() / path)
def select_random_diags(diags: List[Dict[str, Any]], number: int) -> List[Any]:
first_party = [x for x in diags if is_first_party_diag(x)]
if len(first_party) <= number:
return first_party
return random.sample(first_party, number)
def is_diag_in_test_file(diag: Dict[str, Any]) -> bool:
file_stem = os.path.splitext(diag['file_path'])[0]
return (file_stem.endswith('test') or file_stem.endswith('tests')
or '_test_' in file_stem or '_unittest_' in file_stem)
def is_diag_in_third_party(diag: Dict[str, Any]) -> bool:
return 'third_party' in diag['file_path']
def main(argv: List[str]):
logging.basicConfig(
format='>> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: '
'%(message)s',
level=logging.INFO,
)
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument('-n',
'--number',
type=int,
default=30,
help='How many checks to sample')
parser.add_argument('--ignore-tests',
action='store_true',
help='Filters lints in test/unittest files if specified.')
parser.add_argument('--include-third-party',
action='store_true',
help='Includes lints in third_party if specified.')
parser.add_argument('file', help='JSON file to parse')
opts = parser.parse_args(argv)
with open(opts.file) as f:
data = json.load(f)
print(f'Files with tidy errors: {len(data["failed_tidy_files"])}')
print(f'Timed out files: {len(data["timed_out_src_files"])}')
diags = data['diagnostics']
if not opts.include_third_party:
new_diags = [x for x in diags if not is_diag_in_third_party(x)]
print(f'Dropped {len(diags) - len(new_diags)} diags from third_party')
diags = new_diags
if opts.ignore_tests:
new_diags = [x for x in diags if not is_diag_in_test_file(x)]
print(f'Dropped {len(diags) - len(new_diags)} diags from test files')
diags = new_diags
counts = collections.defaultdict(int)
for x in diags:
name = x['diag_name']
counts[name] += 1
print(f'Total number of diagnostics: {len(diags)}')
for x in sorted(counts.keys()):
print(f'\t{x}: {counts[x]}')
print()
diags = select_random_diags(diags, opts.number)
data = [convert_diag_to_cs(x) for x in diags]
print(f'** Sample of first-party lints: **')
for x in data:
print(x['path'])
print(f'\tDiagnostic: {x["name"]}')
print(f'\tReplacement: {x["replacement"]}')
print()
print('** Link summary **')
for x in data:
print(x['path'])
if __name__ == '__main__':
main(sys.argv[1:])
|