Skip to content

Scrabsha/html errors #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
265 changes: 264 additions & 1 deletion compiler/rustc_errors/src/emitter.rs
Original file line number Diff line number Diff line change
@@ -21,13 +21,14 @@ use crate::{

use rustc_lint_defs::pluralize;

use core::slice::SplitInclusive;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::Lrc;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use std::borrow::Cow;
use std::cmp::{max, min, Reverse};
use std::io;
use std::io::prelude::*;
use std::io::{self, ErrorKind};
use std::iter;
use std::path::Path;
use termcolor::{Ansi, BufferWriter, ColorChoice, ColorSpec, StandardStream};
@@ -637,6 +638,39 @@ impl EmitterWriter {
}
}

pub fn html(
dst: Box<dyn Write + Send>,
source_map: Option<Lrc<SourceMap>>,
short_message: bool,
teach: bool,
macro_backtrace: bool,
) -> EmitterWriter {
let output_file = HtmlFormatter::new(dst);

let dst = Destination::HtmlFile(output_file);
EmitterWriter {
dst,
sm: source_map,
short_message,
teach,
ui_testing: false,
// FIXME(scrabsha): do we expect a certain terminal width in
// html-rendered files?
terminal_width: None,
macro_backtrace,
}
}

pub fn html_stderr(
source_map: Option<Lrc<SourceMap>>,
short_message: bool,
teach: bool,
macro_backtrace: bool,
) -> EmitterWriter {
let dst = Box::new(io::stderr());
EmitterWriter::html(dst, source_map, short_message, teach, macro_backtrace)
}

pub fn ui_testing(mut self, ui_testing: bool) -> Self {
self.ui_testing = ui_testing;
self
@@ -2126,6 +2160,8 @@ fn emit_to_destination(

let mut dst = dst.writable();

dst.begin()?;

// In order to prevent error message interleaving, where multiple error lines get intermixed
// when multiple compiler processes error simultaneously, we emit errors with additional
// steps.
@@ -2150,6 +2186,7 @@ fn emit_to_destination(
}
}
dst.flush()?;
dst.end()?;
Ok(())
}

@@ -2158,13 +2195,15 @@ pub enum Destination {
Buffered(BufferWriter),
// The bool denotes whether we should be emitting ansi color codes or not
Raw(Box<(dyn Write + Send)>, bool),
HtmlFile(HtmlFormatter),
}

pub enum WritableDst<'a> {
Terminal(&'a mut StandardStream),
Buffered(&'a mut BufferWriter, Buffer),
Raw(&'a mut (dyn Write + Send)),
ColoredRaw(Ansi<&'a mut (dyn Write + Send)>),
HtmlFile(&'a mut HtmlFormatter),
}

impl Destination {
@@ -2192,6 +2231,7 @@ impl Destination {
}
Destination::Raw(ref mut t, false) => WritableDst::Raw(t),
Destination::Raw(ref mut t, true) => WritableDst::ColoredRaw(Ansi::new(t)),
Destination::HtmlFile(ref mut file) => WritableDst::HtmlFile(file),
}
}

@@ -2200,6 +2240,7 @@ impl Destination {
Self::Terminal(ref stream) => stream.supports_color(),
Self::Buffered(ref buffer) => buffer.buffer().supports_color(),
Self::Raw(_, supports_color) => supports_color,
Self::HtmlFile(_) => true,
}
}
}
@@ -2261,6 +2302,7 @@ impl<'a> WritableDst<'a> {
WritableDst::Buffered(_, ref mut t) => t.set_color(color),
WritableDst::ColoredRaw(ref mut t) => t.set_color(color),
WritableDst::Raw(_) => Ok(()),
WritableDst::HtmlFile(ref mut t) => t.set_color(color),
}
}

@@ -2270,6 +2312,27 @@ impl<'a> WritableDst<'a> {
WritableDst::Buffered(_, ref mut t) => t.reset(),
WritableDst::ColoredRaw(ref mut t) => t.reset(),
WritableDst::Raw(_) => Ok(()),
WritableDst::HtmlFile(ref mut t) => t.reset(),
}
}

