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