1# Lint as: python3 2# Copyright (C) 2019 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""Emit warning messages to html or csv files.""" 17 18# Many functions in this module have too many arguments to be refactored. 19# pylint:disable=too-many-arguments,missing-function-docstring 20 21# To emit html page of warning messages: 22# flags: --byproject, --url, --separator 23# Old stuff for static html components: 24# html_script_style: static html scripts and styles 25# htmlbig: 26# dump_stats, dump_html_prologue, dump_html_epilogue: 27# emit_buttons: 28# dump_fixed 29# sort_warnings: 30# emit_stats_by_project: 31# all_patterns, 32# findproject, classify_warning 33# dump_html 34# 35# New dynamic HTML page's static JavaScript data: 36# Some data are copied from Python to JavaScript, to generate HTML elements. 37# FlagPlatform flags.platform 38# FlagURL flags.url, used by 'android' 39# FlagSeparator flags.separator, used by 'android' 40# SeverityColors: list of colors for all severity levels 41# SeverityHeaders: list of headers for all severity levels 42# SeverityColumnHeaders: list of column_headers for all severity levels 43# ProjectNames: project_names, or project_list[*][0] 44# WarnPatternsSeverity: warn_patterns[*]['severity'] 45# WarnPatternsDescription: warn_patterns[*]['description'] 46# WarningMessages: warning_messages 47# Warnings: warning_records 48# StatsHeader: warning count table header row 49# StatsRows: array of warning count table rows 50# 51# New dynamic HTML page's dynamic JavaScript data: 52# 53# New dynamic HTML related function to emit data: 54# escape_string, strip_escape_string, emit_warning_arrays 55# emit_js_data(): 56 57from __future__ import print_function 58import csv 59import datetime 60import html 61import sys 62 63# pylint:disable=relative-beyond-top-level 64from .severity import Severity 65 66 67# Report files with this number of warnings or more. 68LIMIT_WARNINGS_PER_FILE = 100 69# Report files/directories with this percentage of total warnings or more. 70LIMIT_PERCENT_WARNINGS = 1 71 72HTML_HEAD_SCRIPTS = """\ 73 <script type="text/javascript"> 74 function expand(id) { 75 var e = document.getElementById(id); 76 var f = document.getElementById(id + "_mark"); 77 if (e.style.display == 'block') { 78 e.style.display = 'none'; 79 f.innerHTML = '⊕'; 80 } 81 else { 82 e.style.display = 'block'; 83 f.innerHTML = '⊖'; 84 } 85 }; 86 function expandCollapse(show) { 87 for (var id = 1; ; id++) { 88 var e = document.getElementById(id + ""); 89 var f = document.getElementById(id + "_mark"); 90 if (!e || !f) break; 91 e.style.display = (show ? 'block' : 'none'); 92 f.innerHTML = (show ? '⊖' : '⊕'); 93 } 94 }; 95 </script> 96 <style type="text/css"> 97 th,td{border-collapse:collapse; border:1px solid black;} 98 .button{color:blue;font-size:100%;font-weight:bolder;} 99 .bt{color:black;background-color:transparent;border:none;outline:none; 100 font-size:140%;font-weight:bolder;} 101 .c0{background-color:#e0e0e0;} 102 .c1{background-color:#d0d0d0;} 103 .t1{border-collapse:collapse; width:100%; border:1px solid black;} 104 .box{margin:5pt; padding:5pt; border:1px solid;} 105 </style> 106 <script src="https://www.gstatic.com/charts/loader.js"></script> 107""" 108 109 110def make_writer(output_stream): 111 112 def writer(text): 113 return output_stream.write(text + '\n') 114 115 return writer 116 117 118def html_big(param): 119 return '<font size="+2">' + param + '</font>' 120 121 122def dump_html_prologue(title, writer, warn_patterns, project_names): 123 writer('<html>\n<head>') 124 writer('<title>' + title + '</title>') 125 writer(HTML_HEAD_SCRIPTS) 126 emit_stats_by_project(writer, warn_patterns, project_names) 127 writer('</head>\n<body>') 128 writer(html_big(title)) 129 writer('<p>') 130 131 132def dump_html_epilogue(writer): 133 writer('</body>\n</head>\n</html>') 134 135 136def sort_warnings(warn_patterns): 137 for i in warn_patterns: 138 i['members'] = sorted(set(i['members'])) 139 140 141def create_warnings(warn_patterns, project_names): 142 """Creates warnings s.t. 143 144 warnings[p][s] is as specified in above docs. 145 146 Args: 147 warn_patterns: list of warning patterns for specified platform 148 project_names: list of project names 149 150 Returns: 151 2D warnings array where warnings[p][s] is # of warnings in project name p of 152 severity level s 153 """ 154 warnings = {p: {s.value: 0 for s in Severity.levels} for p in project_names} 155 for pattern in warn_patterns: 156 value = pattern['severity'].value 157 for project in pattern['projects']: 158 warnings[project][value] += pattern['projects'][project] 159 return warnings 160 161 162def get_total_by_project(warnings, project_names): 163 """Returns dict, project as key and # warnings for that project as value.""" 164 return { 165 p: sum(warnings[p][s.value] for s in Severity.levels) 166 for p in project_names 167 } 168 169 170def get_total_by_severity(warnings, project_names): 171 """Returns dict, severity as key and # warnings of that severity as value.""" 172 return { 173 s.value: sum(warnings[p][s.value] for p in project_names) 174 for s in Severity.levels 175 } 176 177 178def emit_table_header(total_by_severity): 179 """Returns list of HTML-formatted content for severity stats.""" 180 181 stats_header = ['Project'] 182 for severity in Severity.levels: 183 if total_by_severity[severity.value]: 184 stats_header.append( 185 '<span style=\'background-color:{}\'>{}</span>'.format( 186 severity.color, severity.column_header)) 187 stats_header.append('TOTAL') 188 return stats_header 189 190 191def emit_row_counts_per_project(warnings, total_by_project, total_by_severity, 192 project_names): 193 """Returns total project warnings and row of stats for each project. 194 195 Args: 196 warnings: output of create_warnings(warn_patterns, project_names) 197 total_by_project: output of get_total_by_project(project_names) 198 total_by_severity: output of get_total_by_severity(project_names) 199 project_names: list of project names 200 201 Returns: 202 total_all_projects, the total number of warnings over all projects 203 stats_rows, a 2d list where each row is [Project Name, <severity counts>, 204 total # warnings for this project] 205 """ 206 207 total_all_projects = 0 208 stats_rows = [] 209 for p_name in project_names: 210 if total_by_project[p_name]: 211 one_row = [p_name] 212 for severity in Severity.levels: 213 if total_by_severity[severity.value]: 214 one_row.append(warnings[p_name][severity.value]) 215 one_row.append(total_by_project[p_name]) 216 stats_rows.append(one_row) 217 total_all_projects += total_by_project[p_name] 218 return total_all_projects, stats_rows 219 220 221def emit_row_counts_per_severity(total_by_severity, stats_header, stats_rows, 222 total_all_projects, writer): 223 """Emits stats_header and stats_rows as specified above. 224 225 Args: 226 total_by_severity: output of get_total_by_severity() 227 stats_header: output of emit_table_header() 228 stats_rows: output of emit_row_counts_per_project() 229 total_all_projects: output of emit_row_counts_per_project() 230 writer: writer returned by make_writer(output_stream) 231 """ 232 233 total_all_severities = 0 234 one_row = ['<b>TOTAL</b>'] 235 for severity in Severity.levels: 236 if total_by_severity[severity.value]: 237 one_row.append(total_by_severity[severity.value]) 238 total_all_severities += total_by_severity[severity.value] 239 one_row.append(total_all_projects) 240 stats_rows.append(one_row) 241 writer('<script>') 242 emit_const_string_array('StatsHeader', stats_header, writer) 243 emit_const_object_array('StatsRows', stats_rows, writer) 244 writer(DRAW_TABLE_JAVASCRIPT) 245 writer('</script>') 246 247 248def emit_stats_by_project(writer, warn_patterns, project_names): 249 """Dump a google chart table of warnings per project and severity.""" 250 251 warnings = create_warnings(warn_patterns, project_names) 252 total_by_project = get_total_by_project(warnings, project_names) 253 total_by_severity = get_total_by_severity(warnings, project_names) 254 stats_header = emit_table_header(total_by_severity) 255 total_all_projects, stats_rows = emit_row_counts_per_project( 256 warnings, total_by_project, total_by_severity, project_names) 257 emit_row_counts_per_severity(total_by_severity, stats_header, stats_rows, 258 total_all_projects, writer) 259 260 261def dump_stats(writer, warn_patterns): 262 """Dump some stats about total number of warnings and date.""" 263 264 known = 0 265 skipped = 0 266 unknown = 0 267 sort_warnings(warn_patterns) 268 for i in warn_patterns: 269 if i['severity'] == Severity.UNMATCHED: 270 unknown += len(i['members']) 271 elif i['severity'] == Severity.SKIP: 272 skipped += len(i['members']) 273 else: 274 known += len(i['members']) 275 writer('Number of classified warnings: <b>' + str(known) + '</b><br>') 276 writer('Number of skipped warnings: <b>' + str(skipped) + '</b><br>') 277 writer('Number of unclassified warnings: <b>' + str(unknown) + '</b><br>') 278 total = unknown + known + skipped 279 extra_msg = '' 280 if total < 1000: 281 extra_msg = ' (low count may indicate incremental build)' 282 writer('Total number of warnings: <b>' + str(total) + '</b>' + extra_msg) 283 date_time_str = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S') 284 writer('<p>(generated on ' + date_time_str + ')') 285 286 287# New base table of warnings, [severity, warn_id, project, warning_message] 288# Need buttons to show warnings in different grouping options. 289# (1) Current, group by severity, id for each warning pattern 290# sort by severity, warn_id, warning_message 291# (2) Current --byproject, group by severity, 292# id for each warning pattern + project name 293# sort by severity, warn_id, project, warning_message 294# (3) New, group by project + severity, 295# id for each warning pattern 296# sort by project, severity, warn_id, warning_message 297def emit_buttons(writer): 298 """Write the button elements in HTML.""" 299 writer('<p><button class="button" onclick="expandCollapse(1);">' 300 'Expand all warnings</button>\n' 301 '<button class="button" onclick="expandCollapse(0);">' 302 'Collapse all warnings</button>\n' 303 '<p><button class="button" onclick="groupBySeverity();">' 304 'Group warnings by severity</button>\n' 305 '<button class="button" onclick="groupByProject();">' 306 'Group warnings by project</button>') 307 308 309def all_patterns(category): 310 patterns = '' 311 for i in category['patterns']: 312 patterns += i 313 patterns += ' / ' 314 return patterns 315 316 317def dump_fixed(writer, warn_patterns): 318 """Show which warnings no longer occur.""" 319 anchor = 'fixed_warnings' 320 mark = anchor + '_mark' 321 writer('\n<br><p style="background-color:lightblue"><b>' 322 '<button id="' + mark + '" ' 323 'class="bt" onclick="expand(\'' + anchor + '\');">' 324 '⊕</button> Fixed warnings. ' 325 'No more occurrences. Please consider turning these into ' 326 'errors if possible, before they are reintroduced in to the build' 327 ':</b></p>') 328 writer('<blockquote>') 329 fixed_patterns = [] 330 for i in warn_patterns: 331 if not i['members']: 332 fixed_patterns.append(i['description'] + ' (' + all_patterns(i) + ')') 333 fixed_patterns = sorted(fixed_patterns) 334 writer('<div id="' + anchor + '" style="display:none;"><table>') 335 cur_row_class = 0 336 for text in fixed_patterns: 337 cur_row_class = 1 - cur_row_class 338 # remove last '\n' 339 out_text = text[:-1] if text[-1] == '\n' else text 340 writer('<tr><td class="c' + str(cur_row_class) + '">' 341 + out_text + '</td></tr>') 342 writer('</table></div>') 343 writer('</blockquote>') 344 345 346def write_severity(csvwriter, sev, kind, warn_patterns): 347 """Count warnings of given severity and write CSV entries to writer.""" 348 total = 0 349 for pattern in warn_patterns: 350 if pattern['severity'] == sev and pattern['members']: 351 num_members = len(pattern['members']) 352 total += num_members 353 warning = kind + ': ' + (pattern['description'] or '?') 354 csvwriter.writerow([num_members, '', warning]) 355 # print number of warnings for each project, ordered by project name 356 projects = sorted(pattern['projects'].keys()) 357 for project in projects: 358 csvwriter.writerow([pattern['projects'][project], project, warning]) 359 csvwriter.writerow([total, '', kind + ' warnings']) 360 return total 361 362 363def dump_csv(csvwriter, warn_patterns): 364 """Dump number of warnings in CSV format to writer.""" 365 sort_warnings(warn_patterns) 366 total = 0 367 for severity in Severity.levels: 368 total += write_severity( 369 csvwriter, severity, severity.column_header, warn_patterns) 370 csvwriter.writerow([total, '', 'All warnings']) 371 372 373def dump_csv_with_description(csvwriter, warning_records, warning_messages, 374 warn_patterns, project_names): 375 """Outputs all the warning messages by project.""" 376 csv_output = [] 377 for record in warning_records: 378 project_name = project_names[record[1]] 379 pattern = warn_patterns[record[0]] 380 severity = pattern['severity'].header 381 category = pattern['category'] 382 description = pattern['description'] 383 warning = warning_messages[record[2]] 384 csv_output.append([project_name, severity, 385 category, description, 386 warning]) 387 csv_output = sorted(csv_output) 388 for output in csv_output: 389 csvwriter.writerow(output) 390 391 392# Return line with escaped backslash and quotation characters. 393def escape_string(line): 394 return line.replace('\\', '\\\\').replace('"', '\\"') 395 396 397# Return line without trailing '\n' and escape the quotation characters. 398def strip_escape_string(line): 399 if not line: 400 return line 401 line = line[:-1] if line[-1] == '\n' else line 402 return escape_string(line) 403 404 405def emit_warning_array(name, writer, warn_patterns): 406 writer('var warning_{} = ['.format(name)) 407 for pattern in warn_patterns: 408 if name == 'severity': 409 writer('{},'.format(pattern[name].value)) 410 else: 411 writer('{},'.format(pattern[name])) 412 writer('];') 413 414 415def emit_warning_arrays(writer, warn_patterns): 416 emit_warning_array('severity', writer, warn_patterns) 417 writer('var warning_description = [') 418 for pattern in warn_patterns: 419 if pattern['members']: 420 writer('"{}",'.format(escape_string(pattern['description']))) 421 else: 422 writer('"",') # no such warning 423 writer('];') 424 425 426SCRIPTS_FOR_WARNING_GROUPS = """ 427 function compareMessages(x1, x2) { // of the same warning type 428 return (WarningMessages[x1[2]] <= WarningMessages[x2[2]]) ? -1 : 1; 429 } 430 function byMessageCount(x1, x2) { 431 return x2[2] - x1[2]; // reversed order 432 } 433 function bySeverityMessageCount(x1, x2) { 434 // orer by severity first 435 if (x1[1] != x2[1]) 436 return x1[1] - x2[1]; 437 return byMessageCount(x1, x2); 438 } 439 const ParseLinePattern = /^([^ :]+):(\\d+):(.+)/; 440 function addURL(line) { // used by Android 441 if (FlagURL == "") return line; 442 if (FlagSeparator == "") { 443 return line.replace(ParseLinePattern, 444 "<a target='_blank' href='" + FlagURL + "/$1'>$1</a>:$2:$3"); 445 } 446 return line.replace(ParseLinePattern, 447 "<a target='_blank' href='" + FlagURL + "/$1" + FlagSeparator + 448 "$2'>$1:$2</a>:$3"); 449 } 450 function addURLToLine(line, link) { // used by Chrome 451 let line_split = line.split(":"); 452 let path = line_split.slice(0,3).join(":"); 453 let msg = line_split.slice(3).join(":"); 454 let html_link = `<a target="_blank" href="${link}">${path}</a>${msg}`; 455 return html_link; 456 } 457 function createArrayOfDictionaries(n) { 458 var result = []; 459 for (var i=0; i<n; i++) result.push({}); 460 return result; 461 } 462 function groupWarningsBySeverity() { 463 // groups is an array of dictionaries, 464 // each dictionary maps from warning type to array of warning messages. 465 var groups = createArrayOfDictionaries(SeverityColors.length); 466 for (var i=0; i<Warnings.length; i++) { 467 var w = Warnings[i][0]; 468 var s = WarnPatternsSeverity[w]; 469 var k = w.toString(); 470 if (!(k in groups[s])) 471 groups[s][k] = []; 472 groups[s][k].push(Warnings[i]); 473 } 474 return groups; 475 } 476 function groupWarningsByProject() { 477 var groups = createArrayOfDictionaries(ProjectNames.length); 478 for (var i=0; i<Warnings.length; i++) { 479 var w = Warnings[i][0]; 480 var p = Warnings[i][1]; 481 var k = w.toString(); 482 if (!(k in groups[p])) 483 groups[p][k] = []; 484 groups[p][k].push(Warnings[i]); 485 } 486 return groups; 487 } 488 var GlobalAnchor = 0; 489 function createWarningSection(header, color, group) { 490 var result = ""; 491 var groupKeys = []; 492 var totalMessages = 0; 493 for (var k in group) { 494 totalMessages += group[k].length; 495 groupKeys.push([k, WarnPatternsSeverity[parseInt(k)], group[k].length]); 496 } 497 groupKeys.sort(bySeverityMessageCount); 498 for (var idx=0; idx<groupKeys.length; idx++) { 499 var k = groupKeys[idx][0]; 500 var messages = group[k]; 501 var w = parseInt(k); 502 var wcolor = SeverityColors[WarnPatternsSeverity[w]]; 503 var description = WarnPatternsDescription[w]; 504 if (description.length == 0) 505 description = "???"; 506 GlobalAnchor += 1; 507 result += "<table class='t1'><tr bgcolor='" + wcolor + "'><td>" + 508 "<button class='bt' id='" + GlobalAnchor + "_mark" + 509 "' onclick='expand(\\"" + GlobalAnchor + "\\");'>" + 510 "⊕</button> " + 511 description + " (" + messages.length + ")</td></tr></table>"; 512 result += "<div id='" + GlobalAnchor + 513 "' style='display:none;'><table class='t1'>"; 514 var c = 0; 515 messages.sort(compareMessages); 516 if (FlagPlatform == "chrome") { 517 for (var i=0; i<messages.length; i++) { 518 result += "<tr><td class='c" + c + "'>" + 519 addURLToLine(WarningMessages[messages[i][2]], WarningLinks[messages[i][3]]) + "</td></tr>"; 520 c = 1 - c; 521 } 522 } else { 523 for (var i=0; i<messages.length; i++) { 524 result += "<tr><td class='c" + c + "'>" + 525 addURL(WarningMessages[messages[i][2]]) + "</td></tr>"; 526 c = 1 - c; 527 } 528 } 529 result += "</table></div>"; 530 } 531 if (result.length > 0) { 532 return "<br><span style='background-color:" + color + "'><b>" + 533 header + ": " + totalMessages + 534 "</b></span><blockquote><table class='t1'>" + 535 result + "</table></blockquote>"; 536 537 } 538 return ""; // empty section 539 } 540 function generateSectionsBySeverity() { 541 var result = ""; 542 var groups = groupWarningsBySeverity(); 543 for (s=0; s<SeverityColors.length; s++) { 544 result += createWarningSection(SeverityHeaders[s], SeverityColors[s], 545 groups[s]); 546 } 547 return result; 548 } 549 function generateSectionsByProject() { 550 var result = ""; 551 var groups = groupWarningsByProject(); 552 for (i=0; i<groups.length; i++) { 553 result += createWarningSection(ProjectNames[i], 'lightgrey', groups[i]); 554 } 555 return result; 556 } 557 function groupWarnings(generator) { 558 GlobalAnchor = 0; 559 var e = document.getElementById("warning_groups"); 560 e.innerHTML = generator(); 561 } 562 function groupBySeverity() { 563 groupWarnings(generateSectionsBySeverity); 564 } 565 function groupByProject() { 566 groupWarnings(generateSectionsByProject); 567 } 568""" 569 570 571# Emit a JavaScript const number 572def emit_const_number(name, value, writer): 573 writer('const ' + name + ' = ' + str(value) + ';') 574 575 576# Emit a JavaScript const string 577def emit_const_string(name, value, writer): 578 writer('const ' + name + ' = "' + escape_string(value) + '";') 579 580 581# Emit a JavaScript const integer array. 582def emit_const_int_array(name, array, writer): 583 writer('const ' + name + ' = [') 584 for item in array: 585 writer(str(item) + ',') 586 writer('];') 587 588 589# Emit a JavaScript const string array. 590def emit_const_string_array(name, array, writer): 591 writer('const ' + name + ' = [') 592 for item in array: 593 writer('"' + strip_escape_string(item) + '",') 594 writer('];') 595 596 597# Emit a JavaScript const string array for HTML. 598def emit_const_html_string_array(name, array, writer): 599 writer('const ' + name + ' = [') 600 for item in array: 601 writer('"' + html.escape(strip_escape_string(item)) + '",') 602 writer('];') 603 604 605# Emit a JavaScript const object array. 606def emit_const_object_array(name, array, writer): 607 writer('const ' + name + ' = [') 608 for item in array: 609 writer(str(item) + ',') 610 writer('];') 611 612 613def emit_js_data(writer, flags, warning_messages, warning_links, 614 warning_records, warn_patterns, project_names): 615 """Dump dynamic HTML page's static JavaScript data.""" 616 emit_const_string('FlagPlatform', flags.platform, writer) 617 emit_const_string('FlagURL', flags.url, writer) 618 emit_const_string('FlagSeparator', flags.separator, writer) 619 emit_const_number('LimitWarningsPerFile', LIMIT_WARNINGS_PER_FILE, writer) 620 emit_const_number('LimitPercentWarnings', LIMIT_PERCENT_WARNINGS, writer) 621 emit_const_string_array('SeverityColors', [s.color for s in Severity.levels], 622 writer) 623 emit_const_string_array('SeverityHeaders', 624 [s.header for s in Severity.levels], writer) 625 emit_const_string_array('SeverityColumnHeaders', 626 [s.column_header for s in Severity.levels], writer) 627 emit_const_string_array('ProjectNames', project_names, writer) 628 # pytype: disable=attribute-error 629 emit_const_int_array('WarnPatternsSeverity', 630 [w['severity'].value for w in warn_patterns], writer) 631 # pytype: enable=attribute-error 632 emit_const_html_string_array('WarnPatternsDescription', 633 [w['description'] for w in warn_patterns], 634 writer) 635 emit_const_html_string_array('WarningMessages', warning_messages, writer) 636 emit_const_object_array('Warnings', warning_records, writer) 637 if flags.platform == 'chrome': 638 emit_const_html_string_array('WarningLinks', warning_links, writer) 639 640 641DRAW_TABLE_JAVASCRIPT = """ 642google.charts.load('current', {'packages':['table']}); 643google.charts.setOnLoadCallback(genTables); 644function genSelectedProjectsTable() { 645 var data = new google.visualization.DataTable(); 646 data.addColumn('string', StatsHeader[0]); 647 for (var i=1; i<StatsHeader.length; i++) { 648 data.addColumn('number', StatsHeader[i]); 649 } 650 data.addRows(StatsRows); 651 for (var i=0; i<StatsRows.length; i++) { 652 for (var j=0; j<StatsHeader.length; j++) { 653 data.setProperty(i, j, 'style', 'border:1px solid black;'); 654 } 655 } 656 var table = new google.visualization.Table( 657 document.getElementById('selected_projects_section')); 658 table.draw(data, {allowHtml: true, alternatingRowStyle: true}); 659} 660// Global TopDirs and TopFiles are computed later by genTables. 661window.TopDirs = []; 662window.TopFiles = []; 663function computeTopDirsFiles() { 664 var numWarnings = WarningMessages.length; 665 var warningsOfFiles = {}; 666 var warningsOfDirs = {}; 667 var subDirs = {}; 668 function addOneWarning(map, key, type, unique) { 669 function increaseCounter(idx) { 670 map[idx] = 1 + ((idx in map) ? map[idx] : 0); 671 } 672 increaseCounter(key) 673 if (type != "") { 674 increaseCounter(type + " " + key) 675 if (unique) { 676 increaseCounter(type + " *") 677 } 678 } 679 } 680 for (var i = 0; i < numWarnings; i++) { 681 var message = WarningMessages[i] 682 var file = message.replace(/:.*/, ""); 683 var warningType = message.endsWith("]") ? message.replace(/.*\[/, "[") : ""; 684 addOneWarning(warningsOfFiles, file, warningType, true); 685 var dirs = file.split("/"); 686 var dir = dirs[0]; 687 addOneWarning(warningsOfDirs, dir, warningType, true); 688 for (var d = 1; d < dirs.length - 1; d++) { 689 var subDir = dir + "/" + dirs[d]; 690 if (!(dir in subDirs)) { 691 subDirs[dir] = {}; 692 } 693 subDirs[dir][subDir] = 1; 694 dir = subDir; 695 addOneWarning(warningsOfDirs, dir, warningType, false); 696 } 697 } 698 var minDirWarnings = numWarnings*(LimitPercentWarnings/100); 699 var minFileWarnings = Math.min(LimitWarningsPerFile, minDirWarnings); 700 // Each row in TopDirs and TopFiles has 701 // [index, {v:<num_of_warnings>, f:<percent>}, file_or_dir_name] 702 function countWarnings(minWarnings, warningsOf, isDir) { 703 var rows = []; 704 for (var name in warningsOf) { 705 if (isDir && name in subDirs && Object.keys(subDirs[name]).length < 2) { 706 continue; // skip a directory if it has only one subdir 707 } 708 var count = warningsOf[name]; 709 if (count >= minWarnings) { 710 name = isDir ? (name + "/...") : name; 711 var percent = (100*count/numWarnings).toFixed(1); 712 var countFormat = count + ' (' + percent + '%)'; 713 rows.push([0, {v:count, f:countFormat}, name]); 714 } 715 } 716 rows.sort((a,b) => b[1].v - a[1].v); 717 for (var i=0; i<rows.length; i++) { 718 rows[i][0] = i; 719 } 720 return rows; 721 } 722 TopDirs = countWarnings(minDirWarnings, warningsOfDirs, true); 723 TopFiles = countWarnings(minFileWarnings, warningsOfFiles, false); 724} 725function genTopDirsFilesTables() { 726 computeTopDirsFiles(); 727 function addTable(name, divName, rows, clickFunction) { 728 var data = new google.visualization.DataTable(); 729 data.addColumn("number", "index"); // not shown in view 730 data.addColumn("number", "# of warnings"); 731 data.addColumn("string", name); 732 data.addRows(rows); 733 var formatter = new google.visualization.PatternFormat( 734 '<p onclick="' + clickFunction + '({0})">{2}</p>'); 735 formatter.format(data, [0, 1, 2], 2); 736 var view = new google.visualization.DataView(data); 737 view.setColumns([1,2]); // hide the index column 738 var table = new google.visualization.Table( 739 document.getElementById(divName)); 740 table.draw(view, {allowHtml: true, alternatingRowStyle: true}); 741 } 742 addTable("[Warning Type] Directory", "top_dirs_table", TopDirs, "selectDir"); 743 addTable("[Warning Type] File", "top_files_table", TopFiles, "selectFile"); 744} 745function selectDirFile(idx, rows, dirFile) { 746 if (rows.length <= idx) { 747 return; 748 } 749 var name = rows[idx][2]; 750 var type = ""; 751 if (name.startsWith("[")) { 752 type = " " + name.replace(/ .*/, ""); 753 name = name.replace(/.* /, ""); 754 } 755 var spanName = "selected_" + dirFile + "_name"; 756 document.getElementById(spanName).innerHTML = name + type; 757 var divName = "selected_" + dirFile + "_warnings"; 758 var numWarnings = rows[idx][1].v; 759 var prefix = name.replace(/\\.\\.\\.$/, ""); 760 var data = new google.visualization.DataTable(); 761 data.addColumn('string', numWarnings + type + ' warnings in ' + name); 762 var getWarningMessage = (FlagPlatform == "chrome") 763 ? ((x) => addURLToLine(WarningMessages[Warnings[x][2]], 764 WarningLinks[Warnings[x][3]])) 765 : ((x) => addURL(WarningMessages[Warnings[x][2]])); 766 for (var i = 0; i < Warnings.length; i++) { 767 if ((prefix.startsWith("*") || WarningMessages[Warnings[i][2]].startsWith(prefix)) && 768 (type == "" || WarningMessages[Warnings[i][2]].endsWith(type))) { 769 data.addRow([getWarningMessage(i)]); 770 } 771 } 772 var table = new google.visualization.Table( 773 document.getElementById(divName)); 774 table.draw(data, {allowHtml: true, alternatingRowStyle: true}); 775} 776function selectDir(idx) { 777 selectDirFile(idx, TopDirs, "directory") 778} 779function selectFile(idx) { 780 selectDirFile(idx, TopFiles, "file"); 781} 782function genTables() { 783 genSelectedProjectsTable(); 784 if (WarningMessages.length > 1) { 785 genTopDirsFilesTables(); 786 } 787} 788""" 789 790 791def dump_boxed_section(writer, func): 792 writer('<div class="box">') 793 func() 794 writer('</div>') 795 796 797def dump_section_header(writer, table_name, section_title): 798 writer('<h3><b><button id="' + table_name + '_mark" class="bt"\n' + 799 ' onclick="expand(\'' + table_name + '\');">⊕</button></b>\n' + 800 section_title + '</h3>') 801 802 803def dump_table_section(writer, table_name, section_title): 804 dump_section_header(writer, table_name, section_title) 805 writer('<div id="' + table_name + '" style="display:none;"></div>') 806 807 808def dump_dir_file_section(writer, dir_file, table_name, section_title): 809 section_name = 'top_' + dir_file + '_section' 810 dump_section_header(writer, section_name, section_title) 811 writer('<div id="' + section_name + '" style="display:none;">') 812 writer('<div id="' + table_name + '"></div>') 813 def subsection(): 814 subsection_name = 'selected_' + dir_file + '_warnings' 815 subsection_title = ('Warnings in <span id="selected_' + dir_file + 816 '_name">(click a ' + dir_file + 817 ' in the above table)</span>') 818 dump_section_header(writer, subsection_name, subsection_title) 819 writer('<div id="' + subsection_name + '" style="display:none;"></div>') 820 dump_boxed_section(writer, subsection) 821 writer('</div>') 822 823 824# HTML output has the following major div elements: 825# selected_projects_section 826# top_directory_section 827# top_dirs_table 828# selected_directory_warnings 829# top_file_section 830# top_files_table 831# selected_file_warnings 832# all_warnings_section 833# warning_groups 834# fixed_warnings 835def dump_html(flags, output_stream, warning_messages, warning_links, 836 warning_records, header_str, warn_patterns, project_names): 837 """Dump the flags output to output_stream.""" 838 writer = make_writer(output_stream) 839 dump_html_prologue('Warnings for ' + header_str, writer, warn_patterns, 840 project_names) 841 dump_stats(writer, warn_patterns) 842 writer('<br><br>Press ⊕ to show section content,' 843 ' and ⊖ to hide the content.') 844 def section1(): 845 dump_table_section(writer, 'selected_projects_section', 846 'Number of warnings in preselected project directories') 847 def section2(): 848 dump_dir_file_section( 849 writer, 'directory', 'top_dirs_table', 850 'Directories/Warnings with at least ' + 851 str(LIMIT_PERCENT_WARNINGS) + '% of all cases') 852 def section3(): 853 dump_dir_file_section( 854 writer, 'file', 'top_files_table', 855 'Files/Warnings with at least ' + 856 str(LIMIT_PERCENT_WARNINGS) + '% of all or ' + 857 str(LIMIT_WARNINGS_PER_FILE) + ' cases') 858 def section4(): 859 writer('<script>') 860 emit_js_data(writer, flags, warning_messages, warning_links, 861 warning_records, warn_patterns, project_names) 862 writer(SCRIPTS_FOR_WARNING_GROUPS) 863 writer('</script>') 864 dump_section_header(writer, 'all_warnings_section', 865 'All warnings grouped by severities or projects') 866 writer('<div id="all_warnings_section" style="display:none;">') 867 emit_buttons(writer) 868 # Warning messages are grouped by severities or project names. 869 writer('<br><div id="warning_groups"></div>') 870 if flags.byproject: 871 writer('<script>groupByProject();</script>') 872 else: 873 writer('<script>groupBySeverity();</script>') 874 dump_fixed(writer, warn_patterns) 875 writer('</div>') 876 dump_boxed_section(writer, section1) 877 dump_boxed_section(writer, section2) 878 dump_boxed_section(writer, section3) 879 dump_boxed_section(writer, section4) 880 dump_html_epilogue(writer) 881 882 883def write_html(flags, project_names, warn_patterns, html_path, warning_messages, 884 warning_links, warning_records, header_str): 885 """Write warnings html file.""" 886 if html_path: 887 with open(html_path, 'w') as outf: 888 dump_html(flags, outf, warning_messages, warning_links, warning_records, 889 header_str, warn_patterns, project_names) 890 891 892def write_out_csv(flags, warn_patterns, warning_messages, warning_links, 893 warning_records, header_str, project_names): 894 """Write warnings csv file.""" 895 if flags.csvpath: 896 with open(flags.csvpath, 'w') as outf: 897 dump_csv(csv.writer(outf, lineterminator='\n'), warn_patterns) 898 899 if flags.csvwithdescription: 900 with open(flags.csvwithdescription, 'w') as outf: 901 dump_csv_with_description(csv.writer(outf, lineterminator='\n'), 902 warning_records, warning_messages, 903 warn_patterns, project_names) 904 905 if flags.gencsv: 906 dump_csv(csv.writer(sys.stdout, lineterminator='\n'), warn_patterns) 907 else: 908 dump_html(flags, sys.stdout, warning_messages, warning_links, 909 warning_records, header_str, warn_patterns, project_names) 910