fn begin(&mut self) -> io::Result<()> {
match *self {
WritableDst::Terminal(_) => Ok(()),
WritableDst::Buffered(_, _) => Ok(()),
WritableDst::Raw(_) => Ok(()),
WritableDst::ColoredRaw(_) => Ok(()),
WritableDst::HtmlFile(ref mut t) => t.begin(),
}
}

fn end(&mut self) -> io::Result<()> {
match *self {
WritableDst::Terminal(_) => Ok(()),
WritableDst::Buffered(_, _) => Ok(()),
WritableDst::Raw(_) => Ok(()),
WritableDst::ColoredRaw(_) => Ok(()),
WritableDst::HtmlFile(ref mut t) => t.end(),
}
}
}
@@ -2281,6 +2344,7 @@ impl<'a> Write for WritableDst<'a> {
WritableDst::Buffered(_, ref mut buf) => buf.write(bytes),
WritableDst::Raw(ref mut w) => w.write(bytes),
WritableDst::ColoredRaw(ref mut t) => t.write(bytes),
WritableDst::HtmlFile(ref mut f) => f.write(bytes),
}
}

@@ -2290,6 +2354,7 @@ impl<'a> Write for WritableDst<'a> {
WritableDst::Buffered(_, ref mut buf) => buf.flush(),
WritableDst::Raw(ref mut w) => w.flush(),
WritableDst::ColoredRaw(ref mut w) => w.flush(),
WritableDst::HtmlFile(ref mut f) => f.flush(),
}
}
}
@@ -2302,6 +2367,204 @@ impl<'a> Drop for WritableDst<'a> {
}
}

/// A type that formats everything it receives to HTML. It implements both
/// Write (so that we can write text in it) and WriteColor (so that we can
/// set which color to use).
pub struct HtmlFormatter {
inner: Box<dyn Write>,
}

impl HtmlFormatter {
fn new(inner: Box<dyn Write>) -> HtmlFormatter {
HtmlFormatter { inner }
}

fn begin(&mut self) -> io::Result<()> {
write!(self.inner, r#"<pre style="margin:0px">"#)
}

fn end(&mut self) -> io::Result<()> {
write!(self.inner, r#"</pre>"#)
}

// Creates the string of a `style` attribute in an html element
fn mk_style_string(spec: &ColorSpec) -> String {
let mut buffer = String::new();

let colors = [("color", spec.fg()), ("background-color", spec.bg())];
let colors = colors.iter().flat_map(|(c_name, c_value)| {
c_value.map(|c_value| (c_name, term_color_to_html_color(*c_value)))
});

for (key, value) in colors {
buffer.push_str(format!("{}: {};", key, value).as_str());
}

let font_modifiers = [
("font-weight:bold", spec.bold()),
("font-style:italic", spec.italic()),
("text-decoration:underline", spec.underline()),
];
let font_modifiers = font_modifiers
.iter()
.flat_map(|(property, applicable)| if *applicable { Some(property) } else { None })
.copied();

for font_modifier in font_modifiers {
buffer.push_str(format!("{};", font_modifier).as_str());
}

// The trailing semicolon must removed as defined in the style
// attribute grammar:
// https://drafts.csswg.org/css-style-attr/#syntax
let _ = buffer.pop();

buffer
}
}

impl Write for HtmlFormatter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for (idx, segment) in EscapedHtmlIter::new(buf) {
match segment {
EscapedHtmlSegment::Modified(buf) => match self.inner.write_all(buf) {
Ok(()) => {}

Err(e) if idx == 0 => return Err(e),
Err(e) => return Ok(idx),
},

EscapedHtmlSegment::Unmodified(buf) => match self.inner.write(buf) {
Ok(bytes_written) if bytes_written == buf.len() => {}
Ok(bytes_written) => return Ok(idx + bytes_written),

Err(e) if idx == 0 => return Err(e),
Err(e) => return Ok(idx),
},
}

first = false;
}

Ok(buf.len())
}

fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}

