| /* |
| * Copyright 2005 Sascha Weinreuter |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.intellij.lang.xpath.validation; |
| |
| import com.intellij.codeInsight.PsiEquivalenceUtil; |
| import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInspection.ProblemHighlightType; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.annotation.Annotation; |
| import com.intellij.lang.annotation.AnnotationHolder; |
| import com.intellij.lang.annotation.Annotator; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| 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.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.intellij.lang.xpath.psi.impl.XPathNumberImpl; |
| import org.intellij.lang.xpath.xslt.impl.XsltReferenceContributor; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.xml.namespace.QName; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| public final class XPathAnnotator extends XPath2ElementVisitor implements Annotator { |
| |
| private AnnotationHolder myHolder; |
| |
| public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) { |
| |
| try { |
| myHolder = holder; |
| psiElement.accept(this); |
| } finally { |
| myHolder = null; |
| } |
| } |
| |
| @Override |
| public void visitXPathNodeTest(XPathNodeTest o) { |
| final ContextProvider contextProvider = o.getXPathContext(); |
| checkNodeTest(contextProvider, myHolder, o); |
| } |
| |
| @Override |
| public void visitXPathStep(XPathStep o) { |
| checkSillyStep(myHolder, o); |
| super.visitXPathStep(o); |
| } |
| |
| @Override |
| public void visitXPathNodeTypeTest(XPathNodeTypeTest o) { |
| checkNodeTypeTest(myHolder, o); |
| visitXPathExpression(o); |
| } |
| |
| @Override |
| public void visitXPathFunctionCall(XPathFunctionCall o) { |
| final ContextProvider contextProvider = o.getXPathContext(); |
| checkFunctionCall(myHolder, o, contextProvider); |
| super.visitXPathFunctionCall(o); |
| } |
| |
| @Override |
| public void visitXPathString(XPathString o) { |
| checkString(myHolder, o); |
| super.visitXPathString(o); |
| } |
| |
| @Override |
| public void visitXPathVariableReference(XPathVariableReference o) { |
| final ContextProvider contextProvider = o.getXPathContext(); |
| checkVariableReference(myHolder, o, contextProvider); |
| super.visitXPathVariableReference(o); |
| } |
| |
| @Override |
| public void visitXPath2TypeElement(XPath2TypeElement o) { |
| final ContextProvider contextProvider = o.getXPathContext(); |
| checkPrefixReferences(myHolder, o, contextProvider); |
| |
| if (o.getDeclaredType() == XPathType.UNKNOWN) { |
| final PsiReference[] references = o.getReferences(); |
| for (PsiReference reference : references) { |
| if (reference instanceof XsltReferenceContributor.SchemaTypeReference ) { |
| if (!reference.isSoft() && reference.resolve() == null) { |
| final String message = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern(); |
| final Annotation annotation = |
| myHolder.createErrorAnnotation(reference.getRangeInElement().shiftRight(o.getTextOffset()), message); |
| annotation.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); |
| } |
| } |
| } |
| } |
| super.visitXPath2TypeElement(o); |
| } |
| |
| @Override |
| public void visitXPathNumber(XPathNumber number) { |
| if (number.getXPathVersion() == XPathVersion.V2) { |
| |
| 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)); |
| } |
| } |
| } |
| } |
| } |
| } |
| } else { |
| if (((XPathNumberImpl)number).isScientificNotation()) { |
| myHolder.createErrorAnnotation(number, "Number literals in scientific notation are not allowed in XPath 1.0"); |
| } |
| } |
| |
| super.visitXPathNumber(number); |
| } |
| |
| @Override |
| public void visitXPathBinaryExpression(final XPathBinaryExpression o) { |
| final XPathExpression operand = o.getLOperand(); |
| if (operand != null && o.getXPathVersion() == XPathVersion.V2) { |
| final XPathElementType operator = o.getOperator(); |
| |
| 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"); |
| |
| final XPathExpression rOperand = o.getROperand(); |
| if (rOperand != null) { |
| final String replacement = "(" + operand.getText() + ") " + o.getOperationSign() + " " + rOperand.getText(); |
| annotation.registerFix(new ExpressionReplacementFix(replacement, o)); |
| } |
| } |
| |
| if (XPath2TokenTypes.NODE_COMP_OPS.contains(operator)) { |
| checkApplicability(o, XPath2Type.NODE); |
| } else if (operator == XPath2TokenTypes.WEQ || operator == XPath2TokenTypes.WNE || operator == XPathTokenTypes.EQ || operator == XPathTokenTypes.NE) { |
| checkApplicability(o, |
| XPath2Type.NUMERIC,XPath2Type.BOOLEAN,XPath2Type.STRING, |
| XPath2Type.DATE,XPath2Type.TIME,XPath2Type.DATETIME,XPath2Type.DURATION, |
| XPath2Type.HEXBINARY,XPath2Type.BASE64BINARY,XPath2Type.ANYURI,XPath2Type.QNAME); |
| } else if (operator == XPath2TokenTypes.WGT || operator == XPath2TokenTypes.WGE || operator == XPath2TokenTypes.WLE || operator == XPath2TokenTypes.WLT || |
| operator == XPathTokenTypes.GT || operator == XPathTokenTypes.GE || operator == XPathTokenTypes.LE || operator == XPathTokenTypes.LT) { |
| checkApplicability(o, |
| XPath2Type.NUMERIC,XPath2Type.BOOLEAN,XPath2Type.STRING, |
| XPath2Type.DATE,XPath2Type.TIME,XPath2Type.DATETIME,XPath2Type.DURATION, |
| XPath2Type.ANYURI); |
| } |
| } else if (XPath2TokenTypes.UNION_OPS.contains(operator) || XPath2TokenTypes.INTERSECT_EXCEPT.contains(operator)) { |
| checkApplicability(o, XPath2SequenceType.create(XPath2Type.NODE, XPath2SequenceType.Cardinality.ZERO_OR_MORE)); |
| } else if (operator == XPath2TokenTypes.TO) { |
| checkApplicability(o, XPath2Type.INTEGER); |
| } else if (operator == XPathTokenTypes.AND || operator == XPathTokenTypes.OR) { |
| checkApplicability(o, XPath2Type.BOOLEAN); |
| } else if (XPathTokenTypes.ADD_OPS.contains(operator)) { |
| checkApplicability(o, XPath2Type.NUMERIC, XPath2Type.DURATION, XPath2Type.DATE, XPath2Type.TIME, XPath2Type.DATETIME); |
| } else if (XPath2TokenTypes.MULT_OPS.contains(operator)) { |
| if (operator == XPath2TokenTypes.IDIV || operator == XPathTokenTypes.MOD) { |
| checkApplicability(o, XPath2Type.NUMERIC); |
| } else if (operator == XPathTokenTypes.DIV) { |
| checkApplicability(o, XPath2Type.NUMERIC, XPath2Type.DURATION); |
| } else { |
| assert operator == XPathTokenTypes.MULT; |
| checkApplicability(o, XPath2Type.NUMERIC, XPath2Type.DURATION); |
| } |
| } |
| } |
| |
| checkExpression(myHolder, o); |
| super.visitXPathBinaryExpression(o); |
| } |
| |
| private void checkApplicability(XPathBinaryExpression o, XPath2Type... applicableTypes) { |
| final XPathExpression lOperand = o.getLOperand(); |
| assert lOperand != null; |
| |
| final XPathType leftType = XPath2Type.mapType(lOperand.getType()); |
| for (XPath2Type applicableType : applicableTypes) { |
| if (XPathType.isAssignable(applicableType, leftType)) { |
| return; |
| } |
| } |
| myHolder.createErrorAnnotation(o, "Operator '" + o.getOperationSign() + "' cannot be applied to expressions of type '" + leftType.getName() + "'"); |
| } |
| |
| @Override |
| public void visitXPathExpression(XPathExpression o) { |
| checkExpression(myHolder, o); |
| } |
| |
| private static void checkString(AnnotationHolder holder, XPathString string) { |
| if (!string.isWellFormed()) { |
| holder.createErrorAnnotation(string, "Malformed string literal"); |
| } |
| } |
| |
| private static void checkVariableReference(AnnotationHolder holder, XPathVariableReference reference, @NotNull ContextProvider contextProvider) { |
| if (reference.resolve() == null) { |
| final VariableContext variableResolver = contextProvider.getVariableContext(); |
| if (variableResolver == null) return; |
| |
| if (!variableResolver.canResolve()) { |
| final Object[] variablesInScope = variableResolver.getVariablesInScope(reference); |
| if (variablesInScope instanceof String[]) { |
| final Set<String> variables = new HashSet<String>(Arrays.asList((String[])variablesInScope)); |
| if (!variables.contains(reference.getReferencedName())) { |
| markUnresolvedVariable(reference, holder); |
| } |
| } else if (variablesInScope instanceof QName[]) { |
| final Set<QName> variables = new HashSet<QName>(Arrays.asList((QName[])variablesInScope)); |
| if (!variables.contains(contextProvider.getQName(reference))) { |
| markUnresolvedVariable(reference, holder); |
| } |
| } |
| } else { |
| markUnresolvedVariable(reference, holder); |
| } |
| } |
| } |
| |
| private static void markUnresolvedVariable(XPathVariableReference reference, AnnotationHolder holder) { |
| final String referencedName = reference.getReferencedName(); |
| // missing name is already flagged by parser |
| if (referencedName.length() > 0) { |
| final TextRange range = reference.getTextRange().shiftRight(1).grown(-1); |
| final Annotation ann = holder.createErrorAnnotation(range, "Unresolved variable '" + referencedName + "'"); |
| ann.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); |
| final VariableContext variableContext = ContextProvider.getContextProvider(reference).getVariableContext(); |
| if (variableContext != null) { |
| final IntentionAction[] fixes = variableContext.getUnresolvedVariableFixes(reference); |
| for (IntentionAction fix : fixes) { |
| ann.registerFix(fix); |
| } |
| } |
| } |
| } |
| |
| private static void checkSillyStep(AnnotationHolder holder, XPathStep step) { |
| final XPathExpression previousStep = step.getPreviousStep(); |
| if (previousStep instanceof XPathStep) { |
| final XPathNodeTest nodeTest = ((XPathStep)previousStep).getNodeTest(); |
| if (nodeTest != null) { |
| final XPathNodeTest.PrincipalType principalType = nodeTest.getPrincipalType(); |
| if (principalType != XPathNodeTest.PrincipalType.ELEMENT) { |
| XPathNodeTest test = step.getNodeTest(); |
| if (test != null) { |
| holder.createWarningAnnotation(test, "Silly location step on " + principalType.getType() + " axis"); |
| } |
| } |
| } |
| } |
| } |
| |
| private static void checkFunctionCall(AnnotationHolder holder, XPathFunctionCall call, @NotNull ContextProvider contextProvider) { |
| final ASTNode node = call.getNode().findChildByType(XPathTokenTypes.FUNCTION_NAME); |
| if (node == null) { |
| return; |
| } |
| |
| final QName name = contextProvider.getQName(call); |
| final XPathFunction function = call.resolve(); |
| final Function functionDecl = function != null ? function.getDeclaration() : null; |
| if (functionDecl == null) { |
| final PrefixedNameImpl qName = (PrefixedNameImpl)call.getQName(); |
| |
| // need special check for extension functions |
| 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"); |
| ann.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); |
| } else if (name != null){ |
| final String extNS = name.getNamespaceURI(); |
| if (!StringUtil.isEmpty(extNS)) { |
| final Set<Pair<QName,Integer>> pairs = contextProvider.getFunctionContext().getFunctions().keySet(); |
| for (Pair<QName, Integer> pair : pairs) { |
| // extension namespace is known |
| final String uri = pair.first.getNamespaceURI(); |
| if (uri != null && uri.equals(extNS)) { |
| holder.createWarningAnnotation(node, "Unknown function '" + name + "'"); |
| } |
| } |
| } |
| } |
| } 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"); |
| ann.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); |
| } |
| } |
| } else { |
| final XPathExpression[] arguments = call.getArgumentList(); |
| for (int i = 0; i < arguments.length; i++) { |
| checkArgument(holder, arguments[i], i, functionDecl.getParameters()); |
| } |
| if (arguments.length < functionDecl.getMinArity()) { |
| if (functionDecl.getMinArity() == 1) { |
| holder.createErrorAnnotation(node, "Missing argument for function '" + name + "'"); |
| } else { |
| final Parameter last = functionDecl.getParameters()[functionDecl.getParameters().length - 1]; |
| final String atLeast = |
| last.kind == Parameter.Kind.OPTIONAL || |
| last.kind == Parameter.Kind.VARARG ? |
| "at least " : ""; |
| holder.createErrorAnnotation(node, "Function '" + name + "' requires " + atLeast + functionDecl.getMinArity() + " arguments"); |
| } |
| } |
| } |
| } |
| |
| private static void checkArgument(AnnotationHolder holder, XPathExpression argument, int i, Parameter[] parameters) { |
| if (i >= parameters.length) { |
| if (parameters.length > 0 && parameters[parameters.length - 1].kind == Parameter.Kind.VARARG) { |
| // OK. Validate types against the last declared - vararg - param. |
| } else { |
| holder.createErrorAnnotation(argument, "Too many arguments"); |
| } |
| } |
| } |
| |
| private static void checkNodeTypeTest(AnnotationHolder holder, XPathNodeTypeTest test) { |
| final NodeType nodeType = test.getNodeType(); |
| if (nodeType == null) { |
| return; |
| } |
| |
| final XPathExpression[] arguments = test.getArgumentList(); |
| if (test.getXPathVersion() == XPathVersion.V2) { |
| |
| switch (nodeType) { |
| case NODE: |
| case TEXT: |
| case COMMENT: |
| markExceedingArguments(holder, arguments, 0); |
| break; |
| |
| // TODO: parser doesn't understand TypeName? yet: |
| // ElementTest ::= "element" "(" (ElementNameOrWildcard ("," TypeName "?"?)?)? ")" |
| case ELEMENT: |
| case ATTRIBUTE: |
| checkKindTestArguments(holder, test, true, 0, 2); |
| break; |
| |
| case SCHEMA_ELEMENT: |
| case SCHEMA_ATTRIBUTE: |
| checkKindTestArguments(holder, test, false, 1, 1); |
| break; |
| |
| case DOCUMENT_NODE: |
| if (arguments.length >= 1) { |
| markExceedingArguments(holder, arguments, 1); |
| |
| final XPathNodeTypeTest argument = findNodeType(arguments[0]); |
| if (argument != null) { |
| final NodeType type = argument.getNodeType(); |
| if (type == NodeType.ELEMENT || type == NodeType.SCHEMA_ELEMENT) { |
| return; |
| } |
| } |
| holder.createErrorAnnotation(arguments[0], "element() or schema-element() expected"); |
| } |
| break; |
| |
| case PROCESSING_INSTRUCTION: |
| if (arguments.length >= 1) { |
| markExceedingArguments(holder, arguments, 1); |
| |
| if (arguments[0] instanceof XPathString) { |
| break; |
| } else { |
| final PrefixedName argument = findQName(arguments[0]); |
| if (argument != null) { |
| if (argument.getPrefix() == null && !"*".equals(argument.getLocalName())) { |
| break; |
| } |
| } |
| } |
| holder.createErrorAnnotation(arguments[0], "String literal or NCName expected"); |
| } |
| break; |
| } |
| } else { |
| if (arguments.length == 0) { |
| return; |
| } |
| if (test.getNodeType() == NodeType.PROCESSING_INSTRUCTION && arguments.length == 1) { |
| if (!(arguments[0] instanceof XPathString)) { |
| holder.createErrorAnnotation(arguments[0], "String literal expected"); |
| } |
| return; |
| } |
| holder.createErrorAnnotation(test, "Invalid number of arguments for node type test '" + nodeType.getType() + "'"); |
| } |
| } |
| |
| private static void checkKindTestArguments(AnnotationHolder holder, |
| XPathNodeTypeTest test, |
| boolean wildcardAllowed, |
| int min, |
| int max) { |
| final XPathExpression[] arguments = test.getArgumentList(); |
| if (arguments.length >= min) { |
| for (XPathExpression arg : arguments) { |
| final PrefixedName argument = findQName(arg); |
| if (argument == null) { |
| holder.createErrorAnnotation(arg, "QName expected"); |
| } else { |
| if (!wildcardAllowed && ("*".equals(argument.getPrefix()) || "*".equals(argument.getLocalName()))) { |
| holder.createErrorAnnotation(arg, "QName expected"); |
| } |
| } |
| } |
| } else { |
| holder.createErrorAnnotation(test, "Missing argument for node kind test"); |
| } |
| |
| markExceedingArguments(holder, arguments, max); |
| } |
| |
| private static void markExceedingArguments(AnnotationHolder holder, XPathExpression[] arguments, int start) { |
| for (int i = start; i < arguments.length; i++) { |
| holder.createErrorAnnotation(arguments[i], "Too many arguments"); |
| } |
| } |
| |
| @Nullable |
| private static XPathNodeTypeTest findNodeType(XPathExpression argument) { |
| final XPathNodeTest test = findNodeTest(argument); |
| if (test != null && !test.isNameTest() && test.getPrincipalType() == XPathNodeTest.PrincipalType.ELEMENT) { |
| return PsiTreeUtil.getChildOfType(test, XPathNodeTypeTest.class); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PrefixedName findQName(XPathExpression argument) { |
| final XPathNodeTest test = findNodeTest(argument); |
| if (test != null && test.isNameTest() && test.getPrincipalType() == XPathNodeTest.PrincipalType.ELEMENT) { |
| return test.getQName(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static XPathNodeTest findNodeTest(XPathExpression argument) { |
| if (argument instanceof XPathLocationPath) { |
| final XPathStep step = ((XPathLocationPath)argument).getFirstStep(); |
| if (step != null && step.getPreviousStep() == null && step.getPredicates().length == 0) { |
| final XPathNodeTest test = step.getNodeTest(); |
| if (test != null) { |
| return test; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static void checkNodeTest(@NotNull ContextProvider myProvider, AnnotationHolder holder, XPathNodeTest nodeTest) { |
| checkSillyNodeTest(holder, nodeTest); |
| |
| checkPrefixReferences(holder, nodeTest, myProvider); |
| } |
| |
| private static void checkPrefixReferences(AnnotationHolder holder, QNameElement element, ContextProvider myProvider) { |
| final PsiReference[] references = element.getReferences(); |
| for (PsiReference reference : references) { |
| if (reference instanceof PrefixReference) { |
| final PrefixReference pr = ((PrefixReference)reference); |
| if (!pr.isSoft() && pr.isUnresolved()) { |
| final TextRange range = pr.getRangeInElement().shiftRight(pr.getElement().getTextRange().getStartOffset()); |
| final Annotation a = holder.createErrorAnnotation(range, "Unresolved namespace prefix '" + pr.getCanonicalText() + "'"); |
| a.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); |
| |
| final NamespaceContext namespaceContext = myProvider.getNamespaceContext(); |
| final PrefixedName qName = element.getQName(); |
| if (namespaceContext != null && qName != null) { |
| final IntentionAction[] fixes = namespaceContext.getUnresolvedNamespaceFixes(reference, qName.getLocalName()); |
| for (IntentionAction fix : fixes) { |
| a.registerFix(fix); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static void checkSillyNodeTest(AnnotationHolder holder, XPathNodeTest nodeTest) { |
| if (nodeTest.getPrincipalType() != XPathNodeTest.PrincipalType.ELEMENT) { |
| final XPathNodeTypeTest typeTest = PsiTreeUtil.getChildOfType(nodeTest, XPathNodeTypeTest.class); |
| if (typeTest != null) { |
| holder.createWarningAnnotation(typeTest, "Silly node type test on axis '" + nodeTest.getPrincipalType().getType() + "'"); |
| } |
| } |
| } |
| |
| @Override |
| public void visitXPathPredicate(XPathPredicate o) { |
| final XPathExpression expression = o.getPredicateExpression(); |
| if (expression instanceof XPathLocationPath) { |
| final XPathExpression parentOfType = PsiTreeUtil.getParentOfType(o, XPathExpression.class, true); |
| if (parentOfType != null && XPath2Type.ANYATOMICTYPE.isAssignableFrom(parentOfType.getType())) { |
| myHolder.createErrorAnnotation(expression, "Axis step cannot be used here: the context item is an atomic value"); |
| } |
| } |
| super.visitXPathPredicate(o); |
| } |
| |
| private static void checkExpression(AnnotationHolder holder, @NotNull XPathExpression expression) { |
| final XPathType expectedType = ExpectedTypeUtil.getExpectedType(expression); |
| final XPathType opType = ExpectedTypeUtil.mapType(expression, expression.getType()); |
| if (!XPathType.isAssignable(expectedType, opType)) { |
| holder.createErrorAnnotation(expression, "Expected type '" + expectedType.getName() + "', got '" + opType.getName() + "'"); |
| } |
| } |
| } |