2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.daemon.impl.analysis;
18 import com.intellij.BundleBase;
19 import com.intellij.codeInsight.daemon.*;
20 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
21 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
22 import com.intellij.codeInsight.daemon.impl.HighlightVisitor;
23 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
24 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
25 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl;
26 import com.intellij.codeInsight.intention.IntentionAction;
27 import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
28 import com.intellij.codeInspection.*;
29 import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspectionBase;
30 import com.intellij.codeInspection.htmlInspections.XmlEntitiesInspection;
31 import com.intellij.lang.ASTNode;
32 import com.intellij.lang.dtd.DTDLanguage;
33 import com.intellij.lang.injection.InjectedLanguageManager;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.editor.markup.TextAttributes;
36 import com.intellij.openapi.extensions.Extensions;
37 import com.intellij.openapi.progress.ProgressManager;
38 import com.intellij.openapi.util.Comparing;
39 import com.intellij.openapi.util.TextRange;
40 import com.intellij.openapi.util.UserDataCache;
41 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
42 import com.intellij.psi.*;
43 import com.intellij.psi.html.HtmlTag;
44 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
45 import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceOwner;
46 import com.intellij.psi.meta.PsiMetaData;
47 import com.intellij.psi.templateLanguages.OuterLanguageElement;
48 import com.intellij.psi.tree.IElementType;
49 import com.intellij.psi.util.PsiTreeUtil;
50 import com.intellij.psi.xml.*;
51 import com.intellij.xml.*;
52 import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
53 import com.intellij.xml.util.AnchorReference;
54 import com.intellij.xml.util.HtmlUtil;
55 import com.intellij.xml.util.XmlTagUtil;
56 import com.intellij.xml.util.XmlUtil;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
60 import java.util.HashSet;
62 import java.util.StringTokenizer;
67 public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightVisitor, IdeValidationHost {
68 private static final Logger LOG = Logger.getInstance("com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor");
69 private static final UserDataCache<Boolean, PsiElement, Object> DO_NOT_VALIDATE =
70 new UserDataCache<Boolean, PsiElement, Object>("do not validate") {
72 protected Boolean compute(PsiElement parent, Object p) {
73 OuterLanguageElement element = PsiTreeUtil.getChildOfType(parent, OuterLanguageElement.class);
75 if (element == null) {
76 // JspOuterLanguageElement is located under XmlText
77 for (PsiElement child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
78 if (child instanceof XmlText) {
79 element = PsiTreeUtil.getChildOfType(child, OuterLanguageElement.class);
80 if (element != null) {
86 if (element == null) return false;
87 PsiFile containingFile = parent.getContainingFile();
88 return containingFile.getViewProvider().getBaseLanguage() != containingFile.getLanguage();
91 private static boolean ourDoJaxpTesting;
93 private static final TextAttributes NONEMPTY_TEXT_ATTRIBUTES = new TextAttributes() {
95 public boolean isEmpty() {
99 private HighlightInfoHolder myHolder;
101 public XmlHighlightVisitor() {
104 private void addElementsForTag(XmlTag tag,
105 @NotNull String localizedMessage,
106 HighlightInfoType type,
107 IntentionAction quickFixAction) {
108 addElementsForTagWithManyQuickFixes(tag, localizedMessage, type, quickFixAction);
111 private void addElementsForTagWithManyQuickFixes(XmlTag tag,
112 @NotNull String localizedMessage,
113 HighlightInfoType type, IntentionAction... quickFixActions) {
114 bindMessageToTag(tag, type, -1, localizedMessage, quickFixActions);
117 @Override public void visitXmlToken(XmlToken token) {
118 IElementType tokenType = token.getTokenType();
119 if (tokenType == XmlTokenType.XML_NAME || tokenType == XmlTokenType.XML_TAG_NAME) {
120 PsiElement element = token.getPrevSibling();
121 while(element instanceof PsiWhiteSpace) element = element.getPrevSibling();
123 if (element instanceof XmlToken) {
124 if (((XmlToken)element).getTokenType() == XmlTokenType.XML_START_TAG_START) {
125 PsiElement parent = element.getParent();
127 if (parent instanceof XmlTag && !(token.getNextSibling() instanceof OuterLanguageElement)) {
128 checkTag((XmlTag)parent);
132 PsiElement parent = token.getParent();
134 if (parent instanceof XmlAttribute && !(token.getNextSibling() instanceof OuterLanguageElement)) {
135 checkAttribute((XmlAttribute) parent);
138 } else if (tokenType == XmlTokenType.XML_DATA_CHARACTERS && token.getParent() instanceof XmlText) {
139 if (token.textContains(']') && token.textContains('>')) {
141 String s = token.getText();
142 String marker = "]]>";
143 int i = s.indexOf(marker);
145 if (i != -1 ) { // TODO: fix
146 XmlTag tag = PsiTreeUtil.getParentOfType(token, XmlTag.class);
147 if (tag != null && XmlExtension.getExtensionByElement(tag).shouldBeHighlightedAsTag(tag) && !skipValidation(tag)) {
148 TextRange textRange = token.getTextRange();
149 int start = textRange.getStartOffset() + i;
150 HighlightInfoType type = tag instanceof HtmlTag ? HighlightInfoType.WARNING : HighlightInfoType.ERROR;
151 String description = XmlErrorMessages.message("cdata.end.should.not.appear.in.content.unless.to.mark.end.of.cdata.section");
152 HighlightInfo info = HighlightInfo.newHighlightInfo(type).range(start, start + marker.length()).descriptionAndTooltip(description).create();
160 private void checkTag(XmlTag tag) {
161 if (ourDoJaxpTesting) return;
163 if (!myHolder.hasErrorResults()) {
164 checkTagByDescriptor(tag);
167 if (!myHolder.hasErrorResults()) {
168 if (!skipValidation(tag)) {
169 final XmlElementDescriptor descriptor = tag.getDescriptor();
171 if (tag instanceof HtmlTag &&
172 ( descriptor instanceof AnyXmlElementDescriptor ||
179 checkReferences(tag);
184 private void bindMessageToTag(final XmlTag tag,
185 final HighlightInfoType warning,
186 final int messageLength,
187 @NotNull String localizedMessage,
188 IntentionAction... quickFixActions) {
189 XmlToken childByRole = XmlTagUtil.getStartTagNameElement(tag);
191 bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
192 childByRole = XmlTagUtil.getEndTagNameElement(tag);
193 bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
198 public void visitXmlProcessingInstruction(XmlProcessingInstruction processingInstruction) {
199 super .visitXmlProcessingInstruction(processingInstruction);
200 PsiElement parent = processingInstruction.getParent();
202 if (parent instanceof XmlProlog && processingInstruction.getText().startsWith("<?xml")) {
203 for(PsiElement e = PsiTreeUtil.prevLeaf(processingInstruction); e != null; e = PsiTreeUtil.prevLeaf(e)) {
204 if (e instanceof PsiWhiteSpace && PsiTreeUtil.prevLeaf(e) != null ||
205 e instanceof OuterLanguageElement) {
208 PsiElement eParent = e.getParent();
209 if (eParent instanceof PsiComment) e = eParent;
210 if (eParent instanceof XmlProcessingInstruction) break;
212 String description = XmlErrorMessages.message("xml.declaration.should.precede.all.document.content");
213 addToResults(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(e).descriptionAndTooltip(description).create());
216 checkReferences(processingInstruction);
219 private void bindMessageToAstNode(final PsiElement childByRole,
220 final HighlightInfoType warning,
223 @NotNull String localizedMessage,
224 IntentionAction... quickFixActions) {
225 if(childByRole != null) {
226 final TextRange textRange = childByRole.getTextRange();
227 if (length == -1) length = textRange.getLength();
228 final int startOffset = textRange.getStartOffset() + offset;
230 HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(warning).range(childByRole, startOffset, startOffset + length).descriptionAndTooltip(localizedMessage).create();
232 if (highlightInfo == null) {
233 highlightInfo = HighlightInfo.newHighlightInfo(warning).range(new TextRange(startOffset, startOffset + length)).textAttributes(NONEMPTY_TEXT_ATTRIBUTES).descriptionAndTooltip(localizedMessage).create();
236 for (final IntentionAction quickFixAction : quickFixActions) {
237 if (quickFixAction == null) continue;
238 QuickFixAction.registerQuickFixAction(highlightInfo, textRange, quickFixAction);
240 addToResults(highlightInfo);
244 private void checkTagByDescriptor(final XmlTag tag) {
245 String name = tag.getName();
247 XmlElementDescriptor elementDescriptor;
249 final PsiElement parent = tag.getParent();
250 if (parent instanceof XmlTag) {
251 XmlTag parentTag = (XmlTag)parent;
253 elementDescriptor = XmlUtil.getDescriptorFromContext(tag);
255 final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
257 if (parentDescriptor != null && elementDescriptor == null && shouldBeValidated(tag)) {
258 if (tag instanceof HtmlTag) {
259 //XmlEntitiesInspection inspection = getInspectionProfile(tag, HtmlStyleLocalInspection.SHORT_NAME);
260 //if (inspection != null /*&& isAdditionallyDeclared(inspection.getAdditionalEntries(XmlEntitiesInspection.UNKNOWN_TAG), name)*/) {
267 XmlErrorMessages.message("element.is.not.allowed.here", name),
268 getTagProblemInfoType(tag),
274 if (elementDescriptor instanceof AnyXmlElementDescriptor ||
275 elementDescriptor == null
277 elementDescriptor = tag.getDescriptor();
280 if (elementDescriptor == null) return;
284 elementDescriptor = tag.getDescriptor();
286 if (elementDescriptor == null) {
287 addElementsForTag(tag, XmlErrorMessages.message("element.must.be.declared", name), HighlightInfoType.WRONG_REF, null);
292 if (!(elementDescriptor instanceof XmlHighlightingAwareElementDescriptor) ||
293 ((XmlHighlightingAwareElementDescriptor)elementDescriptor).shouldCheckRequiredAttributes()) {
294 checkRequiredAttributes(tag, name, elementDescriptor);
297 if (elementDescriptor instanceof Validator) {
298 //noinspection unchecked
299 ((Validator<XmlTag>)elementDescriptor).validate(tag,this);
303 private void checkRequiredAttributes(XmlTag tag, String name, XmlElementDescriptor elementDescriptor) {
304 XmlAttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributesDescriptors(tag);
305 Set<String> requiredAttributes = null;
307 for (XmlAttributeDescriptor attribute : attributeDescriptors) {
308 if (attribute != null && attribute.isRequired()) {
309 if (requiredAttributes == null) {
310 requiredAttributes = new HashSet<String>();
312 requiredAttributes.add(attribute.getName(tag));
316 if (requiredAttributes != null) {
317 for (final String attrName : requiredAttributes) {
318 if (!hasAttribute(tag, attrName) &&
319 !XmlExtension.getExtension(tag.getContainingFile()).isRequiredAttributeImplicitlyPresent(tag, attrName)) {
321 IntentionAction insertRequiredAttributeIntention = XmlQuickFixFactory.getInstance().insertRequiredAttributeFix(tag, attrName);
322 final String localizedMessage = XmlErrorMessages.message("element.doesnt.have.required.attribute", name, attrName);
323 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getCurrentProfile();
324 RequiredAttributesInspectionBase inspection =
325 (RequiredAttributesInspectionBase)profile.getUnwrappedTool(XmlEntitiesInspection.REQUIRED_ATTRIBUTES_SHORT_NAME, tag);
326 if (inspection != null) {
331 insertRequiredAttributeIntention,
332 HighlightDisplayKey.find(XmlEntitiesInspection.REQUIRED_ATTRIBUTES_SHORT_NAME),
334 RequiredAttributesInspectionBase.getIntentionAction(attrName)
342 private static boolean hasAttribute(XmlTag tag, String attrName) {
343 final XmlAttribute attribute = tag.getAttribute(attrName);
344 if (attribute == null) return false;
345 if (attribute.getValueElement() != null) return true;
346 if (!(tag instanceof HtmlTag)) return false;
347 final XmlAttributeDescriptor descriptor = attribute.getDescriptor();
348 return descriptor != null && HtmlUtil.isBooleanAttribute(descriptor, tag);
351 private void reportOneTagProblem(final XmlTag tag,
353 @NotNull String localizedMessage,
354 final IntentionAction basicIntention,
355 final HighlightDisplayKey key,
356 final XmlEntitiesInspection inspection,
357 final IntentionAction addAttributeFix) {
358 boolean htmlTag = false;
360 if (tag instanceof HtmlTag) {
362 if(isAdditionallyDeclared(inspection.getAdditionalEntries(), name)) return;
365 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getCurrentProfile();
366 if (htmlTag && profile.isToolEnabled(key, tag)) {
367 addElementsForTagWithManyQuickFixes(
370 isInjectedWithoutValidation(tag) ?
371 HighlightInfoType.INFORMATION :
372 SeverityRegistrar.getSeverityRegistrar(tag.getProject()).getHighlightInfoTypeBySeverity(profile.getErrorLevel(key, tag).getSeverity()),
375 } else if (!htmlTag) {
379 HighlightInfoType.ERROR,
385 private static boolean isAdditionallyDeclared(final String additional, String name) {
386 name = name.toLowerCase();
387 if (!additional.contains(name)) return false;
389 StringTokenizer tokenizer = new StringTokenizer(additional, ", ");
390 while (tokenizer.hasMoreTokens()) {
391 if (name.equals(tokenizer.nextToken())) {
399 private static HighlightInfoType getTagProblemInfoType(XmlTag tag) {
400 if (tag instanceof HtmlTag && XmlUtil.HTML_URI.equals(tag.getNamespace())) {
401 if (isInjectedWithoutValidation(tag)) return HighlightInfoType.INFORMATION;
402 return HighlightInfoType.WARNING;
404 return HighlightInfoType.WRONG_REF;
407 public static boolean isInjectedWithoutValidation(PsiElement element) {
408 PsiElement context = InjectedLanguageManager.getInstance(element.getProject()).getInjectionHost(element.getContainingFile());
409 return context != null && skipValidation(context);
412 public static boolean skipValidation(PsiElement context) {
413 return DO_NOT_VALIDATE.get(context, null);
416 public static void setSkipValidation(@NotNull PsiElement element) {
417 DO_NOT_VALIDATE.put(element, Boolean.TRUE);
420 @Override public void visitXmlAttribute(XmlAttribute attribute) {}
422 private void checkAttribute(XmlAttribute attribute) {
423 XmlTag tag = attribute.getParent();
424 if (tag == null) return;
426 final String name = attribute.getName();
428 if (XmlExtension.getExtension(attribute.getContainingFile()).needWhitespaceBeforeAttribute()) {
429 PsiElement prevLeaf = PsiTreeUtil.prevLeaf(attribute);
431 if (!(prevLeaf instanceof PsiWhiteSpace)) {
432 TextRange textRange = attribute.getTextRange();
433 HighlightInfoType type = tag instanceof HtmlTag ? HighlightInfoType.WARNING : HighlightInfoType.ERROR;
434 String description = XmlErrorMessages.message("attribute.should.be.preceded.with.space");
435 HighlightInfo info = HighlightInfo.newHighlightInfo(type).range(textRange.getStartOffset(), textRange.getStartOffset()).descriptionAndTooltip(description).create();
440 if (attribute.isNamespaceDeclaration() || XmlUtil.XML_SCHEMA_INSTANCE_URI.equals(attribute.getNamespace())) {
441 //checkReferences(attribute.getValueElement());
445 XmlElementDescriptor elementDescriptor = tag.getDescriptor();
446 if (elementDescriptor == null ||
447 elementDescriptor instanceof AnyXmlElementDescriptor ||
452 XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute);
454 if (attributeDescriptor == null) {
455 if (!XmlUtil.attributeFromTemplateFramework(name, tag)) {
456 final String localizedMessage = XmlErrorMessages.message("attribute.is.not.allowed.here", name);
457 final HighlightInfo highlightInfo = reportAttributeProblem(tag, name, attribute, localizedMessage);
458 if (highlightInfo != null) {
459 PsiFile file = tag.getContainingFile();
461 for (XmlUndefinedElementFixProvider fixProvider : Extensions.getExtensions(XmlUndefinedElementFixProvider.EP_NAME)) {
462 IntentionAction[] fixes = fixProvider.createFixes(attribute);
464 for (IntentionAction action : fixes) {
465 QuickFixAction.registerQuickFixAction(highlightInfo, action);
475 checkDuplicateAttribute(tag, attribute);
477 // we skip resolve of attribute references since there is separate check when taking attribute descriptors
478 PsiReference[] attrRefs = attribute.getReferences();
479 doCheckRefs(attribute, attrRefs, !attribute.getNamespacePrefix().isEmpty() ? 2 : 1);
484 private HighlightInfo reportAttributeProblem(final XmlTag tag,
485 final String localName,
486 final XmlAttribute attribute,
487 @NotNull String localizedMessage) {
489 final RemoveAttributeIntentionFix removeAttributeIntention = new RemoveAttributeIntentionFix(localName,attribute);
491 if (!(tag instanceof HtmlTag)) {
492 final HighlightInfoType tagProblemInfoType = HighlightInfoType.WRONG_REF;
494 final ASTNode node = SourceTreeToPsiMap.psiElementToTree(attribute);
496 final ASTNode child = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node);
497 assert child != null;
498 final HighlightInfo highlightInfo =
499 HighlightInfo.newHighlightInfo(tagProblemInfoType).range(child).descriptionAndTooltip(localizedMessage).create();
500 addToResults(highlightInfo);
502 QuickFixAction.registerQuickFixAction(highlightInfo, removeAttributeIntention);
504 return highlightInfo;
510 private void checkDuplicateAttribute(XmlTag tag, final XmlAttribute attribute) {
511 if (skipValidation(tag)) {
515 final XmlAttribute[] attributes = tag.getAttributes();
516 final PsiFile containingFile = tag.getContainingFile();
517 final XmlExtension extension = containingFile instanceof XmlFile ?
518 XmlExtension.getExtension(containingFile) :
519 DefaultXmlExtension.DEFAULT_EXTENSION;
520 for (XmlAttribute tagAttribute : attributes) {
521 ProgressManager.checkCanceled();
522 if (attribute != tagAttribute && Comparing.strEqual(attribute.getName(), tagAttribute.getName())) {
523 final String localName = attribute.getLocalName();
525 if (extension.canBeDuplicated(tagAttribute)) continue; // multiple import attributes are allowed in jsp directive
527 final ASTNode attributeNode = SourceTreeToPsiMap.psiElementToTree(attribute);
528 assert attributeNode != null;
529 final ASTNode attributeNameNode = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(attributeNode);
530 assert attributeNameNode != null;
531 HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(getTagProblemInfoType(tag))
532 .range(attributeNameNode)
533 .descriptionAndTooltip(XmlErrorMessages.message("duplicate.attribute", localName)).create();
534 addToResults(highlightInfo);
536 IntentionAction intentionAction = new RemoveAttributeIntentionFix(localName, attribute);
538 QuickFixAction.registerQuickFixAction(highlightInfo, intentionAction);
543 @Override public void visitXmlDocument(final XmlDocument document) {
544 if (document.getLanguage() == DTDLanguage.INSTANCE) {
545 final PsiMetaData psiMetaData = document.getMetaData();
546 if (psiMetaData instanceof Validator) {
547 //noinspection unchecked
548 ((Validator<XmlDocument>)psiMetaData).validate(document, this);
553 @Override public void visitXmlTag(XmlTag tag) {
556 @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
557 checkReferences(value);
559 final PsiElement parent = value.getParent();
560 if (!(parent instanceof XmlAttribute)) {
564 XmlAttribute attribute = (XmlAttribute)parent;
566 XmlTag tag = attribute.getParent();
568 XmlElementDescriptor elementDescriptor = tag.getDescriptor();
569 XmlAttributeDescriptor attributeDescriptor = elementDescriptor != null ? elementDescriptor.getAttributeDescriptor(attribute):null;
571 if (attributeDescriptor != null && !skipValidation(value)) {
572 String error = attributeDescriptor.validateValue(value, attribute.getValue());
575 HighlightInfoType type = getTagProblemInfoType(tag);
576 addToResults(HighlightInfo.newHighlightInfo(type).range(value).descriptionAndTooltip(error).create());
581 private void checkReferences(PsiElement value) {
582 if (value == null) return;
584 doCheckRefs(value, value.getReferences(), 0);
587 private void doCheckRefs(final PsiElement value, final PsiReference[] references, int start) {
588 for (int i = start; i < references.length; ++i) {
589 PsiReference reference = references[i];
590 ProgressManager.checkCanceled();
591 if (isUrlReference(reference)) continue;
592 if (!hasBadResolve(reference, false)) {
595 String description = getErrorDescription(reference);
597 final int startOffset = reference.getElement().getTextRange().getStartOffset();
598 final TextRange referenceRange = reference.getRangeInElement();
600 // logging for IDEADEV-29655
601 if (referenceRange.getStartOffset() > referenceRange.getEndOffset()) {
602 LOG.error("Reference range start offset > end offset: " + reference +
603 ", start offset: " + referenceRange.getStartOffset() + ", end offset: " + referenceRange.getEndOffset());
606 HighlightInfoType type = getTagProblemInfoType(PsiTreeUtil.getParentOfType(value, XmlTag.class));
607 if (value instanceof XmlAttributeValue) {
608 PsiElement parent = value.getParent();
609 if (parent instanceof XmlAttribute) {
610 String name = ((XmlAttribute)parent).getName().toLowerCase();
611 if (type.getSeverity(null).compareTo(HighlightInfoType.WARNING.getSeverity(null)) > 0 && name.endsWith("stylename")) {
612 type = HighlightInfoType.WARNING;
616 HighlightInfo info = HighlightInfo.newHighlightInfo(type)
617 .range(startOffset + referenceRange.getStartOffset(), startOffset + referenceRange.getEndOffset())
618 .descriptionAndTooltip(description).create();
620 if (reference instanceof LocalQuickFixProvider) {
621 LocalQuickFix[] fixes = ((LocalQuickFixProvider)reference).getQuickFixes();
623 InspectionManager manager = InspectionManager.getInstance(reference.getElement().getProject());
624 for (LocalQuickFix fix : fixes) {
625 ProblemDescriptor descriptor = manager.createProblemDescriptor(value, description, fix,
626 ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true);
627 QuickFixAction.registerQuickFixAction(info, new LocalQuickFixAsIntentionAdapter(fix, descriptor));
631 UnresolvedReferenceQuickFixProvider.registerReferenceFixes(reference, new QuickFixActionRegistrarImpl(info));
635 public static boolean isUrlReference(PsiReference reference) {
636 return reference instanceof FileReferenceOwner || reference instanceof AnchorReference;
640 public static String getErrorDescription(@NotNull PsiReference reference) {
642 if (reference instanceof EmptyResolveMessageProvider) {
643 message = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern();
646 //noinspection UnresolvedPropertyKey
647 message = PsiBundle.message("cannot.resolve.symbol");
652 description = BundleBase.format(message, reference.getCanonicalText()); // avoid double formatting
654 catch (IllegalArgumentException ex) {
655 // unresolvedMessage provided by third-party reference contains wrong format string (e.g. {}), tolerate it
656 description = message;
661 public static boolean hasBadResolve(final PsiReference reference, boolean checkSoft) {
662 if (!checkSoft && reference.isSoft()) return false;
663 if (reference instanceof PsiPolyVariantReference) {
664 return ((PsiPolyVariantReference)reference).multiResolve(false).length == 0;
666 return reference.resolve() == null;
669 @Override public void visitXmlDoctype(XmlDoctype xmlDoctype) {
670 if (skipValidation(xmlDoctype)) return;
671 checkReferences(xmlDoctype);
674 private void addToResults(final HighlightInfo info) {
678 public static void setDoJaxpTesting(boolean doJaxpTesting) {
679 ourDoJaxpTesting = doJaxpTesting;
683 public void addMessage(PsiElement context, String message, int type) {
684 throw new UnsupportedOperationException();
688 public void addMessage(PsiElement context, String message, @NotNull ErrorType type) {
689 addMessageWithFixes(context, message, type);
693 public void addMessageWithFixes(final PsiElement context, final String message, @NotNull final ErrorType type, @NotNull final IntentionAction... fixes) {
694 if (message != null && !message.isEmpty()) {
695 final PsiFile containingFile = context.getContainingFile();
696 final HighlightInfoType defaultInfoType = type == ErrorType.ERROR ? HighlightInfoType.ERROR : type == ErrorType.WARNING ? HighlightInfoType.WARNING : HighlightInfoType.WEAK_WARNING;
698 if (context instanceof XmlTag && XmlExtension.getExtension(containingFile).shouldBeHighlightedAsTag((XmlTag)context)) {
699 addElementsForTagWithManyQuickFixes((XmlTag)context, message, defaultInfoType, fixes);
702 final PsiElement contextOfFile = InjectedLanguageManager.getInstance(containingFile.getProject()).getInjectionHost(containingFile);
703 final HighlightInfo highlightInfo;
705 if (contextOfFile != null) {
706 TextRange range = InjectedLanguageManager.getInstance(context.getProject()).injectedToHost(context, context.getTextRange());
707 highlightInfo = HighlightInfo.newHighlightInfo(defaultInfoType).range(range).descriptionAndTooltip(message).create();
711 HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(context).descriptionAndTooltip(message).create();
714 for (final IntentionAction quickFixAction : fixes) {
715 if (quickFixAction == null) continue;
716 QuickFixAction.registerQuickFixAction(highlightInfo, quickFixAction);
718 addToResults(highlightInfo);
724 public boolean suitableForFile(@NotNull final PsiFile file) {
725 if (file instanceof XmlFile) return true;
727 for (PsiFile psiFile : file.getViewProvider().getAllFiles()) {
728 if (psiFile instanceof XmlFile) {
736 public void visit(@NotNull final PsiElement element) {
737 element.accept(this);
741 public boolean analyze(@NotNull final PsiFile file,
742 final boolean updateWholeFile,
743 @NotNull HighlightInfoHolder holder,
744 @NotNull Runnable action) {
757 public HighlightVisitor clone() {
758 return new XmlHighlightVisitor();
766 public static String getUnquotedValue(XmlAttributeValue value, XmlTag tag) {
767 String unquotedValue = value.getValue();
769 if (tag instanceof HtmlTag) {
770 unquotedValue = unquotedValue.toLowerCase();
773 return unquotedValue;
776 public static boolean shouldBeValidated(@NotNull XmlTag tag) {
777 PsiElement parent = tag.getParent();
778 if (parent instanceof XmlTag) {
779 return !skipValidation(parent) && !XmlUtil.tagFromTemplateFramework(tag);