package org.elixir_lang.annonator;
import com.intellij.psi.NavigatablePsiElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import org.elixir_lang.errorreport.Logger;
import org.elixir_lang.psi.*;
import org.elixir_lang.psi.call.Call;
import org.elixir_lang.psi.operation.InMatch;
import org.elixir_lang.psi.operation.When;
import org.elixir_lang.structure_view.element.CallDefinitionClause;
import org.elixir_lang.structure_view.element.Delegation;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Parameter {
public enum Type {
FUNCTION_NAME,
MACRO_NAME,
VARIABLE;
public static boolean isCallDefinitionClauseName(Type type) {
return type == FUNCTION_NAME || type == MACRO_NAME;
}
}
/*
* Public Static Methods
*/
/**
* A new {@link Parameter} with {@link #parameterized} filled in if {@code parameter}'s {@link #entrance} is a
* parameter element.
*
* @return a new {@link Parameter} with {@link #parameterized} filled in if {@link #entrance} is a valida parameter
* element.
*/
@Contract(pure = true)
@NotNull
public static Parameter putParameterized(final @NotNull Parameter parameter) {
return putParameterized(parameter, parameter.entrance);
}
/*
* Private Static Methods
*/
@Contract(pure = true)
@NotNull
private static <T> T notNullize(@Nullable T nullable, @NotNull T defaultValue) {
T notNull;
if (nullable == null) {
notNull = defaultValue;
} else {
notNull = nullable;
}
return notNull;
}
private static void error(@NotNull String message, @NotNull PsiElement element) {
Logger.error(Parameter.class, message + " (when element class is " + element.getClass().getName() + ")", element);
}
@Contract(pure = true)
@NotNull
private static Parameter putParameterized(@NotNull final Parameter parameter, final @NotNull Call ancestor) {
Parameter parameterizedParameter;
if (CallDefinitionClause.isFunction(ancestor) || Delegation.is(ancestor)) {
parameterizedParameter = new Parameter(
parameter.defaultValue,
parameter.entrance,
notNullize(parameter.parameterized, ancestor),
notNullize(parameter.type, Type.FUNCTION_NAME)
);
} else if (CallDefinitionClause.isMacro(ancestor)) {
parameterizedParameter = new Parameter(
parameter.defaultValue,
parameter.entrance,
notNullize(parameter.parameterized, ancestor),
notNullize(parameter.type, Type.MACRO_NAME)
);
} else if (ancestor.hasDoBlockOrKeyword()) {
parameterizedParameter = new Parameter(
parameter.defaultValue,
parameter.entrance,
ancestor,
notNullize(parameter.type, Type.VARIABLE)
);
} else {
PsiElement element = ancestor.functionNameElement();
Parameter updatedParameter = parameter;
if (!PsiTreeUtil.isAncestor(element, parameter.entrance, false)) {
updatedParameter = new Parameter(
parameter.defaultValue,
parameter.entrance,
ancestor,
notNullize(parameter.type, Type.VARIABLE)
);
}
// use generic handling so that parent is checked
parameterizedParameter = putParameterized(updatedParameter, (PsiElement) ancestor);
}
return parameterizedParameter;
}
@Contract(pure = true)
@NotNull
private static Parameter putParameterized(@NotNull final Parameter parameter,
@NotNull final ElixirAnonymousFunction ancestor) {
return new Parameter(
parameter.defaultValue,
parameter.entrance,
ancestor,
notNullize(parameter.type, Type.VARIABLE)
);
}
@Contract(pure = true)
@NotNull
private static Parameter putParameterized(@NotNull final Parameter parameter, @NotNull final PsiElement ancestor) {
Parameter parameterizedParameter;
PsiElement parent = ancestor.getParent();
/* MUST be before `Call` because `When` operations are `Call` implementations too in all cases even though
`When` is not a subinterface. */
if (parent instanceof When) {
parameterizedParameter = putParameterized(parameter, parent);
} else if (parent instanceof Call) {
parameterizedParameter = putParameterized(parameter, (Call) parent);
} else if (parent instanceof AtNonNumericOperation ||
parent instanceof ElixirAccessExpression ||
parent instanceof ElixirAssociations ||
parent instanceof ElixirAssociationsBase ||
parent instanceof ElixirBitString ||
parent instanceof ElixirBracketArguments ||
parent instanceof ElixirContainerAssociationOperation ||
parent instanceof ElixirKeywordPair ||
parent instanceof ElixirKeywords ||
parent instanceof ElixirList ||
parent instanceof ElixirMapArguments ||
parent instanceof ElixirMapConstructionArguments ||
parent instanceof ElixirMapOperation ||
parent instanceof ElixirMatchedParenthesesArguments ||
parent instanceof ElixirNoParenthesesArguments ||
parent instanceof ElixirNoParenthesesKeywordPair ||
parent instanceof ElixirNoParenthesesKeywords ||
/* ElixirNoParenthesesManyStrictNoParenthesesExpression indicates a syntax error where no parentheses
calls are nested, so it's invalid, but try to still resolve parameters to have highlighting */
parent instanceof ElixirNoParenthesesManyStrictNoParenthesesExpression ||
parent instanceof ElixirNoParenthesesOneArgument ||
/* handles `(conn, %{})` in `def (conn, %{})`, which can occur in def templates.
See https://github.com/KronicDeth/intellij-elixir/issues/367#issuecomment-244214975 */
parent instanceof ElixirNoParenthesesStrict ||
parent instanceof ElixirParenthesesArguments ||
parent instanceof ElixirParentheticalStab ||
parent instanceof ElixirStab ||
parent instanceof ElixirStabNoParenthesesSignature ||
parent instanceof ElixirStabBody ||
parent instanceof ElixirStabOperation ||
parent instanceof ElixirStabParenthesesSignature ||
parent instanceof ElixirStructOperation ||
parent instanceof ElixirTuple) {
parameterizedParameter = putParameterized(parameter, parent);
} else if (parent instanceof ElixirAnonymousFunction) {
parameterizedParameter = putParameterized(parameter, (ElixirAnonymousFunction) parent);
} else if (parent instanceof InMatch) {
parameterizedParameter = putParameterized(parameter, (InMatch) parent);
} else if (parent instanceof BracketOperation ||
parent instanceof ElixirBlockItem ||
parent instanceof ElixirDoBlock ||
parent instanceof ElixirInterpolation ||
parent instanceof ElixirMapUpdateArguments ||
parent instanceof ElixirMultipleAliases ||
parent instanceof ElixirQuoteStringBody ||
parent instanceof PsiFile ||
parent instanceof QualifiedAlias ||
parent instanceof QualifiedMultipleAliases) {
parameterizedParameter = new Parameter(parameter.entrance);
} else {
error("Don't know how to check if parameter", parent);
parameterizedParameter = new Parameter(parameter.entrance);
}
return parameterizedParameter;
}
/*
* Fields
*/
@Nullable
public final PsiElement defaultValue;
@NotNull
public final PsiElement entrance;
@Nullable
public final NavigatablePsiElement parameterized;
@Nullable
public final Type type;
/*
* Constructors
*/
public Parameter(@NotNull PsiElement entrance) {
this.defaultValue = null;
this.entrance = entrance;
this.parameterized = null;
this.type = null;
}
private Parameter(@Nullable PsiElement defaultValue,
@NotNull PsiElement entrance,
@Nullable NavigatablePsiElement parameterized,
@Nullable Type type) {
this.defaultValue = defaultValue;
this.entrance = entrance;
this.parameterized = parameterized;
this.type = type;
}
/*
* Public Instance Methods
*/
/**
* Whether the {@link #type} is call definition clause name
*
* @return {@code true} if {@link #type} is {@link Type#FUNCTION_NAME} or {@link Type#MACRO_NAME}.
*/
@Contract(pure = true)
public boolean isCallDefinitionClauseName() {
return Type.isCallDefinitionClauseName(type);
}
/**
* Whether {@link #entrance} represents a parameter to a {@link #parameterized} element
* @return {@code true} if {@link #parameterized} is not {@code null}
*/
@Contract(pure = true)
boolean isValid() {
return parameterized == null;
}
/**
* A Parameter that is not a parameter to anything.
*
* @param parameter The original {@link Parameter} that may or may not be parameterized
* @return an invalid parameter
*/
@NotNull
public Parameter not(final @NotNull Parameter parameter) {
Parameter not;
if (parameter.defaultValue == null && parameter.parameterized == null) {
not = parameter;
} else {
not = new Parameter(parameter.entrance);
}
return not;
}
}