package org.angularjs.codeInsight;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.javascript.JSTokenTypes;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.util.containers.ContainerUtil;
import org.angularjs.lang.parser.AngularJSMessageFormatParser;
import org.angularjs.lang.psi.AngularJSElementVisitor;
import org.angularjs.lang.psi.AngularJSMessageFormatExpression;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author Irina.Chernushina on 12/3/2015.
*/
public class AngularJSMessageFormatAnnotator extends AngularJSElementVisitor implements Annotator {
private AnnotationHolder myHolder;
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
try {
assert myHolder == null;
myHolder = holder;
element.accept(this);
} finally {
myHolder = null;
}
}
@Override
public void visitMessageFormatExpression(@NotNull final AngularJSMessageFormatExpression expression) {
final AngularJSMessageFormatParser.ExtensionType type = expression.getExtensionType();
if (type == null) myHolder.createErrorAnnotation((PsiElement)expression, "missing or unknown message format extension");// will not happen, but
final List<PsiElement> elements = expression.getSelectionKeywordElements();
final List<String> selectionKeywords = ContainerUtil.map(elements, element -> element.getText());
checkOptions(type, expression);
if (expression.getNode().getLastChildNode() instanceof PsiErrorElement) return;
checkForRequiredSelectionKeywords(type, expression, selectionKeywords);
checkForDuplicateSelectionKeywords(selectionKeywords, elements);
checkForSelectionKeywordValues(type, selectionKeywords, elements);
}
private void checkOptions(AngularJSMessageFormatParser.ExtensionType type, AngularJSMessageFormatExpression expression) {
if (AngularJSMessageFormatParser.ExtensionType.plural.equals(type)) {
final PsiElement[] options = expression.getOptions();
if (options != null) {
for (PsiElement option : options) {
if (AngularJSMessageFormatParser.OFFSET_OPTION.equals(option.getNode().getFirstChildNode().getText())) {
final ASTNode lastChild = option.getNode().getLastChildNode();
if (lastChild.getElementType() != JSTokenTypes.NUMERIC_LITERAL) {
myHolder.createErrorAnnotation(option, "Expected integer value");
}
}
}
}
}
}
private void checkForSelectionKeywordValues(AngularJSMessageFormatParser.ExtensionType type,
List<String> keywords,
List<PsiElement> elements) {
if (AngularJSMessageFormatParser.ExtensionType.plural.equals(type)) {
final Map<String, String> errors = new HashMap<>();
for (String keyword : keywords) {
if (keyword.startsWith("=")) {
try {
Integer.parseInt(keyword.substring(1));
} catch (NumberFormatException e) {
errors.put(keyword, "Integer expected after =");
}
} else {
try {
AngularJSPluralCategories.valueOf(keyword);
} catch (IllegalArgumentException e) {
errors.put(keyword, "Expected plural category");
}
}
}
if (!errors.isEmpty()) {
for (PsiElement element : elements) {
final String errorText = errors.get(element.getText());
if (errorText != null) {
myHolder.createErrorAnnotation(element, errorText);
}
}
}
}
}
private void checkForDuplicateSelectionKeywords(List<String> keywords, List<PsiElement> elements) {
final Set<String> passedSet = new HashSet<>();
final Set<String> duplicate = new HashSet<>(ContainerUtil.filter(keywords, s -> {
final boolean contains = passedSet.contains(s);
if (!contains) passedSet.add(s);
return contains;
}));
if (!duplicate.isEmpty()) {
for (PsiElement element : elements) {
if (duplicate.contains(element.getText())) {
myHolder.createErrorAnnotation(element, "Duplicate selection keyword");
}
}
}
}
private void checkForRequiredSelectionKeywords(AngularJSMessageFormatParser.ExtensionType type,
@NotNull AngularJSMessageFormatExpression expression,
List<String> selectionKeywords) {
if (type != null) {
final Set<String> requiredKeywords = type.getRequiredSelectionKeywords();
if (!requiredKeywords.isEmpty()) {
for (String requiredKeyword : requiredKeywords) {
if (!selectionKeywords.contains(requiredKeyword)) {
myHolder.createErrorAnnotation((PsiElement)expression, "Missing required selection keyword '" + requiredKeyword + "'");
}
}
}
}
}
}