about summary refs log tree commit diff
path: root/src/librustdoc/passes/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/passes/mod.rs')
-rw-r--r--src/librustdoc/passes/mod.rs83
1 files changed, 83 insertions, 0 deletions
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index e897b9a7de5..23581d00511 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -8,6 +8,8 @@ use rustc::util::nodemap::DefIdSet;
 use std::mem;
 use std::fmt;
 use syntax::ast::NodeId;
+use syntax_pos::Span;
+use std::ops::Range;
 
 use clean::{self, GetDefId, Item};
 use core::{DocContext, DocAccessLevels};
@@ -396,3 +398,84 @@ pub fn look_for_tests<'a, 'tcx: 'a, 'rcx: 'a>(
         }
     }
 }
+
+/// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
+///
+/// This method will return `None` if we cannot construct a span from the source map or if the
+/// attributes are not all sugared doc comments. It's difficult to calculate the correct span in
+/// that case due to escaping and other source features.
+crate fn source_span_for_markdown_range(
+    cx: &DocContext,
+    markdown: &str,
+    md_range: &Range<usize>,
+    attrs: &clean::Attributes,
+) -> Option<Span> {
+    let is_all_sugared_doc = attrs.doc_strings.iter().all(|frag| match frag {
+        clean::DocFragment::SugaredDoc(..) => true,
+        _ => false,
+    });
+
+    if !is_all_sugared_doc {
+        return None;
+    }
+
+    let snippet = cx
+        .sess()
+        .source_map()
+        .span_to_snippet(span_of_attrs(attrs))
+        .ok()?;
+
+    let starting_line = markdown[..md_range.start].lines().count() - 1;
+    let ending_line = markdown[..md_range.end].lines().count() - 1;
+
+    // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we only
+    // we can treat CRLF and LF line endings the same way.
+    let mut src_lines = snippet.split_terminator('\n');
+    let md_lines = markdown.split_terminator('\n');
+
+    // The number of bytes from the source span to the markdown span that are not part
+    // of the markdown, like comment markers.
+    let mut start_bytes = 0;
+    let mut end_bytes = 0;
+
+    'outer: for (line_no, md_line) in md_lines.enumerate() {
+        loop {
+            let source_line = src_lines.next().expect("could not find markdown in source");
+            match source_line.find(md_line) {
+                Some(offset) => {
+                    if line_no == starting_line {
+                        start_bytes += offset;
+
+                        if starting_line == ending_line {
+                            break 'outer;
+                        }
+                    } else if line_no == ending_line {
+                        end_bytes += offset;
+                        break 'outer;
+                    } else if line_no < starting_line {
+                        start_bytes += source_line.len() - md_line.len();
+                    } else {
+                        end_bytes += source_line.len() - md_line.len();
+                    }
+                    break;
+                }
+                None => {
+                    // Since this is a source line that doesn't include a markdown line,
+                    // we have to count the newline that we split from earlier.
+                    if line_no <= starting_line {
+                        start_bytes += source_line.len() + 1;
+                    } else {
+                        end_bytes += source_line.len() + 1;
+                    }
+                }
+            }
+        }
+    }
+
+    let sp = span_of_attrs(attrs).from_inner_byte_pos(
+        md_range.start + start_bytes,
+        md_range.end + start_bytes + end_bytes,
+    );
+
+    Some(sp)
+}