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