diff --git a/java/java-analysis-api/src/com/intellij/codeInspection/SuppressManager.java b/java/java-analysis-api/src/com/intellij/codeInspection/SuppressManager.java index 8a2c63eaf74f..e04fa08b49ee 100644 --- a/java/java-analysis-api/src/com/intellij/codeInspection/SuppressManager.java +++ b/java/java-analysis-api/src/com/intellij/codeInspection/SuppressManager.java @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/util/ChronoUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/util/ChronoUtil.java index 3b5f02eb1c18..9ead8dbf50af 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/util/ChronoUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/util/ChronoUtil.java @@ -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> 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 { } @@ -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 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 diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterAnnotationUtil.kt b/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterAnnotationUtil.kt index 694382fea14b..efba74dbd20f 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterAnnotationUtil.kt +++ b/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterAnnotationUtil.kt @@ -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 = 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() } - } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/spellchecker/JavaSpellcheckingStrategy.java b/java/java-impl/src/com/intellij/spellchecker/JavaSpellcheckingStrategy.java index 2decce4becf9..94b7c873fc18 100644 --- a/java/java-impl/src/com/intellij/spellchecker/JavaSpellcheckingStrategy.java +++ b/java/java-impl/src/com/intellij/spellchecker/JavaSpellcheckingStrategy.java @@ -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; diff --git a/java/java-impl/src/com/intellij/spellchecker/LiteralExpressionTokenizer.java b/java/java-impl/src/com/intellij/spellchecker/LiteralExpressionTokenizer.java index c9c8cb9e82e9..4f981eb7e011 100644 --- a/java/java-impl/src/com/intellij/spellchecker/LiteralExpressionTokenizer.java +++ b/java/java-impl/src/com/intellij/spellchecker/LiteralExpressionTokenizer.java @@ -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 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 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 { @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); } - } diff --git a/java/java-impl/src/com/intellij/spellchecker/NamedElementTokenizer.java b/java/java-impl/src/com/intellij/spellchecker/NamedElementTokenizer.java index fb3d684aed16..40a29e352ca8 100644 --- a/java/java-impl/src/com/intellij/spellchecker/NamedElementTokenizer.java +++ b/java/java-impl/src/com/intellij/spellchecker/NamedElementTokenizer.java @@ -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 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 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 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) { diff --git a/java/java-psi-api/src/com/intellij/codeInsight/DumbAwareAnnotationUtil.kt b/java/java-psi-api/src/com/intellij/codeInsight/DumbAwareAnnotationUtil.kt new file mode 100644 index 000000000000..ee8dbf16aede --- /dev/null +++ b/java/java-psi-api/src/com/intellij/codeInsight/DumbAwareAnnotationUtil.kt @@ -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 = 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) +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationInMethod.java b/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationInMethod.java new file mode 100644 index 000000000000..7a62adcd6e8a --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationInMethod.java @@ -0,0 +1,6 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class A { + @NotNull @Nullable String f() {}; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationWithFqn.java b/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationWithFqn.java new file mode 100644 index 000000000000..f1698def7291 --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationWithFqn.java @@ -0,0 +1,3 @@ +class A { + @org.jetbrains.annotations.NotNull String s; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationWithStarImport.java b/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationWithStarImport.java new file mode 100644 index 000000000000..53d735bc7557 --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/AnnotationWithStarImport.java @@ -0,0 +1,5 @@ +import org.jetbrains.annotations.*; + +class A { + @NotNull String s; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/NonNls.java b/java/java-tests/testData/util/dumbAwareAnnotation/NonNls.java new file mode 100644 index 000000000000..7f7965f6ce9c --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/NonNls.java @@ -0,0 +1,5 @@ +import org.jetbrains.annotations.NonNls; + +class A { + @NonNls String s; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/NotNull.java b/java/java-tests/testData/util/dumbAwareAnnotation/NotNull.java new file mode 100644 index 000000000000..e27155e65e21 --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/NotNull.java @@ -0,0 +1,5 @@ +import org.jetbrains.annotations.NotNull; + +class A { + @NotNull String s; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/Nullable.java b/java/java-tests/testData/util/dumbAwareAnnotation/Nullable.java new file mode 100644 index 000000000000..b914f985bc9e --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/Nullable.java @@ -0,0 +1,5 @@ +import org.jetbrains.annotations.Nullable; + +class A { + @Nullable String s; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/SingleAnnotation.java b/java/java-tests/testData/util/dumbAwareAnnotation/SingleAnnotation.java new file mode 100644 index 000000000000..fbe46b7ade1e --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/SingleAnnotation.java @@ -0,0 +1,5 @@ +import org.jetbrains.annotations.Nullable; + +class A { + @Nullable String s; +} \ No newline at end of file diff --git a/java/java-tests/testData/util/dumbAwareAnnotation/WrongImport.java b/java/java-tests/testData/util/dumbAwareAnnotation/WrongImport.java new file mode 100644 index 000000000000..a247fcd2d397 --- /dev/null +++ b/java/java-tests/testData/util/dumbAwareAnnotation/WrongImport.java @@ -0,0 +1,5 @@ +import org.jetbrains.unknown.annotatios.pack.Nullable; + +class A { + @Nullable String s; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/util/DumbAwareAnnotationUtilTest.kt b/java/java-tests/testSrc/com/intellij/util/DumbAwareAnnotationUtilTest.kt new file mode 100644 index 000000000000..db3bb51b16d2 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/util/DumbAwareAnnotationUtilTest.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/plugins/java-i18n/testData/inspections/spellchecker/DoNotCheckDerivedNames.java b/plugins/java-i18n/testData/inspections/spellchecker/DoNotCheckDerivedNames.java index 33ad3d162fef..72731bd2a7dd 100644 --- a/plugins/java-i18n/testData/inspections/spellchecker/DoNotCheckDerivedNames.java +++ b/plugins/java-i18n/testData/inspections/spellchecker/DoNotCheckDerivedNames.java @@ -5,6 +5,7 @@ class Super { } class Sub extends Super { + @Override void asdftypo() {} } diff --git a/plugins/java-i18n/testSrc/com/intellij/spellchecker/inspection/JavaSpellcheckerInspectionTest.java b/plugins/java-i18n/testSrc/com/intellij/spellchecker/inspection/JavaSpellcheckerInspectionTest.java index 731f859fb986..ba5678bb18bb 100644 --- a/plugins/java-i18n/testSrc/com/intellij/spellchecker/inspection/JavaSpellcheckerInspectionTest.java +++ b/plugins/java-i18n/testSrc/com/intellij/spellchecker/inspection/JavaSpellcheckerInspectionTest.java @@ -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"); + } } }