mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
PY-29717 Remove excess trailing spaces and line breaks from plain text docstrings
This commit is contained in:
@@ -15,21 +15,16 @@
|
||||
*/
|
||||
package com.jetbrains.python.documentation;
|
||||
|
||||
import com.intellij.application.options.CodeStyle;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.documentation.DocumentationMarkup;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.LineTokenizer;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import com.jetbrains.python.*;
|
||||
@@ -42,25 +37,23 @@ import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
|
||||
import com.jetbrains.python.psi.resolve.RootVisitor;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.pyi.PyiUtil;
|
||||
import com.jetbrains.python.toolbox.ChainIterable;
|
||||
import com.jetbrains.python.toolbox.Maybe;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.jetbrains.python.documentation.DocumentationBuilderKit.*;
|
||||
@@ -327,11 +320,10 @@ public class PyDocumentationBuilder {
|
||||
if (!isProperty) {
|
||||
pyClass = pyFunction.getContainingClass();
|
||||
if (pyClass != null) {
|
||||
myBody
|
||||
.addWith(TagSmall,
|
||||
PythonDocumentationProvider.describeClass(pyClass, Function.identity(), TO_ONE_LINE_AND_ESCAPE, true, true, myContext))
|
||||
.addItem(BR)
|
||||
.addItem(BR);
|
||||
final String link = getLinkToClass(pyClass);
|
||||
if (link != null) {
|
||||
myProlog.addItem(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
myBody.add(PythonDocumentationProvider.describeDecorators(pyFunction, WRAP_IN_ITALIC, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, BR, BR));
|
||||
@@ -430,14 +422,9 @@ public class PyDocumentationBuilder {
|
||||
if (pyClass == ancestor) {
|
||||
ancestorLink = PyDocumentationLink.toContainingClass(ancestorName);
|
||||
}
|
||||
else if (ancestorQualifiedName != null && ancestorName != null) {
|
||||
ancestorLink = PyDocumentationLink.toPossibleClass(ancestorName, ancestorQualifiedName, pyClass, myContext);
|
||||
}
|
||||
else {
|
||||
// TODO add a way to reference other local classes
|
||||
ancestorLink = ancestorName;
|
||||
ancestorLink = getLinkToClass(ancestor);
|
||||
}
|
||||
|
||||
}
|
||||
if (ancestorLink != null) {
|
||||
mySectionsMap.get(PyBundle.message("QDOC.documentation.is.copied.from")).addWith(TagCode, $(ancestorLink));
|
||||
@@ -474,21 +461,21 @@ public class PyDocumentationBuilder {
|
||||
|
||||
@NotNull
|
||||
private static ChainIterable<String> formatDocString(@NotNull PsiElement element, @NotNull String docstring) {
|
||||
final Project project = element.getProject();
|
||||
|
||||
final List<String> formatted = PyStructuredDocstringFormatter.formatDocstring(element, docstring);
|
||||
if (formatted != null) {
|
||||
return new ChainIterable<>(formatted);
|
||||
}
|
||||
|
||||
boolean isFirstLine;
|
||||
final String[] lines = removeCommonIndentation(docstring);
|
||||
|
||||
final List<String> origLines = LineTokenizer.tokenizeIntoList(docstring.trim(), false, false);
|
||||
final List<String> updatedLines = StreamEx.of(PyIndentUtil.removeCommonIndent(origLines, true))
|
||||
.takeWhile(line -> !line.startsWith(PyConsoleUtil.ORDINARY_PROMPT))
|
||||
.toList();
|
||||
final ChainIterable<String> result = new ChainIterable<>();
|
||||
// reconstruct back, dropping first empty fragment as needed
|
||||
isFirstLine = true;
|
||||
final int tabSize = CodeStyleSettingsManager.getSettings(project).getTabSize(PythonFileType.INSTANCE);
|
||||
for (String line : lines) {
|
||||
boolean isFirstLine = true;
|
||||
final int tabSize = CodeStyle.getIndentOptions(element.getContainingFile()).TAB_SIZE;
|
||||
for (String line : updatedLines) {
|
||||
if (isFirstLine && ourSpacesPattern.matcher(line).matches()) continue; // ignore all initial whitespace
|
||||
if (isFirstLine) {
|
||||
isFirstLine = false;
|
||||
@@ -508,44 +495,6 @@ public class PyDocumentationBuilder {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String[] removeCommonIndentation(@NotNull final String docstring) {
|
||||
// detect common indentation
|
||||
final String[] lines = LineTokenizer.tokenize(docstring, false);
|
||||
boolean isFirst = true;
|
||||
int cutWidth = Integer.MAX_VALUE;
|
||||
int firstIndentedLine = 0;
|
||||
for (String frag : lines) {
|
||||
if (frag.length() == 0) continue;
|
||||
int padWidth = 0;
|
||||
final Matcher matcher = ourSpacesPattern.matcher(frag);
|
||||
if (matcher.find()) {
|
||||
padWidth = matcher.end();
|
||||
}
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
if (padWidth == 0) { // first line may have zero padding
|
||||
firstIndentedLine = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (padWidth < cutWidth) cutWidth = padWidth;
|
||||
}
|
||||
// remove common indentation
|
||||
if (cutWidth > 0 && cutWidth < Integer.MAX_VALUE) {
|
||||
for (int i = firstIndentedLine; i < lines.length; i += 1) {
|
||||
if (lines[i].length() >= cutWidth) {
|
||||
lines[i] = lines[i].substring(cutWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
final List<String> result = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.startsWith(PyConsoleUtil.ORDINARY_PROMPT)) break;
|
||||
result.add(line);
|
||||
}
|
||||
return ArrayUtil.toStringArray(result);
|
||||
}
|
||||
|
||||
private void addModulePath(@NotNull PyFile followed) {
|
||||
// what to prepend to a module description?
|
||||
final VirtualFile file = followed.getVirtualFile();
|
||||
@@ -565,6 +514,15 @@ public class PyDocumentationBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getLinkToClass(@NotNull PyClass pyClass) {
|
||||
final String qualifiedName = pyClass.getQualifiedName();
|
||||
if (qualifiedName != null && pyClass.getName() != null) {
|
||||
return PyDocumentationLink.toPossibleClass(pyClass.getName(), qualifiedName, pyClass, myContext);
|
||||
}
|
||||
return pyClass.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static PyStringLiteralExpression getEffectiveDocStringExpression(@NotNull PyDocStringOwner owner) {
|
||||
final PyStringLiteralExpression expression = owner.getDocStringExpression();
|
||||
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre>def <b>len</b>(o: Sized) -> <a href="psi_element://#typename#int">int</a></pre></div><div class='content'><br>len(object) -> integer<br><br>Return the number of items of a sequence or collection.<br></div></body></html>
|
||||
<html><body><div class='definition'><pre>def <b>len</b>(o: Sized) -> <a href="psi_element://#typename#int">int</a></pre></div><div class='content'>len(object) -> integer<br><br>Return the number of items of a sequence or collection.</div></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre>Class attribute <b><code>the_attr</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br>the_attr: <a href="psi_element://#typename#str">str</a></pre></div><div class='content'>The documentation for the attribute. </div></body></html>
|
||||
<html><body><div class='definition'><pre>Class attribute <b><code>the_attr</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br>the_attr: <a href="psi_element://#typename#str">str</a> = ""</pre></div><div class='content'>The documentation for the attribute.</div></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre>Instance attribute <b><code>foo</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br>foo: <a href="psi_element://#typename#str">str</a></pre></div><div class='content'>The docstring for the attribute foo. </div></body></html>
|
||||
<html><body><div class='definition'><pre>Instance attribute <b><code>foo</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br>foo: <a href="psi_element://#typename#str">str</a> = "Foo"</pre></div><div class='content'>The docstring for the attribute foo.</div></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre><small>class <a href="psi_element://#class#">Foo</a></small><br><br>@<i>deco</i><br>def <b>meth</b>(self: <a href="psi_element://#typename#Foo">Foo</a>) -> Optional[Any]</pre></div><div class='content'><br>Doc of meth.<br></div></body></html>
|
||||
<html><body><div class='definition'><pre><small>class <a href="psi_element://#class#">Foo</a></small><br><br>@<i>deco</i><br>def <b>meth</b>(self: <a href="psi_element://#typename#Foo">Foo</a>) -> Optional[Any]</pre></div><div class='content'>Doc of meth.</div></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre>Module <b>Module</b></pre></div><div class='content'><br>Module's doc.</div></body></html>
|
||||
<html><body><div class='definition'><pre>Module <b>Module</b></pre></div><div class='content'>Module's doc.</div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><div class='definition'><pre>def <b>func</b>() -> None</pre></div><div class='content'>Docstring.</div></body></html>
|
||||
@@ -0,0 +1,8 @@
|
||||
def fu<ref1>nc():
|
||||
"""Docstring."""
|
||||
|
||||
|
||||
def fu<ref2>nc():
|
||||
"""
|
||||
Docstring.
|
||||
"""
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre>property <b><code>m</code></b> of class <a href="psi_element://#class#">C</a><br>@<i>m.setter</i><br>def <b>m</b>(self: <a href="psi_element://#typename#C">C</a>, x: Any) -> None</pre></div><div class='content'><br>Foo<br></div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'>property getter</td><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
|
||||
<html><body><div class='definition'><pre>property <b><code>m</code></b> of class <a href="psi_element://#class#">C</a><br>@<i>m.setter</i><br>def <b>m</b>(self: <a href="psi_element://#typename#C">C</a>, x: Any) -> None</pre></div><div class='content'>Foo</div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'>property getter</td><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre><small>class <a href="psi_element://#class#">list</a>(<a href="psi_element://#typename#typing.MutableSequence">MutableSequence</a>[_T], Generic[_T])</small><br><br>def <b>count</b>(self: <a href="psi_element://#typename#list">list</a>, object: _T) -> <a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value) -> integer -- return number of occurrences of value </div><table class='sections'><tr><td valign='top' class='section'><p>Assigned to:</td><td valign='top'><code>c1</code></td></table></body></html>
|
||||
<html><body><div class='definition'><pre><small>class <a href="psi_element://#class#">list</a>(<a href="psi_element://#typename#typing.MutableSequence">MutableSequence</a>[_T], Generic[_T])</small><br><br>def <b>count</b>(self: <a href="psi_element://#typename#list">list</a>, object: _T) -> <a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value) -> integer -- return number of occurrences of value</div><table class='sections'><tr><td valign='top' class='section'><p>Assigned to:</td><td valign='top'><code>c1</code></td></table></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre>y: <a href="psi_element://#typename#int">int</a></pre></div></body></html>
|
||||
<html><body><div class='definition'><pre>y: <a href="psi_element://#typename#int">int</a> = 1</pre></div></body></html>
|
||||
@@ -389,4 +389,20 @@ public class PyQuickDocTest extends LightMarkedTestCase {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void testPlainTextDocstringsQuotesPlacementDoesntAffectFormatting() {
|
||||
runWithDocStringFormat(DocStringFormat.PLAIN, () -> {
|
||||
final Map<String, PsiElement> map = loadTest();
|
||||
final PsiElement inline = map.get("<ref1>");
|
||||
final String inlineDoc = myProvider.generateDoc(assertInstanceOf(inline.getParent(), PyFunction.class), inline);
|
||||
|
||||
final PsiElement framed = map.get("<ref2>");
|
||||
final String framedDoc = myProvider.generateDoc(assertInstanceOf(framed.getParent(), PyFunction.class), framed);
|
||||
|
||||
assertEquals(inlineDoc, framedDoc);
|
||||
checkByHTML(inlineDoc);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user