Skip to content

Commit 0cf6f55

Browse files
committed
Localizing inspection, intentions and quick fixes (part 2).
1 parent 4a829f4 commit 0cf6f55

File tree

50 files changed

+3013
-3374
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3013
-3374
lines changed

python-impl/src/main/java/com/jetbrains/python/impl/inspections/PyProtectedMemberInspection.java

Lines changed: 151 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -36,190 +36,168 @@
3636
import consulo.language.psi.PsiElementVisitor;
3737
import consulo.language.psi.PsiReference;
3838
import consulo.language.psi.util.PsiTreeUtil;
39+
import consulo.localize.LocalizeValue;
40+
import consulo.python.impl.localize.PyLocalize;
3941
import consulo.util.lang.StringUtil;
40-
import org.jetbrains.annotations.Nls;
41-
4242
import jakarta.annotation.Nonnull;
4343
import jakarta.annotation.Nullable;
44+
4445
import java.util.ArrayList;
4546
import java.util.Collection;
4647
import java.util.List;
4748

4849
/**
49-
* User: ktisha
50-
* <p>
5150
* Inspection to detect situations, where
5251
* protected member (i.e. class member with a name beginning with an underscore)
5352
* is access outside the class or a descendant of the class where it's defined.
53+
*
54+
* @author ktisha
5455
*/
5556
@ExtensionImpl
56-
public class PyProtectedMemberInspection extends PyInspection
57-
{
58-
@Nonnull
59-
@Override
60-
public InspectionToolState<?> createStateProvider()
61-
{
62-
return new PyProtectedMemberInspectionState();
63-
}
64-
65-
@Nls
66-
@Nonnull
67-
@Override
68-
public String getDisplayName()
69-
{
70-
return PyBundle.message("INSP.NAME.protected.member.access");
71-
}
72-
73-
@Nonnull
74-
@Override
75-
public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder,
76-
boolean isOnTheFly,
77-
@Nonnull LocalInspectionToolSession session,
78-
Object state)
79-
{
80-
PyProtectedMemberInspectionState inspectionState = (PyProtectedMemberInspectionState) state;
81-
return new Visitor(holder, session, inspectionState);
82-
}
83-
84-
85-
private class Visitor extends PyInspectionVisitor
86-
{
87-
private final PyProtectedMemberInspectionState myState;
88-
89-
public Visitor(@Nullable ProblemsHolder holder, @Nonnull LocalInspectionToolSession session, PyProtectedMemberInspectionState inspectionState)
90-
{
91-
super(holder, session);
92-
myState = inspectionState;
93-
}
94-
95-
@Override
96-
public void visitPyImportElement(PyImportElement node)
97-
{
98-
final PyStatement statement = node.getContainingImportStatement();
99-
if(!(statement instanceof PyFromImportStatement))
100-
{
101-
return;
102-
}
103-
final PyReferenceExpression importReferenceExpression = node.getImportReferenceExpression();
104-
final PyReferenceExpression importSource = ((PyFromImportStatement) statement).getImportSource();
105-
if(importReferenceExpression != null && importSource != null && !isImportFromTheSamePackage(importSource))
106-
{
107-
checkReference(importReferenceExpression, importSource);
108-
}
109-
}
110-
111-
private boolean isImportFromTheSamePackage(PyReferenceExpression importSource)
112-
{
113-
PsiDirectory directory = importSource.getContainingFile().getContainingDirectory();
114-
if(directory != null && PyUtil.isPackage(directory, true, importSource.getContainingFile()) &&
115-
directory.getName().equals(importSource.getName()))
116-
{
117-
return true;
118-
}
119-
return false;
120-
}
121-
122-
@Override
123-
public void visitPyReferenceExpression(PyReferenceExpression node)
124-
{
125-
final PyExpression qualifier = node.getQualifier();
126-
if(myState.ignoreAnnotations && PsiTreeUtil.getParentOfType(node, PyAnnotation.class) != null)
127-
{
128-
return;
129-
}
130-
if(qualifier == null || PyNames.CANONICAL_SELF.equals(qualifier.getText()))
131-
{
132-
return;
133-
}
134-
checkReference(node, qualifier);
135-
}
136-
137-
private void checkReference(@Nonnull final PyReferenceExpression node, @Nonnull final PyExpression qualifier)
138-
{
139-
if(myTypeEvalContext.getType(qualifier) instanceof PyNamedTupleType)
140-
{
141-
return;
142-
}
143-
final String name = node.getName();
144-
final List<LocalQuickFix> quickFixes = new ArrayList<>();
145-
quickFixes.add(new PyRenameElementQuickFix());
146-
147-
if(name != null && name.startsWith("_") && !name.startsWith("__") && !name.endsWith("__"))
148-
{
149-
final PsiReference reference = node.getReference(getResolveContext());
150-
for(final PyInspectionExtension inspectionExtension : PyInspectionExtension.EP_NAME.getExtensions())
151-
{
152-
if(inspectionExtension.ignoreProtectedSymbol(node, myTypeEvalContext))
153-
{
154-
return;
155-
}
156-
}
157-
final PsiElement resolvedExpression = reference.resolve();
158-
final PyClass resolvedClass = getClassOwner(resolvedExpression);
159-
if(resolvedExpression instanceof PyTargetExpression)
160-
{
161-
final String newName = StringUtil.trimLeading(name, '_');
162-
if(resolvedClass != null)
163-
{
164-
final String qFixName =
165-
resolvedClass.getProperties().containsKey(newName) ? PyBundle.message("QFIX.use.property") : PyBundle.message(
166-
"QFIX.add.property");
167-
quickFixes.add(new PyAddPropertyForFieldQuickFix(qFixName));
168-
169-
final Collection<String> usedNames = PyRefactoringUtil.collectUsedNames(resolvedClass);
170-
if(!usedNames.contains(newName))
171-
{
172-
quickFixes.add(new PyMakePublicQuickFix());
173-
}
174-
}
175-
}
176-
177-
final PyClass parentClass = getClassOwner(node);
178-
if(parentClass != null)
179-
{
180-
if(PyTestUtil.isPyTestClass(parentClass, null) && myState.ignoreTestFunctions)
181-
{
182-
return;
183-
}
184-
185-
if(parentClass.isSubclass(resolvedClass, myTypeEvalContext))
186-
{
187-
return;
188-
}
189-
190-
PyClass outerClass = getClassOwner(parentClass);
191-
while(outerClass != null)
192-
{
193-
if(outerClass.isSubclass(resolvedClass, myTypeEvalContext))
194-
{
195-
return;
196-
}
197-
198-
outerClass = getClassOwner(outerClass);
199-
}
200-
}
201-
final PyType type = myTypeEvalContext.getType(qualifier);
202-
final String bundleKey =
203-
type instanceof PyModuleType ? "INSP.protected.member.$0.access.module" : "INSP.protected.member.$0.access";
204-
registerProblem(node,
205-
PyBundle.message(bundleKey, name),
206-
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
207-
null,
208-
quickFixes.toArray(new LocalQuickFix[quickFixes.size() - 1]));
209-
}
210-
}
211-
212-
@Nullable
213-
private PyClass getClassOwner(@Nullable PsiElement element)
214-
{
215-
for(ScopeOwner owner = ScopeUtil.getScopeOwner(element); owner != null; owner = ScopeUtil.getScopeOwner(owner))
216-
{
217-
if(owner instanceof PyClass)
218-
{
219-
return (PyClass) owner;
220-
}
221-
}
222-
return null;
223-
}
224-
}
57+
public class PyProtectedMemberInspection extends PyInspection {
58+
@Nonnull
59+
@Override
60+
public InspectionToolState<?> createStateProvider() {
61+
return new PyProtectedMemberInspectionState();
62+
}
63+
64+
@Nonnull
65+
@Override
66+
public LocalizeValue getDisplayName() {
67+
return PyLocalize.inspNameProtectedMemberAccess();
68+
}
69+
70+
@Nonnull
71+
@Override
72+
public PsiElementVisitor buildVisitor(
73+
@Nonnull ProblemsHolder holder,
74+
boolean isOnTheFly,
75+
@Nonnull LocalInspectionToolSession session,
76+
Object state
77+
) {
78+
PyProtectedMemberInspectionState inspectionState = (PyProtectedMemberInspectionState) state;
79+
return new Visitor(holder, session, inspectionState);
80+
}
81+
82+
83+
private class Visitor extends PyInspectionVisitor {
84+
private final PyProtectedMemberInspectionState myState;
85+
86+
public Visitor(
87+
@Nullable ProblemsHolder holder,
88+
@Nonnull LocalInspectionToolSession session,
89+
PyProtectedMemberInspectionState inspectionState
90+
) {
91+
super(holder, session);
92+
myState = inspectionState;
93+
}
94+
95+
@Override
96+
public void visitPyImportElement(PyImportElement node) {
97+
final PyStatement statement = node.getContainingImportStatement();
98+
if (!(statement instanceof PyFromImportStatement)) {
99+
return;
100+
}
101+
final PyReferenceExpression importReferenceExpression = node.getImportReferenceExpression();
102+
final PyReferenceExpression importSource = ((PyFromImportStatement) statement).getImportSource();
103+
if (importReferenceExpression != null && importSource != null && !isImportFromTheSamePackage(importSource)) {
104+
checkReference(importReferenceExpression, importSource);
105+
}
106+
}
107+
108+
private boolean isImportFromTheSamePackage(PyReferenceExpression importSource) {
109+
PsiDirectory directory = importSource.getContainingFile().getContainingDirectory();
110+
if (directory != null && PyUtil.isPackage(directory, true, importSource.getContainingFile()) &&
111+
directory.getName().equals(importSource.getName())) {
112+
return true;
113+
}
114+
return false;
115+
}
116+
117+
@Override
118+
public void visitPyReferenceExpression(PyReferenceExpression node) {
119+
final PyExpression qualifier = node.getQualifier();
120+
if (myState.ignoreAnnotations && PsiTreeUtil.getParentOfType(node, PyAnnotation.class) != null) {
121+
return;
122+
}
123+
if (qualifier == null || PyNames.CANONICAL_SELF.equals(qualifier.getText())) {
124+
return;
125+
}
126+
checkReference(node, qualifier);
127+
}
128+
129+
private void checkReference(@Nonnull final PyReferenceExpression node, @Nonnull final PyExpression qualifier) {
130+
if (myTypeEvalContext.getType(qualifier) instanceof PyNamedTupleType) {
131+
return;
132+
}
133+
final String name = node.getName();
134+
final List<LocalQuickFix> quickFixes = new ArrayList<>();
135+
quickFixes.add(new PyRenameElementQuickFix());
136+
137+
if (name != null && name.startsWith("_") && !name.startsWith("__") && !name.endsWith("__")) {
138+
final PsiReference reference = node.getReference(getResolveContext());
139+
for (final PyInspectionExtension inspectionExtension : PyInspectionExtension.EP_NAME.getExtensions()) {
140+
if (inspectionExtension.ignoreProtectedSymbol(node, myTypeEvalContext)) {
141+
return;
142+
}
143+
}
144+
final PsiElement resolvedExpression = reference.resolve();
145+
final PyClass resolvedClass = getClassOwner(resolvedExpression);
146+
if (resolvedExpression instanceof PyTargetExpression) {
147+
final String newName = StringUtil.trimLeading(name, '_');
148+
if (resolvedClass != null) {
149+
LocalizeValue qFixName = resolvedClass.getProperties().containsKey(newName)
150+
? PyLocalize.qfixUseProperty()
151+
: PyLocalize.qfixAddProperty();
152+
quickFixes.add(new PyAddPropertyForFieldQuickFix(qFixName));
153+
154+
final Collection<String> usedNames = PyRefactoringUtil.collectUsedNames(resolvedClass);
155+
if (!usedNames.contains(newName)) {
156+
quickFixes.add(new PyMakePublicQuickFix());
157+
}
158+
}
159+
}
160+
161+
final PyClass parentClass = getClassOwner(node);
162+
if (parentClass != null) {
163+
if (PyTestUtil.isPyTestClass(parentClass, null) && myState.ignoreTestFunctions) {
164+
return;
165+
}
166+
167+
if (parentClass.isSubclass(resolvedClass, myTypeEvalContext)) {
168+
return;
169+
}
170+
171+
PyClass outerClass = getClassOwner(parentClass);
172+
while (outerClass != null) {
173+
if (outerClass.isSubclass(resolvedClass, myTypeEvalContext)) {
174+
return;
175+
}
176+
177+
outerClass = getClassOwner(outerClass);
178+
}
179+
}
180+
final PyType type = myTypeEvalContext.getType(qualifier);
181+
final String bundleKey =
182+
type instanceof PyModuleType ? "INSP.protected.member.$0.access.module" : "INSP.protected.member.$0.access";
183+
registerProblem(
184+
node,
185+
PyBundle.message(bundleKey, name),
186+
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
187+
null,
188+
quickFixes.toArray(new LocalQuickFix[quickFixes.size() - 1])
189+
);
190+
}
191+
}
192+
193+
@Nullable
194+
private PyClass getClassOwner(@Nullable PsiElement element) {
195+
for (ScopeOwner owner = ScopeUtil.getScopeOwner(element); owner != null; owner = ScopeUtil.getScopeOwner(owner)) {
196+
if (owner instanceof PyClass) {
197+
return (PyClass) owner;
198+
}
199+
}
200+
return null;
201+
}
202+
}
225203
}

0 commit comments

Comments
 (0)