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 66HTML_HEAD_SCRIPTS = """\ 67 <script type="text/javascript"> 68 function expand(id) { 69 var e = document.getElementById(id); 70 var f = document.getElementById(id + "_mark"); 71 if (e.style.display == 'block') { 72 e.style.display = 'none'; 73 f.innerHTML = '⊕'; 74 } 75 else { 76 e.style.display = 'block'; 77 f.innerHTML = '⊖'; 78 } 79 }; 80 function expandCollapse(show) { 81 for (var id = 1; ; id++) { 82 var e = document.getElementById(id + ""); 83 var f = document.getElementById(id + "_mark"); 84 if (!e || !f) break; 85 e.style.display = (show ? 'block' : 'none'); 86 f.innerHTML = (show ? '⊖' : '⊕'); 87 } 88 }; 89 </script> 90 <style type="text/css"> 91 th,td{border-collapse:collapse; border:1px solid black;} 92 .button{color:blue;font-size:110%;font-weight:bolder;} 93 .bt{color:black;background-color:transparent;border:none;outline:none; 94 font-size:140%;font-weight:bolder;} 95 .c0{background-color:#e0e0e0;} 96 .c1{background-color:#d0d0d0;} 97 .t1{border-collapse:collapse; width:100%; border:1px solid black;} 98 </style> 99 <script src="https://www.gstatic.com/charts/loader.js"></script> 100""" 101 102 103def make_writer(output_stream): 104 105 def writer(text): 106 return output_stream.write(text + '\n') 107 108 return writer 109 110 111def html_big(param): 112 return '<font size="+2">' + param + '</font>' 113 114 115def dump_html_prologue(title, writer, warn_patterns, project_names): 116 writer('<html>\n<head>') 117 writer('<title>' + title + '</title>') 118 writer(HTML_HEAD_SCRIPTS) 119 emit_stats_by_project(writer, warn_patterns, project_names) 120 writer('</head>\n<body>') 121 writer(html_big(title)) 122 writer('<p>') 123 124 125def dump_html_epilogue(writer): 126 writer('</body>\n</head>\n</html>') 127 128 129def sort_warnings(warn_patterns): 130 for i in warn_patterns: 131 i['members'] = sorted(set(i['members'])) 132 133 134def create_warnings(warn_patterns, project_names): 135 """Creates warnings s.t. 136 137 warnings[p][s] is as specified in above docs. 138 139 Args: 140 warn_patterns: list of warning patterns for specified platform 141 project_names: list of project names 142 143 Returns: 144 2D warnings array where warnings[p][s] is # of warnings in project name p of 145 severity level s 146 """ 147 warnings = {p: {s.value: 0 for s in Severity.levels} for p in project_names} 148 for pattern in warn_patterns: 149 value = pattern['severity'].value 150 for project in pattern['projects']: 151 warnings[project][value] += pattern['projects'][project] 152 return warnings 153 154 155def get_total_by_project(warnings, project_names): 156 """Returns dict, project as key and # warnings for that project as value.""" 157 return { 158 p: sum(warnings[p][s.value] for s in Severity.levels) 159 for p in project_names 160 } 161 162 163def get_total_by_severity(warnings, project_names): 164 """Returns dict, severity as key and # warnings of that severity as value.""" 165 return { 166 s.value: sum(warnings[p][s.value] for p in project_names) 167 for s in Severity.levels 168 } 169 170 171def emit_table_header(total_by_severity): 172 """Returns list of HTML-formatted content for severity stats.""" 173 174 stats_header = ['Project'] 175 for severity in Severity.levels: 176 if total_by_severity[severity.value]: 177 stats_header.append( 178 '<span style=\'background-color:{}\'>{}</span>'.format( 179 severity.color, severity.column_header)) 180 stats_header.append('TOTAL') 181 return stats_header 182 183 184def emit_row_counts_per_project(warnings, total_by_project, total_by_severity, 185 project_names): 186 """Returns total project warnings and row of stats for each project. 187 188 Args: 189 warnings: output of create_warnings(warn_patterns, project_names) 190 total_by_project: output of get_total_by_project(project_names) 191 total_by_severity: output of get_total_by_severity(project_names) 192 project_names: list of project names 193 194 Returns: 195 total_all_projects, the total number of warnings over all projects 196 stats_rows, a 2d list where each row is [Project Name, <severity counts>, 197 total # warnings for this project] 198 """ 199 200 total_all_projects = 0 201 stats_rows = [] 202 for p_name in project_names: 203 if total_by_project[p_name]: 204 one_row = [p_name] 205 for severity in Severity.levels: 206 if total_by_severity[severity.value]: 207 one_row.append(warnings[p_name][severity.value]) 208 one_row.append(total_by_project[p_name]) 209 stats_rows.append(one_row) 210 total_all_projects += total_by_project[p_name] 211 return total_all_projects, stats_rows 212 213 214def emit_row_counts_per_severity(total_by_severity, stats_header, stats_rows, 215 total_all_projects, writer): 216 """Emits stats_header and stats_rows as specified above. 217 218 Args: 219 total_by_severity: output of get_total_by_severity() 220 stats_header: output of emit_table_header() 221 stats_rows: output of emit_row_counts_per_project() 222 total_all_projects: output of emit_row_counts_per_project() 223 writer: writer returned by make_writer(output_stream) 224 """ 225 226 total_all_severities = 0 227 one_row = ['<b>TOTAL</b>'] 228 for severity in Severity.levels: 229 if total_by_severity[severity.value]: 230 one_row.append(total_by_severity[severity.value]) 231 total_all_severities += total_by_severity[severity.value] 232 one_row.append(total_all_projects) 233 stats_rows.append(one_row) 234 writer('<script>') 235 emit_const_string_array('StatsHeader', stats_header, writer) 236 emit_const_object_array('StatsRows', stats_rows, writer) 237 writer(DRAW_TABLE_JAVASCRIPT) 238 writer('</script>') 239 240 241def emit_stats_by_project(writer, warn_patterns, project_names): 242 """Dump a google chart table of warnings per project and severity.""" 243 244 warnings = create_warnings(warn_patterns, project_names) 245 total_by_project = get_total_by_project(warnings, project_names) 246 total_by_severity = get_total_by_severity(warnings, project_names) 247 stats_header = emit_table_header(total_by_severity) 248 total_all_projects, stats_rows = emit_row_counts_per_project( 249 warnings, total_by_project, total_by_severity, project_names) 250 emit_row_counts_per_severity(total_by_severity, stats_header, stats_rows, 251 total_all_projects, writer) 252 253 254def dump_stats(writer, warn_patterns): 255 """Dump some stats about total number of warnings and such.""" 256 257 known = 0 258 skipped = 0 259 unknown = 0 260 sort_warnings(warn_patterns) 261 for i in warn_patterns: 262 if i['severity'] == Severity.UNMATCHED: 263 unknown += len(i['members']) 264 elif i['severity'] == Severity.SKIP: 265 skipped += len(i['members']) 266 else: 267 known += len(i['members']) 268 writer('Number of classified warnings: <b>' + str(known) + '</b><br>') 269 writer('Number of skipped warnings: <b>' + str(skipped) + '</b><br>') 270 writer('Number of unclassified warnings: <b>' + str(unknown) + '</b><br>') 271 total = unknown + known + skipped 272 extra_msg = '' 273 if total < 1000: 274 extra_msg = ' (low count may indicate incremental build)' 275 writer('Total number of warnings: <b>' + str(total) + '</b>' + extra_msg) 276 277 278# New base table of warnings, [severity, warn_id, project, warning_message] 279# Need buttons to show warnings in different grouping options. 280# (1) Current, group by severity, id for each warning pattern 281# sort by severity, warn_id, warning_message 282# (2) Current --byproject, group by severity, 283# id for each warning pattern + project name 284# sort by severity, warn_id, project, warning_message 285# (3) New, group by project + severity, 286# id for each warning pattern 287# sort by project, severity, warn_id, warning_message 288def emit_buttons(writer): 289 """Write the button elements in HTML.""" 290 writer('<button class="button" onclick="expandCollapse(1);">' 291 'Expand all warnings</button>\n' 292 '<button class="button" onclick="expandCollapse(0);">' 293 'Collapse all warnings</button>\n' 294 '<button class="button" onclick="groupBySeverity();">' 295 'Group warnings by severity</button>\n' 296 '<button class="button" onclick="groupByProject();">' 297 'Group warnings by project</button><br>') 298 299 300def all_patterns(category): 301 patterns = '' 302 for i in category['patterns']: 303 patterns += i 304 patterns += ' / ' 305 return patterns 306 307 308def dump_fixed(writer, warn_patterns): 309 """Show which warnings no longer occur.""" 310 anchor = 'fixed_warnings' 311 mark = anchor + '_mark' 312 writer('\n<br><p style="background-color:lightblue"><b>' 313 '<button id="' + mark + '" ' 314 'class="bt" onclick="expand(\'' + anchor + '\');">' 315 '⊕</button> Fixed warnings. ' 316 'No more occurrences. Please consider turning these into ' 317 'errors if possible, before they are reintroduced in to the build' 318 ':</b></p>') 319 writer('<blockquote>') 320 fixed_patterns = [] 321 for i in warn_patterns: 322 if not i['members']: 323 fixed_patterns.append(i['description'] + ' (' + all_patterns(i) + ')') 324 fixed_patterns = sorted(fixed_patterns) 325 writer('<div id="' + anchor + '" style="display:none;"><table>') 326 cur_row_class = 0 327 for text in fixed_patterns: 328 cur_row_class = 1 - cur_row_class 329 # remove last '\n' 330 out_text = text[:-1] if text[-1] == '\n' else text 331 writer('<tr><td class="c' + str(cur_row_class) + '">' + out_text + '</td></tr>') 332 writer('</table></div>') 333 writer('</blockquote>') 334 335 336def write_severity(csvwriter, sev, kind, warn_patterns): 337 """Count warnings of given severity and write CSV entries to writer.""" 338 total = 0 339 for pattern in warn_patterns: 340 if pattern['severity'] == sev and pattern['members']: 341 num_members = len(pattern['members']) 342 total += num_members 343 warning = kind + ': ' + (pattern['description'] or '?') 344 csvwriter.writerow([num_members, '', warning]) 345 # print number of warnings for each project, ordered by project name 346 projects = sorted(pattern['projects'].keys()) 347 for project in projects: 348 csvwriter.writerow([pattern['projects'][project], project, warning]) 349 csvwriter.writerow([total, '', kind + ' warnings']) 350 return total 351 352 353def dump_csv(csvwriter, warn_patterns): 354 """Dump number of warnings in CSV format to writer.""" 355 sort_warnings(warn_patterns) 356 total = 0 357 for severity in Severity.levels: 358 total += write_severity(csvwriter, severity, severity.column_header, warn_patterns) 359 csvwriter.writerow([total, '', 'All warnings']) 360 361 362def dump_csv_with_description(csvwriter, warning_records, warning_messages, 363 warn_patterns, project_names): 364 """Outputs all the warning messages by project.""" 365 csv_output = [] 366 for record in warning_records: 367 project_name = project_names[record[1]] 368 pattern = warn_patterns[record[0]] 369 severity = pattern['severity'].header 370 category = pattern['category'] 371 description = pattern['description'] 372 warning = warning_messages[record[2]] 373 csv_output.append([project_name, severity, 374 category, description, 375 warning]) 376 csv_output = sorted(csv_output) 377 for output in csv_output: 378 csvwriter.writerow(output) 379 380 381# Return line with escaped backslash and quotation characters. 382def escape_string(line): 383 return line.replace('\\', '\\\\').replace('"', '\\"') 384 385 386# Return line without trailing '\n' and escape the quotation characters. 387def strip_escape_string(line): 388 if not line: 389 return line 390 line = line[:-1] if line[-1] == '\n' else line 391 return escape_string(line) 392 393 394def emit_warning_array(name, writer, warn_patterns): 395 writer('var warning_{} = ['.format(name)) 396 for pattern in warn_patterns: 397 if name == 'severity': 398 writer('{},'.format(pattern[name].value)) 399 else: 400 writer('{},'.format(pattern[name])) 401 writer('];') 402 403 404def emit_warning_arrays(writer, warn_patterns): 405 emit_warning_array('severity', writer, warn_patterns) 406 writer('var warning_description = [') 407 for pattern in warn_patterns: 408 if pattern['members']: 409 writer('"{}",'.format(escape_string(pattern['description']))) 410 else: 411 writer('"",') # no such warning 412 writer('];') 413 414 415SCRIPTS_FOR_WARNING_GROUPS = """ 416 function compareMessages(x1, x2) { // of the same warning type 417 return (WarningMessages[x1[2]] <= WarningMessages[x2[2]]) ? -1 : 1; 418 } 419 function byMessageCount(x1, x2) { 420 return x2[2] - x1[2]; // reversed order 421 } 422 function bySeverityMessageCount(x1, x2) { 423 // orer by severity first 424 if (x1[1] != x2[1]) 425 return x1[1] - x2[1]; 426 return byMessageCount(x1, x2); 427 } 428 const ParseLinePattern = /^([^ :]+):(\\d+):(.+)/; 429 function addURL(line) { // used by Android 430 if (FlagURL == "") return line; 431 if (FlagSeparator == "") { 432 return line.replace(ParseLinePattern, 433 "<a target='_blank' href='" + FlagURL + "/$1'>$1</a>:$2:$3"); 434 } 435 return line.replace(ParseLinePattern, 436 "<a target='_blank' href='" + FlagURL + "/$1" + FlagSeparator + 437 "$2'>$1:$2</a>:$3"); 438 } 439 function addURLToLine(line, link) { // used by Chrome 440 let line_split = line.split(":"); 441 let path = line_split.slice(0,3).join(":"); 442 let msg = line_split.slice(3).join(":"); 443 let html_link = `<a target="_blank" href="${link}">${path}</a>${msg}`; 444 return html_link; 445 } 446 function createArrayOfDictionaries(n) { 447 var result = []; 448 for (var i=0; i<n; i++) result.push({}); 449 return result; 450 } 451 function groupWarningsBySeverity() { 452 // groups is an array of dictionaries, 453 // each dictionary maps from warning type to array of warning messages. 454 var groups = createArrayOfDictionaries(SeverityColors.length); 455 for (var i=0; i<Warnings.length; i++) { 456 var w = Warnings[i][0]; 457 var s = WarnPatternsSeverity[w]; 458 var k = w.toString(); 459 if (!(k in groups[s])) 460 groups[s][k] = []; 461 groups[s][k].push(Warnings[i]); 462 } 463 return groups; 464 } 465 function groupWarningsByProject() { 466 var groups = createArrayOfDictionaries(ProjectNames.length); 467 for (var i=0; i<Warnings.length; i++) { 468 var w = Warnings[i][0]; 469 var p = Warnings[i][1]; 470 var k = w.toString(); 471 if (!(k in groups[p])) 472 groups[p][k] = []; 473 groups[p][k].push(Warnings[i]); 474 } 475 return groups; 476 } 477 var GlobalAnchor = 0; 478 function createWarningSection(header, color, group) { 479 var result = ""; 480 var groupKeys = []; 481 var totalMessages = 0; 482 for (var k in group) { 483 totalMessages += group[k].length; 484 groupKeys.push([k, WarnPatternsSeverity[parseInt(k)], group[k].length]); 485 } 486 groupKeys.sort(bySeverityMessageCount); 487 for (var idx=0; idx<groupKeys.length; idx++) { 488 var k = groupKeys[idx][0]; 489 var messages = group[k]; 490 var w = parseInt(k); 491 var wcolor = SeverityColors[WarnPatternsSeverity[w]]; 492 var description = WarnPatternsDescription[w]; 493 if (description.length == 0) 494 description = "???"; 495 GlobalAnchor += 1; 496 result += "<table class='t1'><tr bgcolor='" + wcolor + "'><td>" + 497 "<button class='bt' id='" + GlobalAnchor + "_mark" + 498 "' onclick='expand(\\"" + GlobalAnchor + "\\");'>" + 499 "⊕</button> " + 500 description + " (" + messages.length + ")</td></tr></table>"; 501 result += "<div id='" + GlobalAnchor + 502 "' style='display:none;'><table class='t1'>"; 503 var c = 0; 504 messages.sort(compareMessages); 505 if (FlagPlatform == "chrome") { 506 for (var i=0; i<messages.length; i++) { 507 result += "<tr><td class='c" + c + "'>" + 508 addURLToLine(WarningMessages[messages[i][2]], WarningLinks[messages[i][3]]) + "</td></tr>"; 509 c = 1 - c; 510 } 511 } else { 512 for (var i=0; i<messages.length; i++) { 513 result += "<tr><td class='c" + c + "'>" + 514 addURL(WarningMessages[messages[i][2]]) + "</td></tr>"; 515 c = 1 - c; 516 } 517 } 518 result += "</table></div>"; 519 } 520 if (result.length > 0) { 521 return "<br><span style='background-color:" + color + "'><b>" + 522 header + ": " + totalMessages + 523 "</b></span><blockquote><table class='t1'>" + 524 result + "</table></blockquote>"; 525 526 } 527 return ""; // empty section 528 } 529 function generateSectionsBySeverity() { 530 var result = ""; 531 var groups = groupWarningsBySeverity(); 532 for (s=0; s<SeverityColors.length; s++) { 533 result += createWarningSection(SeverityHeaders[s], SeverityColors[s], 534 groups[s]); 535 } 536 return result; 537 } 538 function generateSectionsByProject() { 539 var result = ""; 540 var groups = groupWarningsByProject(); 541 for (i=0; i<groups.length; i++) { 542 result += createWarningSection(ProjectNames[i], 'lightgrey', groups[i]); 543 } 544 return result; 545 } 546 function groupWarnings(generator) { 547 GlobalAnchor = 0; 548 var e = document.getElementById("warning_groups"); 549 e.innerHTML = generator(); 550 } 551 function groupBySeverity() { 552 groupWarnings(generateSectionsBySeverity); 553 } 554 function groupByProject() { 555 groupWarnings(generateSectionsByProject); 556 } 557""" 558 559 560# Emit a JavaScript const string 561def emit_const_string(name, value, writer): 562 writer('const ' + name + ' = "' + escape_string(value) + '";') 563 564 565# Emit a JavaScript const integer array. 566def emit_const_int_array(name, array, writer): 567 writer('const ' + name + ' = [') 568 for item in array: 569 writer(str(item) + ',') 570 writer('];') 571 572 573# Emit a JavaScript const string array. 574def emit_const_string_array(name, array, writer): 575 writer('const ' + name + ' = [') 576 for item in array: 577 writer('"' + strip_escape_string(item) + '",') 578 writer('];') 579 580 581# Emit a JavaScript const string array for HTML. 582def emit_const_html_string_array(name, array, writer): 583 writer('const ' + name + ' = [') 584 for item in array: 585 writer('"' + html.escape(strip_escape_string(item)) + '",') 586 writer('];') 587 588 589# Emit a JavaScript const object array. 590def emit_const_object_array(name, array, writer): 591 writer('const ' + name + ' = [') 592 for item in array: 593 writer(str(item) + ',') 594 writer('];') 595 596 597def emit_js_data(writer, flags, warning_messages, warning_links, 598 warning_records, warn_patterns, project_names): 599 """Dump dynamic HTML page's static JavaScript data.""" 600 emit_const_string('FlagPlatform', flags.platform, writer) 601 emit_const_string('FlagURL', flags.url, writer) 602 emit_const_string('FlagSeparator', flags.separator, writer) 603 emit_const_string_array('SeverityColors', [s.color for s in Severity.levels], 604 writer) 605 emit_const_string_array('SeverityHeaders', 606 [s.header for s in Severity.levels], writer) 607 emit_const_string_array('SeverityColumnHeaders', 608 [s.column_header for s in Severity.levels], writer) 609 emit_const_string_array('ProjectNames', project_names, writer) 610 # pytype: disable=attribute-error 611 emit_const_int_array('WarnPatternsSeverity', 612 [w['severity'].value for w in warn_patterns], writer) 613 # pytype: enable=attribute-error 614 emit_const_html_string_array('WarnPatternsDescription', 615 [w['description'] for w in warn_patterns], 616 writer) 617 emit_const_html_string_array('WarningMessages', warning_messages, writer) 618 emit_const_object_array('Warnings', warning_records, writer) 619 if flags.platform == 'chrome': 620 emit_const_html_string_array('WarningLinks', warning_links, writer) 621 622 623DRAW_TABLE_JAVASCRIPT = """ 624google.charts.load('current', {'packages':['table']}); 625google.charts.setOnLoadCallback(drawTable); 626function drawTable() { 627 var data = new google.visualization.DataTable(); 628 data.addColumn('string', StatsHeader[0]); 629 for (var i=1; i<StatsHeader.length; i++) { 630 data.addColumn('number', StatsHeader[i]); 631 } 632 data.addRows(StatsRows); 633 for (var i=0; i<StatsRows.length; i++) { 634 for (var j=0; j<StatsHeader.length; j++) { 635 data.setProperty(i, j, 'style', 'border:1px solid black;'); 636 } 637 } 638 var table = new google.visualization.Table( 639 document.getElementById('stats_table')); 640 table.draw(data, {allowHtml: true, alternatingRowStyle: true}); 641} 642""" 643 644 645def dump_html(flags, output_stream, warning_messages, warning_links, 646 warning_records, header_str, warn_patterns, project_names): 647 """Dump the flags output to output_stream.""" 648 writer = make_writer(output_stream) 649 dump_html_prologue('Warnings for ' + header_str, writer, warn_patterns, 650 project_names) 651 dump_stats(writer, warn_patterns) 652 writer('<br><div id="stats_table"></div><br>') 653 writer('\n<script>') 654 emit_js_data(writer, flags, warning_messages, warning_links, warning_records, 655 warn_patterns, project_names) 656 writer(SCRIPTS_FOR_WARNING_GROUPS) 657 writer('</script>') 658 emit_buttons(writer) 659 # Warning messages are grouped by severities or project names. 660 writer('<br><div id="warning_groups"></div>') 661 if flags.byproject: 662 writer('<script>groupByProject();</script>') 663 else: 664 writer('<script>groupBySeverity();</script>') 665 dump_fixed(writer, warn_patterns) 666 dump_html_epilogue(writer) 667 668 669def write_html(flags, project_names, warn_patterns, html_path, warning_messages, 670 warning_links, warning_records, header_str): 671 """Write warnings html file.""" 672 if html_path: 673 with open(html_path, 'w') as outf: 674 dump_html(flags, outf, warning_messages, warning_links, warning_records, 675 header_str, warn_patterns, project_names) 676 677 678def write_out_csv(flags, warn_patterns, warning_messages, warning_links, 679 warning_records, header_str, project_names): 680 """Write warnings csv file.""" 681 if flags.csvpath: 682 with open(flags.csvpath, 'w') as outf: 683 dump_csv(csv.writer(outf, lineterminator='\n'), warn_patterns) 684 685 if flags.csvwithdescription: 686 with open(flags.csvwithdescription, 'w') as outf: 687 dump_csv_with_description(csv.writer(outf, lineterminator='\n'), 688 warning_records, warning_messages, 689 warn_patterns, project_names) 690 691 if flags.gencsv: 692 dump_csv(csv.writer(sys.stdout, lineterminator='\n'), warn_patterns) 693 else: 694 dump_html(flags, sys.stdout, warning_messages, warning_links, 695 warning_records, header_str, warn_patterns, project_names) 696