diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportHintAction.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportHintAction.java index 3b37b98a..37df779f 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportHintAction.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportHintAction.java @@ -16,68 +16,61 @@ package com.jetbrains.python.impl.codeInsight.imports; -import jakarta.annotation.Nonnull; - -import consulo.language.editor.intention.HighPriorityAction; -import consulo.language.editor.intention.HintAction; +import consulo.codeEditor.Editor; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.codeEditor.Editor; -import consulo.project.Project; +import consulo.language.editor.intention.HighPriorityAction; +import consulo.language.editor.intention.HintAction; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import jakarta.annotation.Nonnull; /** * @author yole */ public class AutoImportHintAction implements LocalQuickFix, HintAction, HighPriorityAction { - private final AutoImportQuickFix myDelegate; - - public AutoImportHintAction(AutoImportQuickFix delegate) { - myDelegate = delegate; - } + private final AutoImportQuickFix myDelegate; - @Override - public boolean showHint(@Nonnull Editor editor) { - return myDelegate.showHint(editor); - } + public AutoImportHintAction(AutoImportQuickFix delegate) { + myDelegate = delegate; + } - @Nonnull - @Override - public String getText() { - return myDelegate.getText(); - } + @Override + public boolean showHint(@Nonnull Editor editor) { + return myDelegate.showHint(editor); + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return myDelegate.isAvailable(); - } + @Nonnull + @Override + public LocalizeValue getText() { + return myDelegate.getText(); + } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - myDelegate.invoke(file); - } + @Override + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + return myDelegate.isAvailable(); + } - @Override - public boolean startInWriteAction() { - return false; - } + @Override + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + myDelegate.invoke(file); + } - @Nonnull - @Override - public String getName() { - return myDelegate.getName(); - } + @Override + public boolean startInWriteAction() { + return false; + } - @Nonnull - @Override - public String getFamilyName() { - return myDelegate.getFamilyName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return myDelegate.getName(); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - myDelegate.applyFix(project, descriptor); - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + myDelegate.applyFix(project, descriptor); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportQuickFix.java index 80f63682..51ef10f2 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/AutoImportQuickFix.java @@ -31,13 +31,16 @@ import consulo.language.psi.*; import consulo.language.psi.util.QualifiedName; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.module.content.ProjectFileIndex; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.util.collection.ContainerUtil; import consulo.virtualFileSystem.VirtualFile; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -52,227 +55,226 @@ * @author dcheryasov */ public class AutoImportQuickFix extends LocalQuickFixOnPsiElement implements HighPriorityAction { + private final List myImports; // from where and what to import + private final String myInitialName; + private final boolean myUseQualifiedImport; + private final Class myReferenceType; + private boolean myExpended = false; - private final List myImports; // from where and what to import - private final String myInitialName; - private final boolean myUseQualifiedImport; - private final Class myReferenceType; - private boolean myExpended = false; - - /** - * Creates a new, empty fix object. - * - * @param node to which the fix applies. - * @param referenceType - * @param name name to import - * @param qualify if true, add an "import ..." statement and qualify the name; else use "from ... import name" - */ - public AutoImportQuickFix(@Nonnull PsiElement node, - @Nonnull Class referenceType, - @Nonnull String name, - boolean qualify) { - this(node, referenceType, name, qualify, Collections.emptyList()); - } - - private AutoImportQuickFix(@Nonnull PsiElement node, - @Nonnull Class referenceType, - @Nonnull String name, - boolean qualify, - @Nonnull Collection candidates) { - super(node); - myReferenceType = referenceType; - myInitialName = name; - myUseQualifiedImport = qualify; - myImports = new ArrayList<>(candidates); - } - - /** - * Adds another import source. - * - * @param importable an element that could be imported either from import element or from file. - * @param file the file which is the source of the importable - * @param importElement an existing import element that can be a source for the importable. - */ - public void addImport(@Nonnull PsiElement importable, @Nonnull PsiFile file, @Nullable PyImportElement importElement) { - myImports.add(new ImportCandidateHolder(importable, file, importElement, null)); - } - - /** - * Adds another import source. - * - * @param importable an element that could be imported either from import element or from file. - * @param file the file which is the source of the importable - * @param path import path for the file, as a qualified name (a.b.c) - */ - public void addImport(@Nonnull PsiElement importable, @Nonnull PsiFileSystemItem file, @Nullable QualifiedName path) { - myImports.add(new ImportCandidateHolder(importable, file, null, path)); - } - - public void addImport(@Nonnull PsiElement importable, - @Nonnull PsiFileSystemItem file, - @Nullable QualifiedName path, - @Nullable String asName) { - myImports.add(new ImportCandidateHolder(importable, file, null, path, asName)); - } - - @Nonnull - public String getText() { - if (myUseQualifiedImport) { - return PyBundle.message("ACT.qualify.with.module"); + /** + * Creates a new, empty fix object. + * + * @param node to which the fix applies. + * @param referenceType + * @param name name to import + * @param qualify if true, add an "import ..." statement and qualify the name; else use "from ... import name" + */ + public AutoImportQuickFix( + @Nonnull PsiElement node, + @Nonnull Class referenceType, + @Nonnull String name, + boolean qualify + ) { + this(node, referenceType, name, qualify, Collections.emptyList()); } - else if (myImports.size() == 1) { - return PyBundle.message("QFIX.auto.import.import.name", myImports.get(0).getPresentableText(myInitialName)); - } - else { - return PyBundle.message("QFIX.auto.import.import.this.name"); - } - } - @Nonnull - public String getFamilyName() { - return PyBundle.message("QFIX.auto.import.family"); - } - - public boolean showHint(Editor editor) { - if (!PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP || - HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true) || - myImports.isEmpty()) { - return false; - } - final PsiElement element = getStartElement(); - PyPsiUtils.assertValid(element); - if (element == null || !element.isValid()) { - return false; + private AutoImportQuickFix( + @Nonnull PsiElement node, + @Nonnull Class referenceType, + @Nonnull String name, + boolean qualify, + @Nonnull Collection candidates + ) { + super(node); + myReferenceType = referenceType; + myInitialName = name; + myUseQualifiedImport = qualify; + myImports = new ArrayList<>(candidates); } - final PyElement pyElement = as(element, PyElement.class); - if (pyElement == null || !myInitialName.equals(pyElement.getName())) { - return false; - } - final PsiReference reference = findOriginalReference(element); - if (reference == null || isResolved(reference)) { - return false; + + /** + * Adds another import source. + * + * @param importable an element that could be imported either from import element or from file. + * @param file the file which is the source of the importable + * @param importElement an existing import element that can be a source for the importable. + */ + public void addImport(@Nonnull PsiElement importable, @Nonnull PsiFile file, @Nullable PyImportElement importElement) { + myImports.add(new ImportCandidateHolder(importable, file, importElement, null)); } - if (element instanceof PyQualifiedExpression && ((PyQualifiedExpression)element).isQualified()) { - return false; // we cannot be qualified + + /** + * Adds another import source. + * + * @param importable an element that could be imported either from import element or from file. + * @param file the file which is the source of the importable + * @param path import path for the file, as a qualified name (a.b.c) + */ + public void addImport(@Nonnull PsiElement importable, @Nonnull PsiFileSystemItem file, @Nullable QualifiedName path) { + myImports.add(new ImportCandidateHolder(importable, file, null, path)); } - final String message = AutoImportHelper.getInstance(element.getProject()).getImportMessage(myImports.size() > 1, - ImportCandidateHolder.getQualifiedName( - myInitialName, - myImports.get(0).getPath(), - myImports.get(0).getImportElement - ())); - final ImportFromExistingAction action = new ImportFromExistingAction(element, myImports, myInitialName, myUseQualifiedImport, false); - action.onDone(() -> myExpended = true); - HintManager.getInstance().showQuestionHint(editor, message, element.getTextOffset(), element.getTextRange().getEndOffset(), action); - return true; - } + public void addImport( + @Nonnull PsiElement importable, + @Nonnull PsiFileSystemItem file, + @Nullable QualifiedName path, + @Nullable String asName + ) { + myImports.add(new ImportCandidateHolder(importable, file, null, path, asName)); + } - public boolean isAvailable() { - final PsiElement element = getStartElement(); - if (element == null) { - return false; + @Nonnull + @Override + public LocalizeValue getText() { + if (myUseQualifiedImport) { + return PyLocalize.actQualifyWithModule(); + } + else if (myImports.size() == 1) { + return PyLocalize.qfixAutoImportImportName(myImports.get(0).getPresentableText(myInitialName)); + } + else { + return PyLocalize.qfixAutoImportImportThisName(); + } } - PyPsiUtils.assertValid(element); - return !myExpended && element.isValid() && !myImports.isEmpty(); - } - @Override - public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { - invoke(getStartElement().getContainingFile()); - } + public boolean showHint(Editor editor) { + if (!PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP || + HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true) || + myImports.isEmpty()) { + return false; + } + final PsiElement element = getStartElement(); + PyPsiUtils.assertValid(element); + if (element == null || !element.isValid()) { + return false; + } + final PyElement pyElement = as(element, PyElement.class); + if (pyElement == null || !myInitialName.equals(pyElement.getName())) { + return false; + } + final PsiReference reference = findOriginalReference(element); + if (reference == null || isResolved(reference)) { + return false; + } + if (element instanceof PyQualifiedExpression && ((PyQualifiedExpression) element).isQualified()) { + return false; // we cannot be qualified + } - public void invoke(PsiFile file) throws IncorrectOperationException { - // make sure file is committed, writable, etc - final PsiElement startElement = getStartElement(); - if (startElement == null) { - return; + final String message = AutoImportHelper.getInstance(element.getProject()).getImportMessage( + myImports.size() > 1, + ImportCandidateHolder.getQualifiedName( + myInitialName, + myImports.get(0).getPath(), + myImports.get(0).getImportElement + () + ) + ); + final ImportFromExistingAction action = + new ImportFromExistingAction(element, myImports, myInitialName, myUseQualifiedImport, false); + action.onDone(() -> myExpended = true); + HintManager.getInstance().showQuestionHint(editor, message, element.getTextOffset(), element.getTextRange().getEndOffset(), action); + return true; } - PyPsiUtils.assertValid(startElement); - if (!FileModificationService.getInstance().prepareFileForWrite(file)) { - return; + + public boolean isAvailable() { + final PsiElement element = getStartElement(); + if (element == null) { + return false; + } + PyPsiUtils.assertValid(element); + return !myExpended && element.isValid() && !myImports.isEmpty(); } - final PsiReference reference = findOriginalReference(startElement); - if (reference == null || isResolved(reference)) { - return; + + @Override + public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + invoke(getStartElement().getContainingFile()); } - // act - ImportFromExistingAction action = createAction(); - action.execute(); // assume that action runs in WriteAction on its own behalf - myExpended = true; - } - @Nonnull - protected ImportFromExistingAction createAction() { - return new ImportFromExistingAction(getStartElement(), myImports, myInitialName, myUseQualifiedImport, false); - } + public void invoke(PsiFile file) throws IncorrectOperationException { + // make sure file is committed, writable, etc + final PsiElement startElement = getStartElement(); + if (startElement == null) { + return; + } + PyPsiUtils.assertValid(startElement); + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; + } + final PsiReference reference = findOriginalReference(startElement); + if (reference == null || isResolved(reference)) { + return; + } + // act + ImportFromExistingAction action = createAction(); + action.execute(); // assume that action runs in WriteAction on its own behalf + myExpended = true; + } - public void sortCandidates() { - Collections.sort(myImports); - } + @Nonnull + protected ImportFromExistingAction createAction() { + return new ImportFromExistingAction(getStartElement(), myImports, myInitialName, myUseQualifiedImport, false); + } - @Nonnull - public List getCandidates() { - return Collections.unmodifiableList(myImports); - } + public void sortCandidates() { + Collections.sort(myImports); + } - public boolean hasOnlyFunctions() { - for (ImportCandidateHolder holder : myImports) { - if (!(holder.getImportable() instanceof PyFunction)) { - return false; - } + @Nonnull + public List getCandidates() { + return Collections.unmodifiableList(myImports); } - return true; - } - public boolean hasProjectImports() { - ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(getStartElement().getProject()); - for (ImportCandidateHolder anImport : myImports) { - VirtualFile file = anImport.getFile().getVirtualFile(); - if (file != null && fileIndex.isInContent(file)) { + public boolean hasOnlyFunctions() { + for (ImportCandidateHolder holder : myImports) { + if (!(holder.getImportable() instanceof PyFunction)) { + return false; + } + } return true; - } } - return false; - } - @Nonnull - public AutoImportQuickFix forLocalImport() { - return new AutoImportQuickFix(getStartElement(), myReferenceType, myInitialName, myUseQualifiedImport, myImports) { - @Nonnull - @Override - public String getFamilyName() { - return PyBundle.message("QFIX.local.auto.import.family"); - } + public boolean hasProjectImports() { + ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(getStartElement().getProject()); + for (ImportCandidateHolder anImport : myImports) { + VirtualFile file = anImport.getFile().getVirtualFile(); + if (file != null && fileIndex.isInContent(file)) { + return true; + } + } + return false; + } - @Nonnull - @Override - public String getText() { - return PyBundle.message("QFIX.local.auto.import.import.locally", super.getText()); - } + @Nonnull + public AutoImportQuickFix forLocalImport() { + return new AutoImportQuickFix(getStartElement(), myReferenceType, myInitialName, myUseQualifiedImport, myImports) { + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.qfixLocalAutoImportImportLocally(super.getText()); + } - @Nonnull - @Override - protected ImportFromExistingAction createAction() { - return new ImportFromExistingAction(getStartElement(), myImports, myInitialName, myUseQualifiedImport, true); - } - }; - } + @Nonnull + @Override + protected ImportFromExistingAction createAction() { + return new ImportFromExistingAction(getStartElement(), myImports, myInitialName, myUseQualifiedImport, true); + } + }; + } - @Nonnull - public String getNameToImport() { - return myInitialName; - } + @Nonnull + public String getNameToImport() { + return myInitialName; + } - private static boolean isResolved(@Nonnull PsiReference reference) { - if (reference instanceof PsiPolyVariantReference) { - return ((PsiPolyVariantReference)reference).multiResolve(false).length > 0; + private static boolean isResolved(@Nonnull PsiReference reference) { + if (reference instanceof PsiPolyVariantReference) { + return ((PsiPolyVariantReference) reference).multiResolve(false).length > 0; + } + return reference.resolve() != null; } - return reference.resolve() != null; - } - @Nullable - private PsiReference findOriginalReference(@Nonnull PsiElement element) { - return ContainerUtil.findInstance(element.getReferences(), myReferenceType); - } + @Nullable + private PsiReference findOriginalReference(@Nonnull PsiElement element) { + return ContainerUtil.findInstance(element.getReferences(), myReferenceType); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/OptimizeImportsQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/OptimizeImportsQuickFix.java index a4384501..951e8424 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/OptimizeImportsQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/imports/OptimizeImportsQuickFix.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.impl.codeInsight.imports; +import consulo.localize.LocalizeValue; import jakarta.annotation.Nonnull; import consulo.language.editor.intention.HighPriorityAction; import consulo.language.editor.intention.IntentionAction; @@ -32,62 +33,51 @@ /** * @author yole */ -public class OptimizeImportsQuickFix implements LocalQuickFix, IntentionAction, HighPriorityAction -{ +public class OptimizeImportsQuickFix implements LocalQuickFix, IntentionAction, HighPriorityAction { + @Nonnull + @Override + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Optimize imports"); + } - @Nonnull - @Override - public String getText() - { - return getName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return getText(); + } - @Nonnull - public String getFamilyName() - { - return "Optimize imports"; - } + @Override + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + return file instanceof PyFile; + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) - { - return file instanceof PyFile; - } + @Override + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + optimizeImports(project, file); + } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - optimizeImports(project, file); - } + @Override + public boolean startInWriteAction() { + return false; + } - @Override - public boolean startInWriteAction() - { - return false; - } + public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element == null) { // stale PSI + return; + } + final PsiFile file = element.getContainingFile(); + optimizeImports(project, file); + } - public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) - { - PsiElement element = descriptor.getPsiElement(); - if(element == null) - { // stale PSI - return; - } - final PsiFile file = element.getContainingFile(); - optimizeImports(project, file); - } - - private void optimizeImports(final Project project, final PsiFile file) - { - ImportOptimizer optimizer = new PyImportOptimizer(); - final Runnable runnable = optimizer.processFile(file); - new WriteCommandAction.Simple(project, getFamilyName(), file) - { - @Override - protected void run() throws Throwable - { - runnable.run(); - } - }.execute(); - } + private void optimizeImports(final Project project, final PsiFile file) { + ImportOptimizer optimizer = new PyImportOptimizer(); + final Runnable runnable = optimizer.processFile(file); + new WriteCommandAction.Simple(project, getName().get(), file) { + @Override + protected void run() throws Throwable { + runnable.run(); + } + }.execute(); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java index 9acd5dd0..059709b9 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java @@ -23,6 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -38,7 +39,6 @@ import consulo.language.psi.PsiWhiteSpace; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.impl.inspections.PyStringFormatParser; @@ -55,419 +55,348 @@ /** * Replaces expressions like "%s" % values with likes of "{0:s}".format(values). - *
- * Author: Alexey.Ivanov, dcheryasov + * + * @author Alexey.Ivanov + * @author dcheryasov */ -public class ConvertFormatOperatorToMethodIntention extends PyBaseIntentionAction -{ - - private static final Pattern FORMAT_PATTERN = Pattern.compile("%(?:\\((\\w+)\\))?([-#0+ ]*)((?:\\*|\\d+)?(?:\\.(?:\\*|\\d+))?)?[hlL]?([diouxXeEfFgGcrs%])"); - // groups: %:ignored, 1:key 2:mods 3:width-and---preci.sion x:len 4: conversion-type - - private static final Pattern BRACE_PATTERN = Pattern.compile("(\\{|\\})"); +public class ConvertFormatOperatorToMethodIntention extends PyBaseIntentionAction { + private static final Pattern FORMAT_PATTERN = + Pattern.compile("%(?:\\((\\w+)\\))?([-#0+ ]*)((?:\\*|\\d+)?(?:\\.(?:\\*|\\d+))?)?[hlL]?([diouxXeEfFgGcrs%])"); + // groups: %:ignored, 1:key 2:mods 3:width-and---preci.sion x:len 4: conversion-type - /** - * copy source to target, doubling every brace. - */ - private static void appendDoublingBraces(CharSequence source, StringBuilder target) - { - int index = 0; - Matcher scanner = BRACE_PATTERN.matcher(source); - boolean skipClosingBrace = false; - while(scanner.find(index)) - { - if(scanner.start() > 1) - { - // handle escaping sequences PY-977 - if("{".equals(scanner.group(0)) && "\\N".equals(source.subSequence(scanner.start() - 2, scanner.start()).toString())) - { - skipClosingBrace = true; - target.append(source.subSequence(index, scanner.end())); - index = scanner.end(); - continue; - } - } - if(skipClosingBrace && "}".equals(scanner.group(0))) - { - skipClosingBrace = false; - target.append(source.subSequence(index, scanner.end())); - index = scanner.end(); - continue; - } + private static final Pattern BRACE_PATTERN = Pattern.compile("(\\{|\\})"); - target.append(source.subSequence(index, scanner.start())); - if("{".equals(scanner.group(0))) - { - target.append("{{"); - } - else - { - target.append("}}"); - } - index = scanner.end(); - } - target.append(source.subSequence(index, source.length())); - } + /** + * copy source to target, doubling every brace. + */ + private static void appendDoublingBraces(CharSequence source, StringBuilder target) { + int index = 0; + Matcher scanner = BRACE_PATTERN.matcher(source); + boolean skipClosingBrace = false; + while (scanner.find(index)) { + if (scanner.start() > 1) { + // handle escaping sequences PY-977 + if ("{".equals(scanner.group(0)) && "\\N".equals(source.subSequence(scanner.start() - 2, scanner.start()).toString())) { + skipClosingBrace = true; + target.append(source.subSequence(index, scanner.end())); + index = scanner.end(); + continue; + } + } + if (skipClosingBrace && "}".equals(scanner.group(0))) { + skipClosingBrace = false; + target.append(source.subSequence(index, scanner.end())); + index = scanner.end(); + continue; + } - /** - * Converts format expressions inside a string - * - * @return a pair of string builder with resulting string expression and a flag which is true if formats inside use mapping by name. - */ - private static Pair convertFormat(PyStringLiteralExpression stringLiteralExpression, String prefix) - { - // python string may be made of several literals, all different - List constants = new ArrayList<>(); - boolean usesNamedFormat = false; - final List stringNodes = stringLiteralExpression.getStringNodes(); - sure(stringNodes); - sure(stringNodes.size() > 0); - for(ASTNode stringNode : stringNodes) - { - // preserve prefixes and quote form - CharSequence text = stringNode.getChars(); - int openPos = 0; - boolean hasPrefix = false; - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(String.valueOf(text)); - if(prefixLength != 0) - { - hasPrefix = true; - } - openPos += prefixLength; + target.append(source.subSequence(index, scanner.start())); + if ("{".equals(scanner.group(0))) { + target.append("{{"); + } + else { + target.append("}}"); + } + index = scanner.end(); + } + target.append(source.subSequence(index, source.length())); + } - char quote = text.charAt(openPos); - sure("\"'".indexOf(quote) >= 0); - if(text.length() - openPos >= 6) - { - // triple-quoted? - if(text.charAt(openPos + 1) == quote && text.charAt(openPos + 2) == quote) - { - openPos += 2; - } - } - int index = openPos + 1; // from quote to first in-string char - StringBuilder out = new StringBuilder(text.subSequence(0, openPos + 1)); - if(!hasPrefix) - { - out.insert(0, prefix); - } - Matcher scanner = FORMAT_PATTERN.matcher(text); - while(scanner.find(index)) - { - // store previous non-format part - appendDoublingBraces(text.subSequence(index, scanner.start()), out); - //out.append(text.subSequence(index, scanner.start())); - // unpack format - final String f_key = scanner.group(1); - final String f_modifier = scanner.group(2); - final String f_width = scanner.group(3); - String fConversion = scanner.group(4); - // convert to format()'s - if("%%".equals(scanner.group(0))) - { - // shortcut to put a literal % - out.append("%"); - } - else - { - sure(fConversion); - sure(!"%".equals(fConversion)); // a padded percent literal; can't bother to autoconvert, and in 3k % is different. - out.append("{"); - if(f_key != null) - { - out.append(f_key); - usesNamedFormat = true; - } - if("r".equals(fConversion)) - { - out.append("!r"); - } - // don't convert %s -> !s, for %s is the normal way to output the default representation - out.append(":"); - if(f_modifier != null) - { - out.append(convertFormatSpec(f_modifier, f_width, fConversion)); - } - if(f_width != null) - { - out.append(f_width); - } - if("i".equals(fConversion) || "u".equals(fConversion)) - { - out.append("d"); - } - else if(!"s".equals(fConversion) && !"r".equals(fConversion)) - { - out.append(fConversion); - } + /** + * Converts format expressions inside a string + * + * @return a pair of string builder with resulting string expression and a flag which is true if formats inside use mapping by name. + */ + private static Pair convertFormat(PyStringLiteralExpression stringLiteralExpression, String prefix) { + // python string may be made of several literals, all different + List constants = new ArrayList<>(); + boolean usesNamedFormat = false; + final List stringNodes = stringLiteralExpression.getStringNodes(); + sure(stringNodes); + sure(stringNodes.size() > 0); + for (ASTNode stringNode : stringNodes) { + // preserve prefixes and quote form + CharSequence text = stringNode.getChars(); + int openPos = 0; + boolean hasPrefix = false; + final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(String.valueOf(text)); + if (prefixLength != 0) { + hasPrefix = true; + } + openPos += prefixLength; - final int lastIndexOf = out.lastIndexOf(":"); - if(lastIndexOf == out.length() - 1) - { - out.deleteCharAt(lastIndexOf); - } - out.append("}"); - } - index = scanner.end(); - } - // store non-format final part - //out.append(text.subSequence(index, text.length()-1)); - appendDoublingBraces(text.subSequence(index, text.length()), out); - constants.add(out); - } - // form the entire literal filling possible gaps between constants. - // we assume that a string literal begins with its first constant, without a gap. - TextRange full_range = stringLiteralExpression.getTextRange(); - int full_start = full_range.getStartOffset(); - CharSequence full_text = stringLiteralExpression.getNode().getChars(); - TextRange prev_range = stringNodes.get(0).getTextRange(); - int fragment_no = 1; // look at second and further fragments - while(fragment_no < stringNodes.size()) - { - TextRange next_range = stringNodes.get(fragment_no).getTextRange(); - int left = prev_range.getEndOffset() - full_start; - int right = next_range.getStartOffset() - full_start; - if(left < right) - { - constants.get(fragment_no - 1).append(full_text.subSequence(left, right)); - } - fragment_no += 1; - prev_range = next_range; - } - final int left = prev_range.getEndOffset() - full_start; - final int right = full_range.getEndOffset() - full_start; - if(left < right) - { - // the barely possible case of last dangling "\" - constants.get(constants.size() - 1).append(full_text.subSequence(left, right)); - } + char quote = text.charAt(openPos); + sure("\"'".indexOf(quote) >= 0); + if (text.length() - openPos >= 6) { + // triple-quoted? + if (text.charAt(openPos + 1) == quote && text.charAt(openPos + 2) == quote) { + openPos += 2; + } + } + int index = openPos + 1; // from quote to first in-string char + StringBuilder out = new StringBuilder(text.subSequence(0, openPos + 1)); + if (!hasPrefix) { + out.insert(0, prefix); + } + Matcher scanner = FORMAT_PATTERN.matcher(text); + while (scanner.find(index)) { + // store previous non-format part + appendDoublingBraces(text.subSequence(index, scanner.start()), out); + //out.append(text.subSequence(index, scanner.start())); + // unpack format + final String f_key = scanner.group(1); + final String f_modifier = scanner.group(2); + final String f_width = scanner.group(3); + String fConversion = scanner.group(4); + // convert to format()'s + if ("%%".equals(scanner.group(0))) { + // shortcut to put a literal % + out.append("%"); + } + else { + sure(fConversion); + sure(!"%".equals(fConversion)); // a padded percent literal; can't bother to autoconvert, and in 3k % is different. + out.append("{"); + if (f_key != null) { + out.append(f_key); + usesNamedFormat = true; + } + if ("r".equals(fConversion)) { + out.append("!r"); + } + // don't convert %s -> !s, for %s is the normal way to output the default representation + out.append(":"); + if (f_modifier != null) { + out.append(convertFormatSpec(f_modifier, f_width, fConversion)); + } + if (f_width != null) { + out.append(f_width); + } + if ("i".equals(fConversion) || "u".equals(fConversion)) { + out.append("d"); + } + else if (!"s".equals(fConversion) && !"r".equals(fConversion)) { + out.append(fConversion); + } - // join everything - StringBuilder result = new StringBuilder(); - for(StringBuilder one : constants) - { - result.append(one); - } - return new Pair<>(result, usesNamedFormat); - } + final int lastIndexOf = out.lastIndexOf(":"); + if (lastIndexOf == out.length() - 1) { + out.deleteCharAt(lastIndexOf); + } + out.append("}"); + } + index = scanner.end(); + } + // store non-format final part + //out.append(text.subSequence(index, text.length()-1)); + appendDoublingBraces(text.subSequence(index, text.length()), out); + constants.add(out); + } + // form the entire literal filling possible gaps between constants. + // we assume that a string literal begins with its first constant, without a gap. + TextRange full_range = stringLiteralExpression.getTextRange(); + int full_start = full_range.getStartOffset(); + CharSequence full_text = stringLiteralExpression.getNode().getChars(); + TextRange prev_range = stringNodes.get(0).getTextRange(); + int fragment_no = 1; // look at second and further fragments + while (fragment_no < stringNodes.size()) { + TextRange next_range = stringNodes.get(fragment_no).getTextRange(); + int left = prev_range.getEndOffset() - full_start; + int right = next_range.getStartOffset() - full_start; + if (left < right) { + constants.get(fragment_no - 1).append(full_text.subSequence(left, right)); + } + fragment_no += 1; + prev_range = next_range; + } + final int left = prev_range.getEndOffset() - full_start; + final int right = full_range.getEndOffset() - full_start; + if (left < right) { + // the barely possible case of last dangling "\" + constants.get(constants.size() - 1).append(full_text.subSequence(left, right)); + } - @Nonnull - public static String convertFormatSpec(@Nonnull String modifier, @Nullable String widthAndPrecision, @Nullable String conversionChar) - { - final StringBuilder result = new StringBuilder(); - // in strict order - if(has(modifier, '-')) - { - result.append("<"); // left align - } - else if("s".equals(conversionChar) && !StringUtil.isEmptyOrSpaces(widthAndPrecision)) - { - // "%20s" aligns right, "{0:20s}" aligns left; to preserve align, make it explicit - result.append(">"); - } - if(has(modifier, '+')) - { - result.append("+"); // signed - } - else if(has(modifier, ' ')) - { - result.append(" "); // default-signed - } - if(has(modifier, '#')) - { - result.append("#"); // alt numbers - } - if(has(modifier, '0')) - { - result.append("0"); // padding - } - // anything else can't be here - return result.toString(); - } + // join everything + StringBuilder result = new StringBuilder(); + for (StringBuilder one : constants) { + result.append(one); + } + return new Pair<>(result, usesNamedFormat); + } - private static boolean has(String where, char what) - { - return where.indexOf(what) >= 0; - } + @Nonnull + public static String convertFormatSpec(@Nonnull String modifier, @Nullable String widthAndPrecision, @Nullable String conversionChar) { + final StringBuilder result = new StringBuilder(); + // in strict order + if (has(modifier, '-')) { + result.append("<"); // left align + } + else if ("s".equals(conversionChar) && !StringUtil.isEmptyOrSpaces(widthAndPrecision)) { + // "%20s" aligns right, "{0:20s}" aligns left; to preserve align, make it explicit + result.append(">"); + } + if (has(modifier, '+')) { + result.append("+"); // signed + } + else if (has(modifier, ' ')) { + result.append(" "); // default-signed + } + if (has(modifier, '#')) { + result.append("#"); // alt numbers + } + if (has(modifier, '0')) { + result.append("0"); // padding + } + // anything else can't be here + return result.toString(); + } - @Nonnull - public String getFamilyName() - { - return PyBundle.message("INTN.format.operator.to.method"); - } + private static boolean has(String where, char what) { + return where.indexOf(what) >= 0; + } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) - { - if(!(file instanceof PyFile)) - { - return false; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - PyBinaryExpression binaryExpression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyBinaryExpression.class, false); - if(binaryExpression == null) - { - return false; - } - final LanguageLevel languageLevel = LanguageLevel.forElement(binaryExpression); - if(languageLevel.isOlderThan(LanguageLevel.PYTHON26)) - { - return false; - } - if(binaryExpression.getLeftExpression() instanceof PyStringLiteralExpression && binaryExpression.getOperator() == PyTokenTypes.PERC) - { - final PyStringLiteralExpression str = (PyStringLiteralExpression) binaryExpression.getLeftExpression(); - if((str.getText().length() > 0 && Character.toUpperCase(str.getText().charAt(0)) == 'B')) - { - return false; - } + PyBinaryExpression binaryExpression = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyBinaryExpression.class, false); + if (binaryExpression == null) { + return false; + } + final LanguageLevel languageLevel = LanguageLevel.forElement(binaryExpression); + if (languageLevel.isOlderThan(LanguageLevel.PYTHON26)) { + return false; + } + if (binaryExpression.getLeftExpression() instanceof PyStringLiteralExpression && binaryExpression.getOperator() == PyTokenTypes.PERC) { + final PyStringLiteralExpression str = (PyStringLiteralExpression) binaryExpression.getLeftExpression(); + if ((str.getText().length() > 0 && Character.toUpperCase(str.getText().charAt(0)) == 'B')) { + return false; + } - final List chunks = PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parsePercentFormat(binaryExpression.getLeftExpression().getText - ())); + final List chunks = + PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parsePercentFormat( + binaryExpression.getLeftExpression().getText() + )); - for(PyStringFormatParser.SubstitutionChunk chunk : chunks) - { - if("*".equals(chunk.getWidth()) || "*".equals(chunk.getPrecision())) - { - return false; - } - } + for (PyStringFormatParser.SubstitutionChunk chunk : chunks) { + if ("*".equals(chunk.getWidth()) || "*".equals(chunk.getPrecision())) { + return false; + } + } - setText(PyBundle.message("INTN.replace.with.method")); - return true; - } - return false; - } + setText(PyLocalize.intnReplaceWithMethod()); + return true; + } + return false; + } - @Override - public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - final PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset()); - final PyBinaryExpression element = PsiTreeUtil.getParentOfType(elementAt, PyBinaryExpression.class, false); - if(element == null) - { - return; - } - final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - final PyExpression rightExpression = sure(element).getRightExpression(); - if(rightExpression == null) - { - return; - } - final PyExpression rhs = PyPsiUtils.flattenParens(rightExpression); - if(rhs == null) - { - return; - } - final String paramText = sure(rhs).getText(); - final TypeEvalContext context = TypeEvalContext.userInitiated(file.getProject(), file); - final PyType rhsType = context.getType(rhs); - String prefix = ""; - final LanguageLevel languageLevel = guessLanguageLevel(project); - if(!languageLevel.isPy3K() && PyTypeChecker.match(PyBuiltinCache.getInstance(rhs).getObjectType("unicode"), rhsType, context)) - { - prefix = "u"; - } - final PyStringLiteralExpression leftExpression = (PyStringLiteralExpression) element.getLeftExpression(); - final Pair converted = convertFormat(leftExpression, prefix); - final StringBuilder target = converted.getFirst(); - final String separator = getSeparator(leftExpression); - target.append(separator).append(".format"); + @Override + public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset()); + final PyBinaryExpression element = PsiTreeUtil.getParentOfType(elementAt, PyBinaryExpression.class, false); + if (element == null) { + return; + } + final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + final PyExpression rightExpression = sure(element).getRightExpression(); + if (rightExpression == null) { + return; + } + final PyExpression rhs = PyPsiUtils.flattenParens(rightExpression); + if (rhs == null) { + return; + } + final String paramText = sure(rhs).getText(); + final TypeEvalContext context = TypeEvalContext.userInitiated(file.getProject(), file); + final PyType rhsType = context.getType(rhs); + String prefix = ""; + final LanguageLevel languageLevel = guessLanguageLevel(project); + if (!languageLevel.isPy3K() && PyTypeChecker.match(PyBuiltinCache.getInstance(rhs).getObjectType("unicode"), rhsType, context)) { + prefix = "u"; + } + final PyStringLiteralExpression leftExpression = (PyStringLiteralExpression) element.getLeftExpression(); + final Pair converted = convertFormat(leftExpression, prefix); + final StringBuilder target = converted.getFirst(); + final String separator = getSeparator(leftExpression); + target.append(separator).append(".format"); - if(rhs instanceof PyReferenceExpression && rhsType instanceof PyTupleType) - { - target.append("(*").append(paramText).append(")"); - } - else if(rhs instanceof PyCallExpression) - { // potential dict(foo=1) -> format(foo=1) - final PyCallExpression callExpression = (PyCallExpression) rhs; - final PyExpression callee = callExpression.getCallee(); - final PyClassType classType = PyUtil.as(rhsType, PyClassType.class); - if(classType != null && callee != null && isDictCall(callee, classType)) - { - target.append(sure(sure(callExpression.getArgumentList()).getNode()).getChars()); - } - else - { // just a call, reuse - target.append("("); - if(converted.getSecond()) - { - target.append("**"); // map-by-name formatting was detected - } - target.append(paramText).append(")"); - } - } - else if(rhsType instanceof PyCollectionType && "dict".equals(rhsType.getName())) - { - target.append("(**").append(paramText).append(")"); - } - else - { - target.append("(").append(paramText).append(")"); // tuple is ok as is - } - // Correctly handle multiline implicitly concatenated string literals (PY-9176) - target.insert(0, '(').append(')'); - final PyExpression parenthesized = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), target.toString()); - element.replace(sure(((PyParenthesizedExpression) parenthesized).getContainedExpression())); - } + if (rhs instanceof PyReferenceExpression && rhsType instanceof PyTupleType) { + target.append("(*").append(paramText).append(")"); + } + else if (rhs instanceof PyCallExpression) { // potential dict(foo=1) -> format(foo=1) + final PyCallExpression callExpression = (PyCallExpression) rhs; + final PyExpression callee = callExpression.getCallee(); + final PyClassType classType = PyUtil.as(rhsType, PyClassType.class); + if (classType != null && callee != null && isDictCall(callee, classType)) { + target.append(sure(sure(callExpression.getArgumentList()).getNode()).getChars()); + } + else { // just a call, reuse + target.append("("); + if (converted.getSecond()) { + target.append("**"); // map-by-name formatting was detected + } + target.append(paramText).append(")"); + } + } + else if (rhsType instanceof PyCollectionType && "dict".equals(rhsType.getName())) { + target.append("(**").append(paramText).append(")"); + } + else { + target.append("(").append(paramText).append(")"); // tuple is ok as is + } + // Correctly handle multiline implicitly concatenated string literals (PY-9176) + target.insert(0, '(').append(')'); + final PyExpression parenthesized = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), target.toString()); + element.replace(sure(((PyParenthesizedExpression) parenthesized).getContainedExpression())); + } - private static boolean isDictCall(@Nonnull PyExpression callee, @Nonnull PyClassType classType) - { - final PyClassType dictType = PyBuiltinCache.getInstance(callee.getContainingFile()).getDictType(); - if(dictType != null && classType.getPyClass() == dictType.getPyClass()) - { - if(callee instanceof PyReferenceExpression) - { - PsiElement maybeDict = ((PyReferenceExpression) callee).getReference().resolve(); - final PyFunction dictInit = PyUtil.as(maybeDict, PyFunction.class); - if(dictInit != null) - { - if(PyNames.INIT.equals(dictInit.getName())) - { - return true; - } - } - } - } - return false; - } + private static boolean isDictCall(@Nonnull PyExpression callee, @Nonnull PyClassType classType) { + final PyClassType dictType = PyBuiltinCache.getInstance(callee.getContainingFile()).getDictType(); + if (dictType != null && classType.getPyClass() == dictType.getPyClass()) { + if (callee instanceof PyReferenceExpression) { + PsiElement maybeDict = ((PyReferenceExpression) callee).getReference().resolve(); + final PyFunction dictInit = PyUtil.as(maybeDict, PyFunction.class); + if (dictInit != null) { + if (PyNames.INIT.equals(dictInit.getName())) { + return true; + } + } + } + } + return false; + } - private static String getSeparator(PyStringLiteralExpression leftExpression) - { - String separator = ""; // detect nontrivial whitespace around the "%" - Pair crop = collectWhitespace(leftExpression); - String maybeSeparator = crop.getFirst(); - if(maybeSeparator != null && !maybeSeparator.isEmpty() && !" ".equals(maybeSeparator)) - { - separator = maybeSeparator; - } - else - { // after "%" - crop = collectWhitespace(crop.getSecond()); - maybeSeparator = crop.getFirst(); - if(maybeSeparator != null && !maybeSeparator.isEmpty() && !" ".equals(maybeSeparator)) - { - separator = maybeSeparator; - } - } - return separator; - } + private static String getSeparator(PyStringLiteralExpression leftExpression) { + String separator = ""; // detect nontrivial whitespace around the "%" + Pair crop = collectWhitespace(leftExpression); + String maybeSeparator = crop.getFirst(); + if (maybeSeparator != null && !maybeSeparator.isEmpty() && !" ".equals(maybeSeparator)) { + separator = maybeSeparator; + } + else { // after "%" + crop = collectWhitespace(crop.getSecond()); + maybeSeparator = crop.getFirst(); + if (maybeSeparator != null && !maybeSeparator.isEmpty() && !" ".equals(maybeSeparator)) { + separator = maybeSeparator; + } + } + return separator; + } - private static Pair collectWhitespace(PsiElement start) - { - StringBuilder sb = new StringBuilder(); - PsiElement seeker = start; - while(seeker != null) - { - seeker = seeker.getNextSibling(); - if(seeker != null && seeker instanceof PsiWhiteSpace) - { - sb.append(seeker.getText()); - } - else - { - break; - } - } - return Pair.create(sb.toString(), seeker); - } + private static Pair collectWhitespace(PsiElement start) { + StringBuilder sb = new StringBuilder(); + PsiElement seeker = start; + while (seeker != null) { + seeker = seeker.getNextSibling(); + if (seeker != null && seeker instanceof PsiWhiteSpace) { + sb.append(seeker.getText()); + } + else { + break; + } + } + return Pair.create(sb.toString(), seeker); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertVariadicParamIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertVariadicParamIntention.java index b7e05651..2c9ac2d9 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertVariadicParamIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ConvertVariadicParamIntention.java @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.codeInsight.intentions; -import consulo.language.editor.intention.BaseIntentionAction; -import consulo.language.ast.ASTNode; +import com.jetbrains.python.psi.*; import consulo.codeEditor.Editor; -import consulo.project.Project; +import consulo.language.ast.ASTNode; +import consulo.language.editor.intention.BaseIntentionAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.util.collection.Stack; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.psi.*; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -34,208 +34,212 @@ import java.util.List; /** - * User: catherine - * * Intention to convert variadic parameter(s) to normal * For instance, * * from: * def foo(**kwargs): - * doSomething(kwargs['foo']) + * doSomething(kwargs['foo']) * * to: * def foo(foo, **kwargs): - * doSomething(foo) + * doSomething(foo) * + * @author catherine */ public class ConvertVariadicParamIntention extends BaseIntentionAction { - @Nonnull - public String getText() { - return PyBundle.message("INTN.convert.variadic.param"); - } - - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.convert.variadic.param"); - } - - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnConvertVariadicParam(); } - PyFunction function = - PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyFunction.class); - if (function != null) { - PyParameter[] parameterList = function.getParameterList().getParameters(); - for (PyParameter parameter : parameterList) { - if (parameter instanceof PyNamedParameter) { - if (((PyNamedParameter)parameter).isKeywordContainer()) { - List subscriptions = fillSubscriptions(function); - List callElements = fillCallExpressions(function); - if ((subscriptions.size() + callElements.size()) != 0) - return true; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; } - } - } - return false; - } - - @Nullable - private static PyParameter getKeywordContainer(PyFunction function) { - if (function != null) { - PyParameter[] parameterList = function.getParameterList().getParameters(); - for (PyParameter parameter : parameterList) { - if (parameter instanceof PyNamedParameter) { - if (((PyNamedParameter)parameter).isKeywordContainer()) { - return parameter; - } + + PyFunction function = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyFunction.class); + if (function != null) { + PyParameter[] parameterList = function.getParameterList().getParameters(); + for (PyParameter parameter : parameterList) { + if (parameter instanceof PyNamedParameter) { + if (((PyNamedParameter) parameter).isKeywordContainer()) { + List subscriptions = fillSubscriptions(function); + List callElements = fillCallExpressions(function); + if ((subscriptions.size() + callElements.size()) != 0) { + return true; + } + } + } + } } - } + return false; } - return null; - } - - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - PyFunction function = - PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyFunction.class); - replaceSubscriptions(function, project); - replaceCallElements(function, project); - } - - /** - * finds subscriptions of keyword container, adds them to mySubscriptions - * @param function - */ - private static List fillSubscriptions(PyFunction function) { - List subscriptions = new ArrayList(); - PyStatementList statementList = function.getStatementList(); - Stack stack = new Stack(); - PyParameter keywordContainer = getKeywordContainer(function); - if (keywordContainer != null && statementList != null) { - String keywordContainerName = keywordContainer.getName(); - for (PyStatement st : statementList.getStatements()) { - stack.push(st); - while (!stack.isEmpty()) { - PsiElement e = stack.pop(); - if (e instanceof PySubscriptionExpression) { - if (((PySubscriptionExpression)e).getOperand().getText().equals(keywordContainerName)) { - subscriptions.add((PySubscriptionExpression)e); - } - } - else { - for (PsiElement psiElement : e.getChildren()) { - stack.push(psiElement); + + @Nullable + private static PyParameter getKeywordContainer(PyFunction function) { + if (function != null) { + PyParameter[] parameterList = function.getParameterList().getParameters(); + for (PyParameter parameter : parameterList) { + if (parameter instanceof PyNamedParameter) { + if (((PyNamedParameter) parameter).isKeywordContainer()) { + return parameter; + } + } } - } } - } + return null; } - return subscriptions; - } - - private static boolean isCallElement(PyExpression callee, String keywordContainerName) { - PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier(); - return (qualifier != null && qualifier.getText().equals(keywordContainerName) - && ("get".equals(((PyQualifiedExpression)callee).getReferencedName()) - || "__getitem__".equals(((PyQualifiedExpression)callee).getReferencedName()) )); - } - - private static List fillCallExpressions(PyFunction function) { - List callElements = new ArrayList(); - PyStatementList statementList = function.getStatementList(); - Stack stack = new Stack(); - PyParameter keywordContainer = getKeywordContainer(function); - if (keywordContainer != null && statementList != null) { - String keywordContainerName = keywordContainer.getName(); - for (PyStatement st : statementList.getStatements()) { - stack.push(st); - while (!stack.isEmpty()) { - PsiElement e = stack.pop(); - if (!(e instanceof PySubscriptionExpression)) { - if (e instanceof PyCallExpression && ((PyCallExpression)e).getCallee() instanceof PyQualifiedExpression - && isCallElement(((PyCallExpression)e).getCallee(), keywordContainerName)) { - callElements.add((PyCallExpression)e); - } - else { - for (PsiElement psiElement : e.getChildren()) { - stack.push(psiElement); - } + + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyFunction function = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyFunction.class); + replaceSubscriptions(function, project); + replaceCallElements(function, project); + } + + /** + * finds subscriptions of keyword container, adds them to mySubscriptions + * + * @param function + */ + private static List fillSubscriptions(PyFunction function) { + List subscriptions = new ArrayList(); + PyStatementList statementList = function.getStatementList(); + Stack stack = new Stack(); + PyParameter keywordContainer = getKeywordContainer(function); + if (keywordContainer != null && statementList != null) { + String keywordContainerName = keywordContainer.getName(); + for (PyStatement st : statementList.getStatements()) { + stack.push(st); + while (!stack.isEmpty()) { + PsiElement e = stack.pop(); + if (e instanceof PySubscriptionExpression) { + if (((PySubscriptionExpression) e).getOperand().getText().equals(keywordContainerName)) { + subscriptions.add((PySubscriptionExpression) e); + } + } + else { + for (PsiElement psiElement : e.getChildren()) { + stack.push(psiElement); + } + } + } } - } } - } + return subscriptions; } - return callElements; - } - - private static void replaceSubscriptions(PyFunction function, Project project) { - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - List subscriptions = fillSubscriptions(function); - int size = subscriptions.size(); - for (int i = 0; i != size; ++i) { - PySubscriptionExpression subscriptionExpression = subscriptions.get(i); - PyExpression indexExpression = subscriptionExpression.getIndexExpression(); - if (indexExpression instanceof PyStringLiteralExpression) { - PyExpression p = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), - ((PyStringLiteralExpression)indexExpression).getStringValue()); - ASTNode comma = elementGenerator.createComma(); - PyClass containingClass = function.getContainingClass(); - if (p != null) { - if (containingClass == null) { - function.getParameterList().addBefore(p, function.getParameterList().getParameters()[0]); - function.getParameterList().addBefore((PsiElement)comma, function.getParameterList().getParameters()[0]); - } - else { - function.getParameterList().addBefore(p, function.getParameterList().getParameters()[1]); - function.getParameterList().addBefore((PsiElement)comma, function.getParameterList().getParameters()[1]); - } - subscriptionExpression.replace(p); - } - } + + private static boolean isCallElement(PyExpression callee, String keywordContainerName) { + PyExpression qualifier = ((PyQualifiedExpression) callee).getQualifier(); + return (qualifier != null && qualifier.getText().equals(keywordContainerName) + && ("get".equals(((PyQualifiedExpression) callee).getReferencedName()) + || "__getitem__".equals(((PyQualifiedExpression) callee).getReferencedName()))); } - } - - private static void replaceCallElements(PyFunction function, Project project) { - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - List callElements = fillCallExpressions(function); - - int size = callElements.size(); - for (int i = 0; i != size; ++i) { - PyCallExpression callExpression = callElements.get(i); - PyExpression indexExpression = callExpression.getArguments()[0]; - - if (indexExpression instanceof PyStringLiteralExpression) { - PyNamedParameter defaultValue = null; - if (callExpression.getArguments().length > 1) { - defaultValue = elementGenerator.createParameter( - ((PyStringLiteralExpression)indexExpression).getStringValue() - + "=" + callExpression.getArguments()[1].getText()); - } - if (defaultValue == null) { - PyExpression callee = callExpression.getCallee(); - if (callee instanceof PyQualifiedExpression && "get".equals(((PyQualifiedExpression)callee).getReferencedName())) { - defaultValue = elementGenerator.createParameter(((PyStringLiteralExpression)indexExpression).getStringValue() + "=None"); - } - } - PyExpression p = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), - ((PyStringLiteralExpression)indexExpression).getStringValue()); - ASTNode comma = elementGenerator.createComma(); + private static List fillCallExpressions(PyFunction function) { + List callElements = new ArrayList(); + PyStatementList statementList = function.getStatementList(); + Stack stack = new Stack(); PyParameter keywordContainer = getKeywordContainer(function); + if (keywordContainer != null && statementList != null) { + String keywordContainerName = keywordContainer.getName(); + for (PyStatement st : statementList.getStatements()) { + stack.push(st); + while (!stack.isEmpty()) { + PsiElement e = stack.pop(); + if (!(e instanceof PySubscriptionExpression)) { + if (e instanceof PyCallExpression && ((PyCallExpression) e).getCallee() instanceof PyQualifiedExpression + && isCallElement(((PyCallExpression) e).getCallee(), keywordContainerName)) { + callElements.add((PyCallExpression) e); + } + else { + for (PsiElement psiElement : e.getChildren()) { + stack.push(psiElement); + } + } + } + } + } + } + return callElements; + } - if (p != null) { - if (defaultValue != null) - function.getParameterList().addBefore(defaultValue, keywordContainer); - else - function.getParameterList().addBefore(p, keywordContainer); - - function.getParameterList().addBefore((PsiElement)comma, keywordContainer); + private static void replaceSubscriptions(PyFunction function, Project project) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + List subscriptions = fillSubscriptions(function); + int size = subscriptions.size(); + for (int i = 0; i != size; ++i) { + PySubscriptionExpression subscriptionExpression = subscriptions.get(i); + PyExpression indexExpression = subscriptionExpression.getIndexExpression(); + if (indexExpression instanceof PyStringLiteralExpression) { + PyExpression p = elementGenerator.createExpressionFromText( + LanguageLevel.forElement(function), + ((PyStringLiteralExpression) indexExpression).getStringValue() + ); + ASTNode comma = elementGenerator.createComma(); + PyClass containingClass = function.getContainingClass(); + if (p != null) { + if (containingClass == null) { + function.getParameterList().addBefore(p, function.getParameterList().getParameters()[0]); + function.getParameterList().addBefore((PsiElement) comma, function.getParameterList().getParameters()[0]); + } + else { + function.getParameterList().addBefore(p, function.getParameterList().getParameters()[1]); + function.getParameterList().addBefore((PsiElement) comma, function.getParameterList().getParameters()[1]); + } + subscriptionExpression.replace(p); + } + } + } + } - callExpression.replace(p); + private static void replaceCallElements(PyFunction function, Project project) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + List callElements = fillCallExpressions(function); + + int size = callElements.size(); + for (int i = 0; i != size; ++i) { + PyCallExpression callExpression = callElements.get(i); + PyExpression indexExpression = callExpression.getArguments()[0]; + + if (indexExpression instanceof PyStringLiteralExpression) { + PyNamedParameter defaultValue = null; + if (callExpression.getArguments().length > 1) { + defaultValue = elementGenerator.createParameter( + ((PyStringLiteralExpression) indexExpression).getStringValue() + + "=" + callExpression.getArguments()[1].getText()); + } + if (defaultValue == null) { + PyExpression callee = callExpression.getCallee(); + if (callee instanceof PyQualifiedExpression && "get".equals(((PyQualifiedExpression) callee).getReferencedName())) { + defaultValue = + elementGenerator.createParameter(((PyStringLiteralExpression) indexExpression).getStringValue() + "=None"); + } + } + PyExpression p = elementGenerator.createExpressionFromText( + LanguageLevel.forElement(function), + ((PyStringLiteralExpression) indexExpression).getStringValue() + ); + ASTNode comma = elementGenerator.createComma(); + + PyParameter keywordContainer = getKeywordContainer(function); + + if (p != null) { + if (defaultValue != null) { + function.getParameterList().addBefore(defaultValue, keywordContainer); + } + else { + function.getParameterList().addBefore(p, keywordContainer); + } + + function.getParameterList().addBefore((PsiElement) comma, keywordContainer); + + callExpression.replace(p); + } + } } - } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportFromToImportIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportFromToImportIntention.java index 95d0fb88..74449bd0 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportFromToImportIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportFromToImportIntention.java @@ -29,11 +29,14 @@ import consulo.language.psi.resolve.PsiElementProcessor; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.ui.NotificationType; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import static com.jetbrains.python.impl.codeInsight.intentions.DeclarationConflictChecker.findDefinitions; @@ -46,281 +49,284 @@ *
  • {@code from ...module_name import names} into {@code from ... import module_name};
  • *
  • {@code from ...moduleA.moduleB import names} into {@code from ...moduleA import moduleB}.
  • * Qualifies any names imported from that module by module name. - *
    - * User: dcheryasov - * Date: Sep 26, 2009 9:12:28 AM - * + * + * + * @author dcheryasov + * @since 2009-09-26 */ public class ImportFromToImportIntention extends PyBaseIntentionAction { - /** - * This class exists to extract bunches of info we can't store in our stateless instance. - * Instead, we store it per thread. - */ - private static class InfoHolder { - PyFromImportStatement myFromImportStatement = null; - PyReferenceExpression myModuleReference = null; - String myModuleName = null; - int myRelativeLevel = 0; + /** + * This class exists to extract bunches of info we can't store in our stateless instance. + * Instead, we store it per thread. + */ + private static class InfoHolder { + PyFromImportStatement myFromImportStatement = null; + PyReferenceExpression myModuleReference = null; + String myModuleName = null; + int myRelativeLevel = 0; - public String getText() { - String name = myModuleName != null ? myModuleName : "..."; - if (myRelativeLevel > 0) { - String[] relative_names = getRelativeNames(false, this); - return PyBundle.message("INTN.convert.to.from.$0.import.$1", relative_names[0], relative_names[1]); - } - else { - return PyBundle.message("INTN.convert.to.import.$0", name); - } - } + public LocalizeValue getText() { + String name = myModuleName != null ? myModuleName : "..."; + if (myRelativeLevel > 0) { + String[] relative_names = getRelativeNames(false, this); + return PyLocalize.intnConvertToFrom$0Import$1(relative_names[0], relative_names[1]); + } + else { + return PyLocalize.intnConvertToImport$0(name); + } + } - public static InfoHolder collect(final PsiElement position) { - InfoHolder ret = new InfoHolder(); - ret.myModuleReference = null; - ret.myFromImportStatement = PsiTreeUtil.getParentOfType(position, PyFromImportStatement.class); - PyPsiUtils.assertValid(ret.myFromImportStatement); - if (ret.myFromImportStatement != null && !ret.myFromImportStatement.isFromFuture()) { - ret.myRelativeLevel = ret.myFromImportStatement.getRelativeLevel(); - ret.myModuleReference = ret.myFromImportStatement.getImportSource(); - } - if (ret.myModuleReference != null) { - ret.myModuleName = PyPsiUtils.toPath(ret.myModuleReference); - } - return ret; + public static InfoHolder collect(final PsiElement position) { + InfoHolder ret = new InfoHolder(); + ret.myModuleReference = null; + ret.myFromImportStatement = PsiTreeUtil.getParentOfType(position, PyFromImportStatement.class); + PyPsiUtils.assertValid(ret.myFromImportStatement); + if (ret.myFromImportStatement != null && !ret.myFromImportStatement.isFromFuture()) { + ret.myRelativeLevel = ret.myFromImportStatement.getRelativeLevel(); + ret.myModuleReference = ret.myFromImportStatement.getImportSource(); + } + if (ret.myModuleReference != null) { + ret.myModuleName = PyPsiUtils.toPath(ret.myModuleReference); + } + return ret; + } } - } - @Nullable - private static PsiElement getElementFromEditor(Editor editor, PsiFile file) { - PsiElement element = null; - Document doc = editor.getDocument(); - PsiFile a_file = file; - if (a_file == null) { - Project project = editor.getProject(); - if (project != null) { - a_file = PsiDocumentManager.getInstance(project).getPsiFile(doc); - } - } - if (a_file != null) { - element = a_file.findElementAt(editor.getCaretModel().getOffset()); + @Nullable + private static PsiElement getElementFromEditor(Editor editor, PsiFile file) { + PsiElement element = null; + Document doc = editor.getDocument(); + PsiFile a_file = file; + if (a_file == null) { + Project project = editor.getProject(); + if (project != null) { + a_file = PsiDocumentManager.getInstance(project).getPsiFile(doc); + } + } + if (a_file != null) { + element = a_file.findElementAt(editor.getCaretModel().getOffset()); + } + return element; } - return element; - } - // given module "...foo.bar". returns "...foo" and "bar"; if not strict, undefined names become "?". - @Nullable - private static String[] getRelativeNames(boolean strict, InfoHolder info) { - String remaining_name = "?"; - String separated_name = "?"; - boolean failure = true; - if (info.myModuleReference != null) { - PyExpression remaining_module = info.myModuleReference.getQualifier(); - if (remaining_module instanceof PyQualifiedExpression) { - remaining_name = PyPsiUtils.toPath((PyQualifiedExpression)remaining_module); - } - else { - remaining_name = ""; // unqualified name: "...module" - } - separated_name = info.myModuleReference.getReferencedName(); - failure = false; - if (separated_name == null) { - separated_name = "?"; - failure = true; - } - } - if (strict && failure) { - return null; - } - else { - return new String[]{ - getDots(info.myRelativeLevel) + remaining_name, - separated_name - }; + // given module "...foo.bar". returns "...foo" and "bar"; if not strict, undefined names become "?". + @Nullable + private static String[] getRelativeNames(boolean strict, InfoHolder info) { + String remaining_name = "?"; + String separated_name = "?"; + boolean failure = true; + if (info.myModuleReference != null) { + PyExpression remaining_module = info.myModuleReference.getQualifier(); + if (remaining_module instanceof PyQualifiedExpression) { + remaining_name = PyPsiUtils.toPath((PyQualifiedExpression) remaining_module); + } + else { + remaining_name = ""; // unqualified name: "...module" + } + separated_name = info.myModuleReference.getReferencedName(); + failure = false; + if (separated_name == null) { + separated_name = "?"; + failure = true; + } + } + if (strict && failure) { + return null; + } + else { + return new String[]{ + getDots(info.myRelativeLevel) + remaining_name, + separated_name + }; + } } - } - private static String getDots(int level) { - String dots = ""; - for (int i = 0; i < level; i += 1) { - dots += "."; // this generally runs 1-2 times, so it's cheaper than allocating a StringBuilder + private static String getDots(int level) { + String dots = ""; + for (int i = 0; i < level; i += 1) { + dots += "."; // this generally runs 1-2 times, so it's cheaper than allocating a StringBuilder + } + return dots; } - return dots; - } - - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.Family.convert.import.qualify"); - } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - InfoHolder info = InfoHolder.collect(getElementFromEditor(editor, file)); - info.myModuleReference = null; - final PsiElement position = file.findElementAt(editor.getCaretModel().getOffset()); - info.myFromImportStatement = PsiTreeUtil.getParentOfType(position, PyFromImportStatement.class); - PyPsiUtils.assertValid(info.myFromImportStatement); - if (info.myFromImportStatement != null && !info.myFromImportStatement.isFromFuture()) { - info.myRelativeLevel = info.myFromImportStatement.getRelativeLevel(); - info.myModuleReference = info.myFromImportStatement.getImportSource(); - if (info.myRelativeLevel > 0) { - // make sure we aren't importing a module from the relative path - for (PyImportElement import_element : info.myFromImportStatement.getImportElements()) { - PyReferenceExpression ref = import_element.getImportReferenceExpression(); - PyPsiUtils.assertValid(ref); - if (ref != null) { - PsiElement target = ref.getReference().resolve(); - final TypeEvalContext context = TypeEvalContext.codeAnalysis(file.getProject(), file); - if (target instanceof PyExpression && context.getType((PyExpression)target) instanceof PyModuleType) { - return false; + InfoHolder info = InfoHolder.collect(getElementFromEditor(editor, file)); + info.myModuleReference = null; + final PsiElement position = file.findElementAt(editor.getCaretModel().getOffset()); + info.myFromImportStatement = PsiTreeUtil.getParentOfType(position, PyFromImportStatement.class); + PyPsiUtils.assertValid(info.myFromImportStatement); + if (info.myFromImportStatement != null && !info.myFromImportStatement.isFromFuture()) { + info.myRelativeLevel = info.myFromImportStatement.getRelativeLevel(); + info.myModuleReference = info.myFromImportStatement.getImportSource(); + if (info.myRelativeLevel > 0) { + // make sure we aren't importing a module from the relative path + for (PyImportElement import_element : info.myFromImportStatement.getImportElements()) { + PyReferenceExpression ref = import_element.getImportReferenceExpression(); + PyPsiUtils.assertValid(ref); + if (ref != null) { + PsiElement target = ref.getReference().resolve(); + final TypeEvalContext context = TypeEvalContext.codeAnalysis(file.getProject(), file); + if (target instanceof PyExpression && context.getType((PyExpression) target) instanceof PyModuleType) { + return false; + } + } + } } - } } - } - } - if (info.myModuleReference != null) { - info.myModuleName = PyPsiUtils.toPath(info.myModuleReference); - } - if (info.myModuleReference != null && info.myModuleName != null && info.myFromImportStatement != null) { - setText(info.getText()); - return true; + if (info.myModuleReference != null) { + info.myModuleName = PyPsiUtils.toPath(info.myModuleReference); + } + if (info.myModuleReference != null && info.myModuleName != null && info.myFromImportStatement != null) { + setText(info.getText()); + return true; + } + return false; } - return false; - } - /** - * Adds myModuleName as a qualifier to target. - * - * @param target_node what to qualify - * @param project - * @param qualifier - */ - private static void qualifyTarget(ASTNode target_node, Project project, String qualifier) { - final PyElementGenerator generator = PyElementGenerator.getInstance(project); - target_node.addChild(generator.createDot(), target_node.getFirstChildNode()); - target_node.addChild(sure(generator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, qualifier, new int[]{ - 0, - 0 - }).getNode()), target_node.getFirstChildNode()); - } + /** + * Adds myModuleName as a qualifier to target. + * + * @param target_node what to qualify + * @param project + * @param qualifier + */ + private static void qualifyTarget(ASTNode target_node, Project project, String qualifier) { + final PyElementGenerator generator = PyElementGenerator.getInstance(project); + target_node.addChild(generator.createDot(), target_node.getFirstChildNode()); + target_node.addChild(sure(generator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, qualifier, new int[]{ + 0, + 0 + }).getNode()), target_node.getFirstChildNode()); + } - @Override - public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - InfoHolder info = InfoHolder.collect(getElementFromEditor(editor, file)); - try { - String qualifier; // we don't always qualify with module name - sure(info.myModuleReference); - sure(info.myModuleName); - String[] relative_names = null; // [0] is remaining import path, [1] is imported module name - if (info.myRelativeLevel > 0) { - relative_names = getRelativeNames(true, info); - if (relative_names == null) { - throw new IncorrectOperationException("failed to get relative names"); - } - qualifier = relative_names[1]; - } - else { - qualifier = info.myModuleName; - } - // find all unqualified references that lead to one of our import elements - final PyImportElement[] ielts = info.myFromImportStatement.getImportElements(); - final PyStarImportElement star_ielt = info.myFromImportStatement.getStarImportElement(); - final Map references = new HashMap<>(); - final List star_references = new ArrayList<>(); - PsiTreeUtil.processElements(file, new PsiElementProcessor() { - public boolean execute(@Nonnull PsiElement element) { - PyPsiUtils.assertValid(element); - if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) { - PyReferenceExpression ref = (PyReferenceExpression)element; - if (!ref.isQualified()) { - ResolveResult[] resolved = ref.getReference().multiResolve(false); - for (ResolveResult rr : resolved) { - if (rr.isValidResult()) { - if (rr.getElement() == star_ielt) { - star_references.add(ref.getReference()); - } - for (PyImportElement ielt : ielts) { - if (rr.getElement() == ielt) { - references.put(ref.getReference(), ielt); - } - } + @Override + public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + InfoHolder info = InfoHolder.collect(getElementFromEditor(editor, file)); + try { + String qualifier; // we don't always qualify with module name + sure(info.myModuleReference); + sure(info.myModuleName); + String[] relative_names = null; // [0] is remaining import path, [1] is imported module name + if (info.myRelativeLevel > 0) { + relative_names = getRelativeNames(true, info); + if (relative_names == null) { + throw new IncorrectOperationException("failed to get relative names"); } - } + qualifier = relative_names[1]; } - } - return true; - } - }); + else { + qualifier = info.myModuleName; + } + // find all unqualified references that lead to one of our import elements + final PyImportElement[] ielts = info.myFromImportStatement.getImportElements(); + final PyStarImportElement star_ielt = info.myFromImportStatement.getStarImportElement(); + final Map references = new HashMap<>(); + final List star_references = new ArrayList<>(); + PsiTreeUtil.processElements(file, new PsiElementProcessor() { + public boolean execute(@Nonnull PsiElement element) { + PyPsiUtils.assertValid(element); + if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) { + PyReferenceExpression ref = (PyReferenceExpression) element; + if (!ref.isQualified()) { + ResolveResult[] resolved = ref.getReference().multiResolve(false); + for (ResolveResult rr : resolved) { + if (rr.isValidResult()) { + if (rr.getElement() == star_ielt) { + star_references.add(ref.getReference()); + } + for (PyImportElement ielt : ielts) { + if (rr.getElement() == ielt) { + references.put(ref.getReference(), ielt); + } + } + } + } + } + } + return true; + } + }); - // check that at every replacement site our topmost qualifier name is visible - PyQualifiedExpression top_qualifier; - PyExpression feeler = info.myModuleReference; - do { - sure(feeler instanceof PyQualifiedExpression); // if for some crazy reason module name refers to numbers, etc, no point to continue. - top_qualifier = (PyQualifiedExpression)feeler; - feeler = top_qualifier.getQualifier(); - } - while (feeler != null); - String top_name = top_qualifier.getName(); - Collection possible_targets = references.keySet(); - if (star_references.size() > 0) { - possible_targets = new ArrayList<>(references.keySet().size() + star_references.size()); - possible_targets.addAll(references.keySet()); - possible_targets.addAll(star_references); - } - final Set ignored = Sets.newHashSet(Arrays.asList(info.myFromImportStatement.getImportElements())); - if (top_name != null && showConflicts(project, findDefinitions(top_name, possible_targets, ignored), top_name, info.myModuleName)) { - return; // got conflicts - } + // check that at every replacement site our topmost qualifier name is visible + PyQualifiedExpression top_qualifier; + PyExpression feeler = info.myModuleReference; + do { + sure(feeler instanceof PyQualifiedExpression); // if for some crazy reason module name refers to numbers, etc, no point to continue. + top_qualifier = (PyQualifiedExpression) feeler; + feeler = top_qualifier.getQualifier(); + } + while (feeler != null); + String top_name = top_qualifier.getName(); + Collection possible_targets = references.keySet(); + if (star_references.size() > 0) { + possible_targets = new ArrayList<>(references.keySet().size() + star_references.size()); + possible_targets.addAll(references.keySet()); + possible_targets.addAll(star_references); + } + final Set ignored = Sets.newHashSet(Arrays.asList(info.myFromImportStatement.getImportElements())); + if (top_name != null && showConflicts( + project, + findDefinitions(top_name, possible_targets, ignored), + top_name, + info.myModuleName + )) { + return; // got conflicts + } - // add qualifiers - PyElementGenerator generator = PyElementGenerator.getInstance(project); - for (Map.Entry entry : references.entrySet()) { - PsiElement referring_elt = entry.getKey().getElement(); - assert referring_elt.isValid(); // else we won't add it - ASTNode target_node = referring_elt.getNode(); - assert target_node != null; // else it won't be valid - PyImportElement ielt = entry.getValue(); - if (ielt.getAsNameElement() != null) { - // we have an alias, replace it with real name - PyReferenceExpression refex = ielt.getImportReferenceExpression(); - assert refex != null; // else we won't resolve to this ielt - String real_name = refex.getReferencedName(); - ASTNode new_qualifier = generator.createExpressionFromText(real_name).getNode(); - assert new_qualifier != null; - //ASTNode first_under_target = target_node.getFirstChildNode(); - //if (first_under_target != null) new_qualifier.addChildren(first_under_target, null, null); // save the children if any - target_node.getTreeParent().replaceChild(target_node, new_qualifier); - target_node = new_qualifier; + // add qualifiers + PyElementGenerator generator = PyElementGenerator.getInstance(project); + for (Map.Entry entry : references.entrySet()) { + PsiElement referring_elt = entry.getKey().getElement(); + assert referring_elt.isValid(); // else we won't add it + ASTNode target_node = referring_elt.getNode(); + assert target_node != null; // else it won't be valid + PyImportElement ielt = entry.getValue(); + if (ielt.getAsNameElement() != null) { + // we have an alias, replace it with real name + PyReferenceExpression refex = ielt.getImportReferenceExpression(); + assert refex != null; // else we won't resolve to this ielt + String real_name = refex.getReferencedName(); + ASTNode new_qualifier = generator.createExpressionFromText(real_name).getNode(); + assert new_qualifier != null; + //ASTNode first_under_target = target_node.getFirstChildNode(); + //if (first_under_target != null) new_qualifier.addChildren(first_under_target, null, null); // save the children if any + target_node.getTreeParent().replaceChild(target_node, new_qualifier); + target_node = new_qualifier; + } + qualifyTarget(target_node, project, qualifier); + } + for (PsiReference reference : star_references) { + PsiElement referring_elt = reference.getElement(); + assert referring_elt.isValid(); // else we won't add it + ASTNode target_node = referring_elt.getNode(); + assert target_node != null; // else it won't be valid + qualifyTarget(target_node, project, qualifier); + } + // transform the import statement + PyStatement new_import; + if (info.myRelativeLevel == 0) { + new_import = + sure(generator.createFromText(LanguageLevel.getDefault(), PyImportStatement.class, "import " + info.myModuleName)); + } + else { + new_import = sure(generator.createFromText( + LanguageLevel.getDefault(), + PyFromImportStatement.class, + "from " + relative_names[0] + " import " + relative_names[1] + )); + } + ASTNode parent = sure(info.myFromImportStatement.getParent().getNode()); + ASTNode old_node = sure(info.myFromImportStatement.getNode()); + parent.replaceChild(old_node, sure(new_import.getNode())); + //myFromImportStatement.replace(new_import); + } + catch (IncorrectOperationException ignored) { + PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), NotificationType.WARNING); } - qualifyTarget(target_node, project, qualifier); - } - for (PsiReference reference : star_references) { - PsiElement referring_elt = reference.getElement(); - assert referring_elt.isValid(); // else we won't add it - ASTNode target_node = referring_elt.getNode(); - assert target_node != null; // else it won't be valid - qualifyTarget(target_node, project, qualifier); - } - // transform the import statement - PyStatement new_import; - if (info.myRelativeLevel == 0) { - new_import = sure(generator.createFromText(LanguageLevel.getDefault(), PyImportStatement.class, "import " + info.myModuleName)); - } - else { - new_import = sure(generator.createFromText(LanguageLevel.getDefault(), - PyFromImportStatement.class, - "from " + relative_names[0] + " import " + relative_names[1])); - } - ASTNode parent = sure(info.myFromImportStatement.getParent().getNode()); - ASTNode old_node = sure(info.myFromImportStatement.getNode()); - parent.replaceChild(old_node, sure(new_import.getNode())); - //myFromImportStatement.replace(new_import); - } - catch (IncorrectOperationException ignored) { - PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), NotificationType.WARNING); } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToImportFromIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToImportFromIntention.java index 79f2b3ea..2728710a 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToImportFromIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToImportFromIntention.java @@ -27,12 +27,15 @@ import consulo.language.psi.resolve.PsiElementProcessor; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.ui.NotificationType; import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -43,220 +46,217 @@ /** * Converts {@code import foo} to {@code from foo import names} or {@code from ... import module} to {@code from ...module import names}. * Module names used as qualifiers are removed. - *
    - * NOTE: currently we only check usage of module name in the same file. For re-exported module names this is not sufficient. - *
    - * User: dcheryasov - * Date: Sep 22, 2009 1:42:52 AM + * + *

    NOTE: currently we only check usage of module name in the same file. For re-exported module names this is not sufficient.

    + * + * @author dcheryasov + * @since 2009-09-22 */ public class ImportToImportFromIntention extends PyBaseIntentionAction { - private static class IntentionState { - private String myModuleName = null; - private String myQualifierName = null; - private PsiElement myReferee = null; - private PyImportElement myImportElement = null; - private Collection myReferences = null; - private boolean myHasModuleReference = false; - // is anything that resolves to our imported module is just an exact reference to that module - private int myRelativeLevel; // true if "from ... import" + private static class IntentionState { + private String myModuleName = null; + private String myQualifierName = null; + private PsiElement myReferee = null; + private PyImportElement myImportElement = null; + private Collection myReferences = null; + private boolean myHasModuleReference = false; + // is anything that resolves to our imported module is just an exact reference to that module + private int myRelativeLevel; // true if "from ... import" - public IntentionState(@Nonnull Editor editor, @Nonnull PsiFile file) { - boolean available = false; - myImportElement = findImportElement(editor, file); - if (myImportElement != null) { - final PsiElement parent = myImportElement.getParent(); - if (parent instanceof PyImportStatement) { - myRelativeLevel = 0; - available = true; - } - else if (parent instanceof PyFromImportStatement) { - final PyFromImportStatement fromImport = (PyFromImportStatement)parent; - final int relativeLevel = fromImport.getRelativeLevel(); - PyPsiUtils.assertValid(fromImport); - if (fromImport.isValid() && relativeLevel > 0 && fromImport.getImportSource() == null) { - myRelativeLevel = relativeLevel; - available = true; - } + public IntentionState(@Nonnull Editor editor, @Nonnull PsiFile file) { + boolean available = false; + myImportElement = findImportElement(editor, file); + if (myImportElement != null) { + final PsiElement parent = myImportElement.getParent(); + if (parent instanceof PyImportStatement) { + myRelativeLevel = 0; + available = true; + } + else if (parent instanceof PyFromImportStatement) { + final PyFromImportStatement fromImport = (PyFromImportStatement) parent; + final int relativeLevel = fromImport.getRelativeLevel(); + PyPsiUtils.assertValid(fromImport); + if (fromImport.isValid() && relativeLevel > 0 && fromImport.getImportSource() == null) { + myRelativeLevel = relativeLevel; + available = true; + } + } + } + if (available) { + collectReferencesAndOtherData(file); // this will cache data for the invocation + } } - } - if (available) { - collectReferencesAndOtherData(file); // this will cache data for the invocation - } - } - public boolean isAvailable() { - return myReferences != null && myReferences.size() > 0; - } + public boolean isAvailable() { + return myReferences != null && myReferences.size() > 0; + } - private void collectReferencesAndOtherData(PsiFile file) { - //PyImportElement myImportElement = findImportElement(editor, file); - assert myImportElement != null : "isAvailable() must have returned true, but myImportElement is null"; + private void collectReferencesAndOtherData(PsiFile file) { + //PyImportElement myImportElement = findImportElement(editor, file); + assert myImportElement != null : "isAvailable() must have returned true, but myImportElement is null"; - // usages of imported name are qualifiers; what they refer to? - final PyReferenceExpression importReference = myImportElement.getImportReferenceExpression(); - if (importReference != null) { - myModuleName = PyPsiUtils.toPath(importReference); - myQualifierName = myImportElement.getVisibleName(); - myReferee = importReference.getReference().resolve(); - myHasModuleReference = false; - if (myReferee != null && myModuleName != null && myQualifierName != null) { - final Collection references = new ArrayList<>(); - PsiTreeUtil.processElements(file, new PsiElementProcessor() { - public boolean execute(@Nonnull PsiElement element) { - if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) { - final PyReferenceExpression ref = (PyReferenceExpression)element; - if (myQualifierName.equals(PyPsiUtils.toPath(ref))) { // filter out other names that might resolve to our target - final PsiElement parentElt = ref.getParent(); - if (parentElt instanceof PyQualifiedExpression) { // really qualified by us, not just referencing? - final PsiElement resolved = ref.getReference().resolve(); - if (resolved == myReferee) { - references.add(ref.getReference()); - } - } - else { - myHasModuleReference = true; - } + // usages of imported name are qualifiers; what they refer to? + final PyReferenceExpression importReference = myImportElement.getImportReferenceExpression(); + if (importReference != null) { + myModuleName = PyPsiUtils.toPath(importReference); + myQualifierName = myImportElement.getVisibleName(); + myReferee = importReference.getReference().resolve(); + myHasModuleReference = false; + if (myReferee != null && myModuleName != null && myQualifierName != null) { + final Collection references = new ArrayList<>(); + PsiTreeUtil.processElements(file, new PsiElementProcessor() { + public boolean execute(@Nonnull PsiElement element) { + if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType( + element, + PyImportElement.class + ) == null) { + final PyReferenceExpression ref = (PyReferenceExpression) element; + if (myQualifierName.equals(PyPsiUtils.toPath(ref))) { // filter out other names that might resolve to our target + final PsiElement parentElt = ref.getParent(); + if (parentElt instanceof PyQualifiedExpression) { // really qualified by us, not just referencing? + final PsiElement resolved = ref.getReference().resolve(); + if (resolved == myReferee) { + references.add(ref.getReference()); + } + } + else { + myHasModuleReference = true; + } + } + } + return true; + } + }); + myReferences = references; } - } - return true; } - }); - myReferences = references; } - } - } - public void invoke() { - assert myImportElement != null : "isAvailable() must have returned true, but myImportElement is null"; - sure(myImportElement.getImportReferenceExpression()); - final Project project = myImportElement.getProject(); + public void invoke() { + assert myImportElement != null : "isAvailable() must have returned true, but myImportElement is null"; + sure(myImportElement.getImportReferenceExpression()); + final Project project = myImportElement.getProject(); - final PyElementGenerator generator = PyElementGenerator.getInstance(project); - final LanguageLevel languageLevel = LanguageLevel.forElement(myImportElement); + final PyElementGenerator generator = PyElementGenerator.getInstance(project); + final LanguageLevel languageLevel = LanguageLevel.forElement(myImportElement); - // usages of imported name are qualifiers; what they refer to? - try { - // remember names and make them drop qualifiers - final Set usedNames = new HashSet<>(); - for (PsiReference ref : myReferences) { - final PsiElement elt = ref.getElement(); - final PsiElement parentElt = elt.getParent(); - // TODO: find ident node more properly - final String nameUsed = sure(sure(parentElt).getLastChild()).getText(); - usedNames.add(nameUsed); - if (!FileModificationService.getInstance().preparePsiElementForWrite(elt)) { - return; - } - assert parentElt instanceof PyReferenceExpression; - final PyElement newReference = generator.createExpressionFromText(languageLevel, nameUsed); - parentElt.replace(newReference); - } + // usages of imported name are qualifiers; what they refer to? + try { + // remember names and make them drop qualifiers + final Set usedNames = new HashSet<>(); + for (PsiReference ref : myReferences) { + final PsiElement elt = ref.getElement(); + final PsiElement parentElt = elt.getParent(); + // TODO: find ident node more properly + final String nameUsed = sure(sure(parentElt).getLastChild()).getText(); + usedNames.add(nameUsed); + if (!FileModificationService.getInstance().preparePsiElementForWrite(elt)) { + return; + } + assert parentElt instanceof PyReferenceExpression; + final PyElement newReference = generator.createExpressionFromText(languageLevel, nameUsed); + parentElt.replace(newReference); + } - // create a separate import stmt for the module - final PsiElement importer = myImportElement.getParent(); - final PyStatement importStatement; - final PyImportElement[] importElements; - if (importer instanceof PyImportStatement) { - importStatement = (PyImportStatement)importer; - importElements = ((PyImportStatement)importStatement).getImportElements(); - } - else if (importer instanceof PyFromImportStatement) { - importStatement = (PyFromImportStatement)importer; - importElements = ((PyFromImportStatement)importStatement).getImportElements(); - } - else { - throw new IncorrectOperationException("Not an import at all"); - } - final PyFromImportStatement newImportStatement = - generator.createFromImportStatement(languageLevel, getDots() + myModuleName, StringUtil.join(usedNames, ", "), null); - final PsiElement parent = importStatement.getParent(); - sure(parent); - sure(parent.isValid()); - if (importElements.length == 1) { - if (myHasModuleReference) { - parent.addAfter(newImportStatement, importStatement); // add 'import from': we need the module imported as is - } - else { // replace entire existing import - sure(parent.getNode()).replaceChild(sure(importStatement.getNode()), sure(newImportStatement.getNode())); - // import_statement.replace(from_import_stmt); - } - } - else { - if (!myHasModuleReference) { - // cut the module out of import, add a from-import. - for (PyImportElement pie : importElements) { - if (pie == myImportElement) { - pie.delete(); - break; - } + // create a separate import stmt for the module + final PsiElement importer = myImportElement.getParent(); + final PyStatement importStatement; + final PyImportElement[] importElements; + if (importer instanceof PyImportStatement) { + importStatement = (PyImportStatement) importer; + importElements = ((PyImportStatement) importStatement).getImportElements(); + } + else if (importer instanceof PyFromImportStatement) { + importStatement = (PyFromImportStatement) importer; + importElements = ((PyFromImportStatement) importStatement).getImportElements(); + } + else { + throw new IncorrectOperationException("Not an import at all"); + } + final PyFromImportStatement newImportStatement = + generator.createFromImportStatement(languageLevel, getDots() + myModuleName, StringUtil.join(usedNames, ", "), null); + final PsiElement parent = importStatement.getParent(); + sure(parent); + sure(parent.isValid()); + if (importElements.length == 1) { + if (myHasModuleReference) { + parent.addAfter(newImportStatement, importStatement); // add 'import from': we need the module imported as is + } + else { // replace entire existing import + sure(parent.getNode()).replaceChild(sure(importStatement.getNode()), sure(newImportStatement.getNode())); + // import_statement.replace(from_import_stmt); + } + } + else { + if (!myHasModuleReference) { + // cut the module out of import, add a from-import. + for (PyImportElement pie : importElements) { + if (pie == myImportElement) { + pie.delete(); + break; + } + } + } + parent.addAfter(newImportStatement, importStatement); + } + } + catch (IncorrectOperationException ignored) { + PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), NotificationType.WARNING); } - } - parent.addAfter(newImportStatement, importStatement); } - } - catch (IncorrectOperationException ignored) { - PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), NotificationType.WARNING); - } - } + @Nonnull + public LocalizeValue getText() { + String moduleName = "?"; + if (myImportElement != null) { + final PyReferenceExpression reference = myImportElement.getImportReferenceExpression(); + if (reference != null) { + moduleName = PyPsiUtils.toPath(reference); + } + } + return PyLocalize.intnConvertToFrom$0Import$1(getDots() + moduleName, "..."); + } - @Nonnull - public String getText() { - String moduleName = "?"; - if (myImportElement != null) { - final PyReferenceExpression reference = myImportElement.getImportReferenceExpression(); - if (reference != null) { - moduleName = PyPsiUtils.toPath(reference); + @Nonnull + private String getDots() { + String dots = ""; + for (int i = 0; i < myRelativeLevel; i += 1) { + dots += "."; // this generally runs 1-2 times, so it's cheaper than allocating a StringBuilder + } + return dots; } - } - return PyBundle.message("INTN.convert.to.from.$0.import.$1", getDots() + moduleName, "..."); } - @Nonnull - private String getDots() { - String dots = ""; - for (int i = 0; i < myRelativeLevel; i += 1) { - dots += "."; // this generally runs 1-2 times, so it's cheaper than allocating a StringBuilder - } - return dots; + @Nullable + private static PyImportElement findImportElement(@Nonnull Editor editor, @Nonnull PsiFile file) { + final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); + final PyImportElement importElement = PsiTreeUtil.getParentOfType(elementAtCaret, PyImportElement.class); + PyPsiUtils.assertValid(importElement); + if (importElement != null && importElement.isValid()) { + return importElement; + } + else { + return null; + } } - } - - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.Family.convert.import.unqualify"); - } - @Nullable - private static PyImportElement findImportElement(@Nonnull Editor editor, @Nonnull PsiFile file) { - final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); - final PyImportElement importElement = PsiTreeUtil.getParentOfType(elementAtCaret, PyImportElement.class); - PyPsiUtils.assertValid(importElement); - if (importElement != null && importElement.isValid()) { - return importElement; - } - else { - return null; - } - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; + final IntentionState state = new IntentionState(editor, file); + if (state.isAvailable()) { + setText(state.getText()); + return true; + } + return false; } - final IntentionState state = new IntentionState(editor, file); - if (state.isAvailable()) { - setText(state.getText()); - return true; + @Override + public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final IntentionState state = new IntentionState(editor, file); + state.invoke(); } - return false; - } - - @Override - public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - final IntentionState state = new IntentionState(editor, file); - state.invoke(); - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToggleAliasIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToggleAliasIntention.java index 103c48a2..71adca8c 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToggleAliasIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/ImportToggleAliasIntention.java @@ -15,12 +15,13 @@ */ package com.jetbrains.python.impl.codeInsight.intentions; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.impl.PyBundle; 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.application.Application; import consulo.application.ApplicationManager; import consulo.codeEditor.Editor; @@ -34,13 +35,15 @@ import consulo.language.psi.resolve.PsiElementProcessor; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.ui.NotificationType; import consulo.ui.ex.InputValidator; import consulo.ui.ex.awt.Messages; import consulo.util.lang.Pair; - import jakarta.annotation.Nonnull; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -52,216 +55,225 @@ /** * Adds an alias to "import foo" or "from foo import bar" import elements, or removes it if it's already present. - * User: dcheryasov - * Date: Oct 9, 2009 6:07:19 PM + * + * @author dcheryasov + * @since 2009-10-09 */ public class ImportToggleAliasIntention extends PyBaseIntentionAction { - private static class IntentionState { - private PyImportElement myImportElement; - private PyFromImportStatement myFromImportStatement; - private PyImportStatement myImportStatement; - private String myAlias; + private static class IntentionState { + private PyImportElement myImportElement; + private PyFromImportStatement myFromImportStatement; + private PyImportStatement myImportStatement; + private String myAlias; - private static IntentionState fromContext(Editor editor, PsiFile file) { - IntentionState state = new IntentionState(); - state.myImportElement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyImportElement.class); - PyPsiUtils.assertValid(state.myImportElement); - if (state.myImportElement != null) { - PyTargetExpression target = state.myImportElement.getAsNameElement(); - PyPsiUtils.assertValid(target); - if (target != null) { - state.myAlias = target.getName(); - } - else { - state.myAlias = null; + private static IntentionState fromContext(Editor editor, PsiFile file) { + IntentionState state = new IntentionState(); + state.myImportElement = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyImportElement.class); + PyPsiUtils.assertValid(state.myImportElement); + if (state.myImportElement != null) { + PyTargetExpression target = state.myImportElement.getAsNameElement(); + PyPsiUtils.assertValid(target); + if (target != null) { + state.myAlias = target.getName(); + } + else { + state.myAlias = null; + } + state.myFromImportStatement = PsiTreeUtil.getParentOfType(state.myImportElement, PyFromImportStatement.class); + state.myImportStatement = PsiTreeUtil.getParentOfType(state.myImportElement, PyImportStatement.class); + } + return state; } - state.myFromImportStatement = PsiTreeUtil.getParentOfType(state.myImportElement, PyFromImportStatement.class); - state.myImportStatement = PsiTreeUtil.getParentOfType(state.myImportElement, PyImportStatement.class); - } - return state; - } - public boolean isAvailable() { - if (myFromImportStatement != null) { - PyPsiUtils.assertValid(myFromImportStatement); - if (!myFromImportStatement.isValid() || myFromImportStatement.isFromFuture()) { - return false; - } - } - else { - PyPsiUtils.assertValid(myImportStatement); - if (myImportStatement == null || !myImportStatement.isValid()) { - return false; + public boolean isAvailable() { + if (myFromImportStatement != null) { + PyPsiUtils.assertValid(myFromImportStatement); + if (!myFromImportStatement.isValid() || myFromImportStatement.isFromFuture()) { + return false; + } + } + else { + PyPsiUtils.assertValid(myImportStatement); + if (myImportStatement == null || !myImportStatement.isValid()) { + return false; + } + } + final PyReferenceExpression referenceExpression = myImportElement.getImportReferenceExpression(); + if (referenceExpression == null || referenceExpression.getReference().resolve() == null) { + return false; + } + return true; } - } - final PyReferenceExpression referenceExpression = myImportElement.getImportReferenceExpression(); - if (referenceExpression == null || referenceExpression.getReference().resolve() == null) { - return false; - } - return true; - } - public String getText() { - String add_name = "Add alias"; - if (myImportElement != null) { - PyReferenceExpression refex = myImportElement.getImportReferenceExpression(); - if (refex != null) { - add_name = PyBundle.message("INTN.add.alias.for.import.$0", refex.getText()); + @RequiredReadAction + public LocalizeValue getText() { + LocalizeValue addName = LocalizeValue.localizeTODO("Add alias"); + if (myImportElement != null) { + PyReferenceExpression refex = myImportElement.getImportReferenceExpression(); + if (refex != null) { + addName = PyLocalize.intnAddAliasForImport$0(refex.getText()); + } + } + return myAlias == null ? addName : PyLocalize.intnRemoveAliasForImport$0(myAlias); } - } - return myAlias == null ? add_name : PyBundle.message("INTN.remove.alias.for.import.$0", myAlias); } - } - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.Family.toggle.import.alias"); - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; + IntentionState state = IntentionState.fromContext(editor, file); + setText(state.getText()); + return state.isAvailable(); } - IntentionState state = IntentionState.fromContext(editor, file); - setText(state.getText()); - return state.isAvailable(); - } - - @Override - public void doInvoke(@Nonnull final Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - // sanity check: isAvailable must have set it. - final IntentionState state = IntentionState.fromContext(editor, file); - // - final String target_name; // we set in in the source - final String remove_name; // we replace it in the source - PyReferenceExpression reference = sure(state.myImportElement.getImportReferenceExpression()); - // search for references to us with the right name - try { - String imported_name = PyPsiUtils.toPath(reference); - if (state.myAlias != null) { - // have to remove alias, rename everything to original - target_name = imported_name; - remove_name = state.myAlias; - } - else { - // ask for and add alias - Application application = ApplicationManager.getApplication(); - if (application != null && !application.isUnitTestMode()) { - String alias = Messages.showInputDialog(project, - PyBundle.message("INTN.alias.for.$0.dialog.title", imported_name), - "Add Alias", - Messages.getQuestionIcon(), - "", - new - InputValidator() { - @Override - public boolean checkInput(String inputString) { - return PyNames.isIdentifier(inputString); - } + @Override + public void doInvoke(@Nonnull final Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + // sanity check: isAvailable must have set it. + final IntentionState state = IntentionState.fromContext(editor, file); + // + final String target_name; // we set in in the source + final String remove_name; // we replace it in the source + PyReferenceExpression reference = sure(state.myImportElement.getImportReferenceExpression()); + // search for references to us with the right name + try { + String imported_name = PyPsiUtils.toPath(reference); + if (state.myAlias != null) { + // have to remove alias, rename everything to original + target_name = imported_name; + remove_name = state.myAlias; + } + else { + // ask for and add alias + Application application = ApplicationManager.getApplication(); + if (application != null && !application.isUnitTestMode()) { + String alias = Messages.showInputDialog( + project, + PyBundle.message("INTN.alias.for.$0.dialog.title", imported_name), + "Add Alias", + Messages.getQuestionIcon(), + "", + new + InputValidator() { + @Override + public boolean checkInput(String inputString) { + return PyNames.isIdentifier(inputString); + } - @Override - public boolean canClose(String inputString) { - return PyNames.isIdentifier(inputString); - } - }); - if (alias == null) { - return; - } - target_name = alias; - } - else { // test mode - target_name = "alias"; - } - remove_name = imported_name; - } - final PsiElement referee = reference.getReference().resolve(); - if (referee != null && imported_name != null) { - final Collection references = new ArrayList<>(); - final ScopeOwner scope = PsiTreeUtil.getParentOfType(state.myImportElement, ScopeOwner.class); - PsiTreeUtil.processElements(scope, new PsiElementProcessor() { - public boolean execute(@Nonnull PsiElement element) { - getReferences(element); - if (element instanceof PyStringLiteralExpression) { - final PsiLanguageInjectionHost host = (PsiLanguageInjectionHost)element; - final List> files = InjectedLanguageManager.getInstance(project).getInjectedPsiFiles(host); - if (files != null) { - for (Pair pair : files) { - final PsiElement first = pair.getFirst(); - if (first instanceof ScopeOwner) { - final ScopeOwner scopeOwner = (ScopeOwner)first; - PsiTreeUtil.processElements(scopeOwner, new PsiElementProcessor() { - public boolean execute(@Nonnull PsiElement element) { + @Override + public boolean canClose(String inputString) { + return PyNames.isIdentifier(inputString); + } + } + ); + if (alias == null) { + return; + } + target_name = alias; + } + else { // test mode + target_name = "alias"; + } + remove_name = imported_name; + } + final PsiElement referee = reference.getReference().resolve(); + if (referee != null && imported_name != null) { + final Collection references = new ArrayList<>(); + final ScopeOwner scope = PsiTreeUtil.getParentOfType(state.myImportElement, ScopeOwner.class); + PsiTreeUtil.processElements(scope, new PsiElementProcessor() { + public boolean execute(@Nonnull PsiElement element) { getReferences(element); + if (element instanceof PyStringLiteralExpression) { + final PsiLanguageInjectionHost host = (PsiLanguageInjectionHost) element; + final List> files = + InjectedLanguageManager.getInstance(project).getInjectedPsiFiles(host); + if (files != null) { + for (Pair pair : files) { + final PsiElement first = pair.getFirst(); + if (first instanceof ScopeOwner) { + final ScopeOwner scopeOwner = (ScopeOwner) first; + PsiTreeUtil.processElements(scopeOwner, new PsiElementProcessor() { + public boolean execute(@Nonnull PsiElement element) { + getReferences(element); + return true; + } + }); + } + } + } + } return true; - } - }); - } + } + + private void getReferences(PsiElement element) { + if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType( + element, + PyImportElement.class + ) == null) { + PyReferenceExpression ref = (PyReferenceExpression) element; + if (remove_name.equals(PyPsiUtils.toPath(ref))) { // filter out other names that might resolve to our target + PsiElement resolved = ref.getReference().resolve(); + if (resolved == referee) { + references.add(ref.getReference()); + } + } + } + } + }); + // no references here is OK by us. + if (showConflicts( + project, + findDefinitions(target_name, references, Collections.emptySet()), + target_name, + null + )) { + return; // got conflicts } - } - } - return true; - } - private void getReferences(PsiElement element) { - if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) { - PyReferenceExpression ref = (PyReferenceExpression)element; - if (remove_name.equals(PyPsiUtils.toPath(ref))) { // filter out other names that might resolve to our target - PsiElement resolved = ref.getReference().resolve(); - if (resolved == referee) { - references.add(ref.getReference()); + // alter the import element + PyElementGenerator generator = PyElementGenerator.getInstance(project); + final LanguageLevel languageLevel = LanguageLevel.forElement(state.myImportElement); + if (state.myAlias != null) { + // remove alias + ASTNode node = sure(state.myImportElement.getNode()); + ASTNode parent = sure(node.getTreeParent()); + node = sure(node.getFirstChildNode()); // this is the reference + node = sure(node.getTreeNext()); // things past the reference: space, 'as', and alias + parent.removeRange(node, null); + } + else { + // add alias + ASTNode my_ielt_node = sure(state.myImportElement.getNode()); + PyImportElement fountain = + generator.createFromText(languageLevel, PyImportElement.class, "import foo as " + target_name, new int[]{ + 0, + 2 + }); + ASTNode graft_node = sure(fountain.getNode()); // at import elt + graft_node = sure(graft_node.getFirstChildNode()); // at ref + graft_node = sure(graft_node.getTreeNext()); // space + my_ielt_node.addChild((ASTNode) graft_node.clone()); + graft_node = sure(graft_node.getTreeNext()); // 'as' + my_ielt_node.addChild((ASTNode) graft_node.clone()); + graft_node = sure(graft_node.getTreeNext()); // space + my_ielt_node.addChild((ASTNode) graft_node.clone()); + graft_node = sure(graft_node.getTreeNext()); // alias + my_ielt_node.addChild((ASTNode) graft_node.clone()); + } + // alter references + for (PsiReference ref : references) { + ASTNode ref_name_node = sure(sure(ref.getElement()).getNode()); + ASTNode parent = sure(ref_name_node.getTreeParent()); + ASTNode new_name_node = generator.createExpressionFromText(languageLevel, target_name).getNode(); + assert new_name_node != null; + parent.replaceChild(ref_name_node, new_name_node); } - } } - } - }); - // no references here is OK by us. - if (showConflicts(project, findDefinitions(target_name, references, Collections.emptySet()), target_name, null)) { - return; // got conflicts } - - // alter the import element - PyElementGenerator generator = PyElementGenerator.getInstance(project); - final LanguageLevel languageLevel = LanguageLevel.forElement(state.myImportElement); - if (state.myAlias != null) { - // remove alias - ASTNode node = sure(state.myImportElement.getNode()); - ASTNode parent = sure(node.getTreeParent()); - node = sure(node.getFirstChildNode()); // this is the reference - node = sure(node.getTreeNext()); // things past the reference: space, 'as', and alias - parent.removeRange(node, null); - } - else { - // add alias - ASTNode my_ielt_node = sure(state.myImportElement.getNode()); - PyImportElement fountain = - generator.createFromText(languageLevel, PyImportElement.class, "import foo as " + target_name, new int[]{ - 0, - 2 - }); - ASTNode graft_node = sure(fountain.getNode()); // at import elt - graft_node = sure(graft_node.getFirstChildNode()); // at ref - graft_node = sure(graft_node.getTreeNext()); // space - my_ielt_node.addChild((ASTNode)graft_node.clone()); - graft_node = sure(graft_node.getTreeNext()); // 'as' - my_ielt_node.addChild((ASTNode)graft_node.clone()); - graft_node = sure(graft_node.getTreeNext()); // space - my_ielt_node.addChild((ASTNode)graft_node.clone()); - graft_node = sure(graft_node.getTreeNext()); // alias - my_ielt_node.addChild((ASTNode)graft_node.clone()); + catch (IncorrectOperationException ignored) { + PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), NotificationType.WARNING); } - // alter references - for (PsiReference ref : references) { - ASTNode ref_name_node = sure(sure(ref.getElement()).getNode()); - ASTNode parent = sure(ref_name_node.getTreeParent()); - ASTNode new_name_node = generator.createExpressionFromText(languageLevel, target_name).getNode(); - assert new_name_node != null; - parent.replaceChild(ref_name_node, new_name_node); - } - } - } - catch (IncorrectOperationException ignored) { - PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), NotificationType.WARNING); } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java index 3369bd76..71d9fa90 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.codeInsight.intentions; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.impl.codeInsight.codeFragment.PyCodeFragmentUtil; import com.jetbrains.python.impl.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.impl.psi.impl.PyFunctionBuilder; @@ -33,122 +31,130 @@ import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; + import java.util.Arrays; import java.util.List; /** - * User: catherine * Intention to convert lambda to function + * + * @author catherine */ public class PyConvertLambdaToFunctionIntention extends BaseIntentionAction { - - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.convert.lambda.to.function"); - } - - @Nonnull - public String getText() { - return PyBundle.message("INTN.convert.lambda.to.function"); - } - - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnConvertLambdaToFunction(); } - PyLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyLambdaExpression.class); - if (lambdaExpression != null) { - if (lambdaExpression.getBody() != null) { - final ControlFlow flow = ControlFlowCache.getControlFlow(lambdaExpression); - final List graph = Arrays.asList(flow.getInstructions()); - final List elements = PyCodeFragmentUtil.getInputElements(graph, graph); - if (elements.size() > 0) return false; - return true; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } + + PyLambdaExpression lambdaExpression = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyLambdaExpression.class); + if (lambdaExpression != null) { + if (lambdaExpression.getBody() != null) { + final ControlFlow flow = ControlFlowCache.getControlFlow(lambdaExpression); + final List graph = Arrays.asList(flow.getInstructions()); + final List elements = PyCodeFragmentUtil.getInputElements(graph, graph); + if (elements.size() > 0) { + return false; + } + return true; + } + } + return false; } - return false; - } - - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - PyLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyLambdaExpression.class); - if (lambdaExpression != null) { - String name = "function"; - while (IntroduceValidator.isDefinedInScope(name, lambdaExpression)) { - name += "1"; - } - - PsiElement parent = lambdaExpression.getParent(); - if (parent instanceof PyAssignmentStatement) { - name = ((PyAssignmentStatement)parent).getLeftHandSideExpression().getText(); - } - - if (name.isEmpty()) return; - PyExpression body = lambdaExpression.getBody(); - PyFunctionBuilder functionBuilder = new PyFunctionBuilder(name); - for (PyParameter param : lambdaExpression.getParameterList().getParameters()) { - functionBuilder.parameter(param.getText()); - } - functionBuilder.statement("return " + body.getText()); - PyFunction function = functionBuilder.buildFunction(project, LanguageLevel.getDefault()); - - final PyStatement statement = PsiTreeUtil.getParentOfType(lambdaExpression, - PyStatement.class); - if (statement != null) { - final PsiElement statementParent = statement.getParent(); - if (statementParent != null) - function = (PyFunction)statementParent.addBefore(function, statement); - } - - function = CodeInsightUtilCore - .forcePsiPostprocessAndRestoreElement(function); - - if (parent instanceof PyAssignmentStatement) { - parent.delete(); - } - else { - PsiFile parentScope = lambdaExpression.getContainingFile(); - final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(parentScope); - PsiElement functionName = function.getNameIdentifier(); - functionName = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(functionName); - lambdaExpression = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(lambdaExpression); - - ReferenceNameExpression refExpr = new ReferenceNameExpression(name); - - builder.replaceElement(lambdaExpression, name, refExpr, true); - builder.replaceElement(functionName, name, name, false); - - int textOffSet = functionName.getTextOffset(); - editor.getCaretModel().moveToOffset(parentScope.getTextRange().getStartOffset()); - - Template template = builder.buildInlineTemplate(); - TemplateManager.getInstance(project).startTemplate(editor, template); - editor.getCaretModel().moveToOffset(textOffSet); - } - } - } - private class ReferenceNameExpression extends Expression { - ReferenceNameExpression(String oldReferenceName) { - myOldReferenceName = oldReferenceName; + + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyLambdaExpression lambdaExpression = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyLambdaExpression.class); + if (lambdaExpression != null) { + String name = "function"; + while (IntroduceValidator.isDefinedInScope(name, lambdaExpression)) { + name += "1"; + } + + PsiElement parent = lambdaExpression.getParent(); + if (parent instanceof PyAssignmentStatement) { + name = ((PyAssignmentStatement) parent).getLeftHandSideExpression().getText(); + } + + if (name.isEmpty()) { + return; + } + PyExpression body = lambdaExpression.getBody(); + PyFunctionBuilder functionBuilder = new PyFunctionBuilder(name); + for (PyParameter param : lambdaExpression.getParameterList().getParameters()) { + functionBuilder.parameter(param.getText()); + } + functionBuilder.statement("return " + body.getText()); + PyFunction function = functionBuilder.buildFunction(project, LanguageLevel.getDefault()); + + final PyStatement statement = PsiTreeUtil.getParentOfType( + lambdaExpression, + PyStatement.class + ); + if (statement != null) { + final PsiElement statementParent = statement.getParent(); + if (statementParent != null) { + function = (PyFunction) statementParent.addBefore(function, statement); + } + } + + function = CodeInsightUtilCore + .forcePsiPostprocessAndRestoreElement(function); + + if (parent instanceof PyAssignmentStatement) { + parent.delete(); + } + else { + PsiFile parentScope = lambdaExpression.getContainingFile(); + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(parentScope); + PsiElement functionName = function.getNameIdentifier(); + functionName = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(functionName); + lambdaExpression = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(lambdaExpression); + + ReferenceNameExpression refExpr = new ReferenceNameExpression(name); + + builder.replaceElement(lambdaExpression, name, refExpr, true); + builder.replaceElement(functionName, name, name, false); + + int textOffSet = functionName.getTextOffset(); + editor.getCaretModel().moveToOffset(parentScope.getTextRange().getStartOffset()); + + Template template = builder.buildInlineTemplate(); + TemplateManager.getInstance(project).startTemplate(editor, template); + editor.getCaretModel().moveToOffset(textOffSet); + } + } } - private final String myOldReferenceName; + private class ReferenceNameExpression extends Expression { + ReferenceNameExpression(String oldReferenceName) { + myOldReferenceName = oldReferenceName; + } - public Result calculateResult(ExpressionContext context) { - return new TextResult(myOldReferenceName); - } + private final String myOldReferenceName; - public Result calculateQuickResult(ExpressionContext context) { - return null; - } + public Result calculateResult(ExpressionContext context) { + return new TextResult(myOldReferenceName); + } - @Override - public LookupElement[] calculateLookupItems(ExpressionContext context) { - return null; + public Result calculateQuickResult(ExpressionContext context) { + return null; + } + + @Override + public LookupElement[] calculateLookupItems(ExpressionContext context) { + return null; + } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertMethodToPropertyIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertMethodToPropertyIntention.java index 86ed34da..835ec6fc 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertMethodToPropertyIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertMethodToPropertyIntention.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.codeInsight.intentions; import com.jetbrains.python.impl.psi.PyUtil; import consulo.language.editor.intention.BaseIntentionAction; import consulo.codeEditor.Editor; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; +import consulo.python.impl.localize.PyLocalize; import consulo.usage.UsageInfo; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.*; import com.jetbrains.python.impl.refactoring.PyRefactoringUtil; import consulo.language.util.IncorrectOperationException; @@ -35,89 +35,101 @@ import java.util.List; /** - * User: ktisha + * @author ktisha */ public class PyConvertMethodToPropertyIntention extends BaseIntentionAction { - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.convert.method.to.property"); - } - - @Nonnull - public String getText() { - return PyBundle.message("INTN.convert.method.to.property"); - } - - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnConvertMethodToProperty(); } - if (!LanguageLevel.forElement(file).isAtLeast(LanguageLevel.PYTHON26)) return false; - final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); - final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (function == null) return false; - final PyClass containingClass = function.getContainingClass(); - if (containingClass == null) return false; - if (function.getParameterList().getParameters().length > 1) return false; - - final PyDecoratorList decoratorList = function.getDecoratorList(); - if (decoratorList != null) return false; - - final boolean[] available = {false}; - function.accept(new PyRecursiveElementVisitor() { - @Override - public void visitPyReturnStatement(PyReturnStatement node) { - if (node.getExpression() != null) - available[0] = true; - } - @Override - public void visitPyYieldExpression(PyYieldExpression node) { - available[0] = true; - } - }); - - return available[0]; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } + if (!LanguageLevel.forElement(file).isAtLeast(LanguageLevel.PYTHON26)) { + return false; + } + final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (function == null) { + return false; + } + final PyClass containingClass = function.getContainingClass(); + if (containingClass == null) { + return false; + } + if (function.getParameterList().getParameters().length > 1) { + return false; + } - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); - PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (problemFunction == null) return; - final PyClass containingClass = problemFunction.getContainingClass(); - if (containingClass == null) return; - final List usages = PyRefactoringUtil.findUsages(problemFunction, false); + final PyDecoratorList decoratorList = function.getDecoratorList(); + if (decoratorList != null) { + return false; + } - final PyDecoratorList problemDecoratorList = problemFunction.getDecoratorList(); - List decoTexts = new ArrayList(); - decoTexts.add("@property"); - if (problemDecoratorList != null) { - final PyDecorator[] decorators = problemDecoratorList.getDecorators(); - for (PyDecorator deco : decorators) { - decoTexts.add(deco.getText()); - } + final boolean[] available = {false}; + function.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPyReturnStatement(PyReturnStatement node) { + if (node.getExpression() != null) { + available[0] = true; + } + } + + @Override + public void visitPyYieldExpression(PyYieldExpression node) { + available[0] = true; + } + }); + + return available[0]; } - PyElementGenerator generator = PyElementGenerator.getInstance(project); - final PyDecoratorList decoratorList = generator.createDecoratorList(decoTexts.toArray(new String[decoTexts.size()])); + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (problemFunction == null) { + return; + } + final PyClass containingClass = problemFunction.getContainingClass(); + if (containingClass == null) { + return; + } + final List usages = PyRefactoringUtil.findUsages(problemFunction, false); + + final PyDecoratorList problemDecoratorList = problemFunction.getDecoratorList(); + List decoTexts = new ArrayList(); + decoTexts.add("@property"); + if (problemDecoratorList != null) { + final PyDecorator[] decorators = problemDecoratorList.getDecorators(); + for (PyDecorator deco : decorators) { + decoTexts.add(deco.getText()); + } + } + + PyElementGenerator generator = PyElementGenerator.getInstance(project); + final PyDecoratorList decoratorList = generator.createDecoratorList(decoTexts.toArray(new String[decoTexts.size()])); - if (problemDecoratorList != null) { - problemDecoratorList.replace(decoratorList); - } - else { - problemFunction.addBefore(decoratorList, problemFunction.getFirstChild()); - } + if (problemDecoratorList != null) { + problemDecoratorList.replace(decoratorList); + } + else { + problemFunction.addBefore(decoratorList, problemFunction.getFirstChild()); + } - for (UsageInfo usage : usages) { - final PsiElement usageElement = usage.getElement(); - if (usageElement instanceof PyReferenceExpression) { - final PsiElement parent = usageElement.getParent(); - if (parent instanceof PyCallExpression) { - final PyArgumentList argumentList = ((PyCallExpression)parent).getArgumentList(); - if (argumentList != null) argumentList.delete(); + for (UsageInfo usage : usages) { + final PsiElement usageElement = usage.getElement(); + if (usageElement instanceof PyReferenceExpression) { + final PsiElement parent = usageElement.getParent(); + if (parent instanceof PyCallExpression) { + final PyArgumentList argumentList = ((PyCallExpression) parent).getArgumentList(); + if (argumentList != null) { + argumentList.delete(); + } + } + } } - } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertStaticMethodToFunctionIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertStaticMethodToFunctionIntention.java index 885dfad3..0e71b636 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertStaticMethodToFunctionIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertStaticMethodToFunctionIntention.java @@ -13,86 +13,92 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.codeInsight.intentions; +import com.jetbrains.python.PyNames; import com.jetbrains.python.impl.psi.PyUtil; -import consulo.language.editor.intention.BaseIntentionAction; +import com.jetbrains.python.impl.refactoring.PyRefactoringUtil; +import com.jetbrains.python.psi.*; import consulo.codeEditor.Editor; -import consulo.project.Project; +import consulo.language.editor.intention.BaseIntentionAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; -import consulo.usage.UsageInfo; import consulo.language.util.IncorrectOperationException; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.impl.refactoring.PyRefactoringUtil; - +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import consulo.usage.UsageInfo; import jakarta.annotation.Nonnull; import java.util.List; /** - * User: ktisha + * @author ktisha */ -public class PyConvertStaticMethodToFunctionIntention extends BaseIntentionAction -{ - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.convert.static.method.to.function"); - } - - @Nonnull - public String getText() { - return PyBundle.message("INTN.convert.static.method.to.function"); - } - - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; - } - final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); - final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (function == null) return false; - final PyClass containingClass = function.getContainingClass(); - if (containingClass == null) return false; - final PyDecoratorList decoratorList = function.getDecoratorList(); - if (decoratorList != null) { - final PyDecorator staticMethod = decoratorList.findDecorator(PyNames.STATICMETHOD); - if (staticMethod != null) return true; +public class PyConvertStaticMethodToFunctionIntention extends BaseIntentionAction { + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnConvertStaticMethodToFunction(); } - return false; - } - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); - PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (problemFunction == null) return; - final PyClass containingClass = problemFunction.getContainingClass(); - if (containingClass == null) return; - final List usages = PyRefactoringUtil.findUsages(problemFunction, false); - final PyDecoratorList decoratorList = problemFunction.getDecoratorList(); - if (decoratorList != null) { - final PyDecorator decorator = decoratorList.findDecorator(PyNames.STATICMETHOD); - if (decorator != null) decorator.delete(); + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } + final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (function == null) { + return false; + } + final PyClass containingClass = function.getContainingClass(); + if (containingClass == null) { + return false; + } + final PyDecoratorList decoratorList = function.getDecoratorList(); + if (decoratorList != null) { + final PyDecorator staticMethod = decoratorList.findDecorator(PyNames.STATICMETHOD); + if (staticMethod != null) { + return true; + } + } + return false; } - final PyElementGenerator generator = PyElementGenerator.getInstance(project); - final PsiElement copy = problemFunction.copy(); - final PyStatementList classStatementList = containingClass.getStatementList(); - classStatementList.deleteChildRange(problemFunction, problemFunction); - if (classStatementList.getStatements().length < 1) { - classStatementList.add(generator.createPassStatement()); - } - file.addAfter(copy, containingClass); + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (problemFunction == null) { + return; + } + final PyClass containingClass = problemFunction.getContainingClass(); + if (containingClass == null) { + return; + } + final List usages = PyRefactoringUtil.findUsages(problemFunction, false); + final PyDecoratorList decoratorList = problemFunction.getDecoratorList(); + if (decoratorList != null) { + final PyDecorator decorator = decoratorList.findDecorator(PyNames.STATICMETHOD); + if (decorator != null) { + decorator.delete(); + } + } + final PyElementGenerator generator = PyElementGenerator.getInstance(project); + + final PsiElement copy = problemFunction.copy(); + final PyStatementList classStatementList = containingClass.getStatementList(); + classStatementList.deleteChildRange(problemFunction, problemFunction); + if (classStatementList.getStatements().length < 1) { + classStatementList.add(generator.createPassStatement()); + } + file.addAfter(copy, containingClass); - for (UsageInfo usage : usages) { - final PsiElement usageElement = usage.getElement(); - if (usageElement instanceof PyReferenceExpression) { - PyUtil.removeQualifier((PyReferenceExpression)usageElement); - } + for (UsageInfo usage : usages) { + final PsiElement usageElement = usage.getElement(); + if (usageElement instanceof PyReferenceExpression) { + PyUtil.removeQualifier((PyReferenceExpression) usageElement); + } + } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java index 90eb3664..9e6c78dd 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java @@ -13,128 +13,139 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.codeInsight.intentions; -import consulo.language.editor.intention.BaseIntentionAction; +import com.jetbrains.python.impl.psi.impl.PyStringLiteralExpressionImpl; +import com.jetbrains.python.psi.*; import consulo.codeEditor.Editor; -import consulo.project.Project; -import consulo.util.lang.StringUtil; +import consulo.language.editor.intention.BaseIntentionAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.impl.psi.impl.PyStringLiteralExpressionImpl; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; import java.util.List; /** - * User: catherine * Intention to convert triple quoted string to single-quoted + * * For instance: + * * from: * a = """This line is ok, - * but "this" includes far too much - * whitespace at the start""" + * but "this" includes far too much + * whitespace at the start""" + * * to: * a = ("This line is ok," "\n" - * "but \"this\" includes far too much" "\n" - * "whitespace at the start") + * "but \"this\" includes far too much" "\n" + * "whitespace at the start") + * + * @author catherine */ public class PyConvertTripleQuotedStringIntention extends BaseIntentionAction { + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnTripleQuotedString(); + } - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.triple.quoted.string"); - } - - @Nonnull - @Override - public String getText() { - return PyBundle.message("INTN.triple.quoted.string"); - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!(file instanceof PyFile)) { - return false; + PyStringLiteralExpression string = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class); + if (string != null) { + final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(string, PyDocStringOwner.class); + if (docStringOwner != null) { + if (docStringOwner.getDocStringExpression() == string) { + return false; + } + } + String stringText = string.getText(); + final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); + stringText = stringText.substring(prefixLength); + if (stringText.length() >= 6) { + if (stringText.startsWith("'''") && stringText.endsWith("'''") || + stringText.startsWith("\"\"\"") && stringText.endsWith("\"\"\"")) { + return true; + } + } + } + return false; } - PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class); - if (string != null) { - final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(string, PyDocStringOwner.class); - if (docStringOwner != null) { - if (docStringOwner.getDocStringExpression() == string) return false; - } - String stringText = string.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); - stringText = stringText.substring(prefixLength); - if (stringText.length() >= 6) { - if (stringText.startsWith("'''") && stringText.endsWith("'''") || - stringText.startsWith("\"\"\"") && stringText.endsWith("\"\"\"")) return true; - } - } - return false; - } + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyStringLiteralExpression string = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + if (string != null) { + final PsiElement parent = string.getParent(); + String stringText = string.getText(); + final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); + String prefix = stringText.substring(0, prefixLength); + Character firstQuote = stringText.substring(prefixLength).charAt(0); - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class); - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - if (string != null) { - final PsiElement parent = string.getParent(); - String stringText = string.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); - String prefix = stringText.substring(0, prefixLength); - Character firstQuote = stringText.substring(prefixLength).charAt(0); + stringText = string.getStringValue(); + List subStrings = StringUtil.split(stringText, "\n", false, true); - stringText = string.getStringValue(); - List subStrings = StringUtil.split(stringText, "\n", false, true); + StringBuilder result = new StringBuilder(); + if (subStrings.size() != 1) { + result.append("("); + } + boolean lastString = false; + for (String s : subStrings) { + result.append(prefix); + result.append(firstQuote); + String validSubstring = convertToValidSubString(s, firstQuote); - StringBuilder result = new StringBuilder(); - if (subStrings.size() != 1) - result.append("("); - boolean lastString = false; - for (String s : subStrings) { - result.append(prefix); - result.append(firstQuote); - String validSubstring = convertToValidSubString(s, firstQuote); + if (s.endsWith("'''") || s.endsWith("\"\"\"")) { + lastString = true; + } + result.append(validSubstring); + result.append(firstQuote); + if (!lastString) { + result.append(" ").append("\n"); + } + } + if (subStrings.size() != 1) { + result.append(")"); + } + PyExpressionStatement e = + elementGenerator.createFromText(LanguageLevel.forElement(string), PyExpressionStatement.class, result.toString()); - if (s.endsWith("'''") || s.endsWith("\"\"\"")) { - lastString = true; + PyExpression expression = e.getExpression(); + if ((parent instanceof PyParenthesizedExpression || parent instanceof PyTupleExpression) + && expression instanceof PyParenthesizedExpression) { + expression = ((PyParenthesizedExpression) expression).getContainedExpression(); + } + if (expression != null) { + string.replace(expression); + } } - result.append(validSubstring); - result.append(firstQuote); - if (!lastString) - result.append(" ").append("\n"); - } - if (subStrings.size() != 1) - result.append(")"); - PyExpressionStatement e = elementGenerator.createFromText(LanguageLevel.forElement(string), PyExpressionStatement.class, result.toString()); - - PyExpression expression = e.getExpression(); - if ((parent instanceof PyParenthesizedExpression || parent instanceof PyTupleExpression) - && expression instanceof PyParenthesizedExpression) - expression = ((PyParenthesizedExpression)expression).getContainedExpression(); - if (expression != null) - string.replace(expression); } - } - private static String convertToValidSubString(String s, Character firstQuote) { - String subString; - if (s.startsWith("'''") || s.startsWith("\"\"\"")) - subString = convertToValidSubString(s.substring(3), firstQuote); - else if (s.endsWith("'''") || s.endsWith("\"\"\"")) { - String trimmed = s.trim(); - subString = convertToValidSubString(trimmed.substring(0, trimmed.length() - 3), firstQuote); - } - else { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder = StringUtil.escapeStringCharacters(s.length(), s, String.valueOf(firstQuote), true, stringBuilder); - subString = stringBuilder.toString(); + private static String convertToValidSubString(String s, Character firstQuote) { + String subString; + if (s.startsWith("'''") || s.startsWith("\"\"\"")) { + subString = convertToValidSubString(s.substring(3), firstQuote); + } + else if (s.endsWith("'''") || s.endsWith("\"\"\"")) { + String trimmed = s.trim(); + subString = convertToValidSubString(trimmed.substring(0, trimmed.length() - 3), firstQuote); + } + else { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder = StringUtil.escapeStringCharacters(s.length(), s, String.valueOf(firstQuote), true, stringBuilder); + subString = stringBuilder.toString(); + } + return subString; } - return subString; - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyDictConstructorToLiteralFormIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyDictConstructorToLiteralFormIntention.java index 36789b9f..6147d959 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyDictConstructorToLiteralFormIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/PyDictConstructorToLiteralFormIntention.java @@ -15,112 +15,93 @@ */ package com.jetbrains.python.impl.codeInsight.intentions; -import jakarta.annotation.Nonnull; - +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.TypeEvalContext; import consulo.codeEditor.Editor; -import consulo.project.Project; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.psi.LanguageLevel; -import com.jetbrains.python.psi.PyCallExpression; -import com.jetbrains.python.psi.PyDictLiteralExpression; -import com.jetbrains.python.psi.PyElementGenerator; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyExpressionStatement; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyKeywordArgument; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.TypeEvalContext; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import jakarta.annotation.Nonnull; /** - * User: catherine * Intention to convert dict constructor to dict literal expression with only explicit keyword arguments * For instance, * dict() -> {} * dict(a=3, b=5) -> {'a': 3, 'b': 5} * dict(foo) -> no transformation * dict(**foo) -> no transformation + * + * @author catherine */ -public class PyDictConstructorToLiteralFormIntention extends PyBaseIntentionAction -{ - @Nonnull - public String getFamilyName() - { - return PyBundle.message("INTN.convert.dict.constructor.to.dict.literal"); - } - - @Nonnull - public String getText() - { - return PyBundle.message("INTN.convert.dict.constructor.to.dict.literal"); - } +public class PyDictConstructorToLiteralFormIntention extends PyBaseIntentionAction { + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnConvertDictConstructorToDictLiteral(); + } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) - { - if(!(file instanceof PyFile)) - { - return false; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - PyCallExpression expression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyCallExpression.class); + PyCallExpression expression = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyCallExpression.class); - if(expression != null && expression.isCalleeText("dict")) - { - final TypeEvalContext context = TypeEvalContext.codeAnalysis(file.getProject(), file); - PyType type = context.getType(expression); - if(type != null && type.isBuiltin()) - { - PyExpression[] argumentList = expression.getArguments(); - for(PyExpression argument : argumentList) - { - if(!(argument instanceof PyKeywordArgument)) - { - return false; - } - } - return true; - } - } - return false; - } + if (expression != null && expression.isCalleeText("dict")) { + final TypeEvalContext context = TypeEvalContext.codeAnalysis(file.getProject(), file); + PyType type = context.getType(expression); + if (type != null && type.isBuiltin()) { + PyExpression[] argumentList = expression.getArguments(); + for (PyExpression argument : argumentList) { + if (!(argument instanceof PyKeywordArgument)) { + return false; + } + } + return true; + } + } + return false; + } - public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - PyCallExpression expression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyCallExpression.class); - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - if(expression != null) - { - replaceDictConstructor(expression, elementGenerator); - } - } + public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyCallExpression expression = + PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyCallExpression.class); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + if (expression != null) { + replaceDictConstructor(expression, elementGenerator); + } + } - private static void replaceDictConstructor(PyCallExpression expression, PyElementGenerator elementGenerator) - { - PyExpression[] argumentList = expression.getArguments(); - StringBuilder stringBuilder = new StringBuilder(); + private static void replaceDictConstructor(PyCallExpression expression, PyElementGenerator elementGenerator) { + PyExpression[] argumentList = expression.getArguments(); + StringBuilder stringBuilder = new StringBuilder(); - int size = argumentList.length; + int size = argumentList.length; - for(int i = 0; i != size; ++i) - { - PyExpression argument = argumentList[i]; - if(argument instanceof PyKeywordArgument) - { - stringBuilder.append("'"); - stringBuilder.append(((PyKeywordArgument) argument).getKeyword()); - stringBuilder.append("' : "); - stringBuilder.append(((PyKeywordArgument) argument).getValueExpression().getText()); - if(i != size - 1) - { - stringBuilder.append(","); - } - } + for (int i = 0; i != size; ++i) { + PyExpression argument = argumentList[i]; + if (argument instanceof PyKeywordArgument) { + stringBuilder.append("'"); + stringBuilder.append(((PyKeywordArgument) argument).getKeyword()); + stringBuilder.append("' : "); + stringBuilder.append(((PyKeywordArgument) argument).getValueExpression().getText()); + if (i != size - 1) { + stringBuilder.append(","); + } + } - } - PyDictLiteralExpression dict = (PyDictLiteralExpression) elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpressionStatement.class, "{" + stringBuilder.toString() + - "}").getExpression(); - expression.replace(dict); - } + } + PyDictLiteralExpression dict = (PyDictLiteralExpression) elementGenerator.createFromText( + LanguageLevel.forElement(expression), + PyExpressionStatement.class, + "{" + stringBuilder.toString() + + "}" + ).getExpression(); + expression.replace(dict); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/TypeAssertionIntention.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/TypeAssertionIntention.java index 6adeb227..ab360d73 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/TypeAssertionIntention.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/intentions/TypeAssertionIntention.java @@ -34,137 +34,122 @@ import consulo.language.psi.PsiReference; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; /** - * User: ktisha - *

    * Helps to specify type by assertion + * + * @author ktisha */ -public class TypeAssertionIntention extends PyBaseIntentionAction -{ - - @Nonnull - public String getText() - { - return PyBundle.message("INTN.insert.assertion"); - } - - @Nonnull - public String getFamilyName() - { - return PyBundle.message("INTN.insert.assertion"); - } +public class TypeAssertionIntention extends PyBaseIntentionAction { + @Nonnull + @Override + public LocalizeValue getText() { + return PyLocalize.intnInsertAssertion(); + } - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) - { - if(!(file instanceof PyFile)) - { - return false; - } + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } - PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); - PyExpression problemElement = PsiTreeUtil.getParentOfType(elementAt, PyReferenceExpression.class); - if(problemElement == null) - { - return false; - } - if(problemElement.getParent() instanceof PyWithItem) - { - return false; - } - final PyExpression qualifier = ((PyQualifiedExpression) problemElement).getQualifier(); - if(qualifier != null && !qualifier.getText().equals(PyNames.CANONICAL_SELF)) - { - problemElement = qualifier; - } - final PsiReference reference = problemElement.getReference(); - if(problemElement.getParent() instanceof PyCallExpression || - PsiTreeUtil.getParentOfType(problemElement, PyComprehensionElement.class) != null || - PsiTreeUtil.getParentOfType(problemElement, PyLambdaExpression.class) != null || - PsiTreeUtil.getParentOfType(problemElement, PyGeneratorExpression.class) != null || - (reference != null && reference.resolve() == null)) - { - return false; - } - final PyType type = TypeEvalContext.codeAnalysis(file.getProject(), file).getType(problemElement); - return type == null; - } + PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + PyExpression problemElement = PsiTreeUtil.getParentOfType(elementAt, PyReferenceExpression.class); + if (problemElement == null) { + return false; + } + if (problemElement.getParent() instanceof PyWithItem) { + return false; + } + final PyExpression qualifier = ((PyQualifiedExpression) problemElement).getQualifier(); + if (qualifier != null && !qualifier.getText().equals(PyNames.CANONICAL_SELF)) { + problemElement = qualifier; + } + final PsiReference reference = problemElement.getReference(); + if (problemElement.getParent() instanceof PyCallExpression || + PsiTreeUtil.getParentOfType(problemElement, PyComprehensionElement.class) != null || + PsiTreeUtil.getParentOfType(problemElement, PyLambdaExpression.class) != null || + PsiTreeUtil.getParentOfType(problemElement, PyGeneratorExpression.class) != null || + (reference != null && reference.resolve() == null)) { + return false; + } + final PyType type = TypeEvalContext.codeAnalysis(file.getProject(), file).getType(problemElement); + return type == null; + } - @Override - public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); - PyExpression problemElement = PsiTreeUtil.getParentOfType(elementAt, PyReferenceExpression.class); - if(problemElement != null) - { - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + @Override + public void doInvoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PsiElement elementAt = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + PyExpression problemElement = PsiTreeUtil.getParentOfType(elementAt, PyReferenceExpression.class); + if (problemElement != null) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - String name = problemElement.getText(); - final PyExpression qualifier = ((PyQualifiedExpression) problemElement).getQualifier(); - if(qualifier != null && !qualifier.getText().equals(PyNames.CANONICAL_SELF)) - { - final String referencedName = ((PyQualifiedExpression) problemElement).getReferencedName(); - if(referencedName == null || PyNames.GETITEM.equals(referencedName)) - { - name = qualifier.getText(); - } - } + String name = problemElement.getText(); + final PyExpression qualifier = ((PyQualifiedExpression) problemElement).getQualifier(); + if (qualifier != null && !qualifier.getText().equals(PyNames.CANONICAL_SELF)) { + final String referencedName = ((PyQualifiedExpression) problemElement).getReferencedName(); + if (referencedName == null || PyNames.GETITEM.equals(referencedName)) { + name = qualifier.getText(); + } + } - final String text = "assert isinstance(" + name + ", )"; - PyAssertStatement assertStatement = elementGenerator.createFromText(LanguageLevel.forElement(problemElement), PyAssertStatement.class, text); + final String text = "assert isinstance(" + name + ", )"; + PyAssertStatement assertStatement = + elementGenerator.createFromText(LanguageLevel.forElement(problemElement), PyAssertStatement.class, text); - final PsiElement parentStatement = PsiTreeUtil.getParentOfType(problemElement, PyStatement.class); - if(parentStatement == null) - { - return; - } - final PsiElement parent = parentStatement.getParent(); - PsiElement element; - if(parentStatement instanceof PyAssignmentStatement && ((PyAssignmentStatement) parentStatement).getTargets()[0] == problemElement) - { - element = parent.addAfter(assertStatement, parentStatement); - } - else - { - PyStatementList statementList = PsiTreeUtil.getParentOfType(parentStatement, PyStatementList.class); - final Document document = editor.getDocument(); + final PsiElement parentStatement = PsiTreeUtil.getParentOfType(problemElement, PyStatement.class); + if (parentStatement == null) { + return; + } + final PsiElement parent = parentStatement.getParent(); + PsiElement element; + if (parentStatement instanceof PyAssignmentStatement && ((PyAssignmentStatement) parentStatement).getTargets()[0] == problemElement) { + element = parent.addAfter(assertStatement, parentStatement); + } + else { + PyStatementList statementList = PsiTreeUtil.getParentOfType(parentStatement, PyStatementList.class); + final Document document = editor.getDocument(); - if(statementList != null) - { - PsiElement statementListParent = PsiTreeUtil.getParentOfType(statementList, PyStatement.class); - if(statementListParent != null && document.getLineNumber(statementList.getTextOffset()) == document.getLineNumber(statementListParent.getTextOffset())) - { - final String substring = TextRange.create(statementListParent.getTextRange().getStartOffset(), statementList.getTextOffset()).substring(document.getText()); - final PyStatement foo = elementGenerator.createFromText(LanguageLevel.forElement(problemElement), PyStatement.class, substring + "\n\t" + - text + "\n\t" + statementList.getText()); + if (statementList != null) { + PsiElement statementListParent = PsiTreeUtil.getParentOfType(statementList, PyStatement.class); + if (statementListParent != null && document.getLineNumber(statementList.getTextOffset()) == document.getLineNumber( + statementListParent.getTextOffset())) { + final String substring = + TextRange.create(statementListParent.getTextRange().getStartOffset(), statementList.getTextOffset()) + .substring(document.getText()); + final PyStatement foo = elementGenerator.createFromText( + LanguageLevel.forElement(problemElement), + PyStatement.class, + substring + "\n\t" + + text + "\n\t" + statementList.getText() + ); - statementListParent = statementListParent.replace(foo); - statementList = PsiTreeUtil.findChildOfType(statementListParent, PyStatementList.class); - assert statementList != null; - element = statementList.getStatements()[0]; - } - else - { - element = parent.addBefore(assertStatement, parentStatement); - } - } - else - { - element = parent.addBefore(assertStatement, parentStatement); - } - } + statementListParent = statementListParent.replace(foo); + statementList = PsiTreeUtil.findChildOfType(statementListParent, PyStatementList.class); + assert statementList != null; + element = statementList.getStatements()[0]; + } + else { + element = parent.addBefore(assertStatement, parentStatement); + } + } + else { + element = parent.addBefore(assertStatement, parentStatement); + } + } - int textOffSet = element.getTextOffset(); - editor.getCaretModel().moveToOffset(textOffSet); + int textOffSet = element.getTextOffset(); + editor.getCaretModel().moveToOffset(textOffSet); - element = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element); - final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(element); - builder.replaceRange(TextRange.create(text.length() - 1, text.length() - 1), PyNames.OBJECT); - Template template = builder.buildInlineTemplate(); - TemplateManager.getInstance(project).startTemplate(editor, template); - } - } + element = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element); + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(element); + builder.replaceRange(TextRange.create(text.length() - 1, text.length() - 1), PyNames.OBJECT); + Template template = builder.buildInlineTemplate(); + TemplateManager.getInstance(project).startTemplate(editor, template); + } + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/testIntegration/CreateTestAction.java b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/testIntegration/CreateTestAction.java index 154e7f58..6a15c0ac 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/testIntegration/CreateTestAction.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/codeInsight/testIntegration/CreateTestAction.java @@ -15,119 +15,105 @@ */ package com.jetbrains.python.impl.codeInsight.testIntegration; -import java.util.List; - -import jakarta.annotation.Nonnull; - import com.google.common.collect.Lists; +import com.jetbrains.python.impl.testing.pytest.PyTestUtil; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFunction; +import consulo.codeEditor.Editor; import consulo.language.editor.CodeInsightBundle; import consulo.language.editor.intention.PsiElementBaseIntentionAction; -import consulo.undoRedo.CommandProcessor; -import consulo.codeEditor.Editor; -import consulo.project.Project; -import consulo.util.lang.StringUtil; +import consulo.language.editor.localize.CodeInsightLocalize; import consulo.language.psi.PsiDirectory; import consulo.language.psi.PsiDocumentManager; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.impl.testing.pytest.PyTestUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.undoRedo.CommandProcessor; +import consulo.util.lang.StringUtil; +import jakarta.annotation.Nonnull; + +import java.util.List; /** - * User: catherine + * @author catherine */ -public class CreateTestAction extends PsiElementBaseIntentionAction -{ - @Nonnull - public String getFamilyName() - { - return CodeInsightBundle.message("intention.create.test"); - } - +public class CreateTestAction extends PsiElementBaseIntentionAction { + @Nonnull + @Override + public LocalizeValue getText() { + return CodeInsightLocalize.intentionCreateTest(); + } - public boolean isAvailable(@Nonnull Project project, Editor editor, @Nonnull PsiElement element) - { - PyClass psiClass = PsiTreeUtil.getParentOfType(element, PyClass.class); + public boolean isAvailable(@Nonnull Project project, Editor editor, @Nonnull PsiElement element) { + PyClass psiClass = PsiTreeUtil.getParentOfType(element, PyClass.class); - if(psiClass != null && PyTestUtil.isPyTestClass(psiClass, null)) - { - return false; - } - return true; - } + if (psiClass != null && PyTestUtil.isPyTestClass(psiClass, null)) { + return false; + } + return true; + } - @Override - public void invoke(final @Nonnull Project project, Editor editor, @Nonnull PsiElement element) throws IncorrectOperationException - { - final PyFunction srcFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); - final PyClass srcClass = PsiTreeUtil.getParentOfType(element, PyClass.class); + @Override + public void invoke(final @Nonnull Project project, Editor editor, @Nonnull PsiElement element) throws IncorrectOperationException { + final PyFunction srcFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + final PyClass srcClass = PsiTreeUtil.getParentOfType(element, PyClass.class); - if(srcClass == null && srcFunction == null) - { - return; - } + if (srcClass == null && srcFunction == null) { + return; + } - final PsiDirectory dir = element.getContainingFile().getContainingDirectory(); - final CreateTestDialog d = new CreateTestDialog(project); - if(srcClass != null) - { - d.setClassName("Test" + StringUtil.capitalize(srcClass.getName())); - d.setFileName("test_" + StringUtil.decapitalize(srcClass.getName()) + ".py"); + final PsiDirectory dir = element.getContainingFile().getContainingDirectory(); + final CreateTestDialog d = new CreateTestDialog(project); + if (srcClass != null) { + d.setClassName("Test" + StringUtil.capitalize(srcClass.getName())); + d.setFileName("test_" + StringUtil.decapitalize(srcClass.getName()) + ".py"); - if(dir != null) - { - d.setTargetDir(dir.getVirtualFile().getPath()); - } + if (dir != null) { + d.setTargetDir(dir.getVirtualFile().getPath()); + } - if(srcFunction != null) - { - d.methodsSize(1); - d.addMethod("test_" + srcFunction.getName(), 0); - } - else - { - final List methods = Lists.newArrayList(); - srcClass.visitMethods(pyFunction -> { - if(pyFunction.getName() != null && !pyFunction.getName().startsWith("__")) - { - methods.add(pyFunction); - } - return true; - }, false, null); + if (srcFunction != null) { + d.methodsSize(1); + d.addMethod("test_" + srcFunction.getName(), 0); + } + else { + final List methods = Lists.newArrayList(); + srcClass.visitMethods(pyFunction -> { + if (pyFunction.getName() != null && !pyFunction.getName().startsWith("__")) { + methods.add(pyFunction); + } + return true; + }, false, null); - d.methodsSize(methods.size()); - int i = 0; - for(PyFunction f : methods) - { - d.addMethod("test_" + f.getName(), i); - ++i; - } - } - } - else - { - d.setClassName("Test" + StringUtil.capitalize(srcFunction.getName())); - d.setFileName("test_" + StringUtil.decapitalize(srcFunction.getName()) + ".py"); - if(dir != null) - { - d.setTargetDir(dir.getVirtualFile().getPath()); - } + d.methodsSize(methods.size()); + int i = 0; + for (PyFunction f : methods) { + d.addMethod("test_" + f.getName(), i); + ++i; + } + } + } + else { + d.setClassName("Test" + StringUtil.capitalize(srcFunction.getName())); + d.setFileName("test_" + StringUtil.decapitalize(srcFunction.getName()) + ".py"); + if (dir != null) { + d.setTargetDir(dir.getVirtualFile().getPath()); + } - d.methodsSize(1); - d.addMethod("test_" + srcFunction.getName(), 0); - } + d.methodsSize(1); + d.addMethod("test_" + srcFunction.getName(), 0); + } - if(!d.showAndGet()) - { - return; - } - CommandProcessor.getInstance().executeCommand(project, () -> { - PsiFile e = PyTestCreator.generateTestAndNavigate(project, d); - final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); - documentManager.commitAllDocuments(); - }, CodeInsightBundle.message("intention.create.test"), this); - } + if (!d.showAndGet()) { + return; + } + CommandProcessor.getInstance().executeCommand(project, () -> { + PsiFile e = PyTestCreator.generateTestAndNavigate(project, d); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitAllDocuments(); + }, CodeInsightBundle.message("intention.create.test"), this); + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyDocstringTypesInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyDocstringTypesInspection.java index c8ac7fea..2b9ad688 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyDocstringTypesInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyDocstringTypesInspection.java @@ -15,27 +15,26 @@ */ package com.jetbrains.python.impl.inspections; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.debugger.PySignature; import com.jetbrains.python.impl.debugger.PySignatureCacheManager; import com.jetbrains.python.impl.debugger.PySignatureUtil; import com.jetbrains.python.impl.documentation.docstrings.DocStringUtil; import com.jetbrains.python.impl.documentation.docstrings.PlainDocString; +import com.jetbrains.python.impl.psi.types.PyTypeChecker; +import com.jetbrains.python.impl.psi.types.PyTypeParser; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStringLiteralExpression; import com.jetbrains.python.psi.StructuredDocString; import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.impl.psi.types.PyTypeChecker; -import com.jetbrains.python.impl.psi.types.PyTypeParser; import com.jetbrains.python.toolbox.Substring; import consulo.annotation.component.ExtensionImpl; import consulo.language.editor.inspection.*; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; - +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -44,120 +43,117 @@ */ @ExtensionImpl public class PyDocstringTypesInspection extends PyInspection { - @Nls - @Nonnull - @Override - public String getDisplayName() { - return PyBundle.message("INSP.NAME.docstring.types"); - } - - @Override - public boolean isEnabledByDefault() { - return false; - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, - boolean isOnTheFly, - @Nonnull LocalInspectionToolSession session, - Object state) { - return new Visitor(holder, session); - } - - public static class Visitor extends PyInspectionVisitor { - public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session) { - super(holder, session); + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return PyLocalize.inspNameDocstringTypes(); } @Override - public void visitPyFunction(PyFunction function) { - final String name = function.getName(); - if (name != null && !name.startsWith("_")) { - checkDocString(function); - } + public boolean isEnabledByDefault() { + return false; } - private void checkDocString(@Nonnull PyFunction function) { - final PyStringLiteralExpression docStringExpression = function.getDocStringExpression(); - if (docStringExpression != null) { - PySignatureCacheManager manager = PySignatureCacheManager.getInstance(function.getProject()); - PySignature signature = manager.findSignature(function); - if (signature != null) { - checkParameters(function, docStringExpression, signature); - } - } + @Nonnull + @Override + public PsiElementVisitor buildVisitor( + @Nonnull ProblemsHolder holder, + boolean isOnTheFly, + @Nonnull LocalInspectionToolSession session, + Object state + ) { + return new Visitor(holder, session); } - private void checkParameters(PyFunction function, PyStringLiteralExpression node, PySignature signature) { - final StructuredDocString docString = DocStringUtil.parseDocString(node); - if (docString instanceof PlainDocString) { - return; - } - - for (String param : docString.getParameters()) { - Substring type = docString.getParamTypeSubstring(param); - if (type != null) { - String dynamicType = signature.getArgTypeQualifiedName(param); - if (dynamicType != null) { - String dynamicTypeShortName = PySignatureUtil.getShortestImportableName(function, dynamicType); - if (!match(function, dynamicType, type.getValue())) { - registerProblem(node, - "Dynamically inferred type '" + - dynamicTypeShortName + - "' doesn't match specified type '" + - type + "'", - ProblemHighlightType.WEAK_WARNING, - null, - type.getTextRange(), - new ChangeTypeQuickFix(param, type, dynamicTypeShortName, node)); + public static class Visitor extends PyInspectionVisitor { + public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyFunction(PyFunction function) { + final String name = function.getName(); + if (name != null && !name.startsWith("_")) { + checkDocString(function); } - } } - } - } - private boolean match(PsiElement anchor, String dynamicTypeName, String specifiedTypeName) { - final PyType dynamicType = PyTypeParser.getTypeByName(anchor, dynamicTypeName); - final PyType specifiedType = PyTypeParser.getTypeByName(anchor, specifiedTypeName); - return PyTypeChecker.match(specifiedType, dynamicType, myTypeEvalContext); - } - } + private void checkDocString(@Nonnull PyFunction function) { + final PyStringLiteralExpression docStringExpression = function.getDocStringExpression(); + if (docStringExpression != null) { + PySignatureCacheManager manager = PySignatureCacheManager.getInstance(function.getProject()); + PySignature signature = manager.findSignature(function); + if (signature != null) { + checkParameters(function, docStringExpression, signature); + } + } + } + private void checkParameters(PyFunction function, PyStringLiteralExpression node, PySignature signature) { + final StructuredDocString docString = DocStringUtil.parseDocString(node); + if (docString instanceof PlainDocString) { + return; + } - private static class ChangeTypeQuickFix implements LocalQuickFix { - private final String myParamName; - private final Substring myTypeSubstring; - private final String myNewType; - private final PyStringLiteralExpression myStringLiteralExpression; + for (String param : docString.getParameters()) { + Substring type = docString.getParamTypeSubstring(param); + if (type != null) { + String dynamicType = signature.getArgTypeQualifiedName(param); + if (dynamicType != null) { + String dynamicTypeShortName = PySignatureUtil.getShortestImportableName(function, dynamicType); + if (!match(function, dynamicType, type.getValue())) { + registerProblem( + node, + "Dynamically inferred type '" + + dynamicTypeShortName + + "' doesn't match specified type '" + + type + "'", + ProblemHighlightType.WEAK_WARNING, + null, + type.getTextRange(), + new ChangeTypeQuickFix(param, type, dynamicTypeShortName, node) + ); + } + } + } + } + } - private ChangeTypeQuickFix(String name, Substring substring, String type, PyStringLiteralExpression expression) { - myParamName = name; - myTypeSubstring = substring; - myNewType = type; - myStringLiteralExpression = expression; + private boolean match(PsiElement anchor, String dynamicTypeName, String specifiedTypeName) { + final PyType dynamicType = PyTypeParser.getTypeByName(anchor, dynamicTypeName); + final PyType specifiedType = PyTypeParser.getTypeByName(anchor, specifiedTypeName); + return PyTypeChecker.match(specifiedType, dynamicType, myTypeEvalContext); + } } - @Nonnull - @Override - public String getName() { - return "Change " + myParamName + " type from " + myTypeSubstring.getValue() + " to " + myNewType; - } - @Nonnull - @Override - public String getFamilyName() { - return "Fix docstring"; - } + private static class ChangeTypeQuickFix implements LocalQuickFix { + private final String myParamName; + private final Substring myTypeSubstring; + private final String myNewType; + private final PyStringLiteralExpression myStringLiteralExpression; - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - String newValue = myTypeSubstring.getTextRange().replace(myTypeSubstring.getSuperString(), myNewType); + private ChangeTypeQuickFix(String name, Substring substring, String type, PyStringLiteralExpression expression) { + myParamName = name; + myTypeSubstring = substring; + myNewType = type; + myStringLiteralExpression = expression; + } - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Change " + myParamName + " type from " + myTypeSubstring.getValue() + " to " + myNewType); + } + + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + String newValue = myTypeSubstring.getTextRange().replace(myTypeSubstring.getSuperString(), myNewType); - myStringLiteralExpression.replace(elementGenerator.createDocstring(newValue).getExpression()); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + + myStringLiteralExpression.replace(elementGenerator.createDocstring(newValue).getExpression()); + } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInspection.java index 6ca59a33..66f89e37 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInspection.java @@ -16,73 +16,72 @@ package com.jetbrains.python.impl.inspections; import com.jetbrains.python.PythonLanguage; -import com.jetbrains.python.psi.PyExpressionCodeFragment; import com.jetbrains.python.impl.psi.impl.PyFileImpl; +import com.jetbrains.python.psi.PyExpressionCodeFragment; import consulo.language.Language; import consulo.language.editor.inspection.LocalInspectionTool; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; -import org.intellij.lang.annotations.Pattern; -import org.jetbrains.annotations.Nls; - +import consulo.localize.LocalizeValue; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import org.intellij.lang.annotations.Pattern; public abstract class PyInspection extends LocalInspectionTool { - @Pattern(VALID_ID_PATTERN) - @Nonnull - @Override - public String getID() { - //noinspection PatternValidation - return getShortName(super.getID()); - } + @Pattern(VALID_ID_PATTERN) + @Nonnull + @Override + public String getID() { + //noinspection PatternValidation + return getShortName(super.getID()); + } - @Nullable - @Override - public Language getLanguage() { - return PythonLanguage.getInstance(); - } + @Nullable + @Override + public Language getLanguage() { + return PythonLanguage.getInstance(); + } - @Nls - @Nonnull - @Override - public String getGroupDisplayName() { - return "General"; - } + @Nonnull + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.inspectionGeneralToolsGroupName(); + } - @Nonnull - @Override - public String getShortName() { - return getClass().getSimpleName(); - } + @Nonnull + @Override + public String getShortName() { + return getClass().getSimpleName(); + } - @Override - public boolean isEnabledByDefault() { - return true; - } + @Override + public boolean isEnabledByDefault() { + return true; + } - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } + @Nonnull + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; + } - @Override - public boolean isSuppressedFor(@Nonnull PsiElement element) { - final PsiFile file = element.getContainingFile(); - if (file instanceof PyFileImpl && !((PyFileImpl)file).isAcceptedFor(this.getClass())) { - return true; + @Override + public boolean isSuppressedFor(@Nonnull PsiElement element) { + final PsiFile file = element.getContainingFile(); + if (file instanceof PyFileImpl && !((PyFileImpl) file).isAcceptedFor(this.getClass())) { + return true; + } + return isSuppressForCodeFragment(element) || super.isSuppressedFor(element); } - return isSuppressForCodeFragment(element) || super.isSuppressedFor(element); - } - private boolean isSuppressForCodeFragment(@Nullable PsiElement element) { - return isSuppressForCodeFragment() && PsiTreeUtil.getParentOfType(element, PyExpressionCodeFragment.class) != null; - } + private boolean isSuppressForCodeFragment(@Nullable PsiElement element) { + return isSuppressForCodeFragment() && PsiTreeUtil.getParentOfType(element, PyExpressionCodeFragment.class) != null; + } - protected boolean isSuppressForCodeFragment() { - return false; - } + protected boolean isSuppressForCodeFragment() { + return false; + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInterpreterInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInterpreterInspection.java index 20697361..1a8b00d1 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInterpreterInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyInterpreterInspection.java @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyFile; import consulo.annotation.component.ExtensionImpl; import consulo.application.ApplicationManager; @@ -26,80 +24,73 @@ import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.inspection.ProblemsHolder; import consulo.language.psi.PsiElementVisitor; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; - +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; /** - * User: ktisha + * @author ktisha */ @ExtensionImpl public class PyInterpreterInspection extends PyInspection { - - @Nls - @Nonnull - public String getDisplayName() { - return PyBundle.message("INSP.NAME.invalid.interpreter"); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, - final boolean isOnTheFly, - @Nonnull final LocalInspectionToolSession session, - Object state) { - return new Visitor(holder, session); - } - - public static class Visitor extends PyInspectionVisitor { - - public Visitor(@Nullable ProblemsHolder holder, - @Nonnull LocalInspectionToolSession session) { - super(holder, session); - } - + @Nonnull @Override - public void visitPyFile(PyFile node) { - super.visitPyFile(node); - /*if (PlatformUtils.isPyCharm()) { - final Module module = ModuleUtilCore.findModuleForPsiElement(node); - if (module != null) { - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if (sdk == null) { - registerProblem(node, "No Python interpreter configured for the project", new ConfigureInterpreterFix()); - } - else if (PythonSdkType.isInvalid(sdk)) { - registerProblem(node, "Invalid Python interpreter selected for the project", new ConfigureInterpreterFix()); - } - } - } */ + public LocalizeValue getDisplayName() { + return PyLocalize.inspNameInvalidInterpreter(); } - } - private static class ConfigureInterpreterFix implements LocalQuickFix { @Nonnull @Override - public String getName() { - return "Configure Python Interpreter"; + public PsiElementVisitor buildVisitor( + @Nonnull ProblemsHolder holder, + final boolean isOnTheFly, + @Nonnull final LocalInspectionToolSession session, + Object state + ) { + return new Visitor(holder, session); } - @Nonnull - @Override - public String getFamilyName() { - return "Configure Python Interpreter"; + public static class Visitor extends PyInspectionVisitor { + public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyFile(PyFile node) { + super.visitPyFile(node); + /*if (PlatformUtils.isPyCharm()) { + final Module module = ModuleUtilCore.findModuleForPsiElement(node); + if (module != null) { + final Sdk sdk = PythonSdkType.findPythonSdk(module); + if (sdk == null) { + registerProblem(node, "No Python interpreter configured for the project", new ConfigureInterpreterFix()); + } + else if (PythonSdkType.isInvalid(sdk)) { + registerProblem(node, "Invalid Python interpreter selected for the project", new ConfigureInterpreterFix()); + } + } + } */ + } } - @Override - public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) { - ApplicationManager.getApplication().invokeLater(new Runnable() { + private static class ConfigureInterpreterFix implements LocalQuickFix { + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Configure Python Interpreter"); + } + @Override - public void run() { - // outside of read action - ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); + public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + // outside of read action + ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); + } + }); } - }); } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPackageRequirementsInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPackageRequirementsInspection.java index 7181c5bf..827f8704 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPackageRequirementsInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPackageRequirementsInspection.java @@ -38,555 +38,490 @@ import consulo.language.editor.inspection.scheme.InspectionProjectProfileManager; import consulo.language.psi.*; import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.module.Module; import consulo.process.ExecutionException; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.Messages; import consulo.undoRedo.CommandProcessor; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; /** * @author vlan */ @ExtensionImpl -public class PyPackageRequirementsInspection extends PyInspection -{ - private static final Logger LOG = Logger.getInstance(PyPackageRequirementsInspection.class); - - @Nonnull - @Override - public String getDisplayName() - { - return "Package requirements"; - } - - @Nonnull - @Override - public InspectionToolState createStateProvider() - { - return new PyPackageRequirementsInspectionState(); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, - boolean isOnTheFly, - @Nonnull LocalInspectionToolSession session, - Object state) - { - PyPackageRequirementsInspectionState inspectionState = (PyPackageRequirementsInspectionState) state; - return new Visitor(holder, session, inspectionState.ignoredPackages); - } - - private static class Visitor extends PyInspectionVisitor - { - private final Set myIgnoredPackages; - - public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session, Collection ignoredPackages) - { - super(holder, session); - myIgnoredPackages = ImmutableSet.copyOf(ignoredPackages); - } - - @Override - public void visitPyFile(PyFile node) - { - final Module module = ModuleUtilCore.findModuleForPsiElement(node); - if(module != null) - { - if(isRunningPackagingTasks(module)) - { - return; - } - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if(sdk != null) - { - final List unsatisfied = findUnsatisfiedRequirements(module, sdk, myIgnoredPackages); - if(unsatisfied != null && !unsatisfied.isEmpty()) - { - final boolean plural = unsatisfied.size() > 1; - String msg = String.format("Package requirement%s %s %s not satisfied", - plural ? "s" : "", - PyPackageUtil.requirementsToString(unsatisfied), - plural ? "are" : "is"); - final Set unsatisfiedNames = new HashSet<>(); - for(PyRequirement req : unsatisfied) - { - unsatisfiedNames.add(req.getFullName()); - } - final List quickFixes = new ArrayList<>(); - quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied)); - quickFixes.add(new IgnoreRequirementFix(unsatisfiedNames)); - registerProblem(node, - msg, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - null, - quickFixes.toArray(new LocalQuickFix[quickFixes.size()])); - } - } - } - } - - @Override - public void visitPyFromImportStatement(PyFromImportStatement node) - { - final PyReferenceExpression expr = node.getImportSource(); - if(expr != null) - { - checkPackageNameInRequirements(expr); - } - } - - @Override - public void visitPyImportStatement(PyImportStatement node) - { - for(PyImportElement element : node.getImportElements()) - { - final PyReferenceExpression expr = element.getImportReferenceExpression(); - if(expr != null) - { - checkPackageNameInRequirements(expr); - } - } - } - - private void checkPackageNameInRequirements(@Nonnull PyQualifiedExpression importedExpression) - { - for(PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) - { - if(extension.ignorePackageNameInRequirements(importedExpression)) - { - return; - } - } - final PyExpression packageReferenceExpression = PyPsiUtils.getFirstQualifier(importedExpression); - if(packageReferenceExpression != null) - { - final String packageName = packageReferenceExpression.getName(); - if(packageName != null && !myIgnoredPackages.contains(packageName)) - { - if(!ApplicationManager.getApplication().isUnitTestMode() && !PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) - { - return; - } - final Collection stdlibPackages = PyStdlibUtil.getPackages(); - if(stdlibPackages != null) - { - if(stdlibPackages.contains(packageName)) - { - return; - } - } - if(PyPackageUtil.SETUPTOOLS.equals(packageName)) - { - return; - } - final Module module = ModuleUtilCore.findModuleForPsiElement(packageReferenceExpression); - if(module != null) - { - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if(sdk != null) - { - final PyPackageManager manager = PyPackageManager.getInstance(sdk); - Collection requirements = manager.getRequirements(module); - if(requirements != null) - { - requirements = getTransitiveRequirements(sdk, requirements, new HashSet<>()); - } - if(requirements == null) - { - return; - } - for(PyRequirement req : requirements) - { - if(packageName.equalsIgnoreCase(req.getName())) - { - return; - } - } - if(!ApplicationManager.getApplication().isUnitTestMode()) - { - final PsiReference reference = packageReferenceExpression.getReference(); - if(reference != null) - { - final PsiElement element = reference.resolve(); - if(element != null) - { - final PsiFile file = element.getContainingFile(); - if(file != null) - { - final VirtualFile virtualFile = file.getVirtualFile(); - if(ModuleUtilCore.moduleContainsFile(module, virtualFile, false)) - { - return; - } - } - } - } - } - final List quickFixes = new ArrayList<>(); - quickFixes.add(new AddToRequirementsFix(module, packageName, LanguageLevel.forElement(importedExpression))); - quickFixes.add(new IgnoreRequirementFix(Collections.singleton(packageName))); - registerProblem(packageReferenceExpression, - String.format("Package '%s' is not listed in project requirements", packageName), - ProblemHighlightType.WEAK_WARNING, - null, - quickFixes.toArray(new LocalQuickFix[quickFixes.size()])); - } - } - } - } - } - } - - @Nullable - private static Set getTransitiveRequirements(@Nonnull Sdk sdk, - @Nonnull Collection requirements, - @Nonnull Set visited) - { - if(requirements.isEmpty()) - { - return Collections.emptySet(); - } - final Set results = new HashSet<>(requirements); - final List packages = PyPackageManager.getInstance(sdk).getPackages(); - if(packages == null) - { - return null; - } - for(PyRequirement req : requirements) - { - final PyPackage pkg = req.match(packages); - if(pkg != null && !visited.contains(pkg)) - { - visited.add(pkg); - final Set transitive = getTransitiveRequirements(sdk, pkg.getRequirements(), visited); - if(transitive == null) - { - return null; - } - results.addAll(transitive); - } - } - return results; - } - - @Nullable - private static List findUnsatisfiedRequirements(@Nonnull Module module, - @Nonnull Sdk sdk, - @Nonnull Set ignoredPackages) - { - final PyPackageManager manager = PyPackageManager.getInstance(sdk); - List requirements = manager.getRequirements(module); - if(requirements != null) - { - final List packages = manager.getPackages(); - if(packages == null) - { - return null; - } - final List unsatisfied = new ArrayList<>(); - for(PyRequirement req : requirements) - { - if(!ignoredPackages.contains(req.getName()) && req.match(packages) == null) - { - unsatisfied.add(req); - } - } - return unsatisfied; - } - return null; - } - - private static void setRunningPackagingTasks(@Nonnull Module module, boolean value) - { - module.putUserData(PyPackageManager.RUNNING_PACKAGING_TASKS, value); - } - - private static boolean isRunningPackagingTasks(@Nonnull Module module) - { - final Boolean value = module.getUserData(PyPackageManager.RUNNING_PACKAGING_TASKS); - return value != null && value; - } - - public static class PyInstallRequirementsFix implements LocalQuickFix - { - @Nonnull - private String myName; - @Nonnull - private final Module myModule; - @Nonnull - private Sdk mySdk; - @Nonnull - private final List myUnsatisfied; - - public PyInstallRequirementsFix(@Nullable String name, - @Nonnull Module module, - @Nonnull Sdk sdk, - @Nonnull List unsatisfied) - { - final boolean plural = unsatisfied.size() > 1; - myName = name != null ? name : String.format("Install requirement%s", plural ? "s" : ""); - myModule = module; - mySdk = sdk; - myUnsatisfied = unsatisfied; - } - - @Nonnull - @Override - public String getFamilyName() - { - return myName; - } - - @Override - public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) - { - boolean installManagement = false; - final PyPackageManager manager = PyPackageManager.getInstance(mySdk); - final List packages = manager.getPackages(); - if(packages == null) - { - return; - } - if(!PyPackageUtil.hasManagement(packages)) - { - final int result = Messages.showYesNoDialog(project, - "Python packaging tools are required for installing packages. Do you want to " + "install 'pip' and 'setuptools' for your " + - "interpreter?", - "Install Python Packaging Tools", - Messages.getQuestionIcon()); - if(result == Messages.YES) - { - installManagement = true; - } - else - { - return; - } - } - final List chosen; - if(myUnsatisfied.size() > 1) - { - final PyChooseRequirementsDialog dialog = new PyChooseRequirementsDialog(project, myUnsatisfied); - if(dialog.showAndGet()) - { - chosen = dialog.getMarkedElements(); - } - else - { - chosen = Collections.emptyList(); - } - } - else - { - chosen = myUnsatisfied; - } - if(chosen.isEmpty()) - { - return; - } - if(installManagement) - { - final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) - { - @Override - public void finished(List exceptions) - { - super.finished(exceptions); - if(exceptions.isEmpty()) - { - installRequirements(project, chosen); - } - } - }); - ui.installManagement(); - } - else - { - installRequirements(project, chosen); - } - } - - private void installRequirements(Project project, List requirements) - { - final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule)); - ui.install(requirements, Collections.emptyList()); - } - } - - public static class InstallAndImportQuickFix implements LocalQuickFix - { - - private final Sdk mySdk; - private final Module myModule; - private String myPackageName; - @Nullable - private final String myAsName; - @Nonnull - private final SmartPsiElementPointer myNode; - - public InstallAndImportQuickFix(@Nonnull final String packageName, @Nullable final String asName, @Nonnull final PyElement node) - { - myPackageName = packageName; - myAsName = asName; - myNode = SmartPointerManager.getInstance(node.getProject()).createSmartPsiElementPointer(node, node.getContainingFile()); - myModule = ModuleUtilCore.findModuleForPsiElement(node); - mySdk = PythonSdkType.findPythonSdk(myModule); - } - - @Nonnull - public String getFamilyName() - { - return "Install and import package " + myPackageName; - } - - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) - { - final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) - { - @Override - public void finished(List exceptions) - { - super.finished(exceptions); - if(exceptions.isEmpty()) - { - - final PyElement element = myNode.getElement(); - if(element == null) - { - return; - } - - CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { - AddImportHelper.addImportStatement(element.getContainingFile(), - myPackageName, - myAsName, - AddImportHelper.ImportPriority.THIRD_PARTY, - element); - }), "Add import", "Add import"); - } - } - }); - ui.install(Collections.singletonList(new PyRequirement(myPackageName)), Collections.emptyList()); - } - } - - private static class UIListener implements PyPackageManagerUI.Listener - { - private final Module myModule; - - public UIListener(Module module) - { - myModule = module; - } - - @Override - public void started() - { - setRunningPackagingTasks(myModule, true); - } - - @Override - public void finished(List exceptions) - { - setRunningPackagingTasks(myModule, false); - } - } - - - private static class IgnoreRequirementFix implements LocalQuickFix - { - @Nonnull - private final Set myPackageNames; - - public IgnoreRequirementFix(@Nonnull Set packageNames) - { - myPackageNames = packageNames; - } - - @Nonnull - @Override - public String getFamilyName() - { - final boolean plural = myPackageNames.size() > 1; - return String.format("Ignore requirement%s", plural ? "s" : ""); - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - final PsiElement element = descriptor.getPsiElement(); - if(element != null) - { - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - - profile.modifyToolSettings(PyPackageRequirementsInspection.class.getSimpleName(), element, (inspectionTool, state) -> - { - for(String name : myPackageNames) - { - if(!state.ignoredPackages.contains(name)) - { - state.ignoredPackages.add(name); - } - } - }); - } - } - } - - private static class AddToRequirementsFix implements LocalQuickFix - { - @Nonnull - private final Module myModule; - @Nonnull - private final String myPackageName; - @Nonnull - private final LanguageLevel myLanguageLevel; - - private AddToRequirementsFix(@Nonnull Module module, @Nonnull String packageName, @Nonnull LanguageLevel languageLevel) - { - myModule = module; - myPackageName = packageName; - myLanguageLevel = languageLevel; - } - - @Nonnull - @Override - public String getFamilyName() - { - return String.format("Add requirement '%s' to %s", myPackageName, calculateTarget()); - } - - @Override - public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) - { - CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { - PyPackageUtil.addRequirementToTxtOrSetupPy(myModule, myPackageName, myLanguageLevel); - }), getName(), null); - } - - @Nonnull - private String calculateTarget() - { - final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(myModule); - if(requirementsTxt != null) - { - return requirementsTxt.getName(); - } - else if(PyPackageUtil.findSetupCall(myModule) != null) - { - return "setup.py"; - } - else - { - return "project requirements"; - } - } - } +public class PyPackageRequirementsInspection extends PyInspection { + private static final Logger LOG = Logger.getInstance(PyPackageRequirementsInspection.class); + + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Package requirements"); + } + + @Nonnull + @Override + public InspectionToolState createStateProvider() { + return new PyPackageRequirementsInspectionState(); + } + + @Nonnull + @Override + public PsiElementVisitor buildVisitor( + @Nonnull ProblemsHolder holder, + boolean isOnTheFly, + @Nonnull LocalInspectionToolSession session, + Object state + ) { + PyPackageRequirementsInspectionState inspectionState = (PyPackageRequirementsInspectionState) state; + return new Visitor(holder, session, inspectionState.ignoredPackages); + } + + private static class Visitor extends PyInspectionVisitor { + private final Set myIgnoredPackages; + + public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session, Collection ignoredPackages) { + super(holder, session); + myIgnoredPackages = ImmutableSet.copyOf(ignoredPackages); + } + + @Override + public void visitPyFile(PyFile node) { + final Module module = ModuleUtilCore.findModuleForPsiElement(node); + if (module != null) { + if (isRunningPackagingTasks(module)) { + return; + } + final Sdk sdk = PythonSdkType.findPythonSdk(module); + if (sdk != null) { + final List unsatisfied = findUnsatisfiedRequirements(module, sdk, myIgnoredPackages); + if (unsatisfied != null && !unsatisfied.isEmpty()) { + final boolean plural = unsatisfied.size() > 1; + String msg = String.format( + "Package requirement%s %s %s not satisfied", + plural ? "s" : "", + PyPackageUtil.requirementsToString(unsatisfied), + plural ? "are" : "is" + ); + final Set unsatisfiedNames = new HashSet<>(); + for (PyRequirement req : unsatisfied) { + unsatisfiedNames.add(req.getFullName()); + } + final List quickFixes = new ArrayList<>(); + quickFixes.add(new PyInstallRequirementsFix( + LocalizeValue.localizeTODO(String.format("Install requirement%s", unsatisfied.size() > 1 ? "s" : "")), + module, + sdk, + unsatisfied + )); + quickFixes.add(new IgnoreRequirementFix(unsatisfiedNames)); + registerProblem( + node, + msg, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + null, + quickFixes.toArray(new LocalQuickFix[quickFixes.size()]) + ); + } + } + } + } + + @Override + public void visitPyFromImportStatement(PyFromImportStatement node) { + final PyReferenceExpression expr = node.getImportSource(); + if (expr != null) { + checkPackageNameInRequirements(expr); + } + } + + @Override + public void visitPyImportStatement(PyImportStatement node) { + for (PyImportElement element : node.getImportElements()) { + final PyReferenceExpression expr = element.getImportReferenceExpression(); + if (expr != null) { + checkPackageNameInRequirements(expr); + } + } + } + + private void checkPackageNameInRequirements(@Nonnull PyQualifiedExpression importedExpression) { + for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) { + if (extension.ignorePackageNameInRequirements(importedExpression)) { + return; + } + } + final PyExpression packageReferenceExpression = PyPsiUtils.getFirstQualifier(importedExpression); + if (packageReferenceExpression != null) { + final String packageName = packageReferenceExpression.getName(); + if (packageName != null && !myIgnoredPackages.contains(packageName)) { + if (!ApplicationManager.getApplication().isUnitTestMode() && !PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) { + return; + } + final Collection stdlibPackages = PyStdlibUtil.getPackages(); + if (stdlibPackages != null) { + if (stdlibPackages.contains(packageName)) { + return; + } + } + if (PyPackageUtil.SETUPTOOLS.equals(packageName)) { + return; + } + final Module module = ModuleUtilCore.findModuleForPsiElement(packageReferenceExpression); + if (module != null) { + final Sdk sdk = PythonSdkType.findPythonSdk(module); + if (sdk != null) { + final PyPackageManager manager = PyPackageManager.getInstance(sdk); + Collection requirements = manager.getRequirements(module); + if (requirements != null) { + requirements = getTransitiveRequirements(sdk, requirements, new HashSet<>()); + } + if (requirements == null) { + return; + } + for (PyRequirement req : requirements) { + if (packageName.equalsIgnoreCase(req.getName())) { + return; + } + } + if (!ApplicationManager.getApplication().isUnitTestMode()) { + final PsiReference reference = packageReferenceExpression.getReference(); + if (reference != null) { + final PsiElement element = reference.resolve(); + if (element != null) { + final PsiFile file = element.getContainingFile(); + if (file != null) { + final VirtualFile virtualFile = file.getVirtualFile(); + if (ModuleUtilCore.moduleContainsFile(module, virtualFile, false)) { + return; + } + } + } + } + } + final List quickFixes = new ArrayList<>(); + quickFixes.add(new AddToRequirementsFix(module, packageName, LanguageLevel.forElement(importedExpression))); + quickFixes.add(new IgnoreRequirementFix(Collections.singleton(packageName))); + registerProblem( + packageReferenceExpression, + String.format("Package '%s' is not listed in project requirements", packageName), + ProblemHighlightType.WEAK_WARNING, + null, + quickFixes.toArray(new LocalQuickFix[quickFixes.size()]) + ); + } + } + } + } + } + } + + @Nullable + private static Set getTransitiveRequirements( + @Nonnull Sdk sdk, + @Nonnull Collection requirements, + @Nonnull Set visited + ) { + if (requirements.isEmpty()) { + return Collections.emptySet(); + } + final Set results = new HashSet<>(requirements); + final List packages = PyPackageManager.getInstance(sdk).getPackages(); + if (packages == null) { + return null; + } + for (PyRequirement req : requirements) { + final PyPackage pkg = req.match(packages); + if (pkg != null && !visited.contains(pkg)) { + visited.add(pkg); + final Set transitive = getTransitiveRequirements(sdk, pkg.getRequirements(), visited); + if (transitive == null) { + return null; + } + results.addAll(transitive); + } + } + return results; + } + + @Nullable + private static List findUnsatisfiedRequirements( + @Nonnull Module module, + @Nonnull Sdk sdk, + @Nonnull Set ignoredPackages + ) { + final PyPackageManager manager = PyPackageManager.getInstance(sdk); + List requirements = manager.getRequirements(module); + if (requirements != null) { + final List packages = manager.getPackages(); + if (packages == null) { + return null; + } + final List unsatisfied = new ArrayList<>(); + for (PyRequirement req : requirements) { + if (!ignoredPackages.contains(req.getName()) && req.match(packages) == null) { + unsatisfied.add(req); + } + } + return unsatisfied; + } + return null; + } + + private static void setRunningPackagingTasks(@Nonnull Module module, boolean value) { + module.putUserData(PyPackageManager.RUNNING_PACKAGING_TASKS, value); + } + + private static boolean isRunningPackagingTasks(@Nonnull Module module) { + final Boolean value = module.getUserData(PyPackageManager.RUNNING_PACKAGING_TASKS); + return value != null && value; + } + + public static class PyInstallRequirementsFix implements LocalQuickFix { + @Nonnull + private final LocalizeValue myName; + @Nonnull + private final Module myModule; + @Nonnull + private final Sdk mySdk; + @Nonnull + private final List myUnsatisfied; + + public PyInstallRequirementsFix( + @Nonnull LocalizeValue name, + @Nonnull Module module, + @Nonnull Sdk sdk, + @Nonnull List unsatisfied + ) { + myName = name; + myModule = module; + mySdk = sdk; + myUnsatisfied = unsatisfied; + } + + @Nonnull + @Override + public LocalizeValue getName() { + return myName; + } + + @Override + public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) { + boolean installManagement = false; + final PyPackageManager manager = PyPackageManager.getInstance(mySdk); + final List packages = manager.getPackages(); + if (packages == null) { + return; + } + if (!PyPackageUtil.hasManagement(packages)) { + final int result = Messages.showYesNoDialog( + project, + "Python packaging tools are required for installing packages. Do you want to " + "install 'pip' and 'setuptools' for your " + + "interpreter?", + "Install Python Packaging Tools", + Messages.getQuestionIcon() + ); + if (result == Messages.YES) { + installManagement = true; + } + else { + return; + } + } + final List chosen; + if (myUnsatisfied.size() > 1) { + final PyChooseRequirementsDialog dialog = new PyChooseRequirementsDialog(project, myUnsatisfied); + if (dialog.showAndGet()) { + chosen = dialog.getMarkedElements(); + } + else { + chosen = Collections.emptyList(); + } + } + else { + chosen = myUnsatisfied; + } + if (chosen.isEmpty()) { + return; + } + if (installManagement) { + final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) { + @Override + public void finished(List exceptions) { + super.finished(exceptions); + if (exceptions.isEmpty()) { + installRequirements(project, chosen); + } + } + }); + ui.installManagement(); + } + else { + installRequirements(project, chosen); + } + } + + private void installRequirements(Project project, List requirements) { + final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule)); + ui.install(requirements, Collections.emptyList()); + } + } + + public static class InstallAndImportQuickFix implements LocalQuickFix { + private final Sdk mySdk; + private final Module myModule; + private String myPackageName; + @Nullable + private final String myAsName; + @Nonnull + private final SmartPsiElementPointer myNode; + + public InstallAndImportQuickFix(@Nonnull final String packageName, @Nullable final String asName, @Nonnull final PyElement node) { + myPackageName = packageName; + myAsName = asName; + myNode = SmartPointerManager.getInstance(node.getProject()).createSmartPsiElementPointer(node, node.getContainingFile()); + myModule = node.getModule(); + mySdk = PythonSdkType.findPythonSdk(myModule); + } + + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Install and import package " + myPackageName); + } + + public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) { + @Override + public void finished(List exceptions) { + super.finished(exceptions); + if (exceptions.isEmpty()) { + + final PyElement element = myNode.getElement(); + if (element == null) { + return; + } + + CommandProcessor.getInstance() + .executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { + AddImportHelper.addImportStatement( + element.getContainingFile(), + myPackageName, + myAsName, + AddImportHelper.ImportPriority.THIRD_PARTY, + element + ); + }), "Add import", "Add import"); + } + } + }); + ui.install(Collections.singletonList(new PyRequirement(myPackageName)), Collections.emptyList()); + } + } + + private static class UIListener implements PyPackageManagerUI.Listener { + private final Module myModule; + + public UIListener(Module module) { + myModule = module; + } + + @Override + public void started() { + setRunningPackagingTasks(myModule, true); + } + + @Override + public void finished(List exceptions) { + setRunningPackagingTasks(myModule, false); + } + } + + + private static class IgnoreRequirementFix implements LocalQuickFix { + @Nonnull + private final Set myPackageNames; + + public IgnoreRequirementFix(@Nonnull Set packageNames) { + myPackageNames = packageNames; + } + + @Nonnull + @Override + public LocalizeValue getName() { + final boolean plural = myPackageNames.size() > 1; + return LocalizeValue.localizeTODO(String.format("Ignore requirement%s", plural ? "s" : "")); + } + + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (element != null) { + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + + profile.modifyToolSettings( + PyPackageRequirementsInspection.class.getSimpleName(), + element, + (inspectionTool, state) -> { + for (String name : myPackageNames) { + if (!state.ignoredPackages.contains(name)) { + state.ignoredPackages.add(name); + } + } + } + ); + } + } + } + + private static class AddToRequirementsFix implements LocalQuickFix { + @Nonnull + private final Module myModule; + @Nonnull + private final String myPackageName; + @Nonnull + private final LanguageLevel myLanguageLevel; + + private AddToRequirementsFix(@Nonnull Module module, @Nonnull String packageName, @Nonnull LanguageLevel languageLevel) { + myModule = module; + myPackageName = packageName; + myLanguageLevel = languageLevel; + } + + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO(String.format("Add requirement '%s' to %s", myPackageName, calculateTarget())); + } + + @Override + @RequiredUIAccess + public void applyFix(@Nonnull final Project project, @Nonnull ProblemDescriptor descriptor) { + CommandProcessor.getInstance().newCommand() + .project(project) + .name(getName()) + .inWriteAction() + .run(() -> PyPackageUtil.addRequirementToTxtOrSetupPy(myModule, myPackageName, myLanguageLevel)); + } + + @Nonnull + private String calculateTarget() { + final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(myModule); + if (requirementsTxt != null) { + return requirementsTxt.getName(); + } + else if (PyPackageUtil.findSetupCall(myModule) != null) { + return "setup.py"; + } + else { + return "project requirements"; + } + } + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPep8NamingInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPep8NamingInspection.java index e1fc9c0d..c549ae17 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPep8NamingInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyPep8NamingInspection.java @@ -22,8 +22,8 @@ import com.jetbrains.python.impl.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.impl.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.impl.psi.PyUtil; -import com.jetbrains.python.psi.*; import com.jetbrains.python.impl.psi.search.PySuperMethodsSearch; +import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyClassLikeType; import com.jetbrains.python.psi.types.TypeEvalContext; import consulo.annotation.component.ExtensionImpl; @@ -38,14 +38,14 @@ import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.psi.util.QualifiedName; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.ui.ex.awt.JBList; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Pair; -import org.jetbrains.annotations.Nls; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -55,361 +55,304 @@ import static consulo.ide.impl.idea.util.containers.ContainerUtilRt.addIfNotNull; /** - * User : ktisha + * @author ktisha */ @ExtensionImpl -public class PyPep8NamingInspection extends PyInspection -{ - private static final Pattern LOWERCASE_REGEX = Pattern.compile("[_\\p{javaLowerCase}][_\\p{javaLowerCase}0-9]*"); - private static final Pattern UPPERCASE_REGEX = Pattern.compile("[_\\p{javaUpperCase}][_\\p{javaUpperCase}0-9]*"); - private static final Pattern MIXEDCASE_REGEX = Pattern.compile("_?_?[\\p{javaUpperCase}][\\p{javaLowerCase}\\p{javaUpperCase}0-9]*"); - private static final String INSPECTION_SHORT_NAME = "PyPep8NamingInspection"; - // See error codes of the tool "pep8-naming" - private static final Map ERROR_CODES_DESCRIPTION = - ImmutableMap.builder().put("N801", "Class names should use CamelCase convention") - .put("N802", "Function name " + "should be lowercase") - .put("N803", "Argument name should be lowercase") - .put("N806", "Variable in function should be lowercase") - .put("N811", "Constant variable imported as non constant") - .put("N812", "Lowercase variable imported as non lowercase") - .put("N813", "CamelCase variable imported as lowercase") - .put("N814", "CamelCase variable imported as constant") - .build(); - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, - boolean isOnTheFly, - @Nonnull LocalInspectionToolSession session, - Object state) - { - return new Visitor(holder, session, (PyPep8NamingInspectionState) state); - } - - @Nonnull - @Override - public String getDisplayName() - { - return "PEP 8 naming convention violation"; - } - - @Nonnull - @Override - public InspectionToolState createStateProvider() - { - return new PyPep8NamingInspectionState(); - } - - public class Visitor extends PyInspectionVisitor - { - private final PyPep8NamingInspectionState myState; - - public Visitor(ProblemsHolder holder, LocalInspectionToolSession session, PyPep8NamingInspectionState state) - { - super(holder, session); - myState = state; - } - - @Override - public void visitPyAssignmentStatement(PyAssignmentStatement node) - { - final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, true, PyClass.class); - if(function == null) - { - return; - } - final Scope scope = ControlFlowCache.getScope(function); - for(Pair pair : node.getTargetsToValuesMapping()) - { - final String name = pair.getFirst().getName(); - if(name == null || scope.isGlobal(name)) - { - continue; - } - if(pair.getFirst() instanceof PyTargetExpression) - { - final PyExpression qualifier = ((PyTargetExpression) pair.getFirst()).getQualifier(); - if(qualifier != null) - { - return; - } - } - - final PyCallExpression assignedValue = PyUtil.as(pair.getSecond(), PyCallExpression.class); - if(assignedValue != null && assignedValue.getCallee() != null && PyNames.NAMEDTUPLE.equals(assignedValue.getCallee().getName())) - { - return; - } - final String errorCode = "N806"; - if(!LOWERCASE_REGEX.matcher(name).matches() && !name.startsWith("_") && !myState.ignoredErrors.contains(errorCode)) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(pair.getFirst(), errorCode); - } - } - } - - @Override - public void visitPyParameter(PyParameter node) - { - final String name = node.getName(); - if(name == null) - { - return; - } - - final String errorCode = "N803"; - if(!LOWERCASE_REGEX.matcher(name).matches() && !myState.ignoredErrors.contains(errorCode)) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(node, errorCode); - } - } - - private void registerAndAddRenameAndIgnoreErrorQuickFixes(@Nullable final PsiElement node, @Nonnull final String errorCode) - { - if(getHolder() != null && getHolder().isOnTheFly()) - { - registerProblem(node, ERROR_CODES_DESCRIPTION.get(errorCode), new PyRenameElementQuickFix(), new IgnoreErrorFix(errorCode)); - } - else - { - registerProblem(node, ERROR_CODES_DESCRIPTION.get(errorCode), new IgnoreErrorFix(errorCode)); - } - } - - @Override - public void visitPyFunction(PyFunction function) - { - final PyClass containingClass = function.getContainingClass(); - if(myState.ignoreOverriddenFunctions && isOverriddenMethod(function)) - { - return; - } - final String name = function.getName(); - if(name == null) - { - return; - } - if(containingClass != null && (PyUtil.isSpecialName(name) || isIgnoredOrHasIgnoredAncestor(containingClass))) - { - return; - } - if(!LOWERCASE_REGEX.matcher(name).matches()) - { - final ASTNode nameNode = function.getNameNode(); - if(nameNode != null) - { - final List quickFixes = Lists.newArrayList(); - if(getHolder() != null && getHolder().isOnTheFly()) - { - quickFixes.add(new PyRenameElementQuickFix()); - } - - if(containingClass != null) - { - quickFixes.add(new IgnoreBaseClassQuickFix(containingClass, myTypeEvalContext)); - } - final String errorCode = "N802"; - if(!myState.ignoredErrors.contains(errorCode)) - { - quickFixes.add(new IgnoreErrorFix(errorCode)); - registerProblem(nameNode.getPsi(), - ERROR_CODES_DESCRIPTION.get(errorCode), - quickFixes.toArray(new LocalQuickFix[quickFixes.size()])); - } - } - } - } - - private boolean isOverriddenMethod(@Nonnull PyFunction function) - { - return PySuperMethodsSearch.search(function, myTypeEvalContext).findFirst() != null; - } - - private boolean isIgnoredOrHasIgnoredAncestor(@Nonnull PyClass pyClass) - { - final Set blackList = Sets.newHashSet(myState.ignoredBaseClasses); - if(blackList.contains(pyClass.getQualifiedName())) - { - return true; - } - for(PyClassLikeType ancestor : pyClass.getAncestorTypes(myTypeEvalContext)) - { - if(ancestor != null && blackList.contains(ancestor.getClassQName())) - { - return true; - } - } - return false; - } - - @Override - public void visitPyClass(PyClass node) - { - final String name = node.getName(); - if(name == null) - { - return; - } - final String errorCode = "N801"; - if(!myState.ignoredErrors.contains(errorCode)) - { - final boolean isLowercaseContextManagerClass = isContextManager(node) && LOWERCASE_REGEX.matcher(name).matches(); - if(!isLowercaseContextManagerClass && !MIXEDCASE_REGEX.matcher(name).matches()) - { - final ASTNode nameNode = node.getNameNode(); - if(nameNode != null) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(nameNode.getPsi(), errorCode); - } - } - } - } - - private boolean isContextManager(PyClass node) - { - final String[] contextManagerFunctionNames = { - PyNames.ENTER, - PyNames.EXIT - }; - for(String name : contextManagerFunctionNames) - { - if(node.findMethodByName(name, false, myTypeEvalContext) == null) - { - return false; - } - } - return true; - } - - @Override - public void visitPyImportElement(PyImportElement node) - { - final String asName = node.getAsName(); - final QualifiedName importedQName = node.getImportedQName(); - if(importedQName == null) - { - return; - } - final String name = importedQName.getLastComponent(); - - if(asName == null || name == null) - { - return; - } - if(UPPERCASE_REGEX.matcher(name).matches()) - { - final String errorCode = "N811"; - if(!UPPERCASE_REGEX.matcher(asName).matches() && !myState.ignoredErrors.contains(errorCode)) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); - } - } - else if(LOWERCASE_REGEX.matcher(name).matches()) - { - final String errorCode = "N812"; - if(!LOWERCASE_REGEX.matcher(asName).matches() && !myState.ignoredErrors.contains(errorCode)) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); - } - } - else if(LOWERCASE_REGEX.matcher(asName).matches()) - { - final String errorCode = "N813"; - if(!myState.ignoredErrors.contains(errorCode)) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); - } - } - else if(UPPERCASE_REGEX.matcher(asName).matches()) - { - final String errorCode = "N814"; - if(!myState.ignoredErrors.contains(errorCode)) - { - registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); - } - } - } - } - - private static class IgnoreBaseClassQuickFix implements LocalQuickFix - { - private final List myBaseClassNames; - - public IgnoreBaseClassQuickFix(@Nonnull PyClass baseClass, @Nonnull TypeEvalContext context) - { - myBaseClassNames = new ArrayList<>(); - ContainerUtil.addIfNotNull(getBaseClassNames(), baseClass.getQualifiedName()); - for(PyClass ancestor : baseClass.getAncestorClasses(context)) - { - ContainerUtil.addIfNotNull(getBaseClassNames(), ancestor.getQualifiedName()); - } - } - - @Nonnull - @Override - public String getFamilyName() - { - return "Ignore method names for descendants of class"; - } - - @Override - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) - { - final JBList list = new JBList(getBaseClassNames()); - final Runnable updateBlackList = () -> - { - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - profile.modifyToolSettings(PyPep8NamingInspection.class.getSimpleName(), descriptor.getPsiElement(), (i, s) -> - { - addIfNotNull(s.ignoredBaseClasses, (String) list.getSelectedValue()); - }); - }; - - DataManager.getInstance() - .getDataContextFromFocus() - .doWhenDone(dataContext -> new PopupChooserBuilder(list).setTitle("Ignore base class") - .setItemChoosenCallback(updateBlackList) - .setFilteringEnabled(o -> (String) o) - .createPopup() - .showInBestPositionFor(dataContext)); - } - - public List getBaseClassNames() - { - return myBaseClassNames; - } - } - - private static class IgnoreErrorFix implements LocalQuickFix - { - private final String myCode; - private static final String myText = "Ignore errors like this"; - - public IgnoreErrorFix(String code) - { - myCode = code; - } - - @Nls - @Nonnull - @Override - public String getFamilyName() - { - return myText; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - final PsiFile file = descriptor.getStartElement().getContainingFile(); - InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - profile.modifyToolSettings(INSPECTION_SHORT_NAME, file, (i, s) -> - { - if(!s.ignoredErrors.contains(myCode)) - { - s.ignoredErrors.add(myCode); - } - }); - } - } +public class PyPep8NamingInspection extends PyInspection { + private static final Pattern LOWERCASE_REGEX = Pattern.compile("[_\\p{javaLowerCase}][_\\p{javaLowerCase}0-9]*"); + private static final Pattern UPPERCASE_REGEX = Pattern.compile("[_\\p{javaUpperCase}][_\\p{javaUpperCase}0-9]*"); + private static final Pattern MIXEDCASE_REGEX = Pattern.compile("_?_?[\\p{javaUpperCase}][\\p{javaLowerCase}\\p{javaUpperCase}0-9]*"); + private static final String INSPECTION_SHORT_NAME = "PyPep8NamingInspection"; + // See error codes of the tool "pep8-naming" + private static final Map ERROR_CODES_DESCRIPTION = + ImmutableMap.builder().put("N801", "Class names should use CamelCase convention") + .put("N802", "Function name " + "should be lowercase") + .put("N803", "Argument name should be lowercase") + .put("N806", "Variable in function should be lowercase") + .put("N811", "Constant variable imported as non constant") + .put("N812", "Lowercase variable imported as non lowercase") + .put("N813", "CamelCase variable imported as lowercase") + .put("N814", "CamelCase variable imported as constant") + .build(); + + @Nonnull + @Override + public PsiElementVisitor buildVisitor( + @Nonnull ProblemsHolder holder, + boolean isOnTheFly, + @Nonnull LocalInspectionToolSession session, + Object state + ) { + return new Visitor(holder, session, (PyPep8NamingInspectionState) state); + } + + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("PEP 8 naming convention violation"); + } + + @Nonnull + @Override + public InspectionToolState createStateProvider() { + return new PyPep8NamingInspectionState(); + } + + public class Visitor extends PyInspectionVisitor { + private final PyPep8NamingInspectionState myState; + + public Visitor(ProblemsHolder holder, LocalInspectionToolSession session, PyPep8NamingInspectionState state) { + super(holder, session); + myState = state; + } + + @Override + public void visitPyAssignmentStatement(PyAssignmentStatement node) { + final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, true, PyClass.class); + if (function == null) { + return; + } + final Scope scope = ControlFlowCache.getScope(function); + for (Pair pair : node.getTargetsToValuesMapping()) { + final String name = pair.getFirst().getName(); + if (name == null || scope.isGlobal(name)) { + continue; + } + if (pair.getFirst() instanceof PyTargetExpression) { + final PyExpression qualifier = ((PyTargetExpression) pair.getFirst()).getQualifier(); + if (qualifier != null) { + return; + } + } + + final PyCallExpression assignedValue = PyUtil.as(pair.getSecond(), PyCallExpression.class); + if (assignedValue != null && assignedValue.getCallee() != null && PyNames.NAMEDTUPLE.equals(assignedValue.getCallee().getName())) { + return; + } + final String errorCode = "N806"; + if (!LOWERCASE_REGEX.matcher(name).matches() && !name.startsWith("_") && !myState.ignoredErrors.contains(errorCode)) { + registerAndAddRenameAndIgnoreErrorQuickFixes(pair.getFirst(), errorCode); + } + } + } + + @Override + public void visitPyParameter(PyParameter node) { + final String name = node.getName(); + if (name == null) { + return; + } + + final String errorCode = "N803"; + if (!LOWERCASE_REGEX.matcher(name).matches() && !myState.ignoredErrors.contains(errorCode)) { + registerAndAddRenameAndIgnoreErrorQuickFixes(node, errorCode); + } + } + + private void registerAndAddRenameAndIgnoreErrorQuickFixes(@Nullable final PsiElement node, @Nonnull final String errorCode) { + if (getHolder() != null && getHolder().isOnTheFly()) { + registerProblem(node, ERROR_CODES_DESCRIPTION.get(errorCode), new PyRenameElementQuickFix(), new IgnoreErrorFix(errorCode)); + } + else { + registerProblem(node, ERROR_CODES_DESCRIPTION.get(errorCode), new IgnoreErrorFix(errorCode)); + } + } + + @Override + public void visitPyFunction(PyFunction function) { + final PyClass containingClass = function.getContainingClass(); + if (myState.ignoreOverriddenFunctions && isOverriddenMethod(function)) { + return; + } + final String name = function.getName(); + if (name == null) { + return; + } + if (containingClass != null && (PyUtil.isSpecialName(name) || isIgnoredOrHasIgnoredAncestor(containingClass))) { + return; + } + if (!LOWERCASE_REGEX.matcher(name).matches()) { + final ASTNode nameNode = function.getNameNode(); + if (nameNode != null) { + final List quickFixes = Lists.newArrayList(); + if (getHolder() != null && getHolder().isOnTheFly()) { + quickFixes.add(new PyRenameElementQuickFix()); + } + + if (containingClass != null) { + quickFixes.add(new IgnoreBaseClassQuickFix(containingClass, myTypeEvalContext)); + } + final String errorCode = "N802"; + if (!myState.ignoredErrors.contains(errorCode)) { + quickFixes.add(new IgnoreErrorFix(errorCode)); + registerProblem( + nameNode.getPsi(), + ERROR_CODES_DESCRIPTION.get(errorCode), + quickFixes.toArray(new LocalQuickFix[quickFixes.size()]) + ); + } + } + } + } + + private boolean isOverriddenMethod(@Nonnull PyFunction function) { + return PySuperMethodsSearch.search(function, myTypeEvalContext).findFirst() != null; + } + + private boolean isIgnoredOrHasIgnoredAncestor(@Nonnull PyClass pyClass) { + final Set blackList = Sets.newHashSet(myState.ignoredBaseClasses); + if (blackList.contains(pyClass.getQualifiedName())) { + return true; + } + for (PyClassLikeType ancestor : pyClass.getAncestorTypes(myTypeEvalContext)) { + if (ancestor != null && blackList.contains(ancestor.getClassQName())) { + return true; + } + } + return false; + } + + @Override + public void visitPyClass(PyClass node) { + final String name = node.getName(); + if (name == null) { + return; + } + final String errorCode = "N801"; + if (!myState.ignoredErrors.contains(errorCode)) { + final boolean isLowercaseContextManagerClass = isContextManager(node) && LOWERCASE_REGEX.matcher(name).matches(); + if (!isLowercaseContextManagerClass && !MIXEDCASE_REGEX.matcher(name).matches()) { + final ASTNode nameNode = node.getNameNode(); + if (nameNode != null) { + registerAndAddRenameAndIgnoreErrorQuickFixes(nameNode.getPsi(), errorCode); + } + } + } + } + + private boolean isContextManager(PyClass node) { + final String[] contextManagerFunctionNames = { + PyNames.ENTER, + PyNames.EXIT + }; + for (String name : contextManagerFunctionNames) { + if (node.findMethodByName(name, false, myTypeEvalContext) == null) { + return false; + } + } + return true; + } + + @Override + public void visitPyImportElement(PyImportElement node) { + final String asName = node.getAsName(); + final QualifiedName importedQName = node.getImportedQName(); + if (importedQName == null) { + return; + } + final String name = importedQName.getLastComponent(); + + if (asName == null || name == null) { + return; + } + if (UPPERCASE_REGEX.matcher(name).matches()) { + final String errorCode = "N811"; + if (!UPPERCASE_REGEX.matcher(asName).matches() && !myState.ignoredErrors.contains(errorCode)) { + registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); + } + } + else if (LOWERCASE_REGEX.matcher(name).matches()) { + final String errorCode = "N812"; + if (!LOWERCASE_REGEX.matcher(asName).matches() && !myState.ignoredErrors.contains(errorCode)) { + registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); + } + } + else if (LOWERCASE_REGEX.matcher(asName).matches()) { + final String errorCode = "N813"; + if (!myState.ignoredErrors.contains(errorCode)) { + registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); + } + } + else if (UPPERCASE_REGEX.matcher(asName).matches()) { + final String errorCode = "N814"; + if (!myState.ignoredErrors.contains(errorCode)) { + registerAndAddRenameAndIgnoreErrorQuickFixes(node.getAsNameElement(), errorCode); + } + } + } + } + + private static class IgnoreBaseClassQuickFix implements LocalQuickFix { + private final List myBaseClassNames; + + public IgnoreBaseClassQuickFix(@Nonnull PyClass baseClass, @Nonnull TypeEvalContext context) { + myBaseClassNames = new ArrayList<>(); + ContainerUtil.addIfNotNull(getBaseClassNames(), baseClass.getQualifiedName()); + for (PyClass ancestor : baseClass.getAncestorClasses(context)) { + ContainerUtil.addIfNotNull(getBaseClassNames(), ancestor.getQualifiedName()); + } + } + + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Ignore method names for descendants of class"); + } + + @Override + public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + final JBList list = new JBList(getBaseClassNames()); + final Runnable updateBlackList = () -> + { + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + profile.modifyToolSettings( + PyPep8NamingInspection.class.getSimpleName(), + descriptor.getPsiElement(), + (i, s) -> + { + addIfNotNull(s.ignoredBaseClasses, (String) list.getSelectedValue()); + } + ); + }; + + DataManager.getInstance() + .getDataContextFromFocus() + .doWhenDone(dataContext -> new PopupChooserBuilder(list).setTitle("Ignore base class") + .setItemChoosenCallback(updateBlackList) + .setFilteringEnabled(o -> (String) o) + .createPopup() + .showInBestPositionFor(dataContext)); + } + + public List getBaseClassNames() { + return myBaseClassNames; + } + } + + private static class IgnoreErrorFix implements LocalQuickFix { + private final String myCode; + private static final LocalizeValue myText = LocalizeValue.localizeTODO("Ignore errors like this"); + + public IgnoreErrorFix(String code) { + myCode = code; + } + + @Nonnull + @Override + public LocalizeValue getName() { + return myText; + } + + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiFile file = descriptor.getStartElement().getContainingFile(); + InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + profile.modifyToolSettings(INSPECTION_SHORT_NAME, file, (i, s) -> + { + if (!s.ignoredErrors.contains(myCode)) { + s.ignoredErrors.add(myCode); + } + }); + } + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyRenameElementQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyRenameElementQuickFix.java index d358abe5..94ad2b57 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyRenameElementQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyRenameElementQuickFix.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections; import com.jetbrains.python.impl.codeInsight.dataflow.scope.ScopeUtil; @@ -34,72 +33,67 @@ import consulo.language.psi.scope.LocalSearchScope; import consulo.language.psi.search.PsiSearchHelper; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.navigation.OpenFileDescriptorFactory; import consulo.project.Project; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; /** - * User: ktisha + * @author ktisha */ public class PyRenameElementQuickFix implements LocalQuickFix { - @Nonnull - @Override - public String getName() { - return "Rename element"; - } - - @Nonnull - @Override - public String getFamilyName() { - return "Rename element"; - } + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Rename element"); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? - (PsiNameIdentifierOwner)element : - PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); - if (nameOwner != null) { - final VirtualFile virtualFile = nameOwner.getContainingFile().getVirtualFile(); - if (virtualFile != null) { - final Editor editor = FileEditorManager.getInstance(project) - .openTextEditor(OpenFileDescriptorFactory.getInstance(project).builder(virtualFile).build(), - true); - if (ApplicationManager.getApplication().isUnitTestMode()) { - renameInUnitTestMode(project, nameOwner, editor); - } - else { - if (checkLocalScope(element) != null && (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression)) { - new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); - } - else { - PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? + (PsiNameIdentifierOwner) element : + PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); + if (nameOwner != null) { + final VirtualFile virtualFile = nameOwner.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + final Editor editor = FileEditorManager.getInstance(project) + .openTextEditor(OpenFileDescriptorFactory.getInstance(project).builder(virtualFile).build(), true); + if (ApplicationManager.getApplication().isUnitTestMode()) { + renameInUnitTestMode(project, nameOwner, editor); + } + else { + if (checkLocalScope(element) != null && (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression)) { + new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); + } + else { + PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); + } + } + } } - } } - } - @Nullable - protected PsiElement checkLocalScope(PsiElement element) { - final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element); - if (searchScope instanceof LocalSearchScope) { - final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope(); - return PsiTreeUtil.findCommonParent(elements); - } + @Nullable + protected PsiElement checkLocalScope(PsiElement element) { + final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element); + if (searchScope instanceof LocalSearchScope) { + final PsiElement[] elements = ((LocalSearchScope) searchScope).getScope(); + return PsiTreeUtil.findCommonParent(elements); + } - return null; - } + return null; + } - private static void renameInUnitTestMode(@Nonnull Project project, @Nonnull PsiNameIdentifierOwner nameOwner, - @Nullable Editor editor) { - final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); - if (substitution != null) { - new RenameProcessor(project, substitution, "a", false, false).run(); + private static void renameInUnitTestMode( + @Nonnull Project project, @Nonnull PsiNameIdentifierOwner nameOwner, + @Nullable Editor editor + ) { + final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); + if (substitution != null) { + new RenameProcessor(project, substitution, "a", false, false).run(); + } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyShadowingBuiltinsInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyShadowingBuiltinsInspection.java index dcf9f73c..f92f4af3 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyShadowingBuiltinsInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyShadowingBuiltinsInspection.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections; import com.google.common.collect.ImmutableSet; @@ -30,10 +29,12 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.PsiNameIdentifierOwner; +import consulo.localize.LocalizeValue; import consulo.project.Project; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.Collection; import java.util.Set; @@ -43,133 +44,115 @@ * @author vlan */ @ExtensionImpl -public class PyShadowingBuiltinsInspection extends PyInspection -{ - @Nonnull - @Override - public InspectionToolState createStateProvider() - { - return new PyShadowingBuiltinsInspectionState(); - } - - @Nonnull - @Override - public String getDisplayName() - { - return "Shadowing built-ins"; - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, - boolean isOnTheFly, - @Nonnull LocalInspectionToolSession session, - Object state) - { - PyShadowingBuiltinsInspectionState inspectionState = (PyShadowingBuiltinsInspectionState) state; - return new Visitor(holder, session, inspectionState.ignoredNames); - } - - private static class Visitor extends PyInspectionVisitor - { - private final Set myIgnoredNames; - - public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session, @Nonnull Collection ignoredNames) - { - super(holder, session); - myIgnoredNames = ImmutableSet.copyOf(ignoredNames); - } - - @Override - public void visitPyClass(@Nonnull PyClass node) - { - processElement(node); - } - - @Override - public void visitPyFunction(@Nonnull PyFunction node) - { - processElement(node); - } - - @Override - public void visitPyNamedParameter(@Nonnull PyNamedParameter node) - { - processElement(node); - } - - @Override - public void visitPyTargetExpression(@Nonnull PyTargetExpression node) - { - if(node.getQualifier() == null) - { - processElement(node); - } - } - - private void processElement(@Nonnull PsiNameIdentifierOwner element) - { - final ScopeOwner owner = ScopeUtil.getScopeOwner(element); - if(owner instanceof PyClass) - { - return; - } - final String name = element.getName(); - if(name != null && !myIgnoredNames.contains(name)) - { - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(element); - final PsiElement builtin = builtinCache.getByName(name); - if(builtin != null && !PyUtil.inSameFile(builtin, element)) - { - final PsiElement identifier = element.getNameIdentifier(); - final PsiElement problemElement = identifier != null ? identifier : element; - registerProblem(problemElement, String.format("Shadows built-in name '%s'", name), - ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix(), new PyIgnoreBuiltinQuickFix(name)); - } - } - } - - private static class PyIgnoreBuiltinQuickFix implements LocalQuickFix, LowPriorityAction - { - @Nonnull - private final String myName; - - private PyIgnoreBuiltinQuickFix(@Nonnull String name) - { - myName = name; - } - - @Nonnull - @Override - public String getName() - { - return getFamilyName() + " \"" + myName + "\""; - } - - @Nonnull - @Override - public String getFamilyName() - { - return "Ignore shadowed built-in name"; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - final PsiElement element = descriptor.getPsiElement(); - if(element != null) - { - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - profile.modifyToolSettings(PyShadowingBuiltinsInspection.class.getSimpleName(), element, (i, s) -> - { - if(!s.ignoredNames.contains(myName)) - { - s.ignoredNames.add(myName); - } - }); - } - } - } - } +public class PyShadowingBuiltinsInspection extends PyInspection { + @Nonnull + @Override + public InspectionToolState createStateProvider() { + return new PyShadowingBuiltinsInspectionState(); + } + + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Shadowing built-ins"); + } + + @Nonnull + @Override + public PsiElementVisitor buildVisitor( + @Nonnull ProblemsHolder holder, + boolean isOnTheFly, + @Nonnull LocalInspectionToolSession session, + Object state + ) { + PyShadowingBuiltinsInspectionState inspectionState = (PyShadowingBuiltinsInspectionState) state; + return new Visitor(holder, session, inspectionState.ignoredNames); + } + + private static class Visitor extends PyInspectionVisitor { + private final Set myIgnoredNames; + + public Visitor( + @Nullable ProblemsHolder holder, + @Nonnull LocalInspectionToolSession session, + @Nonnull Collection ignoredNames + ) { + super(holder, session); + myIgnoredNames = ImmutableSet.copyOf(ignoredNames); + } + + @Override + public void visitPyClass(@Nonnull PyClass node) { + processElement(node); + } + + @Override + public void visitPyFunction(@Nonnull PyFunction node) { + processElement(node); + } + + @Override + public void visitPyNamedParameter(@Nonnull PyNamedParameter node) { + processElement(node); + } + + @Override + public void visitPyTargetExpression(@Nonnull PyTargetExpression node) { + if (node.getQualifier() == null) { + processElement(node); + } + } + + private void processElement(@Nonnull PsiNameIdentifierOwner element) { + final ScopeOwner owner = ScopeUtil.getScopeOwner(element); + if (owner instanceof PyClass) { + return; + } + final String name = element.getName(); + if (name != null && !myIgnoredNames.contains(name)) { + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(element); + final PsiElement builtin = builtinCache.getByName(name); + if (builtin != null && !PyUtil.inSameFile(builtin, element)) { + final PsiElement identifier = element.getNameIdentifier(); + final PsiElement problemElement = identifier != null ? identifier : element; + registerProblem(problemElement, String.format("Shadows built-in name '%s'", name), + ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix(), new PyIgnoreBuiltinQuickFix(name) + ); + } + } + } + + private static class PyIgnoreBuiltinQuickFix implements LocalQuickFix, LowPriorityAction { + @Nonnull + private final String myName; + + private PyIgnoreBuiltinQuickFix(@Nonnull String name) { + myName = name; + } + + @Nonnull + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Ignore shadowed built-in name \"" + myName + "\""); + } + + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (element != null) { + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + profile.modifyToolSettings( + PyShadowingBuiltinsInspection.class.getSimpleName(), + element, + (i, s) -> { + if (!s.ignoredNames.contains(myName)) { + s.ignoredNames.add(myName); + } + } + ); + } + } + } + } } 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 a9854d54..f2068491 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 @@ -15,7 +15,6 @@ */ package com.jetbrains.python.impl.inspections.quickfix; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.impl.psi.PyUtil; @@ -25,13 +24,16 @@ import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Couple; import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; /** @@ -40,365 +42,371 @@ * def __init__(self): * A.__init__(self) # inserted * print "Constructor B was called" - *

    - * User: catherine + * + * @author catherine */ public class AddCallSuperQuickFix implements LocalQuickFix { + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixAddSuper(); + } - @Nonnull - public String getFamilyName() { - return PyBundle.message("QFIX.add.super"); - } + public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + final PyFunction problemFunction = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyFunction.class); + if (problemFunction == null) { + return; + } + final StringBuilder superCall = new StringBuilder(); + final PyClass klass = problemFunction.getContainingClass(); + if (klass == null) { + return; + } + final PyClass[] superClasses = klass.getSuperClasses(null); + if (superClasses.length == 0) { + return; + } - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { - final PyFunction problemFunction = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyFunction.class); - if (problemFunction == null) { - return; - } - final StringBuilder superCall = new StringBuilder(); - final PyClass klass = problemFunction.getContainingClass(); - if (klass == null) { - return; - } - final PyClass[] superClasses = klass.getSuperClasses(null); - if (superClasses.length == 0) { - return; - } + final PyClass superClass = superClasses[0]; + final PyFunction superInit = superClass.findMethodByName(PyNames.INIT, true, null); + if (superInit == null) { + return; + } - final PyClass superClass = superClasses[0]; - final PyFunction superInit = superClass.findMethodByName(PyNames.INIT, true, null); - if (superInit == null) { - return; - } + final ParametersInfo origInfo = new ParametersInfo(problemFunction.getParameterList()); + final ParametersInfo superInfo = new ParametersInfo(superInit.getParameterList()); + final boolean addSelfToCall; + + if (klass.isNewStyleClass(null)) { + addSelfToCall = false; + if (LanguageLevel.forElement(klass).isPy3K()) { + superCall.append("super().__init__("); + } + else { + superCall.append("super(") + .append(klass.getName()) + .append(", ") + .append(getSelfParameterName(origInfo)) + .append(").__init__("); + } + } + else { + addSelfToCall = true; + superCall.append(superClass.getName()).append(".__init__("); + } - final ParametersInfo origInfo = new ParametersInfo(problemFunction.getParameterList()); - final ParametersInfo superInfo = new ParametersInfo(superInit.getParameterList()); - final boolean addSelfToCall; - - if (klass.isNewStyleClass(null)) { - addSelfToCall = false; - if (LanguageLevel.forElement(klass).isPy3K()) { - superCall.append("super().__init__("); - } - else { - superCall.append("super(").append(klass.getName()).append(", ").append(getSelfParameterName(origInfo)).append(").__init__("); - } - } - else { - addSelfToCall = true; - superCall.append(superClass.getName()).append(".__init__("); - } + final Couple> couple = buildNewFunctionParamsAndSuperInitCallArgs(origInfo, superInfo, addSelfToCall); + final StringBuilder newParameters = new StringBuilder("("); + consulo.ide.impl.idea.openapi.util.text.StringUtil.join(couple.getFirst(), ", ", newParameters); + newParameters.append(")"); - final Couple> couple = buildNewFunctionParamsAndSuperInitCallArgs(origInfo, superInfo, addSelfToCall); - final StringBuilder newParameters = new StringBuilder("("); - consulo.ide.impl.idea.openapi.util.text.StringUtil.join(couple.getFirst(), ", ", newParameters); - newParameters.append(")"); - - consulo.ide.impl.idea.openapi.util.text.StringUtil.join(couple.getSecond(), ", ", superCall); - superCall.append(")"); - - final PyElementGenerator generator = PyElementGenerator.getInstance(project); - final LanguageLevel languageLevel = LanguageLevel.forElement(problemFunction); - final PyStatement callSuperStatement = generator.createFromText(languageLevel, PyStatement.class, superCall.toString()); - final PyParameterList newParameterList = generator.createParameterList(languageLevel, newParameters.toString()); - problemFunction.getParameterList().replace(newParameterList); - final PyStatementList statementList = problemFunction.getStatementList(); - PyUtil.addElementToStatementList(callSuperStatement, statementList, true); - PyPsiUtils.removeRedundantPass(statementList); - } - - @Nonnull - private static String getSelfParameterName(@Nonnull ParametersInfo info) { - final PyParameter selfParameter = info.getSelfParameter(); - if (selfParameter == null) { - return PyNames.CANONICAL_SELF; - } - return StringUtil.defaultIfEmpty(selfParameter.getName(), PyNames.CANONICAL_SELF); - } - - @Nonnull - private static Couple> buildNewFunctionParamsAndSuperInitCallArgs(@Nonnull ParametersInfo origInfo, - @Nonnull ParametersInfo superInfo, - boolean addSelfToCall) { - final List newFunctionParams = new ArrayList<>(); - final List superCallArgs = new ArrayList<>(); - - final PyParameter selfParameter = origInfo.getSelfParameter(); - if (selfParameter != null && StringUtil.isNotEmpty(selfParameter.getName())) { - newFunctionParams.add(selfParameter.getText()); - } - else { - newFunctionParams.add(PyNames.CANONICAL_SELF); - } + consulo.ide.impl.idea.openapi.util.text.StringUtil.join(couple.getSecond(), ", ", superCall); + superCall.append(")"); - if (addSelfToCall) { - superCallArgs.add(getSelfParameterName(origInfo)); + final PyElementGenerator generator = PyElementGenerator.getInstance(project); + final LanguageLevel languageLevel = LanguageLevel.forElement(problemFunction); + final PyStatement callSuperStatement = generator.createFromText(languageLevel, PyStatement.class, superCall.toString()); + final PyParameterList newParameterList = generator.createParameterList(languageLevel, newParameters.toString()); + problemFunction.getParameterList().replace(newParameterList); + final PyStatementList statementList = problemFunction.getStatementList(); + PyUtil.addElementToStatementList(callSuperStatement, statementList, true); + PyPsiUtils.removeRedundantPass(statementList); } - // Required parameters (not-keyword) - for (PyParameter param : origInfo.getRequiredParameters()) { - newFunctionParams.add(param.getText()); + @Nonnull + private static String getSelfParameterName(@Nonnull ParametersInfo info) { + final PyParameter selfParameter = info.getSelfParameter(); + if (selfParameter == null) { + return PyNames.CANONICAL_SELF; + } + return StringUtil.defaultIfEmpty(selfParameter.getName(), PyNames.CANONICAL_SELF); } - for (PyParameter param : superInfo.getRequiredParameters()) { - // Special case as if base class has constructor __init__((a, b), c) and - // subclass has constructor __init__(a, (b, c)) - final PyTupleParameter tupleParam = param.getAsTuple(); - if (tupleParam != null) { - final List uniqueNames = collectParameterNames(tupleParam); - final boolean hasDuplicates = uniqueNames.removeAll(origInfo.getAllParameterNames()); - if (hasDuplicates) { - newFunctionParams.addAll(uniqueNames); + + @Nonnull + private static Couple> buildNewFunctionParamsAndSuperInitCallArgs( + @Nonnull ParametersInfo origInfo, + @Nonnull ParametersInfo superInfo, + boolean addSelfToCall + ) { + final List newFunctionParams = new ArrayList<>(); + final List superCallArgs = new ArrayList<>(); + + final PyParameter selfParameter = origInfo.getSelfParameter(); + if (selfParameter != null && StringUtil.isNotEmpty(selfParameter.getName())) { + newFunctionParams.add(selfParameter.getText()); } else { - newFunctionParams.add(param.getText()); - } - // Retain original structure of tuple parameter. - // Note that tuple parameters cannot have annotations or nested default values, so it's syntactically safe - superCallArgs.add(param.getText()); - } - else { - if (!origInfo.getAllParameterNames().contains(param.getName())) { - newFunctionParams.add(param.getText()); - } - superCallArgs.add(param.getName()); - } - } + newFunctionParams.add(PyNames.CANONICAL_SELF); + } - // Optional parameters (not-keyword) - for (PyParameter param : origInfo.getOptionalParameters()) { - newFunctionParams.add(param.getText()); - } + if (addSelfToCall) { + superCallArgs.add(getSelfParameterName(origInfo)); + } - // Pass parameters with default values to super class constructor, only if both functions contain them - for (PyParameter param : superInfo.getOptionalParameters()) { - final PyTupleParameter tupleParam = param.getAsTuple(); - if (tupleParam != null) { - if (origInfo.getAllParameterNames().containsAll(collectParameterNames(tupleParam))) { - final String paramText = tupleParam.getText(); - final PsiElement equalSign = PyPsiUtils.getFirstChildOfType(param, PyTokenTypes.EQ); - if (equalSign != null) { - superCallArgs.add(paramText.substring(0, equalSign.getStartOffsetInParent()).trim()); - } - } - } - else { - if (origInfo.getAllParameterNames().contains(param.getName())) { - superCallArgs.add(param.getName()); - } - } - } + // Required parameters (not-keyword) + for (PyParameter param : origInfo.getRequiredParameters()) { + newFunctionParams.add(param.getText()); + } + for (PyParameter param : superInfo.getRequiredParameters()) { + // Special case as if base class has constructor __init__((a, b), c) and + // subclass has constructor __init__(a, (b, c)) + final PyTupleParameter tupleParam = param.getAsTuple(); + if (tupleParam != null) { + final List uniqueNames = collectParameterNames(tupleParam); + final boolean hasDuplicates = uniqueNames.removeAll(origInfo.getAllParameterNames()); + if (hasDuplicates) { + newFunctionParams.addAll(uniqueNames); + } + else { + newFunctionParams.add(param.getText()); + } + // Retain original structure of tuple parameter. + // Note that tuple parameters cannot have annotations or nested default values, so it's syntactically safe + superCallArgs.add(param.getText()); + } + else { + if (!origInfo.getAllParameterNames().contains(param.getName())) { + newFunctionParams.add(param.getText()); + } + superCallArgs.add(param.getName()); + } + } - // Positional vararg - PyParameter starredParam = null; - if (origInfo.getPositionalContainerParameter() != null) { - starredParam = origInfo.getPositionalContainerParameter(); - } - else if (superInfo.getPositionalContainerParameter() != null) { - starredParam = superInfo.getPositionalContainerParameter(); - } - else if (origInfo.getSingleStarParameter() != null) { - starredParam = origInfo.getSingleStarParameter(); - } - else if (superInfo.getSingleStarParameter() != null) { - starredParam = superInfo.getSingleStarParameter(); - } - if (starredParam != null) { - newFunctionParams.add(starredParam.getText()); - if (superInfo.getPositionalContainerParameter() != null) { - superCallArgs.add("*" + starredParam.getName()); - } - } + // Optional parameters (not-keyword) + for (PyParameter param : origInfo.getOptionalParameters()) { + newFunctionParams.add(param.getText()); + } - // Required keyword-only parameters - boolean newSignatureContainsKeywordParams = false; - for (PyParameter param : origInfo.getRequiredKeywordOnlyParameters()) { - newFunctionParams.add(param.getText()); - newSignatureContainsKeywordParams = true; - } - for (PyParameter param : superInfo.getRequiredKeywordOnlyParameters()) { - if (!origInfo.getAllParameterNames().contains(param.getName())) { - newFunctionParams.add(param.getText()); - newSignatureContainsKeywordParams = true; - } - superCallArgs.add(param.getName() + "=" + param.getName()); - } + // Pass parameters with default values to super class constructor, only if both functions contain them + for (PyParameter param : superInfo.getOptionalParameters()) { + final PyTupleParameter tupleParam = param.getAsTuple(); + if (tupleParam != null) { + if (origInfo.getAllParameterNames().containsAll(collectParameterNames(tupleParam))) { + final String paramText = tupleParam.getText(); + final PsiElement equalSign = PyPsiUtils.getFirstChildOfType(param, PyTokenTypes.EQ); + if (equalSign != null) { + superCallArgs.add(paramText.substring(0, equalSign.getStartOffsetInParent()).trim()); + } + } + } + else { + if (origInfo.getAllParameterNames().contains(param.getName())) { + superCallArgs.add(param.getName()); + } + } + } - // Optional keyword-only parameters - for (PyParameter param : origInfo.getOptionalKeywordOnlyParameters()) { - newFunctionParams.add(param.getText()); - newSignatureContainsKeywordParams = true; - } + // Positional vararg + PyParameter starredParam = null; + if (origInfo.getPositionalContainerParameter() != null) { + starredParam = origInfo.getPositionalContainerParameter(); + } + else if (superInfo.getPositionalContainerParameter() != null) { + starredParam = superInfo.getPositionalContainerParameter(); + } + else if (origInfo.getSingleStarParameter() != null) { + starredParam = origInfo.getSingleStarParameter(); + } + else if (superInfo.getSingleStarParameter() != null) { + starredParam = superInfo.getSingleStarParameter(); + } + if (starredParam != null) { + newFunctionParams.add(starredParam.getText()); + if (superInfo.getPositionalContainerParameter() != null) { + superCallArgs.add("*" + starredParam.getName()); + } + } - // If '*' param is followed by nothing in result signature, remove it altogether - if (starredParam instanceof PySingleStarParameter && !newSignatureContainsKeywordParams) { - newFunctionParams.remove(newFunctionParams.size() - 1); - } + // Required keyword-only parameters + boolean newSignatureContainsKeywordParams = false; + for (PyParameter param : origInfo.getRequiredKeywordOnlyParameters()) { + newFunctionParams.add(param.getText()); + newSignatureContainsKeywordParams = true; + } + for (PyParameter param : superInfo.getRequiredKeywordOnlyParameters()) { + if (!origInfo.getAllParameterNames().contains(param.getName())) { + newFunctionParams.add(param.getText()); + newSignatureContainsKeywordParams = true; + } + superCallArgs.add(param.getName() + "=" + param.getName()); + } - for (PyParameter param : superInfo.getOptionalKeywordOnlyParameters()) { - if (origInfo.getAllParameterNames().contains(param.getName())) { - superCallArgs.add(param.getName() + "=" + param.getName()); - } - } + // Optional keyword-only parameters + for (PyParameter param : origInfo.getOptionalKeywordOnlyParameters()) { + newFunctionParams.add(param.getText()); + newSignatureContainsKeywordParams = true; + } - // Keyword vararg - PyParameter doubleStarredParam = null; - if (origInfo.getKeywordContainerParameter() != null) { - doubleStarredParam = origInfo.getKeywordContainerParameter(); - } - else if (superInfo.getKeywordContainerParameter() != null) { - doubleStarredParam = superInfo.getKeywordContainerParameter(); - } - if (doubleStarredParam != null) { - newFunctionParams.add(doubleStarredParam.getText()); - if (superInfo.getKeywordContainerParameter() != null) { - superCallArgs.add("**" + doubleStarredParam.getName()); - } - } - return Couple.of(newFunctionParams, superCallArgs); - } - - private static class ParametersInfo { - - private final PyParameter mySelfParam; - /** - * Parameters without default value that come before first "*..." parameter. - */ - private final List myRequiredParams = new ArrayList<>(); - /** - * Parameters with default value that come before first "*..." parameter. - */ - private final List myOptionalParams = new ArrayList<>(); - /** - * Parameter of form "*args" (positional vararg), not the same as single "*". - */ - private final PyParameter myPositionalContainerParam; - /** - * Parameter "*", that is used to delimit normal and keyword-only parameters. - */ - private final PyParameter mySingleStarParam; - /** - * Parameters without default value that come after first "*..." parameter. - */ - private final List myRequiredKwOnlyParams = new ArrayList<>(); - /** - * Parameters with default value that come after first "*..." parameter. - */ - private final List myOptionalKwOnlyParams = new ArrayList<>(); - /** - * Parameter of form "**kwargs" (keyword vararg). - */ - private final PyParameter myKeywordContainerParam; - - private final Set myAllParameterNames = new LinkedHashSet<>(); - - public ParametersInfo(@Nonnull PyParameterList parameterList) { - PyParameter positionalContainer = null; - PyParameter singleStarParam = null; - PyParameter keywordContainer = null; - PyParameter selfParam = null; - - for (PyParameter param : parameterList.getParameters()) { - myAllParameterNames.addAll(collectParameterNames(param)); - - if (param.isSelf()) { - selfParam = param; - } - else if (param instanceof PySingleStarParameter) { - singleStarParam = param; - } - else if (param.getAsNamed() != null && param.getAsNamed().isKeywordContainer()) { - keywordContainer = param; - } - else if (param.getAsNamed() != null && param.getAsNamed().isPositionalContainer()) { - positionalContainer = param; - } - else if (param.getAsNamed() == null || !param.getAsNamed().isKeywordOnly()) { - if (param.hasDefaultValue()) { - myOptionalParams.add(param); - } - else { - myRequiredParams.add(param); - } + // If '*' param is followed by nothing in result signature, remove it altogether + if (starredParam instanceof PySingleStarParameter && !newSignatureContainsKeywordParams) { + newFunctionParams.remove(newFunctionParams.size() - 1); } - else { - if (param.hasDefaultValue()) { - myOptionalKwOnlyParams.add(param); - } - else { - myRequiredKwOnlyParams.add(param); - } - } - } - - mySelfParam = selfParam; - myPositionalContainerParam = positionalContainer; - mySingleStarParam = singleStarParam; - myKeywordContainerParam = keywordContainer; - } - @Nullable - public PyParameter getSelfParameter() { - return mySelfParam; - } + for (PyParameter param : superInfo.getOptionalKeywordOnlyParameters()) { + if (origInfo.getAllParameterNames().contains(param.getName())) { + superCallArgs.add(param.getName() + "=" + param.getName()); + } + } - @Nonnull - public List getRequiredParameters() { - return Collections.unmodifiableList(myRequiredParams); - } + // Keyword vararg + PyParameter doubleStarredParam = null; + if (origInfo.getKeywordContainerParameter() != null) { + doubleStarredParam = origInfo.getKeywordContainerParameter(); + } + else if (superInfo.getKeywordContainerParameter() != null) { + doubleStarredParam = superInfo.getKeywordContainerParameter(); + } + if (doubleStarredParam != null) { + newFunctionParams.add(doubleStarredParam.getText()); + if (superInfo.getKeywordContainerParameter() != null) { + superCallArgs.add("**" + doubleStarredParam.getName()); + } + } + return Couple.of(newFunctionParams, superCallArgs); + } + + private static class ParametersInfo { + + private final PyParameter mySelfParam; + /** + * Parameters without default value that come before first "*..." parameter. + */ + private final List myRequiredParams = new ArrayList<>(); + /** + * Parameters with default value that come before first "*..." parameter. + */ + private final List myOptionalParams = new ArrayList<>(); + /** + * Parameter of form "*args" (positional vararg), not the same as single "*". + */ + private final PyParameter myPositionalContainerParam; + /** + * Parameter "*", that is used to delimit normal and keyword-only parameters. + */ + private final PyParameter mySingleStarParam; + /** + * Parameters without default value that come after first "*..." parameter. + */ + private final List myRequiredKwOnlyParams = new ArrayList<>(); + /** + * Parameters with default value that come after first "*..." parameter. + */ + private final List myOptionalKwOnlyParams = new ArrayList<>(); + /** + * Parameter of form "**kwargs" (keyword vararg). + */ + private final PyParameter myKeywordContainerParam; + + private final Set myAllParameterNames = new LinkedHashSet<>(); + + public ParametersInfo(@Nonnull PyParameterList parameterList) { + PyParameter positionalContainer = null; + PyParameter singleStarParam = null; + PyParameter keywordContainer = null; + PyParameter selfParam = null; + + for (PyParameter param : parameterList.getParameters()) { + myAllParameterNames.addAll(collectParameterNames(param)); + + if (param.isSelf()) { + selfParam = param; + } + else if (param instanceof PySingleStarParameter) { + singleStarParam = param; + } + else if (param.getAsNamed() != null && param.getAsNamed().isKeywordContainer()) { + keywordContainer = param; + } + else if (param.getAsNamed() != null && param.getAsNamed().isPositionalContainer()) { + positionalContainer = param; + } + else if (param.getAsNamed() == null || !param.getAsNamed().isKeywordOnly()) { + if (param.hasDefaultValue()) { + myOptionalParams.add(param); + } + else { + myRequiredParams.add(param); + } + } + else { + if (param.hasDefaultValue()) { + myOptionalKwOnlyParams.add(param); + } + else { + myRequiredKwOnlyParams.add(param); + } + } + } + + mySelfParam = selfParam; + myPositionalContainerParam = positionalContainer; + mySingleStarParam = singleStarParam; + myKeywordContainerParam = keywordContainer; + } - @Nonnull - public List getOptionalParameters() { - return Collections.unmodifiableList(myOptionalParams); - } + @Nullable + public PyParameter getSelfParameter() { + return mySelfParam; + } - @Nullable - public PyParameter getPositionalContainerParameter() { - return myPositionalContainerParam; - } + @Nonnull + public List getRequiredParameters() { + return Collections.unmodifiableList(myRequiredParams); + } - @Nullable - public PyParameter getSingleStarParameter() { - return mySingleStarParam; - } + @Nonnull + public List getOptionalParameters() { + return Collections.unmodifiableList(myOptionalParams); + } - @Nonnull - public List getRequiredKeywordOnlyParameters() { - return Collections.unmodifiableList(myRequiredKwOnlyParams); - } + @Nullable + public PyParameter getPositionalContainerParameter() { + return myPositionalContainerParam; + } - @Nonnull - public List getOptionalKeywordOnlyParameters() { - return Collections.unmodifiableList(myOptionalKwOnlyParams); - } + @Nullable + public PyParameter getSingleStarParameter() { + return mySingleStarParam; + } + + @Nonnull + public List getRequiredKeywordOnlyParameters() { + return Collections.unmodifiableList(myRequiredKwOnlyParams); + } - @Nullable - public PyParameter getKeywordContainerParameter() { - return myKeywordContainerParam; + @Nonnull + public List getOptionalKeywordOnlyParameters() { + return Collections.unmodifiableList(myOptionalKwOnlyParams); + } + + @Nullable + public PyParameter getKeywordContainerParameter() { + return myKeywordContainerParam; + } + + @Nonnull + public Set getAllParameterNames() { + return Collections.unmodifiableSet(myAllParameterNames); + } } @Nonnull - public Set getAllParameterNames() { - return Collections.unmodifiableSet(myAllParameterNames); - } - } - - @Nonnull - private static List collectParameterNames(@Nonnull PyParameter param) { - final List result = new ArrayList<>(); - collectParameterNames(param, result); - return result; - } - - - private static void collectParameterNames(@Nonnull PyParameter param, @Nonnull Collection acc) { - final PyTupleParameter tupleParam = param.getAsTuple(); - if (tupleParam != null) { - for (PyParameter subParam : tupleParam.getContents()) { - collectParameterNames(subParam, acc); - } + private static List collectParameterNames(@Nonnull PyParameter param) { + final List result = new ArrayList<>(); + collectParameterNames(param, result); + return result; } - else { - ContainerUtil.addIfNotNull(acc, param.getName()); + + + private static void collectParameterNames(@Nonnull PyParameter param, @Nonnull Collection acc) { + final PyTupleParameter tupleParam = param.getAsTuple(); + if (tupleParam != null) { + for (PyParameter subParam : tupleParam.getContents()) { + collectParameterNames(subParam, acc); + } + } + else { + ContainerUtil.addIfNotNull(acc, param.getName()); + } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddEncodingQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddEncodingQuickFix.java index 5e3f8736..89193969 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddEncodingQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddEncodingQuickFix.java @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.impl.inspections.PyEncodingUtil; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyElementGenerator; @@ -25,9 +23,9 @@ import consulo.language.psi.PsiComment; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; - +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; /** @@ -35,38 +33,35 @@ * # -*- coding: -*- * to the source file * - * User: catherine + * @author catherine */ public class AddEncodingQuickFix implements LocalQuickFix { + private String myDefaultEncoding; + private int myEncodingFormatIndex; - private String myDefaultEncoding; - private int myEncodingFormatIndex; - - public AddEncodingQuickFix(String defaultEncoding, int encodingFormatIndex) { - myDefaultEncoding = defaultEncoding; - myEncodingFormatIndex = encodingFormatIndex; - } - - @Nonnull - public String getName() { - return PyBundle.message("QFIX.add.encoding"); - } + public AddEncodingQuickFix(String defaultEncoding, int encodingFormatIndex) { + myDefaultEncoding = defaultEncoding; + myEncodingFormatIndex = encodingFormatIndex; + } - @NonNls - @Nonnull - public String getFamilyName() { - return getName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixAddEncoding(); + } - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { - PsiFile file = descriptor.getPsiElement().getContainingFile(); - if (file == null) return; - PsiElement firstLine = file.getFirstChild(); - if (firstLine instanceof PsiComment && firstLine.getText().startsWith("#!")) { - firstLine = firstLine.getNextSibling(); + public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + PsiFile file = descriptor.getPsiElement().getContainingFile(); + if (file == null) { + return; + } + PsiElement firstLine = file.getFirstChild(); + if (firstLine instanceof PsiComment && firstLine.getText().startsWith("#!")) { + firstLine = firstLine.getNextSibling(); + } + PsiComment encodingLine = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.forElement(file), PsiComment.class, + String.format(PyEncodingUtil.ENCODING_FORMAT_PATTERN[myEncodingFormatIndex], myDefaultEncoding) + ); + file.addBefore(encodingLine, firstLine); } - PsiComment encodingLine = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.forElement(file), PsiComment.class, - String.format(PyEncodingUtil.ENCODING_FORMAT_PATTERN[myEncodingFormatIndex], myDefaultEncoding)); - file.addBefore(encodingLine, firstLine); - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFieldQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFieldQuickFix.java index 1de31779..457b7354 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFieldQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFieldQuickFix.java @@ -35,8 +35,10 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.navigation.OpenFileDescriptorFactory; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.ui.NotificationType; import consulo.virtualFileSystem.VirtualFile; @@ -46,8 +48,9 @@ /** * Available on self.my_something when my_something is unresolved. - * User: dcheryasov - * Date: Apr 4, 2009 1:53:46 PM + * + * @author dcheryasov + * @since 2009-04-04 */ public class AddFieldQuickFix implements LocalQuickFix { @@ -64,13 +67,9 @@ public AddFieldQuickFix(@Nonnull final String identifier, @Nonnull final String } @Nonnull - public String getName() { - return PyBundle.message("QFIX.NAME.add.field.$0.to.class.$1", myIdentifier, myClassName); - } - - @Nonnull - public String getFamilyName() { - return "Add field to class"; + @Override + public LocalizeValue getName() { + return PyLocalize.qfixNameAddField$0ToClass$1(myIdentifier, myClassName); } @Nullable diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFunctionQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFunctionQuickFix.java index 3a9e07fa..e79bbc4b 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFunctionQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddFunctionQuickFix.java @@ -36,8 +36,10 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.navigation.OpenFileDescriptorFactory; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.ui.NotificationType; import consulo.virtualFileSystem.VirtualFile; @@ -48,10 +50,9 @@ /** * Adds a missing top-level function to a module. - *
    - * User: dcheryasov - * Date: Sep 15, 2010 4:34:23 PM * + * @author dcheryasov + * @since 2010-09-15 * @see AddMethodQuickFix AddMethodQuickFix */ public class AddFunctionQuickFix implements LocalQuickFix { @@ -65,13 +66,9 @@ public AddFunctionQuickFix(@Nonnull String identifier, String moduleName) { } @Nonnull - public String getName() { - return PyBundle.message("QFIX.NAME.add.function.$0.to.module.$1", myIdentifier, myModuleName); - } - - @Nonnull - public String getFamilyName() { - return "Create function in module"; + @Override + public LocalizeValue getName() { + return PyLocalize.qfixNameAddFunction$0ToModule$1(myIdentifier, myModuleName); } public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddGlobalQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddGlobalQuickFix.java index 795c3a56..19cdef0b 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddGlobalQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddGlobalQuickFix.java @@ -13,19 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.psi.*; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; -import consulo.util.lang.ref.Ref; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; -import com.jetbrains.python.psi.*; -import org.jetbrains.annotations.NonNls; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import consulo.util.lang.ref.Ref; import jakarta.annotation.Nonnull; /** @@ -33,14 +32,9 @@ */ public class AddGlobalQuickFix implements LocalQuickFix { @Nonnull - public String getName() { - return PyBundle.message("QFIX.add.global"); - } - - @NonNls - @Nonnull - public String getFamilyName() { - return getName(); + @Override + public LocalizeValue getName() { + return PyLocalize.qfixAddGlobal(); } public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddIgnoredIdentifierQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddIgnoredIdentifierQuickFix.java index 10420cb7..9fcdbb00 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddIgnoredIdentifierQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/AddIgnoredIdentifierQuickFix.java @@ -24,6 +24,7 @@ import consulo.language.editor.intention.LowPriorityAction; import consulo.language.psi.PsiElement; import consulo.language.psi.util.QualifiedName; +import consulo.localize.LocalizeValue; import consulo.project.Project; import jakarta.annotation.Nonnull; @@ -31,57 +32,45 @@ /** * @author yole */ -public class AddIgnoredIdentifierQuickFix implements LocalQuickFix, LowPriorityAction -{ - public static final String END_WILDCARD = ".*"; +public class AddIgnoredIdentifierQuickFix implements LocalQuickFix, LowPriorityAction { + public static final String END_WILDCARD = ".*"; - @Nonnull - private final QualifiedName myIdentifier; - private final boolean myIgnoreAllAttributes; + @Nonnull + private final QualifiedName myIdentifier; + private final boolean myIgnoreAllAttributes; - public AddIgnoredIdentifierQuickFix(@Nonnull QualifiedName identifier, boolean ignoreAllAttributes) - { - myIdentifier = identifier; - myIgnoreAllAttributes = ignoreAllAttributes; - } + public AddIgnoredIdentifierQuickFix(@Nonnull QualifiedName identifier, boolean ignoreAllAttributes) { + myIdentifier = identifier; + myIgnoreAllAttributes = ignoreAllAttributes; + } - @Nonnull - @Override - public String getName() - { - if(myIgnoreAllAttributes) - { - return "Mark all unresolved attributes of '" + myIdentifier + "' as ignored"; - } - else - { - return "Ignore unresolved reference '" + myIdentifier + "'"; - } - } + @Nonnull + @Override + public LocalizeValue getName() { + if (myIgnoreAllAttributes) { + return LocalizeValue.localizeTODO("Mark all unresolved attributes of '" + myIdentifier + "' as ignored"); + } + else { + return LocalizeValue.localizeTODO("Ignore unresolved reference '" + myIdentifier + "'"); + } + } - @Nonnull - @Override - public String getFamilyName() - { - return "Ignore unresolved reference"; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - final PsiElement context = descriptor.getPsiElement(); - InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - profile.modifyToolSettings(PyUnresolvedReferencesInspection.class.getSimpleName(), context, (i, s) -> - { - String name = myIdentifier.toString(); - if(myIgnoreAllAttributes) - { - name += END_WILDCARD; - } - if(!s.ignoredIdentifiers.contains(name)) - { - s.ignoredIdentifiers.add(name); - } - }); - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiElement context = descriptor.getPsiElement(); + InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + profile.modifyToolSettings( + PyUnresolvedReferencesInspection.class.getSimpleName(), + context, + (i, s) -> { + String name = myIdentifier.toString(); + if (myIgnoreAllAttributes) { + name += END_WILDCARD; + } + if (!s.ignoredIdentifiers.contains(name)) { + s.ignoredIdentifiers.add(name); + } + } + ); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyMakeFunctionReturnTypeQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyMakeFunctionReturnTypeQuickFix.java index ccb28d06..e413fbc4 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyMakeFunctionReturnTypeQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyMakeFunctionReturnTypeQuickFix.java @@ -15,85 +15,78 @@ */ package com.jetbrains.python.impl.inspections.quickfix; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; +import com.jetbrains.python.impl.PyBundle; +import com.jetbrains.python.impl.documentation.PythonDocumentationProvider; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; import consulo.language.psi.PsiComment; import consulo.language.psi.SmartPointerManager; import consulo.language.psi.SmartPsiElementPointer; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.impl.documentation.PythonDocumentationProvider; -import com.jetbrains.python.psi.LanguageLevel; -import com.jetbrains.python.psi.PyAnnotation; -import com.jetbrains.python.psi.PyElementGenerator; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** * @author lada */ -public class PyMakeFunctionReturnTypeQuickFix implements LocalQuickFix -{ - private final SmartPsiElementPointer myFunction; - private final SmartPsiElementPointer myAnnotation; - private final SmartPsiElementPointer myTypeCommentAnnotation; - private final String myReturnTypeName; - - public PyMakeFunctionReturnTypeQuickFix(@Nonnull PyFunction function, @Nullable String returnTypeName, @Nonnull TypeEvalContext context) - { - final SmartPointerManager manager = SmartPointerManager.getInstance(function.getProject()); - myFunction = manager.createSmartPsiElementPointer(function); - PyAnnotation annotation = function.getAnnotation(); - myAnnotation = annotation != null ? manager.createSmartPsiElementPointer(annotation) : null; - PsiComment typeCommentAnnotation = function.getTypeComment(); - myTypeCommentAnnotation = typeCommentAnnotation != null ? manager.createSmartPsiElementPointer(typeCommentAnnotation) : null; - myReturnTypeName = (returnTypeName == null) ? PythonDocumentationProvider.getTypeName(function.getReturnStatementType(context), context) : returnTypeName; - } +public class PyMakeFunctionReturnTypeQuickFix implements LocalQuickFix { + private final SmartPsiElementPointer myFunction; + private final SmartPsiElementPointer myAnnotation; + private final SmartPsiElementPointer myTypeCommentAnnotation; + private final String myReturnTypeName; - @Nonnull - public String getName() - { - PyFunction function = myFunction.getElement(); - String functionName = function != null ? function.getName() : "function"; - return PyBundle.message("QFIX.NAME.make.$0.return.$1", functionName, myReturnTypeName); - } + public PyMakeFunctionReturnTypeQuickFix( + @Nonnull PyFunction function, + @Nullable String returnTypeName, + @Nonnull TypeEvalContext context + ) { + final SmartPointerManager manager = SmartPointerManager.getInstance(function.getProject()); + myFunction = manager.createSmartPsiElementPointer(function); + PyAnnotation annotation = function.getAnnotation(); + myAnnotation = annotation != null ? manager.createSmartPsiElementPointer(annotation) : null; + PsiComment typeCommentAnnotation = function.getTypeComment(); + myTypeCommentAnnotation = typeCommentAnnotation != null ? manager.createSmartPsiElementPointer(typeCommentAnnotation) : null; + myReturnTypeName = (returnTypeName == null) + ? PythonDocumentationProvider.getTypeName(function.getReturnStatementType(context), context) + : returnTypeName; + } - @Nonnull - public String getFamilyName() - { - return PyBundle.message("QFIX.NAME.make.$0.return.$1", "function", "inferred type"); - } + @Nonnull + @Override + @RequiredReadAction + public LocalizeValue getName() { + PyFunction function = myFunction.getElement(); + String functionName = function != null ? function.getName() : "function"; + return PyLocalize.qfixNameMake$0Return$1(functionName, myReturnTypeName); + } - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - if(myAnnotation != null) - { - final PyAnnotation annotation = myAnnotation.getElement(); - if(annotation != null) - { - final PyExpression annotationExpr = annotation.getValue(); - if(annotationExpr == null) - { - return; - } - annotationExpr.replace(elementGenerator.createExpressionFromText(LanguageLevel.PYTHON30, myReturnTypeName)); - } - } - else if(myTypeCommentAnnotation != null) - { - final PsiComment typeComment = myTypeCommentAnnotation.getElement(); - if(typeComment != null) - { - final StringBuilder typeCommentAnnotation = new StringBuilder(typeComment.getText()); - typeCommentAnnotation.delete(typeCommentAnnotation.indexOf("->"), typeCommentAnnotation.length()); - typeCommentAnnotation.append("-> ").append(myReturnTypeName); - final PsiComment newTypeComment = elementGenerator.createFromText(LanguageLevel.PYTHON27, PsiComment.class, typeCommentAnnotation.toString()); - typeComment.replace(newTypeComment); - } - } - } + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + if (myAnnotation != null) { + final PyAnnotation annotation = myAnnotation.getElement(); + if (annotation != null) { + final PyExpression annotationExpr = annotation.getValue(); + if (annotationExpr == null) { + return; + } + annotationExpr.replace(elementGenerator.createExpressionFromText(LanguageLevel.PYTHON30, myReturnTypeName)); + } + } + else if (myTypeCommentAnnotation != null) { + final PsiComment typeComment = myTypeCommentAnnotation.getElement(); + if (typeComment != null) { + final StringBuilder typeCommentAnnotation = new StringBuilder(typeComment.getText()); + typeCommentAnnotation.delete(typeCommentAnnotation.indexOf("->"), typeCommentAnnotation.length()); + typeCommentAnnotation.append("-> ").append(myReturnTypeName); + final PsiComment newTypeComment = + elementGenerator.createFromText(LanguageLevel.PYTHON27, PsiComment.class, typeCommentAnnotation.toString()); + typeComment.replace(newTypeComment); + } + } + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRemoveAssignmentQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRemoveAssignmentQuickFix.java index 7f120842..a6886ebc 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRemoveAssignmentQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRemoveAssignmentQuickFix.java @@ -15,36 +15,32 @@ */ package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyAssignmentStatement; import com.jetbrains.python.psi.PyExpression; -public class PyRemoveAssignmentQuickFix implements LocalQuickFix -{ - @Nonnull - @Override - public String getFamilyName() - { - return PyBundle.message("QFIX.NAME.remove.assignment"); - } +public class PyRemoveAssignmentQuickFix implements LocalQuickFix { + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixNameRemoveAssignment(); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - final PsiElement assignment = descriptor.getPsiElement(); - if(assignment instanceof PyAssignmentStatement) - { - final PyExpression value = ((PyAssignmentStatement) assignment).getAssignedValue(); - if(value != null) - { - assignment.replace(value); - } - } - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiElement assignment = descriptor.getPsiElement(); + if (assignment instanceof PyAssignmentStatement) { + final PyExpression value = ((PyAssignmentStatement) assignment).getAssignedValue(); + if (value != null) { + assignment.replace(value); + } + } + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRenameArgumentQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRenameArgumentQuickFix.java index 2d14d8ad..8107898d 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRenameArgumentQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyRenameArgumentQuickFix.java @@ -15,7 +15,6 @@ */ package com.jetbrains.python.impl.inspections.quickfix; -import com.jetbrains.python.impl.PyBundle; import consulo.codeEditor.Editor; import consulo.document.util.TextRange; import consulo.fileEditor.FileEditorManager; @@ -25,37 +24,39 @@ import consulo.language.editor.template.TemplateBuilderFactory; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiNamedElement; +import consulo.localize.LocalizeValue; import consulo.navigation.OpenFileDescriptorFactory; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; public class PyRenameArgumentQuickFix implements LocalQuickFix { - @Nonnull - @Override - public String getFamilyName() { - return PyBundle.message("QFIX.NAME.rename.argument"); - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - if (!(element instanceof PsiNamedElement)) { - return; + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixNameRenameArgument(); } - final VirtualFile virtualFile = element.getContainingFile().getVirtualFile(); - if (virtualFile != null) { - final Editor editor = FileEditorManager.getInstance(project) - .openTextEditor(OpenFileDescriptorFactory.getInstance(project).builder(virtualFile).build(), - true); - final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(element); - final String name = ((PsiNamedElement)element).getName(); - assert name != null; - assert editor != null; - builder.replaceElement(element, TextRange.create(0, name.length()), name); - builder.run(editor, false); - } - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (!(element instanceof PsiNamedElement)) { + return; + } + final VirtualFile virtualFile = element.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + final Editor editor = FileEditorManager.getInstance(project) + .openTextEditor( + OpenFileDescriptorFactory.getInstance(project).builder(virtualFile).build(), + true + ); + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(element); + final String name = ((PsiNamedElement) element).getName(); + assert name != null; + assert editor != null; + builder.replaceElement(element, TextRange.create(0, name.length()), name); + builder.run(editor, false); + } + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyReplaceTupleWithListQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyReplaceTupleWithListQuickFix.java index f6a4d1b2..66328d65 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyReplaceTupleWithListQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/PyReplaceTupleWithListQuickFix.java @@ -15,65 +15,51 @@ */ package com.jetbrains.python.impl.inspections.quickfix; -import jakarta.annotation.Nonnull; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.types.TypeEvalContext; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.psi.LanguageLevel; -import com.jetbrains.python.psi.PyAssignmentStatement; -import com.jetbrains.python.psi.PyElementGenerator; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyParenthesizedExpression; -import com.jetbrains.python.psi.PyReferenceExpression; -import com.jetbrains.python.psi.PySubscriptionExpression; -import com.jetbrains.python.psi.PyTupleExpression; -import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import jakarta.annotation.Nonnull; -public class PyReplaceTupleWithListQuickFix implements LocalQuickFix -{ - @Nonnull - @Override - public String getFamilyName() - { - return PyBundle.message("QFIX.NAME.make.list"); - } +public class PyReplaceTupleWithListQuickFix implements LocalQuickFix { + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixNameMakeList(); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - PsiElement element = descriptor.getPsiElement(); - assert element instanceof PyAssignmentStatement; - PyExpression[] targets = ((PyAssignmentStatement) element).getTargets(); - if(targets.length == 1 && targets[0] instanceof PySubscriptionExpression) - { - PySubscriptionExpression subscriptionExpression = (PySubscriptionExpression) targets[0]; - if(subscriptionExpression.getOperand() instanceof PyReferenceExpression) - { - PyReferenceExpression referenceExpression = (PyReferenceExpression) subscriptionExpression.getOperand(); - final TypeEvalContext context = TypeEvalContext.userInitiated(project, element.getContainingFile()); - final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); - element = referenceExpression.followAssignmentsChain(resolveContext).getElement(); - if(element instanceof PyParenthesizedExpression) - { - final PyExpression expression = ((PyParenthesizedExpression) element).getContainedExpression(); - replaceWithListLiteral(element, (PyTupleExpression) expression); - } - else if(element instanceof PyTupleExpression) - { - replaceWithListLiteral(element, (PyTupleExpression) element); - } - } - } - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + assert element instanceof PyAssignmentStatement; + PyExpression[] targets = ((PyAssignmentStatement) element).getTargets(); + if (targets.length == 1 && targets[0] instanceof PySubscriptionExpression) { + PySubscriptionExpression subscriptionExpression = (PySubscriptionExpression) targets[0]; + if (subscriptionExpression.getOperand() instanceof PyReferenceExpression) { + PyReferenceExpression referenceExpression = (PyReferenceExpression) subscriptionExpression.getOperand(); + final TypeEvalContext context = TypeEvalContext.userInitiated(project, element.getContainingFile()); + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + element = referenceExpression.followAssignmentsChain(resolveContext).getElement(); + if (element instanceof PyParenthesizedExpression) { + final PyExpression expression = ((PyParenthesizedExpression) element).getContainedExpression(); + replaceWithListLiteral(element, (PyTupleExpression) expression); + } + else if (element instanceof PyTupleExpression) { + replaceWithListLiteral(element, (PyTupleExpression) element); + } + } + } + } - private static void replaceWithListLiteral(PsiElement element, PyTupleExpression expression) - { - final String expressionText = expression.isEmpty() ? "" : expression.getText(); - final PyExpression literal = PyElementGenerator.getInstance(element.getProject()). - createExpressionFromText(LanguageLevel.forElement(element), "[" + expressionText + "]"); - element.replace(literal); - } + private static void replaceWithListLiteral(PsiElement element, PyTupleExpression expression) { + final String expressionText = expression.isEmpty() ? "" : expression.getText(); + final PyExpression literal = PyElementGenerator.getInstance(element.getProject()). + createExpressionFromText(LanguageLevel.forElement(element), "[" + expressionText + "]"); + element.replace(literal); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveArgumentEqualDefaultQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveArgumentEqualDefaultQuickFix.java index 477b5165..7b07de92 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveArgumentEqualDefaultQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveArgumentEqualDefaultQuickFix.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.util.lang.StringUtil; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.*; import jakarta.annotation.Nonnull; @@ -31,9 +31,9 @@ import java.util.Set; /** - * User: catherine - * * QuickFix to remove redundant argument equal default + * + * @author catherine */ public class RemoveArgumentEqualDefaultQuickFix implements LocalQuickFix { Set myProblemElements; @@ -42,13 +42,9 @@ public RemoveArgumentEqualDefaultQuickFix(Set problemElements) { } @Nonnull - public String getName() { - return PyBundle.message("QFIX.remove.argument.equal.default"); - } - - @Nonnull - public String getFamilyName() { - return getName(); + @Override + public LocalizeValue getName() { + return PyLocalize.qfixRemoveArgumentEqualDefault(); } public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemovePrefixQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemovePrefixQuickFix.java index 1cb95f08..ae12e233 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemovePrefixQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemovePrefixQuickFix.java @@ -13,52 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyStringLiteralExpression; import com.jetbrains.python.impl.psi.impl.PyStringLiteralExpressionImpl; /** - * Created by IntelliJ IDEA. - * Author: Alexey.Ivanov - * Date: 06.03.2010 - * Time: 16:50:53 + * @author Alexey.Ivanov + * @since 2010-03-06 */ public class RemovePrefixQuickFix implements LocalQuickFix { - private final String myPrefix; - - public RemovePrefixQuickFix(String prefix) { - myPrefix = prefix; - } + private final String myPrefix; - @Nonnull + public RemovePrefixQuickFix(String prefix) { + myPrefix = prefix; + } - @Override - public String getName() { - return PyBundle.message("INTN.remove.leading.$0", myPrefix); - } + @Nonnull - @Nonnull - public String getFamilyName() { - return PyBundle.message("INTN.remove.leading.prefix"); - } + @Override + public LocalizeValue getName() { + return PyLocalize.intnRemoveLeading$0(myPrefix); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiElement stringLiteralExpression = descriptor.getPsiElement(); - if (stringLiteralExpression instanceof PyStringLiteralExpression) { - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - final int length = PyStringLiteralExpressionImpl.getPrefixLength(stringLiteralExpression.getText()); - stringLiteralExpression.replace(elementGenerator.createExpressionFromText(stringLiteralExpression.getText().substring(length))); + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement stringLiteralExpression = descriptor.getPsiElement(); + if (stringLiteralExpression instanceof PyStringLiteralExpression) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + final int length = PyStringLiteralExpressionImpl.getPrefixLength(stringLiteralExpression.getText()); + stringLiteralExpression.replace(elementGenerator.createExpressionFromText(stringLiteralExpression.getText().substring(length))); + } } - } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingBlankLinesFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingBlankLinesFix.java index d2478d90..f09d4512 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingBlankLinesFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingBlankLinesFix.java @@ -15,83 +15,71 @@ */ package com.jetbrains.python.impl.inspections.quickfix; -import jakarta.annotation.Nonnull; - -import consulo.language.editor.intention.HighPriorityAction; -import consulo.language.editor.intention.IntentionAction; +import consulo.codeEditor.Editor; +import consulo.document.Document; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.document.Document; -import consulo.codeEditor.Editor; -import consulo.project.Project; +import consulo.language.editor.intention.HighPriorityAction; +import consulo.language.editor.intention.IntentionAction; import consulo.language.psi.PsiDocumentManager; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import jakarta.annotation.Nonnull; /** * @author yole */ -public class RemoveTrailingBlankLinesFix implements LocalQuickFix, IntentionAction, HighPriorityAction -{ - @Nonnull - @Override - public String getText() - { - return "Remove trailing blank lines"; - } +public class RemoveTrailingBlankLinesFix implements LocalQuickFix, IntentionAction, HighPriorityAction { + @Nonnull + @Override + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Remove trailing blank lines"); + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) - { - return true; - } + @Nonnull + @Override + public LocalizeValue getName() { + return getText(); + } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException - { - removeTrailingBlankLines(file); - } + @Override + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + return true; + } - @Override - public boolean startInWriteAction() - { - return true; - } + @Override + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + removeTrailingBlankLines(file); + } - @Nonnull - @Override - public String getFamilyName() - { - return getText(); - } + @Override + public boolean startInWriteAction() { + return true; + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - removeTrailingBlankLines(descriptor.getPsiElement().getContainingFile()); - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + removeTrailingBlankLines(descriptor.getPsiElement().getContainingFile()); + } - private static void removeTrailingBlankLines(PsiFile file) - { - Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); - if(document == null) - { - return; - } - int lastBlankLineOffset = -1; - for(int i = document.getLineCount() - 1; i >= 0; i--) - { - int lineStart = document.getLineStartOffset(i); - String trimmed = document.getCharsSequence().subSequence(lineStart, document.getLineEndOffset(i)).toString().trim(); - if(trimmed.length() > 0) - { - break; - } - lastBlankLineOffset = lineStart; - } - if(lastBlankLineOffset > 0) - { - document.deleteString(lastBlankLineOffset, document.getTextLength()); - } - } + private static void removeTrailingBlankLines(PsiFile file) { + Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + if (document == null) { + return; + } + int lastBlankLineOffset = -1; + for (int i = document.getLineCount() - 1; i >= 0; i--) { + int lineStart = document.getLineStartOffset(i); + String trimmed = document.getCharsSequence().subSequence(lineStart, document.getLineEndOffset(i)).toString().trim(); + if (trimmed.length() > 0) { + break; + } + lastBlankLineOffset = lineStart; + } + if (lastBlankLineOffset > 0) { + document.deleteString(lastBlankLineOffset, document.getTextLength()); + } + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingSemicolonQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingSemicolonQuickFix.java index a812a5c6..c65e6e58 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingSemicolonQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveTrailingSemicolonQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.FileModificationService; @@ -23,23 +24,15 @@ import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; /** - * Created by IntelliJ IDEA. - * User: Alexey.Ivanov - * Date: Jul 30, 2009 - * Time: 2:57:42 PM + * @since Alexey.Ivanov + * @since 2009-07-30 */ public class RemoveTrailingSemicolonQuickFix implements LocalQuickFix { @Nonnull - public String getName() { - return PyBundle.message("QFIX.remove.trailing.semicolon"); - } - - @Nonnull - public String getFamilyName() { - return getName(); + public LocalizeValue getName() { + return PyLocalize.qfixRemoveTrailingSemicolon(); } public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveUnnecessaryBackslashQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveUnnecessaryBackslashQuickFix.java index f6034302..ab44f99e 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveUnnecessaryBackslashQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RemoveUnnecessaryBackslashQuickFix.java @@ -13,67 +13,63 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; -import jakarta.annotation.Nonnull; - +import com.jetbrains.python.impl.editor.PythonEnterHandler; +import com.jetbrains.python.psi.PyParenthesizedExpression; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiWhiteSpace; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; import consulo.util.collection.Stack; -import com.jetbrains.python.impl.PyBundle; -import com.jetbrains.python.impl.editor.PythonEnterHandler; -import com.jetbrains.python.psi.*; +import jakarta.annotation.Nonnull; /** - * User: catherine - * * QuickFix to remove all unnecessary backslashes in expression + * + * @author catherine */ public class RemoveUnnecessaryBackslashQuickFix implements LocalQuickFix { - @Nonnull - public String getName() { - return PyBundle.message("QFIX.remove.unnecessary.backslash"); - } - - @Nonnull - public String getFamilyName() { - return getName(); - } + @Nonnull + public LocalizeValue getName() { + return PyLocalize.qfixRemoveUnnecessaryBackslash(); + } - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiElement problemElement = descriptor.getPsiElement(); - if (problemElement != null) { - PsiElement parent = PsiTreeUtil.getParentOfType(problemElement, PythonEnterHandler.IMPLICIT_WRAP_CLASSES); - removeBackSlash(parent); + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement problemElement = descriptor.getPsiElement(); + if (problemElement != null) { + PsiElement parent = PsiTreeUtil.getParentOfType(problemElement, PythonEnterHandler.IMPLICIT_WRAP_CLASSES); + removeBackSlash(parent); + } } - } - - public static void removeBackSlash(PsiElement parent) { - if (parent != null) { - Stack stack = new Stack(); - if (parent instanceof PyParenthesizedExpression) - stack.push(((PyParenthesizedExpression)parent).getContainedExpression()); - else - stack.push(parent); - while (!stack.isEmpty()) { - PsiElement el = stack.pop(); - PsiWhiteSpace[] children = PsiTreeUtil.getChildrenOfType(el, PsiWhiteSpace.class); - if (children != null) { - for (PsiWhiteSpace ws : children) { - if (ws.getText().contains("\\")) { - ws.delete(); + + public static void removeBackSlash(PsiElement parent) { + if (parent != null) { + Stack stack = new Stack(); + if (parent instanceof PyParenthesizedExpression) { + stack.push(((PyParenthesizedExpression) parent).getContainedExpression()); + } + else { + stack.push(parent); + } + while (!stack.isEmpty()) { + PsiElement el = stack.pop(); + PsiWhiteSpace[] children = PsiTreeUtil.getChildrenOfType(el, PsiWhiteSpace.class); + if (children != null) { + for (PsiWhiteSpace ws : children) { + if (ws.getText().contains("\\")) { + ws.delete(); + } + } + } + for (PsiElement psiElement : el.getChildren()) { + stack.push(psiElement); + } } - } - } - for (PsiElement psiElement : el.getChildren()) { - stack.push(psiElement); } - } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RenameParameterQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RenameParameterQuickFix.java index b4f1e47d..66a0fda1 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RenameParameterQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/RenameParameterQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; @@ -23,14 +24,13 @@ import consulo.language.editor.refactoring.rename.RenameProcessor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyNamedParameter; /** * Parameter renaming. -* User: dcheryasov -* Date: Nov 30, 2008 6:10:13 AM -*/ + * @author dcheryasov + * @since 2008-11-30 + */ public class RenameParameterQuickFix implements LocalQuickFix { private final String myNewName; @@ -46,12 +46,8 @@ public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescr } @Nonnull - public String getFamilyName() { - return "Rename parameter"; - } - - @Nonnull - public String getName() { - return PyBundle.message("QFIX.rename.parameter.to.$0", myNewName); + @Override + public LocalizeValue getName() { + return PyLocalize.qfixRenameParameterTo$0(myNewName); } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceBackquoteExpressionQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceBackquoteExpressionQuickFix.java index 680b485f..d3c05866 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceBackquoteExpressionQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceBackquoteExpressionQuickFix.java @@ -13,35 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyReprExpression; /** - * Created by IntelliJ IDEA. - * Author: Alexey.Ivanov - * Date: 06.03.2010 - * Time: 16:50:53 + * @author Alexey.Ivanov + * @since 2010-03-06 */ public class ReplaceBackquoteExpressionQuickFix implements LocalQuickFix { @Nonnull @Override - public String getName() { - return PyBundle.message("INTN.replace.backquote.expression"); - } - - @Nonnull - public String getFamilyName() { - return getName(); + public LocalizeValue getName() { + return PyLocalize.intnReplaceBackquoteExpression(); } @Override diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceFunctionWithSetLiteralQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceFunctionWithSetLiteralQuickFix.java index 1df64647..ffcdd018 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceFunctionWithSetLiteralQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceFunctionWithSetLiteralQuickFix.java @@ -13,51 +13,47 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.impl.inspections.PySetFunctionToLiteralInspection; import com.jetbrains.python.psi.*; /** - * User : catherine - * Quick Fix to replace function call of built-in function "set" with - * set literal if applicable + * Quick Fix to replace function call of built-in function "set" with set literal if applicable. + * + * @author catherine */ public class ReplaceFunctionWithSetLiteralQuickFix implements LocalQuickFix { - @Override - @Nonnull - public String getName() { - return PyBundle.message("QFIX.replace.function.set.with.literal"); - } - - @Override - @Nonnull - public String getFamilyName() { - return getName(); - } + @Override + @Nonnull + public LocalizeValue getName() { + return PyLocalize.qfixReplaceFunctionSetWithLiteral(); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PyElement[] elements = PySetFunctionToLiteralInspection.getSetCallArguments((PyCallExpression)descriptor.getPsiElement()); - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - PsiElement functionCall = descriptor.getPsiElement(); - StringBuilder str = new StringBuilder("{"); - for (int i = 0; i != elements.length; ++i) { - PyElement e = elements[i]; - str.append(e.getText()); - if (i != elements.length-1) - str.append(", "); + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PyElement[] elements = PySetFunctionToLiteralInspection.getSetCallArguments((PyCallExpression) descriptor.getPsiElement()); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + PsiElement functionCall = descriptor.getPsiElement(); + StringBuilder str = new StringBuilder("{"); + for (int i = 0; i != elements.length; ++i) { + PyElement e = elements[i]; + str.append(e.getText()); + if (i != elements.length - 1) { + str.append(", "); + } + } + str.append("}"); + functionCall.replace(elementGenerator.createFromText(LanguageLevel.forElement(functionCall), PyExpressionStatement.class, + str.toString() + ).getExpression()); } - str.append("}"); - functionCall.replace(elementGenerator.createFromText(LanguageLevel.forElement(functionCall), PyExpressionStatement.class, - str.toString()).getExpression()); - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceOctalNumericLiteralQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceOctalNumericLiteralQuickFix.java index 312afc3b..396aceef 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceOctalNumericLiteralQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceOctalNumericLiteralQuickFix.java @@ -13,44 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyNumericLiteralExpression; /** - * Created by IntelliJ IDEA. - * Author: Alexey.Ivanov - * Date: 06.03.2010 - * Time: 16:50:53 + * @author Alexey.Ivanov + * @since 2010-03-06 */ public class ReplaceOctalNumericLiteralQuickFix implements LocalQuickFix { - @Nonnull - @Override - public String getName() { - return PyBundle.message("INTN.replace.octal.numeric.literal"); - } - - @Nonnull - public String getFamilyName() { - return getName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.intnReplaceOctalNumericLiteral(); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiElement numericLiteralExpression = descriptor.getPsiElement(); - if (numericLiteralExpression instanceof PyNumericLiteralExpression) { - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - String text = numericLiteralExpression.getText(); - numericLiteralExpression.replace(elementGenerator.createExpressionFromText("0o" + text.substring(1))); + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement numericLiteralExpression = descriptor.getPsiElement(); + if (numericLiteralExpression instanceof PyNumericLiteralExpression) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + String text = numericLiteralExpression.getText(); + numericLiteralExpression.replace(elementGenerator.createExpressionFromText("0o" + text.substring(1))); + } } - } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceRaiseStatementQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceRaiseStatementQuickFix.java index 342103f7..bd5457cc 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceRaiseStatementQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/ReplaceRaiseStatementQuickFix.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyExpression; @@ -32,30 +32,31 @@ * @author Alexey.Ivanov */ public class ReplaceRaiseStatementQuickFix implements LocalQuickFix { - @Nonnull - @Override - public String getName() { - return PyBundle.message("INTN.replace.raise.statement"); - } - - @Nonnull - public String getFamilyName() { - return getName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.intnReplaceRaiseStatement(); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiElement raiseStatement = descriptor.getPsiElement(); - if (raiseStatement instanceof PyRaiseStatement) { - PyExpression[] expressions = ((PyRaiseStatement)raiseStatement).getExpressions(); - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - String newExpressionText = expressions[0].getText() + "(" + expressions[1].getText() + ")"; - if (expressions.length == 2) { - raiseStatement.replace(elementGenerator.createFromText(LanguageLevel.forElement(raiseStatement), PyRaiseStatement.class, "raise " + newExpressionText)); - } else if (expressions.length == 3) { - raiseStatement.replace(elementGenerator.createFromText(LanguageLevel.forElement(raiseStatement), PyRaiseStatement.class, - "raise " + newExpressionText + ".with_traceback(" + expressions[2].getText() + ")")); - } + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement raiseStatement = descriptor.getPsiElement(); + if (raiseStatement instanceof PyRaiseStatement) { + PyExpression[] expressions = ((PyRaiseStatement) raiseStatement).getExpressions(); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + String newExpressionText = expressions[0].getText() + "(" + expressions[1].getText() + ")"; + if (expressions.length == 2) { + raiseStatement.replace(elementGenerator.createFromText( + LanguageLevel.forElement(raiseStatement), + PyRaiseStatement.class, + "raise " + newExpressionText + )); + } + else if (expressions.length == 3) { + raiseStatement.replace(elementGenerator.createFromText(LanguageLevel.forElement(raiseStatement), PyRaiseStatement.class, + "raise " + newExpressionText + ".with_traceback(" + expressions[2].getText() + ")" + )); + } + } } - } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/SimplifyBooleanCheckQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/SimplifyBooleanCheckQuickFix.java index 3c68e639..e2ec3b62 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/SimplifyBooleanCheckQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/SimplifyBooleanCheckQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; @@ -23,7 +24,6 @@ import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.ast.TokenSet; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyBinaryExpression; @@ -31,10 +31,8 @@ import com.jetbrains.python.psi.PyExpression; /** - * Created by IntelliJ IDEA. - * Author: Alexey.Ivanov - * Date: 17.03.2010 - * Time: 19:41:07 + * @author Alexey.Ivanov + * @since 2010-03-17 */ public class SimplifyBooleanCheckQuickFix implements LocalQuickFix { private String myReplacementText; @@ -60,13 +58,9 @@ private static boolean isEmpty(PyExpression expression) { } @Nonnull - public String getName() { - return PyBundle.message("QFIX.simplify.$0", myReplacementText); - } - - @Nonnull - public String getFamilyName() { - return "Simplify boolean expression"; + @Override + public LocalizeValue getName() { + return PyLocalize.qfixSimplify$0(myReplacementText); } public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectDocstringQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectDocstringQuickFix.java index 5e7617c5..24ff3d4e 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectDocstringQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectDocstringQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; @@ -23,44 +24,38 @@ import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.*; /** - * User: catherine - * * QuickFix to move misplaced docstring + * + * @author catherine */ public class StatementEffectDocstringQuickFix implements LocalQuickFix { - @Nonnull - public String getName() { - return PyBundle.message("QFIX.statement.effect.move.docstring"); - } - - @Nonnull - public String getFamilyName() { - return getName(); - } - - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiElement expression = descriptor.getPsiElement(); - if (expression instanceof PyStringLiteralExpression) { - PyStatement st = PsiTreeUtil.getParentOfType(expression, PyStatement.class); - if (st != null) { - PyDocStringOwner parent = PsiTreeUtil.getParentOfType(expression, PyDocStringOwner.class); + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixStatementEffectMoveDocstring(); + } - if (parent instanceof PyClass || parent instanceof PyFunction) { - PyStatementList statementList = PsiTreeUtil.findChildOfType(parent, PyStatementList.class); - if (statementList != null) { - PyStatement[] statements = statementList.getStatements(); - if (statements.length > 0) { - statementList.addBefore(st, statements[0]); - st.delete(); + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElement expression = descriptor.getPsiElement(); + if (expression instanceof PyStringLiteralExpression) { + PyStatement st = PsiTreeUtil.getParentOfType(expression, PyStatement.class); + if (st != null) { + PyDocStringOwner parent = PsiTreeUtil.getParentOfType(expression, PyDocStringOwner.class); + + if (parent instanceof PyClass || parent instanceof PyFunction) { + PyStatementList statementList = PsiTreeUtil.findChildOfType(parent, PyStatementList.class); + if (statementList != null) { + PyStatement[] statements = statementList.getStatements(); + if (statements.length > 0) { + statementList.addBefore(st, statements[0]); + st.delete(); + } + } + } } - } } - } } - } - } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectIntroduceVariableQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectIntroduceVariableQuickFix.java index 0d7f672d..5deddffd 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectIntroduceVariableQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/StatementEffectIntroduceVariableQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.CodeInsightUtilCore; @@ -25,42 +26,39 @@ import consulo.language.editor.inspection.ProblemDescriptor; import consulo.project.Project; import consulo.language.psi.PsiElement; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.*; -import org.jetbrains.annotations.NonNls; /** - * User: catherine - * * Quickfix to introduce variable if statement seems to have no effect + * + * @author catherine */ public class StatementEffectIntroduceVariableQuickFix implements LocalQuickFix { - @Nonnull - public String getName() { - return PyBundle.message("QFIX.introduce.variable"); - } - - @NonNls - @Nonnull - public String getFamilyName() { - return getName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixIntroduceVariable(); + } - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { - PsiElement expression = descriptor.getPsiElement(); - if (expression != null && expression.isValid()) { - final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - final PyAssignmentStatement assignment = elementGenerator.createFromText(LanguageLevel.forElement(expression), PyAssignmentStatement.class, - "var = " + expression.getText()); + public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + PsiElement expression = descriptor.getPsiElement(); + if (expression != null && expression.isValid()) { + final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + final PyAssignmentStatement assignment = + elementGenerator.createFromText(LanguageLevel.forElement(expression), PyAssignmentStatement.class, + "var = " + expression.getText() + ); - expression = expression.replace(assignment); - if (expression == null) return; - expression = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(expression); - final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(expression); - final PyExpression leftHandSideExpression = ((PyAssignmentStatement)expression).getLeftHandSideExpression(); - assert leftHandSideExpression != null; - builder.replaceElement(leftHandSideExpression, "var"); - builder.run(); + expression = expression.replace(assignment); + if (expression == null) { + return; + } + expression = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(expression); + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(expression); + final PyExpression leftHandSideExpression = ((PyAssignmentStatement) expression).getLeftHandSideExpression(); + assert leftHandSideExpression != null; + builder.replaceElement(leftHandSideExpression, "var"); + builder.run(); + } } - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/TransformClassicClassQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/TransformClassicClassQuickFix.java index 3964d981..2e9ded82 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/TransformClassicClassQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/TransformClassicClassQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.inspection.LocalQuickFix; @@ -23,7 +24,6 @@ import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyClass; @@ -31,20 +31,13 @@ import com.jetbrains.python.psi.PyExpression; /** - * Created by IntelliJ IDEA. - * User: Alexey.Ivanov - * Date: 03.03.2010 - * Time: 16:49:59 + * @author Alexey.Ivanov + * @since 2010-03-03 */ public class TransformClassicClassQuickFix implements LocalQuickFix { @Nonnull - public String getName() { - return PyBundle.message("QFIX.classic.class.transform"); - } - - @Nonnull - public String getFamilyName() { - return getName(); + public LocalizeValue getName() { + return PyLocalize.qfixClassicClassTransform(); } public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedRefCreateFunctionQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedRefCreateFunctionQuickFix.java index 3217b724..37e68c6f 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedRefCreateFunctionQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedRefCreateFunctionQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.CodeInsightUtilCore; @@ -27,92 +28,94 @@ import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.impl.psi.impl.ParamHelper; import com.jetbrains.python.impl.psi.impl.PyFunctionBuilder; /** - * User: catherine - * * QuickFix to create function to unresolved unqualified reference + * + * @author catherine */ public class UnresolvedRefCreateFunctionQuickFix implements LocalQuickFix { - private PyCallExpression myElement; - private PyReferenceExpression myReference; - - public UnresolvedRefCreateFunctionQuickFix(PyCallExpression element, PyReferenceExpression reference) { - myElement = element; - myReference = reference; - } + private PyCallExpression myElement; + private PyReferenceExpression myReference; - @Nonnull - public String getName() { - return PyBundle.message("QFIX.unresolved.reference.create.function.$0", myReference.getText()); - } + public UnresolvedRefCreateFunctionQuickFix(PyCallExpression element, PyReferenceExpression reference) { + myElement = element; + myReference = reference; + } - @Nonnull - public String getFamilyName() { - return getName(); - } + @Nonnull + @Override + public LocalizeValue getName() { + return PyLocalize.qfixUnresolvedReferenceCreateFunction$0(myReference.getText()); + } - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - if (!myElement.isValid() || !FileModificationService.getInstance().preparePsiElementForWrite(myElement)) return; + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + if (!myElement.isValid() || !FileModificationService.getInstance().preparePsiElementForWrite(myElement)) { + return; + } - PyFunctionBuilder functionBuilder = new PyFunctionBuilder(myReference.getText()); + PyFunctionBuilder functionBuilder = new PyFunctionBuilder(myReference.getText()); - // if function is actually an argument of a call, don't use other arguments of the call to create parameter list of new function - final PyArgumentList argumentList = myElement.getArgumentList(); - if (argumentList != null && !PsiTreeUtil.isAncestor(argumentList, myReference, false)) { - for (PyExpression param : argumentList.getArguments()) { - if (param instanceof PyKeywordArgument) { - functionBuilder.parameter(((PyKeywordArgument)param).getKeyword()); - } - else if (param instanceof PyReferenceExpression) { - PyReferenceExpression refex = (PyReferenceExpression)param; - functionBuilder.parameter(refex.getReferencedName()); + // if function is actually an argument of a call, don't use other arguments of the call to create parameter list of new function + final PyArgumentList argumentList = myElement.getArgumentList(); + if (argumentList != null && !PsiTreeUtil.isAncestor(argumentList, myReference, false)) { + for (PyExpression param : argumentList.getArguments()) { + if (param instanceof PyKeywordArgument) { + functionBuilder.parameter(((PyKeywordArgument) param).getKeyword()); + } + else if (param instanceof PyReferenceExpression) { + PyReferenceExpression refex = (PyReferenceExpression) param; + functionBuilder.parameter(refex.getReferencedName()); + } + else { + functionBuilder.parameter("param"); + } + } } else { - functionBuilder.parameter("param"); + functionBuilder.parameter("args"); } - } - } - else { - functionBuilder.parameter("args"); - } - PyFunction function = functionBuilder.buildFunction(project, LanguageLevel.getDefault()); - PyFunction parentFunction = PsiTreeUtil.getTopmostParentOfType(myElement, PyFunction.class); - if (parentFunction != null ) { - PyClass parentClass = PsiTreeUtil.getTopmostParentOfType(parentFunction, PyClass.class); - if (parentClass != null) { - PsiElement parent = parentClass.getParent(); - function = (PyFunction)parent.addBefore(function, parentClass); - } else { - PsiElement parent = parentFunction.getParent(); - function = (PyFunction)parent.addBefore(function, parentFunction); - } - } else { - PyStatement statement = PsiTreeUtil.getTopmostParentOfType(myElement, - PyStatement.class); - if (statement != null) { - PsiElement parent = statement.getParent(); - if (parent != null) - function = (PyFunction)parent.addBefore(function, statement); - } - } - function = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(function); - final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(function); - ParamHelper.walkDownParamArray( - function.getParameterList().getParameters(), - new ParamHelper.ParamVisitor() { - public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) { - builder.replaceElement(param, param.getName()); + PyFunction function = functionBuilder.buildFunction(project, LanguageLevel.getDefault()); + PyFunction parentFunction = PsiTreeUtil.getTopmostParentOfType(myElement, PyFunction.class); + if (parentFunction != null) { + PyClass parentClass = PsiTreeUtil.getTopmostParentOfType(parentFunction, PyClass.class); + if (parentClass != null) { + PsiElement parent = parentClass.getParent(); + function = (PyFunction) parent.addBefore(function, parentClass); + } + else { + PsiElement parent = parentFunction.getParent(); + function = (PyFunction) parent.addBefore(function, parentFunction); + } } - } - ); - builder.replaceElement(function.getStatementList(), PyNames.PASS); - builder.run(); - } + else { + PyStatement statement = PsiTreeUtil.getTopmostParentOfType( + myElement, + PyStatement.class + ); + if (statement != null) { + PsiElement parent = statement.getParent(); + if (parent != null) { + function = (PyFunction) parent.addBefore(function, statement); + } + } + } + function = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(function); + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(function); + ParamHelper.walkDownParamArray( + function.getParameterList().getParameters(), + new ParamHelper.ParamVisitor() { + public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) { + builder.replaceElement(param, param.getName()); + } + } + ); + builder.replaceElement(function.getStatementList(), PyNames.PASS); + builder.run(); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddParameterQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddParameterQuickFix.java index dcfd9383..20020f70 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddParameterQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddParameterQuickFix.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; +import consulo.localize.LocalizeValue; +import consulo.python.impl.localize.PyLocalize; import jakarta.annotation.Nonnull; import consulo.language.editor.CodeInsightUtilCore; @@ -27,16 +28,15 @@ import consulo.document.util.TextRange; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.PyParameterList; /** - * User: ktisha - * * QuickFix to add parameter to unresolved reference + * + * @author ktisha */ public class UnresolvedReferenceAddParameterQuickFix implements LocalQuickFix { private String myName; @@ -45,13 +45,9 @@ public UnresolvedReferenceAddParameterQuickFix(String name) { } @Nonnull - public String getName() { - return PyBundle.message("QFIX.unresolved.reference.add.param.$0", myName); - } - - @Nonnull - public String getFamilyName() { - return PyBundle.message("QFIX.unresolved.reference.add.param"); + @Override + public LocalizeValue getName() { + return PyLocalize.qfixUnresolvedReferenceAddParam$0(myName); } public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddSelfQuickFix.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddSelfQuickFix.java index ea20ae3c..c6d67ece 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddSelfQuickFix.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/quickfix/UnresolvedReferenceAddSelfQuickFix.java @@ -13,58 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.inspections.quickfix; -import jakarta.annotation.Nonnull; - -import consulo.language.editor.FileModificationService; -import consulo.language.editor.intention.HighPriorityAction; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; -import com.jetbrains.python.impl.PyBundle; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyReferenceExpression; +import consulo.annotation.access.RequiredReadAction; +import consulo.language.editor.FileModificationService; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.editor.intention.HighPriorityAction; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.python.impl.localize.PyLocalize; +import jakarta.annotation.Nonnull; /** - * User: catherine - *

    * QuickFix to add self to unresolved reference + * + * @author catherine */ -public class UnresolvedReferenceAddSelfQuickFix implements LocalQuickFix, HighPriorityAction -{ - private PyReferenceExpression myElement; - private String myQualifier; - - public UnresolvedReferenceAddSelfQuickFix(@Nonnull final PyReferenceExpression element, @Nonnull final String qualifier) - { - myElement = element; - myQualifier = qualifier; - } - - @Nonnull - public String getName() - { - return PyBundle.message("QFIX.unresolved.reference", myElement.getText(), myQualifier); - } - - @Nonnull - public String getFamilyName() - { - return "Add qualifier"; - } - - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - if(!FileModificationService.getInstance().preparePsiElementForWrite(myElement)) - { - return; - } - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(myElement), myQualifier + "." + myElement.getText()); - myElement.replace(expression); - } +public class UnresolvedReferenceAddSelfQuickFix implements LocalQuickFix, HighPriorityAction { + private PyReferenceExpression myElement; + private String myQualifier; + + public UnresolvedReferenceAddSelfQuickFix(@Nonnull final PyReferenceExpression element, @Nonnull final String qualifier) { + myElement = element; + myQualifier = qualifier; + } + + @Nonnull + @Override + @RequiredReadAction + public LocalizeValue getName() { + return PyLocalize.qfixUnresolvedReference(myElement.getText(), myQualifier); + } + + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(myElement)) { + return; + } + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + PyExpression expression = + elementGenerator.createExpressionFromText(LanguageLevel.forElement(myElement), myQualifier + "." + myElement.getText()); + myElement.replace(expression); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index 2268cb96..ca118d9b 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -70,1116 +70,1156 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.language.psi.util.QualifiedName; import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.module.Module; +import consulo.python.impl.localize.PyLocalize; import consulo.util.collection.ContainerUtil; import consulo.util.collection.SmartList; import consulo.util.dataholder.Key; import consulo.util.lang.Comparing; import consulo.util.lang.Pair; -import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import static com.jetbrains.python.impl.inspections.quickfix.AddIgnoredIdentifierQuickFix.END_WILDCARD; /** * Marks references that fail to resolve. Also tracks unused imports and provides "optimize imports" support. - * User: dcheryasov - * Date: Nov 15, 2008 + * @author dcheryasov + * @since 2008-11-15 */ @ExtensionImpl public class PyUnresolvedReferencesInspection extends PyInspection { - private static final Key KEY = Key.create("PyUnresolvedReferencesInspection.Visitor"); - public static final Key SHORT_NAME_KEY = - Key.create(PyUnresolvedReferencesInspection.class.getSimpleName()); - - public static PyUnresolvedReferencesInspection getInstance(PsiElement element) { - final InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(element.getProject()).getInspectionProfile(); - return (PyUnresolvedReferencesInspection)inspectionProfile.getUnwrappedTool(SHORT_NAME_KEY.toString(), element); - } - - @Nls - @Nonnull - public String getDisplayName() { - return PyBundle.message("INSP.NAME.unresolved.refs"); - } - - @Nonnull - @Override - public InspectionToolState createStateProvider() { - return new PyUnresolvedReferencesInspectionState(); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, - final boolean isOnTheFly, - @Nonnull final LocalInspectionToolSession session, - Object state) { - PyUnresolvedReferencesInspectionState inspectionState = (PyUnresolvedReferencesInspectionState)state; - - final Visitor visitor = new Visitor(holder, session, inspectionState.ignoredIdentifiers); - // buildVisitor() will be called on injected files in the same session - don't overwrite if we already have one - final Visitor existingVisitor = session.getUserData(KEY); - if (existingVisitor == null) { - session.putUserData(KEY, visitor); + private static final Key KEY = Key.create("PyUnresolvedReferencesInspection.Visitor"); + public static final Key SHORT_NAME_KEY = + Key.create(PyUnresolvedReferencesInspection.class.getSimpleName()); + + public static PyUnresolvedReferencesInspection getInstance(PsiElement element) { + final InspectionProfile inspectionProfile = + InspectionProjectProfileManager.getInstance(element.getProject()).getInspectionProfile(); + return (PyUnresolvedReferencesInspection) inspectionProfile.getUnwrappedTool(SHORT_NAME_KEY.toString(), element); } - return visitor; - } - - @Override - public void inspectionFinished(@Nonnull LocalInspectionToolSession session, @Nonnull ProblemsHolder holder, Object state) { - final Visitor visitor = session.getUserData(KEY); - assert visitor != null; - if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) { - visitor.highlightUnusedImports(); + + @Nonnull + @Override + public LocalizeValue getDisplayName() { + return PyLocalize.inspNameUnresolvedRefs(); } - session.putUserData(KEY, null); - } - - public static class Visitor extends PyInspectionVisitor { - private Set myUsedImports = Collections.synchronizedSet(new HashSet()); - private Set myAllImports = Collections.synchronizedSet(new HashSet()); - private final ImmutableSet myIgnoredIdentifiers; - private volatile Boolean myIsEnabled = null; - - public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session, List ignoredIdentifiers) { - super(holder, session); - myIgnoredIdentifiers = ImmutableSet.copyOf(ignoredIdentifiers); + + @Nonnull + @Override + public InspectionToolState createStateProvider() { + return new PyUnresolvedReferencesInspectionState(); } - public boolean isEnabled(@Nonnull PsiElement anchor) { - if (myIsEnabled == null) { - if (PySkeletonRefresher.isGeneratingSkeletons()) { - myIsEnabled = false; - } - else { - myIsEnabled = true; + @Nonnull + @Override + public PsiElementVisitor buildVisitor( + @Nonnull ProblemsHolder holder, + final boolean isOnTheFly, + @Nonnull final LocalInspectionToolSession session, + Object state + ) { + PyUnresolvedReferencesInspectionState inspectionState = (PyUnresolvedReferencesInspectionState) state; + + final Visitor visitor = new Visitor(holder, session, inspectionState.ignoredIdentifiers); + // buildVisitor() will be called on injected files in the same session - don't overwrite if we already have one + final Visitor existingVisitor = session.getUserData(KEY); + if (existingVisitor == null) { + session.putUserData(KEY, visitor); } - } - return myIsEnabled; + return visitor; } @Override - public void visitPyTargetExpression(PyTargetExpression node) { - checkSlotsAndProperties(node); + public void inspectionFinished(@Nonnull LocalInspectionToolSession session, @Nonnull ProblemsHolder holder, Object state) { + final Visitor visitor = session.getUserData(KEY); + assert visitor != null; + if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) { + visitor.highlightUnusedImports(); + } + session.putUserData(KEY, null); } - private void checkSlotsAndProperties(PyQualifiedExpression node) { - final PyExpression qualifier = node.getQualifier(); - if (qualifier != null) { - final PyType type = myTypeEvalContext.getType(qualifier); - if (type instanceof PyClassType) { - final PyClass pyClass = ((PyClassType)type).getPyClass(); - if (pyClass.isNewStyleClass(myTypeEvalContext)) { - if (pyClass.getOwnSlots() == null) { - return; - } - final String attrName = node.getReferencedName(); - if (!canHaveAttribute(pyClass, attrName)) { - for (PyClass ancestor : pyClass.getAncestorClasses(myTypeEvalContext)) { - if (ancestor == null) { - return; - } - if (PyNames.OBJECT.equals(ancestor.getName())) { - break; + public static class Visitor extends PyInspectionVisitor { + private Set myUsedImports = Collections.synchronizedSet(new HashSet()); + private Set myAllImports = Collections.synchronizedSet(new HashSet()); + private final ImmutableSet myIgnoredIdentifiers; + private volatile Boolean myIsEnabled = null; + + public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session, List ignoredIdentifiers) { + super(holder, session); + myIgnoredIdentifiers = ImmutableSet.copyOf(ignoredIdentifiers); + } + + public boolean isEnabled(@Nonnull PsiElement anchor) { + if (myIsEnabled == null) { + if (PySkeletonRefresher.isGeneratingSkeletons()) { + myIsEnabled = false; } - if (canHaveAttribute(ancestor, attrName)) { - return; + else { + myIsEnabled = true; } - } - final ASTNode nameNode = node.getNameElement(); - final PsiElement e = nameNode != null ? nameNode.getPsi() : node; - registerProblem(e, "'" + pyClass.getName() + "' object has no attribute '" + attrName + "'"); } - } + return myIsEnabled; } - } - } - private boolean canHaveAttribute(@Nonnull PyClass cls, @Nullable String attrName) { - final List slots = cls.getOwnSlots(); + @Override + public void visitPyTargetExpression(PyTargetExpression node) { + checkSlotsAndProperties(node); + } - // Class instance can contain attributes with arbitrary names - if (slots == null || slots.contains(PyNames.DICT)) { - return true; - } + private void checkSlotsAndProperties(PyQualifiedExpression node) { + final PyExpression qualifier = node.getQualifier(); + if (qualifier != null) { + final PyType type = myTypeEvalContext.getType(qualifier); + if (type instanceof PyClassType) { + final PyClass pyClass = ((PyClassType) type).getPyClass(); + if (pyClass.isNewStyleClass(myTypeEvalContext)) { + if (pyClass.getOwnSlots() == null) { + return; + } + final String attrName = node.getReferencedName(); + if (!canHaveAttribute(pyClass, attrName)) { + for (PyClass ancestor : pyClass.getAncestorClasses(myTypeEvalContext)) { + if (ancestor == null) { + return; + } + if (PyNames.OBJECT.equals(ancestor.getName())) { + break; + } + if (canHaveAttribute(ancestor, attrName)) { + return; + } + } + final ASTNode nameNode = node.getNameElement(); + final PsiElement e = nameNode != null ? nameNode.getPsi() : node; + registerProblem(e, "'" + pyClass.getName() + "' object has no attribute '" + attrName + "'"); + } + } + } + } + } - if (attrName != null && cls.findClassAttribute(attrName, true, myTypeEvalContext) != null) { - return true; - } + private boolean canHaveAttribute(@Nonnull PyClass cls, @Nullable String attrName) { + final List slots = cls.getOwnSlots(); - return slots.contains(attrName) || cls.getProperties().containsKey(attrName); - } + // Class instance can contain attributes with arbitrary names + if (slots == null || slots.contains(PyNames.DICT)) { + return true; + } - @Override - public void visitPyImportElement(PyImportElement node) { - super.visitPyImportElement(node); - final PyFromImportStatement fromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class); - if (isEnabled(node) && (fromImport == null || !fromImport.isFromFuture())) { - myAllImports.add(node); - } - } + if (attrName != null && cls.findClassAttribute(attrName, true, myTypeEvalContext) != null) { + return true; + } - @Override - public void visitPyStarImportElement(PyStarImportElement node) { - super.visitPyStarImportElement(node); - if (isEnabled(node)) { - myAllImports.add(node); - } - } + return slots.contains(attrName) || cls.getProperties().containsKey(attrName); + } - @Nullable - private static PyExceptPart getImportErrorGuard(PyElement node) { - final PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class); - if (importStatement != null) { - final PyTryPart tryPart = PsiTreeUtil.getParentOfType(node, PyTryPart.class); - if (tryPart != null) { - final PyTryExceptStatement tryExceptStatement = PsiTreeUtil.getParentOfType(tryPart, PyTryExceptStatement.class); - if (tryExceptStatement != null) { - for (PyExceptPart exceptPart : tryExceptStatement.getExceptParts()) { - final PyExpression expr = exceptPart.getExceptClass(); - if (expr != null && "ImportError".equals(expr.getName())) { - return exceptPart; - } - } - } + @Override + public void visitPyImportElement(PyImportElement node) { + super.visitPyImportElement(node); + final PyFromImportStatement fromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class); + if (isEnabled(node) && (fromImport == null || !fromImport.isFromFuture())) { + myAllImports.add(node); + } } - } - return null; - } - private static boolean isGuardedByHasattr(@Nonnull final PyElement node, @Nonnull final String name) { - final String nodeName = node.getName(); - if (nodeName != null) { - final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(node, nodeName); - PyElement e = PsiTreeUtil.getParentOfType(node, PyConditionalStatementPart.class, PyConditionalExpression.class); - while (e != null && PsiTreeUtil.isAncestor(owner, e, true)) { - final ArrayList calls = new ArrayList<>(); - PyExpression cond = null; - if (e instanceof PyConditionalStatementPart) { - cond = ((PyConditionalStatementPart)e).getCondition(); - } - else if (e instanceof PyConditionalExpression && PsiTreeUtil.isAncestor(((PyConditionalExpression)e).getTruePart(), node, true)) { - cond = ((PyConditionalExpression)e).getCondition(); - } - if (cond instanceof PyCallExpression) { - calls.add((PyCallExpression)cond); - } - if (cond != null) { - final PyCallExpression[] callExpressions = PsiTreeUtil.getChildrenOfType(cond, PyCallExpression.class); - if (callExpressions != null) { - calls.addAll(Arrays.asList(callExpressions)); - } - for (PyCallExpression call : calls) { - final PyExpression callee = call.getCallee(); - final PyExpression[] args = call.getArguments(); - // TODO: Search for `node` aliases using aliases analysis - if (callee != null && "hasattr".equals(callee.getName()) && args.length == 2 && - nodeName.equals(args[0].getName()) && args[1] instanceof PyStringLiteralExpression && - ((PyStringLiteralExpression)args[1]).getStringValue().equals(name)) { - return true; - } + @Override + public void visitPyStarImportElement(PyStarImportElement node) { + super.visitPyStarImportElement(node); + if (isEnabled(node)) { + myAllImports.add(node); } - } - e = PsiTreeUtil.getParentOfType(e, PyConditionalStatementPart.class); } - } - return false; - } - @Override - public void visitComment(PsiComment comment) { - super.visitComment(comment); - if (comment instanceof PsiLanguageInjectionHost) { - processInjection((PsiLanguageInjectionHost)comment); - } - } + @Nullable + private static PyExceptPart getImportErrorGuard(PyElement node) { + final PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class); + if (importStatement != null) { + final PyTryPart tryPart = PsiTreeUtil.getParentOfType(node, PyTryPart.class); + if (tryPart != null) { + final PyTryExceptStatement tryExceptStatement = PsiTreeUtil.getParentOfType(tryPart, PyTryExceptStatement.class); + if (tryExceptStatement != null) { + for (PyExceptPart exceptPart : tryExceptStatement.getExceptParts()) { + final PyExpression expr = exceptPart.getExceptClass(); + if (expr != null && "ImportError".equals(expr.getName())) { + return exceptPart; + } + } + } + } + } + return null; + } - @Override - public void visitPyElement(final PyElement node) { - super.visitPyElement(node); - final PsiFile file = node.getContainingFile(); - final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(node.getProject()); - if (injectedLanguageManager.isInjectedFragment(file)) { - final PsiLanguageInjectionHost host = injectedLanguageManager.getInjectionHost(node); - processInjection(host); - } - if (node instanceof PyReferenceOwner) { - final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext); - processReference(node, ((PyReferenceOwner)node).getReference(resolveContext)); - } - else { - if (node instanceof PsiLanguageInjectionHost) { - processInjection((PsiLanguageInjectionHost)node); + private static boolean isGuardedByHasattr(@Nonnull final PyElement node, @Nonnull final String name) { + final String nodeName = node.getName(); + if (nodeName != null) { + final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(node, nodeName); + PyElement e = PsiTreeUtil.getParentOfType(node, PyConditionalStatementPart.class, PyConditionalExpression.class); + while (e != null && PsiTreeUtil.isAncestor(owner, e, true)) { + final ArrayList calls = new ArrayList<>(); + PyExpression cond = null; + if (e instanceof PyConditionalStatementPart) { + cond = ((PyConditionalStatementPart) e).getCondition(); + } + else if (e instanceof PyConditionalExpression && PsiTreeUtil.isAncestor( + ((PyConditionalExpression) e).getTruePart(), + node, + true + )) { + cond = ((PyConditionalExpression) e).getCondition(); + } + if (cond instanceof PyCallExpression) { + calls.add((PyCallExpression) cond); + } + if (cond != null) { + final PyCallExpression[] callExpressions = PsiTreeUtil.getChildrenOfType(cond, PyCallExpression.class); + if (callExpressions != null) { + calls.addAll(Arrays.asList(callExpressions)); + } + for (PyCallExpression call : calls) { + final PyExpression callee = call.getCallee(); + final PyExpression[] args = call.getArguments(); + // TODO: Search for `node` aliases using aliases analysis + if (callee != null && "hasattr".equals(callee.getName()) && args.length == 2 && + nodeName.equals(args[0].getName()) && args[1] instanceof PyStringLiteralExpression && + ((PyStringLiteralExpression) args[1]).getStringValue().equals(name)) { + return true; + } + } + } + e = PsiTreeUtil.getParentOfType(e, PyConditionalStatementPart.class); + } + } + return false; } - for (final PsiReference reference : node.getReferences()) { - processReference(node, reference); + + @Override + public void visitComment(PsiComment comment) { + super.visitComment(comment); + if (comment instanceof PsiLanguageInjectionHost) { + processInjection((PsiLanguageInjectionHost) comment); + } } - } - } - private void processInjection(@Nullable PsiLanguageInjectionHost node) { - if (node == null) { - return; - } - final List> files = InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node); - if (files != null) { - for (Pair pair : files) { - new PyRecursiveElementVisitor() { - @Override - public void visitPyElement(PyElement element) { - super.visitPyElement(element); - if (element instanceof PyReferenceOwner) { + @Override + public void visitPyElement(final PyElement node) { + super.visitPyElement(node); + final PsiFile file = node.getContainingFile(); + final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(node.getProject()); + if (injectedLanguageManager.isInjectedFragment(file)) { + final PsiLanguageInjectionHost host = injectedLanguageManager.getInjectionHost(node); + processInjection(host); + } + if (node instanceof PyReferenceOwner) { final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext); - final PsiPolyVariantReference reference = ((PyReferenceOwner)element).getReference(resolveContext); - markTargetImportsAsUsed(reference); - } + processReference(node, ((PyReferenceOwner) node).getReference(resolveContext)); + } + else { + if (node instanceof PsiLanguageInjectionHost) { + processInjection((PsiLanguageInjectionHost) node); + } + for (final PsiReference reference : node.getReferences()) { + processReference(node, reference); + } } - }.visitElement(pair.getFirst()); } - } - } - private void markTargetImportsAsUsed(@Nonnull PsiPolyVariantReference reference) { - final ResolveResult[] resolveResults = reference.multiResolve(false); - for (ResolveResult resolveResult : resolveResults) { - if (resolveResult instanceof ImportedResolveResult) { - final PyImportedNameDefiner definer = ((ImportedResolveResult)resolveResult).getDefiner(); - if (definer != null) { - myUsedImports.add(definer); - } + private void processInjection(@Nullable PsiLanguageInjectionHost node) { + if (node == null) { + return; + } + final List> files = + InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node); + if (files != null) { + for (Pair pair : files) { + new PyRecursiveElementVisitor() { + @Override + public void visitPyElement(PyElement element) { + super.visitPyElement(element); + if (element instanceof PyReferenceOwner) { + final PyResolveContext resolveContext = + PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext); + final PsiPolyVariantReference reference = ((PyReferenceOwner) element).getReference(resolveContext); + markTargetImportsAsUsed(reference); + } + } + }.visitElement(pair.getFirst()); + } + } } - } - } - private void processReference(PyElement node, @Nullable PsiReference reference) { - if (!isEnabled(node) || reference == null || reference.isSoft()) { - return; - } - HighlightSeverity severity = HighlightSeverity.ERROR; - if (reference instanceof PsiReferenceEx) { - severity = ((PsiReferenceEx)reference).getUnresolvedHighlightSeverity(myTypeEvalContext); - if (severity == null) { - return; - } - } - PyExceptPart guard = getImportErrorGuard(node); - if (guard != null) { - processReferenceInImportGuard(node, guard); - return; - } - if (node instanceof PyQualifiedExpression) { - final PyQualifiedExpression qExpr = (PyQualifiedExpression)node; - final PyExpression qualifier = qExpr.getQualifier(); - final String name = node.getName(); - if (qualifier != null && name != null && isGuardedByHasattr(qualifier, name)) { - return; - } - } - PsiElement target = null; - boolean unresolved; - if (reference instanceof PsiPolyVariantReference) { - final PsiPolyVariantReference poly = (PsiPolyVariantReference)reference; - final ResolveResult[] resolveResults = poly.multiResolve(false); - unresolved = (resolveResults.length == 0); - for (ResolveResult resolveResult : resolveResults) { - if (target == null && resolveResult.isValidResult()) { - target = resolveResult.getElement(); - } - if (resolveResult instanceof ImportedResolveResult) { - final PyImportedNameDefiner definer = ((ImportedResolveResult)resolveResult).getDefiner(); - if (definer != null) { - myUsedImports.add(definer); - } - } - } - } - else { - target = reference.resolve(); - unresolved = (target == null); - } - if (unresolved) { - boolean ignoreUnresolved = false; - for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) { - if (extension.ignoreUnresolvedReference(node, reference)) { - ignoreUnresolved = true; - break; - } - } - if (!ignoreUnresolved) { - registerUnresolvedReferenceProblem(node, reference, severity); + private void markTargetImportsAsUsed(@Nonnull PsiPolyVariantReference reference) { + final ResolveResult[] resolveResults = reference.multiResolve(false); + for (ResolveResult resolveResult : resolveResults) { + if (resolveResult instanceof ImportedResolveResult) { + final PyImportedNameDefiner definer = ((ImportedResolveResult) resolveResult).getDefiner(); + if (definer != null) { + myUsedImports.add(definer); + } + } + } } - // don't highlight unresolved imports as unused - if (node.getParent() instanceof PyImportElement) { - myAllImports.remove(node.getParent()); + + private void processReference(PyElement node, @Nullable PsiReference reference) { + if (!isEnabled(node) || reference == null || reference.isSoft()) { + return; + } + HighlightSeverity severity = HighlightSeverity.ERROR; + if (reference instanceof PsiReferenceEx) { + severity = ((PsiReferenceEx) reference).getUnresolvedHighlightSeverity(myTypeEvalContext); + if (severity == null) { + return; + } + } + PyExceptPart guard = getImportErrorGuard(node); + if (guard != null) { + processReferenceInImportGuard(node, guard); + return; + } + if (node instanceof PyQualifiedExpression) { + final PyQualifiedExpression qExpr = (PyQualifiedExpression) node; + final PyExpression qualifier = qExpr.getQualifier(); + final String name = node.getName(); + if (qualifier != null && name != null && isGuardedByHasattr(qualifier, name)) { + return; + } + } + PsiElement target = null; + boolean unresolved; + if (reference instanceof PsiPolyVariantReference) { + final PsiPolyVariantReference poly = (PsiPolyVariantReference) reference; + final ResolveResult[] resolveResults = poly.multiResolve(false); + unresolved = (resolveResults.length == 0); + for (ResolveResult resolveResult : resolveResults) { + if (target == null && resolveResult.isValidResult()) { + target = resolveResult.getElement(); + } + if (resolveResult instanceof ImportedResolveResult) { + final PyImportedNameDefiner definer = ((ImportedResolveResult) resolveResult).getDefiner(); + if (definer != null) { + myUsedImports.add(definer); + } + } + } + } + else { + target = reference.resolve(); + unresolved = (target == null); + } + if (unresolved) { + boolean ignoreUnresolved = false; + for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) { + if (extension.ignoreUnresolvedReference(node, reference)) { + ignoreUnresolved = true; + break; + } + } + if (!ignoreUnresolved) { + registerUnresolvedReferenceProblem(node, reference, severity); + } + // don't highlight unresolved imports as unused + if (node.getParent() instanceof PyImportElement) { + myAllImports.remove(node.getParent()); + } + } + else if (reference instanceof PyImportReference && + target == reference.getElement().getContainingFile() && + !isContainingFileImportAllowed(node, (PsiFile) target)) { + registerProblem(node, "Import resolves to its containing file"); + } } - } - else if (reference instanceof PyImportReference && - target == reference.getElement().getContainingFile() && - !isContainingFileImportAllowed(node, (PsiFile)target)) { - registerProblem(node, "Import resolves to its containing file"); - } - } - private static boolean isContainingFileImportAllowed(PyElement node, PsiFile target) { - return PyImportStatementNavigator.getImportStatementByElement(node) == null && target.getName().equals(PyNames.INIT_DOT_PY); - } + private static boolean isContainingFileImportAllowed(PyElement node, PsiFile target) { + return PyImportStatementNavigator.getImportStatementByElement(node) == null && target.getName().equals(PyNames.INIT_DOT_PY); + } - private void processReferenceInImportGuard(PyElement node, PyExceptPart guard) { - final PyImportElement importElement = PsiTreeUtil.getParentOfType(node, PyImportElement.class); - if (importElement != null) { - final String visibleName = importElement.getVisibleName(); - final ScopeOwner owner = ScopeUtil.getScopeOwner(importElement); - if (visibleName != null && owner != null) { - final Collection allWrites = ScopeUtil.getReadWriteElements(visibleName, owner, false, true); - final Collection writesInsideGuard = new ArrayList<>(); - for (PsiElement write : allWrites) { - if (PsiTreeUtil.isAncestor(guard, write, false)) { - writesInsideGuard.add(write); - } - } - if (writesInsideGuard.isEmpty()) { - final PyTargetExpression asElement = importElement.getAsNameElement(); - final PyElement toHighlight = asElement != null ? asElement : node; - registerProblem(toHighlight, + private void processReferenceInImportGuard(PyElement node, PyExceptPart guard) { + final PyImportElement importElement = PsiTreeUtil.getParentOfType(node, PyImportElement.class); + if (importElement != null) { + final String visibleName = importElement.getVisibleName(); + final ScopeOwner owner = ScopeUtil.getScopeOwner(importElement); + if (visibleName != null && owner != null) { + final Collection allWrites = ScopeUtil.getReadWriteElements(visibleName, owner, false, true); + final Collection writesInsideGuard = new ArrayList<>(); + for (PsiElement write : allWrites) { + if (PsiTreeUtil.isAncestor(guard, write, false)) { + writesInsideGuard.add(write); + } + } + if (writesInsideGuard.isEmpty()) { + final PyTargetExpression asElement = importElement.getAsNameElement(); + final PyElement toHighlight = asElement != null ? asElement : node; + registerProblem( + toHighlight, PyBundle.message("INSP.try.except.import.error", visibleName), - ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); - } + ProblemHighlightType.LIKE_UNKNOWN_SYMBOL + ); + } + } + } } - } - } - private void registerUnresolvedReferenceProblem(@Nonnull PyElement node, - @Nonnull final PsiReference reference, - @Nonnull HighlightSeverity severity) { - if (reference instanceof DocStringTypeReference) { - return; - } - String description = null; - PsiElement element = reference.getElement(); - final String text = element.getText(); - TextRange rangeInElement = reference.getRangeInElement(); - String refText = text; // text of the part we're working with - if (rangeInElement.getStartOffset() > 0 && rangeInElement.getEndOffset() > 0) { - refText = rangeInElement.substring(text); - } - - final List actions = new ArrayList<>(2); - final String refName = (element instanceof PyQualifiedExpression) ? ((PyQualifiedExpression)element).getReferencedName() : refText; - // Empty text, nothing to highlight - if (refName == null || refName.length() <= 0) { - return; - } - - final List qualifiedNames = getCanonicalNames(reference, myTypeEvalContext); - for (QualifiedName name : qualifiedNames) { - final String canonicalName = name.toString(); - for (String ignored : myIgnoredIdentifiers) { - if (ignored.endsWith(END_WILDCARD)) { - final String prefix = ignored.substring(0, ignored.length() - END_WILDCARD.length()); - if (canonicalName.startsWith(prefix)) { - return; - } - } - else if (canonicalName.equals(ignored)) { - return; - } - } - } - // Legacy non-qualified ignore patterns - if (myIgnoredIdentifiers.contains(refName)) { - return; - } - - if (element instanceof PyReferenceExpression) { - PyReferenceExpression expr = (PyReferenceExpression)element; - if (PyNames.COMPARISON_OPERATORS.contains(refName)) { - return; - } - if (!expr.isQualified()) { - if (PyUnreachableCodeInspection.hasAnyInterruptedControlFlowPaths(expr)) { - return; - } - if (LanguageLevel.forElement(node).isOlderThan(LanguageLevel.PYTHON26)) { - if ("with".equals(refName)) { - actions.add(new UnresolvedRefAddFutureImportQuickFix()); - } - } - if (refText.equals("true") || refText.equals("false")) { - actions.add(new UnresolvedRefTrueFalseQuickFix(element)); - } - addAddSelfFix(node, expr, actions); - PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class); - if (callExpression != null && (!(callExpression.getCallee() instanceof PyQualifiedExpression) || ((PyQualifiedExpression)callExpression - .getCallee()).getQualifier() == null)) { - actions.add(new UnresolvedRefCreateFunctionQuickFix(callExpression, expr)); - } - final PyFunction parentFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); - final PyDecorator decorator = PsiTreeUtil.getParentOfType(element, PyDecorator.class); - final PyAnnotation annotation = PsiTreeUtil.getParentOfType(element, PyAnnotation.class); - final PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class); - if (parentFunction != null && decorator == null && annotation == null && importStatement == null) { - actions.add(new UnresolvedReferenceAddParameterQuickFix(refName)); - } - actions.add(new PyRenameUnresolvedRefQuickFix()); - } - // unqualified: - // may be module's - if (PyModuleType.getPossibleInstanceMembers().contains(refName)) { - return; - } - // may be a "try: import ..."; not an error not to resolve - if ((PsiTreeUtil.getParentOfType(PsiTreeUtil.getParentOfType(node, PyImportElement.class), - PyTryExceptStatement.class, - PyIfStatement.class) != null)) { - severity = HighlightSeverity.WEAK_WARNING; - description = PyBundle.message("INSP.module.$0.not.found", refText); - // TODO: mark the node so that future references pointing to it won't result in a error, but in a warning - } - } - if (reference instanceof PsiReferenceEx && description == null) { - description = ((PsiReferenceEx)reference).getUnresolvedDescription(); - } - if (description == null) { - boolean markedQualified = false; - if (element instanceof PyQualifiedExpression) { - // TODO: Add __qualname__ for Python 3.3 to the skeleton of , introduce a pseudo-class skeleton for - // - if ("__qualname__".equals(refText) && LanguageLevel.forElement(element).isAtLeast(LanguageLevel.PYTHON33)) { - return; - } - final PyQualifiedExpression expr = (PyQualifiedExpression)element; - if (PyNames.COMPARISON_OPERATORS.contains(expr.getReferencedName())) { - return; - } - final PyExpression qualifier = expr.getQualifier(); - if (qualifier != null) { - PyType type = myTypeEvalContext.getType(qualifier); - if (type != null) { - if (ignoreUnresolvedMemberForType(type, reference, refName)) { + private void registerUnresolvedReferenceProblem( + @Nonnull PyElement node, + @Nonnull final PsiReference reference, + @Nonnull HighlightSeverity severity + ) { + if (reference instanceof DocStringTypeReference) { return; - } - addCreateMemberFromUsageFixes(type, reference, refText, actions); - if (type instanceof PyClassType) { - final PyClassType classType = (PyClassType)type; - if (reference instanceof PyOperatorReference) { - String className = type.getName(); - if (classType.isDefinition()) { - final PyClassLikeType metaClassType = classType.getMetaClassType(myTypeEvalContext, true); - if (metaClassType != null) { - className = metaClassType.getName(); + } + String description = null; + PsiElement element = reference.getElement(); + final String text = element.getText(); + TextRange rangeInElement = reference.getRangeInElement(); + String refText = text; // text of the part we're working with + if (rangeInElement.getStartOffset() > 0 && rangeInElement.getEndOffset() > 0) { + refText = rangeInElement.substring(text); + } + + final List actions = new ArrayList<>(2); + final String refName = + (element instanceof PyQualifiedExpression) ? ((PyQualifiedExpression) element).getReferencedName() : refText; + // Empty text, nothing to highlight + if (refName == null || refName.length() <= 0) { + return; + } + + final List qualifiedNames = getCanonicalNames(reference, myTypeEvalContext); + for (QualifiedName name : qualifiedNames) { + final String canonicalName = name.toString(); + for (String ignored : myIgnoredIdentifiers) { + if (ignored.endsWith(END_WILDCARD)) { + final String prefix = ignored.substring(0, ignored.length() - END_WILDCARD.length()); + if (canonicalName.startsWith(prefix)) { + return; + } + } + else if (canonicalName.equals(ignored)) { + return; } - } - description = PyBundle.message("INSP.unresolved.operator.ref", - className, - refName, - ((PyOperatorReference)reference).getReadableOperatorName()); } - else { - final List slots = classType.getPyClass().getOwnSlots(); + } + // Legacy non-qualified ignore patterns + if (myIgnoredIdentifiers.contains(refName)) { + return; + } - if (slots != null && slots.contains(refName)) { + if (element instanceof PyReferenceExpression) { + PyReferenceExpression expr = (PyReferenceExpression) element; + if (PyNames.COMPARISON_OPERATORS.contains(refName)) { return; - } - - description = PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", refText, type.getName()); } - markedQualified = true; - } - else if (isHasCustomMember(refName, type)) { - // We have dynamic members - return; - } - else { - description = PyBundle.message("INSP.cannot.find.$0.in.$1", refText, type.getName()); - markedQualified = true; - } + if (!expr.isQualified()) { + if (PyUnreachableCodeInspection.hasAnyInterruptedControlFlowPaths(expr)) { + return; + } + if (LanguageLevel.forElement(node).isOlderThan(LanguageLevel.PYTHON26)) { + if ("with".equals(refName)) { + actions.add(new UnresolvedRefAddFutureImportQuickFix()); + } + } + if (refText.equals("true") || refText.equals("false")) { + actions.add(new UnresolvedRefTrueFalseQuickFix(element)); + } + addAddSelfFix(node, expr, actions); + PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class); + if (callExpression != null && (!(callExpression.getCallee() instanceof PyQualifiedExpression) || ((PyQualifiedExpression) callExpression + .getCallee()).getQualifier() == null)) { + actions.add(new UnresolvedRefCreateFunctionQuickFix(callExpression, expr)); + } + final PyFunction parentFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + final PyDecorator decorator = PsiTreeUtil.getParentOfType(element, PyDecorator.class); + final PyAnnotation annotation = PsiTreeUtil.getParentOfType(element, PyAnnotation.class); + final PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class); + if (parentFunction != null && decorator == null && annotation == null && importStatement == null) { + actions.add(new UnresolvedReferenceAddParameterQuickFix(refName)); + } + actions.add(new PyRenameUnresolvedRefQuickFix()); + } + // unqualified: + // may be module's + if (PyModuleType.getPossibleInstanceMembers().contains(refName)) { + return; + } + // may be a "try: import ..."; not an error not to resolve + if ((PsiTreeUtil.getParentOfType( + PsiTreeUtil.getParentOfType(node, PyImportElement.class), + PyTryExceptStatement.class, + PyIfStatement.class + ) != null)) { + severity = HighlightSeverity.WEAK_WARNING; + description = PyBundle.message("INSP.module.$0.not.found", refText); + // TODO: mark the node so that future references pointing to it won't result in a error, but in a warning + } } - } - } - if (!markedQualified) { - description = PyBundle.message("INSP.unresolved.ref.$0", refText); + if (reference instanceof PsiReferenceEx && description == null) { + description = ((PsiReferenceEx) reference).getUnresolvedDescription(); + } + if (description == null) { + boolean markedQualified = false; + if (element instanceof PyQualifiedExpression) { + // TODO: Add __qualname__ for Python 3.3 to the skeleton of , introduce a pseudo-class skeleton for + // + if ("__qualname__".equals(refText) && LanguageLevel.forElement(element).isAtLeast(LanguageLevel.PYTHON33)) { + return; + } + final PyQualifiedExpression expr = (PyQualifiedExpression) element; + if (PyNames.COMPARISON_OPERATORS.contains(expr.getReferencedName())) { + return; + } + final PyExpression qualifier = expr.getQualifier(); + if (qualifier != null) { + PyType type = myTypeEvalContext.getType(qualifier); + if (type != null) { + if (ignoreUnresolvedMemberForType(type, reference, refName)) { + return; + } + addCreateMemberFromUsageFixes(type, reference, refText, actions); + if (type instanceof PyClassType) { + final PyClassType classType = (PyClassType) type; + if (reference instanceof PyOperatorReference) { + String className = type.getName(); + if (classType.isDefinition()) { + final PyClassLikeType metaClassType = classType.getMetaClassType(myTypeEvalContext, true); + if (metaClassType != null) { + className = metaClassType.getName(); + } + } + description = PyBundle.message( + "INSP.unresolved.operator.ref", + className, + refName, + ((PyOperatorReference) reference).getReadableOperatorName() + ); + } + else { + final List slots = classType.getPyClass().getOwnSlots(); + + if (slots != null && slots.contains(refName)) { + return; + } + + description = PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", refText, type.getName()); + } + markedQualified = true; + } + else if (isHasCustomMember(refName, type)) { + // We have dynamic members + return; + } + else { + description = PyBundle.message("INSP.cannot.find.$0.in.$1", refText, type.getName()); + markedQualified = true; + } + } + } + } + if (!markedQualified) { + description = PyBundle.message("INSP.unresolved.ref.$0", refText); - // look in other imported modules for this whole name - if (PythonImportUtils.isImportable(element)) { - addAutoImportFix(node, reference, actions); - } + // look in other imported modules for this whole name + if (PythonImportUtils.isImportable(element)) { + addAutoImportFix(node, reference, actions); + } - addCreateClassFix(refText, element, actions); - } - } - ProblemHighlightType hl_type; - if (severity == HighlightSeverity.WARNING) { - hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; - } - else if (severity == HighlightSeverity.ERROR) { - hl_type = ProblemHighlightType.GENERIC_ERROR; - } - else { - hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; - } - - PyImportStatementBase importStatementBase = PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class); - - if (qualifiedNames.size() == 1) { - final QualifiedName qualifiedName = qualifiedNames.get(0); - actions.add(new AddIgnoredIdentifierQuickFix(qualifiedName, false)); - if (qualifiedName.getComponentCount() > 1) { - actions.add(new AddIgnoredIdentifierQuickFix(qualifiedName.removeLastComponent(), true)); - } - } - addPluginQuickFixes(reference, actions); - - if (reference instanceof PyImportReference) { - // TODO: Ignore references in the second part of the 'from ... import ...' expression - final QualifiedName qname = QualifiedName.fromDottedString(refName); - final List components = qname.getComponents(); - if (!components.isEmpty()) { - final String packageName = components.get(0); - final Module module = ModuleUtilCore.findModuleForPsiElement(node); - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if (module != null && sdk != null && PyPackageUtil.packageManagementEnabled(sdk)) { - if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) { - addInstallPackageAction(actions, packageName, module, sdk); + addCreateClassFix(refText, element, actions); + } + } + ProblemHighlightType hl_type; + if (severity == HighlightSeverity.WARNING) { + hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; + } + else if (severity == HighlightSeverity.ERROR) { + hl_type = ProblemHighlightType.GENERIC_ERROR; } else { - if (PyPIPackageUtil.PACKAGES_TOPLEVEL.containsKey(packageName)) { - final String suggestedPackage = PyPIPackageUtil.PACKAGES_TOPLEVEL.get(packageName); - addInstallPackageAction(actions, suggestedPackage, module, sdk); - } + hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; + } + + PyImportStatementBase importStatementBase = PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class); + + if (qualifiedNames.size() == 1) { + final QualifiedName qualifiedName = qualifiedNames.get(0); + actions.add(new AddIgnoredIdentifierQuickFix(qualifiedName, false)); + if (qualifiedName.getComponentCount() > 1) { + actions.add(new AddIgnoredIdentifierQuickFix(qualifiedName.removeLastComponent(), true)); + } } - } + addPluginQuickFixes(reference, actions); + + if (reference instanceof PyImportReference) { + // TODO: Ignore references in the second part of the 'from ... import ...' expression + final QualifiedName qname = QualifiedName.fromDottedString(refName); + final List components = qname.getComponents(); + if (!components.isEmpty()) { + final String packageName = components.get(0); + final Module module = ModuleUtilCore.findModuleForPsiElement(node); + final Sdk sdk = PythonSdkType.findPythonSdk(module); + if (module != null && sdk != null && PyPackageUtil.packageManagementEnabled(sdk)) { + if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) { + addInstallPackageAction(actions, packageName, module, sdk); + } + else { + if (PyPIPackageUtil.PACKAGES_TOPLEVEL.containsKey(packageName)) { + final String suggestedPackage = PyPIPackageUtil.PACKAGES_TOPLEVEL.get(packageName); + addInstallPackageAction(actions, suggestedPackage, module, sdk); + } + } + } + } + } + + registerProblem(node, description, hl_type, null, rangeInElement, actions.toArray(new LocalQuickFix[actions.size()])); } - } - registerProblem(node, description, hl_type, null, rangeInElement, actions.toArray(new LocalQuickFix[actions.size()])); - } + private static void addInstallPackageAction(List actions, String packageName, Module module, Sdk sdk) { + final List requirements = Collections.singletonList(new PyRequirement(packageName)); + LocalizeValue name = LocalizeValue.localizeTODO("Install package " + packageName); + actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements)); + } - private static void addInstallPackageAction(List actions, String packageName, Module module, Sdk sdk) { - final List requirements = Collections.singletonList(new PyRequirement(packageName)); - final String name = "Install package " + packageName; - actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements)); - } + /** + * Checks if type is custom type and has custom member with certain name + * + * @param refName name to check + * @param type type + * @return true if has one + */ + private static boolean isHasCustomMember(@Nonnull final String refName, @Nonnull final PyType type) { + // TODO: check + return false; + /*return (type instanceof PyCustomType) && ((PyCustomType)type).hasMember(refName);*/ + } - /** - * Checks if type is custom type and has custom member with certain name - * - * @param refName name to check - * @param type type - * @return true if has one - */ - private static boolean isHasCustomMember(@Nonnull final String refName, @Nonnull final PyType type) { - // TODO: check - return false; - /*return (type instanceof PyCustomType) && ((PyCustomType)type).hasMember(refName);*/ - } + /** + * Return the canonical qualified names for a reference (even for an unresolved one). + * If reference is qualified and its qualifier has union type, all possible canonical names will be returned. + */ + @Nonnull + private static List getCanonicalNames(@Nonnull PsiReference reference, @Nonnull TypeEvalContext context) { + final PsiElement element = reference.getElement(); + final List result = new SmartList<>(); + if (reference instanceof PyOperatorReference && element instanceof PyQualifiedExpression) { + final PyExpression receiver = ((PyOperatorReference) reference).getReceiver(); + if (receiver != null) { + final PyType type = context.getType(receiver); + if (type instanceof PyClassType) { + final String methodName = ((PyQualifiedExpression) element).getReferencedName(); + ContainerUtil.addIfNotNull(result, extractAttributeQNameFromClassType(methodName, (PyClassType) type)); + } + } + } + else if (element instanceof PyReferenceExpression) { + final PyReferenceExpression expr = (PyReferenceExpression) element; + final PyExpression qualifier = expr.getQualifier(); + final String exprName = expr.getName(); + if (exprName != null) { + if (qualifier != null) { + final PyType type = context.getType(qualifier); + if (type instanceof PyClassType) { + ContainerUtil.addIfNotNull(result, extractAttributeQNameFromClassType(exprName, (PyClassType) type)); + } + else if (type instanceof PyModuleType) { + final PyFile file = ((PyModuleType) type).getModule(); + final QualifiedName name = QualifiedNameFinder.findCanonicalImportPath(file, element); + if (name != null) { + ContainerUtil.addIfNotNull(result, name.append(exprName)); + } + } + else if (type instanceof PyImportedModuleType) { + final PyImportedModule module = ((PyImportedModuleType) type).getImportedModule(); + final PsiElement resolved = module.resolve(); + if (resolved != null) { + final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(resolved, element); + if (path != null) { + ContainerUtil.addIfNotNull(result, path.append(exprName)); + } + } + } + else if (type instanceof PyUnionType) { + for (PyType memberType : ((PyUnionType) type).getMembers()) { + if (memberType instanceof PyClassType) { + ContainerUtil.addIfNotNull( + result, + extractAttributeQNameFromClassType(exprName, (PyClassType) memberType) + ); + } + } + } + } + else { + final PsiElement parent = element.getParent(); + if (parent instanceof PyImportElement) { + final PyImportStatementBase importStmt = PsiTreeUtil.getParentOfType(parent, PyImportStatementBase.class); + if (importStmt instanceof PyImportStatement) { + ContainerUtil.addIfNotNull(result, QualifiedName.fromComponents(exprName)); + } + else if (importStmt instanceof PyFromImportStatement) { + final PsiElement resolved = ((PyFromImportStatement) importStmt).resolveImportSource(); + if (resolved != null) { + final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(resolved, element); + if (path != null) { + ContainerUtil.addIfNotNull(result, path.append(exprName)); + } + } + } + } + else { + final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(element, element); + if (path != null) { + ContainerUtil.addIfNotNull(result, path.append(exprName)); + } + } + } + } + } + else if (reference instanceof DocStringParameterReference) { + ContainerUtil.addIfNotNull(result, QualifiedName.fromDottedString(reference.getCanonicalText())); + } + return result; + } - /** - * Return the canonical qualified names for a reference (even for an unresolved one). - * If reference is qualified and its qualifier has union type, all possible canonical names will be returned. - */ - @Nonnull - private static List getCanonicalNames(@Nonnull PsiReference reference, @Nonnull TypeEvalContext context) { - final PsiElement element = reference.getElement(); - final List result = new SmartList<>(); - if (reference instanceof PyOperatorReference && element instanceof PyQualifiedExpression) { - final PyExpression receiver = ((PyOperatorReference)reference).getReceiver(); - if (receiver != null) { - final PyType type = context.getType(receiver); - if (type instanceof PyClassType) { - final String methodName = ((PyQualifiedExpression)element).getReferencedName(); - ContainerUtil.addIfNotNull(result, extractAttributeQNameFromClassType(methodName, (PyClassType)type)); - } + private static QualifiedName extractAttributeQNameFromClassType(String exprName, PyClassType type) { + final String name = type.getClassQName(); + if (name != null) { + return QualifiedName.fromDottedString(name).append(exprName); + } + return null; } - } - else if (element instanceof PyReferenceExpression) { - final PyReferenceExpression expr = (PyReferenceExpression)element; - final PyExpression qualifier = expr.getQualifier(); - final String exprName = expr.getName(); - if (exprName != null) { - if (qualifier != null) { - final PyType type = context.getType(qualifier); - if (type instanceof PyClassType) { - ContainerUtil.addIfNotNull(result, extractAttributeQNameFromClassType(exprName, (PyClassType)type)); + + private boolean ignoreUnresolvedMemberForType(@Nonnull PyType type, PsiReference reference, String name) { + if (type instanceof PyNoneType || PyTypeChecker.isUnknown(type)) { + // this almost always means that we don't know the type, so don't show an error in this case + return true; } - else if (type instanceof PyModuleType) { - final PyFile file = ((PyModuleType)type).getModule(); - final QualifiedName name = QualifiedNameFinder.findCanonicalImportPath(file, element); - if (name != null) { - ContainerUtil.addIfNotNull(result, name.append(exprName)); - } - } - else if (type instanceof PyImportedModuleType) { - final PyImportedModule module = ((PyImportedModuleType)type).getImportedModule(); - final PsiElement resolved = module.resolve(); - if (resolved != null) { - final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(resolved, element); - if (path != null) { - ContainerUtil.addIfNotNull(result, path.append(exprName)); + if (type instanceof PyStructuralType && ((PyStructuralType) type).isInferredFromUsages()) { + return true; + } + if (type instanceof PyImportedModuleType) { + PyImportedModule module = ((PyImportedModuleType) type).getImportedModule(); + if (module.resolve() == null) { + return true; } - } } - else if (type instanceof PyUnionType) { - for (PyType memberType : ((PyUnionType)type).getMembers()) { - if (memberType instanceof PyClassType) { - ContainerUtil.addIfNotNull(result, extractAttributeQNameFromClassType(exprName, (PyClassType)memberType)); + if (type instanceof PyCustomType) { + // Skip custom member types that mimics another class with fuzzy parents + for (final PyClassLikeType mimic : ((PyCustomType) type).getTypesToMimic()) { + if (!(mimic instanceof PyClassType)) { + continue; + } + if (PyUtil.hasUnresolvedAncestors(((PyClassType) mimic).getPyClass(), myTypeEvalContext)) { + return true; + } + } + } + if (type instanceof PyClassTypeImpl) { + PyClass cls = ((PyClassType) type).getPyClass(); + if (PyTypeChecker.overridesGetAttr(cls, myTypeEvalContext)) { + return true; + } + if (cls.findProperty(name, true, myTypeEvalContext) != null) { + return true; + } + if (PyUtil.hasUnresolvedAncestors(cls, myTypeEvalContext)) { + return true; } - } - } - } - else { - final PsiElement parent = element.getParent(); - if (parent instanceof PyImportElement) { - final PyImportStatementBase importStmt = PsiTreeUtil.getParentOfType(parent, PyImportStatementBase.class); - if (importStmt instanceof PyImportStatement) { - ContainerUtil.addIfNotNull(result, QualifiedName.fromComponents(exprName)); - } - else if (importStmt instanceof PyFromImportStatement) { - final PsiElement resolved = ((PyFromImportStatement)importStmt).resolveImportSource(); - if (resolved != null) { - final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(resolved, element); - if (path != null) { - ContainerUtil.addIfNotNull(result, path.append(exprName)); - } + if (isDecoratedAsDynamic(cls, true)) { + return true; + } + if (hasUnresolvedDynamicMember((PyClassType) type, reference, name, myTypeEvalContext)) { + return true; } - } } - else { - final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(element, element); - if (path != null) { - ContainerUtil.addIfNotNull(result, path.append(exprName)); - } + if (type instanceof PyFunctionTypeImpl) { + final PyCallable callable = ((PyFunctionTypeImpl) type).getCallable(); + if (callable instanceof PyFunction && PyKnownDecoratorUtil.hasNonBuiltinDecorator( + (PyFunction) callable, + myTypeEvalContext + )) { + return true; + } + } + for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) { + if (extension.ignoreUnresolvedMember(type, name)) { + return true; + } } - } + return false; } - } - else if (reference instanceof DocStringParameterReference) { - ContainerUtil.addIfNotNull(result, QualifiedName.fromDottedString(reference.getCanonicalText())); - } - return result; - } - - private static QualifiedName extractAttributeQNameFromClassType(String exprName, PyClassType type) { - final String name = type.getClassQName(); - if (name != null) { - return QualifiedName.fromDottedString(name).append(exprName); - } - return null; - } - private boolean ignoreUnresolvedMemberForType(@Nonnull PyType type, PsiReference reference, String name) { - if (type instanceof PyNoneType || PyTypeChecker.isUnknown(type)) { - // this almost always means that we don't know the type, so don't show an error in this case - return true; - } - if (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages()) { - return true; - } - if (type instanceof PyImportedModuleType) { - PyImportedModule module = ((PyImportedModuleType)type).getImportedModule(); - if (module.resolve() == null) { - return true; - } - } - if (type instanceof PyCustomType) { - // Skip custom member types that mimics another class with fuzzy parents - for (final PyClassLikeType mimic : ((PyCustomType)type).getTypesToMimic()) { - if (!(mimic instanceof PyClassType)) { - continue; - } - if (PyUtil.hasUnresolvedAncestors(((PyClassType)mimic).getPyClass(), myTypeEvalContext)) { - return true; - } - } - } - if (type instanceof PyClassTypeImpl) { - PyClass cls = ((PyClassType)type).getPyClass(); - if (PyTypeChecker.overridesGetAttr(cls, myTypeEvalContext)) { - return true; - } - if (cls.findProperty(name, true, myTypeEvalContext) != null) { - return true; - } - if (PyUtil.hasUnresolvedAncestors(cls, myTypeEvalContext)) { - return true; - } - if (isDecoratedAsDynamic(cls, true)) { - return true; - } - if (hasUnresolvedDynamicMember((PyClassType)type, reference, name, myTypeEvalContext)) { - return true; - } - } - if (type instanceof PyFunctionTypeImpl) { - final PyCallable callable = ((PyFunctionTypeImpl)type).getCallable(); - if (callable instanceof PyFunction && PyKnownDecoratorUtil.hasNonBuiltinDecorator((PyFunction)callable, myTypeEvalContext)) { - return true; - } - } - for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) { - if (extension.ignoreUnresolvedMember(type, name)) { - return true; + private static boolean hasUnresolvedDynamicMember( + @Nonnull final PyClassType type, + PsiReference reference, + @Nonnull final String name, + TypeEvalContext typeEvalContext + ) { + for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { + final Collection resolveResult = provider.getMembers(type, reference.getElement(), typeEvalContext); + for (PyCustomMember member : resolveResult) { + if (member.getName().equals(name)) { + return true; + } + } + } + return false; } - } - return false; - } - private static boolean hasUnresolvedDynamicMember(@Nonnull final PyClassType type, - PsiReference reference, - @Nonnull final String name, - TypeEvalContext typeEvalContext) { - for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { - final Collection resolveResult = provider.getMembers(type, reference.getElement(), typeEvalContext); - for (PyCustomMember member : resolveResult) { - if (member.getName().equals(name)) { - return true; - } + private boolean isDecoratedAsDynamic(@Nonnull PyClass cls, boolean inherited) { + if (inherited) { + if (isDecoratedAsDynamic(cls, false)) { + return true; + } + for (PyClass base : cls.getAncestorClasses(myTypeEvalContext)) { + if (base != null && isDecoratedAsDynamic(base, false)) { + return true; + } + } + } + else { + if (cls.getDecoratorList() != null) { + return true; + } + final String docString = cls.getDocStringValue(); + if (docString != null && docString.contains("@DynamicAttrs")) { + return true; + } + } + return false; } - } - return false; - } - private boolean isDecoratedAsDynamic(@Nonnull PyClass cls, boolean inherited) { - if (inherited) { - if (isDecoratedAsDynamic(cls, false)) { - return true; - } - for (PyClass base : cls.getAncestorClasses(myTypeEvalContext)) { - if (base != null && isDecoratedAsDynamic(base, false)) { - return true; - } - } - } - else { - if (cls.getDecoratorList() != null) { - return true; - } - final String docString = cls.getDocStringValue(); - if (docString != null && docString.contains("@DynamicAttrs")) { - return true; + private void addCreateMemberFromUsageFixes(PyType type, PsiReference reference, String refText, List actions) { + PsiElement element = reference.getElement(); + if (type instanceof PyClassTypeImpl) { + PyClass cls = ((PyClassType) type).getPyClass(); + if (!PyBuiltinCache.getInstance(element).isBuiltin(cls)) { + if (element.getParent() instanceof PyCallExpression) { + actions.add(new AddMethodQuickFix(refText, cls.getName(), true)); + } + else if (!(reference instanceof PyOperatorReference)) { + actions.add(new AddFieldQuickFix(refText, "None", type.getName(), true)); + } + } + } + else if (type instanceof PyModuleType) { + PyFile file = ((PyModuleType) type).getModule(); + actions.add(new AddFunctionQuickFix(refText, file.getName())); + addCreateClassFix(refText, element, actions); + } } - } - return false; - } - private void addCreateMemberFromUsageFixes(PyType type, PsiReference reference, String refText, List actions) { - PsiElement element = reference.getElement(); - if (type instanceof PyClassTypeImpl) { - PyClass cls = ((PyClassType)type).getPyClass(); - if (!PyBuiltinCache.getInstance(element).isBuiltin(cls)) { - if (element.getParent() instanceof PyCallExpression) { - actions.add(new AddMethodQuickFix(refText, cls.getName(), true)); - } - else if (!(reference instanceof PyOperatorReference)) { - actions.add(new AddFieldQuickFix(refText, "None", type.getName(), true)); - } + private void addAddSelfFix(PyElement node, PyReferenceExpression expr, List actions) { + final PyClass containedClass = PsiTreeUtil.getParentOfType(node, PyClass.class); + final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class); + if (containedClass != null && function != null) { + final PyParameter[] parameters = function.getParameterList().getParameters(); + if (parameters.length == 0) { + return; + } + final String qualifier = parameters[0].getText(); + final PyDecoratorList decoratorList = function.getDecoratorList(); + boolean isClassMethod = false; + if (decoratorList != null) { + for (PyDecorator decorator : decoratorList.getDecorators()) { + final PyExpression callee = decorator.getCallee(); + if (callee != null && PyNames.CLASSMETHOD.equals(callee.getText())) { + isClassMethod = true; + } + } + } + for (PyTargetExpression target : containedClass.getInstanceAttributes()) { + if (!isClassMethod && Comparing.strEqual(node.getName(), target.getName())) { + actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier)); + } + } + for (PyStatement statement : containedClass.getStatementList().getStatements()) { + if (statement instanceof PyAssignmentStatement) { + PyExpression lhsExpression = ((PyAssignmentStatement) statement).getLeftHandSideExpression(); + if (lhsExpression != null && lhsExpression.getText().equals(expr.getText())) { + PyExpression assignedValue = ((PyAssignmentStatement) statement).getAssignedValue(); + if (assignedValue instanceof PyCallExpression) { + PyType type = myTypeEvalContext.getType(assignedValue); + if (type != null && type instanceof PyClassTypeImpl) { + if (((PyCallExpression) assignedValue).isCalleeText(PyNames.PROPERTY)) { + actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier)); + } + } + } + } + } + } + for (PyFunction method : containedClass.getMethods()) { + if (expr.getText().equals(method.getName())) { + actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier)); + } + } + } } - } - else if (type instanceof PyModuleType) { - PyFile file = ((PyModuleType)type).getModule(); - actions.add(new AddFunctionQuickFix(refText, file.getName())); - addCreateClassFix(refText, element, actions); - } - } - private void addAddSelfFix(PyElement node, PyReferenceExpression expr, List actions) { - final PyClass containedClass = PsiTreeUtil.getParentOfType(node, PyClass.class); - final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class); - if (containedClass != null && function != null) { - final PyParameter[] parameters = function.getParameterList().getParameters(); - if (parameters.length == 0) { - return; - } - final String qualifier = parameters[0].getText(); - final PyDecoratorList decoratorList = function.getDecoratorList(); - boolean isClassMethod = false; - if (decoratorList != null) { - for (PyDecorator decorator : decoratorList.getDecorators()) { - final PyExpression callee = decorator.getCallee(); - if (callee != null && PyNames.CLASSMETHOD.equals(callee.getText())) { - isClassMethod = true; - } - } - } - for (PyTargetExpression target : containedClass.getInstanceAttributes()) { - if (!isClassMethod && Comparing.strEqual(node.getName(), target.getName())) { - actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier)); - } - } - for (PyStatement statement : containedClass.getStatementList().getStatements()) { - if (statement instanceof PyAssignmentStatement) { - PyExpression lhsExpression = ((PyAssignmentStatement)statement).getLeftHandSideExpression(); - if (lhsExpression != null && lhsExpression.getText().equals(expr.getText())) { - PyExpression assignedValue = ((PyAssignmentStatement)statement).getAssignedValue(); - if (assignedValue instanceof PyCallExpression) { - PyType type = myTypeEvalContext.getType(assignedValue); - if (type != null && type instanceof PyClassTypeImpl) { - if (((PyCallExpression)assignedValue).isCalleeText(PyNames.PROPERTY)) { - actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier)); - } + private static void addAutoImportFix(PyElement node, PsiReference reference, List actions) { + final PsiFile file = InjectedLanguageManager.getInstance(node.getProject()).getTopLevelFile(node); + if (!(file instanceof PyFile)) { + return; + } + AutoImportQuickFix importFix = PythonImportUtils.proposeImportFix(node, reference); + if (importFix != null) { + if (!suppressHintForAutoImport(node, importFix) && PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP) { + final AutoImportHintAction autoImportHintAction = new AutoImportHintAction(importFix); + actions.add(autoImportHintAction); + } + else { + actions.add(importFix); + } + if (ScopeUtil.getScopeOwner(node) instanceof PyFunction) { + actions.add(importFix.forLocalImport()); + } + } + else { + final String refName = + (node instanceof PyQualifiedExpression) ? ((PyQualifiedExpression) node).getReferencedName() : node.getText(); + if (refName == null) { + return; + } + final QualifiedName qname = QualifiedName.fromDottedString(refName); + final List components = qname.getComponents(); + if (!components.isEmpty()) { + final String packageName = components.get(0); + final Module module = ModuleUtilCore.findModuleForPsiElement(node); + if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName) && PythonSdkType.findPythonSdk(module) != null) { + actions.add(new PyPackageRequirementsInspection.InstallAndImportQuickFix(packageName, packageName, node)); + } + else { + final String packageAlias = PyPackageAliasesProvider.commonImportAliases.get(packageName); + if (packageAlias != null && PyPIPackageUtil.INSTANCE.isInPyPI(packageName) && PythonSdkType.findPythonSdk(module) != null) { + actions.add(new PyPackageRequirementsInspection.InstallAndImportQuickFix(packageAlias, packageName, node)); + } + } } - } } - } - } - for (PyFunction method : containedClass.getMethods()) { - if (expr.getText().equals(method.getName())) { - actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier)); - } } - } - } - private static void addAutoImportFix(PyElement node, PsiReference reference, List actions) { - final PsiFile file = InjectedLanguageManager.getInstance(node.getProject()).getTopLevelFile(node); - if (!(file instanceof PyFile)) { - return; - } - AutoImportQuickFix importFix = PythonImportUtils.proposeImportFix(node, reference); - if (importFix != null) { - if (!suppressHintForAutoImport(node, importFix) && PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP) { - final AutoImportHintAction autoImportHintAction = new AutoImportHintAction(importFix); - actions.add(autoImportHintAction); - } - else { - actions.add(importFix); - } - if (ScopeUtil.getScopeOwner(node) instanceof PyFunction) { - actions.add(importFix.forLocalImport()); - } - } - else { - final String refName = (node instanceof PyQualifiedExpression) ? ((PyQualifiedExpression)node).getReferencedName() : node.getText(); - if (refName == null) { - return; + private static boolean suppressHintForAutoImport(PyElement node, AutoImportQuickFix importFix) { + // if the context doesn't look like a function call and we only found imports of functions, suggest auto-import + // as a quickfix but no popup balloon (PY-2312) + if (!isCall(node) && importFix.hasOnlyFunctions()) { + return true; + } + // if we're in a class context and the class defines a variable with the same name, offer auto-import only as quickfix, + // not as popup + PyClass containingClass = PsiTreeUtil.getParentOfType(node, PyClass.class); + if (containingClass != null && (containingClass.findMethodByName( + importFix.getNameToImport(), + true, + null + ) != null || containingClass.findInstanceAttribute( + importFix.getNameToImport(), + true + ) != null)) { + return true; + } + return false; } - final QualifiedName qname = QualifiedName.fromDottedString(refName); - final List components = qname.getComponents(); - if (!components.isEmpty()) { - final String packageName = components.get(0); - final Module module = ModuleUtilCore.findModuleForPsiElement(node); - if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName) && PythonSdkType.findPythonSdk(module) != null) { - actions.add(new PyPackageRequirementsInspection.InstallAndImportQuickFix(packageName, packageName, node)); - } - else { - final String packageAlias = PyPackageAliasesProvider.commonImportAliases.get(packageName); - if (packageAlias != null && PyPIPackageUtil.INSTANCE.isInPyPI(packageName) && PythonSdkType.findPythonSdk(module) != null) { - actions.add(new PyPackageRequirementsInspection.InstallAndImportQuickFix(packageAlias, packageName, node)); - } - } + + private void addCreateClassFix(@NonNls String refText, PsiElement element, List actions) { + if (refText.length() > 2 && Character.isUpperCase(refText.charAt(0)) && !refText.toUpperCase().equals(refText) && + PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) == null) { + PsiElement anchor = element; + if (element instanceof PyQualifiedExpression) { + final PyExpression expr = ((PyQualifiedExpression) element).getQualifier(); + if (expr != null) { + final PyType type = myTypeEvalContext.getType(expr); + if (type instanceof PyModuleType) { + anchor = ((PyModuleType) type).getModule(); + } + else { + anchor = null; + } + } + if (anchor != null) { + actions.add(new CreateClassQuickFix(refText, anchor)); + } + } + } } - } - } - private static boolean suppressHintForAutoImport(PyElement node, AutoImportQuickFix importFix) { - // if the context doesn't look like a function call and we only found imports of functions, suggest auto-import - // as a quickfix but no popup balloon (PY-2312) - if (!isCall(node) && importFix.hasOnlyFunctions()) { - return true; - } - // if we're in a class context and the class defines a variable with the same name, offer auto-import only as quickfix, - // not as popup - PyClass containingClass = PsiTreeUtil.getParentOfType(node, PyClass.class); - if (containingClass != null && (containingClass.findMethodByName(importFix.getNameToImport(), - true, - null) != null || containingClass.findInstanceAttribute(importFix.getNameToImport(), - true) != null)) { - return true; - } - return false; - } + private static boolean isCall(PyElement node) { + final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(node, PyCallExpression.class); + return callExpression != null && node == callExpression.getCallee(); + } - private void addCreateClassFix(@NonNls String refText, PsiElement element, List actions) { - if (refText.length() > 2 && Character.isUpperCase(refText.charAt(0)) && !refText.toUpperCase().equals(refText) && - PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) == null) { - PsiElement anchor = element; - if (element instanceof PyQualifiedExpression) { - final PyExpression expr = ((PyQualifiedExpression)element).getQualifier(); - if (expr != null) { - final PyType type = myTypeEvalContext.getType(expr); - if (type instanceof PyModuleType) { - anchor = ((PyModuleType)type).getModule(); + private static void addPluginQuickFixes(PsiReference reference, final List actions) { + for (PyUnresolvedReferenceQuickFixProvider provider : PyUnresolvedReferenceQuickFixProvider.EP_NAME.getExtensionList()) { + provider.registerQuickFixes(reference, actions::add); } - else { - anchor = null; + } + + public void highlightUnusedImports() { + final List unused = collectUnusedImportElements(); + for (PsiElement element : unused) { + if (element.getTextLength() > 0) { + registerProblem( + element, + "Unused import statement", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + null, + new OptimizeImportsQuickFix() + ); + } } - } - if (anchor != null) { - actions.add(new CreateClassQuickFix(refText, anchor)); - } } - } - } - private static boolean isCall(PyElement node) { - final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(node, PyCallExpression.class); - return callExpression != null && node == callExpression.getCallee(); - } + private List collectUnusedImportElements() { + if (myAllImports.isEmpty()) { + return Collections.emptyList(); + } + // PY-1315 Unused imports inspection shouldn't work in python REPL console + final PyImportedNameDefiner first = myAllImports.iterator().next(); + if (first.getContainingFile() instanceof PyExpressionCodeFragment || PydevConsoleRunner.isInPydevConsole(first)) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); - private static void addPluginQuickFixes(PsiReference reference, final List actions) { - for (PyUnresolvedReferenceQuickFixProvider provider : PyUnresolvedReferenceQuickFixProvider.EP_NAME.getExtensionList()) { - provider.registerQuickFixes(reference, actions::add); - } - } + Set unusedImports = new HashSet<>(myAllImports); + unusedImports.removeAll(myUsedImports); - public void highlightUnusedImports() { - final List unused = collectUnusedImportElements(); - for (PsiElement element : unused) { - if (element.getTextLength() > 0) { - registerProblem(element, "Unused import statement", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, new OptimizeImportsQuickFix()); - } - } - } + // Remove those unsed, that are reported to be skipped by extension points + final Set unusedImportToSkip = new HashSet<>(); + for (final PyImportedNameDefiner unusedImport : unusedImports) { + if (importShouldBeSkippedByExtPoint(unusedImport)) { // Pass to extension points + unusedImportToSkip.add(unusedImport); + } + } - private List collectUnusedImportElements() { - if (myAllImports.isEmpty()) { - return Collections.emptyList(); - } - // PY-1315 Unused imports inspection shouldn't work in python REPL console - final PyImportedNameDefiner first = myAllImports.iterator().next(); - if (first.getContainingFile() instanceof PyExpressionCodeFragment || PydevConsoleRunner.isInPydevConsole(first)) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - - Set unusedImports = new HashSet<>(myAllImports); - unusedImports.removeAll(myUsedImports); - - // Remove those unsed, that are reported to be skipped by extension points - final Set unusedImportToSkip = new HashSet<>(); - for (final PyImportedNameDefiner unusedImport : unusedImports) { - if (importShouldBeSkippedByExtPoint(unusedImport)) { // Pass to extension points - unusedImportToSkip.add(unusedImport); - } - } + unusedImports.removeAll(unusedImportToSkip); - unusedImports.removeAll(unusedImportToSkip); + Set usedImportNames = new HashSet<>(); + for (PyImportedNameDefiner usedImport : myUsedImports) { + for (PyElement e : usedImport.iterateNames()) { + usedImportNames.add(e.getName()); + } + } - Set usedImportNames = new HashSet<>(); - for (PyImportedNameDefiner usedImport : myUsedImports) { - for (PyElement e : usedImport.iterateNames()) { - usedImportNames.add(e.getName()); - } - } - - Set unusedStatements = new HashSet<>(); - final PyUnresolvedReferencesInspection suppressableInspection = new PyUnresolvedReferencesInspection(); - QualifiedName packageQName = null; - List dunderAll = null; - - // TODO: Use strategies instead of pack of "continue" - for (PyImportedNameDefiner unusedImport : unusedImports) { - if (packageQName == null) { - final PsiFile file = unusedImport.getContainingFile(); - if (file instanceof PyFile) { - dunderAll = ((PyFile)file).getDunderAll(); - } - if (file != null && PyUtil.isPackage(file)) { - packageQName = QualifiedNameFinder.findShortestImportableQName(file); - } - } - PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase.class); - if (importStatement != null && !unusedStatements.contains(importStatement) && !myUsedImports.contains(unusedImport)) { - if (suppressableInspection.isSuppressedFor(importStatement)) { - continue; - } - // don't remove as unused imports in try/except statements - if (PsiTreeUtil.getParentOfType(importStatement, PyTryExceptStatement.class) != null) { - continue; - } - // Don't report conditional imports as unused - if (PsiTreeUtil.getParentOfType(unusedImport, PyIfStatement.class) != null) { - boolean isUsed = false; - for (PyElement e : unusedImport.iterateNames()) { - if (usedImportNames.contains(e.getName())) { - isUsed = true; - } - } - if (isUsed) { - continue; - } - } - PsiElement importedElement; - if (unusedImport instanceof PyImportElement) { - final PyImportElement importElement = (PyImportElement)unusedImport; - final PsiElement element = importElement.resolve(); - if (element == null) { - if (importElement.getImportedQName() != null) { - //Mark import as unused even if it can't be resolved - if (areAllImportsUnused(importStatement, unusedImports)) { - result.add(importStatement); + Set unusedStatements = new HashSet<>(); + final PyUnresolvedReferencesInspection suppressableInspection = new PyUnresolvedReferencesInspection(); + QualifiedName packageQName = null; + List dunderAll = null; + + // TODO: Use strategies instead of pack of "continue" + for (PyImportedNameDefiner unusedImport : unusedImports) { + if (packageQName == null) { + final PsiFile file = unusedImport.getContainingFile(); + if (file instanceof PyFile) { + dunderAll = ((PyFile) file).getDunderAll(); + } + if (file != null && PyUtil.isPackage(file)) { + packageQName = QualifiedNameFinder.findShortestImportableQName(file); + } } - else { - result.add(importElement); + PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase.class); + if (importStatement != null && !unusedStatements.contains(importStatement) && !myUsedImports.contains(unusedImport)) { + if (suppressableInspection.isSuppressedFor(importStatement)) { + continue; + } + // don't remove as unused imports in try/except statements + if (PsiTreeUtil.getParentOfType(importStatement, PyTryExceptStatement.class) != null) { + continue; + } + // Don't report conditional imports as unused + if (PsiTreeUtil.getParentOfType(unusedImport, PyIfStatement.class) != null) { + boolean isUsed = false; + for (PyElement e : unusedImport.iterateNames()) { + if (usedImportNames.contains(e.getName())) { + isUsed = true; + } + } + if (isUsed) { + continue; + } + } + PsiElement importedElement; + if (unusedImport instanceof PyImportElement) { + final PyImportElement importElement = (PyImportElement) unusedImport; + final PsiElement element = importElement.resolve(); + if (element == null) { + if (importElement.getImportedQName() != null) { + //Mark import as unused even if it can't be resolved + if (areAllImportsUnused(importStatement, unusedImports)) { + result.add(importStatement); + } + else { + result.add(importElement); + } + } + continue; + } + if (dunderAll != null && dunderAll.contains(importElement.getVisibleName())) { + continue; + } + importedElement = element.getContainingFile(); + } + else { + assert importStatement instanceof PyFromImportStatement; + importedElement = ((PyFromImportStatement) importStatement).resolveImportSource(); + if (importedElement == null) { + continue; + } + } + if (packageQName != null && importedElement instanceof PsiFileSystemItem) { + final QualifiedName importedQName = + QualifiedNameFinder.findShortestImportableQName((PsiFileSystemItem) importedElement); + if (importedQName != null && importedQName.matchesPrefix(packageQName)) { + continue; + } + } + if (unusedImport instanceof PyStarImportElement || areAllImportsUnused(importStatement, unusedImports)) { + unusedStatements.add(importStatement); + result.add(importStatement); + } + else { + result.add(unusedImport); + } } - } - continue; - } - if (dunderAll != null && dunderAll.contains(importElement.getVisibleName())) { - continue; - } - importedElement = element.getContainingFile(); - } - else { - assert importStatement instanceof PyFromImportStatement; - importedElement = ((PyFromImportStatement)importStatement).resolveImportSource(); - if (importedElement == null) { - continue; - } - } - if (packageQName != null && importedElement instanceof PsiFileSystemItem) { - final QualifiedName importedQName = QualifiedNameFinder.findShortestImportableQName((PsiFileSystemItem)importedElement); - if (importedQName != null && importedQName.matchesPrefix(packageQName)) { - continue; - } - } - if (unusedImport instanceof PyStarImportElement || areAllImportsUnused(importStatement, unusedImports)) { - unusedStatements.add(importStatement); - result.add(importStatement); - } - else { - result.add(unusedImport); - } + } + return result; } - } - return result; - } - private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set unusedImports) { - final PyImportElement[] elements = importStatement.getImportElements(); - for (PyImportElement element : elements) { - if (!unusedImports.contains(element)) { - return false; + private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set unusedImports) { + final PyImportElement[] elements = importStatement.getImportElements(); + for (PyImportElement element : elements) { + if (!unusedImports.contains(element)) { + return false; + } + } + return true; } - } - return true; - } - public void optimizeImports() { - final List elementsToDelete = collectUnusedImportElements(); - for (PsiElement element : elementsToDelete) { - PyPsiUtils.assertValid(element); - element.delete(); - } + public void optimizeImports() { + final List elementsToDelete = collectUnusedImportElements(); + for (PsiElement element : elementsToDelete) { + PyPsiUtils.assertValid(element); + element.delete(); + } + } } - } - - /** - * Checks if one or more extension points ask unused import to be skipped - * - * @param importNameDefiner unused import - * @return true of one or more asks - */ - private static boolean importShouldBeSkippedByExtPoint(@Nonnull final PyImportedNameDefiner importNameDefiner) { - for (final PyUnresolvedReferenceSkipperExtPoint skipper : PyUnresolvedReferenceSkipperExtPoint.EP_NAME.getExtensions()) { - if (skipper.unusedImportShouldBeSkipped(importNameDefiner)) { - return true; - } + + /** + * Checks if one or more extension points ask unused import to be skipped + * + * @param importNameDefiner unused import + * @return true of one or more asks + */ + private static boolean importShouldBeSkippedByExtPoint(@Nonnull final PyImportedNameDefiner importNameDefiner) { + for (final PyUnresolvedReferenceSkipperExtPoint skipper : PyUnresolvedReferenceSkipperExtPoint.EP_NAME.getExtensions()) { + if (skipper.unusedImportShouldBeSkipped(importNameDefiner)) { + return true; + } + } + return false; } - return false; - } }