• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = '&#x2295';
80    }
81    else {
82       e.style.display = 'block';
83       f.innerHTML = '&#x2296';
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 ? '&#x2296' : '&#x2295');
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         '&#x2295</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                "&#x2295</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 + '\');">&#x2295</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 &#x2295 to show section content,'
843         ' and &#x2296 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