IDEA-337197 Throwable: the expensive method should not be called inside the highlighting pass in JSP

check unresolved references in a dedicated XmlUnresolvedReferenceInspection

GitOrigin-RevId: d54b7a0b933c33c8656726c84a9611c97c1268ff
This commit is contained in:
Dmitry Avdeev
2023-12-13 16:44:59 +01:00
committed by intellij-monorepo-bot
parent 63045881e6
commit 6ba55541ee
29 changed files with 246 additions and 126 deletions

View File

@@ -0,0 +1,5 @@
<html>
<body>
Reports an unresolved references in XML.
</body>
</html>

View File

@@ -33,6 +33,6 @@ public class HtmlUnknownAnchorTargetInspection extends XmlPathReferenceInspectio
@Override
protected boolean needToCheckRef(PsiReference reference) {
return reference instanceof AnchorReference && HtmlUnknownTargetInspection.notRemoteBase(reference);
return super.needToCheckRef(reference) && reference instanceof AnchorReference && HtmlUnknownTargetInspection.notRemoteBase(reference);
}
}

View File

@@ -36,7 +36,7 @@ public class HtmlUnknownTargetInspection extends XmlPathReferenceInspection {
@Override
protected boolean needToCheckRef(PsiReference reference) {
return !(reference instanceof AnchorReference) && notRemoteBase(reference);
return super.needToCheckRef(reference) && !(reference instanceof AnchorReference) && notRemoteBase(reference);
}
static boolean notRemoteBase(PsiReference reference) {

View File

@@ -27,8 +27,8 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.html.HtmlTag;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceOwner;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileReference;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.*;
import com.intellij.psi.impl.source.xml.TagNameReference;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.psi.tree.IElementType;
@@ -440,7 +440,7 @@ public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightV
for (int i = start; i < references.length; ++i) {
PsiReference reference = references[i];
ProgressManager.checkCanceled();
if (isUrlReference(reference)) continue;
if (!shouldCheckResolve(reference)) continue;
if (!hasBadResolve(reference, false)) {
continue;
}
@@ -484,6 +484,14 @@ public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightV
}
}
static boolean shouldCheckResolve(PsiReference reference) {
return reference instanceof TypeOrElementOrAttributeReference ||
reference instanceof DependentNSReference ||
reference instanceof URLReference ||
reference instanceof TagNameReference ||
reference instanceof PsiReferenceWithUnresolvedQuickFixes;
}
static boolean isUrlReference(PsiReference reference) {
return reference instanceof FileReferenceOwner || reference instanceof AnchorReference || reference instanceof PsiFileReference;
}

View File

@@ -1,111 +1,15 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiReference;
import com.intellij.psi.XmlElementVisitor;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlDoctype;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @author Dmitry Avdeev
*/
public class XmlPathReferenceInspection extends XmlSuppressableInspectionTool {
@NotNull
public class XmlPathReferenceInspection extends XmlReferenceInspectionBase {
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new XmlElementVisitor() {
@Override
public void visitXmlAttributeValue(@NotNull XmlAttributeValue value) {
checkRefs(value, holder);
}
@Override
public void visitXmlDoctype(@NotNull XmlDoctype xmlDoctype) {
checkRefs(xmlDoctype, holder);
}
@Override
public void visitXmlTag(@NotNull XmlTag tag) {
checkRefs(tag, holder);
}
};
}
private void checkRefs(@NotNull XmlElement element, @NotNull ProblemsHolder holder) {
PsiReference[] references = element.getReferences();
if (references.length == 0) {
return;
}
if (XmlHighlightVisitor.isInjectedWithoutValidation(element)) {
return;
}
boolean isHtml = HtmlUtil.isHtmlTagContainingFile(element);
if (isHtml ^ isForHtml()) {
return;
}
if (!isHtml && XmlHighlightVisitor.skipValidation(element)) {
return;
}
Collection<PsiReference> unresolved = getUnresolvedReferencesToAnnotate(references);
for (PsiReference reference : unresolved) {
holder.registerProblem(reference, ProblemsHolder.unresolvedReferenceMessage(reference),
isHtml ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
@NotNull
private Collection<PsiReference> getUnresolvedReferencesToAnnotate(PsiReference[] references) {
Map<TextRange, PsiReference> unresolvedReferences = new HashMap<>();
for (PsiReference reference : references) {
if (!XmlHighlightVisitor.isUrlReference(reference) || !needToCheckRef(reference)) {
continue;
}
TextRange elementRange = reference.getElement().getTextRange();
if (elementRange == null || elementRange.isEmpty()) {
continue;
}
TextRange rangeInElement = reference.getRangeInElement();
if (unresolvedReferences.containsKey(rangeInElement) && unresolvedReferences.get(rangeInElement) == null) {
continue;
}
if (XmlHighlightVisitor.hasBadResolve(reference, true)) {
if (!reference.isSoft()) {
unresolvedReferences.putIfAbsent(rangeInElement, reference);
}
}
else {
// the specific range has at least one resolved reference
// no need to check any other references from that range
unresolvedReferences.put(rangeInElement, null);
}
}
return ContainerUtil.skipNulls(unresolvedReferences.values());
}
protected boolean needToCheckRef(PsiReference reference) {
return true;
}
protected boolean isForHtml() {
return false;
return XmlHighlightVisitor.isUrlReference(reference);
}
}

View File

@@ -0,0 +1,134 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiReference;
import com.intellij.psi.XmlElementVisitor;
import com.intellij.psi.xml.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public abstract class XmlReferenceInspectionBase extends XmlSuppressableInspectionTool {
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new XmlElementVisitor() {
@Override
public void visitXmlAttributeValue(@NotNull XmlAttributeValue value) {
checkRefs(value, holder, 0);
}
@Override
public void visitXmlAttribute(@NotNull XmlAttribute attribute) {
int startIndex = !attribute.getNamespacePrefix().isEmpty() ? 2 : 1;
checkRefs(attribute, holder, startIndex);
}
@Override
public void visitXmlDoctype(@NotNull XmlDoctype xmlDoctype) {
checkRefs(xmlDoctype, holder, 0);
}
@Override
public void visitXmlTag(@NotNull XmlTag tag) {
checkRefs(tag, holder, 0);
}
@Override
public void visitXmlProcessingInstruction(@NotNull XmlProcessingInstruction processingInstruction) {
checkRefs(processingInstruction, holder, 0);
}
};
}
private void checkRefs(@NotNull XmlElement element, @NotNull ProblemsHolder holder, int startIndex) {
PsiReference[] references = element.getReferences();
if (references.length <= startIndex) {
return;
}
if (XmlHighlightVisitor.isInjectedWithoutValidation(element)) {
return;
}
boolean isHtml = HtmlUtil.isHtmlTagContainingFile(element);
if (!checkHtml(element, isHtml)) {
return;
}
Collection<PsiReference> unresolved = getUnresolvedReferencesToAnnotate(Arrays.copyOfRange(references, startIndex, references.length));
for (PsiReference reference : unresolved) {
holder.registerProblem(reference, ProblemsHolder.unresolvedReferenceMessage(reference),
isHtml ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
protected boolean checkHtml(@NotNull XmlElement element, boolean isHtml) {
if (isHtml ^ isForHtml()) {
return false;
}
if (!isHtml && XmlHighlightVisitor.skipValidation(element)) {
return false;
}
return true;
}
@NotNull
private Collection<PsiReference> getUnresolvedReferencesToAnnotate(PsiReference[] references) {
Map<TextRange, PsiReference> unresolvedReferences = new HashMap<>();
for (PsiReference reference : references) {
if (!needToCheckRef(reference)) {
continue;
}
if (!checkRanges()) {
if (XmlHighlightVisitor.hasBadResolve(reference, false)) {
unresolvedReferences.put(reference.getRangeInElement(), reference);
}
continue;
}
TextRange elementRange = reference.getElement().getTextRange();
if (elementRange == null || elementRange.isEmpty()) {
continue;
}
TextRange rangeInElement = reference.getRangeInElement();
if (unresolvedReferences.containsKey(rangeInElement) && unresolvedReferences.get(rangeInElement) == null) {
continue;
}
if (XmlHighlightVisitor.hasBadResolve(reference, true)) {
if (!reference.isSoft()) {
unresolvedReferences.putIfAbsent(rangeInElement, reference);
}
}
else {
// the specific range has at least one resolved reference
// no need to check any other references from that range
unresolvedReferences.put(rangeInElement, null);
}
}
return ContainerUtil.skipNulls(unresolvedReferences.values());
}
protected boolean checkRanges() {
return true;
}
protected abstract boolean needToCheckRef(PsiReference reference);
protected boolean isForHtml() {
return false;
}
}

View File

@@ -0,0 +1,21 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider
import com.intellij.psi.PsiReference
import com.intellij.psi.xml.XmlElement
class XmlUnresolvedReferenceInspection: XmlReferenceInspectionBase() {
override fun needToCheckRef(reference: PsiReference?) = !XmlHighlightVisitor.shouldCheckResolve(reference) &&
!XmlHighlightVisitor.isUrlReference(reference)
override fun checkRanges() = false
override fun checkHtml(element: XmlElement, isHtml: Boolean) = true
}
/**
* Marker interface for references associated with [UnresolvedReferenceQuickFixProvider],
* which should be checked by [XmlHighlightVisitor]
*/
interface PsiReferenceWithUnresolvedQuickFixes: PsiReference