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 = '&#x2295';
74    }
75    else {
76       e.style.display = 'block';
77       f.innerHTML = '&#x2296';
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 ? '&#x2296' : '&#x2295');
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         '&#x2295</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                "&#x2295</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