diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index bc8ef0c5f422..65a1cd64b079 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -22,6 +22,8 @@ QFIX.add.super=Add super class call QFIX.add.property=Add property for the field QFIX.use.property=Use property for the field +QFIX.make.public=Make public + QFIX.add.encoding=Add encoding declaration QFIX.NAME.parameters=Parameters of functions and methods diff --git a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java index 15a1bc96b5b8..75afb3c46414 100644 --- a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java @@ -30,12 +30,14 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; import com.jetbrains.python.inspections.quickfix.PyAddPropertyForFieldQuickFix; +import com.jetbrains.python.inspections.quickfix.PyMakePublicQuickFix; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyReferenceExpression; import com.jetbrains.python.psi.PyTargetExpression; import com.jetbrains.python.psi.types.PyModuleType; import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.testing.pytest.PyTestUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -43,6 +45,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -89,19 +92,24 @@ public class PyProtectedMemberInspection extends PyInspection { final PsiReference reference = node.getReference(getResolveContext()); if (reference == null) return; final PsiElement resolvedExpression = reference.resolve(); + final PyClass resolvedClass = getClassOwner(resolvedExpression); if (resolvedExpression instanceof PyTargetExpression) { - final PyClass containingClass = ((PyTargetExpression)resolvedExpression).getContainingClass(); - if (containingClass != null) { - final String qFixName = containingClass.getProperties().containsKey(StringUtil.trimLeading(name, '_')) ? + final String newName = StringUtil.trimLeading(name, '_'); + if (resolvedClass != null) { + final String qFixName = resolvedClass.getProperties().containsKey(newName) ? PyBundle.message("QFIX.use.property") : PyBundle.message("QFIX.add.property"); quickFixes.add(new PyAddPropertyForFieldQuickFix(qFixName)); + + final Collection usedNames = PyRefactoringUtil.collectUsedNames(resolvedClass); + if (!usedNames.contains(newName)) { + quickFixes.add(new PyMakePublicQuickFix()); + } } } final PyClass parentClass = getClassOwner(node); if (parentClass != null) { if (PyTestUtil.isPyTestClass(parentClass) && ignoreTestFunctions) return; - final PyClass resolvedClass = getClassOwner(resolvedExpression); if (parentClass.isSubclass(resolvedClass)) return; diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMakePublicQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMakePublicQuickFix.java new file mode 100644 index 000000000000..33ab125d0362 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMakePublicQuickFix.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections.quickfix; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.refactoring.rename.RenameProcessor; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.PyTargetExpression; +import org.jetbrains.annotations.NotNull; + +public class PyMakePublicQuickFix implements LocalQuickFix { + @NotNull + @Override + public String getName() { + return PyBundle.message("QFIX.make.public"); + } + + @NotNull + @Override + public String getFamilyName() { + return PyBundle.message("QFIX.make.public"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element instanceof PyReferenceExpression) { + final PsiReference reference = element.getReference(); + if (reference == null) return; + element = reference.resolve(); + } + if (element instanceof PyTargetExpression) { + final String name = ((PyTargetExpression)element).getName(); + if (name == null) return; + final VirtualFile virtualFile = element.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + final String publicName = StringUtil.trimLeading(name, '_'); + new RenameProcessor(project, element, publicName, false, false).run(); + } + } + } +} diff --git a/python/testData/quickFixes/PyMakePublicQuickFixTest/negative.py b/python/testData/quickFixes/PyMakePublicQuickFixTest/negative.py new file mode 100644 index 000000000000..c5c062f478b9 --- /dev/null +++ b/python/testData/quickFixes/PyMakePublicQuickFixTest/negative.py @@ -0,0 +1,16 @@ +class A: + @property + def x(self): + return self._x + + def __init__(self): + self._x = 1 + + def _foo(self): + print(self._x) + +a = A() +a._foo() + +print(a.x) +print(a._x) \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakePublicQuickFixTest/positive.py b/python/testData/quickFixes/PyMakePublicQuickFixTest/positive.py new file mode 100644 index 000000000000..ebf34683ffaa --- /dev/null +++ b/python/testData/quickFixes/PyMakePublicQuickFixTest/positive.py @@ -0,0 +1,11 @@ +class A: + def __init__(self): + self._x = 1 + + def _foo(self): + print(self._x) + +a = A() +a._foo() + +print(a._x) \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakePublicQuickFixTest/positive_after.py b/python/testData/quickFixes/PyMakePublicQuickFixTest/positive_after.py new file mode 100644 index 000000000000..747456fbefe6 --- /dev/null +++ b/python/testData/quickFixes/PyMakePublicQuickFixTest/positive_after.py @@ -0,0 +1,11 @@ +class A: + def __init__(self): + self.x = 1 + + def _foo(self): + print(self.x) + +a = A() +a._foo() + +print(a.x) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyMakePublicQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyMakePublicQuickFixTest.java new file mode 100644 index 000000000000..fb1a6c89cbfe --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyMakePublicQuickFixTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.quickFixes; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.testFramework.TestDataPath; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyQuickFixTestCase; +import com.jetbrains.python.inspections.PyProtectedMemberInspection; + +@TestDataPath("$CONTENT_ROOT/../testData/quickFixes/PyMakePublicQuickFixTest") +public class PyMakePublicQuickFixTest extends PyQuickFixTestCase { + public void testPositive() { + doQuickFixTest(PyProtectedMemberInspection.class, PyBundle.message("QFIX.make.public")); + } + + public void testNegative() { + final String testFileName = getTestName(true); + myFixture.enableInspections(PyProtectedMemberInspection.class); + myFixture.configureByFile(testFileName + ".py"); + myFixture.checkHighlighting(true, false, false); + final IntentionAction intentionAction = myFixture.getAvailableIntention(PyBundle.message("QFIX.make.public")); + assertNull(intentionAction); + } +}