[Java] Make JavaSpellcheckingStrategy dumb aware

IDEA-357681

GitOrigin-RevId: 1fead4d1fdb98c99d8b68d48813f483deaa6b35c
This commit is contained in:
Georgii Ustinov
2024-10-05 10:25:23 +00:00
committed by intellij-monorepo-bot
parent 3eb5909fbd
commit c02d32ef74
19 changed files with 377 additions and 102 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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() }
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -0,0 +1,6 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
class A {
@NotNull @Nullable String f<caret>() {};
}

View File

@@ -0,0 +1,3 @@
class A {
@org.jetbrains.annotations.NotNull String s<caret>;
}

View File

@@ -0,0 +1,5 @@
import org.jetbrains.annotations.*;
class A {
@NotNull String s<caret>;
}

View File

@@ -0,0 +1,5 @@
import org.jetbrains.annotations.NonNls;
class A {
@NonNls String s<caret>;
}

View File

@@ -0,0 +1,5 @@
import org.jetbrains.annotations.NotNull;
class A {
@NotNull String s<caret>;
}

View File

@@ -0,0 +1,5 @@
import org.jetbrains.annotations.Nullable;
class A {
@Nullable String s<caret>;
}

View File

@@ -0,0 +1,5 @@
import org.jetbrains.annotations.Nullable;
class A {
@Nullable<caret> String s;
}

View File

@@ -0,0 +1,5 @@
import org.jetbrains.unknown.annotatios.pack.Nullable;
class A {
@Nullable String s<caret>;
}

View File

@@ -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)
}
}
}

View File

@@ -5,6 +5,7 @@ class Super {
}
class Sub extends Super {
@Override
void asdftypo() {}
}

View File

@@ -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");
}
}
}