diff --git a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/ExpressionReplacementFix.java b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/ExpressionReplacementFix.java index 7e62ee82885c..3cda902c1437 100644 --- a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/ExpressionReplacementFix.java +++ b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/ExpressionReplacementFix.java @@ -20,23 +20,29 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; -import org.intellij.lang.xpath.psi.XPathBinaryExpression; +import org.intellij.lang.xpath.psi.XPathExpression; import org.intellij.lang.xpath.psi.impl.XPathChangeUtil; import org.jetbrains.annotations.NotNull; class ExpressionReplacementFix implements IntentionAction { private final String myReplacement; - private final XPathBinaryExpression myExpr; + private final String myDisplay; + private final XPathExpression myExpr; - public ExpressionReplacementFix(String replacement, XPathBinaryExpression expr) { + public ExpressionReplacementFix(String replacement, XPathExpression expr) { + this(replacement, replacement, expr); + } + + public ExpressionReplacementFix(String replacement, String display, XPathExpression expression) { myReplacement = replacement; - myExpr = expr; + myDisplay = display; + myExpr = expression; } @NotNull @Override public String getText() { - return "Replace with '" + myReplacement + "'"; + return "Replace with '" + myDisplay + "'"; } @NotNull diff --git a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/XPathAnnotator.java b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/XPathAnnotator.java index af95c1fcb750..eec124e3e260 100644 --- a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/XPathAnnotator.java +++ b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/validation/XPathAnnotator.java @@ -15,6 +15,7 @@ */ package org.intellij.lang.xpath.validation; +import com.intellij.codeInsight.PsiEquivalenceUtil; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.lang.ASTNode; @@ -24,20 +25,21 @@ import com.intellij.lang.annotation.Annotator; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; +import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import org.intellij.lang.xpath.XPath2TokenTypes; import org.intellij.lang.xpath.XPathElementType; -import org.intellij.lang.xpath.XPathFileType; -import org.intellij.lang.xpath.context.functions.Function; -import org.jetbrains.annotations.NotNull; - import org.intellij.lang.xpath.XPathTokenTypes; import org.intellij.lang.xpath.context.ContextProvider; import org.intellij.lang.xpath.context.NamespaceContext; import org.intellij.lang.xpath.context.VariableContext; +import org.intellij.lang.xpath.context.XPathVersion; +import org.intellij.lang.xpath.context.functions.Function; import org.intellij.lang.xpath.context.functions.Parameter; import org.intellij.lang.xpath.psi.*; import org.intellij.lang.xpath.psi.impl.PrefixedNameImpl; +import org.intellij.lang.xpath.psi.impl.XPathChangeUtil; +import org.jetbrains.annotations.NotNull; import javax.xml.namespace.QName; import java.util.Arrays; @@ -104,25 +106,57 @@ public final class XPathAnnotator extends XPath2ElementVisitor implements Annota } @Override - public void visitXPathBinaryExpression(final XPathBinaryExpression o) { - if (o.getContainingFile().getLanguage() == XPathFileType.XPATH2.getLanguage()) { - final XPathExpression operand = o.getLOperand(); - final XPathElementType operator = o.getOperator(); - if (operand instanceof XPathNumber) { - if (operator != XPathTokenTypes.STAR && XPath2TokenTypes.KEYWORDS.contains(operator)) { - final String op = o.getOperationSign(); - if (o.getText().startsWith(operand.getText() + op)) { - final Annotation annotation = - myHolder.createErrorAnnotation(o, "Number literal must be followed by whitespace in XPath 2"); + public void visitXPathNumber(XPathNumber number) { + if (number.getXPathVersion() == XPathVersion.V2) { - final XPathExpression rOperand = o.getROperand(); - if (rOperand != null) { - final String replacement = operand.getText() + " " + op + " " + rOperand.getText(); - annotation.registerFix(new ExpressionReplacementFix(replacement, o)); + final PsiElement leaf = PsiTreeUtil.nextLeaf(number); + if (leaf != null) { + final IElementType elementType = leaf.getNode().getElementType(); + if (elementType != XPathTokenTypes.STAR && XPath2TokenTypes.KEYWORDS.contains(elementType)) { + final TextRange range = TextRange.create(number.getTextRange().getStartOffset(), leaf.getTextRange().getEndOffset()); + final Annotation annotation = + myHolder.createErrorAnnotation(range, "Number literal must be followed by whitespace in XPath 2"); + + final XPathBinaryExpression expression = PsiTreeUtil.getParentOfType(number, XPathBinaryExpression.class, true); + if (expression != null) { + final XPathExpression lOperand = expression.getLOperand(); + if (number == lOperand) { + final XPathExpression rOperand = expression.getROperand(); + if (rOperand != null) { + final String display = number.getText() + " " + expression.getOperationSign(); + final String replacement = display + " " + rOperand.getText(); + + assert PsiEquivalenceUtil.areElementsEquivalent(expression, XPathChangeUtil.createExpression(expression, replacement)); + annotation.registerFix(new ExpressionReplacementFix(replacement, display, expression)); + } + } else if (number == expression.getROperand()) { + final PsiElement next = PsiTreeUtil.getParentOfType(PsiTreeUtil.nextLeaf(expression), XPathExpression.class, true); + if (next instanceof XPathBinaryExpression) { + final XPathBinaryExpression left = (XPathBinaryExpression)next; + final XPathExpression rOperand = left.getROperand(); + if (rOperand != null && lOperand != null) { + final String display = number.getText() + " " + left.getOperationSign(); + final String replacement = lOperand.getText() + " " + expression.getOperationSign() + " " + display + " " + rOperand.getText(); + + assert PsiEquivalenceUtil.areElementsEquivalent(next, XPathChangeUtil.createExpression(next, replacement)); + annotation.registerFix(new ExpressionReplacementFix(replacement, display, (XPathExpression)next)); + } + } } } } } + } + + super.visitXPathNumber(number); + } + + @Override + public void visitXPathBinaryExpression(final XPathBinaryExpression o) { + if (o.getXPathVersion() == XPathVersion.V2) { + final XPathElementType operator = o.getOperator(); + final XPathExpression operand = o.getLOperand(); + if (XPath2TokenTypes.COMP_OPS.contains(operator)) { if (operand instanceof XPathBinaryExpression && XPath2TokenTypes.COMP_OPS.contains(((XPathBinaryExpression)operand).getOperator())) { final Annotation annotation = myHolder.createErrorAnnotation(o, "Consecutive comparison is not allowed in XPath 2"); @@ -224,14 +258,18 @@ public final class XPathAnnotator extends XPath2ElementVisitor implements Annota if (call.getQName().getPrefix() != null && contextProvider.getFunctionContext().allowsExtensions()) { final PsiReference[] references = call.getReferences(); if (references.length > 1 && references[1].resolve() == null) { - final Annotation ann = holder.createErrorAnnotation(qName.getPrefixNode(), "Extension namespace prefix '" + qName.getPrefix() + "' has not been declared"); + final Annotation ann = holder.createErrorAnnotation(qName.getPrefixNode(), "Extension namespace prefix '" + + qName.getPrefix() + + "' has not been declared"); ann.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); } } else { if (name != null) { holder.createWarningAnnotation(node, "Unknown function '" + name + "'"); } else if (qName.getPrefixNode() != null) { - final Annotation ann = holder.createErrorAnnotation(qName.getPrefixNode(), "Extension namespace prefix '" + qName.getPrefix() + "' has not been declared"); + final Annotation ann = holder.createErrorAnnotation(qName.getPrefixNode(), "Extension namespace prefix '" + + qName.getPrefix() + + "' has not been declared"); ann.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); } } diff --git a/plugins/xpath/xpath-lang/test/org/intellij/lang/xpath/XPath2HighlightingTest.java b/plugins/xpath/xpath-lang/test/org/intellij/lang/xpath/XPath2HighlightingTest.java index 766a731f799f..4702751d5a01 100644 --- a/plugins/xpath/xpath-lang/test/org/intellij/lang/xpath/XPath2HighlightingTest.java +++ b/plugins/xpath/xpath-lang/test/org/intellij/lang/xpath/XPath2HighlightingTest.java @@ -33,6 +33,10 @@ public class XPath2HighlightingTest extends XPath2HighlightingTestBase { doXPathHighlighting(); } + public void testNumberFollowedByToken2() throws Throwable { + doXPathHighlighting(); + } + @Override protected String getSubPath() { return "xpath/highlighting"; diff --git a/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath b/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath index 6e39817a4ac8..1c8a4b3937b1 100644 --- a/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath +++ b/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath @@ -1 +1 @@ -10div 2 + 3 mod 2 \ No newline at end of file +10div 2 + 3 mod 2div 5 \ No newline at end of file diff --git a/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath2 b/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath2 index f67dd96a57ce..647eb9be50c1 100644 --- a/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath2 +++ b/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken.xpath2 @@ -1 +1 @@ -10div 2 + 3 mod 2 \ No newline at end of file +10div 2 + 3 mod 2 \ No newline at end of file diff --git a/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken2.xpath2 b/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken2.xpath2 new file mode 100644 index 000000000000..6bd3c0ff10a1 --- /dev/null +++ b/plugins/xpath/xpath-lang/testData/xpath/highlighting/numberFollowedByToken2.xpath2 @@ -0,0 +1 @@ +5 div 2gt 1 \ No newline at end of file