Skip to content

Commit 33baecc

Browse files
committed
First attempt at Access Control annotations
1 parent 1a894cb commit 33baecc

Some content is hidden

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

44 files changed

+1035
-40
lines changed

src/main/java/com/intellij/plugins/haxe/ide/HaxeDocumentationProvider.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ public String generateDoc(PsiElement element, PsiElement originalElement) {
102102

103103
HaxeNamedComponent namedComponent = getNamedComponent(element);
104104
if (namedComponent == null) {
105-
105+
if(element instanceof HaxeModule module) {
106+
createModuleDocs(mainBuilder, module);
107+
}
106108
if (element instanceof HaxeLiteralExpression) {
107109
return null; // no need to show docs for literal expressions
108110
}else {
@@ -137,6 +139,19 @@ public String generateDoc(PsiElement element, PsiElement originalElement) {
137139
return mainBuilder.toString();
138140
}
139141

142+
private void createModuleDocs(HtmlBuilder mainBuilder, HaxeModule module) {
143+
if( module.getModel() instanceof HaxeModuleModel model) {
144+
String qname = model.getPackageName();
145+
StringBuilder stringBuilder = new StringBuilder();
146+
DocumentationManagerUtil.createHyperlink(stringBuilder, qname, qname, false, true);
147+
mainBuilder.append(HtmlChunk.icon("AllIcons.Nodes.Package", AllIcons.Nodes.Package)).nbsp(1);
148+
mainBuilder.appendRaw(stringBuilder.toString()).br();
149+
150+
mainBuilder.appendRaw("Module " + model.getName()).br();
151+
//TODO list members with links
152+
}
153+
}
154+
140155
@Override
141156
public @Nls @Nullable String generateRenderedDoc(@NotNull PsiDocCommentBase comment) {
142157
if(comment instanceof haxePsiDocCommentImpl haxeDocComment) {
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package com.intellij.plugins.haxe.ide.annotator.semantics;
2+
3+
import com.intellij.lang.annotation.AnnotationHolder;
4+
import com.intellij.lang.annotation.Annotator;
5+
import com.intellij.lang.annotation.HighlightSeverity;
6+
import com.intellij.plugins.haxe.lang.psi.*;
7+
import com.intellij.plugins.haxe.metadata.HaxeMetadataList;
8+
import com.intellij.plugins.haxe.metadata.psi.HaxeMeta;
9+
import com.intellij.plugins.haxe.metadata.psi.HaxeMetadataCompileTimeMeta;
10+
import com.intellij.plugins.haxe.metadata.psi.HaxeMetadataContent;
11+
import com.intellij.plugins.haxe.metadata.psi.impl.HaxeMetadataTypeName;
12+
import com.intellij.plugins.haxe.metadata.util.HaxeMetadataUtils;
13+
import com.intellij.plugins.haxe.model.*;
14+
import com.intellij.plugins.haxe.model.fixer.HaxeFixer;
15+
import com.intellij.psi.PsiElement;
16+
import com.intellij.psi.PsiPackage;
17+
import com.intellij.psi.util.PsiTreeUtil;
18+
import org.jetbrains.annotations.NotNull;
19+
import org.jetbrains.annotations.Nullable;
20+
21+
import java.util.List;
22+
23+
import static com.intellij.plugins.haxe.metadata.psi.HaxeMeta.*;
24+
25+
public class HaxeAccessAnnotator implements Annotator {
26+
@Override
27+
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
28+
if (element instanceof HaxeReferenceExpression referenceExpression) {
29+
// we want to ignore references used in package, type or metas
30+
if (checkIfShouldBeIgnored(referenceExpression)) return;
31+
checkAccessForReference(referenceExpression, holder);
32+
}
33+
34+
else if (element instanceof HaxeCompiletimeMetaArg compileTimeMeta) {
35+
HaxeMeta haxeMeta = PsiTreeUtil.getParentOfType(compileTimeMeta, HaxeMeta.class);
36+
if (haxeMeta != null) {
37+
if (haxeMeta.isType(ALLOW) || haxeMeta.isType(ACCESS)) {
38+
checkIfFullyQualified(haxeMeta, holder);
39+
}
40+
}
41+
}
42+
}
43+
44+
45+
private static boolean checkIfShouldBeIgnored(HaxeReferenceExpression referenceExpression) {
46+
PsiElement expressionParent = referenceExpression.getParent();
47+
if(expressionParent instanceof HaxePackageStatement) return true;
48+
if(expressionParent instanceof HaxeType) return true;
49+
50+
HaxeMeta haxeMeta = PsiTreeUtil.getParentOfType(expressionParent, HaxeMeta.class);
51+
if(haxeMeta instanceof HaxeCompiletimeMetaArg) return true;
52+
return false;
53+
}
54+
55+
private void checkAccessForReference(@NotNull HaxeReferenceExpression referenceExpression, @NotNull AnnotationHolder holder) {
56+
57+
58+
HaxeClass memberClass = null;
59+
HaxeMemberModel memberModel = null;
60+
String memberName = null;
61+
62+
PsiElement resolve = referenceExpression.resolve();
63+
64+
if (resolve instanceof HaxeFieldDeclaration fieldDeclaration) {
65+
HaxeModel model = fieldDeclaration.getModel();
66+
if (model instanceof HaxeMemberModel haxeMemberModel) {
67+
if (haxeMemberModel.isPublic()) return;
68+
memberModel = haxeMemberModel;
69+
memberName = haxeMemberModel.getName();
70+
memberClass = getMemberHaxeClass(haxeMemberModel);
71+
}
72+
}
73+
if (resolve instanceof HaxeMethodDeclaration methodDeclaration) {
74+
HaxeMethodModel model = methodDeclaration.getModel();
75+
if (model instanceof HaxeMemberModel haxeMemberModel) {
76+
if (haxeMemberModel.isPublic()) return;
77+
memberModel = haxeMemberModel;
78+
memberName = haxeMemberModel.getName();
79+
memberClass = getMemberHaxeClass(haxeMemberModel);
80+
}
81+
}
82+
83+
84+
85+
// ignore if we cant find member (probably a reference to a type)
86+
if (memberModel == null) return;
87+
88+
HaxeClass currentClass = PsiTreeUtil.getParentOfType(referenceExpression, HaxeClass.class);
89+
90+
// ignoring anonymous types for now
91+
if (memberClass != null) {
92+
if (memberClass.isAnonymousType() || memberClass.isObjectLiteralType()) return;
93+
}
94+
if (currentClass != null) {
95+
if (currentClass.isAnonymousType() || currentClass.isObjectLiteralType()) return;
96+
}
97+
98+
if (memberClass == currentClass) {
99+
// if same class then private access allowed
100+
return;
101+
}
102+
103+
if (inheritsFrom(currentClass, memberClass)) {
104+
// if inherited member then private access allowed
105+
return;
106+
}
107+
108+
HaxeMemberModel referenceParentModel = getExpressionsParentsModel(referenceExpression);
109+
if (expressionHasPrivateAccessMeta(referenceExpression)) {
110+
// @:privateAccess should allow access to normal private members
111+
return;
112+
}
113+
if (hasAllowMetaFor(currentClass, memberClass, memberModel, referenceParentModel)) {
114+
return;
115+
}
116+
if (hasAccessMetaFor(currentClass, memberClass, memberModel, referenceParentModel)) {
117+
return;
118+
}
119+
120+
// TODO bundle
121+
holder.newAnnotation(HighlightSeverity.ERROR, "Cannot access private field " + memberName)
122+
.range(referenceExpression.getLastChild())
123+
.create();
124+
125+
}
126+
127+
private void checkIfFullyQualified(HaxeMeta meta, @NotNull AnnotationHolder holder) {
128+
HaxeReferenceExpression metaReference = getAccessMetaReference(meta);
129+
if (metaReference != null) {
130+
PsiElement target = metaReference.resolve();
131+
String metaReferenceText = metaReference.getQualifiedName();
132+
String qualifiedName = null;
133+
if (target instanceof PsiPackage aPackage) {
134+
qualifiedName = aPackage.getQualifiedName();
135+
} else if (target instanceof HaxeClass aClass) {
136+
qualifiedName = aClass.getQualifiedName();
137+
} else if (target instanceof HaxeMethod method) {
138+
qualifiedName = method.getModel().getQualifiedInfo().toShortendImportReferenceString();
139+
}
140+
141+
if(qualifiedName != null) {
142+
if (!qualifiedName.equals(metaReferenceText)) {
143+
String finalQualifiedName = qualifiedName;
144+
holder.newAnnotation(HighlightSeverity.WEAK_WARNING, "Reference is not fully qualified")
145+
.withFix(new HaxeFixer("Replace with fully qualified path") {
146+
@Override
147+
public void run() {
148+
HaxeDocumentModel.fromElement(metaReference).replaceElementText(metaReference, finalQualifiedName);
149+
}
150+
})
151+
.create();
152+
}
153+
}
154+
}
155+
}
156+
157+
158+
159+
private boolean expressionHasPrivateAccessMeta(HaxeReferenceExpression referenceExpression) {
160+
HaxeReferenceExpression refExpression = referenceExpression;
161+
while (refExpression.getParent() instanceof HaxeReferenceExpression parent) refExpression = parent;
162+
163+
PsiElement expression = referenceExpression;
164+
while (true) {
165+
if (HaxeMetadataUtils.hasMeta(expression, HaxeMetadataCompileTimeMeta.class, PRIVATE_ACCESS)) {
166+
return true;
167+
} else {
168+
expression = expression.getParent();
169+
if (expression instanceof HaxeMethodDeclaration || expression instanceof HaxeClass || expression instanceof HaxeModule) {
170+
break;// stop search if we have left any "reasonable" scope
171+
}
172+
}
173+
}
174+
return false;
175+
}
176+
177+
private boolean hasAccessMetaFor(@Nullable HaxeClass currentClass, @Nullable HaxeClass memberClass, HaxeMemberModel memberModel, HaxeMemberModel referenceParentModel) {
178+
HaxeMetadataList metadataList = collectMetadata(currentClass, referenceParentModel, ACCESS);
179+
for (HaxeMeta metadata : metadataList) {
180+
PsiElement target = getAccessMetaTarget(metadata);
181+
//TODO
182+
if(target instanceof PsiPackage aPackage) {
183+
if(memberModel.getPackage() == aPackage) {
184+
return true;
185+
}
186+
}
187+
else if(target instanceof HaxeModule module) {
188+
if(memberModel.getModule() == module){
189+
return true;
190+
}
191+
}
192+
else if(target instanceof HaxeClass aClass) {
193+
if(memberClass == aClass){
194+
return true;
195+
}
196+
if(inheritsFrom(memberClass, aClass)) {
197+
return true;
198+
}
199+
//we allow both directions (tests )
200+
if(inheritsFrom(aClass, memberClass)) {
201+
return true;
202+
}
203+
}
204+
else if(target instanceof HaxeMethod method) {
205+
if(memberModel.getMemberPsi() == method) {
206+
return true;
207+
}
208+
}
209+
}
210+
return false;
211+
}
212+
private boolean hasAllowMetaFor(@Nullable HaxeClass currentClass, @Nullable HaxeClass memberClass, HaxeMemberModel memberModel, HaxeMemberModel referenceParentModel) {
213+
HaxeMetadataList metadataList = collectMetadata(memberClass, memberModel, ALLOW);
214+
215+
for (HaxeMeta metadata : metadataList) {
216+
PsiElement target = getAccessMetaTarget(metadata);
217+
218+
if(target instanceof PsiPackage aPackage) {
219+
if(referenceParentModel.getPackage() == aPackage) {
220+
return true;
221+
}
222+
}
223+
else if(target instanceof HaxeModule module) {
224+
if(referenceParentModel.getModule() == module){
225+
return true;
226+
}
227+
}
228+
else if(target instanceof HaxeClass aClass) {
229+
if(currentClass == aClass){
230+
return true;
231+
}
232+
}
233+
else if(target instanceof HaxeMethod method) {
234+
if(referenceParentModel.getMemberPsi() == method) {
235+
return true;
236+
}
237+
}
238+
}
239+
240+
241+
return false;
242+
}
243+
244+
private static @Nullable HaxeReferenceExpression getAccessMetaReference(HaxeMeta metadata) {
245+
HaxeMetadataContent content = metadata.getContent();
246+
List<HaxeExpression> expressions = HaxeMetadataUtils.getCompileTimeExpressions(content);
247+
if(!expressions.isEmpty()) {
248+
if(expressions.getFirst() instanceof HaxeReferenceExpression expression ) {
249+
return expression;
250+
}
251+
}
252+
return null;
253+
}
254+
private static @Nullable PsiElement getAccessMetaTarget(HaxeMeta metadata) {
255+
HaxeReferenceExpression reference = getAccessMetaReference(metadata);
256+
if(reference != null) return reference.resolve();
257+
return null;
258+
}
259+
260+
private static @NotNull HaxeMetadataList collectMetadata(HaxeClass memberClass, HaxeMemberModel memberModel, HaxeMetadataTypeName metadataTypeName) {
261+
HaxeMetadataList metadataFromClass = HaxeMetadataUtils.getMetadataList(memberClass, HaxeMetadataCompileTimeMeta.class, metadataTypeName);
262+
if(memberModel != null) {
263+
HaxeMetadataList metadataFromMember = HaxeMetadataUtils.getMetadataList(memberModel.getMemberPsi(), HaxeMetadataCompileTimeMeta.class, ALLOW);
264+
metadataFromClass.addAll(metadataFromMember);
265+
}
266+
return metadataFromClass;
267+
}
268+
269+
private static @Nullable HaxeMemberModel getExpressionsParentsModel(@NotNull HaxeReferenceExpression referenceExpression) {
270+
HaxeMethodDeclaration methodDeclaration = PsiTreeUtil.getParentOfType(referenceExpression, HaxeMethodDeclaration.class);
271+
HaxeMemberModel referenceParentModel = methodDeclaration == null ? null : methodDeclaration.getModel();
272+
if(referenceParentModel == null) {
273+
HaxeFieldDeclaration fieldDeclaration = PsiTreeUtil.getParentOfType(referenceExpression, HaxeFieldDeclaration.class);
274+
referenceParentModel = fieldDeclaration == null ? null : (HaxeMemberModel) fieldDeclaration.getModel();
275+
}
276+
return referenceParentModel;
277+
}
278+
279+
private static boolean inheritsFrom(@Nullable HaxeClass currentClass, @Nullable HaxeClass memberClass) {
280+
if(currentClass == null || memberClass == null) return false;
281+
return currentClass.getModel().inheritsFrom(memberClass);
282+
}
283+
284+
@Nullable
285+
private static HaxeClass getMemberHaxeClass(HaxeMemberModel model) {
286+
HaxeClassModel declaringClass = model.getDeclaringClass();
287+
if(declaringClass != null) return declaringClass.haxeClass;
288+
return null;
289+
}
290+
291+
292+
}

src/main/java/com/intellij/plugins/haxe/lang/parser/haxe.bnf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ packageStatement ::= 'package' simpleQualifiedReferenceExpression? ';'
261261
{pin=1 implements="com.intellij.plugins.haxe.lang.psi.HaxePackageStatementPsiMixin" mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxePackageStatementPsiMixinImpl"}
262262

263263
private topLevelList ::= topLevel*
264-
module ::= moduleBody { mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeModulePsiMixinImpl"}
264+
module ::= moduleBody { mixin="com.intellij.plugins.haxe.lang.psi.impl.HaxeModulePsiMixinImpl" implements=["com.intellij.plugins.haxe.lang.psi.HaxePsiCompositeElement" "com.intellij.plugins.haxe.model.HaxeModelTarget"]}
265265
private topLevel ::= importStatement | usingStatement | module {recoverWhile="top_level_recover" name="import, using, or top level declaration"}
266266
//private topLevel ::= importStatement | usingStatement | (embeddedMeta* topLevelDeclaration) | moduleDeclaration {recoverWhile="top_level_recover" name="import, using, or top level declaration"}
267267
private top_level_recover ::= !( 'import' | 'using' | 'class' | 'abstract' | 'interface' | 'typedef' | 'enum' | 'function' | 'public' | 'private' | 'extern' | 'final' | 'var' | 'inline' | metaKeyWord | ppToken)

src/main/java/com/intellij/plugins/haxe/lang/psi/HaxeClass.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.intellij.plugins.haxe.model.type.HaxeGenericResolver;
3333
import com.intellij.plugins.haxe.model.type.SpecificTypeReference;
3434
import com.intellij.psi.PsiClass;
35+
import com.intellij.psi.PsiPackage;
3536
import org.jetbrains.annotations.NonNls;
3637
import org.jetbrains.annotations.NotNull;
3738
import org.jetbrains.annotations.Nullable;
@@ -180,4 +181,8 @@ default boolean hasCompileTimeMeta(HaxeMetadataTypeName meta) {
180181
default HaxeMetadataList getCompileTimeMeta(HaxeMetadataTypeName meta) {
181182
return HaxeMetadataUtils.getMetadataList(this, HaxeMetadataCompileTimeMeta.class, meta);
182183
}
184+
185+
PsiPackage getPackage();
186+
187+
HaxeModule getModule();
183188
}

0 commit comments

Comments
 (0)