highlight unresolved references at *StyleName attributes as warnings, test
[idea/community.git] / xml / impl / src / com / intellij / codeInsight / daemon / impl / analysis / XmlHighlightVisitor.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.codeInsight.daemon.impl.analysis;
17
18 import com.intellij.codeInsight.daemon.*;
19 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
20 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
21 import com.intellij.codeInsight.daemon.impl.HighlightVisitor;
22 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
23 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
24 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl;
25 import com.intellij.codeInsight.intention.IntentionAction;
26 import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
27 import com.intellij.codeInspection.InspectionProfile;
28 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
29 import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspection;
30 import com.intellij.codeInspection.htmlInspections.XmlEntitiesInspection;
31 import com.intellij.idea.LoggerFactory;
32 import com.intellij.lang.ASTNode;
33 import com.intellij.lang.dtd.DTDLanguage;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.editor.markup.TextAttributes;
36 import com.intellij.openapi.progress.ProgressManager;
37 import com.intellij.openapi.util.Comparing;
38 import com.intellij.openapi.util.Key;
39 import com.intellij.openapi.util.TextRange;
40 import com.intellij.openapi.util.text.StringUtil;
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.meta.PsiMetaData;
46 import com.intellij.psi.templateLanguages.OuterLanguageElement;
47 import com.intellij.psi.util.PsiTreeUtil;
48 import com.intellij.psi.util.PsiUtilBase;
49 import com.intellij.psi.xml.*;
50 import com.intellij.util.SmartList;
51 import com.intellij.xml.XmlAttributeDescriptor;
52 import com.intellij.xml.XmlElementDescriptor;
53 import com.intellij.xml.XmlExtension;
54 import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
55 import com.intellij.xml.util.HtmlUtil;
56 import com.intellij.xml.util.XmlTagUtil;
57 import com.intellij.xml.util.XmlUtil;
58 import org.jetbrains.annotations.NotNull;
59 import org.jetbrains.annotations.Nullable;
60
61 import java.text.MessageFormat;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.StringTokenizer;
66
67 /**
68  * @author Mike
69  */
70 public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightVisitor, Validator.ValidationHost {
71   private static final Logger LOG = LoggerFactory.getInstance().getLoggerInstance("com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor");
72   public static final Key<String> DO_NOT_VALIDATE_KEY = Key.create("do not validate");
73   private List<HighlightInfo> myResult;
74
75   private static boolean ourDoJaxpTesting;
76
77   private static final TextAttributes NONEMPTY_TEXT_ATTRIBUTES = new TextAttributes() {
78     public boolean isEmpty() {
79       return false;
80     }
81   };
82
83   private void addElementsForTag(XmlTag tag,
84                                  String localizedMessage,
85                                  HighlightInfoType type,
86                                  IntentionAction quickFixAction) {
87     addElementsForTagWithManyQuickFixes(tag, localizedMessage, type, quickFixAction);
88   }
89
90   private void addElementsForTagWithManyQuickFixes(XmlTag tag,
91                                                    String localizedMessage,
92                                                    HighlightInfoType type, IntentionAction... quickFixActions) {
93     bindMessageToTag(tag, type, -1, localizedMessage, quickFixActions);
94   }
95
96   @Override public void visitXmlToken(XmlToken token) {
97     if (token.getTokenType() == XmlTokenType.XML_NAME || token.getTokenType() == XmlTokenType.XML_TAG_NAME) {
98       PsiElement element = token.getPrevSibling();
99       while(element instanceof PsiWhiteSpace) element = element.getPrevSibling();
100
101       if (element instanceof XmlToken) {
102         if (((XmlToken)element).getTokenType() == XmlTokenType.XML_START_TAG_START) {
103           PsiElement parent = element.getParent();
104
105           if (parent instanceof XmlTag && !(token.getNextSibling() instanceof OuterLanguageElement)) {
106             checkTag((XmlTag)parent);
107           }
108         }
109       } else {
110         PsiElement parent = token.getParent();
111
112         if (parent instanceof XmlAttribute && !(token.getNextSibling() instanceof OuterLanguageElement)) {
113           checkAttribute((XmlAttribute) parent);
114         }
115       }
116     }
117   }
118
119   private void checkTag(XmlTag tag) {
120     if (ourDoJaxpTesting) return;
121
122     if (myResult == null) {
123       checkTagByDescriptor(tag);
124     }
125
126     if (myResult == null) {
127       if (tag.getUserData(DO_NOT_VALIDATE_KEY) == null) {
128         final XmlElementDescriptor descriptor = tag.getDescriptor();
129
130         if (tag instanceof HtmlTag &&
131             ( descriptor instanceof AnyXmlElementDescriptor ||
132               descriptor == null
133             )
134            ) {
135           return;
136         }
137
138         checkReferences(tag);
139       }
140     }
141   }
142
143   private void bindMessageToTag(final XmlTag tag, final HighlightInfoType warning, final int messageLength, final String localizedMessage, IntentionAction... quickFixActions) {
144     XmlToken childByRole = XmlTagUtil.getStartTagNameElement(tag);
145
146     bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
147     childByRole = XmlTagUtil.getEndTagNameElement(tag);
148     bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
149   }
150
151   private void bindMessageToAstNode(final PsiElement childByRole,
152                                     final HighlightInfoType warning,
153                                     final int offset,
154                                     int length,
155                                     final String localizedMessage, IntentionAction... quickFixActions) {
156     if(childByRole != null) {
157       final TextRange textRange = childByRole.getTextRange();
158       if (length == -1) length = textRange.getLength();
159       final int startOffset = textRange.getStartOffset() + offset;
160
161       HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
162         warning,
163         childByRole, startOffset, startOffset + length,
164         localizedMessage, HighlightInfo.htmlEscapeToolTip(localizedMessage)
165       );
166
167       if (highlightInfo == null) {
168         highlightInfo = HighlightInfo.createHighlightInfo(
169           warning,
170           new TextRange(startOffset, startOffset + length),
171           localizedMessage,
172           localizedMessage, NONEMPTY_TEXT_ATTRIBUTES
173         );
174       }
175
176       for (final IntentionAction quickFixAction : quickFixActions) {
177         if (quickFixAction == null) continue;
178         QuickFixAction.registerQuickFixAction(highlightInfo, textRange, quickFixAction, null);
179       }
180       addToResults(highlightInfo);
181     }
182   }
183
184   private void checkTagByDescriptor(final XmlTag tag) {
185     String name = tag.getName();
186
187     XmlElementDescriptor elementDescriptor = null;
188
189     final PsiElement parent = tag.getParent();
190     if (parent instanceof XmlTag) {
191       XmlTag parentTag = (XmlTag)parent;
192       final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
193
194       if (parentDescriptor != null) {
195         elementDescriptor = XmlExtension.getExtension(tag.getContainingFile()).getElementDescriptor(tag, parentTag, parentDescriptor);
196       }
197
198       if (parentDescriptor != null &&
199           elementDescriptor == null &&
200           parentTag.getUserData(DO_NOT_VALIDATE_KEY) == null &&
201           !XmlUtil.tagFromTemplateFramework(tag)
202       ) {
203         if (tag instanceof HtmlTag) {
204           //XmlEntitiesInspection inspection = getInspectionProfile(tag, HtmlStyleLocalInspection.SHORT_NAME);
205           //if (inspection != null /*&& isAdditionallyDeclared(inspection.getAdditionalEntries(XmlEntitiesInspection.UNKNOWN_TAG), name)*/) {
206             return;
207           //}
208         }
209
210         addElementsForTag(
211           tag,
212           XmlErrorMessages.message("element.is.not.allowed.here", name),
213           getTagProblemInfoType(tag),
214           null
215         );
216         return;
217       }
218
219       if (elementDescriptor instanceof AnyXmlElementDescriptor ||
220           elementDescriptor == null
221          ) {
222         elementDescriptor = tag.getDescriptor();
223       }
224
225       if (elementDescriptor == null) return;
226     }
227     else {
228       //root tag
229       elementDescriptor = tag.getDescriptor();
230
231      if (elementDescriptor == null) {
232        addElementsForTag(tag, XmlErrorMessages.message("element.must.be.declared", name), HighlightInfoType.WRONG_REF, null);
233        return;
234       }
235     }
236
237     XmlAttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributesDescriptors(tag);
238     Set<String> requiredAttributes = null;
239
240     for (XmlAttributeDescriptor attribute : attributeDescriptors) {
241       if (attribute != null && attribute.isRequired()) {
242         if (requiredAttributes == null) {
243           requiredAttributes = new HashSet<String>();
244         }
245         requiredAttributes.add(attribute.getName(tag));
246       }
247     }
248
249     if (requiredAttributes != null) {
250       for (final String attrName : requiredAttributes) {
251         if (tag.getAttribute(attrName, "") == null &&
252             !XmlExtension.getExtension(tag.getContainingFile()).isRequiredAttributeImplicitlyPresent(tag, attrName)) {
253
254           final InsertRequiredAttributeFix insertRequiredAttributeIntention = new InsertRequiredAttributeFix(
255               tag, attrName, null);
256           final String localizedMessage = XmlErrorMessages.message("element.doesnt.have.required.attribute", name, attrName);
257           final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getInspectionProfile();
258           final LocalInspectionToolWrapper toolWrapper =
259             (LocalInspectionToolWrapper)profile.getInspectionTool(RequiredAttributesInspection.SHORT_NAME, tag);
260           if (toolWrapper != null) {
261             RequiredAttributesInspection inspection = (RequiredAttributesInspection)toolWrapper.getTool();
262             reportOneTagProblem(
263               tag,
264               attrName,
265               localizedMessage,
266               insertRequiredAttributeIntention,
267               HighlightDisplayKey.find(RequiredAttributesInspection.SHORT_NAME),
268               inspection,
269               XmlEntitiesInspection.NOT_REQUIRED_ATTRIBUTE
270             );
271           }
272         }
273       }
274     }
275
276     if (elementDescriptor instanceof Validator) {
277       //noinspection unchecked
278       ((Validator<XmlTag>)elementDescriptor).validate(tag,this);
279     }
280   }
281
282   private void reportOneTagProblem(final XmlTag tag,
283                                    final String name,
284                                    final String localizedMessage,
285                                    final IntentionAction basicIntention,
286                                    final HighlightDisplayKey key,
287                                    final XmlEntitiesInspection inspection,
288                                    final int type) {
289     boolean htmlTag = false;
290
291     if (tag instanceof HtmlTag) {
292       htmlTag = true;
293       if(isAdditionallyDeclared(inspection.getAdditionalEntries(type), name)) return;
294     }
295
296     final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getInspectionProfile();
297     final IntentionAction intentionAction = inspection.getIntentionAction(name, type);
298     if (htmlTag && profile.isToolEnabled(key, tag)) {
299       addElementsForTagWithManyQuickFixes(
300         tag,
301         localizedMessage,
302         isInjectedHtmlTagForWhichNoProblemsReporting((HtmlTag)tag) ?
303           HighlightInfoType.INFORMATION :
304           SeverityRegistrar.getInstance(tag.getProject()).getHighlightInfoTypeBySeverity(profile.getErrorLevel(key, tag).getSeverity()),
305         intentionAction,
306         basicIntention);
307     } else if (!htmlTag) {
308       addElementsForTag(
309         tag,
310         localizedMessage,
311         HighlightInfoType.ERROR,
312         basicIntention
313       );
314     }
315   }
316
317   private static boolean isAdditionallyDeclared(final String additional, String name) {
318     name = name.toLowerCase();
319     if (!additional.contains(name)) return false;
320
321     StringTokenizer tokenizer = new StringTokenizer(additional, ", ");
322     while (tokenizer.hasMoreTokens()) {
323       if (name.equals(tokenizer.nextToken())) {
324         return true;
325       }
326     }
327
328     return false;
329   }
330
331   private static HighlightInfoType getTagProblemInfoType(XmlTag tag) {
332     if (tag instanceof HtmlTag && XmlUtil.HTML_URI.equals(tag.getNamespace())) {
333       if (isInjectedHtmlTagForWhichNoProblemsReporting((HtmlTag)tag)) return HighlightInfoType.INFORMATION;
334       return HighlightInfoType.WARNING;
335     }
336     return HighlightInfoType.WRONG_REF;
337   }
338
339   private static boolean isInjectedHtmlTagForWhichNoProblemsReporting(HtmlTag tag) {
340     PsiElement context = tag.getContainingFile().getContext();
341     if (context != null && skipValidation(context)) return true;
342     return false;
343   }
344
345   private static boolean skipValidation(PsiElement context) {
346     return context.getUserData(DO_NOT_VALIDATE_KEY) != null;
347   }
348
349   public static void setSkipValidation(@NotNull PsiElement element) {
350     element.putUserData(DO_NOT_VALIDATE_KEY, "");
351   }
352
353   @Override public void visitXmlAttribute(XmlAttribute attribute) {}
354
355   private void checkAttribute(XmlAttribute attribute) {
356     XmlTag tag = attribute.getParent();
357
358     if (attribute.isNamespaceDeclaration()) {
359       checkReferences(attribute.getValueElement());
360       return;
361     }
362     final String namespace = attribute.getNamespace();
363
364     if (XmlUtil.XML_SCHEMA_INSTANCE_URI.equals(namespace)) {
365       checkReferences(attribute.getValueElement());
366       return;
367     }
368
369     XmlElementDescriptor elementDescriptor = tag.getDescriptor();
370     if (elementDescriptor == null ||
371         elementDescriptor instanceof AnyXmlElementDescriptor ||
372         ourDoJaxpTesting) {
373       return;
374     }
375
376     XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute);
377
378     final String name = attribute.getName();
379
380     if (attributeDescriptor == null) {
381       if (!XmlUtil.attributeFromTemplateFramework(name, tag)) {
382         final String localizedMessage = XmlErrorMessages.message("attribute.is.not.allowed.here", name);
383         final HighlightInfo highlightInfo = reportAttributeProblem(tag, name, attribute, localizedMessage);
384         if (highlightInfo != null) {
385           final XmlFile xmlFile = (XmlFile)tag.getContainingFile();
386           if (xmlFile != null) {
387             XmlExtension.getExtension(xmlFile).createAddAttributeFix(attribute, highlightInfo);
388           }
389         }
390       }
391     }
392     else {
393       checkDuplicateAttribute(tag, attribute);
394
395       if (tag instanceof HtmlTag &&
396           attribute.getValueElement() == null &&
397           !HtmlUtil.isSingleHtmlAttribute(name)
398          ) {
399         final String localizedMessage = XmlErrorMessages.message("empty.attribute.is.not.allowed", name);
400         reportAttributeProblem(tag, name, attribute, localizedMessage);
401       }
402
403       // we skip resolve of attribute references since there is separate check when taking attribute descriptors
404       PsiReference[] attrRefs = attribute.getReferences();
405       doCheckRefs(attribute, attrRefs, attribute.getNamespacePrefix().length() > 0 ? 2 : 1);
406     }
407   }
408
409   @Nullable
410   private HighlightInfo reportAttributeProblem(final XmlTag tag,
411                                                final String localName,
412                                                final XmlAttribute attribute,
413                                                final String localizedMessage) {
414
415     final RemoveAttributeIntentionFix removeAttributeIntention = new RemoveAttributeIntentionFix(localName,attribute);
416
417     if (!(tag instanceof HtmlTag)) {
418       final HighlightInfoType tagProblemInfoType = HighlightInfoType.WRONG_REF;
419
420       final ASTNode node = SourceTreeToPsiMap.psiElementToTree(attribute);
421       assert node != null;
422       final ASTNode child = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node);
423       assert child != null;
424       final HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
425         tagProblemInfoType, child,
426         localizedMessage
427       );
428       addToResults(highlightInfo);
429
430       QuickFixAction.registerQuickFixAction(highlightInfo, removeAttributeIntention);
431
432       return highlightInfo;
433     }
434
435     return null;
436   }
437
438   private void checkDuplicateAttribute(XmlTag tag, final XmlAttribute attribute) {
439     if (skipValidation(tag)) {
440       return;
441     }
442
443     final XmlAttribute[] attributes = tag.getAttributes();
444     final PsiFile containingFile = tag.getContainingFile();
445     final XmlExtension extension = containingFile instanceof XmlFile ?
446                                    XmlExtension.getExtension(containingFile) :
447                                    XmlExtension.DEFAULT_EXTENSION;
448     for (XmlAttribute tagAttribute : attributes) {
449       ProgressManager.checkCanceled();
450       if (attribute != tagAttribute && Comparing.strEqual(attribute.getName(), tagAttribute.getName())) {
451         final String localName = attribute.getLocalName();
452
453         if (extension.canBeDuplicated(tagAttribute)) continue; // multiple import attributes are allowed in jsp directive
454
455         HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
456           getTagProblemInfoType(tag),
457           XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(SourceTreeToPsiMap.psiElementToTree(attribute)),
458           XmlErrorMessages.message("duplicate.attribute", localName));
459         addToResults(highlightInfo);
460
461         IntentionAction intentionAction = new RemoveAttributeIntentionFix(localName, attribute);
462
463         QuickFixAction.registerQuickFixAction(highlightInfo, intentionAction);
464       }
465     }
466   }
467
468   @Override public void visitXmlDocument(final XmlDocument document) {
469     if (document.getLanguage() == DTDLanguage.INSTANCE) {
470       final PsiMetaData psiMetaData = document.getMetaData();
471       if (psiMetaData instanceof Validator) {
472         //noinspection unchecked
473         ((Validator<XmlDocument>)psiMetaData).validate(document, this);
474       }
475     }
476   }
477
478   @Override public void visitXmlTag(XmlTag tag) {
479   }
480
481   @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
482     final PsiElement parent = value.getParent();
483     if (!(parent instanceof XmlAttribute)) {
484       checkReferences(value);
485       return;
486     }
487
488     XmlAttribute attribute = (XmlAttribute)parent;
489
490     XmlTag tag = attribute.getParent();
491
492     XmlElementDescriptor elementDescriptor = tag.getDescriptor();
493     XmlAttributeDescriptor attributeDescriptor = elementDescriptor != null ? elementDescriptor.getAttributeDescriptor(attribute):null;
494
495     if (attributeDescriptor != null && value.getUserData(DO_NOT_VALIDATE_KEY) == null) {
496       String error = attributeDescriptor.validateValue(value, attribute.getValue());
497
498       if (error != null) {
499         addToResults(HighlightInfo.createHighlightInfo(
500             getTagProblemInfoType(tag),
501             value,
502             error));
503         return;
504       }
505     }
506
507     checkReferences(value);
508   }
509
510   private void checkReferences(PsiElement value) {
511     if (value == null) return;
512
513     doCheckRefs(value, value.getReferences());
514   }
515
516   private void doCheckRefs(final PsiElement value, final PsiReference[] references) {
517     doCheckRefs(value, references, 0);
518   }
519
520   private void doCheckRefs(final PsiElement value, final PsiReference[] references, int start) {
521     for (int i = start; i < references.length; ++i) {
522       PsiReference reference = references[i];
523       ProgressManager.checkCanceled();
524       if (reference == null) {
525         continue;
526       }
527       if (!reference.isSoft()) {
528         if(hasBadResolve(reference)) {
529           String description = getErrorDescription(reference);
530
531           final int startOffset = reference.getElement().getTextRange().getStartOffset();
532           final TextRange referenceRange = reference.getRangeInElement();
533
534           // logging for IDEADEV-29655
535           if (referenceRange.getStartOffset() > referenceRange.getEndOffset()) {
536             LOG.error("Reference range start offset > end offset:  " + reference +
537             ", start offset: " + referenceRange.getStartOffset() + ", end offset: " + referenceRange.getEndOffset());
538           }
539
540           HighlightInfoType type = getTagProblemInfoType(PsiTreeUtil.getParentOfType(value, XmlTag.class));
541           if (type.getSeverity(null).compareTo(HighlightInfoType.WARNING.getSeverity(null)) > 0 && value instanceof XmlAttributeValue) {
542             PsiElement parent = value.getParent();
543             if (parent instanceof XmlAttribute && ((XmlAttribute)parent).getName().toLowerCase().endsWith("stylename")) {
544               type = HighlightInfoType.WARNING;
545             }
546           }
547           HighlightInfo info = HighlightInfo.createHighlightInfo(
548             type,
549             startOffset + referenceRange.getStartOffset(),
550             startOffset + referenceRange.getEndOffset(),
551             description
552           );
553           addToResults(info);
554           if (reference instanceof QuickFixProvider) ((QuickFixProvider)reference).registerQuickfix(info, reference);
555           UnresolvedReferenceQuickFixProvider.registerReferenceFixes(reference, new QuickFixActionRegistrarImpl(info));
556         }
557       }
558     }
559   }
560
561   public static String getErrorDescription(final PsiReference reference) {
562     String message;
563     if (reference instanceof EmptyResolveMessageProvider) {
564       message = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern();
565     }
566     else {
567       //noinspection UnresolvedPropertyKey
568       message = PsiBundle.message("cannot.resolve.symbol");
569     }
570
571     String description;
572     try {
573       description = MessageFormat.format(message, reference.getCanonicalText());
574     } catch(IllegalArgumentException ex) {
575       // unresolvedMessage provided by third-party reference contains wrong format string (e.g. {}), tolerate it
576       description = message;
577       LOG.warn(XmlErrorMessages.message("plugin.reference.message.problem",reference.getClass().getName(),message));
578     }
579     return description;
580   }
581
582   public static boolean hasBadResolve(final PsiReference reference) {
583     if (reference instanceof PsiPolyVariantReference) {
584       return ((PsiPolyVariantReference)reference).multiResolve(false).length == 0;
585     }
586     return reference.resolve() == null;
587   }
588
589   @Override public void visitXmlDoctype(XmlDoctype xmlDoctype) {
590     if (skipValidation(xmlDoctype)) return;
591     checkReferences(xmlDoctype);
592   }
593
594   private void addToResults(final HighlightInfo info) {
595     if (myResult == null) myResult = new SmartList<HighlightInfo>();
596     myResult.add(info);
597   }
598
599   public static void setDoJaxpTesting(boolean doJaxpTesting) {
600     ourDoJaxpTesting = doJaxpTesting;
601   }
602
603   public void addMessage(PsiElement context, String message, int type) {
604     if (message != null && message.length() > 0) {
605       if (context instanceof XmlTag && XmlExtension.getExtension(context.getContainingFile()).shouldBeHighlightedAsTag((XmlTag)context)) {
606         addElementsForTag((XmlTag)context, message, type == ERROR ? HighlightInfoType.ERROR : type == WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO, null);
607       }
608       else {
609         addToResults(HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message));
610       }
611     }
612   }
613
614   public void addMessage(final PsiElement context, final String message, final ErrorType type, final IntentionAction... fixes) {
615     if (message != null && message.length() > 0) {
616       final PsiFile containingFile = context.getContainingFile();
617       final HighlightInfoType defaultInfoType = type == ErrorType.ERROR ? HighlightInfoType.ERROR : type == ErrorType.WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO;
618
619       if (context instanceof XmlTag && XmlExtension.getExtension(containingFile).shouldBeHighlightedAsTag((XmlTag)context)) {
620         addElementsForTagWithManyQuickFixes((XmlTag)context, message, defaultInfoType, fixes);
621       }
622       else {
623         final PsiElement contextOfFile = containingFile.getContext();
624         final HighlightInfo highlightInfo;
625
626         if (contextOfFile != null) {
627           final int offsetInRealDocument = PsiUtilBase.findInjectedElementOffsetInRealDocument(context);
628           highlightInfo = HighlightInfo.createHighlightInfo(defaultInfoType, context.getTextRange().shiftRight(offsetInRealDocument), message);
629         } else {
630           highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message);
631         }
632
633         if (fixes != null) {
634           for (final IntentionAction quickFixAction : fixes) {
635             if (quickFixAction == null) continue;
636             QuickFixAction.registerQuickFixAction(highlightInfo, quickFixAction);
637           }
638         }
639         addToResults(highlightInfo);
640       }
641     }
642   }
643
644   public static void visitJspElement(OuterLanguageElement text) {
645     PsiElement parent = text.getParent();
646
647     if (parent instanceof XmlText) {
648       parent = parent.getParent();
649     }
650
651     parent.putUserData(DO_NOT_VALIDATE_KEY, "");
652   }
653
654   public boolean suitableForFile(final PsiFile file) {
655     return file instanceof XmlFile;
656   }
657
658   public void visit(final PsiElement element, final HighlightInfoHolder holder) {
659     element.accept(this);
660
661     List<HighlightInfo> result = myResult;
662     holder.addAll(result);
663     myResult = null;
664   }
665
666   public boolean analyze(Runnable action, final boolean updateWholeFile, final PsiFile file) {
667     try {
668       action.run();
669     }
670     finally {
671       myResult = null;
672     }
673     return true;
674   }
675
676   public HighlightVisitor clone() {
677     return new XmlHighlightVisitor();
678   }
679
680   public int order() {
681     return 1;
682   }
683
684   public static String getUnquotedValue(XmlAttributeValue value, XmlTag tag) {
685     String unquotedValue = StringUtil.stripQuotesAroundValue(value.getText());
686
687     if (tag instanceof HtmlTag) {
688       unquotedValue = unquotedValue.toLowerCase();
689     }
690
691     return unquotedValue;
692   }
693 }