| package com.intellij.tasks.jira.jql.codeinsight; |
| |
| import com.intellij.lang.annotation.Annotation; |
| import com.intellij.lang.annotation.AnnotationHolder; |
| import com.intellij.lang.annotation.Annotator; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.tasks.jira.jql.psi.*; |
| import org.jetbrains.annotations.NotNull; |
| |
| import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.CONSTANT; |
| |
| /** |
| * At first, it checks the following errors: |
| * <ol> |
| * <li>not-list operand was given inside 'IN' clauses and vice-versa |
| * <li>constants 'empty' or 'null' weren't used inside 'IS' clauses and vice-versa |
| * </ol> |
| * |
| * |
| * It also highlights JQL identifiers (fields and function names) differently |
| * from unquoted strings. |
| * |
| * @author Mikhail Golubev |
| */ |
| public class JqlAnnotator implements Annotator { |
| @Override |
| public void annotate(@NotNull PsiElement element, final @NotNull AnnotationHolder holder) { |
| // error checks |
| element.accept(new JqlElementVisitor() { |
| @Override |
| public void visitEmptyValue(JqlEmptyValue emptyValue) { |
| JqlSimpleClause clause = PsiTreeUtil.getParentOfType(emptyValue, JqlSimpleClause.class); |
| if (clause != null && !isEmptyClause(clause)) { |
| holder.createErrorAnnotation(emptyValue, String.format("Not expecting '%s' here", emptyValue.getText())); |
| } |
| } |
| |
| @Override |
| public void visitJqlList(JqlList list) { |
| JqlSimpleClause clause = PsiTreeUtil.getParentOfType(list, JqlSimpleClause.class); |
| if (clause != null && !isListClause(clause)) { |
| holder.createErrorAnnotation(list, String.format("Not expecting list of values here")); |
| } |
| } |
| |
| @Override |
| public void visitJqlSimpleClause(JqlSimpleClause clause) { |
| JqlOperand operand = clause.getOperand(); |
| if (operand == null) { |
| return; |
| } |
| boolean operandIsListLiteral = operand instanceof JqlList; |
| boolean operandIsListFunction = false; |
| if (operand instanceof JqlFunctionCall) { |
| JqlFunctionCall functionCall = (JqlFunctionCall)operand; |
| JqlStandardFunction standardFunction = JqlStandardFunction.byName(functionCall.getFunctionName().getText()); |
| operandIsListFunction = standardFunction != null && standardFunction.hasMultipleResults(); |
| } |
| boolean hasListOperand = operandIsListLiteral || operandIsListFunction; |
| if (isListClause(clause) && !hasListOperand) { |
| holder.createErrorAnnotation(operand, "Expecting list of values here"); |
| } |
| |
| boolean hasEmptyOperand = operand instanceof JqlEmptyValue; |
| if (isEmptyClause(clause) && !hasEmptyOperand) { |
| holder.createErrorAnnotation(operand, "Expecting 'empty' or 'null' here"); |
| } |
| } |
| |
| @Override |
| public void visitJqlIdentifier(JqlIdentifier identifier) { |
| Annotation annotation = holder.createInfoAnnotation(identifier, null); |
| annotation.setEnforcedTextAttributes(CONSTANT.getDefaultAttributes()); |
| } |
| }); |
| } |
| |
| private static boolean isEmptyClause(JqlTerminalClause clause) { |
| JqlTerminalClause.Type clauseType = clause.getType(); |
| return clauseType == JqlTerminalClause.Type.IS || clauseType == JqlTerminalClause.Type.IS_NOT; |
| } |
| |
| private static boolean isListClause(JqlTerminalClause clause) { |
| JqlTerminalClause.Type clauseType = clause.getType(); |
| return clauseType != null && clauseType.isListOperator(); |
| } |
| } |