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