From 6f524189e25cde9aa72fb23ef00504116a970d2c Mon Sep 17 00:00:00 2001 From: UNV Date: Fri, 11 Apr 2025 09:09:46 +0300 Subject: [PATCH 1/2] Reformatting of enter handlers and configuration producers. --- .../impl/editor/PyEnterAtIndentHandler.java | 44 +- .../editor/PyEnterBetweenBracketsHandler.java | 33 +- .../impl/editor/PythonEnterHandler.java | 756 +++++++++--------- .../run/PythonRunConfigurationProducer.java | 169 ++-- .../PythonTestConfigurationProducer.java | 524 ++++++------ .../PythonAtTestConfigurationProducer.java | 157 ++-- .../PythonDocTestConfigurationProducer.java | 134 ++-- .../pytest/PyTestConfigurationProducer.java | 266 +++--- .../PythonUnitTestConfigurationProducer.java | 90 +-- 9 files changed, 1065 insertions(+), 1108 deletions(-) diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java index 04326b9f..480a4a89 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java @@ -35,27 +35,29 @@ */ @ExtensionImpl public class PyEnterAtIndentHandler extends EnterHandlerDelegateAdapter { - @Override - public Result preprocessEnter(@Nonnull PsiFile file, - @Nonnull Editor editor, - @Nonnull Ref caretOffset, - @Nonnull Ref caretAdvance, - @Nonnull DataContext dataContext, - EditorActionHandler originalHandler) { - int offset = caretOffset.get(); - if (editor instanceof EditorWindow) { - file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); - editor = EditorWindow.getTopLevelEditor(editor); - offset = editor.getCaretModel().getOffset(); - } - if (!(file instanceof PyFile)) { - return Result.Continue; - } + @Override + public Result preprocessEnter( + @Nonnull PsiFile file, + @Nonnull Editor editor, + @Nonnull Ref caretOffset, + @Nonnull Ref caretAdvance, + @Nonnull DataContext dataContext, + EditorActionHandler originalHandler + ) { + int offset = caretOffset.get(); + if (editor instanceof EditorWindow) { + file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); + editor = EditorWindow.getTopLevelEditor(editor); + offset = editor.getCaretModel().getOffset(); + } + if (!(file instanceof PyFile)) { + return Result.Continue; + } - // honor dedent (PY-3009) - if (EditorBackspaceUtil.isWhitespaceBeforeCaret(editor)) { - return Result.DefaultSkipIndent; + // honor dedent (PY-3009) + if (EditorBackspaceUtil.isWhitespaceBeforeCaret(editor)) { + return Result.DefaultSkipIndent; + } + return Result.Continue; } - return Result.Continue; - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java index a45406b2..3914675f 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.editor; import consulo.annotation.component.ExtensionImpl; @@ -31,21 +30,23 @@ */ @ExtensionImpl public class PyEnterBetweenBracketsHandler extends EnterBetweenBracesHandler { - @Override - public Result preprocessEnter(@Nonnull PsiFile file, - @Nonnull Editor editor, - @Nonnull Ref caretOffsetRef, - @Nonnull Ref caretAdvance, - @Nonnull DataContext dataContext, - EditorActionHandler originalHandler) { - if (!file.getLanguage().is(PythonLanguage.getInstance())) { - return Result.Continue; + @Override + public Result preprocessEnter( + @Nonnull PsiFile file, + @Nonnull Editor editor, + @Nonnull Ref caretOffsetRef, + @Nonnull Ref caretAdvance, + @Nonnull DataContext dataContext, + EditorActionHandler originalHandler + ) { + if (!file.getLanguage().is(PythonLanguage.getInstance())) { + return Result.Continue; + } + return super.preprocessEnter(file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler); } - return super.preprocessEnter(file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler); - } - @Override - protected boolean isBracePair(char c1, char c2) { - return c1 == '[' && c2 == ']'; - } + @Override + protected boolean isBracePair(char c1, char c2) { + return c1 == '[' && c2 == ']'; + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java index 93df5d24..b576fa54 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java @@ -42,6 +42,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.regex.Matcher; /** @@ -49,411 +50,418 @@ */ @ExtensionImpl public class PythonEnterHandler extends EnterHandlerDelegateAdapter { - private int myPostprocessShift = 0; - - public static final Class[] IMPLICIT_WRAP_CLASSES = new Class[]{ - PyListLiteralExpression.class, - PySetLiteralExpression.class, - PyDictLiteralExpression.class, - PyDictLiteralExpression.class, - PyParenthesizedExpression.class, - PyArgumentList.class, - PyParameterList.class - }; - - private static final Class[] WRAPPABLE_CLASSES = new Class[]{ - PsiComment.class, - PyParenthesizedExpression.class, - PyListCompExpression.class, - PyDictCompExpression.class, - PySetCompExpression.class, - PyDictLiteralExpression.class, - PySetLiteralExpression.class, - PyListLiteralExpression.class, - PyArgumentList.class, - PyParameterList.class, - PyDecoratorList.class, - PySliceExpression.class, - PySubscriptionExpression.class, - PyGeneratorExpression.class - }; - - @Override - public Result preprocessEnter(@Nonnull PsiFile file, - @Nonnull Editor editor, - @Nonnull Ref caretOffset, - @Nonnull Ref caretAdvance, - @Nonnull DataContext dataContext, - EditorActionHandler originalHandler) { - int offset = caretOffset.get(); - if (editor instanceof EditorWindow) { - file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); - editor = EditorWindow.getTopLevelEditor(editor); - offset = editor.getCaretModel().getOffset(); - } - if (!(file instanceof PyFile)) { - return Result.Continue; - } - final Boolean isSplitLine = DataManager.getInstance() - .loadFromDataContext(dataContext, - consulo.ide.impl.idea.openapi.editor.actions.SplitLineAction.SPLIT_LINE_KEY); - if (isSplitLine != null) { - return Result.Continue; - } - final Document doc = editor.getDocument(); - PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc); - final PsiElement element = file.findElementAt(offset); - CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); - if (codeInsightSettings.JAVADOC_STUB_ON_ENTER) { - PsiElement comment = element; - if (comment == null && offset != 0) { - comment = file.findElementAt(offset - 1); - } - int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' - if (comment != null) { - final DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); - if (state != DocstringState.NONE) { - insertDocStringStub(editor, comment, state); - return Result.Continue; - } - } - } + private int myPostprocessShift = 0; + + public static final Class[] IMPLICIT_WRAP_CLASSES = new Class[]{ + PyListLiteralExpression.class, + PySetLiteralExpression.class, + PyDictLiteralExpression.class, + PyDictLiteralExpression.class, + PyParenthesizedExpression.class, + PyArgumentList.class, + PyParameterList.class + }; + + private static final Class[] WRAPPABLE_CLASSES = new Class[]{ + PsiComment.class, + PyParenthesizedExpression.class, + PyListCompExpression.class, + PyDictCompExpression.class, + PySetCompExpression.class, + PyDictLiteralExpression.class, + PySetLiteralExpression.class, + PyListLiteralExpression.class, + PyArgumentList.class, + PyParameterList.class, + PyDecoratorList.class, + PySliceExpression.class, + PySubscriptionExpression.class, + PyGeneratorExpression.class + }; + + @Override + public Result preprocessEnter( + @Nonnull PsiFile file, + @Nonnull Editor editor, + @Nonnull Ref caretOffset, + @Nonnull Ref caretAdvance, + @Nonnull DataContext dataContext, + EditorActionHandler originalHandler + ) { + int offset = caretOffset.get(); + if (editor instanceof EditorWindow) { + file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); + editor = EditorWindow.getTopLevelEditor(editor); + offset = editor.getCaretModel().getOffset(); + } + if (!(file instanceof PyFile)) { + return Result.Continue; + } + final Boolean isSplitLine = DataManager.getInstance().loadFromDataContext( + dataContext, + consulo.ide.impl.idea.openapi.editor.actions.SplitLineAction.SPLIT_LINE_KEY + ); + if (isSplitLine != null) { + return Result.Continue; + } + final Document doc = editor.getDocument(); + PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc); + final PsiElement element = file.findElementAt(offset); + CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); + if (codeInsightSettings.JAVADOC_STUB_ON_ENTER) { + PsiElement comment = element; + if (comment == null && offset != 0) { + comment = file.findElementAt(offset - 1); + } + int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' + if (comment != null) { + final DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); + if (state != DocstringState.NONE) { + insertDocStringStub(editor, comment, state); + return Result.Continue; + } + } + } - if (element == null) { - return Result.Continue; - } + if (element == null) { + return Result.Continue; + } - PsiElement elementParent = element.getParent(); - if (element.getNode().getElementType() == PyTokenTypes.LPAR) { - elementParent = elementParent.getParent(); - } - if (elementParent instanceof PyParenthesizedExpression || elementParent instanceof PyGeneratorExpression) { - return Result.Continue; + PsiElement elementParent = element.getParent(); + if (element.getNode().getElementType() == PyTokenTypes.LPAR) { + elementParent = elementParent.getParent(); + } + if (elementParent instanceof PyParenthesizedExpression || elementParent instanceof PyGeneratorExpression) { + return Result.Continue; + } + + if (offset > 0 && !(PyTokenTypes.STRING_NODES.contains(element.getNode().getElementType()))) { + final PsiElement prevElement = file.findElementAt(offset - 1); + if (prevElement == element) { + return Result.Continue; + } + } + + if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) || element.getNode() + .getElementType() == PyTokenTypes.DOCSTRING) { + return Result.Continue; + } + + final PsiElement prevElement = file.findElementAt(offset - 1); + PyStringLiteralExpression string = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false); + + if (string != null && prevElement != null + && PyTokenTypes.STRING_NODES.contains(prevElement.getNode().getElementType()) + && string.getTextOffset() < offset && !(element.getNode() instanceof PsiWhiteSpace)) { + final String stringText = element.getText(); + final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); + if (string.getTextOffset() + prefixLength >= offset) { + return Result.Continue; + } + final String pref = element.getText().substring(0, prefixLength); + final String quote = element.getText().substring(prefixLength, prefixLength + 1); + final boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.create(offset - 1, offset))); + final boolean isEscapedQuote = quote.equals(doc.getText(TextRange.create(offset, offset + 1))) && nextIsBackslash; + final boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.create(offset - 2, offset - 1))) && nextIsBackslash; + if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) { + return Result.Continue; + } + + final StringBuilder replacementString = new StringBuilder(); + myPostprocessShift = prefixLength + quote.length(); + + if (PsiTreeUtil.getParentOfType(string, IMPLICIT_WRAP_CLASSES) != null) { + replacementString.append(quote).append(pref).append(quote); + doc.insertString(offset, replacementString); + caretOffset.set(caretOffset.get() + 1); + return Result.Continue; + } + else { + if (isEscapedQuote) { + replacementString.append(quote); + caretOffset.set(caretOffset.get() + 1); + } + replacementString.append(quote).append(" \\").append(pref); + if (!isEscapedQuote) { + replacementString.append(quote); + } + doc.insertString(offset, replacementString.toString()); + caretOffset.set(caretOffset.get() + 3); + return Result.Continue; + } + } + + if (!PyCodeInsightSettings.getInstance().INSERT_BACKSLASH_ON_WRAP) { + return Result.Continue; + } + return checkInsertBackslash(file, caretOffset, dataContext, offset, doc); } - if (offset > 0 && !(PyTokenTypes.STRING_NODES.contains(element.getNode().getElementType()))) { - final PsiElement prevElement = file.findElementAt(offset - 1); - if (prevElement == element) { + private static Result checkInsertBackslash(PsiFile file, Ref caretOffset, DataContext dataContext, int offset, Document doc) { + boolean autoWrapInProgress = DataManager.getInstance().loadFromDataContext( + dataContext, + consulo.ide.impl.idea.codeInsight.editorActions.AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY + ) != null; + if (needInsertBackslash(file, offset, autoWrapInProgress)) { + doc.insertString(offset, "\\"); + caretOffset.set(caretOffset.get() + 1); + } return Result.Continue; - } } - if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) || element.getNode() - .getElementType() == PyTokenTypes.DOCSTRING) { - return Result.Continue; + public static boolean needInsertBackslash(PsiFile file, int offset, boolean autoWrapInProgress) { + if (offset > 0) { + final PsiElement beforeCaret = file.findElementAt(offset - 1); + if (beforeCaret instanceof PsiWhiteSpace && beforeCaret.getText().indexOf('\\') >= 0) { + // we've got a backslash at EOL already, don't need another one + return false; + } + } + PsiElement atCaret = file.findElementAt(offset); + if (atCaret == null) { + return false; + } + ASTNode nodeAtCaret = atCaret.getNode(); + return needInsertBackslash(nodeAtCaret, autoWrapInProgress); } - final PsiElement prevElement = file.findElementAt(offset - 1); - PyStringLiteralExpression string = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false); + public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapInProgress) { + PsiElement statementBefore = findStatementBeforeCaret(nodeAtCaret); + PsiElement statementAfter = findStatementAfterCaret(nodeAtCaret); + if (statementBefore != statementAfter) { // Enter pressed at statement break + return false; + } + if (statementBefore == null) { // empty file + return false; + } - if (string != null && prevElement != null && PyTokenTypes.STRING_NODES.contains(prevElement.getNode() - .getElementType()) && string.getTextOffset() < offset && !(element - .getNode() instanceof - PsiWhiteSpace)) { - final String stringText = element.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); - if (string.getTextOffset() + prefixLength >= offset) { - return Result.Continue; - } - final String pref = element.getText().substring(0, prefixLength); - final String quote = element.getText().substring(prefixLength, prefixLength + 1); - final boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.create(offset - 1, offset))); - final boolean isEscapedQuote = quote.equals(doc.getText(TextRange.create(offset, offset + 1))) && nextIsBackslash; - final boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.create(offset - 2, offset - 1))) && nextIsBackslash; - if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) { - return Result.Continue; - } + if (PsiTreeUtil.hasErrorElements(statementBefore)) { + if (!autoWrapInProgress) { + // code is already bad, don't mess it up even further + return false; + } + // if we're in middle of typing, it's expected that we will have error elements + } - final StringBuilder replacementString = new StringBuilder(); - myPostprocessShift = prefixLength + quote.length(); + if (inFromImportParentheses(statementBefore, nodeAtCaret.getTextRange().getStartOffset())) { + return false; + } - if (PsiTreeUtil.getParentOfType(string, IMPLICIT_WRAP_CLASSES) != null) { - replacementString.append(quote).append(pref).append(quote); - doc.insertString(offset, replacementString); - caretOffset.set(caretOffset.get() + 1); - return Result.Continue; - } - else { - if (isEscapedQuote) { - replacementString.append(quote); - caretOffset.set(caretOffset.get() + 1); - } - replacementString.append(quote).append(" \\").append(pref); - if (!isEscapedQuote) { - replacementString.append(quote); - } - doc.insertString(offset, replacementString.toString()); - caretOffset.set(caretOffset.get() + 3); - return Result.Continue; - } + PsiElement wrappableBefore = findWrappable(nodeAtCaret, true); + PsiElement wrappableAfter = findWrappable(nodeAtCaret, false); + if (!(wrappableBefore instanceof PsiComment)) { + while (wrappableBefore != null) { + PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, WRAPPABLE_CLASSES); + if (next == null) { + break; + } + wrappableBefore = next; + } + } + if (!(wrappableAfter instanceof PsiComment)) { + while (wrappableAfter != null) { + PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, WRAPPABLE_CLASSES); + if (next == null) { + break; + } + wrappableAfter = next; + } + } + if (wrappableBefore instanceof PsiComment || wrappableAfter instanceof PsiComment) { + return false; + } + if (wrappableAfter == null) { + return !(wrappableBefore instanceof PyDecoratorList); + } + return wrappableBefore != wrappableAfter; + } + + private static void insertDocStringStub(Editor editor, PsiElement element, DocstringState state) { + PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); + if (docOwner != null) { + final int caretOffset = editor.getCaretModel().getOffset(); + final Document document = editor.getDocument(); + final String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); + final String docString = PyDocstringGenerator.forDocStringOwner(docOwner) + .withInferredParameters(true) + .withQuotes(quotes) + .forceNewMode() + .buildDocString(); + if (state == DocstringState.INCOMPLETE) { + document.insertString(caretOffset, docString.substring(3)); + } + else if (state == DocstringState.EMPTY) { + document.replaceString(caretOffset, caretOffset + 3, docString.substring(3)); + } + } } - - if (!PyCodeInsightSettings.getInstance().INSERT_BACKSLASH_ON_WRAP) { - return Result.Continue; - } - return checkInsertBackslash(file, caretOffset, dataContext, offset, doc); - } - - private static Result checkInsertBackslash(PsiFile file, Ref caretOffset, DataContext dataContext, int offset, Document doc) { - boolean autoWrapInProgress = DataManager.getInstance() - .loadFromDataContext(dataContext, - consulo.ide.impl.idea.codeInsight.editorActions.AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) != null; - if (needInsertBackslash(file, offset, autoWrapInProgress)) { - doc.insertString(offset, "\\"); - caretOffset.set(caretOffset.get() + 1); - } - return Result.Continue; - } - - public static boolean needInsertBackslash(PsiFile file, int offset, boolean autoWrapInProgress) { - if (offset > 0) { - final PsiElement beforeCaret = file.findElementAt(offset - 1); - if (beforeCaret instanceof PsiWhiteSpace && beforeCaret.getText().indexOf('\\') >= 0) { - // we've got a backslash at EOL already, don't need another one - return false; - } - } - PsiElement atCaret = file.findElementAt(offset); - if (atCaret == null) { - return false; - } - ASTNode nodeAtCaret = atCaret.getNode(); - return needInsertBackslash(nodeAtCaret, autoWrapInProgress); - } - - public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapInProgress) { - PsiElement statementBefore = findStatementBeforeCaret(nodeAtCaret); - PsiElement statementAfter = findStatementAfterCaret(nodeAtCaret); - if (statementBefore != statementAfter) { // Enter pressed at statement break - return false; - } - if (statementBefore == null) { // empty file - return false; + @Nullable + private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) { + PsiElement wrappable = before ? findBeforeCaret(nodeAtCaret, WRAPPABLE_CLASSES) : findAfterCaret(nodeAtCaret, WRAPPABLE_CLASSES); + if (wrappable == null) { + PsiElement emptyTuple = before + ? findBeforeCaret(nodeAtCaret, PyTupleExpression.class) + : findAfterCaret(nodeAtCaret, PyTupleExpression.class); + if (emptyTuple != null && emptyTuple.getNode().getFirstChildNode().getElementType() == PyTokenTypes.LPAR) { + wrappable = emptyTuple; + } + } + return wrappable; } - if (PsiTreeUtil.hasErrorElements(statementBefore)) { - if (!autoWrapInProgress) { - // code is already bad, don't mess it up even further - return false; - } - // if we're in middle of typing, it's expected that we will have error elements + @Nullable + private static PsiElement findStatementBeforeCaret(ASTNode node) { + return findBeforeCaret(node, PyStatement.class); } - if (inFromImportParentheses(statementBefore, nodeAtCaret.getTextRange().getStartOffset())) { - return false; + @Nullable + private static PsiElement findStatementAfterCaret(ASTNode node) { + return findAfterCaret(node, PyStatement.class); } - PsiElement wrappableBefore = findWrappable(nodeAtCaret, true); - PsiElement wrappableAfter = findWrappable(nodeAtCaret, false); - if (!(wrappableBefore instanceof PsiComment)) { - while (wrappableBefore != null) { - PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, WRAPPABLE_CLASSES); - if (next == null) { - break; + private static PsiElement findBeforeCaret(ASTNode atCaret, Class... classes) { + while (atCaret != null) { + atCaret = TreeUtil.prevLeaf(atCaret); + if (atCaret != null && atCaret.getElementType() != TokenType.WHITE_SPACE) { + return getNonStrictParentOfType(atCaret.getPsi(), classes); + } } - wrappableBefore = next; - } - } - if (!(wrappableAfter instanceof PsiComment)) { - while (wrappableAfter != null) { - PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, WRAPPABLE_CLASSES); - if (next == null) { - break; - } - wrappableAfter = next; - } - } - if (wrappableBefore instanceof PsiComment || wrappableAfter instanceof PsiComment) { - return false; - } - if (wrappableAfter == null) { - return !(wrappableBefore instanceof PyDecoratorList); - } - return wrappableBefore != wrappableAfter; - } - - private static void insertDocStringStub(Editor editor, PsiElement element, DocstringState state) { - PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); - if (docOwner != null) { - final int caretOffset = editor.getCaretModel().getOffset(); - final Document document = editor.getDocument(); - final String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); - final String docString = - PyDocstringGenerator.forDocStringOwner(docOwner).withInferredParameters(true).withQuotes(quotes).forceNewMode().buildDocString(); - if (state == DocstringState.INCOMPLETE) { - document.insertString(caretOffset, docString.substring(3)); - } - else if (state == DocstringState.EMPTY) { - document.replaceString(caretOffset, caretOffset + 3, docString.substring(3)); - } - } - } - - @Nullable - private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) { - PsiElement wrappable = before ? findBeforeCaret(nodeAtCaret, WRAPPABLE_CLASSES) : findAfterCaret(nodeAtCaret, WRAPPABLE_CLASSES); - if (wrappable == null) { - PsiElement emptyTuple = - before ? findBeforeCaret(nodeAtCaret, PyTupleExpression.class) : findAfterCaret(nodeAtCaret, PyTupleExpression.class); - if (emptyTuple != null && emptyTuple.getNode().getFirstChildNode().getElementType() == PyTokenTypes.LPAR) { - wrappable = emptyTuple; - } + return null; } - return wrappable; - } - - @Nullable - private static PsiElement findStatementBeforeCaret(ASTNode node) { - return findBeforeCaret(node, PyStatement.class); - } - - @Nullable - private static PsiElement findStatementAfterCaret(ASTNode node) { - return findAfterCaret(node, PyStatement.class); - } - - private static PsiElement findBeforeCaret(ASTNode atCaret, Class... classes) { - while (atCaret != null) { - atCaret = TreeUtil.prevLeaf(atCaret); - if (atCaret != null && atCaret.getElementType() != TokenType.WHITE_SPACE) { - return getNonStrictParentOfType(atCaret.getPsi(), classes); - } - } - return null; - } - - private static PsiElement findAfterCaret(ASTNode atCaret, Class... classes) { - while (atCaret != null) { - if (atCaret.getElementType() != TokenType.WHITE_SPACE) { - return getNonStrictParentOfType(atCaret.getPsi(), classes); - } - atCaret = TreeUtil.nextLeaf(atCaret); - } - return null; - } - - @Nullable - private static T getNonStrictParentOfType(@Nonnull PsiElement element, @Nonnull Class... classes) { - PsiElement run = element; - while (run != null) { - for (Class aClass : classes) { - if (aClass.isInstance(run)) { - return (T)run; - } - } - if (run instanceof PsiFile || run instanceof PyStatementList) { - break; - } - run = run.getParent(); + + private static PsiElement findAfterCaret(ASTNode atCaret, Class... classes) { + while (atCaret != null) { + if (atCaret.getElementType() != TokenType.WHITE_SPACE) { + return getNonStrictParentOfType(atCaret.getPsi(), classes); + } + atCaret = TreeUtil.nextLeaf(atCaret); + } + return null; } - return null; - } + @Nullable + private static T getNonStrictParentOfType(@Nonnull PsiElement element, @Nonnull Class... classes) { + PsiElement run = element; + while (run != null) { + for (Class aClass : classes) { + if (aClass.isInstance(run)) { + return (T)run; + } + } + if (run instanceof PsiFile || run instanceof PyStatementList) { + break; + } + run = run.getParent(); + } - private static boolean inFromImportParentheses(PsiElement statement, int offset) { - if (!(statement instanceof PyFromImportStatement)) { - return false; - } - PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement; - PsiElement leftParen = fromImportStatement.getLeftParen(); - if (leftParen != null && offset >= leftParen.getTextRange().getEndOffset()) { - return true; + return null; } - return false; - } - @Override - public Result postProcessEnter(@Nonnull PsiFile file, @Nonnull Editor editor, @Nonnull DataContext dataContext) { - if (!(file instanceof PyFile)) { - return Result.Continue; - } - if (myPostprocessShift > 0) { - editor.getCaretModel().moveCaretRelatively(myPostprocessShift, 0, false, false, false); - myPostprocessShift = 0; - return Result.Continue; - } - addGoogleDocStringSectionIndent(file, editor, editor.getCaretModel().getOffset()); - return super.postProcessEnter(file, editor, dataContext); - } - - private static void addGoogleDocStringSectionIndent(@Nonnull PsiFile file, @Nonnull Editor editor, int offset) { - final Document document = editor.getDocument(); - PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); - final PsiElement element = file.findElementAt(offset); - if (element != null) { - // Insert additional indentation after section header in Google code style docstrings - final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); - if (pyString != null) { - final String docStringText = pyString.getText(); - final DocStringFormat format = DocStringUtil.guessDocStringFormat(docStringText, pyString); - if (format == DocStringFormat.GOOGLE && offset + 1 < document.getTextLength()) { - final int lineNum = document.getLineNumber(offset); - final TextRange lineRange = TextRange.create(document.getLineStartOffset(lineNum - 1), document.getLineEndOffset(lineNum - 1)); - final Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER.matcher(document.getText(lineRange)); - if (matcher.matches() && SectionBasedDocString.isValidSectionTitle(matcher.group(1))) { - document.insertString(offset, GoogleCodeStyleDocStringBuilder.getDefaultSectionIndent(file.getProject())); - editor.getCaretModel().moveCaretRelatively(2, 0, false, false, false); - } - } - } + private static boolean inFromImportParentheses(PsiElement statement, int offset) { + if (!(statement instanceof PyFromImportStatement)) { + return false; + } + PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement; + PsiElement leftParen = fromImportStatement.getLeftParen(); + if (leftParen != null && offset >= leftParen.getTextRange().getEndOffset()) { + return true; + } + return false; } - } - - enum DocstringState { - NONE, - INCOMPLETE, - EMPTY - } - - @Nonnull - public static DocstringState canGenerateDocstring(@Nonnull PsiElement element, int firstQuoteOffset, @Nonnull Document document) { - if (firstQuoteOffset < 0 || firstQuoteOffset > document.getTextLength() - 3) { - return DocstringState.NONE; + + @Override + public Result postProcessEnter(@Nonnull PsiFile file, @Nonnull Editor editor, @Nonnull DataContext dataContext) { + if (!(file instanceof PyFile)) { + return Result.Continue; + } + if (myPostprocessShift > 0) { + editor.getCaretModel().moveCaretRelatively(myPostprocessShift, 0, false, false, false); + myPostprocessShift = 0; + return Result.Continue; + } + addGoogleDocStringSectionIndent(file, editor, editor.getCaretModel().getOffset()); + return super.postProcessEnter(file, editor, dataContext); + } + + private static void addGoogleDocStringSectionIndent(@Nonnull PsiFile file, @Nonnull Editor editor, int offset) { + final Document document = editor.getDocument(); + PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); + final PsiElement element = file.findElementAt(offset); + if (element != null) { + // Insert additional indentation after section header in Google code style docstrings + final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + if (pyString != null) { + final String docStringText = pyString.getText(); + final DocStringFormat format = DocStringUtil.guessDocStringFormat(docStringText, pyString); + if (format == DocStringFormat.GOOGLE && offset + 1 < document.getTextLength()) { + final int lineNum = document.getLineNumber(offset); + final TextRange lineRange = + TextRange.create(document.getLineStartOffset(lineNum - 1), document.getLineEndOffset(lineNum - 1)); + final Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER.matcher(document.getText(lineRange)); + if (matcher.matches() && SectionBasedDocString.isValidSectionTitle(matcher.group(1))) { + document.insertString(offset, GoogleCodeStyleDocStringBuilder.getDefaultSectionIndent(file.getProject())); + editor.getCaretModel().moveCaretRelatively(2, 0, false, false, false); + } + } + } + } } - final String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); - if (!quotes.equals("\"\"\"") && !quotes.equals("'''")) { - return DocstringState.NONE; + + enum DocstringState { + NONE, + INCOMPLETE, + EMPTY } - final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); - if (pyString != null) { - - String nodeText = element.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); - nodeText = nodeText.substring(prefixLength); - - final String literalText = pyString.getText(); - if (literalText.endsWith(nodeText) && nodeText.startsWith(quotes)) { - if (firstQuoteOffset == pyString.getTextOffset() + prefixLength) { - PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); - if (error == null) { - error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); - } - if (error != null) { - return DocstringState.INCOMPLETE; - } - - if (nodeText.equals(quotes + quotes)) { - return DocstringState.EMPTY; - } - - if (nodeText.length() < 6 || !nodeText.endsWith(quotes)) { - return DocstringState.INCOMPLETE; - } - // Sometimes if incomplete docstring is followed by another declaration with a docstring, it might be treated - // as complete docstring, because we can't understand that closing quotes actually belong to another docstring. - final String docstringIndent = PyIndentUtil.getLineIndent(document, document.getLineNumber(firstQuoteOffset)); - for (String line : LineTokenizer.tokenizeIntoList(nodeText, false)) { - final String lineIndent = PyIndentUtil.getLineIndent(line); - final String lineContent = line.substring(lineIndent.length()); - if ((lineContent.startsWith("def ") || lineContent.startsWith("class ")) && - docstringIndent.length() > lineIndent.length() && docstringIndent.startsWith(lineIndent)) { - return DocstringState.INCOMPLETE; + + @Nonnull + public static DocstringState canGenerateDocstring(@Nonnull PsiElement element, int firstQuoteOffset, @Nonnull Document document) { + if (firstQuoteOffset < 0 || firstQuoteOffset > document.getTextLength() - 3) { + return DocstringState.NONE; + } + final String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); + if (!quotes.equals("\"\"\"") && !quotes.equals("'''")) { + return DocstringState.NONE; + } + final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + if (pyString != null) { + + String nodeText = element.getText(); + final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); + nodeText = nodeText.substring(prefixLength); + + final String literalText = pyString.getText(); + if (literalText.endsWith(nodeText) && nodeText.startsWith(quotes)) { + if (firstQuoteOffset == pyString.getTextOffset() + prefixLength) { + PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); + if (error == null) { + error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); + } + if (error != null) { + return DocstringState.INCOMPLETE; + } + + if (nodeText.equals(quotes + quotes)) { + return DocstringState.EMPTY; + } + + if (nodeText.length() < 6 || !nodeText.endsWith(quotes)) { + return DocstringState.INCOMPLETE; + } + // Sometimes if incomplete docstring is followed by another declaration with a docstring, it might be treated + // as complete docstring, because we can't understand that closing quotes actually belong to another docstring. + final String docstringIndent = PyIndentUtil.getLineIndent(document, document.getLineNumber(firstQuoteOffset)); + for (String line : LineTokenizer.tokenizeIntoList(nodeText, false)) { + final String lineIndent = PyIndentUtil.getLineIndent(line); + final String lineContent = line.substring(lineIndent.length()); + if ((lineContent.startsWith("def ") || lineContent.startsWith("class ")) && + docstringIndent.length() > lineIndent.length() && docstringIndent.startsWith(lineIndent)) { + return DocstringState.INCOMPLETE; + } + } + } } - } } - } + return DocstringState.NONE; } - return DocstringState.NONE; - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java index ee453044..48e960c0 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java @@ -33,108 +33,93 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; /** * @author yole */ @ExtensionImpl -public class PythonRunConfigurationProducer extends RunConfigurationProducer -{ - - public PythonRunConfigurationProducer() - { - super(PythonConfigurationType.getInstance().getFactory()); - } +public class PythonRunConfigurationProducer extends RunConfigurationProducer { + public PythonRunConfigurationProducer() { + super(PythonConfigurationType.getInstance().getFactory()); + } - @Override - protected boolean setupConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context, Ref sourceElement) - { + @Override + protected boolean setupConfigurationFromContext( + PythonRunConfiguration configuration, + ConfigurationContext context, + Ref sourceElement + ) { - final Location location = context.getLocation(); - if(location == null) - { - return false; - } - final PsiFile script = location.getPsiElement().getContainingFile(); - if(!isAvailable(location, script)) - { - return false; - } + final Location location = context.getLocation(); + if (location == null) { + return false; + } + final PsiFile script = location.getPsiElement().getContainingFile(); + if (!isAvailable(location, script)) { + return false; + } - final VirtualFile vFile = script.getVirtualFile(); - if(vFile == null) - { - return false; - } - configuration.setScriptName(vFile.getPath()); - final VirtualFile parent = vFile.getParent(); - if(parent != null) - { - configuration.setWorkingDirectory(parent.getPath()); - } - final Module module = ModuleUtilCore.findModuleForPsiElement(script); - if(module != null) - { - configuration.setUseModuleSdk(true); - configuration.setModule(module); - } - configuration.setName(configuration.suggestedName()); - return true; - } + final VirtualFile vFile = script.getVirtualFile(); + if (vFile == null) { + return false; + } + configuration.setScriptName(vFile.getPath()); + final VirtualFile parent = vFile.getParent(); + if (parent != null) { + configuration.setWorkingDirectory(parent.getPath()); + } + final Module module = ModuleUtilCore.findModuleForPsiElement(script); + if (module != null) { + configuration.setUseModuleSdk(true); + configuration.setModule(module); + } + configuration.setName(configuration.suggestedName()); + return true; + } - @Override - public boolean isConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context) - { - final Location location = context.getLocation(); - if(location == null) - { - return false; - } - final PsiFile script = location.getPsiElement().getContainingFile(); - if(!isAvailable(location, script)) - { - return false; - } - final VirtualFile virtualFile = script.getVirtualFile(); - if(virtualFile == null) - { - return false; - } - if(virtualFile instanceof LightVirtualFile) - { - return false; - } - final String workingDirectory = configuration.getWorkingDirectory(); - final String scriptName = configuration.getScriptName(); - final String path = virtualFile.getPath(); - return scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - } + @Override + public boolean isConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context) { + final Location location = context.getLocation(); + if (location == null) { + return false; + } + final PsiFile script = location.getPsiElement().getContainingFile(); + if (!isAvailable(location, script)) { + return false; + } + final VirtualFile virtualFile = script.getVirtualFile(); + if (virtualFile == null) { + return false; + } + if (virtualFile instanceof LightVirtualFile) { + return false; + } + final String workingDirectory = configuration.getWorkingDirectory(); + final String scriptName = configuration.getScriptName(); + final String path = virtualFile.getPath(); + return scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); + } - private static boolean isAvailable(@Nonnull final Location location, @Nullable final PsiFile script) - { - if(script == null || script.getFileType() != PythonFileType.INSTANCE) - { - return false; - } - final Module module = ModuleUtilCore.findModuleForPsiElement(script); - if(module != null) - { - for(RunnableScriptFilter f : Extensions.getExtensions(RunnableScriptFilter.EP_NAME)) - { - // Configuration producers always called by user - if(f.isRunnableScript(script, module, location, TypeEvalContext.userInitiated(location.getProject(), null))) - { - return false; - } - } - } - return true; - } + private static boolean isAvailable(@Nonnull final Location location, @Nullable final PsiFile script) { + if (script == null || script.getFileType() != PythonFileType.INSTANCE) { + return false; + } + final Module module = ModuleUtilCore.findModuleForPsiElement(script); + if (module != null) { + for (RunnableScriptFilter f : Extensions.getExtensions(RunnableScriptFilter.EP_NAME)) { + // Configuration producers always called by user + if (f.isRunnableScript(script, module, location, TypeEvalContext.userInitiated(location.getProject(), null))) { + return false; + } + } + } + return true; + } - @Override - public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) - { - return other.isProducedBy(PythonRunConfigurationProducer.class); - } + @Override + public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) { + return other.isProducedBy(PythonRunConfigurationProducer.class); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java index 707f4667..ef7011a3 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java @@ -60,274 +60,258 @@ /** * User: ktisha */ -abstract public class PythonTestConfigurationProducer extends RunConfigurationProducer -{ - - public PythonTestConfigurationProducer(final ConfigurationFactory configurationFactory) - { - super(configurationFactory); - } - - @Override - public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) - { - final Location location = context.getLocation(); - if(location == null || !isAvailable(location)) - { - return false; - } - final PsiElement element = location.getPsiElement(); - final PsiFileSystemItem file = element.getContainingFile(); - if(file == null) - { - return false; - } - final VirtualFile virtualFile = element instanceof PsiDirectory ? ((PsiDirectory) element).getVirtualFile() : file.getVirtualFile(); - if(virtualFile == null) - { - return false; - } - final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class); - - final AbstractPythonTestRunConfiguration.TestType confType = configuration.getTestType(); - final String workingDirectory = configuration.getWorkingDirectory(); - - if(element instanceof PsiDirectory) - { - final String path = ((PsiDirectory) element).getVirtualFile().getPath(); - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER && path.equals(configuration.getFolderName()) || path.equals(new File(workingDirectory, configuration - .getFolderName()).getAbsolutePath()); - } - - final String scriptName = configuration.getScriptName(); - final String path = virtualFile.getPath(); - final boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - - if(pyFunction != null) - { - final String methodName = configuration.getMethodName(); - if(pyFunction.getContainingClass() == null) - { - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION && - methodName.equals(pyFunction.getName()) && isTestFileEquals; - } - else - { - final String className = configuration.getClassName(); - - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_METHOD && - methodName.equals(pyFunction.getName()) && - pyClass != null && className.equals(pyClass.getName()) && isTestFileEquals; - } - } - if(pyClass != null) - { - final String className = configuration.getClassName(); - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_CLASS && - className.equals(pyClass.getName()) && isTestFileEquals; - } - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT && isTestFileEquals; - } - - @Override - protected boolean setupConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context, Ref sourceElement) - { - if(context == null) - { - return false; - } - final Location location = context.getLocation(); - if(location == null || !isAvailable(location)) - { - return false; - } - PsiElement element = location.getPsiElement(); - if(element instanceof PsiWhiteSpace) - { - element = PyUtil.findNonWhitespaceAtOffset(element.getContainingFile(), element.getTextOffset()); - } - - if(PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) - { - return false; - } - final Module module = location.getModule(); - if(!isPythonModule(module)) - { - return false; - } - - if(element instanceof PsiDirectory) - { - return setupConfigurationFromFolder((PsiDirectory) element, configuration); - } - - final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); - if(pyFunction != null && isTestFunction(pyFunction, configuration)) - { - return setupConfigurationFromFunction(pyFunction, configuration); - } - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); - if(pyClass != null && isTestClass(pyClass, configuration, TypeEvalContext.userInitiated(pyClass.getProject(), element.getContainingFile()))) - { - return setupConfigurationFromClass(pyClass, configuration); - } - if(element == null) - { - return false; - } - final PsiFile file = element.getContainingFile(); - if(file instanceof PyFile && isTestFile((PyFile) file)) - { - return setupConfigurationFromFile((PyFile) file, configuration); - } - - return false; - } - - private boolean setupConfigurationFromFolder(@Nonnull final PsiDirectory element, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - final VirtualFile virtualFile = element.getVirtualFile(); - if(!isTestFolder(virtualFile, element.getProject())) - { - return false; - } - final String path = virtualFile.getPath(); - - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER); - configuration.setFolderName(path); - configuration.setWorkingDirectory(path); - configuration.setGeneratedName(); - setModuleSdk(element, configuration); - return true; - } - - private static void setModuleSdk(@Nonnull final PsiElement element, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - configuration.setUseModuleSdk(true); - configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); - } - - protected boolean setupConfigurationFromFunction(@Nonnull final PyFunction pyFunction, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - final PyClass containingClass = pyFunction.getContainingClass(); - configuration.setMethodName(pyFunction.getName()); - - if(containingClass != null) - { - configuration.setClassName(containingClass.getName()); - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_METHOD); - } - else - { - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION); - } - return setupConfigurationScript(configuration, pyFunction); - } - - protected boolean setupConfigurationFromClass(@Nonnull final PyClass pyClass, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_CLASS); - configuration.setClassName(pyClass.getName()); - return setupConfigurationScript(configuration, pyClass); - } - - protected boolean setupConfigurationFromFile(@Nonnull final PyFile pyFile, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT); - return setupConfigurationScript(configuration, pyFile); - } - - protected static boolean setupConfigurationScript(@Nonnull final AbstractPythonTestRunConfiguration cfg, @Nonnull final PyElement element) - { - final PyFile containingFile = PyUtil.getContainingPyFile(element); - if(containingFile == null) - { - return false; - } - final VirtualFile vFile = containingFile.getVirtualFile(); - if(vFile == null) - { - return false; - } - final VirtualFile parent = vFile.getParent(); - if(parent == null) - { - return false; - } - - cfg.setScriptName(vFile.getPath()); - - if(StringUtil.isEmptyOrSpaces(cfg.getWorkingDirectory())) - { - cfg.setWorkingDirectory(parent.getPath()); - } - cfg.setGeneratedName(); - setModuleSdk(element, cfg); - return true; - } - - protected boolean isTestFolder(@Nonnull final VirtualFile virtualFile, @Nonnull final Project project) - { - @NonNls final String name = virtualFile.getName(); - final HashSet roots = Sets.newHashSet(); - final Module[] modules = ModuleManager.getInstance(project).getModules(); - for(Module module : modules) - { - roots.addAll(PyUtil.getSourceRoots(module)); - } - Collections.addAll(roots, ProjectRootManager.getInstance(project).getContentRoots()); - return name.toLowerCase().contains("test") || roots.contains(virtualFile); - } - - protected boolean isAvailable(@Nonnull final Location location) - { - return false; - } - - protected boolean isTestClass(@Nonnull final PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, @Nullable final TypeEvalContext context) - { - return PythonUnitTestUtil.isTestCaseClass(pyClass, context); - } - - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - return PythonUnitTestUtil.isTestCaseFunction(pyFunction); - } - - protected boolean isTestFile(@Nonnull final PyFile file) - { - final List testCases = getTestCaseClassesFromFile(file); - if(testCases.isEmpty()) - { - return false; - } - return true; - } - - protected static boolean isPythonModule(Module module) - { - if(module == null) - { - return false; - } - return ModuleUtilCore.getExtension(module, PyModuleExtension.class) != null; - } - - protected List getTestCaseClassesFromFile(@Nonnull final PyFile pyFile) - { - return PythonUnitTestUtil.getTestCaseClassesFromFile(pyFile, TypeEvalContext.userInitiated(pyFile.getProject(), pyFile)); - } - - @Override - public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) - { - final RunConfiguration configuration = self.getConfiguration(); - if(configuration instanceof PythonUnitTestRunConfiguration && ((PythonUnitTestRunConfiguration) configuration).getTestType() == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER) - { - return true; - } - return other.isProducedBy(PythonTestConfigurationProducer.class) || other.isProducedBy(PythonRunConfigurationProducer.class); - } +abstract public class PythonTestConfigurationProducer extends RunConfigurationProducer { + public PythonTestConfigurationProducer(final ConfigurationFactory configurationFactory) { + super(configurationFactory); + } + + @Override + public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { + final Location location = context.getLocation(); + if (location == null || !isAvailable(location)) { + return false; + } + final PsiElement element = location.getPsiElement(); + final PsiFileSystemItem file = element.getContainingFile(); + if (file == null) { + return false; + } + final VirtualFile virtualFile = element instanceof PsiDirectory ? ((PsiDirectory)element).getVirtualFile() : file.getVirtualFile(); + if (virtualFile == null) { + return false; + } + final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); + final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class); + + final AbstractPythonTestRunConfiguration.TestType confType = configuration.getTestType(); + final String workingDirectory = configuration.getWorkingDirectory(); + + if (element instanceof PsiDirectory) { + final String path = ((PsiDirectory)element).getVirtualFile().getPath(); + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER && path.equals(configuration.getFolderName()) + || path.equals(new File(workingDirectory, configuration.getFolderName()).getAbsolutePath()); + } + + final String scriptName = configuration.getScriptName(); + final String path = virtualFile.getPath(); + final boolean isTestFileEquals = scriptName.equals(path) + || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); + + if (pyFunction != null) { + final String methodName = configuration.getMethodName(); + if (pyFunction.getContainingClass() == null) { + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION + && methodName.equals(pyFunction.getName()) && isTestFileEquals; + } + else { + final String className = configuration.getClassName(); + + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_METHOD + && methodName.equals(pyFunction.getName()) + && pyClass != null && className.equals(pyClass.getName()) && isTestFileEquals; + } + } + if (pyClass != null) { + final String className = configuration.getClassName(); + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_CLASS + && className.equals(pyClass.getName()) && isTestFileEquals; + } + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT && isTestFileEquals; + } + + @Override + protected boolean setupConfigurationFromContext( + AbstractPythonTestRunConfiguration configuration, + ConfigurationContext context, + Ref sourceElement + ) { + if (context == null) { + return false; + } + final Location location = context.getLocation(); + if (location == null || !isAvailable(location)) { + return false; + } + PsiElement element = location.getPsiElement(); + if (element instanceof PsiWhiteSpace) { + element = PyUtil.findNonWhitespaceAtOffset(element.getContainingFile(), element.getTextOffset()); + } + + if (PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) { + return false; + } + final Module module = location.getModule(); + if (!isPythonModule(module)) { + return false; + } + + if (element instanceof PsiDirectory) { + return setupConfigurationFromFolder((PsiDirectory)element, configuration); + } + + final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); + if (pyFunction != null && isTestFunction(pyFunction, configuration)) { + return setupConfigurationFromFunction(pyFunction, configuration); + } + final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + if (pyClass != null && isTestClass( + pyClass, + configuration, + TypeEvalContext.userInitiated(pyClass.getProject(), element.getContainingFile()) + )) { + return setupConfigurationFromClass(pyClass, configuration); + } + if (element == null) { + return false; + } + final PsiFile file = element.getContainingFile(); + if (file instanceof PyFile && isTestFile((PyFile)file)) { + return setupConfigurationFromFile((PyFile)file, configuration); + } + + return false; + } + + private boolean setupConfigurationFromFolder( + @Nonnull final PsiDirectory element, + @Nonnull final AbstractPythonTestRunConfiguration configuration + ) { + final VirtualFile virtualFile = element.getVirtualFile(); + if (!isTestFolder(virtualFile, element.getProject())) { + return false; + } + final String path = virtualFile.getPath(); + + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER); + configuration.setFolderName(path); + configuration.setWorkingDirectory(path); + configuration.setGeneratedName(); + setModuleSdk(element, configuration); + return true; + } + + private static void setModuleSdk(@Nonnull final PsiElement element, @Nonnull final AbstractPythonTestRunConfiguration configuration) { + configuration.setUseModuleSdk(true); + configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); + } + + protected boolean setupConfigurationFromFunction( + @Nonnull final PyFunction pyFunction, + @Nonnull final AbstractPythonTestRunConfiguration configuration + ) { + final PyClass containingClass = pyFunction.getContainingClass(); + configuration.setMethodName(pyFunction.getName()); + + if (containingClass != null) { + configuration.setClassName(containingClass.getName()); + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_METHOD); + } + else { + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION); + } + return setupConfigurationScript(configuration, pyFunction); + } + + protected boolean setupConfigurationFromClass( + @Nonnull final PyClass pyClass, + @Nonnull final AbstractPythonTestRunConfiguration configuration + ) { + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_CLASS); + configuration.setClassName(pyClass.getName()); + return setupConfigurationScript(configuration, pyClass); + } + + protected boolean setupConfigurationFromFile( + @Nonnull final PyFile pyFile, + @Nonnull final AbstractPythonTestRunConfiguration configuration + ) { + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT); + return setupConfigurationScript(configuration, pyFile); + } + + protected static boolean setupConfigurationScript( + @Nonnull final AbstractPythonTestRunConfiguration cfg, + @Nonnull final PyElement element + ) { + final PyFile containingFile = PyUtil.getContainingPyFile(element); + if (containingFile == null) { + return false; + } + final VirtualFile vFile = containingFile.getVirtualFile(); + if (vFile == null) { + return false; + } + final VirtualFile parent = vFile.getParent(); + if (parent == null) { + return false; + } + + cfg.setScriptName(vFile.getPath()); + + if (StringUtil.isEmptyOrSpaces(cfg.getWorkingDirectory())) { + cfg.setWorkingDirectory(parent.getPath()); + } + cfg.setGeneratedName(); + setModuleSdk(element, cfg); + return true; + } + + protected boolean isTestFolder(@Nonnull final VirtualFile virtualFile, @Nonnull final Project project) { + @NonNls final String name = virtualFile.getName(); + final HashSet roots = Sets.newHashSet(); + final Module[] modules = ModuleManager.getInstance(project).getModules(); + for (Module module : modules) { + roots.addAll(PyUtil.getSourceRoots(module)); + } + Collections.addAll(roots, ProjectRootManager.getInstance(project).getContentRoots()); + return name.toLowerCase().contains("test") || roots.contains(virtualFile); + } + + protected boolean isAvailable(@Nonnull final Location location) { + return false; + } + + protected boolean isTestClass( + @Nonnull final PyClass pyClass, + @Nullable final AbstractPythonTestRunConfiguration configuration, + @Nullable final TypeEvalContext context + ) { + return PythonUnitTestUtil.isTestCaseClass(pyClass, context); + } + + protected boolean isTestFunction( + @Nonnull final PyFunction pyFunction, + @Nullable final AbstractPythonTestRunConfiguration configuration + ) { + return PythonUnitTestUtil.isTestCaseFunction(pyFunction); + } + + protected boolean isTestFile(@Nonnull final PyFile file) { + final List testCases = getTestCaseClassesFromFile(file); + if (testCases.isEmpty()) { + return false; + } + return true; + } + + protected static boolean isPythonModule(Module module) { + if (module == null) { + return false; + } + return ModuleUtilCore.getExtension(module, PyModuleExtension.class) != null; + } + + protected List getTestCaseClassesFromFile(@Nonnull final PyFile pyFile) { + return PythonUnitTestUtil.getTestCaseClassesFromFile(pyFile, TypeEvalContext.userInitiated(pyFile.getProject(), pyFile)); + } + + @Override + public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) { + final RunConfiguration configuration = self.getConfiguration(); + if (configuration instanceof PythonUnitTestRunConfiguration + && ((PythonUnitTestRunConfiguration)configuration).getTestType() == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER) { + return true; + } + return other.isProducedBy(PythonTestConfigurationProducer.class) || other.isProducedBy(PythonRunConfigurationProducer.class); + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java index 77e2f67f..cc076792 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java @@ -30,101 +30,90 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; /** * User: catherine */ @ExtensionImpl -public class PythonAtTestConfigurationProducer extends PythonTestConfigurationProducer -{ - public PythonAtTestConfigurationProducer() - { - super(PythonTestConfigurationType.getInstance().PY_ATTEST_FACTORY); - } +public class PythonAtTestConfigurationProducer extends PythonTestConfigurationProducer { + public PythonAtTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_ATTEST_FACTORY); + } - protected boolean isAvailable(@Nonnull final Location location) - { - final PsiElement element = location.getPsiElement(); - Module module = location.getModule(); - if(module == null) - { - module = ModuleUtilCore.findModuleForPsiElement(element); - } + protected boolean isAvailable(@Nonnull final Location location) { + final PsiElement element = location.getPsiElement(); + Module module = location.getModule(); + if (module == null) { + module = ModuleUtilCore.findModuleForPsiElement(element); + } - final Sdk sdk = PythonSdkType.findPythonSdk(module); - return module != null && TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME) && sdk != null; - } + final Sdk sdk = PythonSdkType.findPythonSdk(module); + return module != null && TestRunnerService.getInstance(module) + .getProjectConfiguration() + .equals(PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME) && sdk != null; + } - @Override - protected boolean isTestClass(@Nonnull final PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, @Nullable final TypeEvalContext context) - { - for(PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) - { - if(type != null && "TestBase".equals(type.getName()) && hasTestFunction(pyClass)) - { - return true; - } - } - return false; - } + @Override + protected boolean isTestClass( + @Nonnull final PyClass pyClass, + @Nullable final AbstractPythonTestRunConfiguration configuration, + @Nullable final TypeEvalContext context + ) { + for (PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) { + if (type != null && "TestBase".equals(type.getName()) && hasTestFunction(pyClass)) { + return true; + } + } + return false; + } - private static boolean hasTestFunction(@Nonnull final PyClass pyClass) - { - PyFunction[] methods = pyClass.getMethods(); - for(PyFunction function : methods) - { - PyDecoratorList decorators = function.getDecoratorList(); - if(decorators == null) - { - continue; - } - for(PyDecorator decorator : decorators.getDecorators()) - { - if("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) - { - return true; - } - } - } - return false; - } + private static boolean hasTestFunction(@Nonnull final PyClass pyClass) { + PyFunction[] methods = pyClass.getMethods(); + for (PyFunction function : methods) { + PyDecoratorList decorators = function.getDecoratorList(); + if (decorators == null) { + continue; + } + for (PyDecorator decorator : decorators.getDecorators()) { + if ("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) { + return true; + } + } + } + return false; + } - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - PyDecoratorList decorators = pyFunction.getDecoratorList(); - if(decorators == null) - { - return false; - } - for(PyDecorator decorator : decorators.getDecorators()) - { - if("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) - { - return true; - } - } - return false; - } + protected boolean isTestFunction( + @Nonnull final PyFunction pyFunction, + @Nullable final AbstractPythonTestRunConfiguration configuration + ) { + PyDecoratorList decorators = pyFunction.getDecoratorList(); + if (decorators == null) { + return false; + } + for (PyDecorator decorator : decorators.getDecorators()) { + if ("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) { + return true; + } + } + return false; + } - protected List getTestCaseClassesFromFile(@Nonnull final PyFile file) - { - List result = Lists.newArrayList(); - for(PyClass cls : file.getTopLevelClasses()) - { - if(isTestClass(cls, null, null)) - { - result.add(cls); - } - } + protected List getTestCaseClassesFromFile(@Nonnull final PyFile file) { + List result = Lists.newArrayList(); + for (PyClass cls : file.getTopLevelClasses()) { + if (isTestClass(cls, null, null)) { + result.add(cls); + } + } - for(PyFunction cls : file.getTopLevelFunctions()) - { - if(isTestFunction(cls, null)) - { - result.add(cls); - } - } - return result; - } + for (PyFunction cls : file.getTopLevelFunctions()) { + if (isTestFunction(cls, null)) { + result.add(cls); + } + } + return result; + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java index 80189ffa..7719ec95 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java @@ -38,86 +38,76 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; @ExtensionImpl -public class PythonDocTestConfigurationProducer extends PythonTestConfigurationProducer -{ - - public PythonDocTestConfigurationProducer() - { - super(PythonTestConfigurationType.getInstance().PY_DOCTEST_FACTORY); - } - - @Override - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - return PythonDocTestUtil.isDocTestFunction(pyFunction); - } +public class PythonDocTestConfigurationProducer extends PythonTestConfigurationProducer { + public PythonDocTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_DOCTEST_FACTORY); + } - @Override - protected boolean isTestClass(@Nonnull PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, @Nullable final TypeEvalContext context) - { - return PythonDocTestUtil.isDocTestClass(pyClass); - } + @Override + protected boolean isTestFunction( + @Nonnull final PyFunction pyFunction, + @Nullable final AbstractPythonTestRunConfiguration configuration + ) { + return PythonDocTestUtil.isDocTestFunction(pyFunction); + } - @Override - protected boolean isTestFile(@Nonnull PyFile file) - { - final List testCases = PythonDocTestUtil.getDocTestCasesFromFile(file); - return !testCases.isEmpty(); - } + @Override + protected boolean isTestClass( + @Nonnull PyClass pyClass, + @Nullable final AbstractPythonTestRunConfiguration configuration, + @Nullable final TypeEvalContext context + ) { + return PythonDocTestUtil.isDocTestClass(pyClass); + } - protected boolean isAvailable(@Nonnull final Location location) - { - final Module module = location.getModule(); - if(!isPythonModule(module)) - { - return false; - } - final PsiElement element = location.getPsiElement(); - if(element instanceof PsiFile) - { - final PyDocTestVisitor visitor = new PyDocTestVisitor(); - element.accept(visitor); - return visitor.hasTests; - } - else - { - return true; - } - } + @Override + protected boolean isTestFile(@Nonnull PyFile file) { + final List testCases = PythonDocTestUtil.getDocTestCasesFromFile(file); + return !testCases.isEmpty(); + } - private static class PyDocTestVisitor extends PsiRecursiveElementVisitor - { - boolean hasTests = false; + protected boolean isAvailable(@Nonnull final Location location) { + final Module module = location.getModule(); + if (!isPythonModule(module)) { + return false; + } + final PsiElement element = location.getPsiElement(); + if (element instanceof PsiFile) { + final PyDocTestVisitor visitor = new PyDocTestVisitor(); + element.accept(visitor); + return visitor.hasTests; + } + else { + return true; + } + } - @Override - public void visitFile(PsiFile node) - { - if(node instanceof PyFile) - { - List testClasses = PythonDocTestUtil.getDocTestCasesFromFile((PyFile) node); - if(!testClasses.isEmpty()) - { - hasTests = true; - } - } - else - { - final String text = node.getText(); - if(PythonDocTestUtil.hasExample(text)) - { - hasTests = true; - } - } - } - } + private static class PyDocTestVisitor extends PsiRecursiveElementVisitor { + boolean hasTests = false; - @Override - protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) - { - return false; - } + @Override + public void visitFile(PsiFile node) { + if (node instanceof PyFile) { + List testClasses = PythonDocTestUtil.getDocTestCasesFromFile((PyFile)node); + if (!testClasses.isEmpty()) { + hasTests = true; + } + } + else { + final String text = node.getText(); + if (PythonDocTestUtil.hasExample(text)) { + hasTests = true; + } + } + } + } + @Override + protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) { + return false; + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java index 5601829b..0fb2b959 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java @@ -42,155 +42,155 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.util.List; @ExtensionImpl -public class PyTestConfigurationProducer extends PythonTestConfigurationProducer -{ - - public PyTestConfigurationProducer() { - super(PythonTestConfigurationType.getInstance().PY_PYTEST_FACTORY); - } - - @Override - protected boolean setupConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, - ConfigurationContext context, - Ref sourceElement) { - final PsiElement element = sourceElement.get(); - final Module module = ModuleUtilCore.findModuleForPsiElement(element); - if (!(configuration instanceof PyTestRunConfiguration)) { - return false; - } - if (module == null) { - return false; - } - if (!(TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PY_TEST_NAME))) { - return false; - } +public class PyTestConfigurationProducer extends PythonTestConfigurationProducer { + public PyTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_PYTEST_FACTORY); + } + + @Override + protected boolean setupConfigurationFromContext( + AbstractPythonTestRunConfiguration configuration, + ConfigurationContext context, + Ref sourceElement + ) { + final PsiElement element = sourceElement.get(); + final Module module = ModuleUtilCore.findModuleForPsiElement(element); + if (!(configuration instanceof PyTestRunConfiguration)) { + return false; + } + if (module == null) { + return false; + } + if (!(TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PY_TEST_NAME))) { + return false; + } - final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); - if (file == null) { - return false; - } - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return false; - } + final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); + if (file == null) { + return false; + } + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) { + return false; + } - if (file instanceof PyFile || file instanceof PsiDirectory) { - final List testCases = - PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); - if (testCases.isEmpty()) { - return false; - } - } - else { - return false; - } + if (file instanceof PyFile || file instanceof PsiDirectory) { + final List testCases = + PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); + if (testCases.isEmpty()) { + return false; + } + } + else { + return false; + } - final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); - if (sdk == null) { - return false; - } + final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); + if (sdk == null) { + return false; + } - configuration.setUseModuleSdk(true); - configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); - ((PyTestRunConfiguration)configuration).setTestToRun(virtualFile.getPath()); + configuration.setUseModuleSdk(true); + configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); + ((PyTestRunConfiguration)configuration).setTestToRun(virtualFile.getPath()); - final String keywords = getKeywords(element, sdk); - if (keywords != null) { - ((PyTestRunConfiguration)configuration).useKeyword(true); - ((PyTestRunConfiguration)configuration).setKeywords(keywords); - configuration.setName("py.test in " + keywords); - } - else { - configuration.setName("py.test in " + file.getName()); - } - return true; - } - - @Nullable - private static String getKeywords(@Nonnull final PsiElement element, @Nonnull final Sdk sdk) { - final PyFunction pyFunction = findTestFunction(element); - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); - String keywords = null; - if (pyFunction != null) { - keywords = pyFunction.getName(); - if (pyClass != null) { - final List packages = PyPackageManager.getInstance(sdk).getPackages(); - final PyPackage pytestPackage = packages != null ? PyPackageUtil.findPackage(packages, "pytest") : null; - if (pytestPackage != null && PackageVersionComparator.VERSION_COMPARATOR.compare( - pytestPackage.getVersion(), - "2.3.3") >= 0) { - keywords = pyClass.getName() + " and " + keywords; + final String keywords = getKeywords(element, sdk); + if (keywords != null) { + ((PyTestRunConfiguration)configuration).useKeyword(true); + ((PyTestRunConfiguration)configuration).setKeywords(keywords); + configuration.setName("py.test in " + keywords); } else { - keywords = pyClass.getName() + "." + keywords; + configuration.setName("py.test in " + file.getName()); } - } - } - else if (pyClass != null) { - keywords = pyClass.getName(); - } - return keywords; - } - - @Nullable - private static PyFunction findTestFunction(PsiElement element) { - final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (function != null) { - final String name = function.getName(); - if (name != null && name.startsWith("test")) { - return function; - } - } - return null; - } - - @Override - public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { - final Location location = context.getLocation(); - if (location == null) { - return false; - } - if (!(configuration instanceof PyTestRunConfiguration)) { - return false; + return true; + } + + @Nullable + private static String getKeywords(@Nonnull final PsiElement element, @Nonnull final Sdk sdk) { + final PyFunction pyFunction = findTestFunction(element); + final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + String keywords = null; + if (pyFunction != null) { + keywords = pyFunction.getName(); + if (pyClass != null) { + final List packages = PyPackageManager.getInstance(sdk).getPackages(); + final PyPackage pytestPackage = packages != null ? PyPackageUtil.findPackage(packages, "pytest") : null; + if (pytestPackage != null + && PackageVersionComparator.VERSION_COMPARATOR.compare(pytestPackage.getVersion(), "2.3.3") >= 0) { + keywords = pyClass.getName() + " and " + keywords; + } + else { + keywords = pyClass.getName() + "." + keywords; + } + } + } + else if (pyClass != null) { + keywords = pyClass.getName(); + } + return keywords; } - final PsiElement element = location.getPsiElement(); - final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); - if (file == null) { - return false; - } - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return false; + @Nullable + private static PyFunction findTestFunction(PsiElement element) { + final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (function != null) { + final String name = function.getName(); + if (name != null && name.startsWith("test")) { + return function; + } + } + return null; } - if (file instanceof PyFile || file instanceof PsiDirectory) { - final List testCases = - PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); - if (testCases.isEmpty()) { - return false; - } - } - else { - return false; - } + @Override + public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { + final Location location = context.getLocation(); + if (location == null) { + return false; + } + if (!(configuration instanceof PyTestRunConfiguration)) { + return false; + } + final PsiElement element = location.getPsiElement(); + + final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); + if (file == null) { + return false; + } + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) { + return false; + } + + if (file instanceof PyFile || file instanceof PsiDirectory) { + final List testCases = + PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); + if (testCases.isEmpty()) { + return false; + } + } + else { + return false; + } + + final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); + if (sdk == null) { + return false; + } + final String keywords = getKeywords(element, sdk); + final String scriptName = ((PyTestRunConfiguration)configuration).getTestToRun(); + final String workingDirectory = configuration.getWorkingDirectory(); + final String path = virtualFile.getPath(); + final boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); - if (sdk == null) { - return false; + final String configurationKeywords = ((PyTestRunConfiguration)configuration).getKeywords(); + return isTestFileEquals && (configurationKeywords.equals(keywords) + || StringUtil.isEmptyOrSpaces(((PyTestRunConfiguration)configuration).getKeywords()) && keywords == null); } - final String keywords = getKeywords(element, sdk); - final String scriptName = ((PyTestRunConfiguration)configuration).getTestToRun(); - final String workingDirectory = configuration.getWorkingDirectory(); - final String path = virtualFile.getPath(); - final boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - - final String configurationKeywords = ((PyTestRunConfiguration)configuration).getKeywords(); - return isTestFileEquals && (configurationKeywords.equals(keywords) || StringUtil.isEmptyOrSpaces(((PyTestRunConfiguration)configuration) - .getKeywords()) && keywords == null); - } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java index a92c39e7..c1c7ab9e 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java @@ -35,57 +35,55 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; @ExtensionImpl -public class PythonUnitTestConfigurationProducer extends PythonTestConfigurationProducer -{ - public PythonUnitTestConfigurationProducer() - { - super(PythonTestConfigurationType.getInstance().PY_UNITTEST_FACTORY); - } +public class PythonUnitTestConfigurationProducer extends PythonTestConfigurationProducer { + public PythonUnitTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_UNITTEST_FACTORY); + } - protected boolean isAvailable(@Nonnull final Location location) - { - PsiElement element = location.getPsiElement(); - final Module module = ModuleUtilCore.findModuleForPsiElement(element); - if(module == null) - { - return false; - } - if((TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME))) - { - return true; - } - return false; - } + protected boolean isAvailable(@Nonnull final Location location) { + PsiElement element = location.getPsiElement(); + final Module module = ModuleUtilCore.findModuleForPsiElement(element); + if (module == null) { + return false; + } + if ((TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME))) { + return true; + } + return false; + } - @Override - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - final boolean isTestFunction = super.isTestFunction(pyFunction, configuration); - return isTestFunction || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration) configuration).isPureUnittest()); - } + @Override + protected boolean isTestFunction( + @Nonnull final PyFunction pyFunction, + @Nullable final AbstractPythonTestRunConfiguration configuration + ) { + final boolean isTestFunction = super.isTestFunction(pyFunction, configuration); + return isTestFunction || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration)configuration).isPureUnittest()); + } - @Override - protected boolean isTestClass(@Nonnull PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, TypeEvalContext context) - { - final boolean isTestClass = super.isTestClass(pyClass, configuration, context); - return isTestClass || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration) configuration).isPureUnittest()); - } + @Override + protected boolean isTestClass( + @Nonnull PyClass pyClass, + @Nullable final AbstractPythonTestRunConfiguration configuration, + TypeEvalContext context + ) { + final boolean isTestClass = super.isTestClass(pyClass, configuration, context); + return isTestClass || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration)configuration).isPureUnittest()); + } - @Override - protected boolean isTestFile(@Nonnull final PyFile file) - { - if(PyNames.SETUP_DOT_PY.equals(file.getName())) - { - return true; - } - final List testCases = getTestCaseClassesFromFile(file); - if(testCases.isEmpty()) - { - return false; - } - return true; - } + @Override + protected boolean isTestFile(@Nonnull final PyFile file) { + if (PyNames.SETUP_DOT_PY.equals(file.getName())) { + return true; + } + final List testCases = getTestCaseClassesFromFile(file); + if (testCases.isEmpty()) { + return false; + } + return true; + } } \ No newline at end of file From de2d4221c25ce94affc3c5da9e3aa8baa5303783 Mon Sep 17 00:00:00 2001 From: UNV Date: Fri, 11 Apr 2025 10:28:51 +0300 Subject: [PATCH 2/2] Refactoring of enter handlers and configuration producers. --- .../impl/editor/PyEnterAtIndentHandler.java | 7 +- .../editor/PyEnterBetweenBracketsHandler.java | 14 +- .../impl/editor/PythonEnterHandler.java | 120 ++++++----- .../run/PythonRunConfigurationProducer.java | 40 ++-- .../PythonTestConfigurationProducer.java | 201 ++++++++---------- .../PythonAtTestConfigurationProducer.java | 26 +-- .../PythonDocTestConfigurationProducer.java | 28 +-- .../pytest/PyTestConfigurationProducer.java | 69 +++--- .../PythonUnitTestConfigurationProducer.java | 28 ++- 9 files changed, 274 insertions(+), 259 deletions(-) diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java index 480a4a89..1a094873 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java @@ -26,8 +26,7 @@ import consulo.language.editor.inject.EditorWindow; import consulo.language.inject.InjectedLanguageManager; import consulo.language.psi.PsiFile; -import consulo.util.lang.ref.Ref; - +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; /** @@ -39,8 +38,8 @@ public class PyEnterAtIndentHandler extends EnterHandlerDelegateAdapter { public Result preprocessEnter( @Nonnull PsiFile file, @Nonnull Editor editor, - @Nonnull Ref caretOffset, - @Nonnull Ref caretAdvance, + @Nonnull SimpleReference caretOffset, + @Nonnull SimpleReference caretAdvance, @Nonnull DataContext dataContext, EditorActionHandler originalHandler ) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java index 3914675f..2a9fb3ca 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java @@ -15,14 +15,15 @@ */ package com.jetbrains.python.impl.editor; +import com.jetbrains.python.PythonLanguage; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.ide.impl.idea.codeInsight.editorActions.enter.EnterBetweenBracesHandler; -import consulo.dataContext.DataContext; import consulo.codeEditor.Editor; import consulo.codeEditor.action.EditorActionHandler; -import consulo.util.lang.ref.Ref; +import consulo.dataContext.DataContext; +import consulo.ide.impl.idea.codeInsight.editorActions.enter.EnterBetweenBracesHandler; import consulo.language.psi.PsiFile; -import com.jetbrains.python.PythonLanguage; +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; /** @@ -31,11 +32,12 @@ @ExtensionImpl public class PyEnterBetweenBracketsHandler extends EnterBetweenBracesHandler { @Override + @RequiredReadAction public Result preprocessEnter( @Nonnull PsiFile file, @Nonnull Editor editor, - @Nonnull Ref caretOffsetRef, - @Nonnull Ref caretAdvance, + @Nonnull SimpleReference caretOffsetRef, + @Nonnull SimpleReference caretAdvance, @Nonnull DataContext dataContext, EditorActionHandler originalHandler ) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java index b576fa54..22867358 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java @@ -19,8 +19,9 @@ import com.jetbrains.python.impl.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.impl.documentation.docstrings.*; import com.jetbrains.python.impl.psi.PyIndentUtil; -import com.jetbrains.python.psi.*; import com.jetbrains.python.impl.psi.impl.PyStringLiteralExpressionImpl; +import com.jetbrains.python.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.application.util.LineTokenizer; import consulo.codeEditor.Editor; @@ -38,8 +39,8 @@ import consulo.language.inject.InjectedLanguageManager; import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; -import consulo.util.lang.ref.Ref; - +import consulo.ui.annotation.RequiredUIAccess; +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -80,11 +81,12 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { }; @Override + @RequiredReadAction public Result preprocessEnter( @Nonnull PsiFile file, @Nonnull Editor editor, - @Nonnull Ref caretOffset, - @Nonnull Ref caretAdvance, + @Nonnull SimpleReference caretOffset, + @Nonnull SimpleReference caretAdvance, @Nonnull DataContext dataContext, EditorActionHandler originalHandler ) { @@ -97,16 +99,16 @@ public Result preprocessEnter( if (!(file instanceof PyFile)) { return Result.Continue; } - final Boolean isSplitLine = DataManager.getInstance().loadFromDataContext( + Boolean isSplitLine = DataManager.getInstance().loadFromDataContext( dataContext, consulo.ide.impl.idea.openapi.editor.actions.SplitLineAction.SPLIT_LINE_KEY ); if (isSplitLine != null) { return Result.Continue; } - final Document doc = editor.getDocument(); + Document doc = editor.getDocument(); PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc); - final PsiElement element = file.findElementAt(offset); + PsiElement element = file.findElementAt(offset); CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); if (codeInsightSettings.JAVADOC_STUB_ON_ENTER) { PsiElement comment = element; @@ -115,7 +117,7 @@ public Result preprocessEnter( } int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' if (comment != null) { - final DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); + DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); if (state != DocstringState.NONE) { insertDocStringStub(editor, comment, state); return Result.Continue; @@ -136,40 +138,42 @@ public Result preprocessEnter( } if (offset > 0 && !(PyTokenTypes.STRING_NODES.contains(element.getNode().getElementType()))) { - final PsiElement prevElement = file.findElementAt(offset - 1); + PsiElement prevElement = file.findElementAt(offset - 1); if (prevElement == element) { return Result.Continue; } } - if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) || element.getNode() - .getElementType() == PyTokenTypes.DOCSTRING) { + if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) + || element.getNode().getElementType() == PyTokenTypes.DOCSTRING) { return Result.Continue; } - final PsiElement prevElement = file.findElementAt(offset - 1); - PyStringLiteralExpression string = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false); + PsiElement prevElement = file.findElementAt(offset - 1); + PyStringLiteralExpression string = + PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false); if (string != null && prevElement != null && PyTokenTypes.STRING_NODES.contains(prevElement.getNode().getElementType()) && string.getTextOffset() < offset && !(element.getNode() instanceof PsiWhiteSpace)) { - final String stringText = element.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); + String stringText = element.getText(); + int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); if (string.getTextOffset() + prefixLength >= offset) { return Result.Continue; } - final String pref = element.getText().substring(0, prefixLength); - final String quote = element.getText().substring(prefixLength, prefixLength + 1); - final boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.create(offset - 1, offset))); - final boolean isEscapedQuote = quote.equals(doc.getText(TextRange.create(offset, offset + 1))) && nextIsBackslash; - final boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.create(offset - 2, offset - 1))) && nextIsBackslash; + String pref = element.getText().substring(0, prefixLength); + String quote = element.getText().substring(prefixLength, prefixLength + 1); + boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.create(offset - 1, offset))); + boolean isEscapedQuote = quote.equals(doc.getText(TextRange.create(offset, offset + 1))) && nextIsBackslash; + boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.create(offset - 2, offset - 1))) && nextIsBackslash; if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) { return Result.Continue; } - final StringBuilder replacementString = new StringBuilder(); + StringBuilder replacementString = new StringBuilder(); myPostprocessShift = prefixLength + quote.length(); + //noinspection unchecked if (PsiTreeUtil.getParentOfType(string, IMPLICIT_WRAP_CLASSES) != null) { replacementString.append(quote).append(pref).append(quote); doc.insertString(offset, replacementString); @@ -197,7 +201,14 @@ public Result preprocessEnter( return checkInsertBackslash(file, caretOffset, dataContext, offset, doc); } - private static Result checkInsertBackslash(PsiFile file, Ref caretOffset, DataContext dataContext, int offset, Document doc) { + @RequiredReadAction + private static Result checkInsertBackslash( + PsiFile file, + SimpleReference caretOffset, + DataContext dataContext, + int offset, + Document doc + ) { boolean autoWrapInProgress = DataManager.getInstance().loadFromDataContext( dataContext, consulo.ide.impl.idea.codeInsight.editorActions.AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY @@ -209,9 +220,10 @@ private static Result checkInsertBackslash(PsiFile file, Ref caretOffse return Result.Continue; } + @RequiredReadAction public static boolean needInsertBackslash(PsiFile file, int offset, boolean autoWrapInProgress) { if (offset > 0) { - final PsiElement beforeCaret = file.findElementAt(offset - 1); + PsiElement beforeCaret = file.findElementAt(offset - 1); if (beforeCaret instanceof PsiWhiteSpace && beforeCaret.getText().indexOf('\\') >= 0) { // we've got a backslash at EOL already, don't need another one return false; @@ -225,6 +237,7 @@ public static boolean needInsertBackslash(PsiFile file, int offset, boolean auto return needInsertBackslash(nodeAtCaret, autoWrapInProgress); } + @RequiredReadAction public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapInProgress) { PsiElement statementBefore = findStatementBeforeCaret(nodeAtCaret); PsiElement statementAfter = findStatementAfterCaret(nodeAtCaret); @@ -251,6 +264,7 @@ public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapI PsiElement wrappableAfter = findWrappable(nodeAtCaret, false); if (!(wrappableBefore instanceof PsiComment)) { while (wrappableBefore != null) { + @SuppressWarnings("unchecked") PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, WRAPPABLE_CLASSES); if (next == null) { break; @@ -260,6 +274,7 @@ public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapI } if (!(wrappableAfter instanceof PsiComment)) { while (wrappableAfter != null) { + @SuppressWarnings("unchecked") PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, WRAPPABLE_CLASSES); if (next == null) { break; @@ -279,10 +294,10 @@ public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapI private static void insertDocStringStub(Editor editor, PsiElement element, DocstringState state) { PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); if (docOwner != null) { - final int caretOffset = editor.getCaretModel().getOffset(); - final Document document = editor.getDocument(); - final String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); - final String docString = PyDocstringGenerator.forDocStringOwner(docOwner) + int caretOffset = editor.getCaretModel().getOffset(); + Document document = editor.getDocument(); + String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); + String docString = PyDocstringGenerator.forDocStringOwner(docOwner) .withInferredParameters(true) .withQuotes(quotes) .forceNewMode() @@ -297,6 +312,7 @@ else if (state == DocstringState.EMPTY) { } @Nullable + @SuppressWarnings("unchecked") private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) { PsiElement wrappable = before ? findBeforeCaret(nodeAtCaret, WRAPPABLE_CLASSES) : findAfterCaret(nodeAtCaret, WRAPPABLE_CLASSES); if (wrappable == null) { @@ -311,15 +327,18 @@ private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) { } @Nullable + @SuppressWarnings("unchecked") private static PsiElement findStatementBeforeCaret(ASTNode node) { return findBeforeCaret(node, PyStatement.class); } @Nullable + @SuppressWarnings("unchecked") private static PsiElement findStatementAfterCaret(ASTNode node) { return findAfterCaret(node, PyStatement.class); } + @SafeVarargs private static PsiElement findBeforeCaret(ASTNode atCaret, Class... classes) { while (atCaret != null) { atCaret = TreeUtil.prevLeaf(atCaret); @@ -330,6 +349,7 @@ private static PsiElement findBeforeCaret(ASTNode atCaret, Class... classes) { while (atCaret != null) { if (atCaret.getElementType() != TokenType.WHITE_SPACE) { @@ -341,6 +361,8 @@ private static PsiElement findAfterCaret(ASTNode atCaret, Class T getNonStrictParentOfType(@Nonnull PsiElement element, @Nonnull Class... classes) { PsiElement run = element; while (run != null) { @@ -358,19 +380,17 @@ private static T getNonStrictParentOfType(@Nonnull PsiEle return null; } + @RequiredReadAction private static boolean inFromImportParentheses(PsiElement statement, int offset) { - if (!(statement instanceof PyFromImportStatement)) { + if (!(statement instanceof PyFromImportStatement fromImportStatement)) { return false; } - PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement; PsiElement leftParen = fromImportStatement.getLeftParen(); - if (leftParen != null && offset >= leftParen.getTextRange().getEndOffset()) { - return true; - } - return false; + return leftParen != null && offset >= leftParen.getTextRange().getEndOffset(); } @Override + @RequiredUIAccess public Result postProcessEnter(@Nonnull PsiFile file, @Nonnull Editor editor, @Nonnull DataContext dataContext) { if (!(file instanceof PyFile)) { return Result.Continue; @@ -384,21 +404,22 @@ public Result postProcessEnter(@Nonnull PsiFile file, @Nonnull Editor editor, @N return super.postProcessEnter(file, editor, dataContext); } + @RequiredUIAccess private static void addGoogleDocStringSectionIndent(@Nonnull PsiFile file, @Nonnull Editor editor, int offset) { - final Document document = editor.getDocument(); + Document document = editor.getDocument(); PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); - final PsiElement element = file.findElementAt(offset); + PsiElement element = file.findElementAt(offset); if (element != null) { // Insert additional indentation after section header in Google code style docstrings - final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); if (pyString != null) { - final String docStringText = pyString.getText(); - final DocStringFormat format = DocStringUtil.guessDocStringFormat(docStringText, pyString); + String docStringText = pyString.getText(); + DocStringFormat format = DocStringUtil.guessDocStringFormat(docStringText, pyString); if (format == DocStringFormat.GOOGLE && offset + 1 < document.getTextLength()) { - final int lineNum = document.getLineNumber(offset); - final TextRange lineRange = + int lineNum = document.getLineNumber(offset); + TextRange lineRange = TextRange.create(document.getLineStartOffset(lineNum - 1), document.getLineEndOffset(lineNum - 1)); - final Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER.matcher(document.getText(lineRange)); + Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER.matcher(document.getText(lineRange)); if (matcher.matches() && SectionBasedDocString.isValidSectionTitle(matcher.group(1))) { document.insertString(offset, GoogleCodeStyleDocStringBuilder.getDefaultSectionIndent(file.getProject())); editor.getCaretModel().moveCaretRelatively(2, 0, false, false, false); @@ -415,22 +436,23 @@ enum DocstringState { } @Nonnull + @RequiredReadAction public static DocstringState canGenerateDocstring(@Nonnull PsiElement element, int firstQuoteOffset, @Nonnull Document document) { if (firstQuoteOffset < 0 || firstQuoteOffset > document.getTextLength() - 3) { return DocstringState.NONE; } - final String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); + String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); if (!quotes.equals("\"\"\"") && !quotes.equals("'''")) { return DocstringState.NONE; } - final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); if (pyString != null) { String nodeText = element.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); + int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); nodeText = nodeText.substring(prefixLength); - final String literalText = pyString.getText(); + String literalText = pyString.getText(); if (literalText.endsWith(nodeText) && nodeText.startsWith(quotes)) { if (firstQuoteOffset == pyString.getTextOffset() + prefixLength) { PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); @@ -450,10 +472,10 @@ public static DocstringState canGenerateDocstring(@Nonnull PsiElement element, i } // Sometimes if incomplete docstring is followed by another declaration with a docstring, it might be treated // as complete docstring, because we can't understand that closing quotes actually belong to another docstring. - final String docstringIndent = PyIndentUtil.getLineIndent(document, document.getLineNumber(firstQuoteOffset)); + String docstringIndent = PyIndentUtil.getLineIndent(document, document.getLineNumber(firstQuoteOffset)); for (String line : LineTokenizer.tokenizeIntoList(nodeText, false)) { - final String lineIndent = PyIndentUtil.getLineIndent(line); - final String lineContent = line.substring(lineIndent.length()); + String lineIndent = PyIndentUtil.getLineIndent(line); + String lineContent = line.substring(lineIndent.length()); if ((lineContent.startsWith("def ") || lineContent.startsWith("class ")) && docstringIndent.length() > lineIndent.length() && docstringIndent.startsWith(lineIndent)) { return DocstringState.INCOMPLETE; diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java index 48e960c0..a585aa44 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java @@ -17,8 +17,8 @@ import com.jetbrains.python.PythonFileType; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.component.extension.Extensions; import consulo.execution.action.ConfigurationContext; import consulo.execution.action.ConfigurationFromContext; import consulo.execution.action.Location; @@ -26,11 +26,9 @@ import consulo.language.file.light.LightVirtualFile; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; -import consulo.language.util.ModuleUtilCore; import consulo.module.Module; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -46,31 +44,31 @@ public PythonRunConfigurationProducer() { } @Override + @RequiredReadAction protected boolean setupConfigurationFromContext( PythonRunConfiguration configuration, ConfigurationContext context, - Ref sourceElement + SimpleReference sourceElement ) { - - final Location location = context.getLocation(); + Location location = context.getLocation(); if (location == null) { return false; } - final PsiFile script = location.getPsiElement().getContainingFile(); + PsiFile script = location.getPsiElement().getContainingFile(); if (!isAvailable(location, script)) { return false; } - final VirtualFile vFile = script.getVirtualFile(); + VirtualFile vFile = script.getVirtualFile(); if (vFile == null) { return false; } configuration.setScriptName(vFile.getPath()); - final VirtualFile parent = vFile.getParent(); + VirtualFile parent = vFile.getParent(); if (parent != null) { configuration.setWorkingDirectory(parent.getPath()); } - final Module module = ModuleUtilCore.findModuleForPsiElement(script); + Module module = script.getModule(); if (module != null) { configuration.setUseModuleSdk(true); configuration.setModule(module); @@ -80,35 +78,37 @@ protected boolean setupConfigurationFromContext( } @Override + @RequiredReadAction public boolean isConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context) { - final Location location = context.getLocation(); + Location location = context.getLocation(); if (location == null) { return false; } - final PsiFile script = location.getPsiElement().getContainingFile(); + PsiFile script = location.getPsiElement().getContainingFile(); if (!isAvailable(location, script)) { return false; } - final VirtualFile virtualFile = script.getVirtualFile(); + VirtualFile virtualFile = script.getVirtualFile(); if (virtualFile == null) { return false; } if (virtualFile instanceof LightVirtualFile) { return false; } - final String workingDirectory = configuration.getWorkingDirectory(); - final String scriptName = configuration.getScriptName(); - final String path = virtualFile.getPath(); + String workingDirectory = configuration.getWorkingDirectory(); + String scriptName = configuration.getScriptName(); + String path = virtualFile.getPath(); return scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); } - private static boolean isAvailable(@Nonnull final Location location, @Nullable final PsiFile script) { + @RequiredReadAction + private static boolean isAvailable(@Nonnull Location location, @Nullable PsiFile script) { if (script == null || script.getFileType() != PythonFileType.INSTANCE) { return false; } - final Module module = ModuleUtilCore.findModuleForPsiElement(script); + Module module = script.getModule(); if (module != null) { - for (RunnableScriptFilter f : Extensions.getExtensions(RunnableScriptFilter.EP_NAME)) { + for (RunnableScriptFilter f : RunnableScriptFilter.EP_NAME.getExtensions()) { // Configuration producers always called by user if (f.isRunnableScript(script, module, location, TypeEvalContext.userInitiated(location.getProject(), null))) { return false; diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java index ef7011a3..38e2d616 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java @@ -15,96 +15,87 @@ */ package com.jetbrains.python.impl.testing; -import java.io.File; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; - -import jakarta.annotation.Nonnull; - -import consulo.python.module.extension.PyModuleExtension; -import org.jetbrains.annotations.NonNls; - -import jakarta.annotation.Nullable; import com.google.common.collect.Sets; -import consulo.execution.action.Location; +import com.jetbrains.python.impl.psi.PyUtil; +import com.jetbrains.python.impl.run.PythonRunConfigurationProducer; +import com.jetbrains.python.impl.testing.unittest.PythonUnitTestRunConfiguration; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.execution.action.ConfigurationContext; import consulo.execution.action.ConfigurationFromContext; +import consulo.execution.action.Location; import consulo.execution.action.RunConfigurationProducer; import consulo.execution.configuration.ConfigurationFactory; import consulo.execution.configuration.RunConfiguration; +import consulo.language.psi.*; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import consulo.module.ModuleManager; -import consulo.language.util.ModuleUtilCore; -import consulo.project.Project; import consulo.module.content.ProjectRootManager; -import consulo.util.lang.ref.Ref; +import consulo.project.Project; +import consulo.python.module.extension.PyModuleExtension; import consulo.util.lang.StringUtil; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; -import consulo.language.psi.PsiDirectory; -import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiFile; -import consulo.language.psi.PsiFileSystemItem; -import consulo.language.psi.PsiWhiteSpace; -import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyStatement; -import com.jetbrains.python.impl.psi.PyUtil; -import com.jetbrains.python.psi.types.TypeEvalContext; -import com.jetbrains.python.impl.run.PythonRunConfigurationProducer; -import com.jetbrains.python.impl.testing.unittest.PythonUnitTestRunConfiguration; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; /** * User: ktisha */ abstract public class PythonTestConfigurationProducer extends RunConfigurationProducer { - public PythonTestConfigurationProducer(final ConfigurationFactory configurationFactory) { + public PythonTestConfigurationProducer(ConfigurationFactory configurationFactory) { super(configurationFactory); } @Override + @RequiredReadAction public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { - final Location location = context.getLocation(); + Location location = context.getLocation(); if (location == null || !isAvailable(location)) { return false; } - final PsiElement element = location.getPsiElement(); - final PsiFileSystemItem file = element.getContainingFile(); + PsiElement element = location.getPsiElement(); + PsiFileSystemItem file = element.getContainingFile(); if (file == null) { return false; } - final VirtualFile virtualFile = element instanceof PsiDirectory ? ((PsiDirectory)element).getVirtualFile() : file.getVirtualFile(); + VirtualFile virtualFile = element instanceof PsiDirectory directory ? directory.getVirtualFile() : file.getVirtualFile(); if (virtualFile == null) { return false; } - final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class); + PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); + PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class); - final AbstractPythonTestRunConfiguration.TestType confType = configuration.getTestType(); - final String workingDirectory = configuration.getWorkingDirectory(); + AbstractPythonTestRunConfiguration.TestType confType = configuration.getTestType(); + String workingDirectory = configuration.getWorkingDirectory(); - if (element instanceof PsiDirectory) { - final String path = ((PsiDirectory)element).getVirtualFile().getPath(); + if (element instanceof PsiDirectory directory) { + String path = directory.getVirtualFile().getPath(); return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER && path.equals(configuration.getFolderName()) || path.equals(new File(workingDirectory, configuration.getFolderName()).getAbsolutePath()); } - final String scriptName = configuration.getScriptName(); - final String path = virtualFile.getPath(); - final boolean isTestFileEquals = scriptName.equals(path) - || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); + String scriptName = configuration.getScriptName(); + String path = virtualFile.getPath(); + boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); if (pyFunction != null) { - final String methodName = configuration.getMethodName(); + String methodName = configuration.getMethodName(); if (pyFunction.getContainingClass() == null) { return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION && methodName.equals(pyFunction.getName()) && isTestFileEquals; } else { - final String className = configuration.getClassName(); + String className = configuration.getClassName(); return confType == AbstractPythonTestRunConfiguration.TestType.TEST_METHOD && methodName.equals(pyFunction.getName()) @@ -112,7 +103,7 @@ public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration con } } if (pyClass != null) { - final String className = configuration.getClassName(); + String className = configuration.getClassName(); return confType == AbstractPythonTestRunConfiguration.TestType.TEST_CLASS && className.equals(pyClass.getName()) && isTestFileEquals; } @@ -120,15 +111,16 @@ public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration con } @Override + @RequiredWriteAction protected boolean setupConfigurationFromContext( AbstractPythonTestRunConfiguration configuration, ConfigurationContext context, - Ref sourceElement + SimpleReference sourceElement ) { if (context == null) { return false; } - final Location location = context.getLocation(); + Location location = context.getLocation(); if (location == null || !isAvailable(location)) { return false; } @@ -140,20 +132,20 @@ protected boolean setupConfigurationFromContext( if (PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) { return false; } - final Module module = location.getModule(); + Module module = location.getModule(); if (!isPythonModule(module)) { return false; } - if (element instanceof PsiDirectory) { - return setupConfigurationFromFolder((PsiDirectory)element, configuration); + if (element instanceof PsiDirectory directory) { + return setupConfigurationFromFolder(directory, configuration); } - final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); + PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); if (pyFunction != null && isTestFunction(pyFunction, configuration)) { return setupConfigurationFromFunction(pyFunction, configuration); } - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); if (pyClass != null && isTestClass( pyClass, configuration, @@ -164,23 +156,17 @@ protected boolean setupConfigurationFromContext( if (element == null) { return false; } - final PsiFile file = element.getContainingFile(); - if (file instanceof PyFile && isTestFile((PyFile)file)) { - return setupConfigurationFromFile((PyFile)file, configuration); - } - - return false; + PsiFile file = element.getContainingFile(); + return file instanceof PyFile pyFile && isTestFile(pyFile) && setupConfigurationFromFile(pyFile, configuration); } - private boolean setupConfigurationFromFolder( - @Nonnull final PsiDirectory element, - @Nonnull final AbstractPythonTestRunConfiguration configuration - ) { - final VirtualFile virtualFile = element.getVirtualFile(); + @RequiredWriteAction + private boolean setupConfigurationFromFolder(@Nonnull PsiDirectory element, @Nonnull AbstractPythonTestRunConfiguration configuration) { + VirtualFile virtualFile = element.getVirtualFile(); if (!isTestFolder(virtualFile, element.getProject())) { return false; } - final String path = virtualFile.getPath(); + String path = virtualFile.getPath(); configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER); configuration.setFolderName(path); @@ -190,16 +176,18 @@ private boolean setupConfigurationFromFolder( return true; } - private static void setModuleSdk(@Nonnull final PsiElement element, @Nonnull final AbstractPythonTestRunConfiguration configuration) { + @RequiredWriteAction + private static void setModuleSdk(@Nonnull PsiElement element, @Nonnull AbstractPythonTestRunConfiguration configuration) { configuration.setUseModuleSdk(true); - configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); + configuration.setModule(element.getModule()); } + @RequiredWriteAction protected boolean setupConfigurationFromFunction( - @Nonnull final PyFunction pyFunction, - @Nonnull final AbstractPythonTestRunConfiguration configuration + @Nonnull PyFunction pyFunction, + @Nonnull AbstractPythonTestRunConfiguration configuration ) { - final PyClass containingClass = pyFunction.getContainingClass(); + PyClass containingClass = pyFunction.getContainingClass(); configuration.setMethodName(pyFunction.getName()); if (containingClass != null) { @@ -212,36 +200,39 @@ protected boolean setupConfigurationFromFunction( return setupConfigurationScript(configuration, pyFunction); } + @RequiredWriteAction protected boolean setupConfigurationFromClass( - @Nonnull final PyClass pyClass, - @Nonnull final AbstractPythonTestRunConfiguration configuration + @Nonnull PyClass pyClass, + @Nonnull AbstractPythonTestRunConfiguration configuration ) { configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_CLASS); configuration.setClassName(pyClass.getName()); return setupConfigurationScript(configuration, pyClass); } + @RequiredWriteAction protected boolean setupConfigurationFromFile( - @Nonnull final PyFile pyFile, - @Nonnull final AbstractPythonTestRunConfiguration configuration + @Nonnull PyFile pyFile, + @Nonnull AbstractPythonTestRunConfiguration configuration ) { configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT); return setupConfigurationScript(configuration, pyFile); } + @RequiredWriteAction protected static boolean setupConfigurationScript( - @Nonnull final AbstractPythonTestRunConfiguration cfg, - @Nonnull final PyElement element + @Nonnull AbstractPythonTestRunConfiguration cfg, + @Nonnull PyElement element ) { - final PyFile containingFile = PyUtil.getContainingPyFile(element); + PyFile containingFile = PyUtil.getContainingPyFile(element); if (containingFile == null) { return false; } - final VirtualFile vFile = containingFile.getVirtualFile(); + VirtualFile vFile = containingFile.getVirtualFile(); if (vFile == null) { return false; } - final VirtualFile parent = vFile.getParent(); + VirtualFile parent = vFile.getParent(); if (parent == null) { return false; } @@ -256,10 +247,11 @@ protected static boolean setupConfigurationScript( return true; } - protected boolean isTestFolder(@Nonnull final VirtualFile virtualFile, @Nonnull final Project project) { - @NonNls final String name = virtualFile.getName(); - final HashSet roots = Sets.newHashSet(); - final Module[] modules = ModuleManager.getInstance(project).getModules(); + @RequiredReadAction + protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) { + String name = virtualFile.getName(); + HashSet roots = Sets.newHashSet(); + Module[] modules = ModuleManager.getInstance(project).getModules(); for (Module module : modules) { roots.addAll(PyUtil.getSourceRoots(module)); } @@ -267,51 +259,44 @@ protected boolean isTestFolder(@Nonnull final VirtualFile virtualFile, @Nonnull return name.toLowerCase().contains("test") || roots.contains(virtualFile); } - protected boolean isAvailable(@Nonnull final Location location) { + protected boolean isAvailable(@Nonnull Location location) { return false; } protected boolean isTestClass( - @Nonnull final PyClass pyClass, - @Nullable final AbstractPythonTestRunConfiguration configuration, - @Nullable final TypeEvalContext context + @Nonnull PyClass pyClass, + @Nullable AbstractPythonTestRunConfiguration configuration, + @Nullable TypeEvalContext context ) { return PythonUnitTestUtil.isTestCaseClass(pyClass, context); } protected boolean isTestFunction( - @Nonnull final PyFunction pyFunction, - @Nullable final AbstractPythonTestRunConfiguration configuration + @Nonnull PyFunction pyFunction, + @Nullable AbstractPythonTestRunConfiguration configuration ) { return PythonUnitTestUtil.isTestCaseFunction(pyFunction); } - protected boolean isTestFile(@Nonnull final PyFile file) { - final List testCases = getTestCaseClassesFromFile(file); - if (testCases.isEmpty()) { - return false; - } - return true; + protected boolean isTestFile(@Nonnull PyFile file) { + List testCases = getTestCaseClassesFromFile(file); + return !testCases.isEmpty(); } protected static boolean isPythonModule(Module module) { - if (module == null) { - return false; - } - return ModuleUtilCore.getExtension(module, PyModuleExtension.class) != null; + return module != null && ModuleUtilCore.getExtension(module, PyModuleExtension.class) != null; } - protected List getTestCaseClassesFromFile(@Nonnull final PyFile pyFile) { + protected List getTestCaseClassesFromFile(@Nonnull PyFile pyFile) { return PythonUnitTestUtil.getTestCaseClassesFromFile(pyFile, TypeEvalContext.userInitiated(pyFile.getProject(), pyFile)); } @Override public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) { - final RunConfiguration configuration = self.getConfiguration(); - if (configuration instanceof PythonUnitTestRunConfiguration - && ((PythonUnitTestRunConfiguration)configuration).getTestType() == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER) { - return true; - } - return other.isProducedBy(PythonTestConfigurationProducer.class) || other.isProducedBy(PythonRunConfigurationProducer.class); + RunConfiguration configuration = self.getConfiguration(); + return configuration instanceof PythonUnitTestRunConfiguration unitTestRunConfiguration + && unitTestRunConfiguration.getTestType() == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER + || other.isProducedBy(PythonTestConfigurationProducer.class) + || other.isProducedBy(PythonRunConfigurationProducer.class); } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java index cc076792..69dd7ea8 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java @@ -21,6 +21,7 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyClassLikeType; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.content.bundle.Sdk; import consulo.execution.action.Location; @@ -42,14 +43,16 @@ public PythonAtTestConfigurationProducer() { super(PythonTestConfigurationType.getInstance().PY_ATTEST_FACTORY); } - protected boolean isAvailable(@Nonnull final Location location) { - final PsiElement element = location.getPsiElement(); + @Override + @RequiredReadAction + protected boolean isAvailable(@Nonnull Location location) { + PsiElement element = location.getPsiElement(); Module module = location.getModule(); if (module == null) { module = ModuleUtilCore.findModuleForPsiElement(element); } - final Sdk sdk = PythonSdkType.findPythonSdk(module); + Sdk sdk = PythonSdkType.findPythonSdk(module); return module != null && TestRunnerService.getInstance(module) .getProjectConfiguration() .equals(PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME) && sdk != null; @@ -57,9 +60,9 @@ protected boolean isAvailable(@Nonnull final Location location) { @Override protected boolean isTestClass( - @Nonnull final PyClass pyClass, - @Nullable final AbstractPythonTestRunConfiguration configuration, - @Nullable final TypeEvalContext context + @Nonnull PyClass pyClass, + @Nullable AbstractPythonTestRunConfiguration configuration, + @Nullable TypeEvalContext context ) { for (PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) { if (type != null && "TestBase".equals(type.getName()) && hasTestFunction(pyClass)) { @@ -69,7 +72,7 @@ protected boolean isTestClass( return false; } - private static boolean hasTestFunction(@Nonnull final PyClass pyClass) { + private static boolean hasTestFunction(@Nonnull PyClass pyClass) { PyFunction[] methods = pyClass.getMethods(); for (PyFunction function : methods) { PyDecoratorList decorators = function.getDecoratorList(); @@ -85,10 +88,8 @@ private static boolean hasTestFunction(@Nonnull final PyClass pyClass) { return false; } - protected boolean isTestFunction( - @Nonnull final PyFunction pyFunction, - @Nullable final AbstractPythonTestRunConfiguration configuration - ) { + @Override + protected boolean isTestFunction(@Nonnull PyFunction pyFunction, @Nullable AbstractPythonTestRunConfiguration configuration) { PyDecoratorList decorators = pyFunction.getDecoratorList(); if (decorators == null) { return false; @@ -101,7 +102,8 @@ protected boolean isTestFunction( return false; } - protected List getTestCaseClassesFromFile(@Nonnull final PyFile file) { + @Override + protected List getTestCaseClassesFromFile(@Nonnull PyFile file) { List result = Lists.newArrayList(); for (PyClass cls : file.getTopLevelClasses()) { if (isTestClass(cls, null, null)) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java index 7719ec95..51045226 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java @@ -27,6 +27,7 @@ import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.execution.action.Location; import consulo.language.psi.PsiElement; @@ -49,8 +50,8 @@ public PythonDocTestConfigurationProducer() { @Override protected boolean isTestFunction( - @Nonnull final PyFunction pyFunction, - @Nullable final AbstractPythonTestRunConfiguration configuration + @Nonnull PyFunction pyFunction, + @Nullable AbstractPythonTestRunConfiguration configuration ) { return PythonDocTestUtil.isDocTestFunction(pyFunction); } @@ -58,26 +59,27 @@ protected boolean isTestFunction( @Override protected boolean isTestClass( @Nonnull PyClass pyClass, - @Nullable final AbstractPythonTestRunConfiguration configuration, - @Nullable final TypeEvalContext context + @Nullable AbstractPythonTestRunConfiguration configuration, + @Nullable TypeEvalContext context ) { return PythonDocTestUtil.isDocTestClass(pyClass); } @Override protected boolean isTestFile(@Nonnull PyFile file) { - final List testCases = PythonDocTestUtil.getDocTestCasesFromFile(file); + List testCases = PythonDocTestUtil.getDocTestCasesFromFile(file); return !testCases.isEmpty(); } - protected boolean isAvailable(@Nonnull final Location location) { - final Module module = location.getModule(); + @Override + protected boolean isAvailable(@Nonnull Location location) { + Module module = location.getModule(); if (!isPythonModule(module)) { return false; } - final PsiElement element = location.getPsiElement(); + PsiElement element = location.getPsiElement(); if (element instanceof PsiFile) { - final PyDocTestVisitor visitor = new PyDocTestVisitor(); + PyDocTestVisitor visitor = new PyDocTestVisitor(); element.accept(visitor); return visitor.hasTests; } @@ -90,15 +92,16 @@ private static class PyDocTestVisitor extends PsiRecursiveElementVisitor { boolean hasTests = false; @Override + @RequiredReadAction public void visitFile(PsiFile node) { - if (node instanceof PyFile) { - List testClasses = PythonDocTestUtil.getDocTestCasesFromFile((PyFile)node); + if (node instanceof PyFile pyFile) { + List testClasses = PythonDocTestUtil.getDocTestCasesFromFile(pyFile); if (!testClasses.isEmpty()) { hasTests = true; } } else { - final String text = node.getText(); + String text = node.getText(); if (PythonDocTestUtil.hasExample(text)) { hasTests = true; } @@ -107,6 +110,7 @@ public void visitFile(PsiFile node) { } @Override + @RequiredReadAction protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) { return false; } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java index 0fb2b959..1e398d04 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java @@ -15,16 +15,17 @@ */ package com.jetbrains.python.impl.testing.pytest; +import com.jetbrains.python.impl.packaging.PyPackageUtil; +import com.jetbrains.python.impl.sdk.PythonSdkType; import com.jetbrains.python.impl.testing.*; import com.jetbrains.python.packaging.PyPackage; import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.impl.packaging.PyPackageUtil; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStatement; import com.jetbrains.python.psi.types.TypeEvalContext; -import com.jetbrains.python.impl.sdk.PythonSdkType; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.content.bundle.Sdk; import consulo.execution.action.ConfigurationContext; @@ -33,13 +34,11 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFileSystemItem; import consulo.language.psi.util.PsiTreeUtil; -import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import consulo.repository.ui.PackageVersionComparator; import consulo.util.lang.StringUtil; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -53,13 +52,14 @@ public PyTestConfigurationProducer() { } @Override + @RequiredReadAction protected boolean setupConfigurationFromContext( AbstractPythonTestRunConfiguration configuration, ConfigurationContext context, - Ref sourceElement + SimpleReference sourceElement ) { - final PsiElement element = sourceElement.get(); - final Module module = ModuleUtilCore.findModuleForPsiElement(element); + PsiElement element = sourceElement.get(); + Module module = element.getModule(); if (!(configuration instanceof PyTestRunConfiguration)) { return false; } @@ -70,17 +70,17 @@ protected boolean setupConfigurationFromContext( return false; } - final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); + PsiFileSystemItem file = element instanceof PsiDirectory directory ? directory : element.getContainingFile(); if (file == null) { return false; } - final VirtualFile virtualFile = file.getVirtualFile(); + VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) { return false; } if (file instanceof PyFile || file instanceof PsiDirectory) { - final List testCases = + List testCases = PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); if (testCases.isEmpty()) { return false; @@ -90,16 +90,16 @@ protected boolean setupConfigurationFromContext( return false; } - final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); + Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); if (sdk == null) { return false; } configuration.setUseModuleSdk(true); - configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); + configuration.setModule(element.getModule()); ((PyTestRunConfiguration)configuration).setTestToRun(virtualFile.getPath()); - final String keywords = getKeywords(element, sdk); + String keywords = getKeywords(element, sdk); if (keywords != null) { ((PyTestRunConfiguration)configuration).useKeyword(true); ((PyTestRunConfiguration)configuration).setKeywords(keywords); @@ -112,15 +112,16 @@ protected boolean setupConfigurationFromContext( } @Nullable - private static String getKeywords(@Nonnull final PsiElement element, @Nonnull final Sdk sdk) { - final PyFunction pyFunction = findTestFunction(element); - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + @RequiredReadAction + private static String getKeywords(@Nonnull PsiElement element, @Nonnull Sdk sdk) { + PyFunction pyFunction = findTestFunction(element); + PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); String keywords = null; if (pyFunction != null) { keywords = pyFunction.getName(); if (pyClass != null) { - final List packages = PyPackageManager.getInstance(sdk).getPackages(); - final PyPackage pytestPackage = packages != null ? PyPackageUtil.findPackage(packages, "pytest") : null; + List packages = PyPackageManager.getInstance(sdk).getPackages(); + PyPackage pytestPackage = packages != null ? PyPackageUtil.findPackage(packages, "pytest") : null; if (pytestPackage != null && PackageVersionComparator.VERSION_COMPARATOR.compare(pytestPackage.getVersion(), "2.3.3") >= 0) { keywords = pyClass.getName() + " and " + keywords; @@ -137,10 +138,11 @@ else if (pyClass != null) { } @Nullable + @RequiredReadAction private static PyFunction findTestFunction(PsiElement element) { - final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); if (function != null) { - final String name = function.getName(); + String name = function.getName(); if (name != null && name.startsWith("test")) { return function; } @@ -149,27 +151,28 @@ private static PyFunction findTestFunction(PsiElement element) { } @Override + @RequiredReadAction public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { - final Location location = context.getLocation(); + Location location = context.getLocation(); if (location == null) { return false; } if (!(configuration instanceof PyTestRunConfiguration)) { return false; } - final PsiElement element = location.getPsiElement(); + PsiElement element = location.getPsiElement(); - final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); + PsiFileSystemItem file = element instanceof PsiDirectory directory ? directory : element.getContainingFile(); if (file == null) { return false; } - final VirtualFile virtualFile = file.getVirtualFile(); + VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) { return false; } if (file instanceof PyFile || file instanceof PsiDirectory) { - final List testCases = + List testCases = PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); if (testCases.isEmpty()) { return false; @@ -179,17 +182,17 @@ public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration con return false; } - final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); + Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); if (sdk == null) { return false; } - final String keywords = getKeywords(element, sdk); - final String scriptName = ((PyTestRunConfiguration)configuration).getTestToRun(); - final String workingDirectory = configuration.getWorkingDirectory(); - final String path = virtualFile.getPath(); - final boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); + String keywords = getKeywords(element, sdk); + String scriptName = ((PyTestRunConfiguration)configuration).getTestToRun(); + String workingDirectory = configuration.getWorkingDirectory(); + String path = virtualFile.getPath(); + boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - final String configurationKeywords = ((PyTestRunConfiguration)configuration).getKeywords(); + String configurationKeywords = ((PyTestRunConfiguration)configuration).getKeywords(); return isTestFileEquals && (configurationKeywords.equals(keywords) || StringUtil.isEmptyOrSpaces(((PyTestRunConfiguration)configuration).getKeywords()) && keywords == null); } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java index c1c7ab9e..3a252d93 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java @@ -27,10 +27,10 @@ import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStatement; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.execution.action.Location; import consulo.language.psi.PsiElement; -import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import jakarta.annotation.Nonnull; @@ -44,16 +44,14 @@ public PythonUnitTestConfigurationProducer() { super(PythonTestConfigurationType.getInstance().PY_UNITTEST_FACTORY); } + @Override + @RequiredReadAction protected boolean isAvailable(@Nonnull final Location location) { PsiElement element = location.getPsiElement(); - final Module module = ModuleUtilCore.findModuleForPsiElement(element); - if (module == null) { - return false; - } - if ((TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME))) { - return true; - } - return false; + final Module module = element.getModule(); + return module != null && TestRunnerService.getInstance(module) + .getProjectConfiguration() + .equals(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME); } @Override @@ -62,7 +60,8 @@ protected boolean isTestFunction( @Nullable final AbstractPythonTestRunConfiguration configuration ) { final boolean isTestFunction = super.isTestFunction(pyFunction, configuration); - return isTestFunction || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration)configuration).isPureUnittest()); + return isTestFunction + || (configuration instanceof PythonUnitTestRunConfiguration unitTestRunConfig && !unitTestRunConfig.isPureUnittest()); } @Override @@ -72,18 +71,17 @@ protected boolean isTestClass( TypeEvalContext context ) { final boolean isTestClass = super.isTestClass(pyClass, configuration, context); - return isTestClass || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration)configuration).isPureUnittest()); + return isTestClass + || (configuration instanceof PythonUnitTestRunConfiguration unitTestRunConfig && !unitTestRunConfig.isPureUnittest()); } @Override + @RequiredReadAction protected boolean isTestFile(@Nonnull final PyFile file) { if (PyNames.SETUP_DOT_PY.equals(file.getName())) { return true; } final List testCases = getTestCaseClassesFromFile(file); - if (testCases.isEmpty()) { - return false; - } - return true; + return !testCases.isEmpty(); } } \ No newline at end of file