From c080a8bfdebdd319bcad1b01403d94efa735d0ed Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 19 Feb 2026 22:28:12 +0300 Subject: [PATCH] Replacing deprecated consulo.ide.impl.idea.openapi.util.text.StringUtil with consulo.util.lang.StringUtil. Some refactoring/localizing. --- .../config/psi/impl/BuildoutCfgValueLine.java | 46 +- .../imports/ImportCandidateHolder.java | 2 +- ...yStringConcatenationToFormatIntention.java | 16 +- .../override/PyOverrideImplementUtil.java | 535 +++---- .../docstrings/DocStringUtil.java | 2 +- .../quickfix/AddCallSuperQuickFix.java | 9 +- .../jetbrains/python/impl/run/PythonTask.java | 9 +- .../jetbrains/python/psi/impl/PyPsiUtils.java | 1378 ++++++++--------- 8 files changed, 962 insertions(+), 1035 deletions(-) diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/buildout/config/psi/impl/BuildoutCfgValueLine.java b/python-impl/src/main/java/com/jetbrains/python/impl/buildout/config/psi/impl/BuildoutCfgValueLine.java index 4c8bceae..ff489550 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/buildout/config/psi/impl/BuildoutCfgValueLine.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/buildout/config/psi/impl/BuildoutCfgValueLine.java @@ -16,42 +16,42 @@ package com.jetbrains.python.impl.buildout.config.psi.impl; -import com.google.common.collect.Lists; import com.jetbrains.python.impl.buildout.config.psi.BuildoutPsiUtil; import com.jetbrains.python.impl.buildout.config.ref.BuildoutPartReference; -import consulo.ide.impl.idea.openapi.util.text.StringUtil; import consulo.language.ast.ASTNode; import consulo.language.psi.PsiReference; import consulo.util.lang.Pair; - +import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; + +import java.util.ArrayList; import java.util.List; /** * @author traff */ public class BuildoutCfgValueLine extends BuildoutCfgPsiElement { - public BuildoutCfgValueLine(@Nonnull final ASTNode node) { - super(node); - } - - @Override - public String toString() { - return "BuildoutCfgValue:" + getNode().getElementType().toString(); - } + public BuildoutCfgValueLine(@Nonnull final ASTNode node) { + super(node); + } - @Nonnull - @Override - public PsiReference[] getReferences() { - if (BuildoutPsiUtil.isInBuildoutSection(this) && BuildoutPsiUtil.isAssignedTo(this, "parts")) { - List refs = Lists.newArrayList(); - List> names = StringUtil.getWordsWithOffset(getText()); - for (Pair name : names) { - refs.add(new BuildoutPartReference(this, name.getFirst(), name.getSecond())); - } - return refs.toArray(new BuildoutPartReference[refs.size()]); + @Override + public String toString() { + return "BuildoutCfgValue:" + getNode().getElementType().toString(); } - return PsiReference.EMPTY_ARRAY; - } + @Nonnull + @Override + public PsiReference[] getReferences() { + if (BuildoutPsiUtil.isInBuildoutSection(this) && BuildoutPsiUtil.isAssignedTo(this, "parts")) { + List refs = new ArrayList<>(); + List> names = StringUtil.getWordsWithOffset(getText()); + for (Pair name : names) { + refs.add(new BuildoutPartReference(this, name.getFirst(), name.getSecond())); + } + return refs.toArray(new BuildoutPartReference[refs.size()]); + } + + return PsiReference.EMPTY_ARRAY; + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/ImportCandidateHolder.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/ImportCandidateHolder.java index 501f1ba5..6265a442 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/ImportCandidateHolder.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/ImportCandidateHolder.java @@ -151,7 +151,7 @@ else if (myImportable instanceof PyClass) { ContainerUtil.mapNotNull(((PyClass)myImportable).getSuperClasses(null), cls -> PyUtil.isObjectClass(cls) ? null : cls.getName()); if (!supers.isEmpty()) { sb.append("("); - consulo.ide.impl.idea.openapi.util.text.StringUtil.join(supers, ", ", sb); + StringUtil.join(supers, ", ", sb); sb.append(")"); } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyStringConcatenationToFormatIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyStringConcatenationToFormatIntention.java index 329c76b3..3cb43b92 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyStringConcatenationToFormatIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyStringConcatenationToFormatIntention.java @@ -23,14 +23,15 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; -import consulo.ide.impl.idea.util.NotNullFunction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; import consulo.project.Project; import consulo.python.impl.localize.PyLocalize; +import consulo.util.lang.Couple; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; @@ -38,6 +39,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.function.Function; /** * @author Alexey.Ivanov @@ -126,6 +128,8 @@ private static Collection getOperators(@Nonnull PyBinaryExpressio return res; } + @Override + @RequiredWriteAction public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { PsiElement element = PsiTreeUtil.getTopmostParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyBinaryExpression.class); @@ -136,10 +140,10 @@ public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) thro final LanguageLevel languageLevel = LanguageLevel.forElement(element); final boolean useFormatMethod = languageLevel.isAtLeast(LanguageLevel.PYTHON27); - NotNullFunction escaper = consulo.ide.impl.idea.openapi.util.text.StringUtil.escaper(false, "\"\'\\"); + Function escaper = StringUtil.escaper(false, "\"\'\\"); StringBuilder stringLiteral = new StringBuilder(); List parameters = new ArrayList<>(); - Pair quotes = Pair.create("\"", "\""); + Pair quotes = Couple.of("\"", "\""); boolean quotesDetected = false; final TypeEvalContext context = TypeEvalContext.userInitiated(file.getProject(), file); int paramCount = 0; @@ -147,7 +151,7 @@ public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) thro final PyClassTypeImpl unicodeType = PyBuiltinCache.getInstance(element).getObjectType("unicode"); for (PyExpression expression : getSimpleExpressions((PyBinaryExpression) element)) { - if (expression instanceof PyStringLiteralExpression) { + if (expression instanceof PyStringLiteralExpression stringLiteral1) { final PyType type = context.getType(expression); if (type != null && type.equals(unicodeType)) { isUnicode = true; @@ -156,7 +160,7 @@ public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) thro quotes = PyStringLiteralUtil.getQuotes(expression.getText()); quotesDetected = true; } - String value = ((PyStringLiteralExpression) expression).getStringValue(); + String value = stringLiteral1.getStringValue(); if (!useFormatMethod) { value = value.replace("%", "%%"); } @@ -169,7 +173,7 @@ public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) thro } } if (quotes == null) { - quotes = Pair.create("\"", "\""); + quotes = Couple.of("\"", "\""); } stringLiteral.insert(0, quotes.getFirst()); if (isUnicode && !quotes.getFirst().toLowerCase().contains("u")) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/override/PyOverrideImplementUtil.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/override/PyOverrideImplementUtil.java index 28564e02..f5d2ebc5 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/override/PyOverrideImplementUtil.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/override/PyOverrideImplementUtil.java @@ -26,14 +26,16 @@ import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.types.PyClassLikeType; import com.jetbrains.python.psi.types.TypeEvalContext; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; +import consulo.application.Application; import consulo.application.Result; import consulo.application.util.matcher.MatcherTextRange; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; import consulo.externalService.statistic.FeatureUsageTracker; import consulo.ide.impl.idea.ide.util.MemberChooser; -import consulo.ide.impl.idea.openapi.util.text.StringUtil; +import consulo.util.lang.StringUtil; import consulo.language.editor.CodeInsightUtilCore; import consulo.language.editor.WriteCommandAction; import consulo.language.editor.util.ProductivityFeatureNames; @@ -42,6 +44,7 @@ import consulo.language.psi.PsiWhiteSpace; import consulo.language.psi.util.PsiTreeUtil; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.DialogWrapper; import consulo.ui.ex.awt.speedSearch.SpeedSearchComparator; import jakarta.annotation.Nonnull; @@ -53,297 +56,311 @@ * @author Alexey.Ivanov */ public class PyOverrideImplementUtil { - @Nullable - public static PyClass getContextClass(@Nonnull final Editor editor, @Nonnull final PsiFile file) { - int offset = editor.getCaretModel().getOffset(); - PsiElement element = file.findElementAt(offset); - if (element == null) { - // are we in whitespace after last class? PY-440 - final PsiElement lastChild = file.getLastChild(); - if (lastChild != null && - offset >= lastChild.getTextRange().getStartOffset() && - offset <= lastChild.getTextRange().getEndOffset()) { - element = lastChild; - } - } - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); - if (pyClass == null && element instanceof PsiWhiteSpace && element.getPrevSibling() instanceof PyClass) { - return (PyClass)element.getPrevSibling(); + @Nullable + @RequiredReadAction + public static PyClass getContextClass(@Nonnull final Editor editor, @Nonnull final PsiFile file) { + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + if (element == null) { + // are we in whitespace after last class? PY-440 + final PsiElement lastChild = file.getLastChild(); + if (lastChild != null + && offset >= lastChild.getTextRange().getStartOffset() + && offset <= lastChild.getTextRange().getEndOffset()) { + element = lastChild; + } + } + final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + if (pyClass == null && element instanceof PsiWhiteSpace whiteSpace && whiteSpace.getPrevSibling() instanceof PyClass prevClass) { + return prevClass; + } + return pyClass; } - return pyClass; - } - - public static void chooseAndOverrideMethods(final Project project, @Nonnull final Editor editor, @Nonnull final PyClass pyClass) { - - - FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); - chooseAndOverrideOrImplementMethods(project, editor, pyClass); - } + @RequiredUIAccess + public static void chooseAndOverrideMethods(final Project project, @Nonnull final Editor editor, @Nonnull final PyClass pyClass) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); + chooseAndOverrideOrImplementMethods(project, editor, pyClass); + } - private static void chooseAndOverrideOrImplementMethods(final Project project, - @Nonnull final Editor editor, - @Nonnull final PyClass pyClass) { - PyPsiUtils.assertValid(pyClass); - ApplicationManager.getApplication().assertReadAccessAllowed(); - - final Set result = new HashSet<>(); - TypeEvalContext context = TypeEvalContext.codeCompletion(project, null); - final Collection superFunctions = getAllSuperFunctions(pyClass, context); - + @RequiredUIAccess + private static void chooseAndOverrideOrImplementMethods( + final Project project, + @Nonnull final Editor editor, + @Nonnull final PyClass pyClass + ) { + PyPsiUtils.assertValid(pyClass); + Application.get().assertReadAccessAllowed(); - result.addAll(superFunctions); - chooseAndOverrideOrImplementMethods(project, editor, pyClass, result, "Select Methods to Override", false); - } + final Set result = new HashSet<>(); + TypeEvalContext context = TypeEvalContext.codeCompletion(project, null); + final Collection superFunctions = getAllSuperFunctions(pyClass, context); - public static void chooseAndOverrideOrImplementMethods(@Nonnull final Project project, - @Nonnull final Editor editor, - @Nonnull final PyClass pyClass, - @Nonnull final Collection superFunctions, - @Nonnull final String title, - final boolean implement) { - List elements = new ArrayList<>(); - for (PyFunction function : superFunctions) { - final String name = function.getName(); - if (name == null || PyUtil.isClassPrivateName(name)) { - continue; - } - if (pyClass.findMethodByName(name, false, null) == null) { - final PyMethodMember member = new PyMethodMember(function); - elements.add(member); - } - } - if (elements.size() == 0) { - return; + result.addAll(superFunctions); + chooseAndOverrideOrImplementMethods(project, editor, pyClass, result, "Select Methods to Override", false); } - final MemberChooser chooser = - new MemberChooser(elements.toArray(new PyMethodMember[elements.size()]), false, true, project) { - @Override - protected SpeedSearchComparator getSpeedSearchComparator() { - return new SpeedSearchComparator(false) { - @Nullable - @Override - public List matchingFragments(@Nonnull String pattern, @Nonnull String text) { - return super.matchingFragments(PyMethodMember.trimUnderscores(pattern), text); + @RequiredUIAccess + public static void chooseAndOverrideOrImplementMethods( + @Nonnull final Project project, + @Nonnull final Editor editor, + @Nonnull final PyClass pyClass, + @Nonnull final Collection superFunctions, + @Nonnull final String title, + final boolean implement + ) { + List elements = new ArrayList<>(); + for (PyFunction function : superFunctions) { + final String name = function.getName(); + if (name == null || PyUtil.isClassPrivateName(name)) { + continue; + } + if (pyClass.findMethodByName(name, false, null) == null) { + final PyMethodMember member = new PyMethodMember(function); + elements.add(member); } - }; } - }; - chooser.setTitle(title); - chooser.setCopyJavadocVisible(false); - chooser.show(); - if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return; - } - List membersToOverride = chooser.getSelectedElements(); - overrideMethods(editor, pyClass, membersToOverride, implement); - } + if (elements.size() == 0) { + return; + } - public static void overrideMethods(final Editor editor, - final PyClass pyClass, - final List membersToOverride, - final boolean implement) { - if (membersToOverride == null) { - return; + final MemberChooser chooser = + new MemberChooser(elements.toArray(new PyMethodMember[elements.size()]), false, true, project) { + @Override + protected SpeedSearchComparator getSpeedSearchComparator() { + return new SpeedSearchComparator(false) { + @Nullable + @Override + public List matchingFragments(@Nonnull String pattern, @Nonnull String text) { + return super.matchingFragments(PyMethodMember.trimUnderscores(pattern), text); + } + }; + } + }; + chooser.setTitle(title); + chooser.setCopyJavadocVisible(false); + chooser.show(); + if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) { + return; + } + List membersToOverride = chooser.getSelectedElements(); + overrideMethods(editor, pyClass, membersToOverride, implement); } - new WriteCommandAction(pyClass.getProject(), pyClass.getContainingFile()) { - protected void run(@Nonnull final Result result) throws Throwable { - write(pyClass, membersToOverride, editor, implement); - } - }.execute(); - } - private static void write(@Nonnull final PyClass pyClass, - @Nonnull final List newMembers, - @Nonnull final Editor editor, - boolean implement) { - final PyStatementList statementList = pyClass.getStatementList(); - final int offset = editor.getCaretModel().getOffset(); - PsiElement anchor = null; - for (PyStatement statement : statementList.getStatements()) { - if (statement.getTextRange() - .getStartOffset() < offset || (statement instanceof PyExpressionStatement && ((PyExpressionStatement)statement).getExpression() instanceof - PyStringLiteralExpression)) { - anchor = statement; - } + @RequiredUIAccess + public static void overrideMethods( + final Editor editor, + final PyClass pyClass, + final List membersToOverride, + final boolean implement + ) { + if (membersToOverride == null) { + return; + } + new WriteCommandAction(pyClass.getProject(), pyClass.getContainingFile()) { + @Override + @RequiredWriteAction + protected void run(@Nonnull final Result result) throws Throwable { + write(pyClass, membersToOverride, editor, implement); + } + }.execute(); } - PyFunction element = null; - for (PyMethodMember newMember : newMembers) { - PyFunction baseFunction = (PyFunction)newMember.getPsiElement(); - final PyFunctionBuilder builder = buildOverriddenFunction(pyClass, baseFunction, implement); - PyFunction function = builder.addFunctionAfter(statementList, anchor, LanguageLevel.forElement(statementList)); - element = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(function); - } + @RequiredWriteAction + private static void write( + @Nonnull final PyClass pyClass, + @Nonnull final List newMembers, + @Nonnull final Editor editor, + boolean implement + ) { + final PyStatementList statementList = pyClass.getStatementList(); + final int offset = editor.getCaretModel().getOffset(); + PsiElement anchor = null; + for (PyStatement statement : statementList.getStatements()) { + if (statement.getTextRange().getStartOffset() < offset + || (statement instanceof PyExpressionStatement exprStmt && exprStmt.getExpression() instanceof PyStringLiteralExpression)) { + anchor = statement; + } + } - PyPsiUtils.removeRedundantPass(statementList); - if (element != null) { - final PyStatementList targetStatementList = element.getStatementList(); - final int start = targetStatementList.getTextRange().getStartOffset(); - editor.getCaretModel().moveToOffset(start); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().setSelection(start, element.getTextRange().getEndOffset()); - } - } + PyFunction element = null; + for (PyMethodMember newMember : newMembers) { + PyFunction baseFunction = (PyFunction) newMember.getPsiElement(); + final PyFunctionBuilder builder = buildOverriddenFunction(pyClass, baseFunction, implement); + PyFunction function = builder.addFunctionAfter(statementList, anchor, LanguageLevel.forElement(statementList)); + element = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(function); + } - private static PyFunctionBuilder buildOverriddenFunction(PyClass pyClass, PyFunction baseFunction, boolean implement) { - final boolean overridingNew = PyNames.NEW.equals(baseFunction.getName()); - assert baseFunction.getName() != null; - PyFunctionBuilder pyFunctionBuilder = new PyFunctionBuilder(baseFunction.getName(), baseFunction); - final PyDecoratorList decorators = baseFunction.getDecoratorList(); - boolean baseMethodIsStatic = false; - if (decorators != null) { - if (decorators.findDecorator(PyNames.CLASSMETHOD) != null) { - pyFunctionBuilder.decorate(PyNames.CLASSMETHOD); - } - else if (decorators.findDecorator(PyNames.STATICMETHOD) != null) { - baseMethodIsStatic = true; - pyFunctionBuilder.decorate(PyNames.STATICMETHOD); - } - else if (decorators.findDecorator(PyNames.PROPERTY) != null || decorators.findDecorator(PyNames.ABSTRACTPROPERTY) != null) { - pyFunctionBuilder.decorate(PyNames.PROPERTY); - } - } - PyAnnotation anno = baseFunction.getAnnotation(); - if (anno != null) { - pyFunctionBuilder.annotation(anno.getText()); - } - final TypeEvalContext context = TypeEvalContext.userInitiated(baseFunction.getProject(), baseFunction.getContainingFile()); - final List baseParams = PyUtil.getParameters(baseFunction, context); - for (PyParameter parameter : baseParams) { - pyFunctionBuilder.parameter(parameter.getText()); + PyPsiUtils.removeRedundantPass(statementList); + if (element != null) { + final PyStatementList targetStatementList = element.getStatementList(); + final int start = targetStatementList.getTextRange().getStartOffset(); + editor.getCaretModel().moveToOffset(start); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().setSelection(start, element.getTextRange().getEndOffset()); + } } - PyClass baseClass = baseFunction.getContainingClass(); - assert baseClass != null; - StringBuilder statementBody = new StringBuilder(); - - boolean hadStar = false; - List parameters = new ArrayList<>(); - for (PyParameter parameter : baseParams) { - final PyNamedParameter pyNamedParameter = parameter.getAsNamed(); - if (pyNamedParameter != null) { - String repr = pyNamedParameter.getRepr(false); - parameters.add(hadStar && !pyNamedParameter.isKeywordContainer() ? pyNamedParameter.getName() + "=" + repr : repr); - if (pyNamedParameter.isPositionalContainer()) { - hadStar = true; + @RequiredReadAction + private static PyFunctionBuilder buildOverriddenFunction(PyClass pyClass, PyFunction baseFunction, boolean implement) { + final boolean overridingNew = PyNames.NEW.equals(baseFunction.getName()); + assert baseFunction.getName() != null; + PyFunctionBuilder pyFunctionBuilder = new PyFunctionBuilder(baseFunction.getName(), baseFunction); + final PyDecoratorList decorators = baseFunction.getDecoratorList(); + boolean baseMethodIsStatic = false; + if (decorators != null) { + if (decorators.findDecorator(PyNames.CLASSMETHOD) != null) { + pyFunctionBuilder.decorate(PyNames.CLASSMETHOD); + } + else if (decorators.findDecorator(PyNames.STATICMETHOD) != null) { + baseMethodIsStatic = true; + pyFunctionBuilder.decorate(PyNames.STATICMETHOD); + } + else if (decorators.findDecorator(PyNames.PROPERTY) != null || decorators.findDecorator(PyNames.ABSTRACTPROPERTY) != null) { + pyFunctionBuilder.decorate(PyNames.PROPERTY); + } + } + PyAnnotation anno = baseFunction.getAnnotation(); + if (anno != null) { + pyFunctionBuilder.annotation(anno.getText()); + } + final TypeEvalContext context = TypeEvalContext.userInitiated(baseFunction.getProject(), baseFunction.getContainingFile()); + final List baseParams = PyUtil.getParameters(baseFunction, context); + for (PyParameter parameter : baseParams) { + pyFunctionBuilder.parameter(parameter.getText()); } - } - else if (parameter instanceof PySingleStarParameter) { - hadStar = true; - } - else { - parameters.add(parameter.getText()); - } - } - if (PyNames.FAKE_OLD_BASE.equals(baseClass.getName()) || raisesNotImplementedError(baseFunction) || implement) { - statementBody.append(PyNames.PASS); - } - else { - if (!PyNames.INIT.equals(baseFunction.getName()) && context.getReturnType(baseFunction) != PyNoneType.INSTANCE || overridingNew) { - statementBody.append("return "); - } - if (baseClass.isNewStyleClass(context)) { - statementBody.append(PyNames.SUPER); - statementBody.append("("); - final LanguageLevel langLevel = ((PyFile)pyClass.getContainingFile()).getLanguageLevel(); - if (!langLevel.isPy3K()) { - final String baseFirstName = !baseParams.isEmpty() ? baseParams.get(0).getName() : null; - final String firstName = baseFirstName != null ? baseFirstName : PyNames.CANONICAL_SELF; - PsiElement outerClass = PsiTreeUtil.getParentOfType(pyClass, PyClass.class, true, PyFunction.class); - String className = pyClass.getName(); - final List nameResult = Lists.newArrayList(className); - while (outerClass != null) { - nameResult.add(0, ((PyClass)outerClass).getName()); - outerClass = PsiTreeUtil.getParentOfType(outerClass, PyClass.class, true, PyFunction.class); - } + PyClass baseClass = baseFunction.getContainingClass(); + assert baseClass != null; + StringBuilder statementBody = new StringBuilder(); - consulo.ide.impl.idea.openapi.util.text.StringUtil.join(nameResult, ".", statementBody); - statementBody.append(", ").append(firstName); + boolean hadStar = false; + List parameters = new ArrayList<>(); + for (PyParameter parameter : baseParams) { + final PyNamedParameter pyNamedParameter = parameter.getAsNamed(); + if (pyNamedParameter != null) { + String repr = pyNamedParameter.getRepr(false); + parameters.add(hadStar && !pyNamedParameter.isKeywordContainer() ? pyNamedParameter.getName() + "=" + repr : repr); + if (pyNamedParameter.isPositionalContainer()) { + hadStar = true; + } + } + else if (parameter instanceof PySingleStarParameter) { + hadStar = true; + } + else { + parameters.add(parameter.getText()); + } } - statementBody.append(").").append(baseFunction.getName()).append("("); - // type.__new__ is explicitly decorated as @staticmethod in our stubs, but not in real Python code - if (parameters.size() > 0 && !(baseMethodIsStatic || overridingNew)) { - parameters.remove(0); + + if (PyNames.FAKE_OLD_BASE.equals(baseClass.getName()) || raisesNotImplementedError(baseFunction) || implement) { + statementBody.append(PyNames.PASS); } - } - else { - statementBody.append(getReferenceText(pyClass, baseClass)).append(".").append(baseFunction.getName()).append("("); - } - StringUtil.join(parameters, ", ", statementBody); - statementBody.append(")"); - } + else { + if (!PyNames.INIT.equals(baseFunction.getName()) && context.getReturnType(baseFunction) != PyNoneType.INSTANCE || overridingNew) { + statementBody.append("return "); + } + if (baseClass.isNewStyleClass(context)) { + statementBody.append(PyNames.SUPER); + statementBody.append("("); + final LanguageLevel langLevel = ((PyFile) pyClass.getContainingFile()).getLanguageLevel(); + if (!langLevel.isPy3K()) { + final String baseFirstName = !baseParams.isEmpty() ? baseParams.get(0).getName() : null; + final String firstName = baseFirstName != null ? baseFirstName : PyNames.CANONICAL_SELF; + PsiElement outerClass = PsiTreeUtil.getParentOfType(pyClass, PyClass.class, true, PyFunction.class); + String className = pyClass.getName(); + final List nameResult = Lists.newArrayList(className); + while (outerClass != null) { + nameResult.add(0, ((PyClass) outerClass).getName()); + outerClass = PsiTreeUtil.getParentOfType(outerClass, PyClass.class, true, PyFunction.class); + } - pyFunctionBuilder.statement(statementBody.toString()); - return pyFunctionBuilder; - } + StringUtil.join(nameResult, ".", statementBody); + statementBody.append(", ").append(firstName); + } + statementBody.append(").").append(baseFunction.getName()).append("("); + // type.__new__ is explicitly decorated as @staticmethod in our stubs, but not in real Python code + if (parameters.size() > 0 && !(baseMethodIsStatic || overridingNew)) { + parameters.remove(0); + } + } + else { + statementBody.append(getReferenceText(pyClass, baseClass)).append(".").append(baseFunction.getName()).append("("); + } + StringUtil.join(parameters, ", ", statementBody); + statementBody.append(")"); + } - public static boolean raisesNotImplementedError(@Nonnull PyFunction function) { - PyStatementList statementList = function.getStatementList(); - IfVisitor visitor = new IfVisitor(); - statementList.accept(visitor); - return !visitor.hasReturnInside && visitor.raiseNotImplemented; - } + pyFunctionBuilder.statement(statementBody.toString()); + return pyFunctionBuilder; + } - // TODO find a better place for this logic - private static String getReferenceText(PyClass fromClass, PyClass toClass) { - final PyExpression[] superClassExpressions = fromClass.getSuperClassExpressions(); - for (PyExpression expression : superClassExpressions) { - if (expression instanceof PyReferenceExpression) { - PsiElement target = ((PyReferenceExpression)expression).getReference().resolve(); - if (target == toClass) { - return expression.getText(); - } - } + public static boolean raisesNotImplementedError(@Nonnull PyFunction function) { + PyStatementList statementList = function.getStatementList(); + IfVisitor visitor = new IfVisitor(); + statementList.accept(visitor); + return !visitor.hasReturnInside && visitor.raiseNotImplemented; } - return toClass.getName(); - } - /** - * Returns all super functions available through MRO. - */ - @Nonnull - public static List getAllSuperFunctions(@Nonnull PyClass pyClass, @Nonnull TypeEvalContext context) { - final Map functions = Maps.newLinkedHashMap(); - for (final PyClassLikeType type : pyClass.getAncestorTypes(context)) { - if (type != null) { - for (PyFunction function : PyTypeUtil.getMembersOfType(type, PyFunction.class, false, context)) { - final String name = function.getName(); - if (name != null && !functions.containsKey(name)) { - functions.put(name, function); - } + // TODO find a better place for this logic + @RequiredReadAction + private static String getReferenceText(PyClass fromClass, PyClass toClass) { + final PyExpression[] superClassExpressions = fromClass.getSuperClassExpressions(); + for (PyExpression expression : superClassExpressions) { + if (expression instanceof PyReferenceExpression refExpr) { + PsiElement target = refExpr.getReference().resolve(); + if (target == toClass) { + return expression.getText(); + } + } } - } + return toClass.getName(); } - return Lists.newArrayList(functions.values()); - } - - private static class IfVisitor extends PyRecursiveElementVisitor { - private boolean hasReturnInside; - private boolean raiseNotImplemented; - @Override - public void visitPyReturnStatement(PyReturnStatement node) { - hasReturnInside = true; + /** + * Returns all super functions available through MRO. + */ + @Nonnull + @RequiredReadAction + public static List getAllSuperFunctions(@Nonnull PyClass pyClass, @Nonnull TypeEvalContext context) { + final Map functions = Maps.newLinkedHashMap(); + for (final PyClassLikeType type : pyClass.getAncestorTypes(context)) { + if (type != null) { + for (PyFunction function : PyTypeUtil.getMembersOfType(type, PyFunction.class, false, context)) { + final String name = function.getName(); + if (name != null && !functions.containsKey(name)) { + functions.put(name, function); + } + } + } + } + return Lists.newArrayList(functions.values()); } - @Override - public void visitPyRaiseStatement(PyRaiseStatement node) { - final PyExpression[] expressions = node.getExpressions(); - if (expressions.length > 0) { - final PyExpression firstExpression = expressions[0]; - if (firstExpression instanceof PyCallExpression) { - final PyExpression callee = ((PyCallExpression)firstExpression).getCallee(); - if (callee != null && callee.getText().equals(PyNames.NOT_IMPLEMENTED_ERROR)) { - raiseNotImplemented = true; - } + private static class IfVisitor extends PyRecursiveElementVisitor { + private boolean hasReturnInside; + private boolean raiseNotImplemented; + + @Override + public void visitPyReturnStatement(PyReturnStatement node) { + hasReturnInside = true; } - else if (firstExpression.getText().equals(PyNames.NOT_IMPLEMENTED_ERROR)) { - raiseNotImplemented = true; + + @Override + @RequiredReadAction + public void visitPyRaiseStatement(PyRaiseStatement node) { + final PyExpression[] expressions = node.getExpressions(); + if (expressions.length > 0) { + if (expressions[0] instanceof PyCallExpression call) { + final PyExpression callee = call.getCallee(); + if (callee != null && callee.getText().equals(PyNames.NOT_IMPLEMENTED_ERROR)) { + raiseNotImplemented = true; + } + } + else if (expressions[0].getText().equals(PyNames.NOT_IMPLEMENTED_ERROR)) { + raiseNotImplemented = true; + } + } } - } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/documentation/docstrings/DocStringUtil.java b/python-impl/src/main/java/com/jetbrains/python/impl/documentation/docstrings/DocStringUtil.java index ee3979fe..59ba4e78 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/documentation/docstrings/DocStringUtil.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/documentation/docstrings/DocStringUtil.java @@ -198,7 +198,7 @@ public static boolean isLikeEpydocDocString(@Nonnull String text) { } public static boolean isLikeGoogleDocString(@Nonnull String text) { - for (@NonNls String title : consulo.ide.impl.idea.openapi.util.text.StringUtil.findMatches(text, GoogleCodeStyleDocString.SECTION_HEADER, 1)) { + for (@NonNls String title : StringUtil.findMatches(text, GoogleCodeStyleDocString.SECTION_HEADER, 1)) { if (SectionBasedDocString.isValidSectionTitle(title)) { return true; } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddCallSuperQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddCallSuperQuickFix.java index f2068491..5c54e310 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddCallSuperQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddCallSuperQuickFix.java @@ -20,6 +20,8 @@ import com.jetbrains.python.impl.psi.PyUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.psi.PsiElement; @@ -52,6 +54,8 @@ public LocalizeValue getName() { return PyLocalize.qfixAddSuper(); } + @Override + @RequiredWriteAction public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { final PyFunction problemFunction = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyFunction.class); if (problemFunction == null) { @@ -97,10 +101,10 @@ public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescr final Couple> couple = buildNewFunctionParamsAndSuperInitCallArgs(origInfo, superInfo, addSelfToCall); final StringBuilder newParameters = new StringBuilder("("); - consulo.ide.impl.idea.openapi.util.text.StringUtil.join(couple.getFirst(), ", ", newParameters); + StringUtil.join(couple.getFirst(), ", ", newParameters); newParameters.append(")"); - consulo.ide.impl.idea.openapi.util.text.StringUtil.join(couple.getSecond(), ", ", superCall); + StringUtil.join(couple.getSecond(), ", ", superCall); superCall.append(")"); final PyElementGenerator generator = PyElementGenerator.getInstance(project); @@ -123,6 +127,7 @@ private static String getSelfParameterName(@Nonnull ParametersInfo info) { } @Nonnull + @RequiredReadAction private static Couple> buildNewFunctionParamsAndSuperInitCallArgs( @Nonnull ParametersInfo origInfo, @Nonnull ParametersInfo superInfo, diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonTask.java b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonTask.java index 04193af2..47d3c600 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonTask.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonTask.java @@ -28,7 +28,7 @@ import consulo.execution.process.ProcessTerminatedListener; import consulo.execution.ui.console.ConsoleView; import consulo.ide.impl.idea.execution.configurations.EncodingEnvironmentUtil; -import consulo.ide.impl.idea.openapi.util.text.StringUtil; +import consulo.util.lang.StringUtil; import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import consulo.process.ExecutionException; @@ -44,6 +44,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -275,9 +276,11 @@ public final String runNoConsole() throws ExecutionException { if (exitCode == 0) { return output.getStdout(); } - throw new ExecutionException(String.format("Error on python side. " + "Exit code: %s, err: %s out: %s", + throw new ExecutionException(String.format( + "Error on python side. " + "Exit code: %s, err: %s out: %s", exitCode, output.getStderr(), - output.getStdout())); + output.getStdout() + )); } } diff --git a/python-psi-api/src/main/java/com/jetbrains/python/psi/impl/PyPsiUtils.java b/python-psi-api/src/main/java/com/jetbrains/python/psi/impl/PyPsiUtils.java index af8dc8ec..2e3c626e 100644 --- a/python-psi-api/src/main/java/com/jetbrains/python/psi/impl/PyPsiUtils.java +++ b/python-psi-api/src/main/java/com/jetbrains/python/psi/impl/PyPsiUtils.java @@ -17,6 +17,8 @@ import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.language.ast.ASTNode; import consulo.language.ast.IElementType; import consulo.language.ast.TokenSet; @@ -33,6 +35,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.lang.reflect.Array; import java.util.ArrayList; import java.util.LinkedList; @@ -41,745 +44,640 @@ /** * @author max */ -public class PyPsiUtils -{ - - private static final Logger LOG = Logger.getInstance(PyPsiUtils.class.getName()); - - private PyPsiUtils() - { - } - - @Nonnull - public static T[] nodesToPsi(ASTNode[] nodes, T[] array) - { - T[] psiElements = (T[]) Array.newInstance(array.getClass().getComponentType(), nodes.length); - for(int i = 0; i < nodes.length; i++) - { - //noinspection unchecked - psiElements[i] = (T) nodes[i].getPsi(); - } - return psiElements; - } - - /** - * Finds the closest comma after the element skipping any whitespaces in-between. - */ - @Nullable - public static PsiElement getPrevComma(@Nonnull PsiElement element) - { - final PsiElement prevNode = getPrevNonWhitespaceSibling(element); - return prevNode != null && prevNode.getNode().getElementType() == PyTokenTypes.COMMA ? prevNode : null; - } - - /** - * Finds first non-whitespace sibling before given PSI element. - */ - @Nullable - public static PsiElement getPrevNonWhitespaceSibling(@Nullable PsiElement element) - { - return PsiTreeUtil.skipSiblingsBackward(element, PsiWhiteSpace.class); - } - - /** - * Finds first non-whitespace sibling before given AST node. - */ - @Nullable - public static ASTNode getPrevNonWhitespaceSibling(@Nonnull ASTNode node) - { - return skipSiblingsBackward(node, TokenSet.create(TokenType.WHITE_SPACE)); - } - - /** - * Finds first sibling that is neither comment, nor whitespace before given element. - * - * @param strict prohibit returning element itself - */ - @Nullable - public static PsiElement getPrevNonCommentSibling(@Nullable PsiElement start, boolean strict) - { - if(!strict && !(start instanceof PsiWhiteSpace || start instanceof PsiComment)) - { - return start; - } - return PsiTreeUtil.skipSiblingsBackward(start, PsiWhiteSpace.class, PsiComment.class); - } - - /** - * Finds the closest comma after the element skipping any whitespaces in-between. - */ - @Nullable - public static PsiElement getNextComma(@Nonnull PsiElement element) - { - final PsiElement nextNode = getNextNonWhitespaceSibling(element); - return nextNode != null && nextNode.getNode().getElementType() == PyTokenTypes.COMMA ? nextNode : null; - } - - /** - * Finds first non-whitespace sibling after given PSI element. - */ - @Nullable - public static PsiElement getNextNonWhitespaceSibling(@Nullable PsiElement element) - { - return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class); - } - - /** - * Finds first non-whitespace sibling after given PSI element but stops at first whitespace containing line feed. - */ - @Nullable - public static PsiElement getNextNonWhitespaceSiblingOnSameLine(@Nonnull PsiElement element) - { - PsiElement cur = element.getNextSibling(); - while(cur != null) - { - if(!(cur instanceof PsiWhiteSpace)) - { - return cur; - } - else if(cur.textContains('\n')) - { - break; - } - cur = cur.getNextSibling(); - } - return null; - } - - /** - * Finds first non-whitespace sibling after given AST node. - */ - @Nullable - public static ASTNode getNextNonWhitespaceSibling(@Nonnull ASTNode after) - { - return skipSiblingsForward(after, TokenSet.create(TokenType.WHITE_SPACE)); - } - - /** - * Finds first sibling that is neither comment, nor whitespace after given element. - * - * @param strict prohibit returning element itself - */ - @Nullable - public static PsiElement getNextNonCommentSibling(@Nullable PsiElement start, boolean strict) - { - if(!strict && !(start instanceof PsiWhiteSpace || start instanceof PsiComment)) - { - return start; - } - return PsiTreeUtil.skipSiblingsForward(start, PsiWhiteSpace.class, PsiComment.class); - } - - /** - * Finds first token after given element that doesn't consist solely of spaces and is not empty (e.g. error marker). - * - * @param ignoreComments ignore commentaries as well - */ - @Nullable - public static PsiElement getNextSignificantLeaf(@Nullable PsiElement element, boolean ignoreComments) - { - while(element != null && StringUtil.isEmptyOrSpaces(element.getText()) || ignoreComments && element instanceof PsiComment) - { - element = PsiTreeUtil.nextLeaf(element); - } - return element; - } - - /** - * Finds first token before given element that doesn't consist solely of spaces and is not empty (e.g. error marker). - * - * @param ignoreComments ignore commentaries as well - */ - @Nullable - public static PsiElement getPrevSignificantLeaf(@Nullable PsiElement element, boolean ignoreComments) - { - while(element != null && StringUtil.isEmptyOrSpaces(element.getText()) || ignoreComments && element instanceof PsiComment) - { - element = PsiTreeUtil.prevLeaf(element); - } - return element; - } - - /** - * Finds the closest comma looking for the next comma first and then for the preceding one. - */ - @Nullable - public static PsiElement getAdjacentComma(@Nonnull PsiElement element) - { - final PsiElement nextComma = getNextComma(element); - return nextComma != null ? nextComma : getPrevComma(element); - } - - /** - * Works similarly to {@link PsiTreeUtil#skipSiblingsForward(PsiElement, Class[])}, but for AST nodes. - */ - @Nullable - public static ASTNode skipSiblingsForward(@Nullable ASTNode node, @Nonnull TokenSet types) - { - if(node == null) - { - return null; - } - for(ASTNode next = node.getTreeNext(); next != null; next = next.getTreeNext()) - { - if(!types.contains(next.getElementType())) - { - return next; - } - } - return null; - } - - /** - * Works similarly to {@link PsiTreeUtil#skipSiblingsBackward(PsiElement, Class[])}, but for AST nodes. - */ - @Nullable - public static ASTNode skipSiblingsBackward(@Nullable ASTNode node, @Nonnull TokenSet types) - { - if(node == null) - { - return null; - } - for(ASTNode prev = node.getTreePrev(); prev != null; prev = prev.getTreePrev()) - { - if(!types.contains(prev.getElementType())) - { - return prev; - } - } - return null; - } - - /** - * Returns first child psi element with specified element type or {@code null} if no such element exists. - * Semantically it's the same as {@code getChildByFilter(element, TokenSet.create(type), 0)}. - * - * @param element tree parent node - * @param type element type expected - * @return child element described - */ - @Nullable - public static PsiElement getFirstChildOfType(@Nonnull final PsiElement element, @Nonnull PyElementType type) - { - final ASTNode child = element.getNode().findChildByType(type); - return child != null ? child.getPsi() : null; - } - - /** - * Returns child element in the psi tree - * - * @param filter Types of expected child - * @param number number - * @param element tree parent node - * @return PsiElement - child psiElement - */ - @Nullable - public static PsiElement getChildByFilter(@Nonnull PsiElement element, @Nonnull TokenSet filter, int number) - { - final ASTNode node = element.getNode(); - if(node != null) - { - final ASTNode[] children = node.getChildren(filter); - return (0 <= number && number < children.length) ? children[number].getPsi() : null; - } - return null; - } - - public static void addBeforeInParent(@Nonnull final PsiElement anchor, @Nonnull final PsiElement... newElements) - { - final ASTNode anchorNode = anchor.getNode(); - LOG.assertTrue(anchorNode != null); - for(PsiElement newElement : newElements) - { - anchorNode.getTreeParent().addChild(newElement.getNode(), anchorNode); - } - } - - public static void removeElements(@Nonnull final PsiElement... elements) - { - final ASTNode parentNode = elements[0].getParent().getNode(); - LOG.assertTrue(parentNode != null); - for(PsiElement element : elements) - { - //noinspection ConstantConditions - parentNode.removeChild(element.getNode()); - } - } - - @Nullable - public static PsiElement getStatement(@Nonnull final PsiElement element) - { - final PyElement compStatement = getStatementList(element); - if(compStatement == null) - { - return null; - } - return getParentRightBefore(element, compStatement); - } - - public static PyElement getStatementList(final PsiElement element) - { - //noinspection ConstantConditions - return element instanceof PyFile || element instanceof PyStatementList ? (PyElement) element : PsiTreeUtil.getParentOfType(element, - PyFile.class, - PyStatementList.class); - } - - /** - * Returns ancestor of the element that is also direct child of the given super parent. - * - * @param element element to start search from - * @param superParent direct parent of the desired ancestor - * @return described element or {@code null} if it doesn't exist - */ - @Nullable - public static PsiElement getParentRightBefore(@Nonnull PsiElement element, @Nonnull final PsiElement superParent) - { - return PsiTreeUtil.findFirstParent(element, false, element1 -> element1.getParent() == superParent); - } - - public static List collectElements(final PsiElement statement1, final PsiElement statement2) - { - // Process ASTNodes here to handle all the nodes - final ASTNode node1 = statement1.getNode(); - final ASTNode node2 = statement2.getNode(); - final ASTNode parentNode = node1.getTreeParent(); - - boolean insideRange = false; - final List result = new ArrayList<>(); - for(ASTNode node : parentNode.getChildren(null)) - { - // start - if(node1 == node) - { - insideRange = true; - } - if(insideRange) - { - result.add(node.getPsi()); - } - // stop - if(node == node2) - { - break; - } - } - return result; - } - - public static int getElementIndentation(final PsiElement element) - { - final PsiElement compStatement = getStatementList(element); - final PsiElement statement = getParentRightBefore(element, compStatement); - if(statement == null) - { - return 0; - } - PsiElement sibling = statement.getPrevSibling(); - if(sibling == null) - { - sibling = compStatement.getPrevSibling(); - } - final String whitespace = sibling instanceof PsiWhiteSpace ? sibling.getText() : ""; - final int i = whitespace.lastIndexOf("\n"); - return i != -1 ? whitespace.length() - i - 1 : 0; - } - - public static void removeRedundantPass(final PyStatementList statementList) - { - final PyStatement[] statements = statementList.getStatements(); - if(statements.length > 1) - { - for(PyStatement statement : statements) - { - if(statement instanceof PyPassStatement) - { - statement.delete(); - } - } - } - } - - public static boolean isMethodContext(final PsiElement element) - { - final PsiNamedElement parent = PsiTreeUtil.getParentOfType(element, PyFile.class, PyFunction.class, PyClass.class); - // In case if element is inside method which is inside class - if(parent instanceof PyFunction && PsiTreeUtil.getParentOfType(parent, PyFile.class, PyClass.class) instanceof PyClass) - { - return true; - } - return false; - } - - - @Nonnull - public static PsiElement getRealContext(@Nonnull final PsiElement element) - { - assertValid(element); - final PsiFile file = element.getContainingFile(); - if(file instanceof PyExpressionCodeFragment) - { - final PsiElement context = file.getContext(); - if(LOG.isDebugEnabled()) - { - LOG.debug("PyPsiUtil.getRealContext(" + element + ") is called. Returned " + context + ". Element inside code fragment"); - } - return context != null ? context : element; - } - else - { - if(LOG.isDebugEnabled()) - { - LOG.debug("PyPsiUtil.getRealContext(" + element + ") is called. Returned " + element + "."); - } - return element; - } - } - - /** - * Removes comma closest to the given child node along with any whitespaces around. First following comma is checked and only - * then, if it doesn't exists, preceding one. - * - * @param element parent node - * @param child child node comma should be adjacent to - * @see #getAdjacentComma(PsiElement) - */ - public static void deleteAdjacentCommaWithWhitespaces(@Nonnull PsiElement element, @Nonnull PsiElement child) - { - final PsiElement commaNode = getAdjacentComma(child); - if(commaNode != null) - { - final PsiElement nextNonWhitespace = getNextNonWhitespaceSibling(commaNode); - final PsiElement last = nextNonWhitespace == null ? element.getLastChild() : nextNonWhitespace.getPrevSibling(); - final PsiElement prevNonWhitespace = getPrevNonWhitespaceSibling(commaNode); - final PsiElement first = prevNonWhitespace == null ? element.getFirstChild() : prevNonWhitespace.getNextSibling(); - element.deleteChildRange(first, last); - } - } - - /** - * Returns comments preceding given elements as pair of the first and the last such comments. Comments should not be - * separated by any empty line. - * - * @param element element comments should be adjacent to - * @return described range or {@code null} if there are no such comments - */ - @Nullable - public static Couple getPrecedingComments(@Nonnull PsiElement element) - { - PsiComment firstComment = null, lastComment = null; - overComments: - while(true) - { - int newLinesCount = 0; - for(element = element.getPrevSibling(); element instanceof PsiWhiteSpace; element = element.getPrevSibling()) - { - newLinesCount += StringUtil.getLineBreakCount(element.getText()); - if(newLinesCount > 1) - { - break overComments; - } - } - if(element instanceof PsiComment) - { - if(lastComment == null) - { - lastComment = (PsiComment) element; - } - firstComment = (PsiComment) element; - } - else - { - break; - } - } - return lastComment == null ? null : Couple.of(firstComment, lastComment); - } - - @Nonnull - public static List collectStubChildren(U e, - final StubElement stub, - final IElementType elementType, - final Class itemClass) - { - final List result = new ArrayList<>(); - if(stub != null) - { - final List children = stub.getChildrenStubs(); - for(StubElement child : children) - { - if(child.getStubType() == elementType) - { - //noinspection unchecked - result.add((T) child.getPsi()); - } - } - } - else - { - e.acceptChildren(new TopLevelVisitor() - { - @Override - protected void checkAddElement(PsiElement node) - { - if(node.getNode().getElementType() == elementType) - { - //noinspection unchecked - result.add((T) node); - } - } - }); - } - return result; - } - - public static List collectAllStubChildren(PsiElement e, StubElement stub) - { - final List result = new ArrayList<>(); - if(stub != null) - { - final List children = stub.getChildrenStubs(); - for(StubElement child : children) - { - //noinspection unchecked - result.add(child.getPsi()); - } - } - else - { - e.acceptChildren(new TopLevelVisitor() - { - @Override - protected void checkAddElement(PsiElement node) - { - result.add(node); - } - }); - } - return result; - } - - public static int findArgumentIndex(PyCallExpression call, PsiElement argument) - { - final PyExpression[] args = call.getArguments(); - for(int i = 0; i < args.length; i++) - { - PyExpression expression = args[i]; - if(expression instanceof PyKeywordArgument) - { - expression = ((PyKeywordArgument) expression).getValueExpression(); - } - expression = flattenParens(expression); - if(expression == argument) - { - return i; - } - } - return -1; - } - - @Nullable - public static PyTargetExpression getAttribute(@Nonnull final PyFile file, @Nonnull final String name) - { - PyTargetExpression attr = file.findTopLevelAttribute(name); - if(attr == null) - { - for(PyFromImportStatement element : file.getFromImports()) - { - PyReferenceExpression expression = element.getImportSource(); - if(expression == null) - { - continue; - } - final PsiElement resolved = expression.getReference().resolve(); - if(resolved instanceof PyFile && resolved != file) - { - return ((PyFile) resolved).findTopLevelAttribute(name); - } - } - } - return attr; - } - - public static List getAttributeValuesFromFile(@Nonnull PyFile file, @Nonnull String name) - { - List result = ContainerUtil.newArrayList(); - final PyTargetExpression attr = file.findTopLevelAttribute(name); - if(attr != null) - { - sequenceToList(result, attr.findAssignedValue()); - } - return result; - } - - public static void sequenceToList(List result, PyExpression value) - { - value = flattenParens(value); - if(value instanceof PySequenceExpression) - { - result.addAll(ContainerUtil.newArrayList(((PySequenceExpression) value).getElements())); - } - else - { - result.add(value); - } - } - - public static List getStringValues(PyExpression[] elements) - { - List results = ContainerUtil.newArrayList(); - for(PyExpression element : elements) - { - if(element instanceof PyStringLiteralExpression) - { - results.add(((PyStringLiteralExpression) element).getStringValue()); - } - } - return results; - } - - @Nullable - public static PyExpression flattenParens(@Nullable PyExpression expr) - { - while(expr instanceof PyParenthesizedExpression) - { - expr = ((PyParenthesizedExpression) expr).getContainedExpression(); - } - return expr; - } - - @Nullable - public static String strValue(@Nullable PyExpression expression) - { - return expression instanceof PyStringLiteralExpression ? ((PyStringLiteralExpression) expression).getStringValue() : null; - } - - public static boolean isBefore(@Nonnull final PsiElement element, @Nonnull final PsiElement element2) - { - // TODO: From RubyPsiUtil, should be moved to PsiTreeUtil - return element.getTextOffset() <= element2.getTextOffset(); - } - - @Nullable - public static QualifiedName toQualifiedName(@Nullable PyExpression expr) - { - return expr instanceof PyQualifiedExpression ? ((PyQualifiedExpression) expr).asQualifiedName() : null; - } - - @Nullable - public static PyExpression getFirstQualifier(@Nonnull PyQualifiedExpression expr) - { - final List expressions = unwindQualifiers(expr); - if(!expressions.isEmpty()) - { - return expressions.get(0); - } - return null; - } - - @Nonnull - public static String toPath(@Nullable PyQualifiedExpression expr) - { - if(expr != null) - { - final QualifiedName qName = expr.asQualifiedName(); - if(qName != null) - { - return qName.toString(); - } - final String name = expr.getName(); - if(name != null) - { - return name; - } - } - return ""; - } - - @Nullable - public static QualifiedName asQualifiedName(@Nonnull PyQualifiedExpression expr) - { - return fromReferenceChain(unwindQualifiers(expr)); - } - - @Nonnull - private static List unwindQualifiers(@Nonnull final PyQualifiedExpression expr) - { - final List path = new LinkedList<>(); - PyQualifiedExpression e = expr; - while(e != null) - { - path.add(0, e); - final PyExpression q = e.getQualifier(); - e = q instanceof PyQualifiedExpression ? (PyQualifiedExpression) q : null; - } - return path; - } - +public class PyPsiUtils { + private static final Logger LOG = Logger.getInstance(PyPsiUtils.class); + + private PyPsiUtils() { + } + + @Nonnull + public static T[] nodesToPsi(ASTNode[] nodes, T[] array) { + T[] psiElements = (T[]) Array.newInstance(array.getClass().getComponentType(), nodes.length); + for (int i = 0; i < nodes.length; i++) { + //noinspection unchecked + psiElements[i] = (T) nodes[i].getPsi(); + } + return psiElements; + } + + /** + * Finds the closest comma after the element skipping any whitespaces in-between. + */ + @Nullable + @RequiredReadAction + public static PsiElement getPrevComma(@Nonnull PsiElement element) { + final PsiElement prevNode = getPrevNonWhitespaceSibling(element); + return prevNode != null && prevNode.getNode().getElementType() == PyTokenTypes.COMMA ? prevNode : null; + } + + /** + * Finds first non-whitespace sibling before given PSI element. + */ @Nullable - private static QualifiedName fromReferenceChain(@Nonnull List components) - { - final List componentNames = new ArrayList<>(components.size()); - for(PyExpression component : components) - { - final String refName = (component instanceof PyQualifiedExpression) ? ((PyQualifiedExpression) component).getReferencedName() : null; - if(refName == null) - { - return null; - } - componentNames.add(refName); - } - return QualifiedName.fromComponents(componentNames); - } - - /** - * Wrapper for {@link PsiUtilCore#ensureValid(PsiElement)} that skips nulls - */ - public static void assertValid(@Nullable final PsiElement element) - { - if(element == null) - { - return; - } - PsiUtilCore.ensureValid(element); - } - - public static void assertValid(@Nonnull final Module module) - { - if(module.isDisposed()) - { - throw new IllegalArgumentException(String.format("Module %s is disposed", module)); - } - } - - @Nonnull - public static PsiFileSystemItem getFileSystemItem(@Nonnull PsiElement element) - { - if(element instanceof PsiFileSystemItem) - { - return (PsiFileSystemItem) element; - } - return element.getContainingFile(); - } - - private static abstract class TopLevelVisitor extends PyRecursiveElementVisitor - { - public void visitPyElement(final PyElement node) - { - super.visitPyElement(node); - checkAddElement(node); - } - - public void visitPyClass(final PyClass node) - { - checkAddElement(node); // do not recurse into functions - } - - public void visitPyFunction(final PyFunction node) - { - checkAddElement(node); // do not recurse into classes - } - - protected abstract void checkAddElement(PsiElement node); - } + @RequiredReadAction + public static PsiElement getPrevNonWhitespaceSibling(@Nullable PsiElement element) { + return PsiTreeUtil.skipSiblingsBackward(element, PsiWhiteSpace.class); + } + + /** + * Finds first non-whitespace sibling before given AST node. + */ + @Nullable + public static ASTNode getPrevNonWhitespaceSibling(@Nonnull ASTNode node) { + return skipSiblingsBackward(node, TokenSet.create(TokenType.WHITE_SPACE)); + } + + /** + * Finds first sibling that is neither comment, nor whitespace before given element. + * + * @param strict prohibit returning element itself + */ + @Nullable + @RequiredReadAction + public static PsiElement getPrevNonCommentSibling(@Nullable PsiElement start, boolean strict) { + if (!strict && !(start instanceof PsiWhiteSpace || start instanceof PsiComment)) { + return start; + } + return PsiTreeUtil.skipSiblingsBackward(start, PsiWhiteSpace.class, PsiComment.class); + } + + /** + * Finds the closest comma after the element skipping any whitespaces in-between. + */ + @Nullable + @RequiredReadAction + public static PsiElement getNextComma(@Nonnull PsiElement element) { + final PsiElement nextNode = getNextNonWhitespaceSibling(element); + return nextNode != null && nextNode.getNode().getElementType() == PyTokenTypes.COMMA ? nextNode : null; + } + + /** + * Finds first non-whitespace sibling after given PSI element. + */ + @Nullable + @RequiredReadAction + public static PsiElement getNextNonWhitespaceSibling(@Nullable PsiElement element) { + return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class); + } + + /** + * Finds first non-whitespace sibling after given PSI element but stops at first whitespace containing line feed. + */ + @Nullable + @RequiredReadAction + public static PsiElement getNextNonWhitespaceSiblingOnSameLine(@Nonnull PsiElement element) { + PsiElement cur = element.getNextSibling(); + while (cur != null) { + if (!(cur instanceof PsiWhiteSpace)) { + return cur; + } + else if (cur.textContains('\n')) { + break; + } + cur = cur.getNextSibling(); + } + return null; + } + + /** + * Finds first non-whitespace sibling after given AST node. + */ + @Nullable + public static ASTNode getNextNonWhitespaceSibling(@Nonnull ASTNode after) { + return skipSiblingsForward(after, TokenSet.create(TokenType.WHITE_SPACE)); + } + + /** + * Finds first sibling that is neither comment, nor whitespace after given element. + * + * @param strict prohibit returning element itself + */ + @Nullable + @RequiredReadAction + public static PsiElement getNextNonCommentSibling(@Nullable PsiElement start, boolean strict) { + if (!strict && !(start instanceof PsiWhiteSpace || start instanceof PsiComment)) { + return start; + } + return PsiTreeUtil.skipSiblingsForward(start, PsiWhiteSpace.class, PsiComment.class); + } + + /** + * Finds first token after given element that doesn't consist solely of spaces and is not empty (e.g. error marker). + * + * @param ignoreComments ignore commentaries as well + */ + @Nullable + @RequiredReadAction + public static PsiElement getNextSignificantLeaf(@Nullable PsiElement element, boolean ignoreComments) { + while (element != null && StringUtil.isEmptyOrSpaces(element.getText()) || ignoreComments && element instanceof PsiComment) { + element = PsiTreeUtil.nextLeaf(element); + } + return element; + } + + /** + * Finds first token before given element that doesn't consist solely of spaces and is not empty (e.g. error marker). + * + * @param ignoreComments ignore commentaries as well + */ + @Nullable + @RequiredReadAction + public static PsiElement getPrevSignificantLeaf(@Nullable PsiElement element, boolean ignoreComments) { + while (element != null && StringUtil.isEmptyOrSpaces(element.getText()) || ignoreComments && element instanceof PsiComment) { + element = PsiTreeUtil.prevLeaf(element); + } + return element; + } + + /** + * Finds the closest comma looking for the next comma first and then for the preceding one. + */ + @Nullable + @RequiredReadAction + public static PsiElement getAdjacentComma(@Nonnull PsiElement element) { + final PsiElement nextComma = getNextComma(element); + return nextComma != null ? nextComma : getPrevComma(element); + } + + /** + * Works similarly to {@link PsiTreeUtil#skipSiblingsForward(PsiElement, Class[])}, but for AST nodes. + */ + @Nullable + public static ASTNode skipSiblingsForward(@Nullable ASTNode node, @Nonnull TokenSet types) { + if (node == null) { + return null; + } + for (ASTNode next = node.getTreeNext(); next != null; next = next.getTreeNext()) { + if (!types.contains(next.getElementType())) { + return next; + } + } + return null; + } + + /** + * Works similarly to {@link PsiTreeUtil#skipSiblingsBackward(PsiElement, Class[])}, but for AST nodes. + */ + @Nullable + public static ASTNode skipSiblingsBackward(@Nullable ASTNode node, @Nonnull TokenSet types) { + if (node == null) { + return null; + } + for (ASTNode prev = node.getTreePrev(); prev != null; prev = prev.getTreePrev()) { + if (!types.contains(prev.getElementType())) { + return prev; + } + } + return null; + } + + /** + * Returns first child psi element with specified element type or {@code null} if no such element exists. + * Semantically it's the same as {@code getChildByFilter(element, TokenSet.create(type), 0)}. + * + * @param element tree parent node + * @param type element type expected + * @return child element described + */ + @Nullable + public static PsiElement getFirstChildOfType(@Nonnull final PsiElement element, @Nonnull PyElementType type) { + final ASTNode child = element.getNode().findChildByType(type); + return child != null ? child.getPsi() : null; + } + + /** + * Returns child element in the psi tree + * + * @param filter Types of expected child + * @param number number + * @param element tree parent node + * @return PsiElement - child psiElement + */ + @Nullable + public static PsiElement getChildByFilter(@Nonnull PsiElement element, @Nonnull TokenSet filter, int number) { + final ASTNode node = element.getNode(); + if (node != null) { + final ASTNode[] children = node.getChildren(filter); + return (0 <= number && number < children.length) ? children[number].getPsi() : null; + } + return null; + } + + public static void addBeforeInParent(@Nonnull final PsiElement anchor, @Nonnull final PsiElement... newElements) { + final ASTNode anchorNode = anchor.getNode(); + LOG.assertTrue(anchorNode != null); + for (PsiElement newElement : newElements) { + anchorNode.getTreeParent().addChild(newElement.getNode(), anchorNode); + } + } + + public static void removeElements(@Nonnull final PsiElement... elements) { + final ASTNode parentNode = elements[0].getParent().getNode(); + LOG.assertTrue(parentNode != null); + for (PsiElement element : elements) { + //noinspection ConstantConditions + parentNode.removeChild(element.getNode()); + } + } + + @Nullable + public static PsiElement getStatement(@Nonnull final PsiElement element) { + final PyElement compStatement = getStatementList(element); + if (compStatement == null) { + return null; + } + return getParentRightBefore(element, compStatement); + } + + public static PyElement getStatementList(final PsiElement element) { + //noinspection ConstantConditions + return element instanceof PyFile || element instanceof PyStatementList ? (PyElement) element : PsiTreeUtil.getParentOfType( + element, + PyFile.class, + PyStatementList.class + ); + } + + /** + * Returns ancestor of the element that is also direct child of the given super parent. + * + * @param element element to start search from + * @param superParent direct parent of the desired ancestor + * @return described element or {@code null} if it doesn't exist + */ + @Nullable + public static PsiElement getParentRightBefore(@Nonnull PsiElement element, @Nonnull final PsiElement superParent) { + return PsiTreeUtil.findFirstParent(element, false, element1 -> element1.getParent() == superParent); + } + + public static List collectElements(final PsiElement statement1, final PsiElement statement2) { + // Process ASTNodes here to handle all the nodes + final ASTNode node1 = statement1.getNode(); + final ASTNode node2 = statement2.getNode(); + final ASTNode parentNode = node1.getTreeParent(); + + boolean insideRange = false; + final List result = new ArrayList<>(); + for (ASTNode node : parentNode.getChildren(null)) { + // start + if (node1 == node) { + insideRange = true; + } + if (insideRange) { + result.add(node.getPsi()); + } + // stop + if (node == node2) { + break; + } + } + return result; + } + + @RequiredReadAction + public static int getElementIndentation(final PsiElement element) { + final PsiElement compStatement = getStatementList(element); + final PsiElement statement = getParentRightBefore(element, compStatement); + if (statement == null) { + return 0; + } + PsiElement sibling = statement.getPrevSibling(); + if (sibling == null) { + sibling = compStatement.getPrevSibling(); + } + final String whitespace = sibling instanceof PsiWhiteSpace ? sibling.getText() : ""; + final int i = whitespace.lastIndexOf("\n"); + return i != -1 ? whitespace.length() - i - 1 : 0; + } + + @RequiredWriteAction + public static void removeRedundantPass(final PyStatementList statementList) { + final PyStatement[] statements = statementList.getStatements(); + if (statements.length > 1) { + for (PyStatement statement : statements) { + if (statement instanceof PyPassStatement) { + statement.delete(); + } + } + } + } + + public static boolean isMethodContext(final PsiElement element) { + final PsiNamedElement parent = PsiTreeUtil.getParentOfType(element, PyFile.class, PyFunction.class, PyClass.class); + // In case if element is inside method which is inside class + if (parent instanceof PyFunction && PsiTreeUtil.getParentOfType(parent, PyFile.class, PyClass.class) instanceof PyClass) { + return true; + } + return false; + } + + + @Nonnull + @RequiredReadAction + public static PsiElement getRealContext(@Nonnull final PsiElement element) { + assertValid(element); + final PsiFile file = element.getContainingFile(); + if (file instanceof PyExpressionCodeFragment) { + final PsiElement context = file.getContext(); + if (LOG.isDebugEnabled()) { + LOG.debug("PyPsiUtil.getRealContext(" + element + ") is called. Returned " + context + ". Element inside code fragment"); + } + return context != null ? context : element; + } + else { + if (LOG.isDebugEnabled()) { + LOG.debug("PyPsiUtil.getRealContext(" + element + ") is called. Returned " + element + "."); + } + return element; + } + } + + /** + * Removes comma closest to the given child node along with any whitespaces around. First following comma is checked and only + * then, if it doesn't exists, preceding one. + * + * @param element parent node + * @param child child node comma should be adjacent to + * @see #getAdjacentComma(PsiElement) + */ + @RequiredWriteAction + public static void deleteAdjacentCommaWithWhitespaces(@Nonnull PsiElement element, @Nonnull PsiElement child) { + final PsiElement commaNode = getAdjacentComma(child); + if (commaNode != null) { + final PsiElement nextNonWhitespace = getNextNonWhitespaceSibling(commaNode); + final PsiElement last = nextNonWhitespace == null ? element.getLastChild() : nextNonWhitespace.getPrevSibling(); + final PsiElement prevNonWhitespace = getPrevNonWhitespaceSibling(commaNode); + final PsiElement first = prevNonWhitespace == null ? element.getFirstChild() : prevNonWhitespace.getNextSibling(); + element.deleteChildRange(first, last); + } + } + + /** + * Returns comments preceding given elements as pair of the first and the last such comments. Comments should not be + * separated by any empty line. + * + * @param element element comments should be adjacent to + * @return described range or {@code null} if there are no such comments + */ + @Nullable + @RequiredReadAction + public static Couple getPrecedingComments(@Nonnull PsiElement element) { + PsiComment firstComment = null, lastComment = null; + overComments: + while (true) { + int newLinesCount = 0; + for (element = element.getPrevSibling(); element instanceof PsiWhiteSpace; element = element.getPrevSibling()) { + newLinesCount += StringUtil.getLineBreakCount(element.getText()); + if (newLinesCount > 1) { + break overComments; + } + } + if (element instanceof PsiComment) { + if (lastComment == null) { + lastComment = (PsiComment) element; + } + firstComment = (PsiComment) element; + } + else { + break; + } + } + return lastComment == null ? null : Couple.of(firstComment, lastComment); + } + + @Nonnull + public static List collectStubChildren( + U e, + final StubElement stub, + final IElementType elementType, + final Class itemClass + ) { + final List result = new ArrayList<>(); + if (stub != null) { + final List children = stub.getChildrenStubs(); + for (StubElement child : children) { + if (child.getStubType() == elementType) { + //noinspection unchecked + result.add((T) child.getPsi()); + } + } + } + else { + e.acceptChildren(new TopLevelVisitor() { + @Override + protected void checkAddElement(PsiElement node) { + if (node.getNode().getElementType() == elementType) { + //noinspection unchecked + result.add((T) node); + } + } + }); + } + return result; + } + + public static List collectAllStubChildren(PsiElement e, StubElement stub) { + final List result = new ArrayList<>(); + if (stub != null) { + final List children = stub.getChildrenStubs(); + for (StubElement child : children) { + //noinspection unchecked + result.add(child.getPsi()); + } + } + else { + e.acceptChildren(new TopLevelVisitor() { + @Override + protected void checkAddElement(PsiElement node) { + result.add(node); + } + }); + } + return result; + } + + public static int findArgumentIndex(PyCallExpression call, PsiElement argument) { + final PyExpression[] args = call.getArguments(); + for (int i = 0; i < args.length; i++) { + PyExpression expression = args[i]; + if (expression instanceof PyKeywordArgument) { + expression = ((PyKeywordArgument) expression).getValueExpression(); + } + expression = flattenParens(expression); + if (expression == argument) { + return i; + } + } + return -1; + } + + @Nullable + @RequiredReadAction + public static PyTargetExpression getAttribute(@Nonnull final PyFile file, @Nonnull final String name) { + PyTargetExpression attr = file.findTopLevelAttribute(name); + if (attr == null) { + for (PyFromImportStatement element : file.getFromImports()) { + PyReferenceExpression expression = element.getImportSource(); + if (expression == null) { + continue; + } + final PsiElement resolved = expression.getReference().resolve(); + if (resolved instanceof PyFile resolvedFile && resolved != file) { + return resolvedFile.findTopLevelAttribute(name); + } + } + } + return attr; + } + + @RequiredReadAction + public static List getAttributeValuesFromFile(@Nonnull PyFile file, @Nonnull String name) { + List result = new ArrayList<>(); + final PyTargetExpression attr = file.findTopLevelAttribute(name); + if (attr != null) { + sequenceToList(result, attr.findAssignedValue()); + } + return result; + } + + public static void sequenceToList(List result, PyExpression value) { + value = flattenParens(value); + if (value instanceof PySequenceExpression sequenceExpr) { + result.addAll(ContainerUtil.newArrayList(sequenceExpr.getElements())); + } + else { + result.add(value); + } + } + + public static List getStringValues(PyExpression[] elements) { + List results = new ArrayList<>(); + for (PyExpression element : elements) { + if (element instanceof PyStringLiteralExpression stringLiteral) { + results.add(stringLiteral.getStringValue()); + } + } + return results; + } + + @Nullable + public static PyExpression flattenParens(@Nullable PyExpression expr) { + while (expr instanceof PyParenthesizedExpression parenthesized) { + expr = parenthesized.getContainedExpression(); + } + return expr; + } + + @Nullable + public static String strValue(@Nullable PyExpression expression) { + return expression instanceof PyStringLiteralExpression stringLiteral ? stringLiteral.getStringValue() : null; + } + + public static boolean isBefore(@Nonnull final PsiElement element, @Nonnull final PsiElement element2) { + // TODO: From RubyPsiUtil, should be moved to PsiTreeUtil + return element.getTextOffset() <= element2.getTextOffset(); + } + + @Nullable + public static QualifiedName toQualifiedName(@Nullable PyExpression expr) { + return expr instanceof PyQualifiedExpression qualifiedExpr ? qualifiedExpr.asQualifiedName() : null; + } + + @Nullable + public static PyExpression getFirstQualifier(@Nonnull PyQualifiedExpression expr) { + final List expressions = unwindQualifiers(expr); + if (!expressions.isEmpty()) { + return expressions.get(0); + } + return null; + } + + @Nonnull + public static String toPath(@Nullable PyQualifiedExpression expr) { + if (expr != null) { + final QualifiedName qName = expr.asQualifiedName(); + if (qName != null) { + return qName.toString(); + } + final String name = expr.getName(); + if (name != null) { + return name; + } + } + return ""; + } + + @Nullable + public static QualifiedName asQualifiedName(@Nonnull PyQualifiedExpression expr) { + return fromReferenceChain(unwindQualifiers(expr)); + } + + @Nonnull + private static List unwindQualifiers(@Nonnull final PyQualifiedExpression expr) { + final List path = new LinkedList<>(); + PyQualifiedExpression e = expr; + while (e != null) { + path.add(0, e); + e = e.getQualifier() instanceof PyQualifiedExpression qualifiedExpr ? qualifiedExpr : null; + } + return path; + } + + @Nullable + private static QualifiedName fromReferenceChain(@Nonnull List components) { + final List componentNames = new ArrayList<>(components.size()); + for (PyExpression component : components) { + final String refName = component instanceof PyQualifiedExpression qualifiedExpr ? qualifiedExpr.getReferencedName() : null; + if (refName == null) { + return null; + } + componentNames.add(refName); + } + return QualifiedName.fromComponents(componentNames); + } + + /** + * Wrapper for {@link PsiUtilCore#ensureValid(PsiElement)} that skips nulls + */ + @RequiredReadAction + public static void assertValid(@Nullable final PsiElement element) { + if (element == null) { + return; + } + PsiUtilCore.ensureValid(element); + } + + public static void assertValid(@Nonnull final Module module) { + if (module.isDisposed()) { + throw new IllegalArgumentException(String.format("Module %s is disposed", module)); + } + } + + @Nonnull + public static PsiFileSystemItem getFileSystemItem(@Nonnull PsiElement element) { + if (element instanceof PsiFileSystemItem) { + return (PsiFileSystemItem) element; + } + return element.getContainingFile(); + } + + private static abstract class TopLevelVisitor extends PyRecursiveElementVisitor { + @Override + public void visitPyElement(final PyElement node) { + super.visitPyElement(node); + checkAddElement(node); + } + + @Override + public void visitPyClass(final PyClass node) { + checkAddElement(node); // do not recurse into functions + } + + @Override + public void visitPyFunction(final PyFunction node) { + checkAddElement(node); // do not recurse into classes + } + + protected abstract void checkAddElement(PsiElement node); + } } \ No newline at end of file