diff --git a/crates/ty_ide/src/docstring.rs b/crates/ty_ide/src/docstring.rs
index b749b1feb3..163dbe1ae2 100644
--- a/crates/ty_ide/src/docstring.rs
+++ b/crates/ty_ide/src/docstring.rs
@@ -65,11 +65,7 @@ impl Docstring {
/// Render the docstring for markdown display
pub fn render_markdown(&self) -> String {
let trimmed = documentation_trim(&self.0);
- // TODO: now actually parse it and "render" it to markdown.
- //
- // For now we just wrap the content in a plaintext codeblock
- // to avoid the contents erroneously being interpreted as markdown.
- format!("```text\n{trimmed}\n```")
+ render_markdown(&trimmed)
}
/// Extract parameter documentation from popular docstring formats.
@@ -153,6 +149,223 @@ fn documentation_trim(docs: &str) -> String {
output
}
+/// Given a presumed reStructuredText docstring, render it to GitHub Flavored Markdown.
+///
+/// This function assumes the input has had its whitespace normalized by `documentation_trim`,
+/// so leading whitespace is always a space, and newlines are always `\n`.
+///
+/// The general approach here is:
+///
+/// * Preserve the docstring verbatim by default, ensuring indent/linewraps are preserved
+/// * Escape problematic things where necessary (bare `__dunder__` => `\_\_dunder\_\_`)
+/// * Introduce code fences where appropriate
+///
+/// The first rule is significant in ensuring various docstring idioms render clearly.
+/// In particular ensuring things like this are faithfully rendered:
+///
+/// ```text
+/// param1 -- a good parameter
+/// param2 -- another good parameter
+/// with longer docs
+/// ```
+///
+/// If we didn't go out of our way to preserve the indentation and line-breaks, markdown would
+/// constantly render inputs like that into abominations like:
+///
+/// ```html
+///
+/// param1 -- a good parameter param2 -- another good parameter
+///
+///
+///
+/// with longer docs
+///
+/// ```
+fn render_markdown(docstring: &str) -> String {
+ // TODO: there is a convention that `singletick` is for items that can
+ // be looked up in-scope while ``multitick`` is for opaque inline code.
+ // While rendering this we should make note of all the `singletick` locations
+ // and (possibly in a higher up piece of logic) try to resolve the names for
+ // cross-linking. (Similar to `TypeDetails` in the type formatting code.)
+ let mut output = String::new();
+ let mut first_line = true;
+ let mut block_indent = 0;
+ let mut in_doctest = false;
+ let mut starting_literal = false;
+ let mut in_literal = false;
+ let mut in_any_code = false;
+ for untrimmed_line in docstring.lines() {
+ // We can assume leading whitespace has been normalized
+ let mut line = untrimmed_line.trim_start_matches(' ');
+ let line_indent = untrimmed_line.len() - line.len();
+
+ // First thing's first, add a newline to start the new line
+ if !first_line {
+ // If we're not in a codeblock, add trailing space to the line to authentically wrap it
+ // (Lines ending with two spaces tell markdown to preserve a linebreak)
+ if !in_any_code {
+ output.push_str(" ");
+ }
+ // Only push newlines if we're not scanning for a real line
+ if !starting_literal {
+ output.push('\n');
+ }
+ }
+
+ // If we're in a literal block and we find a non-empty dedented line, end the block
+ // TODO: we should remove all the trailing blank lines
+ // (Just pop all trailing `\n` from `output`?)
+ if in_literal && line_indent < block_indent && !line.is_empty() {
+ in_literal = false;
+ in_any_code = false;
+ block_indent = 0;
+ output.push_str("```\n");
+ }
+
+ // We previously entered a literal block and we just found our first non-blank line
+ // So now we're actually in the literal block
+ if starting_literal && !line.is_empty() {
+ starting_literal = false;
+ in_literal = true;
+ in_any_code = true;
+ block_indent = line_indent;
+ // TODO: I hope people don't have literal blocks about markdown code fence syntax
+ // TODO: should we not be this aggressive? Let it autodetect?
+ // TODO: respect `.. code-block::` directives:
+ //
+ output.push_str("\n```python\n");
+ }
+
+ // If we're not in a codeblock and we see something that signals a doctest, start one
+ if !in_any_code && line.starts_with(">>>") {
+ block_indent = line_indent;
+ in_doctest = true;
+ in_any_code = true;
+ // TODO: is there something more specific? `pycon`?
+ output.push_str("```python\n");
+ }
+
+ // If we're not in a codeblock and we see something that signals a literal block, start one
+ if !in_any_code && let Some(without_lit) = line.strip_suffix("::") {
+ let trimmed_without_lit = without_lit.trim();
+ if let Some(character) = trimmed_without_lit.chars().next_back() {
+ if character.is_whitespace() {
+ // Remove the marker completely
+ line = trimmed_without_lit;
+ } else {
+ // Only remove the first `:`
+ line = line.strip_suffix(":").unwrap();
+ }
+ } else {
+ // Delete whole line
+ line = trimmed_without_lit;
+ }
+ starting_literal = true;
+ }
+
+ // Add this line's indentation.
+ // We could subtract the block_indent here but in practice it's uglier
+ // TODO: should we not do this if the `line.is_empty()`? When would it matter?
+ for _ in 0..line_indent {
+ // If we're not in a codeblock use non-breaking spaces to preserve the indent
+ if !in_any_code {
+ // TODO: would the raw unicode codepoint be handled *better* or *worse*
+ // by various IDEs? VS Code handles this approach well, at least.
+ output.push_str(" ");
+ } else {
+ output.push(' ');
+ }
+ }
+
+ if !in_any_code {
+ // This line is plain text, so we need to escape things that are inert in reST
+ // but active syntax in markdown... but not if it's inside `inline code`.
+ // Inline-code syntax is shared by reST and markdown which is really convenient
+ // except we need to find and parse it anyway to do this escaping properly! :(
+ // For now we assume `inline code` does not span a line (I'm not even sure if can).
+ //
+ // Things that need to be escaped: underscores
+ //
+ // e.g. we want __init__ => \_\_init\_\_ but `__init__` => `__init__`
+ let escape = |input: &str| input.replace('_', "\\_");
+
+ let mut in_inline_code = false;
+ let mut first_chunk = true;
+ let mut opening_tick_count = 0;
+ let mut current_tick_count = 0;
+ for chunk in line.split('`') {
+ // First chunk is definitionally not in inline-code and so always plaintext
+ if first_chunk {
+ first_chunk = false;
+ output.push_str(&escape(chunk));
+ continue;
+ }
+ // Not in first chunk, emit the ` between the last chunk and this one
+ output.push('`');
+ current_tick_count += 1;
+
+ // If we're in an inline block and have enough close-ticks to terminate it, do so.
+ // TODO: we parse ``hello```there` as (hello)(there) which probably isn't correct
+ // (definitely not for markdown) but it's close enough for horse grenades in this
+ // MVP impl. Notably we're verbatime emitting all the `'s so as long as reST and
+ // markdown agree we're *fine*. The accuracy of this parsing only affects the
+ // accuracy of where we apply escaping (so we need to misparse and see escapables
+ // for any of this to matter).
+ if opening_tick_count > 0 && current_tick_count >= opening_tick_count {
+ opening_tick_count = 0;
+ current_tick_count = 0;
+ in_inline_code = false;
+ }
+
+ // If this chunk is completely empty we're just in a run of ticks, continue
+ if chunk.is_empty() {
+ continue;
+ }
+
+ // Ok the chunk is non-empty, our run of ticks is complete
+ if in_inline_code {
+ // The previous check for >= open_tick_count didn't trip, so these can't close
+ // and these ticks will be verbatim rendered in the content
+ current_tick_count = 0;
+ } else if current_tick_count > 0 {
+ // Ok we're now in inline code
+ opening_tick_count = current_tick_count;
+ current_tick_count = 0;
+ in_inline_code = true;
+ }
+
+ // Finally include the content either escaped or not
+ if in_inline_code {
+ output.push_str(chunk);
+ } else {
+ output.push_str(&escape(chunk));
+ }
+ }
+ // NOTE: explicitly not "flushing" the ticks here.
+ // We respect however the user closed their inline code.
+ } else if line.is_empty() {
+ if in_doctest {
+ // This is the end of a doctest
+ block_indent = 0;
+ in_any_code = false;
+ in_literal = false;
+ output.push_str("```");
+ }
+ } else {
+ // Print the line verbatim, it's in code
+ output.push_str(line);
+ }
+
+ first_line = false;
+ }
+ // Flush codeblock
+ if in_any_code {
+ output.push_str("\n```");
+ }
+
+ output
+}
+
/// Extract parameter documentation from Google-style docstrings.
fn extract_google_style_params(docstring: &str) -> HashMap {
let mut param_docs = HashMap::new();
@@ -490,6 +703,511 @@ mod tests {
use super::*;
+ // A nice doctest that is surrounded by prose
+ #[test]
+ fn dunder_escape() {
+ let docstring = r#"
+ Here _this_ and ___that__ should be escaped
+ Here *this* and **that** should be untouched
+ Here `this` and ``that`` should be untouched
+
+ Here `_this_` and ``__that__`` should be untouched
+ Here `_this_` ``__that__`` should be untouched
+ `_this_too_should_be_untouched_`
+
+ Here `_this_```__that__`` should be untouched but this_is_escaped
+ Here ``_this_```__that__` should be untouched but this_is_escaped
+
+ Here `_this_ and _that_ should be escaped (but isn't)
+ Here _this_ and _that_` should be escaped
+ `Here _this_ and _that_ should be escaped (but isn't)
+ Here _this_ and _that_ should be escaped`
+
+ Here ```_is_``__a__`_balanced_``_mess_```
+ Here ```_is_`````__a__``_random_````_mess__````
+ ```_is_`````__a__``_random_````_mess__````
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ Here _this_ and ___that__ should be escaped
+ Here *this* and **that** should be untouched
+ Here `this` and ``that`` should be untouched
+
+ Here `_this_` and ``__that__`` should be untouched
+ Here `_this_` ``__that__`` should be untouched
+ `_this_too_should_be_untouched_`
+
+ Here `_this_```__that__`` should be untouched but this_is_escaped
+ Here ``_this_```__that__` should be untouched but this_is_escaped
+
+ Here `_this_ and _that_ should be escaped (but isn't)
+ Here _this_ and _that_` should be escaped
+ `Here _this_ and _that_ should be escaped (but isn't)
+ Here _this_ and _that_ should be escaped`
+
+ Here ```_is_``__a__`_balanced_``_mess_```
+ Here ```_is_`````__a__``_random_````_mess__````
+ ```_is_`````__a__``_random_````_mess__````
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ Here \_this\_ and \_\_\_that\_\_ should be escaped
+ Here *this* and **that** should be untouched
+ Here `this` and ``that`` should be untouched
+
+ Here `_this_` and ``__that__`` should be untouched
+ Here `_this_` ``__that__`` should be untouched
+ `_this_too_should_be_untouched_`
+
+ Here `_this_```__that__`` should be untouched but this\_is\_escaped
+ Here ``_this_```__that__` should be untouched but this\_is\_escaped
+
+ Here `_this_ and _that_ should be escaped (but isn't)
+ Here \_this\_ and \_that\_` should be escaped
+ `Here _this_ and _that_ should be escaped (but isn't)
+ Here \_this\_ and \_that\_ should be escaped`
+
+ Here ```_is_``__a__`_balanced_``_mess_```
+ Here ```_is_`````__a__``\_random\_````_mess__````
+ ```_is_`````__a__``\_random\_````_mess__````
+ ");
+ }
+
+ // A literal block where the `::` is flush with the paragraph
+ // and should become `:`
+ #[test]
+ fn literal_colon() {
+ let docstring = r#"
+ Check out this great example code::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ You love to see it.
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r#"
+ Check out this great example code::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ You love to see it.
+ "#);
+
+ assert_snapshot!(docstring.render_markdown(), @r#"
+ Check out this great example code:
+ ```python
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ ```
+ You love to see it.
+ "#);
+ }
+
+ // A literal block where the `::` with the paragraph
+ // and should be erased
+ #[test]
+ fn literal_space() {
+ let docstring = r#"
+ Check out this great example code ::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ You love to see it.
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r#"
+ Check out this great example code ::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ You love to see it.
+ "#);
+
+ assert_snapshot!(docstring.render_markdown(), @r#"
+ Check out this great example code :
+ ```python
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ ```
+ You love to see it.
+ "#);
+ }
+
+ // A literal block where the `::` is floating
+ // and the whole line should be deleted
+ #[test]
+ fn literal_own_line() {
+ let docstring = r#"
+ Check out this great example code
+ ::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ You love to see it.
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r#"
+ Check out this great example code
+ ::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ You love to see it.
+ "#);
+
+ assert_snapshot!(docstring.render_markdown(), @r#"
+ Check out this great example code
+
+ ```python
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+
+ ```
+ You love to see it.
+ "#);
+ }
+
+ // A literal block where the blank lines are missing
+ // and I have no idea what Should happen but let's record what Does
+ #[test]
+ fn literal_squeezed() {
+ let docstring = r#"
+ Check out this great example code::
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+ You love to see it.
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r#"
+ Check out this great example code::
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+ You love to see it.
+ "#);
+
+ assert_snapshot!(docstring.render_markdown(), @r#"
+ Check out this great example code:
+ ```python
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+ ```
+ You love to see it.
+ "#);
+ }
+
+ // A literal block where the docstring just ends
+ // and we should tidy up
+ #[test]
+ fn literal_flush() {
+ let docstring = r#"
+ Check out this great example code::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")"#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r#"
+ Check out this great example code::
+
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+ "#);
+
+ assert_snapshot!(docstring.render_markdown(), @r#"
+ Check out this great example code:
+ ```python
+ x_y = "hello"
+
+ if len(x_y) > 4:
+ print(x_y)
+ else:
+ print("too short :(")
+
+ print("done")
+ ```
+ "#);
+ }
+
+ // A nice doctest that is surrounded by prose
+ #[test]
+ fn doctest_simple() {
+ let docstring = r#"
+ This is a function description
+
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ As you can see it did the thing!
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ This is a function description
+
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ As you can see it did the thing!
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ This is a function description
+
+ ```python
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+ ```
+ As you can see it did the thing!
+ ");
+ }
+
+ // A nice doctest that is surrounded by prose with an indent
+ #[test]
+ fn doctest_simple_indent() {
+ let docstring = r#"
+ This is a function description
+
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ As you can see it did the thing!
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ This is a function description
+
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ As you can see it did the thing!
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ This is a function description
+
+ ```python
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+ ```
+ As you can see it did the thing!
+ ");
+ }
+
+ // A doctest that has nothing around it
+ #[test]
+ fn doctest_flush() {
+ let docstring = r#">>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing"#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ ```python
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+ ```
+ ");
+ }
+
+ // A doctest embedded in a literal block (it's just a literal block)
+ #[test]
+ fn literal_doctest() {
+ let docstring = r#"
+ This is a function description::
+
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ As you can see it did the thing!
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ This is a function description::
+
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ As you can see it did the thing!
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ This is a function description:
+ ```python
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+
+ ```
+ As you can see it did the thing!
+ ");
+ }
+
+ #[test]
+ fn doctest_indent_flush() {
+ let docstring = r#"
+ And so you can see that
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing"#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ And so you can see that
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ And so you can see that
+ ```python
+ >>> thing.do_thing()
+ wow it did the thing
+ >>> thing.do_other_thing()
+ it sure did the thing
+ ```
+ ");
+ }
+
#[test]
fn test_google_style_parameter_documentation() {
let docstring = r#"
@@ -530,19 +1248,16 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
-
- Args:
- param1 (str): The first parameter description
- param2 (int): The second parameter description
- This is a continuation of param2 description.
- param3: A parameter without type annotation
-
- Returns:
- str: The return value description
-
- ```
+ This is a function description.
+
+ Args:
+ param1 (str): The first parameter description
+ param2 (int): The second parameter description
+ This is a continuation of param2 description.
+ param3: A parameter without type annotation
+
+ Returns:
+ str: The return value description
");
}
@@ -604,24 +1319,77 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
+ This is a function description.
+
+ Parameters
+ ----------
+ param1 : str
+ The first parameter description
+ param2 : int
+ The second parameter description
+ This is a continuation of param2 description.
+ param3
+ A parameter without type annotation
+
+ Returns
+ -------
+ str
+ The return value description
+ ");
+ }
- Parameters
- ----------
- param1 : str
- The first parameter description
- param2 : int
- The second parameter description
- This is a continuation of param2 description.
- param3
- A parameter without type annotation
+ #[test]
+ fn test_pep257_style_parameter_documentation() {
+ let docstring = r#"Insert an entry into the list of warnings filters (at the front).
- Returns
- -------
- str
- The return value description
+ 'param1' -- The first parameter description
+ 'param2' -- The second parameter description
+ This is a continuation of param2 description.
+ 'param3' -- A parameter without type annotation
+ >>> print repr(foo.__doc__)
+ '\n This is the second line of the docstring.\n '
+ >>> foo.__doc__.splitlines()
+ ['', ' This is the second line of the docstring.', ' ']
+ >>> trim(foo.__doc__)
+ 'This is the second line of the docstring.'
+ "#;
+
+ let docstring = Docstring::new(docstring.to_owned());
+ let param_docs = docstring.parameter_documentation();
+ assert!(param_docs.is_empty());
+
+ assert_snapshot!(docstring.render_plaintext(), @r"
+ Insert an entry into the list of warnings filters (at the front).
+
+ 'param1' -- The first parameter description
+ 'param2' -- The second parameter description
+ This is a continuation of param2 description.
+ 'param3' -- A parameter without type annotation
+
+ >>> print repr(foo.__doc__)
+ '\n This is the second line of the docstring.\n '
+ >>> foo.__doc__.splitlines()
+ ['', ' This is the second line of the docstring.', ' ']
+ >>> trim(foo.__doc__)
+ 'This is the second line of the docstring.'
+ ");
+
+ assert_snapshot!(docstring.render_markdown(), @r"
+ Insert an entry into the list of warnings filters (at the front).
+
+ 'param1' -- The first parameter description
+ 'param2' -- The second parameter description
+ This is a continuation of param2 description.
+ 'param3' -- A parameter without type annotation
+
+ ```python
+ >>> print repr(foo.__doc__)
+ '\n This is the second line of the docstring.\n '
+ >>> foo.__doc__.splitlines()
+ ['', ' This is the second line of the docstring.', ' ']
+ >>> trim(foo.__doc__)
+ 'This is the second line of the docstring.'
```
");
}
@@ -638,12 +1406,7 @@ mod tests {
assert_snapshot!(docstring.render_plaintext(), @"This is a simple function description without parameter documentation.");
- assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a simple function description without parameter documentation.
-
- ```
- ");
+ assert_snapshot!(docstring.render_markdown(), @"This is a simple function description without parameter documentation.");
}
#[test]
@@ -692,19 +1455,16 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
-
- Args:
- param1 (str): Google-style parameter
- param2 (int): Another Google-style parameter
-
- Parameters
- ----------
- param3 : bool
- NumPy-style parameter
-
- ```
+ This is a function description.
+
+ Args:
+ param1 (str): Google-style parameter
+ param2 (int): Another Google-style parameter
+
+ Parameters
+ ----------
+ param3 : bool
+ NumPy-style parameter
");
}
@@ -750,17 +1510,14 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
-
- :param str param1: The first parameter description
- :param int param2: The second parameter description
- This is a continuation of param2 description.
- :param param3: A parameter without type annotation
- :returns: The return value description
+ This is a function description.
+
+ :param str param1: The first parameter description
+ :param int param2: The second parameter description
+ This is a continuation of param2 description.
+ :param param3: A parameter without type annotation
+ :returns: The return value description
:rtype: str
-
- ```
");
}
@@ -818,21 +1575,18 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
-
- Args:
- param1 (str): Google-style parameter
-
- :param int param2: reST-style parameter
- :param param3: Another reST-style parameter
-
- Parameters
- ----------
- param4 : bool
- NumPy-style parameter
-
- ```
+ This is a function description.
+
+ Args:
+ param1 (str): Google-style parameter
+
+ :param int param2: reST-style parameter
+ :param param3: Another reST-style parameter
+
+ Parameters
+ ----------
+ param4 : bool
+ NumPy-style parameter
");
}
@@ -894,25 +1648,22 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
-
- Parameters
- ----------
- param1 : str
- The first parameter description
- param2 : int
- The second parameter description
- This is a continuation of param2 description.
- param3
- A parameter without type annotation
-
- Returns
- -------
- str
- The return value description
-
- ```
+ This is a function description.
+
+ Parameters
+ ----------
+ param1 : str
+ The first parameter description
+ param2 : int
+ The second parameter description
+ This is a continuation of param2 description.
+ param3
+ A parameter without type annotation
+
+ Returns
+ -------
+ str
+ The return value description
");
}
@@ -965,20 +1716,17 @@ mod tests {
");
assert_snapshot!(docstring.render_markdown(), @r"
- ```text
- This is a function description.
-
- Parameters
- ----------
- param1 : str
- The first parameter description
- param2 : int
- The second parameter description
- This is a continuation of param2 description.
- param3
- A parameter without type annotation
-
- ```
+ This is a function description.
+
+ Parameters
+ ----------
+ param1 : str
+ The first parameter description
+ param2 : int
+ The second parameter description
+ This is a continuation of param2 description.
+ param3
+ A parameter without type annotation
");
}
@@ -1028,14 +1776,11 @@ mod tests {
");
assert_snapshot!(docstring_windows.render_markdown(), @r"
- ```text
- This is a function description.
-
- Args:
- param1 (str): The first parameter
- param2 (int): The second parameter
-
- ```
+ This is a function description.
+
+ Args:
+ param1 (str): The first parameter
+ param2 (int): The second parameter
");
assert_snapshot!(docstring_mac.render_plaintext(), @r"
@@ -1047,14 +1792,11 @@ mod tests {
");
assert_snapshot!(docstring_mac.render_markdown(), @r"
- ```text
- This is a function description.
-
- Args:
- param1 (str): The first parameter
- param2 (int): The second parameter
-
- ```
+ This is a function description.
+
+ Args:
+ param1 (str): The first parameter
+ param2 (int): The second parameter
");
assert_snapshot!(docstring_unix.render_plaintext(), @r"
@@ -1066,14 +1808,11 @@ mod tests {
");
assert_snapshot!(docstring_unix.render_markdown(), @r"
- ```text
- This is a function description.
-
- Args:
- param1 (str): The first parameter
- param2 (int): The second parameter
-
- ```
+ This is a function description.
+
+ Args:
+ param1 (str): The first parameter
+ param2 (int): The second parameter
");
}
}
diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs
index f1de7316bd..1b8716f18e 100644
--- a/crates/ty_ide/src/hover.rs
+++ b/crates/ty_ide/src/hover.rs
@@ -245,14 +245,11 @@ mod tests {
) -> Unknown
```
---
- ```text
- This is such a great func!!
-
- Args:
- a: first for a reason
- b: coming for `a`'s title
-
- ```
+ This is such a great func!!
+
+ Args:
+ a: first for a reason
+ b: coming for `a`'s title
---------------------------------------------
info[hover]: Hovered content is
--> main.py:11:1
@@ -303,14 +300,11 @@ mod tests {
) -> Unknown
```
---
- ```text
- This is such a great func!!
-
- Args:
- a: first for a reason
- b: coming for `a`'s title
-
- ```
+ This is such a great func!!
+
+ Args:
+ a: first for a reason
+ b: coming for `a`'s title
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:5
@@ -369,14 +363,11 @@ mod tests {
```
---
- ```text
- This is such a great class!!
-
- Don't you know?
-
+ This is such a great class!!
+
+ Don't you know?
+
Everyone loves my class!!
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:24:1
@@ -434,14 +425,11 @@ mod tests {
```
---
- ```text
- This is such a great class!!
-
- Don't you know?
-
+ This is such a great class!!
+
+ Don't you know?
+
Everyone loves my class!!
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:7
@@ -497,10 +485,7 @@ mod tests {
```
---
- ```text
initializes MyClass (perfectly)
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:24:5
@@ -556,10 +541,7 @@ mod tests {
```
---
- ```text
initializes MyClass (perfectly)
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:11
@@ -618,14 +600,11 @@ mod tests {
```
---
- ```text
- This is such a great class!!
-
- Don't you know?
-
+ This is such a great class!!
+
+ Don't you know?
+
Everyone loves my class!!
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:23:5
@@ -692,14 +671,11 @@ mod tests {
) -> Unknown
```
---
- ```text
- This is such a great func!!
-
- Args:
- a: first for a reason
- b: coming for `a`'s title
-
- ```
+ This is such a great func!!
+
+ Args:
+ a: first for a reason
+ b: coming for `a`'s title
---------------------------------------------
info[hover]: Hovered content is
--> main.py:25:3
@@ -973,10 +949,7 @@ def ab(a: str): ...
(a: int) -> Unknown
```
---
- ```text
the int overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1036,10 +1009,7 @@ def ab(a: str):
(a: str) -> Unknown
```
---
- ```text
the int overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1105,10 +1075,7 @@ def ab(a: int):
) -> Unknown
```
---
- ```text
the two arg overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1168,10 +1135,7 @@ def ab(a: int):
(a: int) -> Unknown
```
---
- ```text
the two arg overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1243,10 +1207,7 @@ def ab(a: int, *, c: int):
) -> Unknown
```
---
- ```text
keywordless overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1318,10 +1279,7 @@ def ab(a: int, *, c: int):
) -> Unknown
```
---
- ```text
keywordless overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1386,10 +1344,7 @@ def ab(a: int, *, c: int):
) -> Unknown
```
---
- ```text
The first overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:19:1
@@ -1441,10 +1396,7 @@ def ab(a: int, *, c: int):
(a: str) -> Unknown
```
---
- ```text
The first overload
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:19:1
@@ -1494,12 +1446,9 @@ def ab(a: int, *, c: int):
```
---
- ```text
- The cool lib_py module!
-
+ The cool lib/_py module!
+
Wow this module rocks.
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:4:1
@@ -1544,12 +1493,9 @@ def ab(a: int, *, c: int):
Wow this module rocks.
---------------------------------------------
- ```text
- The cool lib_py module!
-
+ The cool lib/_py module!
+
Wow this module rocks.
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:8
@@ -2499,10 +2445,7 @@ def ab(a: int, *, c: int):
bound method int.__add__(value: int, /) -> int
```
---
- ```text
Return self+value.
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:12
@@ -2618,10 +2561,7 @@ def ab(a: int, *, c: int):
int | float
```
---
- ```text
Convert a string or number to a floating-point number, if possible.
-
- ```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:4