Update HTML output so that it renders highlight-a vs highlight-b via <span> tags in the message itself, in the quoted source line, in the underlines, and in the labels and their vertical bars.
Example output can be seen at: https://dmalcolm.fedorapeople.org/gcc/2025-05-28/diagnostic-ranges.c.html Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r16-1024-ga746747e2102c9. gcc/ChangeLog: PR other/116792 * diagnostic-format-html.cc (HTML_STYLE): Add ".highlight-a" and ".highlight-b". (html_builder::make_element_for_diagnostic): Handle begin_color and end_color. * diagnostic-show-locus.cc (to_html::to_html): Add "richloc" param and use it to initialize m_richloc. (to_html::colorize_text_for_range_idx): Drop. (to_html::get_location_range_by_idx): New. (to_html::get_highlight_color_for_range_idx): New. (to_html::m_richloc): New field. (print_html_span_start): Update for new param of to_html ctor. (line_printer::m_was_in_range_p): New field. (line_printer::m_last_range_idx): New field. (layout_printer<Sink>::print_source_line): Use set_in_range and set_outside_range rather than colorization calls. (layout_printer<Sink>::set_in_range): New. (layout_printer<Sink>::set_outside_range): New. (layout_printer<Sink>::print_annotation_line): Use set_in_range and set_outside_range rather than colorization calls. (layout_printer<to_text>::begin_label): Convert param from label to state_idx. Add "is_label_text" param and use it to guard logic for turning off colorization within paths. (layout_printer<to_html>::begin_label): Likewise. Push <span> for any highlight color. (layout_printer<to_text>::end_label): Likewise. (layout_printer<to_text>::end_label): Likewise, popping the <span>. (layout_printer<Sink>::print_any_labels): Convert begin/end_label calls to pass in state_idx rather than label. Use begin/end_label rather than colorization calls. (layout_printer<Sink>::layout_printer): Likewise. (layout_printer<Sink>::layout_printer): Initialize new fields. (diagnostic_source_print_policy::print_as_html): Update for new param of to_html ctor. gcc/testsuite/ChangeLog: PR other/116792 * gcc.dg/format/diagnostic-ranges-html.py: New test script. * gcc.dg/format/diagnostic-ranges.c: Add HTML generation to options, and invoke the new script to check the HTML output. Signed-off-by: David Malcolm <dmalc...@redhat.com> --- gcc/diagnostic-format-html.cc | 13 +- gcc/diagnostic-show-locus.cc | 140 ++++++++++++++---- .../gcc.dg/format/diagnostic-ranges-html.py | 99 +++++++++++++ .../gcc.dg/format/diagnostic-ranges.c | 6 +- 4 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc index c430bdac75ae..05d4273c2c61 100644 --- a/gcc/diagnostic-format-html.cc +++ b/gcc/diagnostic-format-html.cc @@ -511,6 +511,10 @@ static const char * const HTML_STYLE " box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n" " .frame-funcname { text-align: right;\n" " font-style: italic; } \n" + " .highlight-a { color: #703fec;\n" // pf-purple-400 + " font-weight: bold; }\n" + " .highlight-b { color: #3f9c35;\n" // pf-green-400 + " font-weight: bold; }\n" " </style>\n"); /* A little JavaScript for ease of navigation. @@ -725,8 +729,15 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, break; case pp_token::kind::begin_color: + { + pp_token_begin_color *sub = as_a <pp_token_begin_color *> (iter); + gcc_assert (sub->m_value.get ()); + m_xp.push_tag_with_class ("span", sub->m_value.get ()); + } + break; + case pp_token::kind::end_color: - /* These are no-ops. */ + m_xp.pop_tag (); break; case pp_token::kind::begin_quote: diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc index c6a0cd80ce8c..3654102509b9 100644 --- a/gcc/diagnostic-show-locus.cc +++ b/gcc/diagnostic-show-locus.cc @@ -539,8 +539,10 @@ struct to_html friend class layout_printer<to_html>; to_html (xml::printer &xp, + const rich_location *richloc, html_label_writer *html_label_writer) : m_xp (xp), + m_richloc (richloc), m_html_label_writer (html_label_writer) {} @@ -620,11 +622,6 @@ struct to_html // no-op for HTML } - void colorize_text_for_range_idx (int) - { - // no-op for HTML - } - void colorize_text_for_cfg_edge () { // no-op for HTML @@ -648,7 +645,26 @@ struct to_html source_policy.get_html_start_span_fn () (loc_policy, *this, exploc); } + const location_range * + get_location_range_by_idx (int range_idx) + { + if (!m_richloc) + return nullptr; + return m_richloc->get_range (range_idx); + } + + const char * + get_highlight_color_for_range_idx (int range_idx) + { + const location_range *const loc_range + = get_location_range_by_idx (range_idx); + if (!loc_range) + return nullptr; + return loc_range->m_highlight_color; + } + xml::printer &m_xp; + const rich_location *m_richloc; private: html_label_writer *m_html_label_writer; pretty_printer m_scratch_pp; @@ -671,7 +687,7 @@ print_html_span_start (const diagnostic_context &dc, xml::printer &xp, const expanded_location &exploc) { - to_html sink (xp, nullptr); + to_html sink (xp, nullptr, nullptr); diagnostic_source_print_policy source_policy (dc); source_policy.get_html_start_span_fn () (*this, sink, exploc); } @@ -835,8 +851,8 @@ private: void end_line (); void print_annotation_line (linenum_type row, const line_bounds lbounds); void print_any_labels (linenum_type row); - void begin_label (const line_label &label); - void end_label (); + void begin_label (int state_idx, bool is_label_text); + void end_label (int state_idx, bool is_label_text); void print_trailing_fixits (linenum_type row); void @@ -844,11 +860,17 @@ private: void print_any_right_to_left_edge_lines (); + void set_in_range (int range_idx); + void set_outside_range (); + private: Sink &m_sink; const layout &m_layout; bool m_is_diagnostic_path; + bool m_was_in_range_p; + int m_last_range_idx; + /* Fields for handling links between labels (e.g. for showing CFG edges in execution paths). Note that the logic for printing such links makes various simplifying @@ -2302,9 +2324,9 @@ layout_printer<Sink>::print_source_line (linenum_type row, CU_BYTES, &state); if (in_range_p) - m_sink.colorize_text_for_range_idx (state.range_idx); + set_in_range (state.range_idx); else - m_sink.colorize_text_ensure_normal (); + set_outside_range (); } /* Get the display width of the next character to be output, expanding @@ -2335,6 +2357,7 @@ layout_printer<Sink>::print_source_line (linenum_type row, m_sink.print_decoded_char (m_layout.m_char_policy, cp); c = dw.next_byte (); } + set_outside_range (); end_line (); return lbounds; } @@ -2477,6 +2500,41 @@ layout_printer<to_html>::end_line () m_sink.pop_html_tag ("tr"); } +/* Handle the various transitions between being-in-range and + not-being-in-a-range, and between ranges. */ + +template<typename Sink> +void +layout_printer<Sink>::set_in_range (int range_idx) +{ + if (m_was_in_range_p) + { + if (m_last_range_idx != range_idx) + { + /* transition between ranges. */ + end_label (m_last_range_idx, false); + begin_label (range_idx, false); + } + } + else + { + /* transition from "not in a range" to "in a range". */ + begin_label (range_idx, false); + m_was_in_range_p = true; + } + m_last_range_idx = range_idx; +} + +template<typename Sink> +void +layout_printer<Sink>::set_outside_range () +{ + if (m_was_in_range_p) + /* transition from "in a range" to "not in a range". */ + end_label (m_last_range_idx, false); + m_was_in_range_p = false; +} + /* Print a line consisting of the caret/underlines for the given source line. */ @@ -2500,10 +2558,14 @@ layout_printer<Sink>::print_annotation_line (linenum_type row, lbounds.m_last_non_ws_disp_col, CU_DISPLAY_COLS, &state); + if (in_range_p) + set_in_range (state.range_idx); + else + set_outside_range (); + if (in_range_p) { /* Within a range. Draw either the caret or an underline. */ - m_sink.colorize_text_for_range_idx (state.range_idx); if (state.draw_caret_p) { /* Draw the caret. */ @@ -2520,11 +2582,12 @@ layout_printer<Sink>::print_annotation_line (linenum_type row, else { /* Not in a range. */ - m_sink.colorize_text_ensure_normal (); m_sink.add_character (' '); } } + set_outside_range (); + end_line (); } @@ -2606,36 +2669,59 @@ public: bool m_has_out_edge; }; +/* Implementations of layout_printer::{begin,end}_label for + to_text and to_html. + + RANGE_IDX is the index of the range within the rich_location. + + IS_LABEL_TEXT is true for the text of the label, + false when quoting the source code, underlining the source + code, and for the vertical bars connecting the underlines + to the text of the label. */ + template<> void -layout_printer<to_text>::begin_label (const line_label &label) +layout_printer<to_text>::begin_label (int range_idx, + bool is_label_text) { - /* Colorize the text, unless it's for events in a + /* Colorize the text, unless it's for labels for events in a diagnostic_path. */ - if (!m_is_diagnostic_path) - m_sink.colorize_text_for_range_idx (label.m_state_idx); + if (is_label_text && m_is_diagnostic_path) + return; + + gcc_assert (m_sink.m_colorizer); + m_sink.m_colorizer->set_range (range_idx); } template<> void -layout_printer<to_html>::begin_label (const line_label &) +layout_printer<to_html>::begin_label (int range_idx, + bool is_label_text) { - if (m_sink.m_html_label_writer) + if (is_label_text && m_sink.m_html_label_writer) m_sink.m_html_label_writer->begin_label (); + + if (const char *highlight_color + = m_sink.get_highlight_color_for_range_idx (range_idx)) + m_sink.m_xp.push_tag_with_class ("span", highlight_color); } template<> void -layout_printer<to_text>::end_label () +layout_printer<to_text>::end_label (int, bool) { m_sink.colorize_text_ensure_normal (); } template<> void -layout_printer<to_html>::end_label () +layout_printer<to_html>::end_label (int range_idx, + bool is_label_text) { - if (m_sink.m_html_label_writer) + if (m_sink.get_highlight_color_for_range_idx (range_idx)) + m_sink.m_xp.pop_tag (); + + if (is_label_text && m_sink.m_html_label_writer) m_sink.m_html_label_writer->end_label (); } @@ -2804,9 +2890,9 @@ layout_printer<Sink>::print_any_labels (linenum_type row) move_to_column (&column, label->m_column, true); gcc_assert (column == label->m_column); - begin_label (*label); + begin_label (label->m_state_idx, true); m_sink.add_text (label->m_text.m_buffer); - end_label (); + end_label (label->m_state_idx, true); column += label->m_display_width; if (get_options ().show_event_links_p && label->m_has_out_edge) @@ -2837,9 +2923,9 @@ layout_printer<Sink>::print_any_labels (linenum_type row) { gcc_assert (column <= label->m_column); move_to_column (&column, label->m_column, true); - m_sink.colorize_text_for_range_idx (label->m_state_idx); + begin_label (label->m_state_idx, false); m_sink.add_character ('|'); - m_sink.colorize_text_ensure_normal (); + end_label (label->m_state_idx, false); column++; } } @@ -3676,6 +3762,8 @@ layout_printer<Sink>::layout_printer (Sink &sink, : m_sink (sink), m_layout (layout), m_is_diagnostic_path (is_diagnostic_path), + m_was_in_range_p (false), + m_last_range_idx (0), m_link_lhs_state (link_lhs_state::none), m_link_rhs_column (-1) { @@ -3857,7 +3945,7 @@ diagnostic_source_print_policy::print_as_html (xml::printer &xp, const { layout layout (*this, richloc, effects); - to_html sink (xp, label_writer); + to_html sink (xp, &richloc, label_writer); layout_printer<to_html> lp (sink, layout, diagnostic_kind == DK_DIAGNOSTIC_PATH); lp.print (*this); diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py new file mode 100644 index 000000000000..91383d64457a --- /dev/null +++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py @@ -0,0 +1,99 @@ +from htmltest import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def html_tree(): + return html_tree_from_env() + +def assert_highlighted_text(element, expected_highlight, expected_text): + assert_tag(element, 'span') + assert_class(element, expected_highlight) + assert element.text == expected_text + +def test_message(html_tree): + """ + Verify that the quoted text in the message has the correct + highlight colors. + """ + diag = get_diag_by_index(html_tree, 0) + msg = get_message_within_diag(diag) + + assert_tag(msg[0], 'span') + assert_class(msg[0], 'gcc-quoted-text') + assert_highlighted_text(msg[0][0], 'highlight-a', '%i') + + assert_tag(msg[1], 'span') + assert_class(msg[1], 'gcc-quoted-text') + assert_highlighted_text(msg[1][0], 'highlight-a', 'int') + + assert_tag(msg[2], 'span') + assert_class(msg[2], 'gcc-quoted-text') + assert_highlighted_text(msg[2][0], 'highlight-b', 'const char *') + +def test_annotations(html_tree): + """ + Verify that the labels in the annotations have the correct + highlight colors. + """ + diag = get_diag_by_index(html_tree, 0) + locus = get_locus_within_diag(diag) + tbody = locus.find('xhtml:tbody', ns) + assert tbody.attrib['class'] == 'line-span' + + rows = tbody.findall('xhtml:tr', ns) + + # Source row + row = rows[0] + tds = row.findall('xhtml:td', ns) + assert len(tds) == 2 + assert_class(tds[1], 'source') + assert_highlighted_text(tds[1][0], 'highlight-a', '%i') + assert_highlighted_text(tds[1][1], 'highlight-b', 'msg') + + # Underline row: + row = rows[1] + tds = row.findall('xhtml:td', ns) + assert len(tds) == 2 + assert_class(tds[1], 'annotation') + assert_highlighted_text(tds[1][0], 'highlight-a', '~^') + assert_highlighted_text(tds[1][1], 'highlight-b', '~~~') + + # vline row: + row = rows[2] + tds = row.findall('xhtml:td', ns) + assert len(tds) == 2 + assert_class(tds[1], 'annotation') + assert_highlighted_text(tds[1][0], 'highlight-a', '|') + assert_highlighted_text(tds[1][1], 'highlight-b', '|') + + # Label row: + row = rows[3] + tds = row.findall('xhtml:td', ns) + assert len(tds) == 2 + assert_class(tds[1], 'annotation') + assert_highlighted_text(tds[1][0], 'highlight-a', 'int') + assert_highlighted_text(tds[1][1], 'highlight-b', 'const char *') + +# For reference, here's the generated HTML: +""" + <span class="gcc-message" id="gcc-diag-0-message">format '<span class="gcc-quoted-text"><span class="high +light-a">%i</span></span>' expects argument of type '<span class="gcc-quoted-text"><span class="highlight-a" +>int</span></span>', but argument 2 has type '<span class="gcc-quoted-text"><span class="highlight-b">const +char *</span></span>'</span> + + <span class="gcc-option">[<a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wformat">-Wfo +rmat=</a>]</span> + <table class="locus"> + <tbody class="line-span"> + <tr><td class="left-margin"> </td><td class="source"> printf("hello <span class="highlight-a">%i</span>", <span class="highlight-b">msg</span>); /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */</td></tr> + <tr><td class="left-margin"> </td><td class="annotation"> <span class="highlight-a">~^</spa +n> <span class="highlight-b">~~~</span></td></tr> + <tr><td class="left-margin"> </td><td class="annotation"> <span class="highlight-a">|</spa +n> <span class="highlight-b">|</span></td></tr> + <tr><td class="left-margin"> </td><td class="annotation"> <span class="highlight-a">int</s +pan> <span class="highlight-b">const char *</span></td></tr> + <tr><td class="left-margin"> </td><td class="annotation"> %s</td></tr> + </tbody> + </table> +""" diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c index 2c33ce2c0d56..d3e334d581c9 100644 --- a/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c +++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges.c @@ -1,4 +1,4 @@ -/* { dg-options "-Wformat -fdiagnostics-show-caret" } */ +/* { dg-options "-Wformat -fdiagnostics-show-caret -fdiagnostics-add-output=experimental-html:javascript=no" } */ /* See PR 52952. */ @@ -390,3 +390,7 @@ void test_const_arrays (void) double { dg-end-multiline-output "" } */ } + +/* Use a Python script to verify various properties about the generated + HTML file: + { dg-final { run-html-pytest diagnostic-ranges.c "diagnostic-ranges-html.py" } } */ -- 2.26.3