mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
[Java] Make JavaSpellcheckingStrategy dumb aware
IDEA-357681 GitOrigin-RevId: 1fead4d1fdb98c99d8b68d48813f483deaa6b35c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3eb5909fbd
commit
c02d32ef74
@@ -2,8 +2,10 @@
|
||||
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil;
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -16,7 +18,9 @@ public abstract class SuppressManager implements BatchSuppressManager, Inspectio
|
||||
|
||||
public static boolean isSuppressedInspectionName(PsiLiteralExpression expression) {
|
||||
PsiAnnotation annotation = PsiTreeUtil.getParentOfType(expression, PsiAnnotation.class, true, PsiCodeBlock.class, PsiField.class, PsiCall.class);
|
||||
return annotation != null && SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName());
|
||||
return annotation != null &&
|
||||
(!DumbService.isDumb(expression.getProject()) && SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName()) ||
|
||||
DumbAwareAnnotationUtil.isAnnotationMatchesFqn(annotation, SUPPRESS_INSPECTIONS_ANNOTATION_NAME));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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.codeInspection.util;
|
||||
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.siyeh.ig.callMatcher.CallHandler;
|
||||
@@ -30,8 +32,10 @@ public final class ChronoUtil {
|
||||
public static final String CHRONO_FIELD = "java.time.temporal.ChronoField";
|
||||
public static final String CHRONO_UNIT = "java.time.temporal.ChronoUnit";
|
||||
|
||||
private static final String JAVA_TEXT_SIMPLE_DATE_FORMAT = "java.text.SimpleDateFormat";
|
||||
|
||||
private static final CallMatcher FORMAT_PATTERN_METHOD_MATCHER = CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall("java.text.SimpleDateFormat", "applyPattern", "applyLocalizedPattern").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
|
||||
CallMatcher.instanceCall(JAVA_TEXT_SIMPLE_DATE_FORMAT, "applyPattern", "applyLocalizedPattern").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
|
||||
CallMatcher.staticCall("java.time.format.DateTimeFormatter", "ofPattern"),
|
||||
CallMatcher.instanceCall("java.time.format.DateTimeFormatterBuilder", "appendPattern").parameterTypes(CommonClassNames.JAVA_LANG_STRING)
|
||||
);
|
||||
@@ -84,7 +88,7 @@ public final class ChronoUtil {
|
||||
|
||||
private static final Map<String, BiPredicate<PsiNewExpression, PsiElement>> SKIP_ARGUMENT_CONSTRUCTOR_HANDLER =
|
||||
Map.ofEntries(
|
||||
Map.entry("java.text.SimpleDateFormat", (expression, psiElement) -> argumentNumber(0, expression).test(psiElement))
|
||||
Map.entry(JAVA_TEXT_SIMPLE_DATE_FORMAT, (expression, psiElement) -> argumentNumber(0, expression).test(psiElement))
|
||||
);
|
||||
|
||||
private interface ArgumentMatcher extends Predicate<PsiElement> { }
|
||||
@@ -143,13 +147,14 @@ public final class ChronoUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@code literalExpression} is used as pattern for date formatters (SimpleDateFormat, DateTimeFormatter)
|
||||
* @return true if {@code literalExpression} is used as pattern for date formatters (SimpleDateFormat, DateTimeFormatter)
|
||||
* This method can work in dumb mode
|
||||
*/
|
||||
public static boolean isPatternForDateFormat(@NotNull PsiLiteralExpression literalExpression) {
|
||||
PsiType type = literalExpression.getType();
|
||||
if (type == null || !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||
if (!hasStringType(literalExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PsiElement element = ExpressionUtils.getPassThroughParent(literalExpression);
|
||||
if (element == null) {
|
||||
return false;
|
||||
@@ -163,17 +168,52 @@ public final class ChronoUtil {
|
||||
}
|
||||
|
||||
if (psiCall instanceof PsiMethodCallExpression callExpression) {
|
||||
return isCallExpressionContainsDateFormatMethods(literalExpression, callExpression);
|
||||
}
|
||||
if (psiCall instanceof PsiNewExpression newExpression) {
|
||||
return isConstructorOfSimpleDateFormat(literalExpression, newExpression);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasStringType(PsiLiteralExpression literalExpression) {
|
||||
if (!DumbService.isDumb(literalExpression.getProject())) {
|
||||
PsiType type = literalExpression.getType();
|
||||
if (type == null || !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String text = literalExpression.getText();
|
||||
return text.startsWith("\"") && text.endsWith("\"");
|
||||
}
|
||||
|
||||
private static boolean isCallExpressionContainsDateFormatMethods(@NotNull PsiLiteralExpression literalExpression, PsiMethodCallExpression callExpression) {
|
||||
if (!DumbService.isDumb(literalExpression.getProject())) {
|
||||
ArgumentMatcher matcher = SKIP_ARGUMENT_METHOD_HANDLER.mapFirst(callExpression);
|
||||
if (matcher == null) {
|
||||
return false;
|
||||
}
|
||||
return matcher.test(literalExpression);
|
||||
}
|
||||
if (psiCall instanceof PsiNewExpression newExpression) {
|
||||
PsiJavaCodeReferenceElement reference = newExpression.getClassReference();
|
||||
if (reference == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PsiReferenceExpression referenceExpression = callExpression.getMethodExpression();
|
||||
String referenceName = referenceExpression.getReferenceName();
|
||||
|
||||
return referenceName != null &&
|
||||
(referenceName.equals("applyPattern") ||
|
||||
referenceName.equals("applyLocalizedPattern") ||
|
||||
referenceName.equals("ofPattern") ||
|
||||
referenceName.equals("appendPattern"));
|
||||
}
|
||||
|
||||
private static boolean isConstructorOfSimpleDateFormat(@NotNull PsiLiteralExpression literalExpression, PsiNewExpression newExpression) {
|
||||
PsiJavaCodeReferenceElement reference = newExpression.getClassReference();
|
||||
if (reference == null) {
|
||||
return false;
|
||||
}
|
||||
if (!DumbService.isDumb(literalExpression.getProject())) {
|
||||
BiPredicate<PsiNewExpression, PsiElement> predicate =
|
||||
SKIP_ARGUMENT_CONSTRUCTOR_HANDLER.get(reference.getQualifiedName());
|
||||
if (predicate == null) {
|
||||
@@ -181,7 +221,15 @@ public final class ChronoUtil {
|
||||
}
|
||||
return predicate.test(newExpression, literalExpression);
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
String text = DumbAwareAnnotationUtil.getFormattedReferenceFqn(reference.getText());
|
||||
if (reference.isQualified() && text.equals(JAVA_TEXT_SIMPLE_DATE_FORMAT)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = reference.getReferenceName();
|
||||
return name != null && name.equals("SimpleDateFormat");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -3,14 +3,14 @@ package com.intellij.psi.formatter.java
|
||||
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.openapi.roots.LanguageLevelProjectExtension
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.PsiAnnotation
|
||||
import com.intellij.psi.PsiKeyword
|
||||
import com.intellij.psi.PsiModifierListOwner
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.formatter.FormatterUtil
|
||||
import com.intellij.psi.impl.source.tree.JavaElementType
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
|
||||
internal object JavaFormatterAnnotationUtil {
|
||||
@@ -36,16 +36,7 @@ internal object JavaFormatterAnnotationUtil {
|
||||
val next = PsiTreeUtil.skipSiblingsForward(node, PsiWhiteSpace::class.java, PsiAnnotation::class.java)
|
||||
if (next is PsiKeyword) return false
|
||||
|
||||
val psiReference: PsiJavaCodeReferenceElement = node.nameReferenceElement ?: return false
|
||||
if (psiReference.isQualified) {
|
||||
return KNOWN_TYPE_ANNOTATIONS.contains(getCanonicalTextOfTheReference(psiReference))
|
||||
}
|
||||
else {
|
||||
val referenceName = psiReference.referenceNameElement ?: return false
|
||||
val file = psiReference.containingFile as? PsiJavaFile ?: return false
|
||||
val referenceNameText = referenceName.text
|
||||
return getImportedTypeAnnotations(file).contains(referenceNameText)
|
||||
}
|
||||
return KNOWN_TYPE_ANNOTATIONS.any { DumbAwareAnnotationUtil.isAnnotationMatchesFqn(node, it) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,27 +78,4 @@ internal object JavaFormatterAnnotationUtil {
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getImportedTypeAnnotations(file: PsiJavaFile): Set<String> = CachedValuesManager.getCachedValue(file) {
|
||||
val importList = file.importList
|
||||
?: return@getCachedValue CachedValueProvider.Result(emptySet(), PsiModificationTracker.MODIFICATION_COUNT)
|
||||
val filteredAnnotations = KNOWN_TYPE_ANNOTATIONS.filter { isAnnotationInImportList(it, importList) }
|
||||
.mapNotNull { fqn -> fqn.split(".").lastOrNull() }
|
||||
.toSet()
|
||||
CachedValueProvider.Result.create(filteredAnnotations, PsiModificationTracker.MODIFICATION_COUNT)
|
||||
}
|
||||
|
||||
private fun isAnnotationInImportList(annotationFqn: String, importList: PsiImportList): Boolean {
|
||||
val packageName = StringUtil.getPackageName(annotationFqn)
|
||||
return importList.importStatements.any { statement: PsiImportStatement ->
|
||||
val referenceElement = statement.importReference ?: return@any false
|
||||
val referenceElementText = getCanonicalTextOfTheReference(referenceElement)
|
||||
referenceElementText == annotationFqn || statement.isOnDemand && referenceElementText.startsWith(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCanonicalTextOfTheReference(importReference: PsiJavaCodeReferenceElement): String = importReference.text.let { referenceText ->
|
||||
referenceText
|
||||
.split(".")
|
||||
.joinToString(separator = ".") { pathPart -> pathPart.trim() }
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.intellij.spellchecker;
|
||||
|
||||
import com.intellij.codeInspection.SuppressManager;
|
||||
import com.intellij.codeInspection.util.ChronoUtil;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
@@ -15,7 +16,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* @author shkate@jetbrains.com
|
||||
*/
|
||||
public final class JavaSpellcheckingStrategy extends SpellcheckingStrategy {
|
||||
public final class JavaSpellcheckingStrategy extends SpellcheckingStrategy implements DumbAware {
|
||||
private final MethodNameTokenizerJava myMethodNameTokenizer = new MethodNameTokenizerJava();
|
||||
private final DocCommentTokenizer myDocCommentTokenizer = new DocCommentTokenizer();
|
||||
private final LiteralExpressionTokenizer myLiteralExpressionTokenizer = new LiteralExpressionTokenizer();
|
||||
@@ -30,8 +31,7 @@ public final class JavaSpellcheckingStrategy extends SpellcheckingStrategy {
|
||||
return myDocCommentTokenizer;
|
||||
}
|
||||
if (element instanceof PsiLiteralExpression literalExpression) {
|
||||
if (SuppressManager.isSuppressedInspectionName(literalExpression) ||
|
||||
ChronoUtil.isPatternForDateFormat(literalExpression)) {
|
||||
if (ChronoUtil.isPatternForDateFormat(literalExpression) || SuppressManager.isSuppressedInspectionName(literalExpression)) {
|
||||
return EMPTY_TOKENIZER;
|
||||
}
|
||||
return myLiteralExpressionTokenizer;
|
||||
|
||||
@@ -4,8 +4,10 @@ package com.intellij.spellchecker;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil;
|
||||
import com.intellij.psi.util.PsiLiteralUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
@@ -14,6 +16,7 @@ import com.intellij.spellchecker.tokenizer.EscapeSequenceTokenizer;
|
||||
import com.intellij.spellchecker.tokenizer.TokenConsumer;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -24,7 +27,7 @@ public class LiteralExpressionTokenizer extends EscapeSequenceTokenizer<PsiLiter
|
||||
@Override
|
||||
public void tokenize(@NotNull PsiLiteralExpression expression, @NotNull TokenConsumer consumer) {
|
||||
String text;
|
||||
if (!ExpressionUtils.hasStringType(expression)) {
|
||||
if (!hasStringType(expression)) {
|
||||
text = null;
|
||||
}
|
||||
else if (expression.isTextBlock()) {
|
||||
@@ -42,16 +45,11 @@ public class LiteralExpressionTokenizer extends EscapeSequenceTokenizer<PsiLiter
|
||||
|
||||
if (InjectedLanguageManager.getInstance(expression.getProject()).getInjectedPsiFiles(expression) != null) return;
|
||||
|
||||
final PsiModifierListOwner listOwner = PsiTreeUtil.getParentOfType(skipParenthesizedExprUp(expression),
|
||||
final PsiModifierListOwner listOwner = PsiTreeUtil.getParentOfType(skipParenthesizedExprUp(expression),
|
||||
PsiModifierListOwner.class);
|
||||
if (listOwner != null && AnnotationUtil.isAnnotated(listOwner, AnnotationUtil.NON_NLS, AnnotationUtil.CHECK_EXTERNAL)) {
|
||||
PsiElement targetElement = skipParenthesizedExprUp(getCompleteStringValueExpression(expression));
|
||||
if (listOwner instanceof PsiMethod) {
|
||||
if (Arrays.stream(PsiUtil.findReturnStatements(((PsiMethod)listOwner))).map(s -> s.getReturnValue()).anyMatch(e -> e == targetElement)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (listOwner instanceof PsiVariable && ((PsiVariable)listOwner).getInitializer() == targetElement) {
|
||||
if (listOwner != null && !shouldProcessLiteralExpression(expression, listOwner)) {
|
||||
if (!DumbService.isDumb(listOwner.getProject()) && AnnotationUtil.isAnnotated(listOwner, AnnotationUtil.NON_NLS, AnnotationUtil.CHECK_EXTERNAL) ||
|
||||
DumbAwareAnnotationUtil.hasAnnotation(listOwner, AnnotationUtil.NON_NLS)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +62,19 @@ public class LiteralExpressionTokenizer extends EscapeSequenceTokenizer<PsiLiter
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldProcessLiteralExpression(@NotNull PsiLiteralExpression expression, PsiModifierListOwner listOwner) {
|
||||
PsiElement targetElement = skipParenthesizedExprUp(getCompleteStringValueExpression(expression));
|
||||
if (listOwner instanceof PsiMethod) {
|
||||
if (Arrays.stream(PsiUtil.findReturnStatements(((PsiMethod)listOwner))).map(s -> s.getReturnValue())
|
||||
.anyMatch(e -> e == targetElement)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (listOwner instanceof PsiVariable psiVariable && psiVariable.getInitializer() == targetElement) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PsiElement skipParenthesizedExprUp(PsiElement expression) {
|
||||
while (expression.getParent() instanceof PsiParenthesizedExpression) {
|
||||
expression = expression.getParent();
|
||||
@@ -71,6 +82,22 @@ public class LiteralExpressionTokenizer extends EscapeSequenceTokenizer<PsiLiter
|
||||
return expression;
|
||||
}
|
||||
|
||||
private static boolean hasStringType(@Nullable PsiLiteralExpression expression) {
|
||||
if (expression == null) return false;
|
||||
if (!DumbService.isDumb(expression.getProject())) return ExpressionUtils.hasStringType(expression);
|
||||
String text = expression.getText();
|
||||
return text.startsWith("\"") && text.endsWith("\"");
|
||||
}
|
||||
|
||||
private static PsiElement getCompleteStringValueExpression(@NotNull PsiLiteralExpression expression) {
|
||||
PsiElement parent = expression.getParent();
|
||||
final PsiElement skipParenthesizedExprUpElement = skipParenthesizedExprUp(parent);
|
||||
if (!(skipParenthesizedExprUpElement instanceof PsiPolyadicExpression polyadicExpression)) return expression;
|
||||
if (!JavaTokenType.PLUS.equals(polyadicExpression.getOperationTokenType())) return expression;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
public static void processTextWithEscapeSequences(PsiLiteralExpression element, String text, TokenConsumer consumer) {
|
||||
StringBuilder unescapedText = new StringBuilder(text.length());
|
||||
int[] offsets = new int[text.length() + 1];
|
||||
@@ -79,8 +106,4 @@ public class LiteralExpressionTokenizer extends EscapeSequenceTokenizer<PsiLiter
|
||||
int startOffset = (element != null && element.isTextBlock()) ? 3 : 1;
|
||||
processTextWithOffsets(element, consumer, unescapedText, offsets, startOffset);
|
||||
}
|
||||
|
||||
public static PsiElement getCompleteStringValueExpression(PsiExpression expression) {
|
||||
return ExpressionUtils.isStringConcatenationOperand(expression) ? expression.getParent() : expression;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.intellij.spellchecker;
|
||||
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil;
|
||||
import com.intellij.spellchecker.tokenizer.TokenConsumer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -26,9 +28,11 @@ public class MethodNameTokenizerJava extends NamedElementTokenizer<PsiMethod> {
|
||||
|
||||
@Override
|
||||
public void tokenize(@NotNull PsiMethod element, @NotNull TokenConsumer consumer) {
|
||||
if (element.isConstructor() || element.findDeepestSuperMethods().length > 0) return;
|
||||
|
||||
if (element.isConstructor() ||
|
||||
(!DumbService.isDumb(element.getProject()) && element.findDeepestSuperMethods().length > 0) ||
|
||||
DumbAwareAnnotationUtil.hasAnnotation(element, CommonClassNames.JAVA_LANG_OVERRIDE)) {
|
||||
return;
|
||||
}
|
||||
super.tokenize(element, consumer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.spellchecker;
|
||||
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.spellchecker.tokenizer.TokenConsumer;
|
||||
@@ -8,6 +9,8 @@ import com.intellij.spellchecker.tokenizer.Tokenizer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@@ -28,10 +31,22 @@ public class NamedElementTokenizer<T extends PsiNamedElement> extends Tokenizer<
|
||||
return;
|
||||
}
|
||||
|
||||
if (element instanceof PsiClass) {
|
||||
for (PsiClassType superType : ((PsiClass)element).getSuperTypes()) {
|
||||
if (nameSeemsDerived(identifier, getClassName(superType))) {
|
||||
return;
|
||||
if (element instanceof PsiClass psiClass) {
|
||||
if (DumbService.isDumb(element.getProject())) {
|
||||
PsiReferenceList extendList = psiClass.getExtendsList();
|
||||
PsiReferenceList implementList = psiClass.getImplementsList();
|
||||
List<PsiJavaCodeReferenceElement> referenceElements = new ArrayList<>();
|
||||
if (extendList != null) referenceElements.addAll(List.of(extendList.getReferenceElements()));
|
||||
if (implementList != null) referenceElements.addAll(List.of(implementList.getReferenceElements()));
|
||||
for (PsiJavaCodeReferenceElement referenceElement : referenceElements) {
|
||||
PsiElement nameElement = referenceElement.getReferenceNameElement();
|
||||
if (nameElement != null && nameSeemsDerived(identifier, nameElement.getText())) return;
|
||||
}
|
||||
} else {
|
||||
for (PsiClassType superType : psiClass.getSuperTypes()) {
|
||||
if (nameSeemsDerived(identifier, getClassName(superType))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,10 +58,20 @@ public class NamedElementTokenizer<T extends PsiNamedElement> extends Tokenizer<
|
||||
return source != null && name.toLowerCase(Locale.ROOT).endsWith(source.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
private static @Nullable String getTypeText(PsiElement element) {
|
||||
private static @Nullable String getTypeText(@NotNull PsiElement element) {
|
||||
PsiTypeElement typeElement = PsiTreeUtil.getChildOfType(element, PsiTypeElement.class);
|
||||
PsiType type = typeElement != null ? typeElement.getType() : element instanceof PsiVariable ? ((PsiVariable)element).getType() : null;
|
||||
return getClassName(type);
|
||||
if (DumbService.isDumb(element.getProject())) {
|
||||
if (typeElement == null || typeElement.isInferredType()) return null;
|
||||
PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement();
|
||||
if (referenceElement == null) return null;
|
||||
PsiElement identifier = referenceElement.getReferenceNameElement();
|
||||
if (identifier == null) return null;
|
||||
return identifier.getText();
|
||||
}
|
||||
else {
|
||||
PsiType type = typeElement != null ? typeElement.getType() : element instanceof PsiVariable ? ((PsiVariable)element).getType() : null;
|
||||
return getClassName(type);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getClassName(PsiType type) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight
|
||||
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil.KNOWN_ANNOTATIONS
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil.hasAnnotation
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
|
||||
/**
|
||||
* Utility which helps to detect annotation in `Dumb mode`.
|
||||
*/
|
||||
object DumbAwareAnnotationUtil {
|
||||
private const val JAVA_LANG_PACKAGE = "java.lang"
|
||||
|
||||
private val KNOWN_ANNOTATIONS = setOf(
|
||||
AnnotationUtil.NOT_NULL,
|
||||
AnnotationUtil.NULLABLE,
|
||||
AnnotationUtil.NON_NLS
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks if the given `PsiModifierListOwner` has an annotation with the specified fully qualified name. If the annotation has not
|
||||
* fqn (which is more likely), it will find it if and only if it is present in [KNOWN_ANNOTATIONS].
|
||||
* @param owner The `PsiModifierListOwner` instance to check for annotations.
|
||||
* @param fqn The fully qualified name of the annotation to look for.
|
||||
* @return `true` if an annotation with the given fully qualified name is present, `false` otherwise.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun hasAnnotation(owner: PsiModifierListOwner, fqn: String): Boolean {
|
||||
for (annotation in owner.annotations) {
|
||||
if (isAnnotationMatchesFqn(annotation, fqn)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given annotation matches with the specified fully qualified name.
|
||||
* @see hasAnnotation
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isAnnotationMatchesFqn(annotation: PsiAnnotation, annotationFqn: String): Boolean {
|
||||
val file = annotation.containingFile as? PsiJavaFile ?: return false
|
||||
val referenceElement = annotation.nameReferenceElement ?: return false
|
||||
if (referenceElement.isQualified && getCanonicalTextOfTheReference(referenceElement) == annotationFqn) return true
|
||||
val nameElement = referenceElement.referenceNameElement ?: return false
|
||||
val importInfo = getAnnotationImportInfo(annotationFqn)
|
||||
if (importInfo.packageName == JAVA_LANG_PACKAGE && importInfo.className == nameElement.text) return true
|
||||
|
||||
return getImportedKnownAnnotations(file).contains(nameElement.text) && importInfo.className == nameElement.text
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given fully qualified name (FQN) by trimming whitespace around each segment.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getFormattedReferenceFqn(referenceText: @NlsSafe String) = referenceText.split(".").joinToString(separator = ".") { pathPart -> pathPart.trim() }
|
||||
|
||||
private fun getImportedKnownAnnotations(file: PsiJavaFile): Set<String> = CachedValuesManager.getCachedValue(file) {
|
||||
val importList = file.importList
|
||||
?: return@getCachedValue CachedValueProvider.Result(emptySet(), PsiModificationTracker.MODIFICATION_COUNT)
|
||||
val filteredAnnotations = KNOWN_ANNOTATIONS.filter { isAnnotationInImportList(it, importList) }
|
||||
.mapNotNull { fqn -> fqn.split(".").lastOrNull() }
|
||||
.toSet()
|
||||
CachedValueProvider.Result.create(filteredAnnotations, PsiModificationTracker.MODIFICATION_COUNT)
|
||||
}
|
||||
|
||||
private fun isAnnotationInImportList(annotationFqn: String, importList: PsiImportList): Boolean {
|
||||
val packageName = StringUtil.getPackageName(annotationFqn)
|
||||
return importList.importStatements.any { statement: PsiImportStatement ->
|
||||
val referenceElement = statement.importReference ?: return@any false
|
||||
val referenceElementText = getCanonicalTextOfTheReference(referenceElement)
|
||||
referenceElementText == annotationFqn || statement.isOnDemand && referenceElementText.startsWith(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAnnotationImportInfo(annotationFqn: String): AnnotationImportInfo {
|
||||
val packageName = StringUtil.getPackageName(annotationFqn)
|
||||
val className = StringUtil.getShortName(annotationFqn)
|
||||
return AnnotationImportInfo(packageName, className)
|
||||
}
|
||||
|
||||
private fun getCanonicalTextOfTheReference(reference: PsiJavaCodeReferenceElement): String = getFormattedReferenceFqn(reference.text)
|
||||
|
||||
private data class AnnotationImportInfo(val packageName: String, val className: String)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class A {
|
||||
@NotNull @Nullable String f<caret>() {};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
@org.jetbrains.annotations.NotNull String s<caret>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
class A {
|
||||
@NotNull String s<caret>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
class A {
|
||||
@NonNls String s<caret>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class A {
|
||||
@NotNull String s<caret>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class A {
|
||||
@Nullable String s<caret>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class A {
|
||||
@Nullable<caret> String s;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import org.jetbrains.unknown.annotatios.pack.Nullable;
|
||||
|
||||
class A {
|
||||
@Nullable String s<caret>;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.util
|
||||
|
||||
import com.intellij.JavaTestUtil
|
||||
import com.intellij.codeInsight.AnnotationUtil
|
||||
import com.intellij.codeInsight.DumbAwareAnnotationUtil
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.psi.PsiModifierListOwner
|
||||
import com.intellij.testFramework.DumbModeTestUtils
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
|
||||
|
||||
class DumbAwareAnnotationUtilTest : JavaCodeInsightFixtureTestCase() {
|
||||
override fun getTestDataPath() = JavaTestUtil.getJavaTestDataPath() + "/util/dumbAwareAnnotation/"
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
ModuleRootModificationUtil.updateModel(myFixture.getModule(), DefaultLightProjectDescriptor::addJetBrainsAnnotations)
|
||||
}
|
||||
|
||||
fun testNonNls() = doFindAnnotationTest(AnnotationUtil.NON_NLS)
|
||||
|
||||
fun testNotNull() = doFindAnnotationTest(AnnotationUtil.NOT_NULL)
|
||||
|
||||
fun testNullable() = doFindAnnotationTest(AnnotationUtil.NULLABLE)
|
||||
|
||||
fun testAnnotationWithFqn() = doFindAnnotationTest(AnnotationUtil.NOT_NULL)
|
||||
|
||||
fun testAnnotationWithStarImport() = doFindAnnotationTest(AnnotationUtil.NOT_NULL)
|
||||
|
||||
fun testWrongImport() = doFindAnnotationTest(AnnotationUtil.NULLABLE, false)
|
||||
|
||||
fun testAnnotationInMethod() = doFindAnnotationTest(AnnotationUtil.NOT_NULL)
|
||||
|
||||
fun testFormatReferenceSimple() = doFormatReferenceTest("foo.bar", "foo.bar")
|
||||
|
||||
fun testFormatReferenceSpacesOnTheEnd() = doFormatReferenceTest("foo. bar ", "foo.bar")
|
||||
|
||||
fun testFormatReferenceSpacesInTheMiddle() = doFormatReferenceTest("foo. bar .baz", "foo.bar.baz")
|
||||
|
||||
fun testFormatReferenceSpacesInTheBeginning() = doFormatReferenceTest(" foo .bar", "foo.bar")
|
||||
|
||||
fun testFormatReferenceWithNewLines() = doFormatReferenceTest("\nfoo\n.\n\nbar\n.baz", "foo.bar.baz")
|
||||
|
||||
private fun doFormatReferenceTest(input: String, expected: String) {
|
||||
val actual = DumbModeTestUtils.computeInDumbModeSynchronously(project) {
|
||||
DumbAwareAnnotationUtil.getFormattedReferenceFqn(input)
|
||||
}
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
private fun doFindAnnotationTest(annotationFqn: String, shouldAnnotationBePresent: Boolean = true) {
|
||||
myFixture.configureByFile(getTestName(false) + ".java")
|
||||
DumbModeTestUtils.runInDumbModeSynchronously(project) {
|
||||
val element = myFixture.elementAtCaret
|
||||
assertTrue(element is PsiModifierListOwner)
|
||||
assertEquals(DumbAwareAnnotationUtil.hasAnnotation(element as PsiModifierListOwner, annotationFqn), shouldAnnotationBePresent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ class Super {
|
||||
}
|
||||
|
||||
class Sub extends Super {
|
||||
@Override
|
||||
void asdftypo() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.spellchecker.inspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
|
||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
|
||||
import com.intellij.openapi.application.PluginPathManager;
|
||||
import com.intellij.testFramework.DumbModeTestUtils;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -17,34 +20,45 @@ public class JavaSpellcheckerInspectionTest extends LightJavaCodeInsightFixtureT
|
||||
return JAVA_21;
|
||||
}
|
||||
|
||||
public void testCorrectJava() { doTest(); }
|
||||
public void testTypoInJava() { doTest(); }
|
||||
public void testVarArg() { doTest(); }
|
||||
public void testJapanese() { doTest(); }
|
||||
public void testCorrectJava() { doTestInAllModes(); }
|
||||
public void testTypoInJava() { doTestInAllModes(); }
|
||||
public void testVarArg() { doTestInAllModes(); }
|
||||
public void testJapanese() { doTestInAllModes(); }
|
||||
|
||||
public void testClassName() { doTest(); }
|
||||
public void testFieldName() { doTest(); }
|
||||
public void testMethodName() { doTest(); }
|
||||
public void testConstructorIgnored() { doTest();}
|
||||
public void testLocalVariableName() { doTest(); }
|
||||
public void testDocComment() { doTest(); }
|
||||
public void testStringLiteral() { doTest(); }
|
||||
public void testStringLiteralEscaping() { doTest(); }
|
||||
public void testSuppressions() { doTest(); }
|
||||
public void testClassName() { doTestInAllModes(); }
|
||||
public void testFieldName() { doTestInAllModes(); }
|
||||
public void testMethodName() { doTestInAllModes(); }
|
||||
public void testConstructorIgnored() { doTestInAllModes();}
|
||||
public void testLocalVariableName() { doTestInAllModes(); }
|
||||
public void testDocComment() { doTestInAllModes(); }
|
||||
public void testStringLiteral() { doTestInAllModes(); }
|
||||
public void testStringLiteralEscaping() { doTestInAllModes(); }
|
||||
public void testSuppressions() { doTest(false); }
|
||||
|
||||
// suppression by @NonNls
|
||||
public void testMethodReturnTypeWithNonNls() { doTest(); }
|
||||
public void testMethodReturnTypeWithNonNlsReturnsLiteral() { doTest(); }
|
||||
public void testNonNlsField() { doTest(); }
|
||||
public void testNonNlsField2() { doTest(); }
|
||||
public void testNonNlsLocalVariable() { doTest(); }
|
||||
public void testNonNlsLocalVariableAndComment() { doTest(); }
|
||||
public void testFieldComment() { doTest(); }
|
||||
public void testDoNotCheckDerivedNames() { doTest(); }
|
||||
public void testSkipDateTime() { doTest(); }
|
||||
public void testMethodReturnTypeWithNonNls() { doTestInAllModes(); }
|
||||
public void testMethodReturnTypeWithNonNlsReturnsLiteral() { doTestInAllModes(); }
|
||||
public void testNonNlsField() { doTestInAllModes(); }
|
||||
public void testNonNlsField2() { doTestInAllModes(); }
|
||||
public void testNonNlsLocalVariable() { doTestInAllModes(); }
|
||||
public void testNonNlsLocalVariableAndComment() { doTestInAllModes(); }
|
||||
public void testFieldComment() { doTestInAllModes(); }
|
||||
public void testDoNotCheckDerivedNames() { doTestInAllModes(); }
|
||||
public void testSkipDateTime() { doTestInAllModes(); }
|
||||
|
||||
private void doTest() {
|
||||
private void doTestInAllModes() {
|
||||
doTest(false);
|
||||
doTest(true);
|
||||
}
|
||||
|
||||
private void doTest(boolean inDumbMode) {
|
||||
myFixture.enableInspections(SpellcheckerInspectionTestCase.getInspectionTools());
|
||||
myFixture.testHighlighting(false, false, true, getTestName(false) + ".java");
|
||||
if (inDumbMode) {
|
||||
((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).mustWaitForSmartMode(false, getTestRootDisposable());
|
||||
DumbModeTestUtils.runInDumbModeSynchronously(getProject(),
|
||||
() -> myFixture.testHighlighting(false, false, true, getTestName(false) + ".java"));
|
||||
} else {
|
||||
myFixture.testHighlighting(false, false, true, getTestName(false) + ".java");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user