|
36 | 36 | import consulo.language.psi.PsiElementVisitor; |
37 | 37 | import consulo.language.psi.PsiReference; |
38 | 38 | import consulo.language.psi.util.PsiTreeUtil; |
| 39 | +import consulo.localize.LocalizeValue; |
| 40 | +import consulo.python.impl.localize.PyLocalize; |
39 | 41 | import consulo.util.lang.StringUtil; |
40 | | -import org.jetbrains.annotations.Nls; |
41 | | - |
42 | 42 | import jakarta.annotation.Nonnull; |
43 | 43 | import jakarta.annotation.Nullable; |
| 44 | + |
44 | 45 | import java.util.ArrayList; |
45 | 46 | import java.util.Collection; |
46 | 47 | import java.util.List; |
47 | 48 |
|
48 | 49 | /** |
49 | | - * User: ktisha |
50 | | - * <p> |
51 | 50 | * Inspection to detect situations, where |
52 | 51 | * protected member (i.e. class member with a name beginning with an underscore) |
53 | 52 | * is access outside the class or a descendant of the class where it's defined. |
| 53 | + * |
| 54 | + * @author ktisha |
54 | 55 | */ |
55 | 56 | @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 | + } |
225 | 203 | } |
0 commit comments