impl WriteColor for HtmlFormatter {
fn supports_color(&self) -> bool {
true
}

fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
write!(self.inner, r#"<span style="{}">"#, Self::mk_style_string(spec))
}

fn reset(&mut self) -> io::Result<()> {
write!(self.inner, r#"</span>"#)
}
}

struct EscapedHtmlIter<'a> {
iter: SplitInclusive<'a, u8, fn(&u8) -> bool>,
index: usize,
trimmed_escaped: Option<&'static [u8]>,
}

impl<'a> EscapedHtmlIter<'a> {
fn new(input: &'a [u8]) -> Self {
EscapedHtmlIter {
iter: input.split_inclusive(Self::is_reserved_byte),
index: 0,
trimmed_escaped: None,
}
}

fn is_reserved_byte(byte: &u8) -> bool {
// Reserved characters are described at:
// https://developer.mozilla.org/en-US/docs/Glossary/Entity#reserved_characters
[b'&', b'<', b'>', b'"'].contains(&byte)
}

fn substitute_reserved_byte(byte: u8) -> Option<&'static [u8]> {
match byte {
// Substitutions for reserved characters are described at:
// https://developer.mozilla.org/en-US/docs/Glossary/Entity#reserved_characters
b'&' => Some(b"&amp;"),
b'<' => Some(b"&lt;"),
b'>' => Some(b"&gt;"),
b'"' => Some(b"&quot;"),

_ => None,
}
}

fn trim_escapable_and_escape(input: &[u8]) -> (&[u8], Option<&'static [u8]>) {
input
.split_last()
.and_then(|(last, rest)| {
Self::substitute_reserved_byte(*last).map(|substituted| (rest, Some(substituted)))
})
.unwrap_or_else(|| (input, None))
}
}

impl<'a> Iterator for EscapedHtmlIter<'a> {
type Item = (usize, EscapedHtmlSegment<'a>);

fn next(&mut self) -> Option<Self::Item> {
match self.trimmed_escaped.take() {
Some(substitution) => {
let idx = self.index;
self.index += 1;

Some((idx, EscapedHtmlSegment::Modified(substitution)))
}

None => {
let segment = self.iter.next()?;
let (already_escaped, just_escaped) = Self::trim_escapable_and_escape(segment);
let idx = self.index;

self.trimmed_escaped = just_escaped;
self.index += already_escaped.len();

Some((idx, EscapedHtmlSegment::Unmodified(already_escaped)))
}
}
}
}

enum EscapedHtmlSegment<'a> {
Modified(&'a [u8]),
Unmodified(&'a [u8]),
}

fn term_color_to_html_color(color: Color) -> Cow<'static, str> {
// All the CSS color keywords are available at:
// https://drafts.csswg.org/css-color/#named-colors
match color {
Color::Black => Cow::Borrowed("black"),
Color::Blue => Cow::Borrowed("blue"),
Color::Green => Cow::Borrowed("green"),
Color::Red => Cow::Borrowed("red"),
Color::Cyan => Cow::Borrowed("cyan"),
Color::Magenta => Cow::Borrowed("magenta"),
Color::Yellow => Cow::Borrowed("yellow"),
Color::White => Cow::Borrowed("white"),

Color::Rgb(r, g, b) => Cow::Owned(format!("rgb({},{},{})", r, g, b)),

// FIXME: should we support ANSI colors?
Color::Ansi256(_) => unreachable!(),

anything => panic!("Unknown color code: {:?}", anything),
}
}

/// Whether the original and suggested code are visually similar enough to warrant extra wording.
pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
// FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
1 change: 1 addition & 0 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
@@ -649,6 +649,7 @@ fn test_debugging_options_tracking_hash() {
untracked!(emit_stack_sizes, true);
untracked!(future_incompat_test, true);
untracked!(hir_stats, true);
untracked!(html_output, true);
untracked!(identify_regions, true);
untracked!(incremental_ignore_spans, true);
untracked!(incremental_info, true);
Loading