diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples.py index 296da3d816545e..68c7a528a6cf87 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_code_examples.py @@ -795,6 +795,19 @@ def rst_literal_skipped_doctest(): pass +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + def rst_directive_skipped_not_indented(): """ .. code-block:: python @@ -828,3 +841,496 @@ def rst_directive_skipped_doctest(): Done. """ pass + + +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### + + +def markdown_simple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_simple_continued(): + """ + Do cool stuff. + + ```python + def cool_stuff( x ): + print( f"hi {x}" ); + ``` + + Done. + """ + pass + + +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. + + ``` + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. + + ~~~py + cool_stuff( 1 ) + ~~~ + + Done. + """ + pass + + +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + `````` + + Done. + """ + pass + + +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ```invalid + ''' + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. + + `````` + do_something( ''' + ``` + did i trick you? + ``` + ''' ) + `````` + + Done. + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + + + """ + pass + + +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + """ + pass + + +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ```""" + pass + + +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 )""" + pass + + +def markdown_with_blank_lines(): + """ + Do cool stuff. + + ```py + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_odd_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. + + ```py + + + cool_stuff( 1 ) + + + ``` + + Done. + """ + pass + + +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. + + ```py + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. + + ```python + cool_stuff( 1 ) + + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` + + Now the code block is closed + """ + pass + + +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print( 5 ) + ``` + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass + + +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. + """ + pass + + +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. +# +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. + + ```py +cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. +# +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_doctest(): + """ + Do cool stuff. + + ```py + >>> cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_literal(): + """ + Do cool stuff. + + ```py + And do this:: + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_directive(): + """ + Do cool stuff. + + ```py + .. code-block:: python + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass diff --git a/crates/ruff_python_formatter/src/expression/string/docstring.rs b/crates/ruff_python_formatter/src/expression/string/docstring.rs index c90fa4480b6352..d030543c7c9c6e 100644 --- a/crates/ruff_python_formatter/src/expression/string/docstring.rs +++ b/crates/ruff_python_formatter/src/expression/string/docstring.rs @@ -354,6 +354,16 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> { )?; } } + CodeExampleKind::Markdown(fenced) => { + // This looks suspicious, but it's consistent with the whitespace + // normalization that will occur anyway. + let indent = " ".repeat(fenced.opening_fence_indent.to_usize()); + for docline in formatted_lines { + self.print_one( + &docline.map(|line| std::format!("{indent}{line}")), + )?; + } + } } } } @@ -648,6 +658,18 @@ impl<'src> CodeExample<'src> { }; self.kind = Some(CodeExampleKind::Rst(litblock)); } + Some(CodeExampleKind::Markdown(fenced)) => { + let Some(fenced) = fenced.add_code_line(original, queue) else { + // For Markdown, the last line in a block should be printed + // as-is. Especially since the last line in many Markdown + // fenced code blocks is identical to the start of a code + // block. So if we try to start a new code block with + // the last line, we risk opening another Markdown block + // inappropriately. + return; + }; + self.kind = Some(CodeExampleKind::Markdown(fenced)); + } } } @@ -681,6 +703,9 @@ impl<'src> CodeExample<'src> { } else if let Some(litblock) = CodeExampleRst::new(original) { self.kind = Some(CodeExampleKind::Rst(litblock)); queue.push_back(CodeExampleAddAction::Print { original }); + } else if let Some(fenced) = CodeExampleMarkdown::new(original) { + self.kind = Some(CodeExampleKind::Markdown(fenced)); + queue.push_back(CodeExampleAddAction::Print { original }); } else { queue.push_back(CodeExampleAddAction::Print { original }); } @@ -707,6 +732,10 @@ enum CodeExampleKind<'src> { /// [literal block]: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks /// [code block directive]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block Rst(CodeExampleRst<'src>), + /// Code found from a Markdown "[fenced code block]". + /// + /// [fenced code block]: https://spec.commonmark.org/0.30/#fenced-code-blocks + Markdown(CodeExampleMarkdown<'src>), } impl<'src> CodeExampleKind<'src> { @@ -718,6 +747,7 @@ impl<'src> CodeExampleKind<'src> { match *self { CodeExampleKind::Doctest(ref doctest) => &doctest.lines, CodeExampleKind::Rst(ref mut litblock) => litblock.indented_code(), + CodeExampleKind::Markdown(ref fenced) => &fenced.lines, } } @@ -731,6 +761,7 @@ impl<'src> CodeExampleKind<'src> { match self { CodeExampleKind::Doctest(doctest) => doctest.lines, CodeExampleKind::Rst(litblock) => litblock.lines, + CodeExampleKind::Markdown(fenced) => fenced.lines, } } } @@ -1156,6 +1187,226 @@ impl<'src> CodeExampleRst<'src> { } } +/// Represents a code example extracted from a Markdown [fenced code block]. +/// +/// [fenced code block]: https://spec.commonmark.org/0.30/#fenced-code-blocks +#[derive(Debug)] +struct CodeExampleMarkdown<'src> { + /// The lines that have been seen so far that make up the block. + lines: Vec>, + + /// The indent of the line "opening" fence of this block measured via + /// `indentation_length`. + /// + /// This indentation is trimmed from the indentation of every line in the + /// body of the code block, + opening_fence_indent: TextSize, + + /// The kind of fence, backticks or tildes, used for this block. We need to + /// keep track of which kind was used to open the block in order to look + /// for a correct close of the block. + fence_kind: MarkdownFenceKind, + + /// The size of the fence, in codepoints, in the opening line. A correct + /// close of the fence must use *at least* this many characters. In other + /// words, this is the number of backticks or tildes that opened the fenced + /// code block. + fence_len: usize, +} + +impl<'src> CodeExampleMarkdown<'src> { + /// Looks for the start of a Markdown [fenced code block]. + /// + /// If the start of a block is found, then this returns a correctly + /// initialized Markdown code block. Callers should print the line as given + /// as it is not retained as part of the block. + /// + /// [fenced code block]: https://spec.commonmark.org/0.30/#fenced-code-blocks + fn new(original: InputDocstringLine<'src>) -> Option> { + let (opening_fence_indent, rest) = indent_with_suffix(original.line); + // Quit quickly in the vast majority of cases. + if !rest.starts_with("```") && !rest.starts_with("~~~") { + return None; + } + + static FENCE_START: Lazy = Lazy::new(|| { + Regex::new( + r"(?xm) + ^ + (?: + # In the backtick case, info strings (following the fence) + # cannot contain backticks themselves, since it would + # introduce ambiguity with parsing inline code. In other + # words, if we didn't specifically exclude matching ` + # in the info string for backtick fences, then we might + # erroneously consider something to be a code fence block + # that is actually inline code. + # + # NOTE: The `ticklang` and `tildlang` capture groups are + # currently unused, but there was some discussion about not + # assuming unlabeled blocks were Python. At the time of + # writing, we do assume unlabeled blocks are Python, but + # one could inspect the `ticklang` and `tildlang` capture + # groups to determine whether the block is labeled or not. + (?```+)(?:\s*(?(?i:python|py|python3|py3))[^`]*)? + | + (?~~~+)(?:\s*(?(?i:python|py|python3|py3))\p{any}*)? + ) + $ + ", + ) + .unwrap() + }); + let caps = FENCE_START.captures(rest)?; + let (fence_kind, fence_len) = if let Some(ticks) = caps.name("ticks") { + (MarkdownFenceKind::Backtick, ticks.as_str().chars().count()) + } else { + let tildes = caps + .name("tilds") + .expect("no ticks means it must be tildes"); + (MarkdownFenceKind::Tilde, tildes.as_str().chars().count()) + }; + Some(CodeExampleMarkdown { + lines: vec![], + opening_fence_indent: indentation_length(opening_fence_indent), + fence_kind, + fence_len, + }) + } + + /// Attempts to add the given line from a docstring to the Markdown code + /// snippet being collected. + /// + /// In this case, ownership is only not returned when the end of the block + /// was found, or if the block was determined to be invalid. A formatting + /// action is then pushed onto the queue. + fn add_code_line( + mut self, + original: InputDocstringLine<'src>, + queue: &mut VecDeque>, + ) -> Option> { + if self.is_end(original) { + queue.push_back(self.into_format_action()); + queue.push_back(CodeExampleAddAction::Print { original }); + return None; + } + // When a line in a Markdown fenced closed block is indented *less* + // than the opening indent, we treat the entire block as invalid. + // + // I believe that code blocks of this form are actually valid Markdown + // in some cases, but the interplay between it and our docstring + // whitespace normalization leads to undesirable outcomes. For example, + // if the line here is unindented out beyond the initial indent of the + // docstring itself, then this causes the entire docstring to have + // its indent normalized. And, at the time of writing, a subsequent + // formatting run undoes this indentation, thus violating idempotency. + if !original.line.trim_whitespace().is_empty() + && indentation_length(original.line) < self.opening_fence_indent + { + queue.push_back(self.into_reset_action()); + queue.push_back(CodeExampleAddAction::Print { original }); + return None; + } + self.push(original); + queue.push_back(CodeExampleAddAction::Kept); + Some(self) + } + + /// Returns true when given line ends this fenced code block. + fn is_end(&self, original: InputDocstringLine<'src>) -> bool { + let (_, rest) = indent_with_suffix(original.line); + // We can bail early if we don't have at least three backticks or + // tildes. + if !rest.starts_with("```") && !rest.starts_with("~~~") { + return false; + } + // We do need to check that we have the right number of + // backticks/tildes... + let fence_len = rest + .chars() + .take_while(|&ch| ch == self.fence_kind.to_char()) + .count(); + // A closing fence only needs *at least* the number of ticks/tildes + // that are in the opening fence. + if fence_len < self.fence_len { + return false; + } + // And, also, there can only be trailing whitespace. Nothing else. + assert!( + self.fence_kind.to_char().is_ascii(), + "fence char should be ASCII", + ); + if !rest[fence_len..].chars().all(is_python_whitespace) { + return false; + } + true + } + + /// Pushes the given line as part of this code example. + fn push(&mut self, original: InputDocstringLine<'src>) { + // Unlike reStructuredText blocks, for Markdown fenced code blocks, the + // indentation that we want to strip from each line is known when the + // block is opened. So we can strip it as we collect lines. + let code = indentation_trim(self.opening_fence_indent, original.line); + self.lines.push(CodeExampleLine { original, code }); + } + + /// Consume this block and turn it into a reset action. + /// + /// This occurs when we started collecting a code example from something + /// that looked like a block, but later determined that it wasn't a valid + /// block. + fn into_format_action(self) -> CodeExampleAddAction<'src> { + // Note that unlike in reStructuredText blocks, if a Markdown fenced + // code block is unclosed, then *all* remaining lines should be treated + // as part of the block[1]: + // + // > If the end of the containing block (or document) is reached and no + // > closing code fence has been found, the code block contains all of the + // > lines after the opening code fence until the end of the containing + // > block (or document). + // + // This means that we don't need to try and trim trailing empty lines. + // Those will get fed into the code formatter and ultimately stripped, + // which is what you'd expect if those lines are treated as part of the + // block. + // + // [1]: https://spec.commonmark.org/0.30/#fenced-code-blocks + CodeExampleAddAction::Format { + kind: CodeExampleKind::Markdown(self), + } + } + + /// Consume this block and turn it into a reset action. + /// + /// This occurs when we started collecting a code example from something + /// that looked like a code fence, but later determined that it wasn't a + /// valid. + fn into_reset_action(self) -> CodeExampleAddAction<'src> { + CodeExampleAddAction::Reset { code: self.lines } + } +} + +/// The kind of fence used in a Markdown code block. +/// +/// This indicates that the fence is either surrounded by fences made from +/// backticks, or fences made from tildes. +#[derive(Clone, Copy, Debug)] +enum MarkdownFenceKind { + Backtick, + Tilde, +} + +impl MarkdownFenceKind { + /// Convert the fence kind to the actual character used to build the fence. + fn to_char(&self) -> char { + match *self { + MarkdownFenceKind::Backtick => '`', + MarkdownFenceKind::Tilde => '~', + } + } +} + /// A single line in a code example found in a docstring. /// /// A code example line exists prior to formatting, and is thus in full diff --git a/crates/ruff_python_formatter/tests/normalizer.rs b/crates/ruff_python_formatter/tests/normalizer.rs index 8f01694468dcbd..2bab8915cc0543 100644 --- a/crates/ruff_python_formatter/tests/normalizer.rs +++ b/crates/ruff_python_formatter/tests/normalizer.rs @@ -82,6 +82,10 @@ impl Transformer for Normalizer { // everything after it. Talk about a hammer. Regex::new(r#"::(?s:.*)"#).unwrap() }); + static STRIP_MARKDOWN_BLOCKS: Lazy = Lazy::new(|| { + // This covers more than valid Markdown blocks, but that's OK. + Regex::new(r#"(```|~~~)\p{any}*(```|~~~|$)"#).unwrap() + }); // Start by (1) stripping everything that looks like a code // snippet, since code snippets may be completely reformatted if @@ -98,6 +102,12 @@ impl Transformer for Normalizer { "\n", ) .into_owned(); + string_literal.value = STRIP_MARKDOWN_BLOCKS + .replace_all( + &string_literal.value, + "\n", + ) + .into_owned(); // Normalize a string by (2) stripping any leading and trailing space from each // line, and (3) removing any blank lines from the start and end of the string. string_literal.value = string_literal diff --git a/crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples.py.snap index cae50ad8c6d91d..7dc1badfe2b8a5 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@docstring_code_examples.py.snap @@ -801,6 +801,19 @@ def rst_literal_skipped_doctest(): pass +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + def rst_directive_skipped_not_indented(): """ .. code-block:: python @@ -834,1716 +847,5931 @@ def rst_directive_skipped_doctest(): Done. """ pass -``` -## Outputs -### Output 1 -``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Disabled -preview = Disabled -``` -```python ############################################################################### -# DOCTEST CODE EXAMPLES +# Markdown CODE EXAMPLES # # This section shows examples of docstrings that contain code snippets in -# Python's "doctest" format. +# Markdown fenced code blocks. # -# See: https://docs.python.org/3/library/doctest.html +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks ############################################################################### -# The simplest doctest to ensure basic formatting works. -def doctest_simple(): + +def markdown_simple(): """ Do cool stuff. - >>> cool_stuff( 1 ) - 2 + ```py + cool_stuff( 1 ) + ``` + + Done. """ pass -# Another simple test, but one where the Python code -# extends over multiple lines. -def doctest_simple_continued(): +def markdown_simple_continued(): """ Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 + ```python + def cool_stuff( x ): + print( f"hi {x}" ); + ``` + + Done. """ pass -# Test that we support multiple directly adjacent -# doctests. -def doctest_adjacent(): +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): """ Do cool stuff. - >>> cool_stuff( x ) - >>> cool_stuff( y ) - 2 + ``` + cool_stuff( 1 ) + ``` + + Done. """ pass -# Test that a doctest on the last non-whitespace line of a docstring -# reformats correctly. -def doctest_last_line(): +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): """ Do cool stuff. - >>> cool_stuff( x ) + ~~~py + cool_stuff( 1 ) + ~~~ + + Done. """ pass -# Test that a doctest that continues to the last non-whitespace line of -# a docstring reformats correctly. -def doctest_last_line_continued(): +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): """ Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); + ```py + cool_stuff( 1 ) + `````` + + Done. """ pass -# Test that a doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line(): +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): """ Do cool stuff. - >>> cool_stuff( x )""" + ```py + cool_stuff( 1 ) + ''' + ```invalid + ''' + cool_stuff( 2 ) + ``` + + Done. + """ pass -# Test that a continued doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line_continued(): +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): """ Do cool stuff. - >>> cool_stuff( x ) - ... more( y )""" + `````` + do_something( ''' + ``` + did i trick you? + ``` + ''' ) + `````` + + Done. + """ pass -# Test that a doctest is correctly identified and formatted with a blank -# continuation line. -def doctest_blank_continued(): +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): """ Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... - ... print( x ) + ```py + cool_stuff( 1 ) + + + """ pass -# Tests that a blank PS2 line at the end of a doctest can get dropped. -# It is treated as part of the Python snippet which will trim the -# trailing whitespace. -def doctest_blank_end(): +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): """ Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... + ```py + cool_stuff( 1 ) + ``` """ pass -# Tests that a blank PS2 line at the end of a doctest can get dropped -# even when there is text following it. -def doctest_blank_end_then_some_text(): +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): """ Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... - - And say something else. + ```py + cool_stuff( 1 ) """ pass -# Test that a doctest containing a triple quoted string gets formatted -# correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): """ Do cool stuff. - >>> x = '''tricksy''' - """ + ```py + cool_stuff( 1 ) + ```""" pass -# Test that a doctest containing a triple quoted f-string gets -# formatted correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): """ Do cool stuff. - >>> x = f'''tricksy''' - """ + ```py + cool_stuff( 1 )""" pass -# Another nested multi-line string case, but with triple escaped double -# quotes inside a triple single quoted string. -def doctest_with_triple_escaped_double(): +def markdown_with_blank_lines(): """ Do cool stuff. - >>> x = '''\"\"\"''' + ```py + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + ``` + + Done. """ pass -# Tests that inverting the triple quoting works as expected. -def doctest_with_triple_inverted(): - ''' +def markdown_first_line_indent_uses_tabs_4spaces(): + """ Do cool stuff. - >>> x = """tricksy""" - ''' + ```py + cool_stuff( 1 ) + ``` + + Done. + """ pass -# Tests that inverting the triple quoting with an f-string works as -# expected. -def doctest_with_triple_inverted_fstring(): - ''' +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ Do cool stuff. - >>> x = f"""tricksy""" - ''' + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ pass -# Tests nested doctests are ignored. That is, we don't format doctests -# recursively. We only recognize "top level" doctests. -# -# This restriction primarily exists to avoid needing to deal with -# nesting quotes. It also seems like a generally sensible restriction, -# although it could be lifted if necessary I believe. -def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. - >>> def nested( x ): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' - pass + ```py + cool_stuff( 1 ) + ``` + Done. + """ + pass -# Tests that the starting column does not matter. -def doctest_varying_start_column(): + +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_tab_second_line_spaces(): """ Do cool stuff. - >>> assert ("Easy!") - >>> import math - >>> math.floor( 1.9 ) - 1 + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. """ pass -# Tests that long lines get wrapped... appropriately. -# -# The docstring code formatter uses the same line width settings as for -# formatting other code. This means that a line in the docstring can -# actually extend past the configured line limit. -# -# It's not quite clear whether this is desirable or not. We could in -# theory compute the intendation length of a code snippet and then -# adjust the line-width setting on a recursive call to the formatter. -# But there are assuredly pathological cases to consider. Another path -# would be to expose another formatter option for controlling the -# line-width of code snippets independently. -def doctest_long_lines(): +def markdown_odd_indentation(): """ Do cool stuff. - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) + Done. """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + pass -# Checks that a simple but invalid doctest gets skipped. -def doctest_skipped_simple(): +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): """ Do cool stuff. - >>> cool-stuff( x ): - 2 + ```py + + + cool_stuff( 1 ) + + + ``` + + Done. """ pass -# Checks that a simple doctest that is continued over multiple lines, -# but is invalid, gets skipped. -def doctest_skipped_simple_continued(): +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): """ Do cool stuff. - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 + ```py + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + ``` + + Done. """ pass -# Checks that a doctest with improper indentation gets skipped. -def doctest_skipped_inconsistent_indent(): +def markdown_weird_closing(): """ - Do cool stuff. - - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + Code block with weirdly placed closing fences. + ```python + cool_stuff( 1 ) -# Checks that a doctest with some proper indentation and some improper -# indentation is "partially" formatted. That is, the part that appears -# before the inconsistent indentation is formatted. This requires that -# the part before it is valid Python. -def doctest_skipped_partial_inconsistent_indent(): - """ - Do cool stuff. + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` - >>> def cool_stuff( x ): - ... print( x ) - ... print( f"hi {x}" ); - hi 2 + Now the code block is closed """ pass -# Checks that a doctest with improper triple single quoted string gets -# skipped. That is, the code snippet is itself invalid Python, so it is -# left as is. -def doctest_skipped_triple_incorrect(): +def markdown_over_indented(): """ - Do cool stuff. - - >>> foo( x ) - ... '''tri'''cksy''' + A docstring + over intended + ```python + print( 5 ) + ``` """ pass -# Tests that a doctest on a single line is skipped. -def doctest_skipped_one_line(): - ">>> foo( x )" - pass - - -# f-strings are not considered docstrings[1], so any doctests -# inside of them should not be formatted. -# -# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals -def doctest_skipped_fstring(): - f""" +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ Do cool stuff. - >>> cool_stuff( 1 ) - 2 + ```py + cool_stuff( 1 ) + + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. """ pass -# Test that a doctest containing a triple quoted string at least -# does not result in invalid Python code. Ideally this would format -# correctly, but at time of writing it does not. -def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): """ Do cool stuff. - >>> x = '\"\"\"' + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. """ pass -############################################################################### -# reStructuredText CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# reStructuredText formatted code blocks. +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. # -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 -############################################################################### - - -def rst_literal_simple(): +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff( 1 ) + ```py +cool_stuff( 1 ) + ``` Done. """ pass -def rst_literal_simple_continued(): +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): """ - Do cool stuff:: + Do cool stuff. - def cool_stuff( x ): - print( f"hi {x}" ); + ```py + cool_stuff( 1 ) + ``` Done. """ pass -# Tests that we can end the literal block on the second -# to last line of the docstring. -def rst_literal_second_to_last(): - """ - Do cool stuff:: - - cool_stuff( 1 ) +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. +# +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): """ - pass + Do cool stuff. + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` -# Tests that we can end the literal block on the actual -# last line of the docstring. -def rst_literal_actually_last(): + Done. """ - Do cool stuff:: - - cool_stuff( 1 )""" pass -def rst_literal_with_blank_lines(): +def markdown_skipped_doctest(): """ - Do cool stuff:: - - def cool_stuff( x ): - print( f"hi {x}" ); + Do cool stuff. - def other_stuff( y ): - print( y ) + ```py + >>> cool_stuff( 1 ) + ``` Done. """ pass -# Extra blanks should be preserved. -def rst_literal_extra_blanks(): +def markdown_skipped_rst_literal(): """ - Do cool stuff:: - + Do cool stuff. + ```py + And do this:: cool_stuff( 1 ) - + ``` Done. """ pass -# If a literal block is never properly ended (via a non-empty unindented line), -# then the end of the block should be the last non-empty line. And subsequent -# empty lines should be preserved as-is. -def rst_literal_extra_blanks_at_end(): +def markdown_skipped_rst_directive(): """ - Do cool stuff:: + Do cool stuff. + ```py + .. code-block:: python cool_stuff( 1 ) + ``` - + Done. """ pass +``` +## Outputs +### Output 1 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +preview = Disabled +``` -# A literal block can contain many empty lines and it should not end the block -# if it continues. -def rst_literal_extra_blanks_in_snippet(): - """ - Do cool stuff:: - - cool_stuff( 1 ) - +```python +############################################################################### +# DOCTEST CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Python's "doctest" format. +# +# See: https://docs.python.org/3/library/doctest.html +############################################################################### - cool_stuff( 2 ) +# The simplest doctest to ensure basic formatting works. +def doctest_simple(): + """ + Do cool stuff. - Done. + >>> cool_stuff( 1 ) + 2 """ pass -# This tests that a unindented line appearing after an indented line (but where -# the indent is still beyond the minimum) gets formatted properly. -def rst_literal_subsequent_line_not_indented(): +# Another simple test, but one where the Python code +# extends over multiple lines. +def doctest_simple_continued(): """ - Do cool stuff:: - - if True: - cool_stuff( ''' - hiya''' ) + Do cool stuff. - Done. + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 """ pass -# This checks that if the first line in a code snippet has been indented with -# tabs, then so long as its "indentation length" is considered bigger than the -# line with `::`, it is reformatted as code. -# -# (If your tabwidth is set to 4, then it looks like the code snippet -# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST -# itself also seems to recognize this as a code block, although it appears -# under-specified.) -def rst_literal_first_line_indent_uses_tabs_4spaces(): +# Test that we support multiple directly adjacent +# doctests. +def doctest_adjacent(): """ - Do cool stuff:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> cool_stuff( x ) + >>> cool_stuff( y ) + 2 """ pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): +# Test that a doctest on the last non-whitespace line of a docstring +# reformats correctly. +def doctest_last_line(): """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) + Do cool stuff. - Done. + >>> cool_stuff( x ) """ pass -# Another test with tabs, except in this case, if your tabwidth is less than -# 8, than the code snippet actually looks like its indent is *less* than the -# opening line with a `::`. One might presume this means that the code snippet -# is not treated as a literal block and thus not reformatted, but since we -# assume all tabs have tabwidth=8 when computing indentation length, the code -# snippet is actually seen as being more indented than the opening `::` line. -# As with the above example, reST seems to behave the same way here. -def rst_literal_first_line_indent_uses_tabs_8spaces(): +# Test that a doctest that continues to the last non-whitespace line of +# a docstring reformats correctly. +def doctest_last_line_continued(): """ - Do cool stuff:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); """ pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): +# Test that a doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line(): """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) + Do cool stuff. - Done. - """ + >>> cool_stuff( x )""" pass -# Tests that if two lines in a literal block are indented to the same level -# but by different means (tabs versus spaces), then we correctly recognize the -# block and format it. -def rst_literal_first_line_tab_second_line_spaces(): +# Test that a continued doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line_continued(): """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) + Do cool stuff. - Done. - """ + >>> cool_stuff( x ) + ... more( y )""" pass -# Tests that when two lines in a code snippet have weird and inconsistent -# indentation, the code still gets formatted so long as the indent is greater -# than the indent of the `::` line. -# -# In this case, the minimum indent is 5 spaces (from the second line) where as -# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). -# The minimum indent is stripped from each code line. Since tabs aren't -# divisible, the entire tab is stripped, which means the first and second lines -# wind up with the same level of indentation. -# -# An alternative behavior here would be that the tab is replaced with 3 spaces -# instead of being stripped entirely. The code snippet itself would then have -# inconsistent indentation to the point of being invalid Python, and thus code -# formatting would be skipped. -# -# I decided on the former behavior because it seems a bit easier to implement, -# but we might want to switch to the alternative if cases like this show up in -# the real world. ---AG -def rst_literal_odd_indentation(): +# Test that a doctest is correctly identified and formatted with a blank +# continuation line. +def doctest_blank_continued(): """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) + Do cool stuff. - Done. + >>> def cool_stuff ( x ): + ... print( x ) + ... + ... print( x ) """ pass -# Tests that having a line with a lone `::` works as an introduction of a -# literal block. -def rst_literal_lone_colon(): +# Tests that a blank PS2 line at the end of a doctest can get dropped. +# It is treated as part of the Python snippet which will trim the +# trailing whitespace. +def doctest_blank_end(): """ Do cool stuff. - :: - - cool_stuff( 1 ) - - Done. + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... """ pass -def rst_directive_simple(): +# Tests that a blank PS2 line at the end of a doctest can get dropped +# even when there is text following it. +def doctest_blank_end_then_some_text(): """ - .. code-block:: python + Do cool stuff. - cool_stuff( 1 ) + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... - Done. + And say something else. """ pass -def rst_directive_case_insensitive(): +# Test that a doctest containing a triple quoted string gets formatted +# correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): """ - .. cOdE-bLoCk:: python - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> x = '''tricksy''' """ pass -def rst_directive_sourcecode(): +# Test that a doctest containing a triple quoted f-string gets +# formatted correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): """ - .. sourcecode:: python - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> x = f'''tricksy''' """ pass -def rst_directive_options(): +# Another nested multi-line string case, but with triple escaped double +# quotes inside a triple single quoted string. +def doctest_with_triple_escaped_double(): """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah - - cool_stuff( 1 ) - cool_stuff( 2 ) - cool_stuff( 3 ) - cool_stuff( 4 ) + Do cool stuff. - Done. + >>> x = '''\"\"\"''' """ pass -# In this case, since `pycon` isn't recognized as a Python code snippet, the -# docstring reformatter ignores it. But it then picks up the doctest and -# reformats it. -def rst_directive_doctest(): - """ - .. code-block:: pycon - - >>> cool_stuff( 1 ) +# Tests that inverting the triple quoting works as expected. +def doctest_with_triple_inverted(): + ''' + Do cool stuff. - Done. - """ + >>> x = """tricksy""" + ''' pass -# This checks that if the first non-empty line after the start of a literal -# block is not indented more than the line containing the `::`, then it is not -# treated as a code snippet. -def rst_literal_skipped_first_line_not_indented(): - """ - Do cool stuff:: - - cool_stuff( 1 ) +# Tests that inverting the triple quoting with an f-string works as +# expected. +def doctest_with_triple_inverted_fstring(): + ''' + Do cool stuff. - Done. - """ + >>> x = f"""tricksy""" + ''' pass -# Like the test above, but inserts an indented line after the un-indented one. -# This should not cause the literal block to be resumed. -def rst_literal_skipped_first_line_not_indented_then_indented(): - """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) +# Tests nested doctests are ignored. That is, we don't format doctests +# recursively. We only recognize "top level" doctests. +# +# This restriction primarily exists to avoid needing to deal with +# nesting quotes. It also seems like a generally sensible restriction, +# although it could be lifted if necessary I believe. +def doctest_nested_doctest_not_formatted(): + ''' + Do cool stuff. - Done. - """ + >>> def nested( x ): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' pass -# This also checks that a code snippet is not reformatted when the indentation -# of the first line is not more than the line with `::`, but this uses tabs to -# make it a little more confounding. It relies on the fact that indentation -# length is computed by assuming a tabwidth equal to 8. reST also rejects this -# and doesn't treat it as a literal block. -def rst_literal_skipped_first_line_not_indented_tab(): +# Tests that the starting column does not matter. +def doctest_varying_start_column(): """ - Do cool stuff:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> assert ("Easy!") + >>> import math + >>> math.floor( 1.9 ) + 1 """ pass -# Like the previous test, but adds a second line. -def rst_literal_skipped_first_line_not_indented_tab_multiple(): +# Tests that long lines get wrapped... appropriately. +# +# The docstring code formatter uses the same line width settings as for +# formatting other code. This means that a line in the docstring can +# actually extend past the configured line limit. +# +# It's not quite clear whether this is desirable or not. We could in +# theory compute the intendation length of a code snippet and then +# adjust the line-width setting on a recursive call to the formatter. +# But there are assuredly pathological cases to consider. Another path +# would be to expose another formatter option for controlling the +# line-width of code snippets independently. +def doctest_long_lines(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff( 1 ) - cool_stuff( 2 ) + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) - Done. + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) """ - pass + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) -# Tests that a code block with a second line that is not properly indented gets -# skipped. A valid code block needs to have an empty line separating these. -# -# One trick here is that we need to make sure the Python code in the snippet is -# valid, otherwise it would be skipped because of invalid Python. -def rst_literal_skipped_subsequent_line_not_indented(): +# Checks that a simple but invalid doctest gets skipped. +def doctest_skipped_simple(): """ - Do cool stuff:: - - if True: - cool_stuff( ''' - hiya''' ) + Do cool stuff. - Done. + >>> cool-stuff( x ): + 2 """ pass -# In this test, we write what looks like a code-block, but it should be treated -# as invalid due to the missing `language` argument. -# -# It does still look like it could be a literal block according to the literal -# rules, but we currently consider the `.. ` prefix to indicate that it is not -# a literal block. -def rst_literal_skipped_not_directive(): +# Checks that a simple doctest that is continued over multiple lines, +# but is invalid, gets skipped. +def doctest_skipped_simple_continued(): """ - .. code-block:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 """ pass -# In this test, we start a line with `.. `, which makes it look like it might -# be a directive. But instead continue it as if it was just some periods from -# the previous line, and then try to end it by starting a literal block. -# -# But because of the `.. ` in the beginning, we wind up not treating this as a -# code snippet. The reST render I was using to test things does actually treat -# this as a code block, so we may be out of conformance here. -def rst_literal_skipped_possible_false_negative(): +# Checks that a doctest with improper indentation gets skipped. +def doctest_skipped_inconsistent_indent(): """ - This is a test. - .. This is a test:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 """ pass -# This tests that a doctest inside of a reST literal block doesn't get -# reformatted. It's plausible this isn't the right behavior, but it also seems -# like it might be the right behavior since it is a literal block. (The doctest -# makes the Python code invalid.) -def rst_literal_skipped_doctest(): +# Checks that a doctest with some proper indentation and some improper +# indentation is "partially" formatted. That is, the part that appears +# before the inconsistent indentation is formatted. This requires that +# the part before it is valid Python. +def doctest_skipped_partial_inconsistent_indent(): """ - Do cool stuff:: - - >>> cool_stuff( 1 ) + Do cool stuff. - Done. + >>> def cool_stuff( x ): + ... print( x ) + ... print( f"hi {x}" ); + hi 2 """ pass -def rst_directive_skipped_not_indented(): +# Checks that a doctest with improper triple single quoted string gets +# skipped. That is, the code snippet is itself invalid Python, so it is +# left as is. +def doctest_skipped_triple_incorrect(): """ - .. code-block:: python - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> foo( x ) + ... '''tri'''cksy''' """ pass -def rst_directive_skipped_wrong_language(): - """ - .. code-block:: rust +# Tests that a doctest on a single line is skipped. +def doctest_skipped_one_line(): + ">>> foo( x )" + pass - cool_stuff( 1 ) - Done. +# f-strings are not considered docstrings[1], so any doctests +# inside of them should not be formatted. +# +# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +def doctest_skipped_fstring(): + f""" + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 """ pass -# This gets skipped for the same reason that the doctest in a literal block -# gets skipped. -def rst_directive_skipped_doctest(): +# Test that a doctest containing a triple quoted string at least +# does not result in invalid Python code. Ideally this would format +# correctly, but at time of writing it does not. +def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): """ - .. code-block:: python - - >>> cool_stuff( 1 ) + Do cool stuff. - Done. + >>> x = '\"\"\"' """ pass -``` -### Output 2 -``` -indent-style = space -line-width = 88 -indent-width = 2 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Disabled -preview = Disabled -``` - -```python ############################################################################### -# DOCTEST CODE EXAMPLES +# reStructuredText CODE EXAMPLES # # This section shows examples of docstrings that contain code snippets in -# Python's "doctest" format. +# reStructuredText formatted code blocks. # -# See: https://docs.python.org/3/library/doctest.html +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 ############################################################################### -# The simplest doctest to ensure basic formatting works. -def doctest_simple(): - """ - Do cool stuff. - - >>> cool_stuff( 1 ) - 2 - """ - pass +def rst_literal_simple(): + """ + Do cool stuff:: -# Another simple test, but one where the Python code -# extends over multiple lines. -def doctest_simple_continued(): - """ - Do cool stuff. + cool_stuff( 1 ) - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + Done. + """ + pass -# Test that we support multiple directly adjacent -# doctests. -def doctest_adjacent(): - """ - Do cool stuff. +def rst_literal_simple_continued(): + """ + Do cool stuff:: - >>> cool_stuff( x ) - >>> cool_stuff( y ) - 2 - """ - pass + def cool_stuff( x ): + print( f"hi {x}" ); + Done. + """ + pass -# Test that a doctest on the last non-whitespace line of a docstring -# reformats correctly. -def doctest_last_line(): - """ - Do cool stuff. - >>> cool_stuff( x ) - """ - pass +# Tests that we can end the literal block on the second +# to last line of the docstring. +def rst_literal_second_to_last(): + """ + Do cool stuff:: + cool_stuff( 1 ) + """ + pass -# Test that a doctest that continues to the last non-whitespace line of -# a docstring reformats correctly. -def doctest_last_line_continued(): - """ - Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - """ - pass +# Tests that we can end the literal block on the actual +# last line of the docstring. +def rst_literal_actually_last(): + """ + Do cool stuff:: + cool_stuff( 1 )""" + pass -# Test that a doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line(): - """ - Do cool stuff. - >>> cool_stuff( x )""" - pass +def rst_literal_with_blank_lines(): + """ + Do cool stuff:: + def cool_stuff( x ): + print( f"hi {x}" ); -# Test that a continued doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line_continued(): - """ - Do cool stuff. + def other_stuff( y ): + print( y ) - >>> cool_stuff( x ) - ... more( y )""" - pass + Done. + """ + pass -# Test that a doctest is correctly identified and formatted with a blank -# continuation line. -def doctest_blank_continued(): - """ - Do cool stuff. +# Extra blanks should be preserved. +def rst_literal_extra_blanks(): + """ + Do cool stuff:: - >>> def cool_stuff ( x ): - ... print( x ) - ... - ... print( x ) - """ - pass -# Tests that a blank PS2 line at the end of a doctest can get dropped. -# It is treated as part of the Python snippet which will trim the -# trailing whitespace. -def doctest_blank_end(): - """ - Do cool stuff. + cool_stuff( 1 ) - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... - """ - pass -# Tests that a blank PS2 line at the end of a doctest can get dropped -# even when there is text following it. -def doctest_blank_end_then_some_text(): - """ - Do cool stuff. + Done. + """ + pass - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... - And say something else. - """ - pass +# If a literal block is never properly ended (via a non-empty unindented line), +# then the end of the block should be the last non-empty line. And subsequent +# empty lines should be preserved as-is. +def rst_literal_extra_blanks_at_end(): + """ + Do cool stuff:: -# Test that a doctest containing a triple quoted string gets formatted -# correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): - """ - Do cool stuff. + cool_stuff( 1 ) - >>> x = '''tricksy''' - """ - pass -# Test that a doctest containing a triple quoted f-string gets -# formatted correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): - """ - Do cool stuff. + """ + pass - >>> x = f'''tricksy''' - """ - pass +# A literal block can contain many empty lines and it should not end the block +# if it continues. +def rst_literal_extra_blanks_in_snippet(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + + Done. + """ + pass + + +# This tests that a unindented line appearing after an indented line (but where +# the indent is still beyond the minimum) gets formatted properly. +def rst_literal_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# This checks that if the first line in a code snippet has been indented with +# tabs, then so long as its "indentation length" is considered bigger than the +# line with `::`, it is reformatted as code. +# +# (If your tabwidth is set to 4, then it looks like the code snippet +# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST +# itself also seems to recognize this as a code block, although it appears +# under-specified.) +def rst_literal_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Another test with tabs, except in this case, if your tabwidth is less than +# 8, than the code snippet actually looks like its indent is *less* than the +# opening line with a `::`. One might presume this means that the code snippet +# is not treated as a literal block and thus not reformatted, but since we +# assume all tabs have tabwidth=8 when computing indentation length, the code +# snippet is actually seen as being more indented than the opening `::` line. +# As with the above example, reST seems to behave the same way here. +def rst_literal_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that if two lines in a literal block are indented to the same level +# but by different means (tabs versus spaces), then we correctly recognize the +# block and format it. +def rst_literal_first_line_tab_second_line_spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that when two lines in a code snippet have weird and inconsistent +# indentation, the code still gets formatted so long as the indent is greater +# than the indent of the `::` line. +# +# In this case, the minimum indent is 5 spaces (from the second line) where as +# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). +# The minimum indent is stripped from each code line. Since tabs aren't +# divisible, the entire tab is stripped, which means the first and second lines +# wind up with the same level of indentation. +# +# An alternative behavior here would be that the tab is replaced with 3 spaces +# instead of being stripped entirely. The code snippet itself would then have +# inconsistent indentation to the point of being invalid Python, and thus code +# formatting would be skipped. +# +# I decided on the former behavior because it seems a bit easier to implement, +# but we might want to switch to the alternative if cases like this show up in +# the real world. ---AG +def rst_literal_odd_indentation(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that having a line with a lone `::` works as an introduction of a +# literal block. +def rst_literal_lone_colon(): + """ + Do cool stuff. + + :: + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_simple(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_case_insensitive(): + """ + .. cOdE-bLoCk:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_sourcecode(): + """ + .. sourcecode:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_options(): + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah + + cool_stuff( 1 ) + cool_stuff( 2 ) + cool_stuff( 3 ) + cool_stuff( 4 ) + + Done. + """ + pass + + +# In this case, since `pycon` isn't recognized as a Python code snippet, the +# docstring reformatter ignores it. But it then picks up the doctest and +# reformats it. +def rst_directive_doctest(): + """ + .. code-block:: pycon + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +# This checks that if the first non-empty line after the start of a literal +# block is not indented more than the line containing the `::`, then it is not +# treated as a code snippet. +def rst_literal_skipped_first_line_not_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but inserts an indented line after the un-indented one. +# This should not cause the literal block to be resumed. +def rst_literal_skipped_first_line_not_indented_then_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# This also checks that a code snippet is not reformatted when the indentation +# of the first line is not more than the line with `::`, but this uses tabs to +# make it a little more confounding. It relies on the fact that indentation +# length is computed by assuming a tabwidth equal to 8. reST also rejects this +# and doesn't treat it as a literal block. +def rst_literal_skipped_first_line_not_indented_tab(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the previous test, but adds a second line. +def rst_literal_skipped_first_line_not_indented_tab_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that a code block with a second line that is not properly indented gets +# skipped. A valid code block needs to have an empty line separating these. +# +# One trick here is that we need to make sure the Python code in the snippet is +# valid, otherwise it would be skipped because of invalid Python. +def rst_literal_skipped_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# In this test, we write what looks like a code-block, but it should be treated +# as invalid due to the missing `language` argument. +# +# It does still look like it could be a literal block according to the literal +# rules, but we currently consider the `.. ` prefix to indicate that it is not +# a literal block. +def rst_literal_skipped_not_directive(): + """ + .. code-block:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# In this test, we start a line with `.. `, which makes it look like it might +# be a directive. But instead continue it as if it was just some periods from +# the previous line, and then try to end it by starting a literal block. +# +# But because of the `.. ` in the beginning, we wind up not treating this as a +# code snippet. The reST render I was using to test things does actually treat +# this as a code block, so we may be out of conformance here. +def rst_literal_skipped_possible_false_negative(): + """ + This is a test. + .. This is a test:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# This tests that a doctest inside of a reST literal block doesn't get +# reformatted. It's plausible this isn't the right behavior, but it also seems +# like it might be the right behavior since it is a literal block. (The doctest +# makes the Python code invalid.) +def rst_literal_skipped_doctest(): + """ + Do cool stuff:: + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def rst_directive_skipped_not_indented(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_skipped_wrong_language(): + """ + .. code-block:: rust + + cool_stuff( 1 ) + + Done. + """ + pass + + +# This gets skipped for the same reason that the doctest in a literal block +# gets skipped. +def rst_directive_skipped_doctest(): + """ + .. code-block:: python + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### + + +def markdown_simple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_simple_continued(): + """ + Do cool stuff. + + ```python + def cool_stuff( x ): + print( f"hi {x}" ); + ``` + + Done. + """ + pass + + +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. + + ``` + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. + + ~~~py + cool_stuff( 1 ) + ~~~ + + Done. + """ + pass + + +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + `````` + + Done. + """ + pass + + +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ```invalid + ''' + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. + + `````` + do_something( ''' + ``` + did i trick you? + ``` + ''' ) + `````` + + Done. + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + + + """ + pass + + +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + """ + pass + + +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ```""" + pass + + +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 )""" + pass + + +def markdown_with_blank_lines(): + """ + Do cool stuff. + + ```py + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_odd_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. + + ```py + + + cool_stuff( 1 ) + + + ``` + + Done. + """ + pass + + +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. + + ```py + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. + + ```python + cool_stuff( 1 ) + + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` + + Now the code block is closed + """ + pass + + +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print( 5 ) + ``` + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass + + +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. + """ + pass + + +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. +# +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. +# +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_doctest(): + """ + Do cool stuff. + + ```py + >>> cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_literal(): + """ + Do cool stuff. + + ```py + And do this:: + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_directive(): + """ + Do cool stuff. + + ```py + .. code-block:: python + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass +``` + + +### Output 2 +``` +indent-style = space +line-width = 88 +indent-width = 2 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +preview = Disabled +``` + +```python +############################################################################### +# DOCTEST CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Python's "doctest" format. +# +# See: https://docs.python.org/3/library/doctest.html +############################################################################### + +# The simplest doctest to ensure basic formatting works. +def doctest_simple(): + """ + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 + """ + pass + + +# Another simple test, but one where the Python code +# extends over multiple lines. +def doctest_simple_continued(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Test that we support multiple directly adjacent +# doctests. +def doctest_adjacent(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + >>> cool_stuff( y ) + 2 + """ + pass + + +# Test that a doctest on the last non-whitespace line of a docstring +# reformats correctly. +def doctest_last_line(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + """ + pass + + +# Test that a doctest that continues to the last non-whitespace line of +# a docstring reformats correctly. +def doctest_last_line_continued(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + """ + pass + + +# Test that a doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line(): + """ + Do cool stuff. + + >>> cool_stuff( x )""" + pass + + +# Test that a continued doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line_continued(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + ... more( y )""" + pass + + +# Test that a doctest is correctly identified and formatted with a blank +# continuation line. +def doctest_blank_continued(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... + ... print( x ) + """ + pass + + +# Tests that a blank PS2 line at the end of a doctest can get dropped. +# It is treated as part of the Python snippet which will trim the +# trailing whitespace. +def doctest_blank_end(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... + """ + pass + + +# Tests that a blank PS2 line at the end of a doctest can get dropped +# even when there is text following it. +def doctest_blank_end_then_some_text(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... + + And say something else. + """ + pass + + +# Test that a doctest containing a triple quoted string gets formatted +# correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. + + >>> x = '''tricksy''' + """ + pass + + +# Test that a doctest containing a triple quoted f-string gets +# formatted correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. + + >>> x = f'''tricksy''' + """ + pass + + +# Another nested multi-line string case, but with triple escaped double +# quotes inside a triple single quoted string. +def doctest_with_triple_escaped_double(): + """ + Do cool stuff. + + >>> x = '''\"\"\"''' + """ + pass + + +# Tests that inverting the triple quoting works as expected. +def doctest_with_triple_inverted(): + ''' + Do cool stuff. + + >>> x = """tricksy""" + ''' + pass + + +# Tests that inverting the triple quoting with an f-string works as +# expected. +def doctest_with_triple_inverted_fstring(): + ''' + Do cool stuff. + + >>> x = f"""tricksy""" + ''' + pass + + +# Tests nested doctests are ignored. That is, we don't format doctests +# recursively. We only recognize "top level" doctests. +# +# This restriction primarily exists to avoid needing to deal with +# nesting quotes. It also seems like a generally sensible restriction, +# although it could be lifted if necessary I believe. +def doctest_nested_doctest_not_formatted(): + ''' + Do cool stuff. + + >>> def nested( x ): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' + pass + + +# Tests that the starting column does not matter. +def doctest_varying_start_column(): + """ + Do cool stuff. + + >>> assert ("Easy!") + >>> import math + >>> math.floor( 1.9 ) + 1 + """ + pass + + +# Tests that long lines get wrapped... appropriately. +# +# The docstring code formatter uses the same line width settings as for +# formatting other code. This means that a line in the docstring can +# actually extend past the configured line limit. +# +# It's not quite clear whether this is desirable or not. We could in +# theory compute the intendation length of a code snippet and then +# adjust the line-width setting on a recursive call to the formatter. +# But there are assuredly pathological cases to consider. Another path +# would be to expose another formatter option for controlling the +# line-width of code snippets independently. +def doctest_long_lines(): + """ + Do cool stuff. + + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) + """ + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) + + +# Checks that a simple but invalid doctest gets skipped. +def doctest_skipped_simple(): + """ + Do cool stuff. + + >>> cool-stuff( x ): + 2 + """ + pass + + +# Checks that a simple doctest that is continued over multiple lines, +# but is invalid, gets skipped. +def doctest_skipped_simple_continued(): + """ + Do cool stuff. + + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 + """ + pass + + +# Checks that a doctest with improper indentation gets skipped. +def doctest_skipped_inconsistent_indent(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Checks that a doctest with some proper indentation and some improper +# indentation is "partially" formatted. That is, the part that appears +# before the inconsistent indentation is formatted. This requires that +# the part before it is valid Python. +def doctest_skipped_partial_inconsistent_indent(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( x ) + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Checks that a doctest with improper triple single quoted string gets +# skipped. That is, the code snippet is itself invalid Python, so it is +# left as is. +def doctest_skipped_triple_incorrect(): + """ + Do cool stuff. + + >>> foo( x ) + ... '''tri'''cksy''' + """ + pass + + +# Tests that a doctest on a single line is skipped. +def doctest_skipped_one_line(): + ">>> foo( x )" + pass + + +# f-strings are not considered docstrings[1], so any doctests +# inside of them should not be formatted. +# +# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +def doctest_skipped_fstring(): + f""" + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 + """ + pass + + +# Test that a doctest containing a triple quoted string at least +# does not result in invalid Python code. Ideally this would format +# correctly, but at time of writing it does not. +def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): + """ + Do cool stuff. + + >>> x = '\"\"\"' + """ + pass + + +############################################################################### +# reStructuredText CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# reStructuredText formatted code blocks. +# +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 +############################################################################### + + +def rst_literal_simple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_literal_simple_continued(): + """ + Do cool stuff:: + + def cool_stuff( x ): + print( f"hi {x}" ); + + Done. + """ + pass + + +# Tests that we can end the literal block on the second +# to last line of the docstring. +def rst_literal_second_to_last(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the literal block on the actual +# last line of the docstring. +def rst_literal_actually_last(): + """ + Do cool stuff:: + + cool_stuff( 1 )""" + pass + + +def rst_literal_with_blank_lines(): + """ + Do cool stuff:: + + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + + Done. + """ + pass + + +# Extra blanks should be preserved. +def rst_literal_extra_blanks(): + """ + Do cool stuff:: + + + + cool_stuff( 1 ) + + + + Done. + """ + pass + + +# If a literal block is never properly ended (via a non-empty unindented line), +# then the end of the block should be the last non-empty line. And subsequent +# empty lines should be preserved as-is. +def rst_literal_extra_blanks_at_end(): + """ + Do cool stuff:: + + + cool_stuff( 1 ) + + + + """ + pass + + +# A literal block can contain many empty lines and it should not end the block +# if it continues. +def rst_literal_extra_blanks_in_snippet(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + + Done. + """ + pass + + +# This tests that a unindented line appearing after an indented line (but where +# the indent is still beyond the minimum) gets formatted properly. +def rst_literal_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# This checks that if the first line in a code snippet has been indented with +# tabs, then so long as its "indentation length" is considered bigger than the +# line with `::`, it is reformatted as code. +# +# (If your tabwidth is set to 4, then it looks like the code snippet +# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST +# itself also seems to recognize this as a code block, although it appears +# under-specified.) +def rst_literal_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Another test with tabs, except in this case, if your tabwidth is less than +# 8, than the code snippet actually looks like its indent is *less* than the +# opening line with a `::`. One might presume this means that the code snippet +# is not treated as a literal block and thus not reformatted, but since we +# assume all tabs have tabwidth=8 when computing indentation length, the code +# snippet is actually seen as being more indented than the opening `::` line. +# As with the above example, reST seems to behave the same way here. +def rst_literal_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that if two lines in a literal block are indented to the same level +# but by different means (tabs versus spaces), then we correctly recognize the +# block and format it. +def rst_literal_first_line_tab_second_line_spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that when two lines in a code snippet have weird and inconsistent +# indentation, the code still gets formatted so long as the indent is greater +# than the indent of the `::` line. +# +# In this case, the minimum indent is 5 spaces (from the second line) where as +# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). +# The minimum indent is stripped from each code line. Since tabs aren't +# divisible, the entire tab is stripped, which means the first and second lines +# wind up with the same level of indentation. +# +# An alternative behavior here would be that the tab is replaced with 3 spaces +# instead of being stripped entirely. The code snippet itself would then have +# inconsistent indentation to the point of being invalid Python, and thus code +# formatting would be skipped. +# +# I decided on the former behavior because it seems a bit easier to implement, +# but we might want to switch to the alternative if cases like this show up in +# the real world. ---AG +def rst_literal_odd_indentation(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that having a line with a lone `::` works as an introduction of a +# literal block. +def rst_literal_lone_colon(): + """ + Do cool stuff. + + :: + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_simple(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_case_insensitive(): + """ + .. cOdE-bLoCk:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_sourcecode(): + """ + .. sourcecode:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_options(): + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah + + cool_stuff( 1 ) + cool_stuff( 2 ) + cool_stuff( 3 ) + cool_stuff( 4 ) + + Done. + """ + pass + + +# In this case, since `pycon` isn't recognized as a Python code snippet, the +# docstring reformatter ignores it. But it then picks up the doctest and +# reformats it. +def rst_directive_doctest(): + """ + .. code-block:: pycon + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +# This checks that if the first non-empty line after the start of a literal +# block is not indented more than the line containing the `::`, then it is not +# treated as a code snippet. +def rst_literal_skipped_first_line_not_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but inserts an indented line after the un-indented one. +# This should not cause the literal block to be resumed. +def rst_literal_skipped_first_line_not_indented_then_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# This also checks that a code snippet is not reformatted when the indentation +# of the first line is not more than the line with `::`, but this uses tabs to +# make it a little more confounding. It relies on the fact that indentation +# length is computed by assuming a tabwidth equal to 8. reST also rejects this +# and doesn't treat it as a literal block. +def rst_literal_skipped_first_line_not_indented_tab(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the previous test, but adds a second line. +def rst_literal_skipped_first_line_not_indented_tab_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that a code block with a second line that is not properly indented gets +# skipped. A valid code block needs to have an empty line separating these. +# +# One trick here is that we need to make sure the Python code in the snippet is +# valid, otherwise it would be skipped because of invalid Python. +def rst_literal_skipped_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# In this test, we write what looks like a code-block, but it should be treated +# as invalid due to the missing `language` argument. +# +# It does still look like it could be a literal block according to the literal +# rules, but we currently consider the `.. ` prefix to indicate that it is not +# a literal block. +def rst_literal_skipped_not_directive(): + """ + .. code-block:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# In this test, we start a line with `.. `, which makes it look like it might +# be a directive. But instead continue it as if it was just some periods from +# the previous line, and then try to end it by starting a literal block. +# +# But because of the `.. ` in the beginning, we wind up not treating this as a +# code snippet. The reST render I was using to test things does actually treat +# this as a code block, so we may be out of conformance here. +def rst_literal_skipped_possible_false_negative(): + """ + This is a test. + .. This is a test:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# This tests that a doctest inside of a reST literal block doesn't get +# reformatted. It's plausible this isn't the right behavior, but it also seems +# like it might be the right behavior since it is a literal block. (The doctest +# makes the Python code invalid.) +def rst_literal_skipped_doctest(): + """ + Do cool stuff:: + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def rst_directive_skipped_not_indented(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_skipped_wrong_language(): + """ + .. code-block:: rust + + cool_stuff( 1 ) + + Done. + """ + pass + + +# This gets skipped for the same reason that the doctest in a literal block +# gets skipped. +def rst_directive_skipped_doctest(): + """ + .. code-block:: python + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### + + +def markdown_simple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_simple_continued(): + """ + Do cool stuff. + + ```python + def cool_stuff( x ): + print( f"hi {x}" ); + ``` + + Done. + """ + pass + + +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. + + ``` + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. + + ~~~py + cool_stuff( 1 ) + ~~~ + + Done. + """ + pass + + +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + `````` + + Done. + """ + pass + + +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ```invalid + ''' + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. + + `````` + do_something( ''' + ``` + did i trick you? + ``` + ''' ) + `````` + + Done. + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + + + """ + pass + + +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + """ + pass + + +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ```""" + pass + + +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 )""" + pass + + +def markdown_with_blank_lines(): + """ + Do cool stuff. + + ```py + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_odd_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. + + ```py + + + cool_stuff( 1 ) + + + ``` + + Done. + """ + pass + + +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. + + ```py + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. + + ```python + cool_stuff( 1 ) + + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` + + Now the code block is closed + """ + pass + + +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print( 5 ) + ``` + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass + + +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. + """ + pass + + +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. +# +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. +# +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_doctest(): + """ + Do cool stuff. + + ```py + >>> cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_literal(): + """ + Do cool stuff. + + ```py + And do this:: + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_directive(): + """ + Do cool stuff. + + ```py + .. code-block:: python + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass +``` + + +### Output 3 +``` +indent-style = tab +line-width = 88 +indent-width = 8 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +preview = Disabled +``` + +```python +############################################################################### +# DOCTEST CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Python's "doctest" format. +# +# See: https://docs.python.org/3/library/doctest.html +############################################################################### + +# The simplest doctest to ensure basic formatting works. +def doctest_simple(): + """ + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 + """ + pass + + +# Another simple test, but one where the Python code +# extends over multiple lines. +def doctest_simple_continued(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Test that we support multiple directly adjacent +# doctests. +def doctest_adjacent(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + >>> cool_stuff( y ) + 2 + """ + pass + + +# Test that a doctest on the last non-whitespace line of a docstring +# reformats correctly. +def doctest_last_line(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + """ + pass + + +# Test that a doctest that continues to the last non-whitespace line of +# a docstring reformats correctly. +def doctest_last_line_continued(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + """ + pass + + +# Test that a doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line(): + """ + Do cool stuff. + + >>> cool_stuff( x )""" + pass + + +# Test that a continued doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line_continued(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + ... more( y )""" + pass + + +# Test that a doctest is correctly identified and formatted with a blank +# continuation line. +def doctest_blank_continued(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... + ... print( x ) + """ + pass + + +# Tests that a blank PS2 line at the end of a doctest can get dropped. +# It is treated as part of the Python snippet which will trim the +# trailing whitespace. +def doctest_blank_end(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... + """ + pass + + +# Tests that a blank PS2 line at the end of a doctest can get dropped +# even when there is text following it. +def doctest_blank_end_then_some_text(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... + + And say something else. + """ + pass + + +# Test that a doctest containing a triple quoted string gets formatted +# correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. + + >>> x = '''tricksy''' + """ + pass + + +# Test that a doctest containing a triple quoted f-string gets +# formatted correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. + + >>> x = f'''tricksy''' + """ + pass + + +# Another nested multi-line string case, but with triple escaped double +# quotes inside a triple single quoted string. +def doctest_with_triple_escaped_double(): + """ + Do cool stuff. + + >>> x = '''\"\"\"''' + """ + pass + + +# Tests that inverting the triple quoting works as expected. +def doctest_with_triple_inverted(): + ''' + Do cool stuff. + + >>> x = """tricksy""" + ''' + pass + + +# Tests that inverting the triple quoting with an f-string works as +# expected. +def doctest_with_triple_inverted_fstring(): + ''' + Do cool stuff. + + >>> x = f"""tricksy""" + ''' + pass + + +# Tests nested doctests are ignored. That is, we don't format doctests +# recursively. We only recognize "top level" doctests. +# +# This restriction primarily exists to avoid needing to deal with +# nesting quotes. It also seems like a generally sensible restriction, +# although it could be lifted if necessary I believe. +def doctest_nested_doctest_not_formatted(): + ''' + Do cool stuff. + + >>> def nested( x ): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' + pass + + +# Tests that the starting column does not matter. +def doctest_varying_start_column(): + """ + Do cool stuff. + + >>> assert ("Easy!") + >>> import math + >>> math.floor( 1.9 ) + 1 + """ + pass + + +# Tests that long lines get wrapped... appropriately. +# +# The docstring code formatter uses the same line width settings as for +# formatting other code. This means that a line in the docstring can +# actually extend past the configured line limit. +# +# It's not quite clear whether this is desirable or not. We could in +# theory compute the intendation length of a code snippet and then +# adjust the line-width setting on a recursive call to the formatter. +# But there are assuredly pathological cases to consider. Another path +# would be to expose another formatter option for controlling the +# line-width of code snippets independently. +def doctest_long_lines(): + """ + Do cool stuff. + + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) + """ + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) + + +# Checks that a simple but invalid doctest gets skipped. +def doctest_skipped_simple(): + """ + Do cool stuff. + + >>> cool-stuff( x ): + 2 + """ + pass + + +# Checks that a simple doctest that is continued over multiple lines, +# but is invalid, gets skipped. +def doctest_skipped_simple_continued(): + """ + Do cool stuff. + + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 + """ + pass + + +# Checks that a doctest with improper indentation gets skipped. +def doctest_skipped_inconsistent_indent(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Checks that a doctest with some proper indentation and some improper +# indentation is "partially" formatted. That is, the part that appears +# before the inconsistent indentation is formatted. This requires that +# the part before it is valid Python. +def doctest_skipped_partial_inconsistent_indent(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( x ) + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Checks that a doctest with improper triple single quoted string gets +# skipped. That is, the code snippet is itself invalid Python, so it is +# left as is. +def doctest_skipped_triple_incorrect(): + """ + Do cool stuff. + + >>> foo( x ) + ... '''tri'''cksy''' + """ + pass + + +# Tests that a doctest on a single line is skipped. +def doctest_skipped_one_line(): + ">>> foo( x )" + pass + + +# f-strings are not considered docstrings[1], so any doctests +# inside of them should not be formatted. +# +# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +def doctest_skipped_fstring(): + f""" + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 + """ + pass + + +# Test that a doctest containing a triple quoted string at least +# does not result in invalid Python code. Ideally this would format +# correctly, but at time of writing it does not. +def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): + """ + Do cool stuff. + + >>> x = '\"\"\"' + """ + pass + + +############################################################################### +# reStructuredText CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# reStructuredText formatted code blocks. +# +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 +############################################################################### + + +def rst_literal_simple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_literal_simple_continued(): + """ + Do cool stuff:: + + def cool_stuff( x ): + print( f"hi {x}" ); + + Done. + """ + pass + + +# Tests that we can end the literal block on the second +# to last line of the docstring. +def rst_literal_second_to_last(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the literal block on the actual +# last line of the docstring. +def rst_literal_actually_last(): + """ + Do cool stuff:: + + cool_stuff( 1 )""" + pass + + +def rst_literal_with_blank_lines(): + """ + Do cool stuff:: + + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + + Done. + """ + pass + + +# Extra blanks should be preserved. +def rst_literal_extra_blanks(): + """ + Do cool stuff:: + + + + cool_stuff( 1 ) + + + + Done. + """ + pass + + +# If a literal block is never properly ended (via a non-empty unindented line), +# then the end of the block should be the last non-empty line. And subsequent +# empty lines should be preserved as-is. +def rst_literal_extra_blanks_at_end(): + """ + Do cool stuff:: + + + cool_stuff( 1 ) + + + + """ + pass + + +# A literal block can contain many empty lines and it should not end the block +# if it continues. +def rst_literal_extra_blanks_in_snippet(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + + Done. + """ + pass + + +# This tests that a unindented line appearing after an indented line (but where +# the indent is still beyond the minimum) gets formatted properly. +def rst_literal_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# This checks that if the first line in a code snippet has been indented with +# tabs, then so long as its "indentation length" is considered bigger than the +# line with `::`, it is reformatted as code. +# +# (If your tabwidth is set to 4, then it looks like the code snippet +# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST +# itself also seems to recognize this as a code block, although it appears +# under-specified.) +def rst_literal_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Another test with tabs, except in this case, if your tabwidth is less than +# 8, than the code snippet actually looks like its indent is *less* than the +# opening line with a `::`. One might presume this means that the code snippet +# is not treated as a literal block and thus not reformatted, but since we +# assume all tabs have tabwidth=8 when computing indentation length, the code +# snippet is actually seen as being more indented than the opening `::` line. +# As with the above example, reST seems to behave the same way here. +def rst_literal_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that if two lines in a literal block are indented to the same level +# but by different means (tabs versus spaces), then we correctly recognize the +# block and format it. +def rst_literal_first_line_tab_second_line_spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that when two lines in a code snippet have weird and inconsistent +# indentation, the code still gets formatted so long as the indent is greater +# than the indent of the `::` line. +# +# In this case, the minimum indent is 5 spaces (from the second line) where as +# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). +# The minimum indent is stripped from each code line. Since tabs aren't +# divisible, the entire tab is stripped, which means the first and second lines +# wind up with the same level of indentation. +# +# An alternative behavior here would be that the tab is replaced with 3 spaces +# instead of being stripped entirely. The code snippet itself would then have +# inconsistent indentation to the point of being invalid Python, and thus code +# formatting would be skipped. +# +# I decided on the former behavior because it seems a bit easier to implement, +# but we might want to switch to the alternative if cases like this show up in +# the real world. ---AG +def rst_literal_odd_indentation(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that having a line with a lone `::` works as an introduction of a +# literal block. +def rst_literal_lone_colon(): + """ + Do cool stuff. + + :: + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_simple(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_case_insensitive(): + """ + .. cOdE-bLoCk:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_sourcecode(): + """ + .. sourcecode:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_options(): + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah + + cool_stuff( 1 ) + cool_stuff( 2 ) + cool_stuff( 3 ) + cool_stuff( 4 ) + + Done. + """ + pass + + +# In this case, since `pycon` isn't recognized as a Python code snippet, the +# docstring reformatter ignores it. But it then picks up the doctest and +# reformats it. +def rst_directive_doctest(): + """ + .. code-block:: pycon + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +# This checks that if the first non-empty line after the start of a literal +# block is not indented more than the line containing the `::`, then it is not +# treated as a code snippet. +def rst_literal_skipped_first_line_not_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but inserts an indented line after the un-indented one. +# This should not cause the literal block to be resumed. +def rst_literal_skipped_first_line_not_indented_then_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# This also checks that a code snippet is not reformatted when the indentation +# of the first line is not more than the line with `::`, but this uses tabs to +# make it a little more confounding. It relies on the fact that indentation +# length is computed by assuming a tabwidth equal to 8. reST also rejects this +# and doesn't treat it as a literal block. +def rst_literal_skipped_first_line_not_indented_tab(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the previous test, but adds a second line. +def rst_literal_skipped_first_line_not_indented_tab_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that a code block with a second line that is not properly indented gets +# skipped. A valid code block needs to have an empty line separating these. +# +# One trick here is that we need to make sure the Python code in the snippet is +# valid, otherwise it would be skipped because of invalid Python. +def rst_literal_skipped_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# In this test, we write what looks like a code-block, but it should be treated +# as invalid due to the missing `language` argument. +# +# It does still look like it could be a literal block according to the literal +# rules, but we currently consider the `.. ` prefix to indicate that it is not +# a literal block. +def rst_literal_skipped_not_directive(): + """ + .. code-block:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# In this test, we start a line with `.. `, which makes it look like it might +# be a directive. But instead continue it as if it was just some periods from +# the previous line, and then try to end it by starting a literal block. +# +# But because of the `.. ` in the beginning, we wind up not treating this as a +# code snippet. The reST render I was using to test things does actually treat +# this as a code block, so we may be out of conformance here. +def rst_literal_skipped_possible_false_negative(): + """ + This is a test. + .. This is a test:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# This tests that a doctest inside of a reST literal block doesn't get +# reformatted. It's plausible this isn't the right behavior, but it also seems +# like it might be the right behavior since it is a literal block. (The doctest +# makes the Python code invalid.) +def rst_literal_skipped_doctest(): + """ + Do cool stuff:: + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def rst_directive_skipped_not_indented(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_skipped_wrong_language(): + """ + .. code-block:: rust + + cool_stuff( 1 ) + + Done. + """ + pass + + +# This gets skipped for the same reason that the doctest in a literal block +# gets skipped. +def rst_directive_skipped_doctest(): + """ + .. code-block:: python + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### + + +def markdown_simple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_simple_continued(): + """ + Do cool stuff. + + ```python + def cool_stuff( x ): + print( f"hi {x}" ); + ``` + + Done. + """ + pass + + +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. + + ``` + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. + + ~~~py + cool_stuff( 1 ) + ~~~ + + Done. + """ + pass + + +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + `````` + + Done. + """ + pass + + +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ```invalid + ''' + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. + + `````` + do_something( ''' + ``` + did i trick you? + ``` + ''' ) + `````` + + Done. + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + + + """ + pass + + +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + """ + pass + + +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ```""" + pass + + +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 )""" + pass + + +def markdown_with_blank_lines(): + """ + Do cool stuff. + + ```py + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_odd_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. + + ```py + + + cool_stuff( 1 ) + + + ``` + + Done. + """ + pass + + +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. + + ```py + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. + + ```python + cool_stuff( 1 ) + + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` + + Now the code block is closed + """ + pass + + +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print( 5 ) + ``` + """ + pass + + +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass + + +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. + """ + pass + + +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. +# +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. +# +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_doctest(): + """ + Do cool stuff. + + ```py + >>> cool_stuff( 1 ) + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_literal(): + """ + Do cool stuff. + + ```py + And do this:: + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass + + +def markdown_skipped_rst_directive(): + """ + Do cool stuff. + + ```py + .. code-block:: python + + cool_stuff( 1 ) + + ``` + + Done. + """ + pass +``` + + +### Output 4 +``` +indent-style = tab +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +preview = Disabled +``` + +```python +############################################################################### +# DOCTEST CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Python's "doctest" format. +# +# See: https://docs.python.org/3/library/doctest.html +############################################################################### + +# The simplest doctest to ensure basic formatting works. +def doctest_simple(): + """ + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 + """ + pass + + +# Another simple test, but one where the Python code +# extends over multiple lines. +def doctest_simple_continued(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Test that we support multiple directly adjacent +# doctests. +def doctest_adjacent(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + >>> cool_stuff( y ) + 2 + """ + pass + + +# Test that a doctest on the last non-whitespace line of a docstring +# reformats correctly. +def doctest_last_line(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + """ + pass + + +# Test that a doctest that continues to the last non-whitespace line of +# a docstring reformats correctly. +def doctest_last_line_continued(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + """ + pass + + +# Test that a doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line(): + """ + Do cool stuff. + + >>> cool_stuff( x )""" + pass + + +# Test that a continued doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line_continued(): + """ + Do cool stuff. + + >>> cool_stuff( x ) + ... more( y )""" + pass + + +# Test that a doctest is correctly identified and formatted with a blank +# continuation line. +def doctest_blank_continued(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... + ... print( x ) + """ + pass + + +# Tests that a blank PS2 line at the end of a doctest can get dropped. +# It is treated as part of the Python snippet which will trim the +# trailing whitespace. +def doctest_blank_end(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... + """ + pass + + +# Tests that a blank PS2 line at the end of a doctest can get dropped +# even when there is text following it. +def doctest_blank_end_then_some_text(): + """ + Do cool stuff. + + >>> def cool_stuff ( x ): + ... print( x ) + ... print( x ) + ... + + And say something else. + """ + pass + + +# Test that a doctest containing a triple quoted string gets formatted +# correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. + + >>> x = '''tricksy''' + """ + pass + + +# Test that a doctest containing a triple quoted f-string gets +# formatted correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. + + >>> x = f'''tricksy''' + """ + pass + + +# Another nested multi-line string case, but with triple escaped double +# quotes inside a triple single quoted string. +def doctest_with_triple_escaped_double(): + """ + Do cool stuff. + + >>> x = '''\"\"\"''' + """ + pass + + +# Tests that inverting the triple quoting works as expected. +def doctest_with_triple_inverted(): + ''' + Do cool stuff. + + >>> x = """tricksy""" + ''' + pass + + +# Tests that inverting the triple quoting with an f-string works as +# expected. +def doctest_with_triple_inverted_fstring(): + ''' + Do cool stuff. + + >>> x = f"""tricksy""" + ''' + pass + + +# Tests nested doctests are ignored. That is, we don't format doctests +# recursively. We only recognize "top level" doctests. +# +# This restriction primarily exists to avoid needing to deal with +# nesting quotes. It also seems like a generally sensible restriction, +# although it could be lifted if necessary I believe. +def doctest_nested_doctest_not_formatted(): + ''' + Do cool stuff. + + >>> def nested( x ): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' + pass + + +# Tests that the starting column does not matter. +def doctest_varying_start_column(): + """ + Do cool stuff. + + >>> assert ("Easy!") + >>> import math + >>> math.floor( 1.9 ) + 1 + """ + pass + + +# Tests that long lines get wrapped... appropriately. +# +# The docstring code formatter uses the same line width settings as for +# formatting other code. This means that a line in the docstring can +# actually extend past the configured line limit. +# +# It's not quite clear whether this is desirable or not. We could in +# theory compute the intendation length of a code snippet and then +# adjust the line-width setting on a recursive call to the formatter. +# But there are assuredly pathological cases to consider. Another path +# would be to expose another formatter option for controlling the +# line-width of code snippets independently. +def doctest_long_lines(): + """ + Do cool stuff. + + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) + """ + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) + + +# Checks that a simple but invalid doctest gets skipped. +def doctest_skipped_simple(): + """ + Do cool stuff. + + >>> cool-stuff( x ): + 2 + """ + pass + + +# Checks that a simple doctest that is continued over multiple lines, +# but is invalid, gets skipped. +def doctest_skipped_simple_continued(): + """ + Do cool stuff. + + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 + """ + pass + + +# Checks that a doctest with improper indentation gets skipped. +def doctest_skipped_inconsistent_indent(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Checks that a doctest with some proper indentation and some improper +# indentation is "partially" formatted. That is, the part that appears +# before the inconsistent indentation is formatted. This requires that +# the part before it is valid Python. +def doctest_skipped_partial_inconsistent_indent(): + """ + Do cool stuff. + + >>> def cool_stuff( x ): + ... print( x ) + ... print( f"hi {x}" ); + hi 2 + """ + pass + + +# Checks that a doctest with improper triple single quoted string gets +# skipped. That is, the code snippet is itself invalid Python, so it is +# left as is. +def doctest_skipped_triple_incorrect(): + """ + Do cool stuff. + + >>> foo( x ) + ... '''tri'''cksy''' + """ + pass + + +# Tests that a doctest on a single line is skipped. +def doctest_skipped_one_line(): + ">>> foo( x )" + pass + + +# f-strings are not considered docstrings[1], so any doctests +# inside of them should not be formatted. +# +# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +def doctest_skipped_fstring(): + f""" + Do cool stuff. + + >>> cool_stuff( 1 ) + 2 + """ + pass + + +# Test that a doctest containing a triple quoted string at least +# does not result in invalid Python code. Ideally this would format +# correctly, but at time of writing it does not. +def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): + """ + Do cool stuff. + + >>> x = '\"\"\"' + """ + pass + + +############################################################################### +# reStructuredText CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# reStructuredText formatted code blocks. +# +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 +############################################################################### + + +def rst_literal_simple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_literal_simple_continued(): + """ + Do cool stuff:: + + def cool_stuff( x ): + print( f"hi {x}" ); + + Done. + """ + pass + + +# Tests that we can end the literal block on the second +# to last line of the docstring. +def rst_literal_second_to_last(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + """ + pass + + +# Tests that we can end the literal block on the actual +# last line of the docstring. +def rst_literal_actually_last(): + """ + Do cool stuff:: + + cool_stuff( 1 )""" + pass + + +def rst_literal_with_blank_lines(): + """ + Do cool stuff:: + + def cool_stuff( x ): + print( f"hi {x}" ); + + def other_stuff( y ): + print( y ) + + Done. + """ + pass + + +# Extra blanks should be preserved. +def rst_literal_extra_blanks(): + """ + Do cool stuff:: + + + + cool_stuff( 1 ) + + + + Done. + """ + pass + + +# If a literal block is never properly ended (via a non-empty unindented line), +# then the end of the block should be the last non-empty line. And subsequent +# empty lines should be preserved as-is. +def rst_literal_extra_blanks_at_end(): + """ + Do cool stuff:: + + + cool_stuff( 1 ) + + + + """ + pass + + +# A literal block can contain many empty lines and it should not end the block +# if it continues. +def rst_literal_extra_blanks_in_snippet(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + + cool_stuff( 2 ) + + Done. + """ + pass + + +# This tests that a unindented line appearing after an indented line (but where +# the indent is still beyond the minimum) gets formatted properly. +def rst_literal_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# This checks that if the first line in a code snippet has been indented with +# tabs, then so long as its "indentation length" is considered bigger than the +# line with `::`, it is reformatted as code. +# +# (If your tabwidth is set to 4, then it looks like the code snippet +# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST +# itself also seems to recognize this as a code block, although it appears +# under-specified.) +def rst_literal_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Another test with tabs, except in this case, if your tabwidth is less than +# 8, than the code snippet actually looks like its indent is *less* than the +# opening line with a `::`. One might presume this means that the code snippet +# is not treated as a literal block and thus not reformatted, but since we +# assume all tabs have tabwidth=8 when computing indentation length, the code +# snippet is actually seen as being more indented than the opening `::` line. +# As with the above example, reST seems to behave the same way here. +def rst_literal_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff:: + + cool_stuff( 1 ) -# Another nested multi-line string case, but with triple escaped double -# quotes inside a triple single quoted string. -def doctest_with_triple_escaped_double(): - """ - Do cool stuff. + Done. + """ + pass - >>> x = '''\"\"\"''' - """ - pass +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff:: -# Tests that inverting the triple quoting works as expected. -def doctest_with_triple_inverted(): - ''' - Do cool stuff. + cool_stuff( 1 ) + cool_stuff( 2 ) - >>> x = """tricksy""" - ''' - pass + Done. + """ + pass -# Tests that inverting the triple quoting with an f-string works as -# expected. -def doctest_with_triple_inverted_fstring(): - ''' - Do cool stuff. +# Tests that if two lines in a literal block are indented to the same level +# but by different means (tabs versus spaces), then we correctly recognize the +# block and format it. +def rst_literal_first_line_tab_second_line_spaces(): + """ + Do cool stuff:: - >>> x = f"""tricksy""" - ''' - pass + cool_stuff( 1 ) + cool_stuff( 2 ) + Done. + """ + pass -# Tests nested doctests are ignored. That is, we don't format doctests -# recursively. We only recognize "top level" doctests. + +# Tests that when two lines in a code snippet have weird and inconsistent +# indentation, the code still gets formatted so long as the indent is greater +# than the indent of the `::` line. # -# This restriction primarily exists to avoid needing to deal with -# nesting quotes. It also seems like a generally sensible restriction, -# although it could be lifted if necessary I believe. -def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. +# In this case, the minimum indent is 5 spaces (from the second line) where as +# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). +# The minimum indent is stripped from each code line. Since tabs aren't +# divisible, the entire tab is stripped, which means the first and second lines +# wind up with the same level of indentation. +# +# An alternative behavior here would be that the tab is replaced with 3 spaces +# instead of being stripped entirely. The code snippet itself would then have +# inconsistent indentation to the point of being invalid Python, and thus code +# formatting would be skipped. +# +# I decided on the former behavior because it seems a bit easier to implement, +# but we might want to switch to the alternative if cases like this show up in +# the real world. ---AG +def rst_literal_odd_indentation(): + """ + Do cool stuff:: - >>> def nested( x ): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' - pass + cool_stuff( 1 ) + cool_stuff( 2 ) + Done. + """ + pass -# Tests that the starting column does not matter. -def doctest_varying_start_column(): - """ - Do cool stuff. - >>> assert ("Easy!") - >>> import math - >>> math.floor( 1.9 ) - 1 - """ - pass +# Tests that having a line with a lone `::` works as an introduction of a +# literal block. +def rst_literal_lone_colon(): + """ + Do cool stuff. + :: -# Tests that long lines get wrapped... appropriately. + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_simple(): + """ + .. code-block:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_case_insensitive(): + """ + .. cOdE-bLoCk:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_sourcecode(): + """ + .. sourcecode:: python + + cool_stuff( 1 ) + + Done. + """ + pass + + +def rst_directive_options(): + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah + + cool_stuff( 1 ) + cool_stuff( 2 ) + cool_stuff( 3 ) + cool_stuff( 4 ) + + Done. + """ + pass + + +# In this case, since `pycon` isn't recognized as a Python code snippet, the +# docstring reformatter ignores it. But it then picks up the doctest and +# reformats it. +def rst_directive_doctest(): + """ + .. code-block:: pycon + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +# This checks that if the first non-empty line after the start of a literal +# block is not indented more than the line containing the `::`, then it is not +# treated as a code snippet. +def rst_literal_skipped_first_line_not_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the test above, but inserts an indented line after the un-indented one. +# This should not cause the literal block to be resumed. +def rst_literal_skipped_first_line_not_indented_then_indented(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# This also checks that a code snippet is not reformatted when the indentation +# of the first line is not more than the line with `::`, but this uses tabs to +# make it a little more confounding. It relies on the fact that indentation +# length is computed by assuming a tabwidth equal to 8. reST also rejects this +# and doesn't treat it as a literal block. +def rst_literal_skipped_first_line_not_indented_tab(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + + Done. + """ + pass + + +# Like the previous test, but adds a second line. +def rst_literal_skipped_first_line_not_indented_tab_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ + pass + + +# Tests that a code block with a second line that is not properly indented gets +# skipped. A valid code block needs to have an empty line separating these. # -# The docstring code formatter uses the same line width settings as for -# formatting other code. This means that a line in the docstring can -# actually extend past the configured line limit. +# One trick here is that we need to make sure the Python code in the snippet is +# valid, otherwise it would be skipped because of invalid Python. +def rst_literal_skipped_subsequent_line_not_indented(): + """ + Do cool stuff:: + + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ + pass + + +# In this test, we write what looks like a code-block, but it should be treated +# as invalid due to the missing `language` argument. # -# It's not quite clear whether this is desirable or not. We could in -# theory compute the intendation length of a code snippet and then -# adjust the line-width setting on a recursive call to the formatter. -# But there are assuredly pathological cases to consider. Another path -# would be to expose another formatter option for controlling the -# line-width of code snippets independently. -def doctest_long_lines(): - """ - Do cool stuff. +# It does still look like it could be a literal block according to the literal +# rules, but we currently consider the `.. ` prefix to indicate that it is not +# a literal block. +def rst_literal_skipped_not_directive(): + """ + .. code-block:: - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + cool_stuff( 1 ) - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) - """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + Done. + """ + pass -# Checks that a simple but invalid doctest gets skipped. -def doctest_skipped_simple(): - """ - Do cool stuff. +# In this test, we start a line with `.. `, which makes it look like it might +# be a directive. But instead continue it as if it was just some periods from +# the previous line, and then try to end it by starting a literal block. +# +# But because of the `.. ` in the beginning, we wind up not treating this as a +# code snippet. The reST render I was using to test things does actually treat +# this as a code block, so we may be out of conformance here. +def rst_literal_skipped_possible_false_negative(): + """ + This is a test. + .. This is a test:: - >>> cool-stuff( x ): - 2 - """ - pass + cool_stuff( 1 ) + Done. + """ + pass -# Checks that a simple doctest that is continued over multiple lines, -# but is invalid, gets skipped. -def doctest_skipped_simple_continued(): - """ - Do cool stuff. - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 - """ - pass +# This tests that a doctest inside of a reST literal block doesn't get +# reformatted. It's plausible this isn't the right behavior, but it also seems +# like it might be the right behavior since it is a literal block. (The doctest +# makes the Python code invalid.) +def rst_literal_skipped_doctest(): + """ + Do cool stuff:: + >>> cool_stuff( 1 ) -# Checks that a doctest with improper indentation gets skipped. -def doctest_skipped_inconsistent_indent(): - """ - Do cool stuff. + Done. + """ + pass - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: -# Checks that a doctest with some proper indentation and some improper -# indentation is "partially" formatted. That is, the part that appears -# before the inconsistent indentation is formatted. This requires that -# the part before it is valid Python. -def doctest_skipped_partial_inconsistent_indent(): - """ - Do cool stuff. + ```py + cool_stuff( 1 ) + ``` - >>> def cool_stuff( x ): - ... print( x ) - ... print( f"hi {x}" ); - hi 2 - """ - pass + Done. + """ + pass -# Checks that a doctest with improper triple single quoted string gets -# skipped. That is, the code snippet is itself invalid Python, so it is -# left as is. -def doctest_skipped_triple_incorrect(): - """ - Do cool stuff. +def rst_directive_skipped_not_indented(): + """ + .. code-block:: python - >>> foo( x ) - ... '''tri'''cksy''' - """ - pass + cool_stuff( 1 ) + + Done. + """ + pass -# Tests that a doctest on a single line is skipped. -def doctest_skipped_one_line(): - ">>> foo( x )" - pass +def rst_directive_skipped_wrong_language(): + """ + .. code-block:: rust + cool_stuff( 1 ) -# f-strings are not considered docstrings[1], so any doctests -# inside of them should not be formatted. -# -# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals -def doctest_skipped_fstring(): - f""" - Do cool stuff. + Done. + """ + pass - >>> cool_stuff( 1 ) - 2 - """ - pass +# This gets skipped for the same reason that the doctest in a literal block +# gets skipped. +def rst_directive_skipped_doctest(): + """ + .. code-block:: python -# Test that a doctest containing a triple quoted string at least -# does not result in invalid Python code. Ideally this would format -# correctly, but at time of writing it does not. -def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): - """ - Do cool stuff. + >>> cool_stuff( 1 ) - >>> x = '\"\"\"' - """ - pass + Done. + """ + pass ############################################################################### -# reStructuredText CODE EXAMPLES +# Markdown CODE EXAMPLES # # This section shows examples of docstrings that contain code snippets in -# reStructuredText formatted code blocks. +# Markdown fenced code blocks. # -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks ############################################################################### -def rst_literal_simple(): - """ - Do cool stuff:: - - cool_stuff( 1 ) - - Done. - """ - pass - - -def rst_literal_simple_continued(): - """ - Do cool stuff:: +def markdown_simple(): + """ + Do cool stuff. - def cool_stuff( x ): - print( f"hi {x}" ); + ```py + cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -# Tests that we can end the literal block on the second -# to last line of the docstring. -def rst_literal_second_to_last(): - """ - Do cool stuff:: +def markdown_simple_continued(): + """ + Do cool stuff. - cool_stuff( 1 ) - """ - pass + ```python + def cool_stuff( x ): + print( f"hi {x}" ); + ``` + Done. + """ + pass -# Tests that we can end the literal block on the actual -# last line of the docstring. -def rst_literal_actually_last(): - """ - Do cool stuff:: - cool_stuff( 1 )""" - pass +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. + ``` + cool_stuff( 1 ) + ``` -def rst_literal_with_blank_lines(): - """ - Do cool stuff:: + Done. + """ + pass - def cool_stuff( x ): - print( f"hi {x}" ); - def other_stuff( y ): - print( y ) +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. - Done. - """ - pass + ~~~py + cool_stuff( 1 ) + ~~~ + Done. + """ + pass -# Extra blanks should be preserved. -def rst_literal_extra_blanks(): - """ - Do cool stuff:: +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + ```py + cool_stuff( 1 ) + `````` - cool_stuff( 1 ) + Done. + """ + pass +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff( 1 ) + ''' + ```invalid + ''' + cool_stuff( 2 ) + ``` + Done. + """ + pass -# If a literal block is never properly ended (via a non-empty unindented line), -# then the end of the block should be the last non-empty line. And subsequent -# empty lines should be preserved as-is. -def rst_literal_extra_blanks_at_end(): - """ - Do cool stuff:: +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. - cool_stuff( 1 ) + `````` + do_something( ''' + ``` + did i trick you? + ``` + ''' ) + `````` + Done. + """ + pass - """ - pass +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. + ```py + cool_stuff( 1 ) -# A literal block can contain many empty lines and it should not end the block -# if it continues. -def rst_literal_extra_blanks_in_snippet(): - """ - Do cool stuff:: - cool_stuff( 1 ) + """ + pass - cool_stuff( 2 ) - Done. - """ - pass +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. + ```py + cool_stuff( 1 ) + ``` + """ + pass -# This tests that a unindented line appearing after an indented line (but where -# the indent is still beyond the minimum) gets formatted properly. -def rst_literal_subsequent_line_not_indented(): - """ - Do cool stuff:: - if True: - cool_stuff( ''' - hiya''' ) +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff( 1 ) + """ + pass -# This checks that if the first line in a code snippet has been indented with -# tabs, then so long as its "indentation length" is considered bigger than the -# line with `::`, it is reformatted as code. -# -# (If your tabwidth is set to 4, then it looks like the code snippet -# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST -# itself also seems to recognize this as a code block, although it appears -# under-specified.) -def rst_literal_first_line_indent_uses_tabs_4spaces(): - """ - Do cool stuff:: +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + ```""" + pass - Done. - """ - pass +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): - """ - Do cool stuff:: + ```py + cool_stuff( 1 )""" + pass - cool_stuff( 1 ) - cool_stuff( 2 ) - Done. - """ - pass +def markdown_with_blank_lines(): + """ + Do cool stuff. + ```py + def cool_stuff( x ): + print( f"hi {x}" ); -# Another test with tabs, except in this case, if your tabwidth is less than -# 8, than the code snippet actually looks like its indent is *less* than the -# opening line with a `::`. One might presume this means that the code snippet -# is not treated as a literal block and thus not reformatted, but since we -# assume all tabs have tabwidth=8 when computing indentation length, the code -# snippet is actually seen as being more indented than the opening `::` line. -# As with the above example, reST seems to behave the same way here. -def rst_literal_first_line_indent_uses_tabs_8spaces(): - """ - Do cool stuff:: + def other_stuff( y ): + print( y ) + ``` - cool_stuff( 1 ) + Done. + """ + pass - Done. - """ - pass +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): - """ - Do cool stuff:: + ```py + cool_stuff( 1 ) + ``` - cool_stuff( 1 ) - cool_stuff( 2 ) + Done. + """ + pass - Done. - """ - pass +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. -# Tests that if two lines in a literal block are indented to the same level -# but by different means (tabs versus spaces), then we correctly recognize the -# block and format it. -def rst_literal_first_line_tab_second_line_spaces(): - """ - Do cool stuff:: + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` - cool_stuff( 1 ) - cool_stuff( 2 ) + Done. + """ + pass - Done. - """ - pass +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. -# Tests that when two lines in a code snippet have weird and inconsistent -# indentation, the code still gets formatted so long as the indent is greater -# than the indent of the `::` line. -# -# In this case, the minimum indent is 5 spaces (from the second line) where as -# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). -# The minimum indent is stripped from each code line. Since tabs aren't -# divisible, the entire tab is stripped, which means the first and second lines -# wind up with the same level of indentation. -# -# An alternative behavior here would be that the tab is replaced with 3 spaces -# instead of being stripped entirely. The code snippet itself would then have -# inconsistent indentation to the point of being invalid Python, and thus code -# formatting would be skipped. -# -# I decided on the former behavior because it seems a bit easier to implement, -# but we might want to switch to the alternative if cases like this show up in -# the real world. ---AG -def rst_literal_odd_indentation(): - """ - Do cool stuff:: + ```py + cool_stuff( 1 ) + ``` - cool_stuff( 1 ) - cool_stuff( 2 ) + Done. + """ + pass - Done. - """ - pass +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. -# Tests that having a line with a lone `::` works as an introduction of a -# literal block. -def rst_literal_lone_colon(): - """ - Do cool stuff. + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` - :: + Done. + """ + pass - cool_stuff( 1 ) - Done. - """ - pass +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` -def rst_directive_simple(): - """ - .. code-block:: python + Done. + """ + pass - cool_stuff( 1 ) - Done. - """ - pass +def markdown_odd_indentation(): + """ + Do cool stuff. + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` -def rst_directive_case_insensitive(): - """ - .. cOdE-bLoCk:: python + Done. + """ + pass - cool_stuff( 1 ) - Done. - """ - pass +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. + ```py -def rst_directive_sourcecode(): - """ - .. sourcecode:: python - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + ``` -def rst_directive_options(): - """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah + Done. + """ + pass - cool_stuff( 1 ) - cool_stuff( 2 ) - cool_stuff( 3 ) - cool_stuff( 4 ) - Done. - """ - pass +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. + ```py -# In this case, since `pycon` isn't recognized as a Python code snippet, the -# docstring reformatter ignores it. But it then picks up the doctest and -# reformats it. -def rst_directive_doctest(): - """ - .. code-block:: pycon + cool_stuff( 1 ) - >>> cool_stuff( 1 ) - Done. - """ - pass + cool_stuff( 2 ) + ``` + Done. + """ + pass -# This checks that if the first non-empty line after the start of a literal -# block is not indented more than the line containing the `::`, then it is not -# treated as a code snippet. -def rst_literal_skipped_first_line_not_indented(): - """ - Do cool stuff:: - cool_stuff( 1 ) +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. - Done. - """ - pass + ```python + cool_stuff( 1 ) + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` -# Like the test above, but inserts an indented line after the un-indented one. -# This should not cause the literal block to be resumed. -def rst_literal_skipped_first_line_not_indented_then_indented(): - """ - Do cool stuff:: + Now the code block is closed + """ + pass - cool_stuff( 1 ) - cool_stuff( 2 ) - Done. - """ - pass +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print( 5 ) + ``` + """ + pass -# This also checks that a code snippet is not reformatted when the indentation -# of the first line is not more than the line with `::`, but this uses tabs to -# make it a little more confounding. It relies on the fact that indentation -# length is computed by assuming a tabwidth equal to 8. reST also rejects this -# and doesn't treat it as a literal block. -def rst_literal_skipped_first_line_not_indented_tab(): - """ - Do cool stuff:: +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) - Done. - """ - pass + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass -# Like the previous test, but adds a second line. -def rst_literal_skipped_first_line_not_indented_tab_multiple(): - """ - Do cool stuff:: +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. - cool_stuff( 1 ) - cool_stuff( 2 ) + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` - Done. - """ - pass + Done. + """ + pass -# Tests that a code block with a second line that is not properly indented gets -# skipped. A valid code block needs to have an empty line separating these. +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. # -# One trick here is that we need to make sure the Python code in the snippet is -# valid, otherwise it would be skipped because of invalid Python. -def rst_literal_skipped_subsequent_line_not_indented(): - """ - Do cool stuff:: +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. - if True: - cool_stuff( ''' - hiya''' ) + ```py + cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -# In this test, we write what looks like a code-block, but it should be treated -# as invalid due to the missing `language` argument. -# -# It does still look like it could be a literal block according to the literal -# rules, but we currently consider the `.. ` prefix to indicate that it is not -# a literal block. -def rst_literal_skipped_not_directive(): - """ - .. code-block:: +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -# In this test, we start a line with `.. `, which makes it look like it might -# be a directive. But instead continue it as if it was just some periods from -# the previous line, and then try to end it by starting a literal block. +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. # -# But because of the `.. ` in the beginning, we wind up not treating this as a -# code snippet. The reST render I was using to test things does actually treat -# this as a code block, so we may be out of conformance here. -def rst_literal_skipped_possible_false_negative(): - """ - This is a test. - .. This is a test:: +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` - Done. - """ - pass + Done. + """ + pass -# This tests that a doctest inside of a reST literal block doesn't get -# reformatted. It's plausible this isn't the right behavior, but it also seems -# like it might be the right behavior since it is a literal block. (The doctest -# makes the Python code invalid.) -def rst_literal_skipped_doctest(): - """ - Do cool stuff:: +def markdown_skipped_doctest(): + """ + Do cool stuff. - >>> cool_stuff( 1 ) + ```py + >>> cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -def rst_directive_skipped_not_indented(): - """ - .. code-block:: python +def markdown_skipped_rst_literal(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + And do this:: - Done. - """ - pass + cool_stuff( 1 ) + ``` -def rst_directive_skipped_wrong_language(): - """ - .. code-block:: rust + Done. + """ + pass - cool_stuff( 1 ) - Done. - """ - pass +def markdown_skipped_rst_directive(): + """ + Do cool stuff. + ```py + .. code-block:: python -# This gets skipped for the same reason that the doctest in a literal block -# gets skipped. -def rst_directive_skipped_doctest(): - """ - .. code-block:: python + cool_stuff( 1 ) - >>> cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass ``` -### Output 3 +### Output 5 ``` -indent-style = tab +indent-style = space line-width = 88 -indent-width = 8 +indent-width = 4 quote-style = Double line-ending = LineFeed magic-trailing-comma = Respect -docstring-code = Disabled +docstring-code = Enabled preview = Disabled ``` @@ -2559,182 +6787,180 @@ preview = Disabled # The simplest doctest to ensure basic formatting works. def doctest_simple(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff( 1 ) - 2 - """ - pass + >>> cool_stuff(1) + 2 + """ + pass # Another simple test, but one where the Python code # extends over multiple lines. def doctest_simple_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + >>> def cool_stuff(x): + ... print(f"hi {x}") + hi 2 + """ + pass # Test that we support multiple directly adjacent # doctests. def doctest_adjacent(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff( x ) - >>> cool_stuff( y ) - 2 - """ - pass + >>> cool_stuff(x) + >>> cool_stuff(y) + 2 + """ + pass # Test that a doctest on the last non-whitespace line of a docstring # reformats correctly. def doctest_last_line(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff( x ) - """ - pass + >>> cool_stuff(x) + """ + pass # Test that a doctest that continues to the last non-whitespace line of # a docstring reformats correctly. def doctest_last_line_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - """ - pass + >>> def cool_stuff(x): + ... print(f"hi {x}") + """ + pass # Test that a doctest on the real last line of a docstring reformats # correctly. def doctest_really_last_line(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff( x )""" - pass + >>> cool_stuff(x)""" + pass # Test that a continued doctest on the real last line of a docstring reformats # correctly. def doctest_really_last_line_continued(): - """ - Do cool stuff. - - >>> cool_stuff( x ) - ... more( y )""" - pass + """ + Do cool stuff. + + >>> cool_stuff(x) + ... more(y)""" + pass # Test that a doctest is correctly identified and formatted with a blank # continuation line. def doctest_blank_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... - ... print( x ) - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... + ... print(x) + """ + pass # Tests that a blank PS2 line at the end of a doctest can get dropped. # It is treated as part of the Python snippet which will trim the # trailing whitespace. def doctest_blank_end(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... print(x) + """ + pass # Tests that a blank PS2 line at the end of a doctest can get dropped # even when there is text following it. def doctest_blank_end_then_some_text(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... + >>> def cool_stuff(x): + ... print(x) + ... print(x) - And say something else. - """ - pass + And say something else. + """ + pass # Test that a doctest containing a triple quoted string gets formatted # correctly and doesn't result in invalid syntax. def doctest_with_triple_single(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = '''tricksy''' - """ - pass + >>> x = '''tricksy''' + """ + pass # Test that a doctest containing a triple quoted f-string gets # formatted correctly and doesn't result in invalid syntax. def doctest_with_triple_single(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = f'''tricksy''' - """ - pass + >>> x = f'''tricksy''' + """ + pass # Another nested multi-line string case, but with triple escaped double # quotes inside a triple single quoted string. def doctest_with_triple_escaped_double(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = '''\"\"\"''' - """ - pass + >>> x = '''\"\"\"''' + """ + pass # Tests that inverting the triple quoting works as expected. def doctest_with_triple_inverted(): - ''' - Do cool stuff. + ''' + Do cool stuff. - >>> x = """tricksy""" - ''' - pass + >>> x = """tricksy""" + ''' + pass # Tests that inverting the triple quoting with an f-string works as # expected. def doctest_with_triple_inverted_fstring(): - ''' - Do cool stuff. + ''' + Do cool stuff. - >>> x = f"""tricksy""" - ''' - pass + >>> x = f"""tricksy""" + ''' + pass # Tests nested doctests are ignored. That is, we don't format doctests @@ -2744,30 +6970,30 @@ def doctest_with_triple_inverted_fstring(): # nesting quotes. It also seems like a generally sensible restriction, # although it could be lifted if necessary I believe. def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. + ''' + Do cool stuff. - >>> def nested( x ): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' - pass + >>> def nested(x): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' + pass # Tests that the starting column does not matter. def doctest_varying_start_column(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> assert ("Easy!") - >>> import math - >>> math.floor( 1.9 ) - 1 - """ - pass + >>> assert "Easy!" + >>> import math + >>> math.floor(1.9) + 1 + """ + pass # Tests that long lines get wrapped... appropriately. @@ -2783,59 +7009,61 @@ def doctest_varying_start_column(): # would be to expose another formatter option for controlling the # line-width of code snippets independently. def doctest_long_lines(): - """ - Do cool stuff. + """ + Do cool stuff. - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) - """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line( + ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard + ... ) + """ + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) # Checks that a simple but invalid doctest gets skipped. def doctest_skipped_simple(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool-stuff( x ): - 2 - """ - pass + >>> cool-stuff( x ): + 2 + """ + pass # Checks that a simple doctest that is continued over multiple lines, # but is invalid, gets skipped. def doctest_skipped_simple_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 - """ - pass + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 + """ + pass # Checks that a doctest with improper indentation gets skipped. def doctest_skipped_inconsistent_indent(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass # Checks that a doctest with some proper indentation and some improper @@ -2843,34 +7071,34 @@ def doctest_skipped_inconsistent_indent(): # before the inconsistent indentation is formatted. This requires that # the part before it is valid Python. def doctest_skipped_partial_inconsistent_indent(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( x ) - ... print( f"hi {x}" ); - hi 2 - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... print( f"hi {x}" ); + hi 2 + """ + pass # Checks that a doctest with improper triple single quoted string gets # skipped. That is, the code snippet is itself invalid Python, so it is # left as is. def doctest_skipped_triple_incorrect(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> foo( x ) - ... '''tri'''cksy''' - """ - pass + >>> foo( x ) + ... '''tri'''cksy''' + """ + pass # Tests that a doctest on a single line is skipped. def doctest_skipped_one_line(): - ">>> foo( x )" - pass + ">>> foo( x )" + pass # f-strings are not considered docstrings[1], so any doctests @@ -2878,25 +7106,25 @@ def doctest_skipped_one_line(): # # [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals def doctest_skipped_fstring(): - f""" + f""" Do cool stuff. >>> cool_stuff( 1 ) 2 """ - pass + pass # Test that a doctest containing a triple quoted string at least # does not result in invalid Python code. Ideally this would format # correctly, but at time of writing it does not. def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = '\"\"\"' - """ - pass + >>> x = '\"\"\"' + """ + pass ############################################################################### @@ -2914,125 +7142,128 @@ def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): def rst_literal_simple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_literal_simple_continued(): - """ - Do cool stuff:: + """ + Do cool stuff:: - def cool_stuff( x ): - print( f"hi {x}" ); + def cool_stuff(x): + print(f"hi {x}") - Done. - """ - pass + Done. + """ + pass # Tests that we can end the literal block on the second # to last line of the docstring. def rst_literal_second_to_last(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - """ - pass + cool_stuff(1) + """ + pass # Tests that we can end the literal block on the actual # last line of the docstring. def rst_literal_actually_last(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 )""" - pass + cool_stuff(1)""" + pass def rst_literal_with_blank_lines(): - """ - Do cool stuff:: + """ + Do cool stuff:: - def cool_stuff( x ): - print( f"hi {x}" ); + def cool_stuff(x): + print(f"hi {x}") - def other_stuff( y ): - print( y ) - Done. - """ - pass + def other_stuff(y): + print(y) + + Done. + """ + pass # Extra blanks should be preserved. def rst_literal_extra_blanks(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # If a literal block is never properly ended (via a non-empty unindented line), # then the end of the block should be the last non-empty line. And subsequent # empty lines should be preserved as-is. def rst_literal_extra_blanks_at_end(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - """ - pass + """ + pass # A literal block can contain many empty lines and it should not end the block # if it continues. def rst_literal_extra_blanks_in_snippet(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - cool_stuff( 2 ) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # This tests that a unindented line appearing after an indented line (but where # the indent is still beyond the minimum) gets formatted properly. def rst_literal_subsequent_line_not_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - if True: - cool_stuff( ''' - hiya''' ) + if True: + cool_stuff( + ''' + hiya''' + ) - Done. - """ - pass + Done. + """ + pass # This checks that if the first line in a code snippet has been indented with @@ -3044,27 +7275,27 @@ def rst_literal_subsequent_line_not_indented(): # itself also seems to recognize this as a code block, although it appears # under-specified.) def rst_literal_first_line_indent_uses_tabs_4spaces(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # Like the test above, but with multiple lines. def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Another test with tabs, except in this case, if your tabwidth is less than @@ -3075,42 +7306,42 @@ def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): # snippet is actually seen as being more indented than the opening `::` line. # As with the above example, reST seems to behave the same way here. def rst_literal_first_line_indent_uses_tabs_8spaces(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # Like the test above, but with multiple lines. def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Tests that if two lines in a literal block are indented to the same level # but by different means (tabs versus spaces), then we correctly recognize the # block and format it. def rst_literal_first_line_tab_second_line_spaces(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Tests that when two lines in a code snippet have weird and inconsistent @@ -3132,122 +7363,122 @@ def rst_literal_first_line_tab_second_line_spaces(): # but we might want to switch to the alternative if cases like this show up in # the real world. ---AG def rst_literal_odd_indentation(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Tests that having a line with a lone `::` works as an introduction of a # literal block. def rst_literal_lone_colon(): - """ - Do cool stuff. + """ + Do cool stuff. - :: + :: - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_simple(): - """ - .. code-block:: python + """ + .. code-block:: python - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_case_insensitive(): - """ - .. cOdE-bLoCk:: python + """ + .. cOdE-bLoCk:: python - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_sourcecode(): - """ - .. sourcecode:: python - - cool_stuff( 1 ) - - Done. - """ - pass + """ + .. sourcecode:: python + cool_stuff(1) -def rst_directive_options(): - """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah + Done. + """ + pass - cool_stuff( 1 ) - cool_stuff( 2 ) - cool_stuff( 3 ) - cool_stuff( 4 ) - Done. - """ - pass +def rst_directive_options(): + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah + + cool_stuff(1) + cool_stuff(2) + cool_stuff(3) + cool_stuff(4) + + Done. + """ + pass # In this case, since `pycon` isn't recognized as a Python code snippet, the # docstring reformatter ignores it. But it then picks up the doctest and # reformats it. def rst_directive_doctest(): - """ - .. code-block:: pycon + """ + .. code-block:: pycon - >>> cool_stuff( 1 ) + >>> cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # This checks that if the first non-empty line after the start of a literal # block is not indented more than the line containing the `::`, then it is not # treated as a code snippet. def rst_literal_skipped_first_line_not_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # Like the test above, but inserts an indented line after the un-indented one. # This should not cause the literal block to be resumed. def rst_literal_skipped_first_line_not_indented_then_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff( 1 ) + cool_stuff( 2 ) - Done. - """ - pass + Done. + """ + pass # This also checks that a code snippet is not reformatted when the indentation @@ -3256,27 +7487,27 @@ def rst_literal_skipped_first_line_not_indented_then_indented(): # length is computed by assuming a tabwidth equal to 8. reST also rejects this # and doesn't treat it as a literal block. def rst_literal_skipped_first_line_not_indented_tab(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # Like the previous test, but adds a second line. def rst_literal_skipped_first_line_not_indented_tab_multiple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff( 1 ) + cool_stuff( 2 ) - Done. - """ - pass + Done. + """ + pass # Tests that a code block with a second line that is not properly indented gets @@ -3285,16 +7516,16 @@ def rst_literal_skipped_first_line_not_indented_tab_multiple(): # One trick here is that we need to make sure the Python code in the snippet is # valid, otherwise it would be skipped because of invalid Python. def rst_literal_skipped_subsequent_line_not_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - if True: - cool_stuff( ''' - hiya''' ) + if True: + cool_stuff( ''' + hiya''' ) - Done. - """ - pass + Done. + """ + pass # In this test, we write what looks like a code-block, but it should be treated @@ -3304,14 +7535,14 @@ def rst_literal_skipped_subsequent_line_not_indented(): # rules, but we currently consider the `.. ` prefix to indicate that it is not # a literal block. def rst_literal_skipped_not_directive(): - """ - .. code-block:: + """ + .. code-block:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # In this test, we start a line with `.. `, which makes it look like it might @@ -3322,15 +7553,15 @@ def rst_literal_skipped_not_directive(): # code snippet. The reST render I was using to test things does actually treat # this as a code block, so we may be out of conformance here. def rst_literal_skipped_possible_false_negative(): - """ - This is a test. - .. This is a test:: + """ + This is a test. + .. This is a test:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # This tests that a doctest inside of a reST literal block doesn't get @@ -3338,1758 +7569,1905 @@ def rst_literal_skipped_possible_false_negative(): # like it might be the right behavior since it is a literal block. (The doctest # makes the Python code invalid.) def rst_literal_skipped_doctest(): - """ - Do cool stuff:: + """ + Do cool stuff:: - >>> cool_stuff( 1 ) + >>> cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass + + +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass def rst_directive_skipped_not_indented(): - """ - .. code-block:: python + """ + .. code-block:: python - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass def rst_directive_skipped_wrong_language(): - """ - .. code-block:: rust + """ + .. code-block:: rust - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # This gets skipped for the same reason that the doctest in a literal block # gets skipped. def rst_directive_skipped_doctest(): - """ - .. code-block:: python + """ + .. code-block:: python + + >>> cool_stuff( 1 ) + + Done. + """ + pass + + +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### + + +def markdown_simple(): + """ + Do cool stuff. + + ```py + cool_stuff(1) + ``` + + Done. + """ + pass + + +def markdown_simple_continued(): + """ + Do cool stuff. + + ```python + def cool_stuff(x): + print(f"hi {x}") + ``` + + Done. + """ + pass + + +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. + + ``` + cool_stuff(1) + ``` + + Done. + """ + pass + + +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. + + ~~~py + cool_stuff(1) + ~~~ + + Done. + """ + pass + + +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff(1) + `````` + + Done. + """ + pass + + +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + + ```py + cool_stuff(1) + ''' + ```invalid + ''' + cool_stuff(2) + ``` + + Done. + """ + pass + + +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. + + `````` + do_something( + ''' + ``` + did i trick you? + ``` + ''' + ) + `````` - >>> cool_stuff( 1 ) + Done. + """ + pass - Done. - """ - pass -``` +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. -### Output 4 -``` -indent-style = tab -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Disabled -preview = Disabled -``` + ```py + cool_stuff(1)""" + pass -```python -############################################################################### -# DOCTEST CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# Python's "doctest" format. -# -# See: https://docs.python.org/3/library/doctest.html -############################################################################### -# The simplest doctest to ensure basic formatting works. -def doctest_simple(): - """ - Do cool stuff. +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. - >>> cool_stuff( 1 ) - 2 - """ - pass + ```py + cool_stuff(1) + ``` + """ + pass -# Another simple test, but one where the Python code -# extends over multiple lines. -def doctest_simple_continued(): - """ - Do cool stuff. +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + ```py + cool_stuff(1)""" + pass -# Test that we support multiple directly adjacent -# doctests. -def doctest_adjacent(): - """ - Do cool stuff. +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. - >>> cool_stuff( x ) - >>> cool_stuff( y ) - 2 - """ - pass + ```py + cool_stuff(1) + ```""" + pass -# Test that a doctest on the last non-whitespace line of a docstring -# reformats correctly. -def doctest_last_line(): - """ - Do cool stuff. +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. - >>> cool_stuff( x ) - """ - pass + ```py + cool_stuff(1)""" + pass -# Test that a doctest that continues to the last non-whitespace line of -# a docstring reformats correctly. -def doctest_last_line_continued(): - """ - Do cool stuff. +def markdown_with_blank_lines(): + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - """ - pass + ```py + def cool_stuff(x): + print(f"hi {x}") -# Test that a doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line(): - """ - Do cool stuff. + def other_stuff(y): + print(y) + ``` - >>> cool_stuff( x )""" - pass + Done. + """ + pass -# Test that a continued doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line_continued(): - """ - Do cool stuff. +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. - >>> cool_stuff( x ) - ... more( y )""" - pass + ```py + cool_stuff(1) + ``` + Done. + """ + pass -# Test that a doctest is correctly identified and formatted with a blank -# continuation line. -def doctest_blank_continued(): - """ - Do cool stuff. - >>> def cool_stuff ( x ): - ... print( x ) - ... - ... print( x ) - """ - pass +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. + ```py + cool_stuff(1) + cool_stuff(2) + ``` -# Tests that a blank PS2 line at the end of a doctest can get dropped. -# It is treated as part of the Python snippet which will trim the -# trailing whitespace. -def doctest_blank_end(): - """ - Do cool stuff. + Done. + """ + pass - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... - """ - pass +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. -# Tests that a blank PS2 line at the end of a doctest can get dropped -# even when there is text following it. -def doctest_blank_end_then_some_text(): - """ - Do cool stuff. + ```py + cool_stuff(1) + ``` - >>> def cool_stuff ( x ): - ... print( x ) - ... print( x ) - ... + Done. + """ + pass - And say something else. - """ - pass +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. -# Test that a doctest containing a triple quoted string gets formatted -# correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): - """ - Do cool stuff. + ```py + cool_stuff(1) + cool_stuff(2) + ``` - >>> x = '''tricksy''' - """ - pass + Done. + """ + pass -# Test that a doctest containing a triple quoted f-string gets -# formatted correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): - """ - Do cool stuff. +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. - >>> x = f'''tricksy''' - """ - pass + ```py + cool_stuff(1) + cool_stuff(2) + ``` + Done. + """ + pass -# Another nested multi-line string case, but with triple escaped double -# quotes inside a triple single quoted string. -def doctest_with_triple_escaped_double(): - """ - Do cool stuff. - >>> x = '''\"\"\"''' - """ - pass +def markdown_odd_indentation(): + """ + Do cool stuff. + ```py + cool_stuff(1) + cool_stuff(2) + ``` -# Tests that inverting the triple quoting works as expected. -def doctest_with_triple_inverted(): - ''' - Do cool stuff. + Done. + """ + pass - >>> x = """tricksy""" - ''' - pass +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. -# Tests that inverting the triple quoting with an f-string works as -# expected. -def doctest_with_triple_inverted_fstring(): - ''' - Do cool stuff. + ```py + cool_stuff(1) + ``` - >>> x = f"""tricksy""" - ''' - pass + Done. + """ + pass -# Tests nested doctests are ignored. That is, we don't format doctests -# recursively. We only recognize "top level" doctests. -# -# This restriction primarily exists to avoid needing to deal with -# nesting quotes. It also seems like a generally sensible restriction, -# although it could be lifted if necessary I believe. -def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. - >>> def nested( x ): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' - pass + ```py + cool_stuff(1) -# Tests that the starting column does not matter. -def doctest_varying_start_column(): - """ - Do cool stuff. + cool_stuff(2) + ``` - >>> assert ("Easy!") - >>> import math - >>> math.floor( 1.9 ) - 1 - """ - pass + Done. + """ + pass -# Tests that long lines get wrapped... appropriately. -# -# The docstring code formatter uses the same line width settings as for -# formatting other code. This means that a line in the docstring can -# actually extend past the configured line limit. -# -# It's not quite clear whether this is desirable or not. We could in -# theory compute the intendation length of a code snippet and then -# adjust the line-width setting on a recursive call to the formatter. -# But there are assuredly pathological cases to consider. Another path -# would be to expose another formatter option for controlling the -# line-width of code snippets independently. -def doctest_long_lines(): - """ - Do cool stuff. +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + ```python + cool_stuff(1) + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard) - """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + Now the code block is closed + """ + pass -# Checks that a simple but invalid doctest gets skipped. -def doctest_skipped_simple(): - """ - Do cool stuff. +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print(5) + ``` + """ + pass - >>> cool-stuff( x ): - 2 - """ - pass +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. -# Checks that a simple doctest that is continued over multiple lines, -# but is invalid, gets skipped. -def doctest_skipped_simple_continued(): - """ - Do cool stuff. + ```py + cool_stuff( 1 ) - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 - """ - pass + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass -# Checks that a doctest with improper indentation gets skipped. -def doctest_skipped_inconsistent_indent(): - """ - Do cool stuff. +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + Done. + """ + pass -# Checks that a doctest with some proper indentation and some improper -# indentation is "partially" formatted. That is, the part that appears -# before the inconsistent indentation is formatted. This requires that -# the part before it is valid Python. -def doctest_skipped_partial_inconsistent_indent(): - """ - Do cool stuff. - >>> def cool_stuff( x ): - ... print( x ) - ... print( f"hi {x}" ); - hi 2 - """ - pass +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. +# +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. + ```py + cool_stuff( 1 ) + ``` -# Checks that a doctest with improper triple single quoted string gets -# skipped. That is, the code snippet is itself invalid Python, so it is -# left as is. -def doctest_skipped_triple_incorrect(): - """ - Do cool stuff. + Done. + """ + pass - >>> foo( x ) - ... '''tri'''cksy''' - """ - pass +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. -# Tests that a doctest on a single line is skipped. -def doctest_skipped_one_line(): - ">>> foo( x )" - pass + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass -# f-strings are not considered docstrings[1], so any doctests -# inside of them should not be formatted. +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. # -# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals -def doctest_skipped_fstring(): - f""" +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ Do cool stuff. - >>> cool_stuff( 1 ) - 2 + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` + + Done. """ - pass + pass -# Test that a doctest containing a triple quoted string at least -# does not result in invalid Python code. Ideally this would format -# correctly, but at time of writing it does not. -def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): - """ - Do cool stuff. +def markdown_skipped_doctest(): + """ + Do cool stuff. - >>> x = '\"\"\"' - """ - pass + ```py + >>> cool_stuff( 1 ) + ``` + Done. + """ + pass -############################################################################### -# reStructuredText CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# reStructuredText formatted code blocks. -# -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 -############################################################################### +def markdown_skipped_rst_literal(): + """ + Do cool stuff. -def rst_literal_simple(): - """ - Do cool stuff:: + ```py + And do this:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + ``` + Done. + """ + pass -def rst_literal_simple_continued(): - """ - Do cool stuff:: - def cool_stuff( x ): - print( f"hi {x}" ); +def markdown_skipped_rst_directive(): + """ + Do cool stuff. - Done. - """ - pass + ```py + .. code-block:: python + cool_stuff( 1 ) -# Tests that we can end the literal block on the second -# to last line of the docstring. -def rst_literal_second_to_last(): - """ - Do cool stuff:: + ``` - cool_stuff( 1 ) - """ - pass + Done. + """ + pass +``` -# Tests that we can end the literal block on the actual -# last line of the docstring. -def rst_literal_actually_last(): - """ - Do cool stuff:: +### Output 6 +``` +indent-style = space +line-width = 88 +indent-width = 2 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Enabled +preview = Disabled +``` - cool_stuff( 1 )""" - pass +```python +############################################################################### +# DOCTEST CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Python's "doctest" format. +# +# See: https://docs.python.org/3/library/doctest.html +############################################################################### +# The simplest doctest to ensure basic formatting works. +def doctest_simple(): + """ + Do cool stuff. -def rst_literal_with_blank_lines(): - """ - Do cool stuff:: + >>> cool_stuff(1) + 2 + """ + pass - def cool_stuff( x ): - print( f"hi {x}" ); - def other_stuff( y ): - print( y ) +# Another simple test, but one where the Python code +# extends over multiple lines. +def doctest_simple_continued(): + """ + Do cool stuff. - Done. - """ - pass + >>> def cool_stuff(x): + ... print(f"hi {x}") + hi 2 + """ + pass -# Extra blanks should be preserved. -def rst_literal_extra_blanks(): - """ - Do cool stuff:: +# Test that we support multiple directly adjacent +# doctests. +def doctest_adjacent(): + """ + Do cool stuff. + >>> cool_stuff(x) + >>> cool_stuff(y) + 2 + """ + pass - cool_stuff( 1 ) +# Test that a doctest on the last non-whitespace line of a docstring +# reformats correctly. +def doctest_last_line(): + """ + Do cool stuff. + >>> cool_stuff(x) + """ + pass - Done. - """ - pass +# Test that a doctest that continues to the last non-whitespace line of +# a docstring reformats correctly. +def doctest_last_line_continued(): + """ + Do cool stuff. + >>> def cool_stuff(x): + ... print(f"hi {x}") + """ + pass -# If a literal block is never properly ended (via a non-empty unindented line), -# then the end of the block should be the last non-empty line. And subsequent -# empty lines should be preserved as-is. -def rst_literal_extra_blanks_at_end(): - """ - Do cool stuff:: +# Test that a doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line(): + """ + Do cool stuff. - cool_stuff( 1 ) + >>> cool_stuff(x)""" + pass +# Test that a continued doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line_continued(): + """ + Do cool stuff. - """ - pass + >>> cool_stuff(x) + ... more(y)""" + pass -# A literal block can contain many empty lines and it should not end the block -# if it continues. -def rst_literal_extra_blanks_in_snippet(): - """ - Do cool stuff:: +# Test that a doctest is correctly identified and formatted with a blank +# continuation line. +def doctest_blank_continued(): + """ + Do cool stuff. - cool_stuff( 1 ) + >>> def cool_stuff(x): + ... print(x) + ... + ... print(x) + """ + pass - cool_stuff( 2 ) +# Tests that a blank PS2 line at the end of a doctest can get dropped. +# It is treated as part of the Python snippet which will trim the +# trailing whitespace. +def doctest_blank_end(): + """ + Do cool stuff. - Done. - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... print(x) + """ + pass -# This tests that a unindented line appearing after an indented line (but where -# the indent is still beyond the minimum) gets formatted properly. -def rst_literal_subsequent_line_not_indented(): - """ - Do cool stuff:: +# Tests that a blank PS2 line at the end of a doctest can get dropped +# even when there is text following it. +def doctest_blank_end_then_some_text(): + """ + Do cool stuff. - if True: - cool_stuff( ''' - hiya''' ) + >>> def cool_stuff(x): + ... print(x) + ... print(x) - Done. - """ - pass + And say something else. + """ + pass -# This checks that if the first line in a code snippet has been indented with -# tabs, then so long as its "indentation length" is considered bigger than the -# line with `::`, it is reformatted as code. -# -# (If your tabwidth is set to 4, then it looks like the code snippet -# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST -# itself also seems to recognize this as a code block, although it appears -# under-specified.) -def rst_literal_first_line_indent_uses_tabs_4spaces(): - """ - Do cool stuff:: +# Test that a doctest containing a triple quoted string gets formatted +# correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. - cool_stuff( 1 ) + >>> x = '''tricksy''' + """ + pass - Done. - """ - pass +# Test that a doctest containing a triple quoted f-string gets +# formatted correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): + """ + Do cool stuff. -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): - """ - Do cool stuff:: + >>> x = f'''tricksy''' + """ + pass - cool_stuff( 1 ) - cool_stuff( 2 ) - Done. - """ - pass +# Another nested multi-line string case, but with triple escaped double +# quotes inside a triple single quoted string. +def doctest_with_triple_escaped_double(): + """ + Do cool stuff. + >>> x = '''\"\"\"''' + """ + pass -# Another test with tabs, except in this case, if your tabwidth is less than -# 8, than the code snippet actually looks like its indent is *less* than the -# opening line with a `::`. One might presume this means that the code snippet -# is not treated as a literal block and thus not reformatted, but since we -# assume all tabs have tabwidth=8 when computing indentation length, the code -# snippet is actually seen as being more indented than the opening `::` line. -# As with the above example, reST seems to behave the same way here. -def rst_literal_first_line_indent_uses_tabs_8spaces(): - """ - Do cool stuff:: - cool_stuff( 1 ) +# Tests that inverting the triple quoting works as expected. +def doctest_with_triple_inverted(): + ''' + Do cool stuff. - Done. - """ - pass + >>> x = """tricksy""" + ''' + pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): - """ - Do cool stuff:: +# Tests that inverting the triple quoting with an f-string works as +# expected. +def doctest_with_triple_inverted_fstring(): + ''' + Do cool stuff. - cool_stuff( 1 ) - cool_stuff( 2 ) + >>> x = f"""tricksy""" + ''' + pass - Done. - """ - pass +# Tests nested doctests are ignored. That is, we don't format doctests +# recursively. We only recognize "top level" doctests. +# +# This restriction primarily exists to avoid needing to deal with +# nesting quotes. It also seems like a generally sensible restriction, +# although it could be lifted if necessary I believe. +def doctest_nested_doctest_not_formatted(): + ''' + Do cool stuff. -# Tests that if two lines in a literal block are indented to the same level -# but by different means (tabs versus spaces), then we correctly recognize the -# block and format it. -def rst_literal_first_line_tab_second_line_spaces(): - """ - Do cool stuff:: + >>> def nested(x): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' + pass - cool_stuff( 1 ) - cool_stuff( 2 ) - Done. - """ - pass +# Tests that the starting column does not matter. +def doctest_varying_start_column(): + """ + Do cool stuff. + >>> assert "Easy!" + >>> import math + >>> math.floor(1.9) + 1 + """ + pass -# Tests that when two lines in a code snippet have weird and inconsistent -# indentation, the code still gets formatted so long as the indent is greater -# than the indent of the `::` line. -# -# In this case, the minimum indent is 5 spaces (from the second line) where as -# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). -# The minimum indent is stripped from each code line. Since tabs aren't -# divisible, the entire tab is stripped, which means the first and second lines -# wind up with the same level of indentation. -# -# An alternative behavior here would be that the tab is replaced with 3 spaces -# instead of being stripped entirely. The code snippet itself would then have -# inconsistent indentation to the point of being invalid Python, and thus code -# formatting would be skipped. -# -# I decided on the former behavior because it seems a bit easier to implement, -# but we might want to switch to the alternative if cases like this show up in -# the real world. ---AG -def rst_literal_odd_indentation(): - """ - Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) +# Tests that long lines get wrapped... appropriately. +# +# The docstring code formatter uses the same line width settings as for +# formatting other code. This means that a line in the docstring can +# actually extend past the configured line limit. +# +# It's not quite clear whether this is desirable or not. We could in +# theory compute the intendation length of a code snippet and then +# adjust the line-width setting on a recursive call to the formatter. +# But there are assuredly pathological cases to consider. Another path +# would be to expose another formatter option for controlling the +# line-width of code snippets independently. +def doctest_long_lines(): + """ + Do cool stuff. - Done. - """ - pass + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line( + ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard + ... ) + """ + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) -# Tests that having a line with a lone `::` works as an introduction of a -# literal block. -def rst_literal_lone_colon(): - """ - Do cool stuff. - :: +# Checks that a simple but invalid doctest gets skipped. +def doctest_skipped_simple(): + """ + Do cool stuff. - cool_stuff( 1 ) + >>> cool-stuff( x ): + 2 + """ + pass - Done. - """ - pass +# Checks that a simple doctest that is continued over multiple lines, +# but is invalid, gets skipped. +def doctest_skipped_simple_continued(): + """ + Do cool stuff. -def rst_directive_simple(): - """ - .. code-block:: python + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 + """ + pass - cool_stuff( 1 ) - Done. - """ - pass +# Checks that a doctest with improper indentation gets skipped. +def doctest_skipped_inconsistent_indent(): + """ + Do cool stuff. + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass -def rst_directive_case_insensitive(): - """ - .. cOdE-bLoCk:: python - cool_stuff( 1 ) +# Checks that a doctest with some proper indentation and some improper +# indentation is "partially" formatted. That is, the part that appears +# before the inconsistent indentation is formatted. This requires that +# the part before it is valid Python. +def doctest_skipped_partial_inconsistent_indent(): + """ + Do cool stuff. - Done. - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... print( f"hi {x}" ); + hi 2 + """ + pass -def rst_directive_sourcecode(): - """ - .. sourcecode:: python +# Checks that a doctest with improper triple single quoted string gets +# skipped. That is, the code snippet is itself invalid Python, so it is +# left as is. +def doctest_skipped_triple_incorrect(): + """ + Do cool stuff. - cool_stuff( 1 ) + >>> foo( x ) + ... '''tri'''cksy''' + """ + pass - Done. - """ - pass +# Tests that a doctest on a single line is skipped. +def doctest_skipped_one_line(): + ">>> foo( x )" + pass -def rst_directive_options(): - """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah - cool_stuff( 1 ) - cool_stuff( 2 ) - cool_stuff( 3 ) - cool_stuff( 4 ) +# f-strings are not considered docstrings[1], so any doctests +# inside of them should not be formatted. +# +# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +def doctest_skipped_fstring(): + f""" + Do cool stuff. - Done. - """ - pass + >>> cool_stuff( 1 ) + 2 + """ + pass -# In this case, since `pycon` isn't recognized as a Python code snippet, the -# docstring reformatter ignores it. But it then picks up the doctest and -# reformats it. -def rst_directive_doctest(): - """ - .. code-block:: pycon +# Test that a doctest containing a triple quoted string at least +# does not result in invalid Python code. Ideally this would format +# correctly, but at time of writing it does not. +def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): + """ + Do cool stuff. - >>> cool_stuff( 1 ) + >>> x = '\"\"\"' + """ + pass - Done. - """ - pass +############################################################################### +# reStructuredText CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# reStructuredText formatted code blocks. +# +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 +############################################################################### -# This checks that if the first non-empty line after the start of a literal -# block is not indented more than the line containing the `::`, then it is not -# treated as a code snippet. -def rst_literal_skipped_first_line_not_indented(): - """ - Do cool stuff:: - cool_stuff( 1 ) +def rst_literal_simple(): + """ + Do cool stuff:: - Done. - """ - pass + cool_stuff(1) + Done. + """ + pass -# Like the test above, but inserts an indented line after the un-indented one. -# This should not cause the literal block to be resumed. -def rst_literal_skipped_first_line_not_indented_then_indented(): - """ - Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) +def rst_literal_simple_continued(): + """ + Do cool stuff:: - Done. - """ - pass + def cool_stuff(x): + print(f"hi {x}") + Done. + """ + pass -# This also checks that a code snippet is not reformatted when the indentation -# of the first line is not more than the line with `::`, but this uses tabs to -# make it a little more confounding. It relies on the fact that indentation -# length is computed by assuming a tabwidth equal to 8. reST also rejects this -# and doesn't treat it as a literal block. -def rst_literal_skipped_first_line_not_indented_tab(): - """ - Do cool stuff:: - cool_stuff( 1 ) +# Tests that we can end the literal block on the second +# to last line of the docstring. +def rst_literal_second_to_last(): + """ + Do cool stuff:: - Done. - """ - pass + cool_stuff(1) + """ + pass -# Like the previous test, but adds a second line. -def rst_literal_skipped_first_line_not_indented_tab_multiple(): - """ - Do cool stuff:: +# Tests that we can end the literal block on the actual +# last line of the docstring. +def rst_literal_actually_last(): + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff(1)""" + pass - Done. - """ - pass +def rst_literal_with_blank_lines(): + """ + Do cool stuff:: -# Tests that a code block with a second line that is not properly indented gets -# skipped. A valid code block needs to have an empty line separating these. -# -# One trick here is that we need to make sure the Python code in the snippet is -# valid, otherwise it would be skipped because of invalid Python. -def rst_literal_skipped_subsequent_line_not_indented(): - """ - Do cool stuff:: + def cool_stuff(x): + print(f"hi {x}") - if True: - cool_stuff( ''' - hiya''' ) - Done. - """ - pass + def other_stuff(y): + print(y) + Done. + """ + pass -# In this test, we write what looks like a code-block, but it should be treated -# as invalid due to the missing `language` argument. -# -# It does still look like it could be a literal block according to the literal -# rules, but we currently consider the `.. ` prefix to indicate that it is not -# a literal block. -def rst_literal_skipped_not_directive(): - """ - .. code-block:: - cool_stuff( 1 ) +# Extra blanks should be preserved. +def rst_literal_extra_blanks(): + """ + Do cool stuff:: - Done. - """ - pass -# In this test, we start a line with `.. `, which makes it look like it might -# be a directive. But instead continue it as if it was just some periods from -# the previous line, and then try to end it by starting a literal block. -# -# But because of the `.. ` in the beginning, we wind up not treating this as a -# code snippet. The reST render I was using to test things does actually treat -# this as a code block, so we may be out of conformance here. -def rst_literal_skipped_possible_false_negative(): - """ - This is a test. - .. This is a test:: + cool_stuff(1) - cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass -# This tests that a doctest inside of a reST literal block doesn't get -# reformatted. It's plausible this isn't the right behavior, but it also seems -# like it might be the right behavior since it is a literal block. (The doctest -# makes the Python code invalid.) -def rst_literal_skipped_doctest(): - """ - Do cool stuff:: - >>> cool_stuff( 1 ) +# If a literal block is never properly ended (via a non-empty unindented line), +# then the end of the block should be the last non-empty line. And subsequent +# empty lines should be preserved as-is. +def rst_literal_extra_blanks_at_end(): + """ + Do cool stuff:: - Done. - """ - pass + cool_stuff(1) -def rst_directive_skipped_not_indented(): - """ - .. code-block:: python - cool_stuff( 1 ) - Done. - """ - pass + """ + pass -def rst_directive_skipped_wrong_language(): - """ - .. code-block:: rust +# A literal block can contain many empty lines and it should not end the block +# if it continues. +def rst_literal_extra_blanks_in_snippet(): + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) - Done. - """ - pass + cool_stuff(2) -# This gets skipped for the same reason that the doctest in a literal block -# gets skipped. -def rst_directive_skipped_doctest(): - """ - .. code-block:: python + Done. + """ + pass - >>> cool_stuff( 1 ) - Done. - """ - pass -``` +# This tests that a unindented line appearing after an indented line (but where +# the indent is still beyond the minimum) gets formatted properly. +def rst_literal_subsequent_line_not_indented(): + """ + Do cool stuff:: + if True: + cool_stuff( + ''' + hiya''' + ) -### Output 5 -``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Enabled -preview = Disabled -``` + Done. + """ + pass -```python -############################################################################### -# DOCTEST CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# Python's "doctest" format. + +# This checks that if the first line in a code snippet has been indented with +# tabs, then so long as its "indentation length" is considered bigger than the +# line with `::`, it is reformatted as code. # -# See: https://docs.python.org/3/library/doctest.html -############################################################################### +# (If your tabwidth is set to 4, then it looks like the code snippet +# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST +# itself also seems to recognize this as a code block, although it appears +# under-specified.) +def rst_literal_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff:: -# The simplest doctest to ensure basic formatting works. -def doctest_simple(): - """ - Do cool stuff. + cool_stuff(1) - >>> cool_stuff(1) - 2 - """ - pass + Done. + """ + pass -# Another simple test, but one where the Python code -# extends over multiple lines. -def doctest_simple_continued(): - """ - Do cool stuff. +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff:: - >>> def cool_stuff(x): - ... print(f"hi {x}") - hi 2 - """ - pass + cool_stuff(1) + cool_stuff(2) + Done. + """ + pass -# Test that we support multiple directly adjacent -# doctests. -def doctest_adjacent(): - """ - Do cool stuff. - >>> cool_stuff(x) - >>> cool_stuff(y) - 2 - """ - pass +# Another test with tabs, except in this case, if your tabwidth is less than +# 8, than the code snippet actually looks like its indent is *less* than the +# opening line with a `::`. One might presume this means that the code snippet +# is not treated as a literal block and thus not reformatted, but since we +# assume all tabs have tabwidth=8 when computing indentation length, the code +# snippet is actually seen as being more indented than the opening `::` line. +# As with the above example, reST seems to behave the same way here. +def rst_literal_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff:: + cool_stuff(1) -# Test that a doctest on the last non-whitespace line of a docstring -# reformats correctly. -def doctest_last_line(): - """ - Do cool stuff. + Done. + """ + pass - >>> cool_stuff(x) - """ - pass +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff:: -# Test that a doctest that continues to the last non-whitespace line of -# a docstring reformats correctly. -def doctest_last_line_continued(): - """ - Do cool stuff. + cool_stuff(1) + cool_stuff(2) - >>> def cool_stuff(x): - ... print(f"hi {x}") - """ - pass + Done. + """ + pass -# Test that a doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line(): - """ - Do cool stuff. +# Tests that if two lines in a literal block are indented to the same level +# but by different means (tabs versus spaces), then we correctly recognize the +# block and format it. +def rst_literal_first_line_tab_second_line_spaces(): + """ + Do cool stuff:: - >>> cool_stuff(x)""" - pass + cool_stuff(1) + cool_stuff(2) + Done. + """ + pass + + +# Tests that when two lines in a code snippet have weird and inconsistent +# indentation, the code still gets formatted so long as the indent is greater +# than the indent of the `::` line. +# +# In this case, the minimum indent is 5 spaces (from the second line) where as +# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). +# The minimum indent is stripped from each code line. Since tabs aren't +# divisible, the entire tab is stripped, which means the first and second lines +# wind up with the same level of indentation. +# +# An alternative behavior here would be that the tab is replaced with 3 spaces +# instead of being stripped entirely. The code snippet itself would then have +# inconsistent indentation to the point of being invalid Python, and thus code +# formatting would be skipped. +# +# I decided on the former behavior because it seems a bit easier to implement, +# but we might want to switch to the alternative if cases like this show up in +# the real world. ---AG +def rst_literal_odd_indentation(): + """ + Do cool stuff:: -# Test that a continued doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line_continued(): - """ - Do cool stuff. + cool_stuff(1) + cool_stuff(2) - >>> cool_stuff(x) - ... more(y)""" - pass + Done. + """ + pass -# Test that a doctest is correctly identified and formatted with a blank -# continuation line. -def doctest_blank_continued(): - """ - Do cool stuff. +# Tests that having a line with a lone `::` works as an introduction of a +# literal block. +def rst_literal_lone_colon(): + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... - ... print(x) - """ - pass + :: + cool_stuff(1) -# Tests that a blank PS2 line at the end of a doctest can get dropped. -# It is treated as part of the Python snippet which will trim the -# trailing whitespace. -def doctest_blank_end(): - """ - Do cool stuff. + Done. + """ + pass - >>> def cool_stuff(x): - ... print(x) - ... print(x) - """ - pass +def rst_directive_simple(): + """ + .. code-block:: python -# Tests that a blank PS2 line at the end of a doctest can get dropped -# even when there is text following it. -def doctest_blank_end_then_some_text(): - """ - Do cool stuff. + cool_stuff(1) - >>> def cool_stuff(x): - ... print(x) - ... print(x) + Done. + """ + pass - And say something else. - """ - pass +def rst_directive_case_insensitive(): + """ + .. cOdE-bLoCk:: python -# Test that a doctest containing a triple quoted string gets formatted -# correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): - """ - Do cool stuff. + cool_stuff(1) - >>> x = '''tricksy''' - """ - pass + Done. + """ + pass -# Test that a doctest containing a triple quoted f-string gets -# formatted correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): - """ - Do cool stuff. +def rst_directive_sourcecode(): + """ + .. sourcecode:: python - >>> x = f'''tricksy''' - """ - pass + cool_stuff(1) + Done. + """ + pass -# Another nested multi-line string case, but with triple escaped double -# quotes inside a triple single quoted string. -def doctest_with_triple_escaped_double(): - """ - Do cool stuff. - >>> x = '''\"\"\"''' - """ - pass +def rst_directive_options(): + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah + cool_stuff(1) + cool_stuff(2) + cool_stuff(3) + cool_stuff(4) -# Tests that inverting the triple quoting works as expected. -def doctest_with_triple_inverted(): - ''' - Do cool stuff. + Done. + """ + pass - >>> x = """tricksy""" - ''' - pass +# In this case, since `pycon` isn't recognized as a Python code snippet, the +# docstring reformatter ignores it. But it then picks up the doctest and +# reformats it. +def rst_directive_doctest(): + """ + .. code-block:: pycon -# Tests that inverting the triple quoting with an f-string works as -# expected. -def doctest_with_triple_inverted_fstring(): - ''' - Do cool stuff. + >>> cool_stuff(1) - >>> x = f"""tricksy""" - ''' - pass + Done. + """ + pass -# Tests nested doctests are ignored. That is, we don't format doctests -# recursively. We only recognize "top level" doctests. -# -# This restriction primarily exists to avoid needing to deal with -# nesting quotes. It also seems like a generally sensible restriction, -# although it could be lifted if necessary I believe. -def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. +# This checks that if the first non-empty line after the start of a literal +# block is not indented more than the line containing the `::`, then it is not +# treated as a code snippet. +def rst_literal_skipped_first_line_not_indented(): + """ + Do cool stuff:: - >>> def nested(x): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' - pass + cool_stuff( 1 ) + Done. + """ + pass -# Tests that the starting column does not matter. -def doctest_varying_start_column(): - """ - Do cool stuff. - >>> assert "Easy!" - >>> import math - >>> math.floor(1.9) - 1 - """ - pass +# Like the test above, but inserts an indented line after the un-indented one. +# This should not cause the literal block to be resumed. +def rst_literal_skipped_first_line_not_indented_then_indented(): + """ + Do cool stuff:: + cool_stuff( 1 ) + cool_stuff( 2 ) -# Tests that long lines get wrapped... appropriately. -# -# The docstring code formatter uses the same line width settings as for -# formatting other code. This means that a line in the docstring can -# actually extend past the configured line limit. -# -# It's not quite clear whether this is desirable or not. We could in -# theory compute the intendation length of a code snippet and then -# adjust the line-width setting on a recursive call to the formatter. -# But there are assuredly pathological cases to consider. Another path -# would be to expose another formatter option for controlling the -# line-width of code snippets independently. -def doctest_long_lines(): - """ - Do cool stuff. + Done. + """ + pass - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line( - ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard - ... ) - """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) +# This also checks that a code snippet is not reformatted when the indentation +# of the first line is not more than the line with `::`, but this uses tabs to +# make it a little more confounding. It relies on the fact that indentation +# length is computed by assuming a tabwidth equal to 8. reST also rejects this +# and doesn't treat it as a literal block. +def rst_literal_skipped_first_line_not_indented_tab(): + """ + Do cool stuff:: + cool_stuff( 1 ) -# Checks that a simple but invalid doctest gets skipped. -def doctest_skipped_simple(): - """ - Do cool stuff. + Done. + """ + pass - >>> cool-stuff( x ): - 2 - """ - pass +# Like the previous test, but adds a second line. +def rst_literal_skipped_first_line_not_indented_tab_multiple(): + """ + Do cool stuff:: -# Checks that a simple doctest that is continued over multiple lines, -# but is invalid, gets skipped. -def doctest_skipped_simple_continued(): - """ - Do cool stuff. + cool_stuff( 1 ) + cool_stuff( 2 ) - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 - """ - pass + Done. + """ + pass -# Checks that a doctest with improper indentation gets skipped. -def doctest_skipped_inconsistent_indent(): - """ - Do cool stuff. +# Tests that a code block with a second line that is not properly indented gets +# skipped. A valid code block needs to have an empty line separating these. +# +# One trick here is that we need to make sure the Python code in the snippet is +# valid, otherwise it would be skipped because of invalid Python. +def rst_literal_skipped_subsequent_line_not_indented(): + """ + Do cool stuff:: - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + if True: + cool_stuff( ''' + hiya''' ) + Done. + """ + pass -# Checks that a doctest with some proper indentation and some improper -# indentation is "partially" formatted. That is, the part that appears -# before the inconsistent indentation is formatted. This requires that -# the part before it is valid Python. -def doctest_skipped_partial_inconsistent_indent(): - """ - Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... print( f"hi {x}" ); - hi 2 - """ - pass +# In this test, we write what looks like a code-block, but it should be treated +# as invalid due to the missing `language` argument. +# +# It does still look like it could be a literal block according to the literal +# rules, but we currently consider the `.. ` prefix to indicate that it is not +# a literal block. +def rst_literal_skipped_not_directive(): + """ + .. code-block:: + cool_stuff( 1 ) -# Checks that a doctest with improper triple single quoted string gets -# skipped. That is, the code snippet is itself invalid Python, so it is -# left as is. -def doctest_skipped_triple_incorrect(): - """ - Do cool stuff. + Done. + """ + pass - >>> foo( x ) - ... '''tri'''cksy''' - """ - pass +# In this test, we start a line with `.. `, which makes it look like it might +# be a directive. But instead continue it as if it was just some periods from +# the previous line, and then try to end it by starting a literal block. +# +# But because of the `.. ` in the beginning, we wind up not treating this as a +# code snippet. The reST render I was using to test things does actually treat +# this as a code block, so we may be out of conformance here. +def rst_literal_skipped_possible_false_negative(): + """ + This is a test. + .. This is a test:: -# Tests that a doctest on a single line is skipped. -def doctest_skipped_one_line(): - ">>> foo( x )" - pass + cool_stuff( 1 ) + Done. + """ + pass -# f-strings are not considered docstrings[1], so any doctests -# inside of them should not be formatted. -# -# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals -def doctest_skipped_fstring(): - f""" - Do cool stuff. - >>> cool_stuff( 1 ) - 2 - """ - pass +# This tests that a doctest inside of a reST literal block doesn't get +# reformatted. It's plausible this isn't the right behavior, but it also seems +# like it might be the right behavior since it is a literal block. (The doctest +# makes the Python code invalid.) +def rst_literal_skipped_doctest(): + """ + Do cool stuff:: + >>> cool_stuff( 1 ) -# Test that a doctest containing a triple quoted string at least -# does not result in invalid Python code. Ideally this would format -# correctly, but at time of writing it does not. -def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): - """ - Do cool stuff. + Done. + """ + pass - >>> x = '\"\"\"' - """ - pass +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: -############################################################################### -# reStructuredText CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# reStructuredText formatted code blocks. -# -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 -############################################################################### + ```py + cool_stuff( 1 ) + ``` + Done. + """ + pass -def rst_literal_simple(): - """ - Do cool stuff:: - cool_stuff(1) +def rst_directive_skipped_not_indented(): + """ + .. code-block:: python - Done. - """ - pass + cool_stuff( 1 ) + Done. + """ + pass -def rst_literal_simple_continued(): - """ - Do cool stuff:: - def cool_stuff(x): - print(f"hi {x}") +def rst_directive_skipped_wrong_language(): + """ + .. code-block:: rust - Done. - """ - pass + cool_stuff( 1 ) + Done. + """ + pass -# Tests that we can end the literal block on the second -# to last line of the docstring. -def rst_literal_second_to_last(): - """ - Do cool stuff:: - cool_stuff(1) - """ - pass +# This gets skipped for the same reason that the doctest in a literal block +# gets skipped. +def rst_directive_skipped_doctest(): + """ + .. code-block:: python + >>> cool_stuff( 1 ) -# Tests that we can end the literal block on the actual -# last line of the docstring. -def rst_literal_actually_last(): - """ - Do cool stuff:: + Done. + """ + pass - cool_stuff(1)""" - pass + +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### -def rst_literal_with_blank_lines(): - """ - Do cool stuff:: +def markdown_simple(): + """ + Do cool stuff. - def cool_stuff(x): - print(f"hi {x}") + ```py + cool_stuff(1) + ``` + Done. + """ + pass - def other_stuff(y): - print(y) - Done. - """ - pass +def markdown_simple_continued(): + """ + Do cool stuff. + ```python + def cool_stuff(x): + print(f"hi {x}") + ``` -# Extra blanks should be preserved. -def rst_literal_extra_blanks(): - """ - Do cool stuff:: + Done. + """ + pass +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): + """ + Do cool stuff. - cool_stuff(1) + ``` + cool_stuff(1) + ``` + Done. + """ + pass - Done. - """ - pass +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. + ~~~py + cool_stuff(1) + ~~~ -# If a literal block is never properly ended (via a non-empty unindented line), -# then the end of the block should be the last non-empty line. And subsequent -# empty lines should be preserved as-is. -def rst_literal_extra_blanks_at_end(): - """ - Do cool stuff:: + Done. + """ + pass - cool_stuff(1) +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): + """ + Do cool stuff. + ```py + cool_stuff(1) + `````` + Done. + """ + pass - """ - pass +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): + """ + Do cool stuff. -# A literal block can contain many empty lines and it should not end the block -# if it continues. -def rst_literal_extra_blanks_in_snippet(): - """ - Do cool stuff:: + ```py + cool_stuff(1) + ''' + ```invalid + ''' + cool_stuff(2) + ``` - cool_stuff(1) + Done. + """ + pass - cool_stuff(2) +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): + """ + Do cool stuff. - Done. - """ - pass + `````` + do_something( + ''' + ``` + did i trick you? + ``` + ''' + ) + `````` + Done. + """ + pass -# This tests that a unindented line appearing after an indented line (but where -# the indent is still beyond the minimum) gets formatted properly. -def rst_literal_subsequent_line_not_indented(): - """ - Do cool stuff:: - if True: - cool_stuff( - ''' - hiya''' - ) +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1)""" + pass -# This checks that if the first line in a code snippet has been indented with -# tabs, then so long as its "indentation length" is considered bigger than the -# line with `::`, it is reformatted as code. -# -# (If your tabwidth is set to 4, then it looks like the code snippet -# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST -# itself also seems to recognize this as a code block, although it appears -# under-specified.) -def rst_literal_first_line_indent_uses_tabs_4spaces(): - """ - Do cool stuff:: +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. - cool_stuff(1) + ```py + cool_stuff(1) + ``` + """ + pass - Done. - """ - pass +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): + """ + Do cool stuff. -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): - """ - Do cool stuff:: + ```py + cool_stuff(1)""" + pass - cool_stuff(1) - cool_stuff(2) - Done. - """ - pass +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): + """ + Do cool stuff. + ```py + cool_stuff(1) + ```""" + pass -# Another test with tabs, except in this case, if your tabwidth is less than -# 8, than the code snippet actually looks like its indent is *less* than the -# opening line with a `::`. One might presume this means that the code snippet -# is not treated as a literal block and thus not reformatted, but since we -# assume all tabs have tabwidth=8 when computing indentation length, the code -# snippet is actually seen as being more indented than the opening `::` line. -# As with the above example, reST seems to behave the same way here. -def rst_literal_first_line_indent_uses_tabs_8spaces(): - """ - Do cool stuff:: - cool_stuff(1) +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1)""" + pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): - """ - Do cool stuff:: +def markdown_with_blank_lines(): + """ + Do cool stuff. - cool_stuff(1) - cool_stuff(2) + ```py + def cool_stuff(x): + print(f"hi {x}") - Done. - """ - pass + def other_stuff(y): + print(y) + ``` -# Tests that if two lines in a literal block are indented to the same level -# but by different means (tabs versus spaces), then we correctly recognize the -# block and format it. -def rst_literal_first_line_tab_second_line_spaces(): - """ - Do cool stuff:: + Done. + """ + pass - cool_stuff(1) - cool_stuff(2) - Done. - """ - pass +def markdown_first_line_indent_uses_tabs_4spaces(): + """ + Do cool stuff. + ```py + cool_stuff(1) + ``` -# Tests that when two lines in a code snippet have weird and inconsistent -# indentation, the code still gets formatted so long as the indent is greater -# than the indent of the `::` line. -# -# In this case, the minimum indent is 5 spaces (from the second line) where as -# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). -# The minimum indent is stripped from each code line. Since tabs aren't -# divisible, the entire tab is stripped, which means the first and second lines -# wind up with the same level of indentation. -# -# An alternative behavior here would be that the tab is replaced with 3 spaces -# instead of being stripped entirely. The code snippet itself would then have -# inconsistent indentation to the point of being invalid Python, and thus code -# formatting would be skipped. -# -# I decided on the former behavior because it seems a bit easier to implement, -# but we might want to switch to the alternative if cases like this show up in -# the real world. ---AG -def rst_literal_odd_indentation(): - """ - Do cool stuff:: + Done. + """ + pass - cool_stuff(1) - cool_stuff(2) - Done. - """ - pass +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ + Do cool stuff. + ```py + cool_stuff(1) + cool_stuff(2) + ``` -# Tests that having a line with a lone `::` works as an introduction of a -# literal block. -def rst_literal_lone_colon(): - """ - Do cool stuff. + Done. + """ + pass - :: - cool_stuff(1) +def markdown_first_line_indent_uses_tabs_8spaces(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1) + ``` + Done. + """ + pass -def rst_directive_simple(): - """ - .. code-block:: python - cool_stuff(1) +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1) + cool_stuff(2) + ``` + Done. + """ + pass -def rst_directive_case_insensitive(): - """ - .. cOdE-bLoCk:: python - cool_stuff(1) +def markdown_first_line_tab_second_line_spaces(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1) + cool_stuff(2) + ``` + Done. + """ + pass -def rst_directive_sourcecode(): - """ - .. sourcecode:: python - cool_stuff(1) +def markdown_odd_indentation(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1) + cool_stuff(2) + ``` + Done. + """ + pass -def rst_directive_options(): - """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah - cool_stuff(1) - cool_stuff(2) - cool_stuff(3) - cool_stuff(4) +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1) + ``` + Done. + """ + pass -# In this case, since `pycon` isn't recognized as a Python code snippet, the -# docstring reformatter ignores it. But it then picks up the doctest and -# reformats it. -def rst_directive_doctest(): - """ - .. code-block:: pycon - >>> cool_stuff(1) +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff(1) -# This checks that if the first non-empty line after the start of a literal -# block is not indented more than the line containing the `::`, then it is not -# treated as a code snippet. -def rst_literal_skipped_first_line_not_indented(): - """ - Do cool stuff:: + cool_stuff(2) + ``` - cool_stuff( 1 ) + Done. + """ + pass - Done. - """ - pass +def markdown_weird_closing(): + """ + Code block with weirdly placed closing fences. -# Like the test above, but inserts an indented line after the un-indented one. -# This should not cause the literal block to be resumed. -def rst_literal_skipped_first_line_not_indented_then_indented(): - """ - Do cool stuff:: + ```python + cool_stuff(1) + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` - cool_stuff( 1 ) - cool_stuff( 2 ) + Now the code block is closed + """ + pass - Done. - """ - pass +def markdown_over_indented(): + """ + A docstring + over intended + ```python + print(5) + ``` + """ + pass -# This also checks that a code snippet is not reformatted when the indentation -# of the first line is not more than the line with `::`, but this uses tabs to -# make it a little more confounding. It relies on the fact that indentation -# length is computed by assuming a tabwidth equal to 8. reST also rejects this -# and doesn't treat it as a literal block. -def rst_literal_skipped_first_line_not_indented_tab(): - """ - Do cool stuff:: - cool_stuff( 1 ) +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff( 1 ) + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. + """ + pass -# Like the previous test, but adds a second line. -def rst_literal_skipped_first_line_not_indented_tab_multiple(): - """ - Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. - Done. - """ - pass + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. + """ + pass -# Tests that a code block with a second line that is not properly indented gets -# skipped. A valid code block needs to have an empty line separating these. +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. # -# One trick here is that we need to make sure the Python code in the snippet is -# valid, otherwise it would be skipped because of invalid Python. -def rst_literal_skipped_subsequent_line_not_indented(): - """ - Do cool stuff:: +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. - if True: - cool_stuff( ''' - hiya''' ) + ```py + cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -# In this test, we write what looks like a code-block, but it should be treated -# as invalid due to the missing `language` argument. -# -# It does still look like it could be a literal block according to the literal -# rules, but we currently consider the `.. ` prefix to indicate that it is not -# a literal block. -def rst_literal_skipped_not_directive(): - """ - .. code-block:: +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -# In this test, we start a line with `.. `, which makes it look like it might -# be a directive. But instead continue it as if it was just some periods from -# the previous line, and then try to end it by starting a literal block. +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. # -# But because of the `.. ` in the beginning, we wind up not treating this as a -# code snippet. The reST render I was using to test things does actually treat -# this as a code block, so we may be out of conformance here. -def rst_literal_skipped_possible_false_negative(): - """ - This is a test. - .. This is a test:: +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` - Done. - """ - pass + Done. + """ + pass -# This tests that a doctest inside of a reST literal block doesn't get -# reformatted. It's plausible this isn't the right behavior, but it also seems -# like it might be the right behavior since it is a literal block. (The doctest -# makes the Python code invalid.) -def rst_literal_skipped_doctest(): - """ - Do cool stuff:: +def markdown_skipped_doctest(): + """ + Do cool stuff. - >>> cool_stuff( 1 ) + ```py + >>> cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass -def rst_directive_skipped_not_indented(): - """ - .. code-block:: python +def markdown_skipped_rst_literal(): + """ + Do cool stuff. - cool_stuff( 1 ) + ```py + And do this:: - Done. - """ - pass + cool_stuff( 1 ) + ``` -def rst_directive_skipped_wrong_language(): - """ - .. code-block:: rust + Done. + """ + pass - cool_stuff( 1 ) - Done. - """ - pass +def markdown_skipped_rst_directive(): + """ + Do cool stuff. + ```py + .. code-block:: python -# This gets skipped for the same reason that the doctest in a literal block -# gets skipped. -def rst_directive_skipped_doctest(): - """ - .. code-block:: python + cool_stuff( 1 ) - >>> cool_stuff( 1 ) + ``` - Done. - """ - pass + Done. + """ + pass ``` -### Output 6 +### Output 7 ``` -indent-style = space +indent-style = tab line-width = 88 -indent-width = 2 +indent-width = 8 quote-style = Double line-ending = LineFeed magic-trailing-comma = Respect @@ -5109,180 +9487,180 @@ preview = Disabled # The simplest doctest to ensure basic formatting works. def doctest_simple(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff(1) - 2 - """ - pass + >>> cool_stuff(1) + 2 + """ + pass # Another simple test, but one where the Python code # extends over multiple lines. def doctest_simple_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(f"hi {x}") - hi 2 - """ - pass + >>> def cool_stuff(x): + ... print(f"hi {x}") + hi 2 + """ + pass # Test that we support multiple directly adjacent # doctests. def doctest_adjacent(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff(x) - >>> cool_stuff(y) - 2 - """ - pass + >>> cool_stuff(x) + >>> cool_stuff(y) + 2 + """ + pass # Test that a doctest on the last non-whitespace line of a docstring # reformats correctly. def doctest_last_line(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff(x) - """ - pass + >>> cool_stuff(x) + """ + pass # Test that a doctest that continues to the last non-whitespace line of # a docstring reformats correctly. def doctest_last_line_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(f"hi {x}") - """ - pass + >>> def cool_stuff(x): + ... print(f"hi {x}") + """ + pass # Test that a doctest on the real last line of a docstring reformats # correctly. def doctest_really_last_line(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff(x)""" - pass + >>> cool_stuff(x)""" + pass # Test that a continued doctest on the real last line of a docstring reformats # correctly. def doctest_really_last_line_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool_stuff(x) - ... more(y)""" - pass + >>> cool_stuff(x) + ... more(y)""" + pass # Test that a doctest is correctly identified and formatted with a blank # continuation line. def doctest_blank_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... - ... print(x) - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... + ... print(x) + """ + pass # Tests that a blank PS2 line at the end of a doctest can get dropped. # It is treated as part of the Python snippet which will trim the # trailing whitespace. def doctest_blank_end(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... print(x) - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... print(x) + """ + pass # Tests that a blank PS2 line at the end of a doctest can get dropped # even when there is text following it. def doctest_blank_end_then_some_text(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... print(x) + >>> def cool_stuff(x): + ... print(x) + ... print(x) - And say something else. - """ - pass + And say something else. + """ + pass # Test that a doctest containing a triple quoted string gets formatted # correctly and doesn't result in invalid syntax. def doctest_with_triple_single(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = '''tricksy''' - """ - pass + >>> x = '''tricksy''' + """ + pass # Test that a doctest containing a triple quoted f-string gets # formatted correctly and doesn't result in invalid syntax. def doctest_with_triple_single(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = f'''tricksy''' - """ - pass + >>> x = f'''tricksy''' + """ + pass # Another nested multi-line string case, but with triple escaped double # quotes inside a triple single quoted string. def doctest_with_triple_escaped_double(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = '''\"\"\"''' - """ - pass + >>> x = '''\"\"\"''' + """ + pass # Tests that inverting the triple quoting works as expected. def doctest_with_triple_inverted(): - ''' - Do cool stuff. + ''' + Do cool stuff. - >>> x = """tricksy""" - ''' - pass + >>> x = """tricksy""" + ''' + pass # Tests that inverting the triple quoting with an f-string works as # expected. def doctest_with_triple_inverted_fstring(): - ''' - Do cool stuff. + ''' + Do cool stuff. - >>> x = f"""tricksy""" - ''' - pass + >>> x = f"""tricksy""" + ''' + pass # Tests nested doctests are ignored. That is, we don't format doctests @@ -5292,30 +9670,30 @@ def doctest_with_triple_inverted_fstring(): # nesting quotes. It also seems like a generally sensible restriction, # although it could be lifted if necessary I believe. def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. + ''' + Do cool stuff. - >>> def nested(x): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' - pass + >>> def nested(x): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' + pass # Tests that the starting column does not matter. def doctest_varying_start_column(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> assert "Easy!" - >>> import math - >>> math.floor(1.9) - 1 - """ - pass + >>> assert "Easy!" + >>> import math + >>> math.floor(1.9) + 1 + """ + pass # Tests that long lines get wrapped... appropriately. @@ -5331,61 +9709,61 @@ def doctest_varying_start_column(): # would be to expose another formatter option for controlling the # line-width of code snippets independently. def doctest_long_lines(): - """ - Do cool stuff. + """ + Do cool stuff. - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line( - ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard - ... ) - """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line( + ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard + ... ) + """ + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) # Checks that a simple but invalid doctest gets skipped. def doctest_skipped_simple(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> cool-stuff( x ): - 2 - """ - pass + >>> cool-stuff( x ): + 2 + """ + pass # Checks that a simple doctest that is continued over multiple lines, # but is invalid, gets skipped. def doctest_skipped_simple_continued(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 - """ - pass + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 + """ + pass # Checks that a doctest with improper indentation gets skipped. def doctest_skipped_inconsistent_indent(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 - """ - pass + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 + """ + pass # Checks that a doctest with some proper indentation and some improper @@ -5393,34 +9771,34 @@ def doctest_skipped_inconsistent_indent(): # before the inconsistent indentation is formatted. This requires that # the part before it is valid Python. def doctest_skipped_partial_inconsistent_indent(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... print( f"hi {x}" ); - hi 2 - """ - pass + >>> def cool_stuff(x): + ... print(x) + ... print( f"hi {x}" ); + hi 2 + """ + pass # Checks that a doctest with improper triple single quoted string gets # skipped. That is, the code snippet is itself invalid Python, so it is # left as is. def doctest_skipped_triple_incorrect(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> foo( x ) - ... '''tri'''cksy''' - """ - pass + >>> foo( x ) + ... '''tri'''cksy''' + """ + pass # Tests that a doctest on a single line is skipped. def doctest_skipped_one_line(): - ">>> foo( x )" - pass + ">>> foo( x )" + pass # f-strings are not considered docstrings[1], so any doctests @@ -5428,25 +9806,25 @@ def doctest_skipped_one_line(): # # [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals def doctest_skipped_fstring(): - f""" + f""" Do cool stuff. >>> cool_stuff( 1 ) 2 """ - pass + pass # Test that a doctest containing a triple quoted string at least # does not result in invalid Python code. Ideally this would format # correctly, but at time of writing it does not. def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): - """ - Do cool stuff. + """ + Do cool stuff. - >>> x = '\"\"\"' - """ - pass + >>> x = '\"\"\"' + """ + pass ############################################################################### @@ -5464,128 +9842,128 @@ def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): def rst_literal_simple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_literal_simple_continued(): - """ - Do cool stuff:: - - def cool_stuff(x): - print(f"hi {x}") + """ + Do cool stuff:: - Done. - """ - pass + def cool_stuff(x): + print(f"hi {x}") + + Done. + """ + pass # Tests that we can end the literal block on the second # to last line of the docstring. def rst_literal_second_to_last(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) - """ - pass + cool_stuff(1) + """ + pass # Tests that we can end the literal block on the actual # last line of the docstring. def rst_literal_actually_last(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1)""" - pass + cool_stuff(1)""" + pass def rst_literal_with_blank_lines(): - """ - Do cool stuff:: + """ + Do cool stuff:: - def cool_stuff(x): - print(f"hi {x}") + def cool_stuff(x): + print(f"hi {x}") - def other_stuff(y): - print(y) + def other_stuff(y): + print(y) - Done. - """ - pass + Done. + """ + pass # Extra blanks should be preserved. def rst_literal_extra_blanks(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # If a literal block is never properly ended (via a non-empty unindented line), # then the end of the block should be the last non-empty line. And subsequent # empty lines should be preserved as-is. def rst_literal_extra_blanks_at_end(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) + cool_stuff(1) - """ - pass + """ + pass # A literal block can contain many empty lines and it should not end the block # if it continues. def rst_literal_extra_blanks_in_snippet(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) + cool_stuff(1) - cool_stuff(2) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # This tests that a unindented line appearing after an indented line (but where # the indent is still beyond the minimum) gets formatted properly. def rst_literal_subsequent_line_not_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - if True: - cool_stuff( - ''' - hiya''' - ) + if True: + cool_stuff( + ''' + hiya''' + ) - Done. - """ - pass + Done. + """ + pass # This checks that if the first line in a code snippet has been indented with @@ -5597,27 +9975,27 @@ def rst_literal_subsequent_line_not_indented(): # itself also seems to recognize this as a code block, although it appears # under-specified.) def rst_literal_first_line_indent_uses_tabs_4spaces(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # Like the test above, but with multiple lines. def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) - cool_stuff(2) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Another test with tabs, except in this case, if your tabwidth is less than @@ -5628,42 +10006,42 @@ def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): # snippet is actually seen as being more indented than the opening `::` line. # As with the above example, reST seems to behave the same way here. def rst_literal_first_line_indent_uses_tabs_8spaces(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # Like the test above, but with multiple lines. def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) - cool_stuff(2) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Tests that if two lines in a literal block are indented to the same level # but by different means (tabs versus spaces), then we correctly recognize the # block and format it. def rst_literal_first_line_tab_second_line_spaces(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) - cool_stuff(2) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Tests that when two lines in a code snippet have weird and inconsistent @@ -5685,122 +10063,122 @@ def rst_literal_first_line_tab_second_line_spaces(): # but we might want to switch to the alternative if cases like this show up in # the real world. ---AG def rst_literal_odd_indentation(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff(1) - cool_stuff(2) + cool_stuff(1) + cool_stuff(2) - Done. - """ - pass + Done. + """ + pass # Tests that having a line with a lone `::` works as an introduction of a # literal block. def rst_literal_lone_colon(): - """ - Do cool stuff. + """ + Do cool stuff. - :: + :: - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_simple(): - """ - .. code-block:: python + """ + .. code-block:: python - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_case_insensitive(): - """ - .. cOdE-bLoCk:: python + """ + .. cOdE-bLoCk:: python - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_sourcecode(): - """ - .. sourcecode:: python + """ + .. sourcecode:: python - cool_stuff(1) + cool_stuff(1) - Done. - """ - pass + Done. + """ + pass def rst_directive_options(): - """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah + """ + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah - cool_stuff(1) - cool_stuff(2) - cool_stuff(3) - cool_stuff(4) + cool_stuff(1) + cool_stuff(2) + cool_stuff(3) + cool_stuff(4) - Done. - """ - pass + Done. + """ + pass # In this case, since `pycon` isn't recognized as a Python code snippet, the # docstring reformatter ignores it. But it then picks up the doctest and # reformats it. def rst_directive_doctest(): - """ - .. code-block:: pycon + """ + .. code-block:: pycon - >>> cool_stuff(1) + >>> cool_stuff(1) - Done. - """ - pass + Done. + """ + pass # This checks that if the first non-empty line after the start of a literal # block is not indented more than the line containing the `::`, then it is not # treated as a code snippet. def rst_literal_skipped_first_line_not_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # Like the test above, but inserts an indented line after the un-indented one. # This should not cause the literal block to be resumed. def rst_literal_skipped_first_line_not_indented_then_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff( 1 ) + cool_stuff( 2 ) - Done. - """ - pass + Done. + """ + pass # This also checks that a code snippet is not reformatted when the indentation @@ -5809,27 +10187,27 @@ def rst_literal_skipped_first_line_not_indented_then_indented(): # length is computed by assuming a tabwidth equal to 8. reST also rejects this # and doesn't treat it as a literal block. def rst_literal_skipped_first_line_not_indented_tab(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # Like the previous test, but adds a second line. def rst_literal_skipped_first_line_not_indented_tab_multiple(): - """ - Do cool stuff:: + """ + Do cool stuff:: - cool_stuff( 1 ) - cool_stuff( 2 ) + cool_stuff( 1 ) + cool_stuff( 2 ) - Done. - """ - pass + Done. + """ + pass # Tests that a code block with a second line that is not properly indented gets @@ -5838,16 +10216,16 @@ def rst_literal_skipped_first_line_not_indented_tab_multiple(): # One trick here is that we need to make sure the Python code in the snippet is # valid, otherwise it would be skipped because of invalid Python. def rst_literal_skipped_subsequent_line_not_indented(): - """ - Do cool stuff:: + """ + Do cool stuff:: - if True: - cool_stuff( ''' - hiya''' ) + if True: + cool_stuff( ''' + hiya''' ) - Done. - """ - pass + Done. + """ + pass # In this test, we write what looks like a code-block, but it should be treated @@ -5857,14 +10235,14 @@ def rst_literal_skipped_subsequent_line_not_indented(): # rules, but we currently consider the `.. ` prefix to indicate that it is not # a literal block. def rst_literal_skipped_not_directive(): - """ - .. code-block:: + """ + .. code-block:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # In this test, we start a line with `.. `, which makes it look like it might @@ -5875,15 +10253,15 @@ def rst_literal_skipped_not_directive(): # code snippet. The reST render I was using to test things does actually treat # this as a code block, so we may be out of conformance here. def rst_literal_skipped_possible_false_negative(): - """ - This is a test. - .. This is a test:: + """ + This is a test. + .. This is a test:: - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # This tests that a doctest inside of a reST literal block doesn't get @@ -5891,1749 +10269,1893 @@ def rst_literal_skipped_possible_false_negative(): # like it might be the right behavior since it is a literal block. (The doctest # makes the Python code invalid.) def rst_literal_skipped_doctest(): - """ - Do cool stuff:: + """ + Do cool stuff:: - >>> cool_stuff( 1 ) + >>> cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass + + +def rst_literal_skipped_markdown(): + """ + Do cool stuff:: + + ```py + cool_stuff( 1 ) + ``` + + Done. + """ + pass def rst_directive_skipped_not_indented(): - """ - .. code-block:: python + """ + .. code-block:: python - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass def rst_directive_skipped_wrong_language(): - """ - .. code-block:: rust + """ + .. code-block:: rust - cool_stuff( 1 ) + cool_stuff( 1 ) - Done. - """ - pass + Done. + """ + pass # This gets skipped for the same reason that the doctest in a literal block # gets skipped. def rst_directive_skipped_doctest(): - """ - .. code-block:: python - - >>> cool_stuff( 1 ) + """ + .. code-block:: python - Done. - """ - pass -``` + >>> cool_stuff( 1 ) + Done. + """ + pass -### Output 7 -``` -indent-style = tab -line-width = 88 -indent-width = 8 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Enabled -preview = Disabled -``` -```python ############################################################################### -# DOCTEST CODE EXAMPLES +# Markdown CODE EXAMPLES # # This section shows examples of docstrings that contain code snippets in -# Python's "doctest" format. +# Markdown fenced code blocks. # -# See: https://docs.python.org/3/library/doctest.html +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks ############################################################################### -# The simplest doctest to ensure basic formatting works. -def doctest_simple(): + +def markdown_simple(): """ Do cool stuff. - >>> cool_stuff(1) - 2 + ```py + cool_stuff(1) + ``` + + Done. """ pass -# Another simple test, but one where the Python code -# extends over multiple lines. -def doctest_simple_continued(): +def markdown_simple_continued(): """ Do cool stuff. - >>> def cool_stuff(x): - ... print(f"hi {x}") - hi 2 + ```python + def cool_stuff(x): + print(f"hi {x}") + ``` + + Done. """ pass -# Test that we support multiple directly adjacent -# doctests. -def doctest_adjacent(): +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): """ Do cool stuff. - >>> cool_stuff(x) - >>> cool_stuff(y) - 2 + ``` + cool_stuff(1) + ``` + + Done. """ pass -# Test that a doctest on the last non-whitespace line of a docstring -# reformats correctly. -def doctest_last_line(): +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): """ Do cool stuff. - >>> cool_stuff(x) + ~~~py + cool_stuff(1) + ~~~ + + Done. """ pass -# Test that a doctest that continues to the last non-whitespace line of -# a docstring reformats correctly. -def doctest_last_line_continued(): +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): """ Do cool stuff. - >>> def cool_stuff(x): - ... print(f"hi {x}") + ```py + cool_stuff(1) + `````` + + Done. """ pass -# Test that a doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line(): +# Tests that an invalid closing fence is treated as invalid. +# +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): """ Do cool stuff. - >>> cool_stuff(x)""" + ```py + cool_stuff(1) + ''' + ```invalid + ''' + cool_stuff(2) + ``` + + Done. + """ pass -# Test that a continued doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line_continued(): +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): """ Do cool stuff. - >>> cool_stuff(x) - ... more(y)""" + `````` + do_something( + ''' + ``` + did i trick you? + ``` + ''' + ) + `````` + + Done. + """ pass -# Test that a doctest is correctly identified and formatted with a blank -# continuation line. -def doctest_blank_continued(): +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): """ Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... - ... print(x) - """ + ```py + cool_stuff(1)""" pass -# Tests that a blank PS2 line at the end of a doctest can get dropped. -# It is treated as part of the Python snippet which will trim the -# trailing whitespace. -def doctest_blank_end(): +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): """ Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... print(x) + ```py + cool_stuff(1) + ``` """ pass -# Tests that a blank PS2 line at the end of a doctest can get dropped -# even when there is text following it. -def doctest_blank_end_then_some_text(): +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): """ Do cool stuff. - >>> def cool_stuff(x): - ... print(x) - ... print(x) + ```py + cool_stuff(1)""" + pass + - And say something else. +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): """ + Do cool stuff. + + ```py + cool_stuff(1) + ```""" pass -# Test that a doctest containing a triple quoted string gets formatted -# correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): """ Do cool stuff. - >>> x = '''tricksy''' - """ + ```py + cool_stuff(1)""" pass -# Test that a doctest containing a triple quoted f-string gets -# formatted correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): +def markdown_with_blank_lines(): """ Do cool stuff. - >>> x = f'''tricksy''' + ```py + def cool_stuff(x): + print(f"hi {x}") + + + def other_stuff(y): + print(y) + ``` + + Done. """ pass -# Another nested multi-line string case, but with triple escaped double -# quotes inside a triple single quoted string. -def doctest_with_triple_escaped_double(): +def markdown_first_line_indent_uses_tabs_4spaces(): """ Do cool stuff. - >>> x = '''\"\"\"''' + ```py + cool_stuff(1) + ``` + + Done. """ pass -# Tests that inverting the triple quoting works as expected. -def doctest_with_triple_inverted(): - ''' +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): + """ Do cool stuff. - >>> x = """tricksy""" - ''' + ```py + cool_stuff(1) + cool_stuff(2) + ``` + + Done. + """ pass -# Tests that inverting the triple quoting with an f-string works as -# expected. -def doctest_with_triple_inverted_fstring(): - ''' +def markdown_first_line_indent_uses_tabs_8spaces(): + """ Do cool stuff. - >>> x = f"""tricksy""" - ''' + ```py + cool_stuff(1) + ``` + + Done. + """ pass -# Tests nested doctests are ignored. That is, we don't format doctests -# recursively. We only recognize "top level" doctests. -# -# This restriction primarily exists to avoid needing to deal with -# nesting quotes. It also seems like a generally sensible restriction, -# although it could be lifted if necessary I believe. -def doctest_nested_doctest_not_formatted(): - ''' +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): + """ Do cool stuff. - >>> def nested(x): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' + ```py + cool_stuff(1) + cool_stuff(2) + ``` + + Done. + """ pass -# Tests that the starting column does not matter. -def doctest_varying_start_column(): +def markdown_first_line_tab_second_line_spaces(): """ Do cool stuff. - >>> assert "Easy!" - >>> import math - >>> math.floor(1.9) - 1 + ```py + cool_stuff(1) + cool_stuff(2) + ``` + + Done. """ pass -# Tests that long lines get wrapped... appropriately. -# -# The docstring code formatter uses the same line width settings as for -# formatting other code. This means that a line in the docstring can -# actually extend past the configured line limit. -# -# It's not quite clear whether this is desirable or not. We could in -# theory compute the intendation length of a code snippet and then -# adjust the line-width setting on a recursive call to the formatter. -# But there are assuredly pathological cases to consider. Another path -# would be to expose another formatter option for controlling the -# line-width of code snippets independently. -def doctest_long_lines(): +def markdown_odd_indentation(): """ Do cool stuff. - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + ```py + cool_stuff(1) + cool_stuff(2) + ``` - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line( - ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard - ... ) + Done. """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + pass -# Checks that a simple but invalid doctest gets skipped. -def doctest_skipped_simple(): +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): """ Do cool stuff. - >>> cool-stuff( x ): - 2 + ```py + cool_stuff(1) + ``` + + Done. """ pass -# Checks that a simple doctest that is continued over multiple lines, -# but is invalid, gets skipped. -def doctest_skipped_simple_continued(): +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): """ Do cool stuff. - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 + ```py + cool_stuff(1) + + + cool_stuff(2) + ``` + + Done. """ pass -# Checks that a doctest with improper indentation gets skipped. -def doctest_skipped_inconsistent_indent(): +def markdown_weird_closing(): """ - Do cool stuff. + Code block with weirdly placed closing fences. - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 + ```python + cool_stuff(1) + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` + + Now the code block is closed """ pass -# Checks that a doctest with some proper indentation and some improper -# indentation is "partially" formatted. That is, the part that appears -# before the inconsistent indentation is formatted. This requires that -# the part before it is valid Python. -def doctest_skipped_partial_inconsistent_indent(): +def markdown_over_indented(): """ - Do cool stuff. - - >>> def cool_stuff(x): - ... print(x) - ... print( f"hi {x}" ); - hi 2 + A docstring + over intended + ```python + print(5) + ``` """ pass -# Checks that a doctest with improper triple single quoted string gets -# skipped. That is, the code snippet is itself invalid Python, so it is -# left as is. -def doctest_skipped_triple_incorrect(): +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): """ Do cool stuff. - >>> foo( x ) - ... '''tri'''cksy''' + ```py + cool_stuff( 1 ) + + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. """ pass -# Tests that a doctest on a single line is skipped. -def doctest_skipped_one_line(): - ">>> foo( x )" +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): + """ + Do cool stuff. + + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` + + Done. + """ pass -# f-strings are not considered docstrings[1], so any doctests -# inside of them should not be formatted. +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. # -# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals -def doctest_skipped_fstring(): - f""" - Do cool stuff. +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): + """ + Do cool stuff. - >>> cool_stuff( 1 ) - 2 - """ + ```py + cool_stuff( 1 ) + ``` + + Done. + """ pass -# Test that a doctest containing a triple quoted string at least -# does not result in invalid Python code. Ideally this would format -# correctly, but at time of writing it does not. -def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): """ Do cool stuff. - >>> x = '\"\"\"' + ```py + cool_stuff( 1 ) + ``` + + Done. """ pass -############################################################################### -# reStructuredText CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# reStructuredText formatted code blocks. +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. # -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 -############################################################################### - - -def rst_literal_simple(): +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` Done. """ pass -def rst_literal_simple_continued(): +def markdown_skipped_doctest(): """ - Do cool stuff:: + Do cool stuff. - def cool_stuff(x): - print(f"hi {x}") + ```py + >>> cool_stuff( 1 ) + ``` Done. """ pass -# Tests that we can end the literal block on the second -# to last line of the docstring. -def rst_literal_second_to_last(): +def markdown_skipped_rst_literal(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) + ```py + And do this:: + + cool_stuff( 1 ) + + ``` + + Done. """ pass -# Tests that we can end the literal block on the actual -# last line of the docstring. -def rst_literal_actually_last(): +def markdown_skipped_rst_directive(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1)""" - pass + ```py + .. code-block:: python + + cool_stuff( 1 ) + ``` -def rst_literal_with_blank_lines(): + Done. """ - Do cool stuff:: + pass +``` - def cool_stuff(x): - print(f"hi {x}") +### Output 8 +``` +indent-style = tab +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Enabled +preview = Disabled +``` - def other_stuff(y): - print(y) +```python +############################################################################### +# DOCTEST CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Python's "doctest" format. +# +# See: https://docs.python.org/3/library/doctest.html +############################################################################### - Done. +# The simplest doctest to ensure basic formatting works. +def doctest_simple(): """ - pass - + Do cool stuff. -# Extra blanks should be preserved. -def rst_literal_extra_blanks(): + >>> cool_stuff(1) + 2 """ - Do cool stuff:: - - - - cool_stuff(1) + pass +# Another simple test, but one where the Python code +# extends over multiple lines. +def doctest_simple_continued(): + """ + Do cool stuff. - Done. + >>> def cool_stuff(x): + ... print(f"hi {x}") + hi 2 """ pass -# If a literal block is never properly ended (via a non-empty unindented line), -# then the end of the block should be the last non-empty line. And subsequent -# empty lines should be preserved as-is. -def rst_literal_extra_blanks_at_end(): +# Test that we support multiple directly adjacent +# doctests. +def doctest_adjacent(): """ - Do cool stuff:: - - - cool_stuff(1) - - + Do cool stuff. + >>> cool_stuff(x) + >>> cool_stuff(y) + 2 """ pass -# A literal block can contain many empty lines and it should not end the block -# if it continues. -def rst_literal_extra_blanks_in_snippet(): +# Test that a doctest on the last non-whitespace line of a docstring +# reformats correctly. +def doctest_last_line(): """ - Do cool stuff:: - - cool_stuff(1) - - - cool_stuff(2) + Do cool stuff. - Done. + >>> cool_stuff(x) """ pass -# This tests that a unindented line appearing after an indented line (but where -# the indent is still beyond the minimum) gets formatted properly. -def rst_literal_subsequent_line_not_indented(): +# Test that a doctest that continues to the last non-whitespace line of +# a docstring reformats correctly. +def doctest_last_line_continued(): """ - Do cool stuff:: - - if True: - cool_stuff( - ''' - hiya''' - ) + Do cool stuff. - Done. + >>> def cool_stuff(x): + ... print(f"hi {x}") """ pass -# This checks that if the first line in a code snippet has been indented with -# tabs, then so long as its "indentation length" is considered bigger than the -# line with `::`, it is reformatted as code. -# -# (If your tabwidth is set to 4, then it looks like the code snippet -# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST -# itself also seems to recognize this as a code block, although it appears -# under-specified.) -def rst_literal_first_line_indent_uses_tabs_4spaces(): +# Test that a doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line(): """ - Do cool stuff:: - - cool_stuff(1) + Do cool stuff. - Done. - """ + >>> cool_stuff(x)""" pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): +# Test that a continued doctest on the real last line of a docstring reformats +# correctly. +def doctest_really_last_line_continued(): """ - Do cool stuff:: - - cool_stuff(1) - cool_stuff(2) + Do cool stuff. - Done. - """ + >>> cool_stuff(x) + ... more(y)""" pass -# Another test with tabs, except in this case, if your tabwidth is less than -# 8, than the code snippet actually looks like its indent is *less* than the -# opening line with a `::`. One might presume this means that the code snippet -# is not treated as a literal block and thus not reformatted, but since we -# assume all tabs have tabwidth=8 when computing indentation length, the code -# snippet is actually seen as being more indented than the opening `::` line. -# As with the above example, reST seems to behave the same way here. -def rst_literal_first_line_indent_uses_tabs_8spaces(): +# Test that a doctest is correctly identified and formatted with a blank +# continuation line. +def doctest_blank_continued(): """ - Do cool stuff:: - - cool_stuff(1) + Do cool stuff. - Done. + >>> def cool_stuff(x): + ... print(x) + ... + ... print(x) """ pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): +# Tests that a blank PS2 line at the end of a doctest can get dropped. +# It is treated as part of the Python snippet which will trim the +# trailing whitespace. +def doctest_blank_end(): """ - Do cool stuff:: - - cool_stuff(1) - cool_stuff(2) + Do cool stuff. - Done. + >>> def cool_stuff(x): + ... print(x) + ... print(x) """ pass -# Tests that if two lines in a literal block are indented to the same level -# but by different means (tabs versus spaces), then we correctly recognize the -# block and format it. -def rst_literal_first_line_tab_second_line_spaces(): +# Tests that a blank PS2 line at the end of a doctest can get dropped +# even when there is text following it. +def doctest_blank_end_then_some_text(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) - cool_stuff(2) + >>> def cool_stuff(x): + ... print(x) + ... print(x) - Done. + And say something else. """ pass -# Tests that when two lines in a code snippet have weird and inconsistent -# indentation, the code still gets formatted so long as the indent is greater -# than the indent of the `::` line. -# -# In this case, the minimum indent is 5 spaces (from the second line) where as -# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). -# The minimum indent is stripped from each code line. Since tabs aren't -# divisible, the entire tab is stripped, which means the first and second lines -# wind up with the same level of indentation. -# -# An alternative behavior here would be that the tab is replaced with 3 spaces -# instead of being stripped entirely. The code snippet itself would then have -# inconsistent indentation to the point of being invalid Python, and thus code -# formatting would be skipped. -# -# I decided on the former behavior because it seems a bit easier to implement, -# but we might want to switch to the alternative if cases like this show up in -# the real world. ---AG -def rst_literal_odd_indentation(): +# Test that a doctest containing a triple quoted string gets formatted +# correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): """ - Do cool stuff:: - - cool_stuff(1) - cool_stuff(2) + Do cool stuff. - Done. + >>> x = '''tricksy''' """ pass -# Tests that having a line with a lone `::` works as an introduction of a -# literal block. -def rst_literal_lone_colon(): +# Test that a doctest containing a triple quoted f-string gets +# formatted correctly and doesn't result in invalid syntax. +def doctest_with_triple_single(): """ Do cool stuff. - :: - - cool_stuff(1) - - Done. + >>> x = f'''tricksy''' """ pass -def rst_directive_simple(): +# Another nested multi-line string case, but with triple escaped double +# quotes inside a triple single quoted string. +def doctest_with_triple_escaped_double(): """ - .. code-block:: python - - cool_stuff(1) + Do cool stuff. - Done. + >>> x = '''\"\"\"''' """ pass -def rst_directive_case_insensitive(): - """ - .. cOdE-bLoCk:: python - - cool_stuff(1) +# Tests that inverting the triple quoting works as expected. +def doctest_with_triple_inverted(): + ''' + Do cool stuff. - Done. - """ + >>> x = """tricksy""" + ''' pass -def rst_directive_sourcecode(): - """ - .. sourcecode:: python - - cool_stuff(1) +# Tests that inverting the triple quoting with an f-string works as +# expected. +def doctest_with_triple_inverted_fstring(): + ''' + Do cool stuff. - Done. - """ + >>> x = f"""tricksy""" + ''' pass -def rst_directive_options(): - """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah - - cool_stuff(1) - cool_stuff(2) - cool_stuff(3) - cool_stuff(4) +# Tests nested doctests are ignored. That is, we don't format doctests +# recursively. We only recognize "top level" doctests. +# +# This restriction primarily exists to avoid needing to deal with +# nesting quotes. It also seems like a generally sensible restriction, +# although it could be lifted if necessary I believe. +def doctest_nested_doctest_not_formatted(): + ''' + Do cool stuff. - Done. - """ + >>> def nested(x): + ... """ + ... Do nested cool stuff. + ... >>> func_call( 5 ) + ... """ + ... pass + ''' pass -# In this case, since `pycon` isn't recognized as a Python code snippet, the -# docstring reformatter ignores it. But it then picks up the doctest and -# reformats it. -def rst_directive_doctest(): +# Tests that the starting column does not matter. +def doctest_varying_start_column(): """ - .. code-block:: pycon - - >>> cool_stuff(1) + Do cool stuff. - Done. + >>> assert "Easy!" + >>> import math + >>> math.floor(1.9) + 1 """ pass -# This checks that if the first non-empty line after the start of a literal -# block is not indented more than the line containing the `::`, then it is not -# treated as a code snippet. -def rst_literal_skipped_first_line_not_indented(): +# Tests that long lines get wrapped... appropriately. +# +# The docstring code formatter uses the same line width settings as for +# formatting other code. This means that a line in the docstring can +# actually extend past the configured line limit. +# +# It's not quite clear whether this is desirable or not. We could in +# theory compute the intendation length of a code snippet and then +# adjust the line-width setting on a recursive call to the formatter. +# But there are assuredly pathological cases to consider. Another path +# would be to expose another formatter option for controlling the +# line-width of code snippets independently. +def doctest_long_lines(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff( 1 ) + This won't get wrapped even though it exceeds our configured + line width because it doesn't exceed the line width within this + docstring. e.g, the `f` in `foo` is treated as the first column. + >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) - Done. + But this one is long enough to get wrapped. + >>> foo, bar, quux = this_is_a_long_line( + ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard + ... ) """ - pass + # This demostrates a normal line that will get wrapped but won't + # get wrapped in the docstring above because of how the line-width + # setting gets reset at the first column in each code snippet. + foo, bar, quux = this_is_a_long_line( + lion, giraffe, hippo, zeba, lemur, penguin, monkey + ) -# Like the test above, but inserts an indented line after the un-indented one. -# This should not cause the literal block to be resumed. -def rst_literal_skipped_first_line_not_indented_then_indented(): +# Checks that a simple but invalid doctest gets skipped. +def doctest_skipped_simple(): """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) + Do cool stuff. - Done. + >>> cool-stuff( x ): + 2 """ pass -# This also checks that a code snippet is not reformatted when the indentation -# of the first line is not more than the line with `::`, but this uses tabs to -# make it a little more confounding. It relies on the fact that indentation -# length is computed by assuming a tabwidth equal to 8. reST also rejects this -# and doesn't treat it as a literal block. -def rst_literal_skipped_first_line_not_indented_tab(): +# Checks that a simple doctest that is continued over multiple lines, +# but is invalid, gets skipped. +def doctest_skipped_simple_continued(): """ - Do cool stuff:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> def cool-stuff( x ): + ... print( f"hi {x}" ); + 2 """ pass -# Like the previous test, but adds a second line. -def rst_literal_skipped_first_line_not_indented_tab_multiple(): +# Checks that a doctest with improper indentation gets skipped. +def doctest_skipped_inconsistent_indent(): """ - Do cool stuff:: - - cool_stuff( 1 ) - cool_stuff( 2 ) + Do cool stuff. - Done. + >>> def cool_stuff( x ): + ... print( f"hi {x}" ); + hi 2 """ pass -# Tests that a code block with a second line that is not properly indented gets -# skipped. A valid code block needs to have an empty line separating these. -# -# One trick here is that we need to make sure the Python code in the snippet is -# valid, otherwise it would be skipped because of invalid Python. -def rst_literal_skipped_subsequent_line_not_indented(): +# Checks that a doctest with some proper indentation and some improper +# indentation is "partially" formatted. That is, the part that appears +# before the inconsistent indentation is formatted. This requires that +# the part before it is valid Python. +def doctest_skipped_partial_inconsistent_indent(): """ - Do cool stuff:: - - if True: - cool_stuff( ''' - hiya''' ) + Do cool stuff. - Done. + >>> def cool_stuff(x): + ... print(x) + ... print( f"hi {x}" ); + hi 2 """ pass -# In this test, we write what looks like a code-block, but it should be treated -# as invalid due to the missing `language` argument. -# -# It does still look like it could be a literal block according to the literal -# rules, but we currently consider the `.. ` prefix to indicate that it is not -# a literal block. -def rst_literal_skipped_not_directive(): +# Checks that a doctest with improper triple single quoted string gets +# skipped. That is, the code snippet is itself invalid Python, so it is +# left as is. +def doctest_skipped_triple_incorrect(): """ - .. code-block:: - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> foo( x ) + ... '''tri'''cksy''' """ pass -# In this test, we start a line with `.. `, which makes it look like it might -# be a directive. But instead continue it as if it was just some periods from -# the previous line, and then try to end it by starting a literal block. -# -# But because of the `.. ` in the beginning, we wind up not treating this as a -# code snippet. The reST render I was using to test things does actually treat -# this as a code block, so we may be out of conformance here. -def rst_literal_skipped_possible_false_negative(): - """ - This is a test. - .. This is a test:: - - cool_stuff( 1 ) - - Done. - """ +# Tests that a doctest on a single line is skipped. +def doctest_skipped_one_line(): + ">>> foo( x )" pass -# This tests that a doctest inside of a reST literal block doesn't get -# reformatted. It's plausible this isn't the right behavior, but it also seems -# like it might be the right behavior since it is a literal block. (The doctest -# makes the Python code invalid.) -def rst_literal_skipped_doctest(): - """ - Do cool stuff:: - - >>> cool_stuff( 1 ) +# f-strings are not considered docstrings[1], so any doctests +# inside of them should not be formatted. +# +# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +def doctest_skipped_fstring(): + f""" + Do cool stuff. - Done. - """ + >>> cool_stuff( 1 ) + 2 + """ pass -def rst_directive_skipped_not_indented(): +# Test that a doctest containing a triple quoted string at least +# does not result in invalid Python code. Ideally this would format +# correctly, but at time of writing it does not. +def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): """ - .. code-block:: python - - cool_stuff( 1 ) + Do cool stuff. - Done. + >>> x = '\"\"\"' """ pass -def rst_directive_skipped_wrong_language(): +############################################################################### +# reStructuredText CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# reStructuredText formatted code blocks. +# +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks +# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 +# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 +############################################################################### + + +def rst_literal_simple(): """ - .. code-block:: rust + Do cool stuff:: - cool_stuff( 1 ) + cool_stuff(1) Done. """ pass -# This gets skipped for the same reason that the doctest in a literal block -# gets skipped. -def rst_directive_skipped_doctest(): +def rst_literal_simple_continued(): """ - .. code-block:: python + Do cool stuff:: - >>> cool_stuff( 1 ) + def cool_stuff(x): + print(f"hi {x}") Done. """ pass -``` -### Output 8 -``` -indent-style = tab -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Enabled -preview = Disabled -``` - -```python -############################################################################### -# DOCTEST CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# Python's "doctest" format. -# -# See: https://docs.python.org/3/library/doctest.html -############################################################################### - -# The simplest doctest to ensure basic formatting works. -def doctest_simple(): +# Tests that we can end the literal block on the second +# to last line of the docstring. +def rst_literal_second_to_last(): """ - Do cool stuff. + Do cool stuff:: - >>> cool_stuff(1) - 2 + cool_stuff(1) """ pass -# Another simple test, but one where the Python code -# extends over multiple lines. -def doctest_simple_continued(): +# Tests that we can end the literal block on the actual +# last line of the docstring. +def rst_literal_actually_last(): """ - Do cool stuff. + Do cool stuff:: - >>> def cool_stuff(x): - ... print(f"hi {x}") - hi 2 - """ + cool_stuff(1)""" pass -# Test that we support multiple directly adjacent -# doctests. -def doctest_adjacent(): +def rst_literal_with_blank_lines(): """ - Do cool stuff. + Do cool stuff:: - >>> cool_stuff(x) - >>> cool_stuff(y) - 2 + def cool_stuff(x): + print(f"hi {x}") + + + def other_stuff(y): + print(y) + + Done. """ pass -# Test that a doctest on the last non-whitespace line of a docstring -# reformats correctly. -def doctest_last_line(): +# Extra blanks should be preserved. +def rst_literal_extra_blanks(): """ - Do cool stuff. + Do cool stuff:: - >>> cool_stuff(x) + + + cool_stuff(1) + + + + Done. """ pass -# Test that a doctest that continues to the last non-whitespace line of -# a docstring reformats correctly. -def doctest_last_line_continued(): +# If a literal block is never properly ended (via a non-empty unindented line), +# then the end of the block should be the last non-empty line. And subsequent +# empty lines should be preserved as-is. +def rst_literal_extra_blanks_at_end(): """ - Do cool stuff. + Do cool stuff:: + + + cool_stuff(1) + + - >>> def cool_stuff(x): - ... print(f"hi {x}") """ pass -# Test that a doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line(): +# A literal block can contain many empty lines and it should not end the block +# if it continues. +def rst_literal_extra_blanks_in_snippet(): """ - Do cool stuff. + Do cool stuff:: - >>> cool_stuff(x)""" - pass + cool_stuff(1) -# Test that a continued doctest on the real last line of a docstring reformats -# correctly. -def doctest_really_last_line_continued(): - """ - Do cool stuff. + cool_stuff(2) - >>> cool_stuff(x) - ... more(y)""" + Done. + """ pass -# Test that a doctest is correctly identified and formatted with a blank -# continuation line. -def doctest_blank_continued(): +# This tests that a unindented line appearing after an indented line (but where +# the indent is still beyond the minimum) gets formatted properly. +def rst_literal_subsequent_line_not_indented(): """ - Do cool stuff. + Do cool stuff:: - >>> def cool_stuff(x): - ... print(x) - ... - ... print(x) + if True: + cool_stuff( + ''' + hiya''' + ) + + Done. """ pass -# Tests that a blank PS2 line at the end of a doctest can get dropped. -# It is treated as part of the Python snippet which will trim the -# trailing whitespace. -def doctest_blank_end(): +# This checks that if the first line in a code snippet has been indented with +# tabs, then so long as its "indentation length" is considered bigger than the +# line with `::`, it is reformatted as code. +# +# (If your tabwidth is set to 4, then it looks like the code snippet +# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST +# itself also seems to recognize this as a code block, although it appears +# under-specified.) +def rst_literal_first_line_indent_uses_tabs_4spaces(): """ - Do cool stuff. + Do cool stuff:: - >>> def cool_stuff(x): - ... print(x) - ... print(x) + cool_stuff(1) + + Done. """ pass -# Tests that a blank PS2 line at the end of a doctest can get dropped -# even when there is text following it. -def doctest_blank_end_then_some_text(): +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): """ - Do cool stuff. + Do cool stuff:: - >>> def cool_stuff(x): - ... print(x) - ... print(x) + cool_stuff(1) + cool_stuff(2) - And say something else. + Done. """ pass -# Test that a doctest containing a triple quoted string gets formatted -# correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): +# Another test with tabs, except in this case, if your tabwidth is less than +# 8, than the code snippet actually looks like its indent is *less* than the +# opening line with a `::`. One might presume this means that the code snippet +# is not treated as a literal block and thus not reformatted, but since we +# assume all tabs have tabwidth=8 when computing indentation length, the code +# snippet is actually seen as being more indented than the opening `::` line. +# As with the above example, reST seems to behave the same way here. +def rst_literal_first_line_indent_uses_tabs_8spaces(): """ - Do cool stuff. + Do cool stuff:: - >>> x = '''tricksy''' + cool_stuff(1) + + Done. """ pass -# Test that a doctest containing a triple quoted f-string gets -# formatted correctly and doesn't result in invalid syntax. -def doctest_with_triple_single(): +# Like the test above, but with multiple lines. +def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): """ - Do cool stuff. + Do cool stuff:: - >>> x = f'''tricksy''' + cool_stuff(1) + cool_stuff(2) + + Done. """ pass -# Another nested multi-line string case, but with triple escaped double -# quotes inside a triple single quoted string. -def doctest_with_triple_escaped_double(): +# Tests that if two lines in a literal block are indented to the same level +# but by different means (tabs versus spaces), then we correctly recognize the +# block and format it. +def rst_literal_first_line_tab_second_line_spaces(): """ - Do cool stuff. + Do cool stuff:: - >>> x = '''\"\"\"''' + cool_stuff(1) + cool_stuff(2) + + Done. """ pass -# Tests that inverting the triple quoting works as expected. -def doctest_with_triple_inverted(): - ''' - Do cool stuff. +# Tests that when two lines in a code snippet have weird and inconsistent +# indentation, the code still gets formatted so long as the indent is greater +# than the indent of the `::` line. +# +# In this case, the minimum indent is 5 spaces (from the second line) where as +# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). +# The minimum indent is stripped from each code line. Since tabs aren't +# divisible, the entire tab is stripped, which means the first and second lines +# wind up with the same level of indentation. +# +# An alternative behavior here would be that the tab is replaced with 3 spaces +# instead of being stripped entirely. The code snippet itself would then have +# inconsistent indentation to the point of being invalid Python, and thus code +# formatting would be skipped. +# +# I decided on the former behavior because it seems a bit easier to implement, +# but we might want to switch to the alternative if cases like this show up in +# the real world. ---AG +def rst_literal_odd_indentation(): + """ + Do cool stuff:: - >>> x = """tricksy""" - ''' + cool_stuff(1) + cool_stuff(2) + + Done. + """ pass -# Tests that inverting the triple quoting with an f-string works as -# expected. -def doctest_with_triple_inverted_fstring(): - ''' +# Tests that having a line with a lone `::` works as an introduction of a +# literal block. +def rst_literal_lone_colon(): + """ Do cool stuff. - >>> x = f"""tricksy""" - ''' + :: + + cool_stuff(1) + + Done. + """ pass -# Tests nested doctests are ignored. That is, we don't format doctests -# recursively. We only recognize "top level" doctests. -# -# This restriction primarily exists to avoid needing to deal with -# nesting quotes. It also seems like a generally sensible restriction, -# although it could be lifted if necessary I believe. -def doctest_nested_doctest_not_formatted(): - ''' - Do cool stuff. +def rst_directive_simple(): + """ + .. code-block:: python - >>> def nested(x): - ... """ - ... Do nested cool stuff. - ... >>> func_call( 5 ) - ... """ - ... pass - ''' + cool_stuff(1) + + Done. + """ pass -# Tests that the starting column does not matter. -def doctest_varying_start_column(): +def rst_directive_case_insensitive(): """ - Do cool stuff. + .. cOdE-bLoCk:: python + + cool_stuff(1) - >>> assert "Easy!" - >>> import math - >>> math.floor(1.9) - 1 + Done. """ pass -# Tests that long lines get wrapped... appropriately. -# -# The docstring code formatter uses the same line width settings as for -# formatting other code. This means that a line in the docstring can -# actually extend past the configured line limit. -# -# It's not quite clear whether this is desirable or not. We could in -# theory compute the intendation length of a code snippet and then -# adjust the line-width setting on a recursive call to the formatter. -# But there are assuredly pathological cases to consider. Another path -# would be to expose another formatter option for controlling the -# line-width of code snippets independently. -def doctest_long_lines(): +def rst_directive_sourcecode(): """ - Do cool stuff. + .. sourcecode:: python - This won't get wrapped even though it exceeds our configured - line width because it doesn't exceed the line width within this - docstring. e.g, the `f` in `foo` is treated as the first column. - >>> foo, bar, quux = this_is_a_long_line(lion, giraffe, hippo, zeba, lemur, penguin, monkey) + cool_stuff(1) - But this one is long enough to get wrapped. - >>> foo, bar, quux = this_is_a_long_line( - ... lion, giraffe, hippo, zeba, lemur, penguin, monkey, spider, bear, leopard - ... ) + Done. """ - # This demostrates a normal line that will get wrapped but won't - # get wrapped in the docstring above because of how the line-width - # setting gets reset at the first column in each code snippet. - foo, bar, quux = this_is_a_long_line( - lion, giraffe, hippo, zeba, lemur, penguin, monkey - ) + pass -# Checks that a simple but invalid doctest gets skipped. -def doctest_skipped_simple(): +def rst_directive_options(): """ - Do cool stuff. + .. code-block:: python + :linenos: + :emphasize-lines: 2,3 + :name: blah blah - >>> cool-stuff( x ): - 2 + cool_stuff(1) + cool_stuff(2) + cool_stuff(3) + cool_stuff(4) + + Done. """ pass -# Checks that a simple doctest that is continued over multiple lines, -# but is invalid, gets skipped. -def doctest_skipped_simple_continued(): +# In this case, since `pycon` isn't recognized as a Python code snippet, the +# docstring reformatter ignores it. But it then picks up the doctest and +# reformats it. +def rst_directive_doctest(): """ - Do cool stuff. + .. code-block:: pycon - >>> def cool-stuff( x ): - ... print( f"hi {x}" ); - 2 + >>> cool_stuff(1) + + Done. """ pass -# Checks that a doctest with improper indentation gets skipped. -def doctest_skipped_inconsistent_indent(): +# This checks that if the first non-empty line after the start of a literal +# block is not indented more than the line containing the `::`, then it is not +# treated as a code snippet. +def rst_literal_skipped_first_line_not_indented(): """ - Do cool stuff. + Do cool stuff:: - >>> def cool_stuff( x ): - ... print( f"hi {x}" ); - hi 2 + cool_stuff( 1 ) + + Done. """ pass -# Checks that a doctest with some proper indentation and some improper -# indentation is "partially" formatted. That is, the part that appears -# before the inconsistent indentation is formatted. This requires that -# the part before it is valid Python. -def doctest_skipped_partial_inconsistent_indent(): +# Like the test above, but inserts an indented line after the un-indented one. +# This should not cause the literal block to be resumed. +def rst_literal_skipped_first_line_not_indented_then_indented(): """ - Do cool stuff. + Do cool stuff:: - >>> def cool_stuff(x): - ... print(x) - ... print( f"hi {x}" ); - hi 2 + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. """ pass -# Checks that a doctest with improper triple single quoted string gets -# skipped. That is, the code snippet is itself invalid Python, so it is -# left as is. -def doctest_skipped_triple_incorrect(): +# This also checks that a code snippet is not reformatted when the indentation +# of the first line is not more than the line with `::`, but this uses tabs to +# make it a little more confounding. It relies on the fact that indentation +# length is computed by assuming a tabwidth equal to 8. reST also rejects this +# and doesn't treat it as a literal block. +def rst_literal_skipped_first_line_not_indented_tab(): """ - Do cool stuff. + Do cool stuff:: - >>> foo( x ) - ... '''tri'''cksy''' + cool_stuff( 1 ) + + Done. """ pass -# Tests that a doctest on a single line is skipped. -def doctest_skipped_one_line(): - ">>> foo( x )" +# Like the previous test, but adds a second line. +def rst_literal_skipped_first_line_not_indented_tab_multiple(): + """ + Do cool stuff:: + + cool_stuff( 1 ) + cool_stuff( 2 ) + + Done. + """ pass -# f-strings are not considered docstrings[1], so any doctests -# inside of them should not be formatted. +# Tests that a code block with a second line that is not properly indented gets +# skipped. A valid code block needs to have an empty line separating these. # -# [1]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals -def doctest_skipped_fstring(): - f""" - Do cool stuff. +# One trick here is that we need to make sure the Python code in the snippet is +# valid, otherwise it would be skipped because of invalid Python. +def rst_literal_skipped_subsequent_line_not_indented(): + """ + Do cool stuff:: - >>> cool_stuff( 1 ) - 2 - """ + if True: + cool_stuff( ''' + hiya''' ) + + Done. + """ pass -# Test that a doctest containing a triple quoted string at least -# does not result in invalid Python code. Ideally this would format -# correctly, but at time of writing it does not. -def doctest_invalid_skipped_with_triple_double_in_single_quote_string(): +# In this test, we write what looks like a code-block, but it should be treated +# as invalid due to the missing `language` argument. +# +# It does still look like it could be a literal block according to the literal +# rules, but we currently consider the `.. ` prefix to indicate that it is not +# a literal block. +def rst_literal_skipped_not_directive(): """ - Do cool stuff. + .. code-block:: - >>> x = '\"\"\"' + cool_stuff( 1 ) + + Done. """ pass -############################################################################### -# reStructuredText CODE EXAMPLES -# -# This section shows examples of docstrings that contain code snippets in -# reStructuredText formatted code blocks. +# In this test, we start a line with `.. `, which makes it look like it might +# be a directive. But instead continue it as if it was just some periods from +# the previous line, and then try to end it by starting a literal block. # -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks -# See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-30 -# See: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#toc-entry-38 -############################################################################### - - -def rst_literal_simple(): +# But because of the `.. ` in the beginning, we wind up not treating this as a +# code snippet. The reST render I was using to test things does actually treat +# this as a code block, so we may be out of conformance here. +def rst_literal_skipped_possible_false_negative(): """ - Do cool stuff:: + This is a test. + .. This is a test:: - cool_stuff(1) + cool_stuff( 1 ) Done. """ pass -def rst_literal_simple_continued(): +# This tests that a doctest inside of a reST literal block doesn't get +# reformatted. It's plausible this isn't the right behavior, but it also seems +# like it might be the right behavior since it is a literal block. (The doctest +# makes the Python code invalid.) +def rst_literal_skipped_doctest(): """ Do cool stuff:: - def cool_stuff(x): - print(f"hi {x}") + >>> cool_stuff( 1 ) Done. """ pass -# Tests that we can end the literal block on the second -# to last line of the docstring. -def rst_literal_second_to_last(): +def rst_literal_skipped_markdown(): """ Do cool stuff:: - cool_stuff(1) + ```py + cool_stuff( 1 ) + ``` + + Done. """ pass -# Tests that we can end the literal block on the actual -# last line of the docstring. -def rst_literal_actually_last(): +def rst_directive_skipped_not_indented(): """ - Do cool stuff:: - - cool_stuff(1)""" - pass + .. code-block:: python + cool_stuff( 1 ) -def rst_literal_with_blank_lines(): + Done. """ - Do cool stuff:: + pass - def cool_stuff(x): - print(f"hi {x}") +def rst_directive_skipped_wrong_language(): + """ + .. code-block:: rust - def other_stuff(y): - print(y) + cool_stuff( 1 ) Done. """ pass -# Extra blanks should be preserved. -def rst_literal_extra_blanks(): +# This gets skipped for the same reason that the doctest in a literal block +# gets skipped. +def rst_directive_skipped_doctest(): """ - Do cool stuff:: + .. code-block:: python + + >>> cool_stuff( 1 ) + Done. + """ + pass - cool_stuff(1) +############################################################################### +# Markdown CODE EXAMPLES +# +# This section shows examples of docstrings that contain code snippets in +# Markdown fenced code blocks. +# +# See: https://spec.commonmark.org/0.30/#fenced-code-blocks +############################################################################### + +def markdown_simple(): + """ + Do cool stuff. + ```py + cool_stuff(1) + ``` Done. """ pass -# If a literal block is never properly ended (via a non-empty unindented line), -# then the end of the block should be the last non-empty line. And subsequent -# empty lines should be preserved as-is. -def rst_literal_extra_blanks_at_end(): +def markdown_simple_continued(): """ - Do cool stuff:: - + Do cool stuff. - cool_stuff(1) + ```python + def cool_stuff(x): + print(f"hi {x}") + ``` + Done. + """ + pass +# Tests that unlabeled Markdown fenced code blocks are assumed to be Python. +def markdown_unlabeled(): """ - pass + Do cool stuff. + ``` + cool_stuff(1) + ``` -# A literal block can contain many empty lines and it should not end the block -# if it continues. -def rst_literal_extra_blanks_in_snippet(): + Done. """ - Do cool stuff:: + pass - cool_stuff(1) +# Tests that fenced code blocks using tildes work. +def markdown_tildes(): + """ + Do cool stuff. - cool_stuff(2) + ~~~py + cool_stuff(1) + ~~~ Done. """ pass -# This tests that a unindented line appearing after an indented line (but where -# the indent is still beyond the minimum) gets formatted properly. -def rst_literal_subsequent_line_not_indented(): +# Tests that a longer closing fence is just fine and dandy. +def markdown_longer_closing_fence(): """ - Do cool stuff:: + Do cool stuff. - if True: - cool_stuff( - ''' - hiya''' - ) + ```py + cool_stuff(1) + `````` Done. """ pass -# This checks that if the first line in a code snippet has been indented with -# tabs, then so long as its "indentation length" is considered bigger than the -# line with `::`, it is reformatted as code. +# Tests that an invalid closing fence is treated as invalid. # -# (If your tabwidth is set to 4, then it looks like the code snippet -# isn't indented at all, which is perhaps counter-intuitive. Indeed, reST -# itself also seems to recognize this as a code block, although it appears -# under-specified.) -def rst_literal_first_line_indent_uses_tabs_4spaces(): +# We embed it into a docstring so that the surrounding Python +# remains valid. +def markdown_longer_closing_fence(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) + ```py + cool_stuff(1) + ''' + ```invalid + ''' + cool_stuff(2) + ``` Done. """ pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_4spaces_multiple(): +# Tests that one can nest fenced code blocks by using different numbers of +# backticks. +def markdown_nested_fences(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) - cool_stuff(2) + `````` + do_something( + ''' + ``` + did i trick you? + ``` + ''' + ) + `````` Done. """ pass -# Another test with tabs, except in this case, if your tabwidth is less than -# 8, than the code snippet actually looks like its indent is *less* than the -# opening line with a `::`. One might presume this means that the code snippet -# is not treated as a literal block and thus not reformatted, but since we -# assume all tabs have tabwidth=8 when computing indentation length, the code -# snippet is actually seen as being more indented than the opening `::` line. -# As with the above example, reST seems to behave the same way here. -def rst_literal_first_line_indent_uses_tabs_8spaces(): +# Tests that an unclosed block gobbles up everything remaining in the +# docstring. When it's only empty lines, those are passed into the formatter +# and thus stripped. +def markdown_unclosed_empty_lines(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) + ```py + cool_stuff(1)""" + pass - Done. + +# Tests that we can end the block on the second to last line of the +# docstring. +def markdown_second_to_last(): + """ + Do cool stuff. + + ```py + cool_stuff(1) + ``` """ pass -# Like the test above, but with multiple lines. -def rst_literal_first_line_indent_uses_tabs_8spaces_multiple(): +# Tests that an unclosed block with one extra line at the end is treated +# correctly. As per the CommonMark spec, an unclosed fenced code block contains +# everything following the opening fences. Since formatting the code snippet +# trims lines, the last empty line is removed here. +def markdown_second_to_last(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) - cool_stuff(2) + ```py + cool_stuff(1)""" + pass - Done. + +# Tests that we can end the block on the actual last line of the docstring. +def markdown_actually_last(): """ + Do cool stuff. + + ```py + cool_stuff(1) + ```""" pass -# Tests that if two lines in a literal block are indented to the same level -# but by different means (tabs versus spaces), then we correctly recognize the -# block and format it. -def rst_literal_first_line_tab_second_line_spaces(): +# Tests that an unclosed block that ends on the last line of a docstring +# is handled correctly. +def markdown_unclosed_actually_last(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) - cool_stuff(2) + ```py + cool_stuff(1)""" + pass + + +def markdown_with_blank_lines(): + """ + Do cool stuff. + + ```py + def cool_stuff(x): + print(f"hi {x}") + + + def other_stuff(y): + print(y) + ``` Done. """ pass -# Tests that when two lines in a code snippet have weird and inconsistent -# indentation, the code still gets formatted so long as the indent is greater -# than the indent of the `::` line. -# -# In this case, the minimum indent is 5 spaces (from the second line) where as -# the first line has an indent of 8 spaces via a tab (by assuming tabwidth=8). -# The minimum indent is stripped from each code line. Since tabs aren't -# divisible, the entire tab is stripped, which means the first and second lines -# wind up with the same level of indentation. -# -# An alternative behavior here would be that the tab is replaced with 3 spaces -# instead of being stripped entirely. The code snippet itself would then have -# inconsistent indentation to the point of being invalid Python, and thus code -# formatting would be skipped. -# -# I decided on the former behavior because it seems a bit easier to implement, -# but we might want to switch to the alternative if cases like this show up in -# the real world. ---AG -def rst_literal_odd_indentation(): +def markdown_first_line_indent_uses_tabs_4spaces(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff(1) - cool_stuff(2) + ```py + cool_stuff(1) + ``` Done. """ pass -# Tests that having a line with a lone `::` works as an introduction of a -# literal block. -def rst_literal_lone_colon(): +def markdown_first_line_indent_uses_tabs_4spaces_multiple(): """ Do cool stuff. - :: - - cool_stuff(1) + ```py + cool_stuff(1) + cool_stuff(2) + ``` Done. """ pass -def rst_directive_simple(): +def markdown_first_line_indent_uses_tabs_8spaces(): """ - .. code-block:: python + Do cool stuff. - cool_stuff(1) + ```py + cool_stuff(1) + ``` Done. """ pass -def rst_directive_case_insensitive(): +def markdown_first_line_indent_uses_tabs_8spaces_multiple(): """ - .. cOdE-bLoCk:: python + Do cool stuff. - cool_stuff(1) + ```py + cool_stuff(1) + cool_stuff(2) + ``` Done. """ pass -def rst_directive_sourcecode(): +def markdown_first_line_tab_second_line_spaces(): """ - .. sourcecode:: python + Do cool stuff. + ```py cool_stuff(1) + cool_stuff(2) + ``` Done. """ pass -def rst_directive_options(): +def markdown_odd_indentation(): """ - .. code-block:: python - :linenos: - :emphasize-lines: 2,3 - :name: blah blah + Do cool stuff. + ```py cool_stuff(1) cool_stuff(2) - cool_stuff(3) - cool_stuff(4) + ``` Done. """ pass -# In this case, since `pycon` isn't recognized as a Python code snippet, the -# docstring reformatter ignores it. But it then picks up the doctest and -# reformats it. -def rst_directive_doctest(): +# Extra blanks should be *not* be preserved (unlike reST) because they are part +# of the code snippet (per CommonMark spec), and thus get trimmed as part of +# code formatting. +def markdown_extra_blanks(): """ - .. code-block:: pycon + Do cool stuff. - >>> cool_stuff(1) + ```py + cool_stuff(1) + ``` Done. """ pass -# This checks that if the first non-empty line after the start of a literal -# block is not indented more than the line containing the `::`, then it is not -# treated as a code snippet. -def rst_literal_skipped_first_line_not_indented(): +# A block can contain many empty lines within it. +def markdown_extra_blanks_in_snippet(): """ - Do cool stuff:: + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff(1) + + + cool_stuff(2) + ``` Done. """ pass -# Like the test above, but inserts an indented line after the un-indented one. -# This should not cause the literal block to be resumed. -def rst_literal_skipped_first_line_not_indented_then_indented(): +def markdown_weird_closing(): """ - Do cool stuff:: + Code block with weirdly placed closing fences. - cool_stuff( 1 ) - cool_stuff( 2 ) + ```python + cool_stuff(1) + ``` + # The above fences look like it shouldn't close the block, but we + # allow it to. The fences below re-open a block (until the end of + # the docstring), but it's invalid Python and thus doesn't get + # reformatted. + a = 10 + ``` - Done. + Now the code block is closed """ pass -# This also checks that a code snippet is not reformatted when the indentation -# of the first line is not more than the line with `::`, but this uses tabs to -# make it a little more confounding. It relies on the fact that indentation -# length is computed by assuming a tabwidth equal to 8. reST also rejects this -# and doesn't treat it as a literal block. -def rst_literal_skipped_first_line_not_indented_tab(): +def markdown_over_indented(): """ - Do cool stuff:: - - cool_stuff( 1 ) - - Done. + A docstring + over intended + ```python + print(5) + ``` """ pass -# Like the previous test, but adds a second line. -def rst_literal_skipped_first_line_not_indented_tab_multiple(): +# Tests that an unclosed block gobbles up everything remaining in the +# docstring, even if it isn't valid Python. Since it isn't valid Python, +# reformatting fails and the entire thing is skipped. +def markdown_skipped_unclosed_non_python(): """ - Do cool stuff:: + Do cool stuff. + ```py cool_stuff( 1 ) - cool_stuff( 2 ) - Done. + I forgot to close the code block, and this is definitely not + Python. So nothing here gets formatted. """ pass -# Tests that a code block with a second line that is not properly indented gets -# skipped. A valid code block needs to have an empty line separating these. -# -# One trick here is that we need to make sure the Python code in the snippet is -# valid, otherwise it would be skipped because of invalid Python. -def rst_literal_skipped_subsequent_line_not_indented(): +# This has a Python snippet with a docstring that contains a closing fence. +# This splits the embedded docstring and makes the overall snippet invalid. +def markdown_skipped_accidental_closure(): """ - Do cool stuff:: + Do cool stuff. - if True: - cool_stuff( ''' - hiya''' ) + ```py + cool_stuff( 1 ) + ''' + ``` + ''' + ``` Done. """ pass -# In this test, we write what looks like a code-block, but it should be treated -# as invalid due to the missing `language` argument. +# When a line is unindented all the way out before the standard indent of the +# docstring, the code reformatting ends up interacting poorly with the standard +# docstring whitespace normalization logic. This is probably a bug, and we +# should probably treat the Markdown block as valid, but for now, we detect +# the unindented line and declare the block as invalid and thus do no code +# reformatting. # -# It does still look like it could be a literal block according to the literal -# rules, but we currently consider the `.. ` prefix to indicate that it is not -# a literal block. -def rst_literal_skipped_not_directive(): +# FIXME: Fixing this (if we think it's a bug) probably requires refactoring the +# docstring whitespace normalization to be aware of code snippets. Or perhaps +# plausibly, to do normalization *after* code snippets have been formatted. +def markdown_skipped_unindented_completely(): """ - .. code-block:: + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + ``` - Done. + Done. """ pass -# In this test, we start a line with `.. `, which makes it look like it might -# be a directive. But instead continue it as if it was just some periods from -# the previous line, and then try to end it by starting a literal block. -# -# But because of the `.. ` in the beginning, we wind up not treating this as a -# code snippet. The reST render I was using to test things does actually treat -# this as a code block, so we may be out of conformance here. -def rst_literal_skipped_possible_false_negative(): +# This test is fallout from treating fenced code blocks with unindented lines +# as invalid. We probably should treat this as a valid block. Indeed, if we +# remove the logic that makes the `markdown_skipped_unindented_completely` test +# pass, then this code snippet will get reformatted correctly. +def markdown_skipped_unindented_somewhat(): """ - This is a test. - .. This is a test:: + Do cool stuff. - cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + ``` Done. """ pass -# This tests that a doctest inside of a reST literal block doesn't get -# reformatted. It's plausible this isn't the right behavior, but it also seems -# like it might be the right behavior since it is a literal block. (The doctest -# makes the Python code invalid.) -def rst_literal_skipped_doctest(): +# This tests that if a Markdown block contains a line that has less of an +# indent than another line. +# +# There is some judgment involved in what the right behavior is here. We +# could "normalize" the indentation so that the minimum is the indent of the +# opening fence line. If we did that here, then the code snippet would become +# valid and format as Python. But at time of writing, we don't, which leads to +# inconsistent indentation and thus invalid Python. +def markdown_skipped_unindented_with_inconsistent_indentation(): """ - Do cool stuff:: + Do cool stuff. - >>> cool_stuff( 1 ) + ```py + cool_stuff( 1 ) + cool_stuff( 2 ) + ``` Done. """ pass -def rst_directive_skipped_not_indented(): +def markdown_skipped_doctest(): """ - .. code-block:: python + Do cool stuff. - cool_stuff( 1 ) + ```py + >>> cool_stuff( 1 ) + ``` Done. """ pass -def rst_directive_skipped_wrong_language(): +def markdown_skipped_rst_literal(): """ - .. code-block:: rust + Do cool stuff. + + ```py + And do this:: cool_stuff( 1 ) + ``` + Done. """ pass -# This gets skipped for the same reason that the doctest in a literal block -# gets skipped. -def rst_directive_skipped_doctest(): +def markdown_skipped_rst_directive(): """ + Do cool stuff. + + ```py .. code-block:: python - >>> cool_stuff( 1 ) + cool_stuff( 1 ) + + ``` Done. """