From 72607096d0cef86a2d4640da71f569d7ae6d0627 Mon Sep 17 00:00:00 2001 From: Sergey Patrikeev Date: Mon, 22 Oct 2018 12:33:26 +0300 Subject: [PATCH] IDEA-200832 Deprecation inspection: recognise externally deprecated APIs. Extend API to support repeatable annotations and annotations from different external roots. --- .../DeprecationInspectionBase.java | 50 ++++++-- .../ExternalAnnotationsManagerImpl.java | 8 +- .../ExternalAnnotationsManager.java | 68 +++++++--- .../BaseExternalAnnotationsManager.java | 118 ++++++++++++++---- .../expected.xml | 9 ++ .../extAnnotations/a/annotations.xml | 5 + .../src/Test.java | 5 + .../src/a/A.java | 5 + .../DeprecationInspectionTest.java | 36 +++--- 9 files changed, 232 insertions(+), 72 deletions(-) create mode 100644 java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml create mode 100644 java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/extAnnotations/a/annotations.xml create mode 100644 java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java create mode 100644 java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/a/A.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java index 5f01b940291e..3bc7209a29db 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java @@ -2,6 +2,7 @@ package com.intellij.codeInspection.deprecation; import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.ExternalAnnotationsManager; import com.intellij.codeInsight.daemon.JavaErrorMessages; import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil; import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; @@ -26,6 +27,7 @@ import com.intellij.psi.javadoc.PsiDocTag; import com.intellij.psi.util.*; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; import one.util.streamex.MoreCollectors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -145,7 +147,7 @@ abstract class DeprecationInspectionBase extends AbstractBaseJavaLocalInspection final PsiClass containingClass = method.getContainingClass(); assert containingClass != null; final PsiClass superClass = containingClass.getSuperClass(); - if (hasDefaultDeprecatedConstructor(superClass, myForRemoval)) { + if (superClass != null && hasDefaultDeprecatedConstructor(superClass, myForRemoval)) { if (superClass instanceof PsiAnonymousClass) { final PsiExpressionList argumentList = ((PsiAnonymousClass)superClass).getArgumentList(); if (argumentList != null && !argumentList.isEmpty()) return; @@ -174,7 +176,7 @@ abstract class DeprecationInspectionBase extends AbstractBaseJavaLocalInspection final PsiMethod[] currentConstructors = aClass.getConstructors(); if (currentConstructors.length == 0) { final PsiClass superClass = aClass.getSuperClass(); - if (hasDefaultDeprecatedConstructor(superClass, myForRemoval)) { + if (superClass != null && hasDefaultDeprecatedConstructor(superClass, myForRemoval)) { final boolean isAnonymous = aClass instanceof PsiAnonymousClass; if (isAnonymous) { final PsiExpressionList argumentList = ((PsiAnonymousClass)aClass).getArgumentList(); @@ -225,8 +227,27 @@ abstract class DeprecationInspectionBase extends AbstractBaseJavaLocalInspection } } - private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass, boolean forRemoval) { - return superClass != null && Arrays.stream(superClass.getConstructors()) + private static boolean hasDefaultDeprecatedConstructor(@NotNull PsiClass superClass, boolean forRemoval) { + PsiMethod[] constructors = superClass.getConstructors(); + if (constructors.length == 0) { + /* + The default constructor of a class can be externally annotated (IDEA-200832). + There cannot be inferred annotations for a default constructor, + so here is no need to check all annotations returned + by `AnnotationUtil.findAnnotations()`, but only external ones. + + Note that there may be multiple external annotations roots, + so we check them all. + */ + List externalDeprecated = ExternalAnnotationsManager + .getInstance(superClass.getProject()) + .findDefaultConstructorExternalAnnotations(superClass, CommonClassNames.JAVA_LANG_DEPRECATED); + + return externalDeprecated != null + && !externalDeprecated.isEmpty() + && ContainerUtil.exists(externalDeprecated, annotation -> isMarkedForRemoval(annotation) == forRemoval); + } + return Arrays.stream(constructors) .anyMatch(constructor -> constructor.getParameterList().isEmpty() && constructor.isDeprecated() && isMarkedForRemoval(constructor, forRemoval)); @@ -306,7 +327,11 @@ abstract class DeprecationInspectionBase extends AbstractBaseJavaLocalInspection } private static boolean isMarkedForRemoval(PsiModifierListOwner element, boolean forRemoval) { - return isMarkedForRemoval(element) == forRemoval; + PsiAnnotation annotation = AnnotationUtil.findAnnotation(element, CommonClassNames.JAVA_LANG_DEPRECATED); + if (annotation == null) { + return !forRemoval; + } + return isMarkedForRemoval(annotation) == forRemoval; } private static boolean isInSameOutermostClass(PsiElement refElement, PsiElement elementToHighlight, boolean ignoreInSameOutermostClass) { @@ -324,12 +349,15 @@ abstract class DeprecationInspectionBase extends AbstractBaseJavaLocalInspection return maybeClass instanceof PsiClass ? (PsiClass)maybeClass : null; } - private static boolean isMarkedForRemoval(@Nullable PsiModifierListOwner element) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(element, CommonClassNames.JAVA_LANG_DEPRECATED); - if (annotation == null) { - return false; - } - PsiAnnotationMemberValue value = annotation.findAttributeValue("forRemoval"); + /** + * Returns value of {@link Deprecated#forRemoval} attribute, which is available since Java 9. + * + * @param deprecatedAnnotation annotation instance to extract value of + * @return {@code true} if the {@code forRemoval} attribute is set to true, + * {@code false} if it isn't set or is set to {@code false}. + */ + private static boolean isMarkedForRemoval(@NotNull PsiAnnotation deprecatedAnnotation) { + PsiAnnotationMemberValue value = deprecatedAnnotation.findAttributeValue("forRemoval"); Object result = null; if (value instanceof PsiLiteral) { result = ((PsiLiteral)value).getValue(); diff --git a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java index fb9cb7a5919f..79fbeeb822dd 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java @@ -182,7 +182,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM notifyAfterAnnotationChanging(listOwner, annotationFQName, false); return false; } - String externalName = getExternalName(listOwner, false); + String externalName = getExternalName(listOwner); WriteCommandAction.writeCommandAction(project).run(() -> { appendChosenAnnotationsRoot(entry, newRoot); XmlFile xmlFileInRoot = findXmlFileInRoot(findExternalAnnotationsXmlFiles(listOwner), newRoot); @@ -279,7 +279,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM } Set annotationFiles = xmlFiles == null ? new THashSet<>() : new THashSet<>(xmlFiles); - String externalName = getExternalName(listOwner, false); + String externalName = getExternalName(listOwner); WriteCommandAction.writeCommandAction(project).run(() -> { if (existingXml != null) { annotateExternally(listOwner, annotationFQName, existingXml, fromFile, value, externalName); @@ -355,7 +355,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM .runWriteCommandAction(myPsiManager.getProject(), ExternalAnnotationsManagerImpl.class.getName(), null, () -> { PsiDocumentManager.getInstance(myPsiManager.getProject()).commitAllDocuments(); try { - tag.setAttribute("name", StringUtil.escapeXml(getExternalName(element, false))); + tag.setAttribute("name", StringUtil.escapeXml(getExternalName(element))); commitChanges(file); } catch (IncorrectOperationException e) { @@ -410,7 +410,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM if (rootTag == null) { continue; } - final String externalName = getExternalName(listOwner, false); + final String externalName = getExternalName(listOwner); final List tagsToProcess = new ArrayList<>(); for (XmlTag tag : rootTag.getSubTags()) { diff --git a/java/java-psi-api/src/com/intellij/codeInsight/ExternalAnnotationsManager.java b/java/java-psi-api/src/com/intellij/codeInsight/ExternalAnnotationsManager.java index 115f7178d44c..c9e7b41de301 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/ExternalAnnotationsManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/ExternalAnnotationsManager.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 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.codeInsight; import com.intellij.openapi.components.ServiceManager; @@ -50,12 +36,64 @@ public abstract class ExternalAnnotationsManager { @Nullable public abstract PsiAnnotation findExternalAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN); + /** + * Returns external annotations with fully qualified name of {@code annotationFQN} + * associated with {@code listOwner}. + * + * Multiple results may be returned for repeatable annotations and annotations + * from several external annotations roots. + * + * @param listOwner API element to return external annotations of + * @param annotationFQN fully qualified name of the annotation to search for + * @return external annotations of the {@code listOwner} + */ + @NotNull + public abstract List findExternalAnnotations(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN); + + // Method used in Kotlin plugin public abstract boolean isExternalAnnotationWritable(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN); @Nullable public abstract PsiAnnotation[] findExternalAnnotations(@NotNull PsiModifierListOwner listOwner); + /** + * Returns external annotations associated with default + * constructor of the {@code aClass}, if the constructor exists. + *

+ * Default constructors should be handled specially + * because they don't have {@code PsiModifierListOwner}, + * nor they are returned in {@link PsiClass#getConstructors()}. + *

+ * Yet default constructors may be externally annotated + * in corresponding {@code annotations.xml}: + *

{@code 
+   *  
+   * }
+ * + * @param aClass class of which default constructor's external annotations are to be found + * @return external annotations of the default constructor of {@code aClass} or {@code null} + * if the class doesn't have a default constructor + */ + @Nullable + public abstract List findDefaultConstructorExternalAnnotations(@NotNull PsiClass aClass); + + /** + * Returns external annotations with fully qualified name of {@code annotationFQN} + * associated with default constructor of the {@code aClass}, if the constructor exists. + * + * Multiple annotations may be returned since there may be repeatable annotations + * or annotations from several external annotations roots. + * + * @param aClass class of which default constructor's external annotations are to be found + * @param annotationFQN fully qualified name of annotation class to search for + * @return annotations of the default constructor of {@code aClass}, or {@code null} if the + * class doesn't have a default constructor. + * @see #findDefaultConstructorExternalAnnotations(PsiClass) + */ + @Nullable + public abstract List findDefaultConstructorExternalAnnotations(@NotNull PsiClass aClass, @NotNull String annotationFQN); + public abstract void annotateExternally(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQName, @NotNull PsiFile fromFile, diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/BaseExternalAnnotationsManager.java b/java/java-psi-impl/src/com/intellij/codeInsight/BaseExternalAnnotationsManager.java index abe82349b9d9..3c43714e25a7 100644 --- a/java/java-psi-impl/src/com/intellij/codeInsight/BaseExternalAnnotationsManager.java +++ b/java/java-psi-impl/src/com/intellij/codeInsight/BaseExternalAnnotationsManager.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 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.codeInsight; import com.intellij.lang.java.parser.JavaParser; @@ -35,6 +21,8 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MostlySingularMultiMap; import com.intellij.util.text.CharSequenceReader; import gnu.trove.THashSet; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -48,6 +36,7 @@ import javax.xml.parsers.SAXParserFactory; import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; public abstract class BaseExternalAnnotationsManager extends ExternalAnnotationsManager { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.BaseExternalAnnotationsManager"); @@ -65,6 +54,25 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations LowMemoryWatcher.register(this::dropCache, psiManager.getProject()); } + /** + * Returns canonical string presentation of {@code listOwner} + * used in external annotations files. + * + * @param listOwner API element to return external name of + * @return external name or {@code null} if the {@code listOwner} + * is of unknown type (neither class, method, field nor parameter) + */ + @Nullable + protected static String getExternalName(@NotNull PsiModifierListOwner listOwner) { + return getExternalName(listOwner, false); + } + + /** + * @deprecated use {@link #getExternalName(PsiModifierListOwner)} instead + * since external annotations files don't contain parameters' names anyway. + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2019.3") + @Deprecated @Nullable protected static String getExternalName(@NotNull PsiModifierListOwner listOwner, boolean showParamName) { return PsiFormatUtil.getExternalName(listOwner, showParamName, Integer.MAX_VALUE); @@ -85,9 +93,45 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations @Override @Nullable public PsiAnnotation findExternalAnnotation(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQN) { - List list = collectExternalAnnotations(listOwner); - AnnotationData data = findByFQN(list, annotationFQN); - return data == null ? null : data.getAnnotation(this); + List result = findExternalAnnotations(listOwner, annotationFQN); + return result.isEmpty() ? null : result.get(0); + } + + @NotNull + @Override + public List findExternalAnnotations(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) { + List result = collectExternalAnnotations(listOwner); + return filterAnnotations(result, annotationFQN); + } + + @Nullable + @Override + public List findDefaultConstructorExternalAnnotations(@NotNull PsiClass aClass, @NotNull String annotationFQN) { + if (aClass.getConstructors().length > 0) { + return null; + } + List result = collectDefaultConstructorExternalAnnotations(aClass); + return filterAnnotations(result, annotationFQN); + } + + @NotNull + private List filterAnnotations(@NotNull List result, @NotNull String annotationFQN) { + return StreamEx.of(result) + .filter(data -> data.annotationClassFqName.equals(annotationFQN)) + .map(data -> data.getAnnotation(this)) + .toCollection(SmartList::new); + } + + @Nullable + @Override + public List findDefaultConstructorExternalAnnotations(@NotNull PsiClass aClass) { + if (aClass.getConstructors().length > 0) { + return null; + } + List result = collectDefaultConstructorExternalAnnotations(aClass); + return StreamEx.of(result) + .map(data -> data.getAnnotation(this)) + .toCollection(SmartList::new); } @Override @@ -110,21 +154,36 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations } private static final List NO_DATA = new ArrayList<>(1); - private final ConcurrentMostlySingularMultiMap cache = new ConcurrentMostlySingularMultiMap<>(); + private final ConcurrentMostlySingularMultiMap cache = new ConcurrentMostlySingularMultiMap<>(); // interner for storing annotation FQN private final CharTableImpl charTable = new CharTableImpl(); @NotNull - private List collectExternalAnnotations(@NotNull PsiModifierListOwner listOwner) { - if (!hasAnyAnnotationsRoots()) return Collections.emptyList(); + private List collectDefaultConstructorExternalAnnotations(@NotNull PsiClass aClass) { + //External annotations of default constructor are stored at the same annotations files as class' ones. + List annotationsFiles = findExternalAnnotationsFiles(aClass); + if (annotationsFiles == null) return NO_DATA; + String defCtrExternalName = getExternalName(aClass) + " " + aClass.getName() + "()"; + return collectExternalAnnotations(defCtrExternalName, () -> doCollect(defCtrExternalName, annotationsFiles, false)); + } + + @NotNull + private List collectExternalAnnotations(@NotNull PsiModifierListOwner listOwner) { + return collectExternalAnnotations(listOwner, () -> doCollect(listOwner, false)); + } + + @NotNull + private List collectExternalAnnotations(@NotNull Object cacheKey, + @NotNull Supplier> dataSupplier) { + if (!hasAnyAnnotationsRoots()) return Collections.emptyList(); List cached; while (true) { - cached = (List)cache.get(listOwner); + cached = (List)cache.get(cacheKey); if (cached == NO_DATA || !cached.isEmpty()) return cached; - List computed = doCollect(listOwner, false); - if (cache.replace(listOwner, cached, computed)) { + List computed = dataSupplier.get(); + if (cache.replace(cacheKey, cached, computed)) { cached = computed; break; } @@ -182,11 +241,16 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations List files = findExternalAnnotationsFiles(listOwner); if (files == null) return NO_DATA; - String externalName = getExternalName(listOwner, false); + String externalName = getExternalName(listOwner); if (externalName == null) return NO_DATA; + return doCollect(externalName, files, onlyWritable); + } + + @NotNull + private List doCollect(@NotNull String externalName, @NotNull List annotationsFiles, boolean onlyWritable) { SmartList result = new SmartList<>(); - for (PsiFile file : files) { + for (PsiFile file : annotationsFiles) { if (!file.isValid()) continue; if (onlyWritable && !file.isWritable()) continue; @@ -423,7 +487,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations } @Override - public void endElement(String uri, String localName, String qName) throws SAXException { + public void endElement(String uri, String localName, String qName) { if ("item".equals(qName)) { myExternalName = null; } diff --git a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml new file mode 100644 index 000000000000..4c8349b3b997 --- /dev/null +++ b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml @@ -0,0 +1,9 @@ + + + + Test.java + 4 + Default constructor in 'a.A' is deprecated + + + diff --git a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/extAnnotations/a/annotations.xml b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/extAnnotations/a/annotations.xml new file mode 100644 index 000000000000..c7d32cdd3c9f --- /dev/null +++ b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/extAnnotations/a/annotations.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java new file mode 100644 index 000000000000..60c5ca4d16d2 --- /dev/null +++ b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java @@ -0,0 +1,5 @@ +import a.A; + +//Here "Default constructor in A is deprecated" warning must be reported. +public class Test extends A { +} diff --git a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/a/A.java b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/a/A.java new file mode 100644 index 000000000000..7cb743b14e8f --- /dev/null +++ b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/a/A.java @@ -0,0 +1,5 @@ +package a; + +//This class' default constructor is externally deprecated. +public class A { +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DeprecationInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DeprecationInspectionTest.java index 901f880f5238..5a2db65218be 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DeprecationInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DeprecationInspectionTest.java @@ -1,23 +1,13 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 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.java.codeInspection; import com.intellij.JavaTestUtil; import com.intellij.codeInspection.deprecation.DeprecationInspection; +import com.intellij.openapi.roots.JavaModuleExternalPaths; +import com.intellij.openapi.roots.ModuleRootModificationUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.testFramework.InspectionTestCase; /** @@ -79,4 +69,20 @@ public class DeprecationInspectionTest extends InspectionTestCase { doTest("deprecation/" + getTestName(true), tool); } + /** + * Sets up external deprecation annotations + * for the current test module. + */ + private void configureExternalAnnotationsUrls(String... urls) { + ModuleRootModificationUtil.updateModel(myModule, (root) -> { + JavaModuleExternalPaths extension = root.getModuleExtension(JavaModuleExternalPaths.class); + extension.setExternalAnnotationUrls(urls); + }); + } + + public void testExternallyDeprecatedDefaultConstructor() { + String url = VfsUtilCore.pathToUrl(FileUtil.toSystemIndependentName(getTestDataPath()) + "/deprecation/externallyDeprecatedDefaultConstructor/extAnnotations"); + configureExternalAnnotationsUrls(url); + doTest(); + } }