package com.intellij.javascript.flex.resolve;
import com.intellij.lang.javascript.index.JSSymbolUtil;
import com.intellij.lang.javascript.psi.JSElement;
import com.intellij.lang.javascript.psi.JSExpression;
import com.intellij.lang.javascript.psi.JSPsiElementBase;
import com.intellij.lang.javascript.psi.JSReferenceExpression;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList;
import com.intellij.lang.javascript.psi.ecmal4.JSAttributeListOwner;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.impl.JSPsiImplUtils;
import com.intellij.lang.javascript.psi.resolve.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveResult;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
/**
* @author Konstantin.Ulitin
*/
public class ActionScriptAccessibilityProcessingHandler extends AccessibilityProcessingHandler {
private Map<String, String> openedNses;
private boolean defaultNsIsNotAllowed;
private boolean anyNsAllowed;
public ActionScriptAccessibilityProcessingHandler(@Nullable PsiElement _place, boolean skipNsResolving) {
super(_place);
if (place instanceof JSReferenceExpression) {
final JSReferenceExpression namespace = ((JSReferenceExpression)place).getNamespaceElement();
// TODO: e.g. protected is also ns
if (namespace != null) {
String ns = skipNsResolving ? namespace.getText() : JSPsiImplUtils.calcNamespaceReference(place);
if (ns != null) {
openedNses = new THashMap<>(1);
openedNses.put(ns, null);
defaultNsIsNotAllowed = true;
}
else {
anyNsAllowed = true;
}
}
}
}
@Override
protected boolean acceptsForMembersVisibility(@NotNull JSPsiElementBase element, @NotNull SinkResolveProcessor resolveProcessor) {
if (!(element instanceof JSAttributeListOwner)) return true;
final JSAttributeList attributeList = ((JSAttributeListOwner)element).getAttributeList();
if (JSResolveUtil.getClassOfContext(place) != JSResolveUtil.getClassOfContext(element)) {
if (!acceptPrivateMembers) {
if (attributeList != null && attributeList.getAccessType() == JSAttributeList.AccessType.PRIVATE) {
resolveProcessor.addPossibleCandidateResult(element, JSResolveResult.PRIVATE_MEMBER_IS_NOT_ACCESSIBLE);
return false;
}
}
if (!acceptProtectedMembers) {
if (attributeList != null &&
attributeList.getAccessType() == JSAttributeList.AccessType.PROTECTED
) {
// we are resolving in context of the class or element within context of the class
if ((myClassScopeTypeName != null || isParentClassContext(element))) {
resolveProcessor.addPossibleCandidateResult(element, JSResolveResult.PROTECTED_MEMBER_IS_NOT_ACCESSIBLE);
return false;
} // if element / context out of class then protected element is ok due to includes
}
}
}
PsiElement elt = JSResolveUtil.findParent(element);
if (processStatics) {
if ((attributeList == null || !attributeList.hasModifier(JSAttributeList.ModifierType.STATIC))) {
if (JSResolveUtil.PROTOTYPE_FIELD_NAME.equals(resolveProcessor.getName())) return true;
resolveProcessor.addPossibleCandidateResult(element, JSResolveResult.INSTANCE_MEMBER_INACCESSIBLE);
return false;
}
if (myTypeName != null && elt instanceof JSClass && !myTypeName.equals(((JSClass)elt).getQualifiedName())) {
// static members are inherited in TypeScript classes
resolveProcessor.addPossibleCandidateResult(element, JSResolveResult.STATIC_MEMBER_INACCESSIBLE);
return false;
}
}
else if (myClassDeclarationStarted && !allowUnqualifiedStaticsFromInstance) {
// ActionScript only?
if (attributeList != null && attributeList.hasModifier(JSAttributeList.ModifierType.STATIC)) {
boolean referencingClass = false;
if (place instanceof JSReferenceExpression) {
JSExpression qualifier = ((JSReferenceExpression)place).getQualifier();
if (qualifier instanceof JSReferenceExpression) {
JSElement expression = JSSymbolUtil.calcRefExprValue((JSReferenceExpression)qualifier);
if (expression instanceof JSReferenceExpression) {
for (ResolveResult r : ((JSReferenceExpression)expression).multiResolve(false)) {
PsiElement rElement = r.getElement();
if (rElement instanceof JSClass) {
referencingClass = true;
break;
}
}
}
}
}
if (!referencingClass) {
resolveProcessor.addPossibleCandidateResult(element, JSResolveResult.STATIC_MEMBER_INACCESSIBLE);
return false;
}
}
}
if (processActionScriptNotAllowedNsAttributes(element, resolveProcessor, attributeList)) return false;
return true;
}
private boolean processActionScriptNotAllowedNsAttributes(@NotNull PsiElement element,
@NotNull SinkResolveProcessor resolveProcessor,
@Nullable JSAttributeList attributeList) {
if (!resolveProcessor.getResultSink().isActionScript()) return false;
String attributeNs = attributeList != null ? attributeList.getNamespace() : null;
if (attributeNs != null) {
if (!resolveProcessor.isProcessingFromIndices()) {
String resolvedNs = attributeList.resolveNamespaceValue();
if (resolvedNs == null && !resolveProcessor.getResultSink().isActionScript()) {
resolvedNs = attributeNs; // AS3
}
attributeNs = resolvedNs;
}
else {
attributeNs = null; // do not care about namespaces during indices built because it needs interfile resolve
}
}
if (openedNses == null && attributeNs != null) {
if (!anyNsAllowed &&
place instanceof JSReferenceExpression &&
!JSResolveUtil.isExprInTypeContext((JSReferenceExpression)place)
) {
openedNses = JSResolveUtil.calculateOpenNses(place);
}
}
if (openedNses != null &&
!openedNses.containsKey(attributeNs) &&
!AS3_NAMESPACE_VALUE.equals(attributeNs) &&
!ResolveProcessor.AS3_NAMESPACE.equals(attributeNs) // AS3 is opened by default from compiler settings and for JavaScript symbols
) {
if (attributeNs != null || defaultNsIsNotAllowed) {
resolveProcessor.addPossibleCandidateResult(element, JSResolveResult.MEMBER_FROM_UNOPENED_NAMESPACE);
return true;
}
}
return false;
}
}