package org.elixir_lang.psi.scope;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import org.elixir_lang.psi.*;
import org.elixir_lang.psi.call.Named;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.elixir_lang.psi.call.name.Function.ALIAS;
import static org.elixir_lang.psi.call.name.Module.KERNEL;
import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.finalArguments;
import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.keywordArgument;
import static org.elixir_lang.psi.stub.type.call.Stub.isModular;
public abstract class Module implements PsiScopeProcessor {
/*
* Public Instance Methods
*/
@Nullable
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
return null;
}
@Override
public void handleEvent(@NotNull Event event, @Nullable Object associated) {
}
/*
* Protected Instance Methods
*/
/**
* Decides whether {@code match} matches the criteria being searched for. All other {@link #execute} methods
* eventually end here.
*
* @return {@code true} to keep processing; {@code false} to stop processing.
*/
protected abstract boolean executeOnAliasedName(@NotNull final PsiNamedElement match,
@NotNull String aliasedName,
@NotNull ResolveState state);
/*
* Protected Instance Methods
*/
protected boolean execute(@NotNull Named match, @NotNull ResolveState state) {
boolean keepProcessing = true;
if (isModular(match)) {
keepProcessing = executeOnMaybeAliasedName(match, match.getName(), state);
} else if (match.isCalling(KERNEL, ALIAS)) {
keepProcessing = executeOnAliasCall(match, state);
}
return keepProcessing;
}
/*
* Private Instance Methods
*/
@Nullable
private String aliasedName(@NotNull ElixirAlias element) {
return element.getName();
}
@Nullable
private String aliasedName(@NotNull PsiElement element) {
String aliasedName = null;
if (element instanceof ElixirAlias) {
aliasedName = aliasedName((ElixirAlias) element);
} else if (element instanceof QualifiedAlias) {
aliasedName = aliasedName((QualifiedAlias) element);
}
return aliasedName;
}
@Nullable
private String aliasedName(@NotNull QualifiedAlias element) {
String aliasedName = null;
PsiElement[] children = element.getChildren();
int operatorIndex = org.elixir_lang.psi.operation.Normalized.operatorIndex(children);
PsiElement unqualified = org.elixir_lang.psi.operation.infix.Normalized.rightOperand(children, operatorIndex); assert children.length == 3;
if (unqualified instanceof PsiNamedElement) {
PsiNamedElement namedElement = (PsiNamedElement) unqualified;
aliasedName = namedElement.getName();
}
return aliasedName;
}
private boolean executeOnAliasCall(@NotNull Named aliasCall, @NotNull ResolveState state) {
boolean keepProcessing = true;
PsiElement asKeywordValue = keywordArgument(aliasCall, "as");
if (asKeywordValue != null) {
keepProcessing = executeOnAs(asKeywordValue, state);
} else {
PsiElement[] finalArguments = finalArguments(aliasCall);
if (finalArguments != null && finalArguments.length > 0) {
keepProcessing = executeOnAliasCallArgument(finalArguments[0], state);
}
}
return keepProcessing;
}
private boolean executeOnAliasCallArgument(@NotNull ElixirAccessExpression accessExpression,
@NotNull ResolveState state) {
return executeOnAliasCallArgument(accessExpression.getChildren(), state);
}
private boolean executeOnAliasCallArgument(@NotNull ElixirMultipleAliases multipleAliases,
@NotNull ResolveState state) {
return executeOnMultipleAliasChildren(multipleAliases.getChildren(), state);
}
private boolean executeOnAliasCallArgument(@Nullable PsiElement element, @NotNull ResolveState state) {
boolean keepProcessing = true;
if (element instanceof ElixirAccessExpression) {
keepProcessing = executeOnAliasCallArgument((ElixirAccessExpression) element, state);
} else if (element instanceof QualifiableAlias) {
keepProcessing = executeOnAliasCallArgument((QualifiableAlias) element, state);
} else if (element instanceof QualifiedMultipleAliases) {
keepProcessing = executeOnAliasCallArgument((QualifiedMultipleAliases) element, state);
}
return keepProcessing;
}
private boolean executeOnAliasCallArgument(@NotNull PsiElement[] children, @NotNull ResolveState state) {
boolean keepProcessing = true;
for (@Nullable PsiElement child : children) {
if (child != null) {
keepProcessing = executeOnAliasCallArgument(child, state);
if (!keepProcessing) {
break;
}
}
}
return keepProcessing;
}
private boolean executeOnMultipleAliasChild(@NotNull ElixirAccessExpression child, ResolveState state) {
PsiElement[] accessChildren = child.getChildren();
boolean keepProcessing = true;
if (accessChildren != null) {
keepProcessing = executeOnMultipleAliasChild(accessChildren, state);
}
return keepProcessing;
}
private boolean executeOnMultipleAliasChild(@NotNull PsiElement child, @NotNull ResolveState state) {
boolean keepProcessing = true;
if (child instanceof ElixirAccessExpression) {
keepProcessing = executeOnMultipleAliasChild((ElixirAccessExpression) child, state);
} else if (child instanceof QualifiableAlias) {
keepProcessing = executeOnMultipleAliasChild((QualifiableAlias) child, state);
}
return keepProcessing;
}
private boolean executeOnMultipleAliasChild(@NotNull PsiElement[] elements, @NotNull ResolveState state) {
boolean keepProcessing = true;
for (PsiElement element : elements) {
if (element != null) {
keepProcessing = executeOnMultipleAliasChild(element, state);
if (!keepProcessing) {
break;
}
}
}
return keepProcessing;
}
private boolean executeOnMultipleAliasChild(@NotNull QualifiableAlias element, @NotNull ResolveState state) {
return executeOnMaybeAliasedName(element, aliasedName(element), state);
}
private boolean executeOnMultipleAliasChildren(@Nullable PsiElement[] elements, @NotNull ResolveState state) {
boolean keepProcessing = true;
if (elements != null) {
for (PsiElement element : elements) {
if (element != null) {
keepProcessing = executeOnMultipleAliasChild(element, state);
if (!keepProcessing) {
break;
}
}
}
}
return keepProcessing;
}
private boolean executeOnAliasCallArgument(@NotNull QualifiableAlias qualifiableAlias,
@NotNull ResolveState state) {
return executeOnMaybeAliasedName(qualifiableAlias, aliasedName(qualifiableAlias), state);
}
private boolean executeOnAliasCallArgument(@NotNull QualifiedMultipleAliases qualifiedMultipleAliases,
@NotNull ResolveState state) {
PsiElement[] children = qualifiedMultipleAliases.getChildren();
int operatorIndex = org.elixir_lang.psi.operation.Normalized.operatorIndex(children);
PsiElement unqualified = org.elixir_lang.psi.operation.infix.Normalized.rightOperand(children, operatorIndex);
boolean keepProcessing = true;
if (unqualified instanceof ElixirMultipleAliases) {
keepProcessing = executeOnAliasCallArgument((ElixirMultipleAliases) unqualified, state);
}
return keepProcessing;
}
private boolean executeOnAs(ElixirAccessExpression asKeywordValue, @NotNull ResolveState state) {
PsiElement[] children = asKeywordValue.getChildren();
boolean keepProcessing = true;
if (children.length > 0) {
PsiElement child = children[0];
keepProcessing = executeOnAs(child, state);
}
return keepProcessing;
}
private boolean executeOnAs(PsiElement asKeywordValue, @NotNull ResolveState state) {
boolean keepProcessing = true;
if (asKeywordValue instanceof ElixirAccessExpression) {
keepProcessing = executeOnAs((ElixirAccessExpression) asKeywordValue, state);
} else if (asKeywordValue instanceof PsiNamedElement) {
PsiNamedElement namedElement = (PsiNamedElement) asKeywordValue;
keepProcessing = executeOnMaybeAliasedName(
namedElement,
namedElement.getName(),
state
);
}
return keepProcessing;
}
private boolean executeOnMaybeAliasedName(@NotNull PsiNamedElement named,
@Nullable String aliasedName,
@NotNull ResolveState state) {
boolean keepProcessing = true;
if (aliasedName != null) {
keepProcessing = executeOnAliasedName(named, aliasedName, state);
}
return keepProcessing;
}
}