All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition java-analysis-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.
 */
package com.intellij.codeInsight.daemon.impl;

import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.ex.EntryPointsManagerBase;
import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider;
import com.intellij.find.findUsages.*;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.FindSuperElementsHelper;
import com.intellij.psi.impl.source.PsiClassImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class UnusedSymbolUtil {
  private static final ImplicitUsageProvider[] ourImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);

  public static boolean isInjected(@NotNull Project project, @NotNull PsiModifierListOwner modifierListOwner) {
    return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner);
  }

  public static boolean isImplicitUsage(@NotNull Project project,
                                        @NotNull PsiModifierListOwner element,
                                        @NotNull ProgressIndicator progress) {
    if (isInjected(project, element)) return true;
    for (ImplicitUsageProvider provider : ourImplicitUsageProviders) {
      progress.checkCanceled();
      if (provider.isImplicitUsage(element)) {
        return true;
      }
    }

    return false;
  }

  public static boolean isImplicitRead(@NotNull Project project, @NotNull PsiVariable element, @NotNull ProgressIndicator progress) {
    for(ImplicitUsageProvider provider: ourImplicitUsageProviders) {
      progress.checkCanceled();
      if (provider.isImplicitRead(element)) {
        return true;
      }
    }
    return isInjected(project, element);
  }

  public static boolean isImplicitWrite(@NotNull Project project,
                                        @NotNull PsiVariable element,
                                        @NotNull ProgressIndicator progress) {
    for(ImplicitUsageProvider provider: ourImplicitUsageProviders) {
      progress.checkCanceled();
      if (provider.isImplicitWrite(element)) {
        return true;
      }
    }
    return isInjected(project, element);
  }

  @Nullable
  public static HighlightInfo createUnusedSymbolInfo(@NotNull PsiElement element,
                                                     @NotNull String message,
                                                     @NotNull final HighlightInfoType highlightInfoType) {
    HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element).descriptionAndTooltip(message).create();
    if (info == null) {
      return null; //filtered out
    }

    UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME);
    for (UnusedDeclarationFixProvider provider : fixProviders) {
      IntentionAction[] fixes = provider.getQuickFixes(element);
      for (IntentionAction fix : fixes) {
        QuickFixAction.registerQuickFixAction(info, fix);
      }
    }
    return info;
  }

  public static boolean isFieldUnused(@NotNull Project project,
                                      @NotNull PsiFile containingFile,
                                      @NotNull PsiField field,
                                      @NotNull ProgressIndicator progress,
                                      @NotNull GlobalUsageHelper helper) {
    if (helper.isLocallyUsed(field)) {
      return false;
    }
    if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(project, containingFile, field, progress, helper)) {
      return false;
    }
    return weAreSureThereAreNoUsages(project, containingFile, field, progress, helper);
  }

  public static boolean isMethodReferenced(@NotNull Project project,
                                           @NotNull PsiFile containingFile,
                                           @NotNull PsiMethod method,
                                           @NotNull ProgressIndicator progress,
                                           @NotNull GlobalUsageHelper helper) {
    if (helper.isLocallyUsed(method)) return true;

    boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
    PsiClass containingClass = method.getContainingClass();
    if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) return true;
    if (isPrivate) {
      if (isIntentionalPrivateConstructor(method, containingClass)) {
        return true;
      }
      if (isImplicitUsage(project, method, progress)) {
        return true;
      }
      if (!helper.isCurrentFileAlreadyChecked()) {
        return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper);
      }
    }
    else {
      //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
      boolean isConstructor = method.isConstructor();
      if (containingClass != null && isConstructor
          && containingClass.getConstructors().length == 1
          && isClassUsed(project, containingFile, containingClass, progress, helper)) {
        return true;
      }
      if (isImplicitUsage(project, method, progress)) return true;

      if (!isConstructor && FindSuperElementsHelper.findSuperElements(method).length != 0) {
        return true;
      }
      if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) {
        return true;
      }
    }
    return false;
  }

  private static boolean weAreSureThereAreNoUsages(@NotNull Project project,
                                                   @NotNull PsiFile containingFile,
                                                   @NotNull final PsiMember member,
                                                   @NotNull ProgressIndicator progress,
                                                   @NotNull GlobalUsageHelper helper) {
    log("* " + member.getName() + ": call wearesure");
    if (!helper.shouldCheckUsages(member)) {
      log("* "+member.getName()+": should not check");
      return false;
    }

    final PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null;

    boolean sure = processUsages(project, containingFile, member, progress, ignoreFile, new Processor() {
      @Override
      public boolean process(UsageInfo info) {
        PsiFile psiFile = info.getFile();
        if (psiFile == ignoreFile || psiFile == null) {
          return true; // ignore usages in containingFile because isLocallyUsed() method would have caught that
        }
        int offset = info.getNavigationOffset();
        if (offset == -1) return true;
        PsiElement element = psiFile.findElementAt(offset);
        boolean inComment = element instanceof PsiComment;
        log("*     "+member.getName()+": usage :"+element);
        return inComment; // ignore comments
      }
    });
    log("*     "+member.getName()+": result:"+sure);
    return sure;
  }

  private static void log(String s) {
    //System.out.println(s);
  }

  // return false if can't process usages (weird member of too may usages) or processor returned false
  public static boolean processUsages(@NotNull Project project,
                                      @NotNull PsiFile containingFile,
                                      @NotNull PsiMember member,
                                      @NotNull ProgressIndicator progress,
                                      @Nullable PsiFile ignoreFile,
                                      @NotNull Processor usageInfoProcessor) {
    String name = member.getName();
    if (name == null) {
      log("* "+member.getName()+" no name; false");
      return false;
    }
    SearchScope useScope = member.getUseScope();
    PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project);
    if (useScope instanceof GlobalSearchScope) {
      // some classes may have references from within XML outside dependent modules, e.g. our actions
      if (member instanceof PsiClass) {
        useScope = GlobalSearchScope.projectScope(project).uniteWith((GlobalSearchScope)useScope);
      }

      // if we've resolved all references, find usages will be fast
      PsiSearchHelper.SearchCostResult cheapEnough = RefResolveService.ENABLED && RefResolveService.getInstance(project).isUpToDate() ? PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES :
                                                     searchHelper.isCheapEnoughToSearch(name, (GlobalSearchScope)useScope, ignoreFile, progress);
      if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
        log("* "+member.getName()+" too many usages; false");
        return false;
      }

      //search usages if it cheap
      //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
      if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames(member, containingFile)) {
        log("* "+member.getName()+" 0 usages; true");
        return true;
      }

      if (member instanceof PsiMethod) {
        String propertyName = PropertyUtil.getPropertyName(member);
        if (propertyName != null) {
          SearchScope fileScope = containingFile.getUseScope();
          if (fileScope instanceof GlobalSearchScope &&
              searchHelper.isCheapEnoughToSearch(propertyName, (GlobalSearchScope)fileScope, ignoreFile, progress) ==
              PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
            log("* "+member.getName()+" too many prop usages; false");
            return false;
          }
        }
      }
    }
    FindUsagesOptions options;
    if (member instanceof PsiPackage) {
      options = new JavaPackageFindUsagesOptions(project);
      options.isSearchForTextOccurrences = true;
    }
    else if (member instanceof PsiClass) {
      options = new JavaClassFindUsagesOptions(project);
      options.isSearchForTextOccurrences = true;
    }
    else if (member instanceof PsiMethod) {
      PsiMethod method = (PsiMethod)member;
      options = new JavaMethodFindUsagesOptions(project);
      options.isSearchForTextOccurrences = method.isConstructor();
    }
    else if (member instanceof PsiVariable) {
      options = new JavaVariableFindUsagesOptions(project);
      options.isSearchForTextOccurrences = false;
    }
    else {
      options = new FindUsagesOptions(project);
      options.isSearchForTextOccurrences = true;
    }
    options.isUsages = true;
    options.searchScope = useScope;
    return JavaFindUsagesHelper.processElementUsages(member, options, usageInfoProcessor);
  }

  private static boolean isEnumValuesMethodUsed(@NotNull Project project,
                                                @NotNull PsiFile containingFile,
                                                @NotNull PsiMember member,
                                                @NotNull ProgressIndicator progress,
                                                @NotNull GlobalUsageHelper helper) {
    final PsiClass containingClass = member.getContainingClass();
    if (!(containingClass instanceof PsiClassImpl)) return true;
    final PsiMethod valuesMethod = ((PsiClassImpl)containingClass).getValuesMethod();
    return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper);
  }

  private static boolean canBeReferencedViaWeirdNames(@NotNull PsiMember member, @NotNull PsiFile containingFile) {
    if (member instanceof PsiClass) return false;
    if (!(containingFile instanceof PsiJavaFile)) return true;  // Groovy field can be referenced from Java by getter
    if (member instanceof PsiField) return false;  //Java field cannot be referenced by anything but its name
    if (member instanceof PsiMethod) {
      return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member);  //Java accessors can be referenced by field name from Groovy
    }
    return false;
  }

  public static boolean isClassUsed(@NotNull Project project,
                                    @NotNull PsiFile containingFile,
                                    @NotNull PsiClass aClass,
                                    @NotNull ProgressIndicator progress,
                                    @NotNull GlobalUsageHelper helper) {
    Boolean result = helper.unusedClassCache.get(aClass);
    if (result == null) {
      result = isReallyUsed(project, containingFile, aClass, progress, helper);
      helper.unusedClassCache.put(aClass, result);
    }
    return result;
  }

  private static boolean isReallyUsed(@NotNull Project project,
                                      @NotNull PsiFile containingFile,
                                      @NotNull PsiClass aClass,
                                      @NotNull ProgressIndicator progress,
                                      @NotNull GlobalUsageHelper helper) {
    if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) return true;
    if (helper.isCurrentFileAlreadyChecked()) {
      if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
             aClass.getParent() instanceof PsiDeclarationStatement ||
             aClass instanceof PsiTypeParameter) return false;
    }
    return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper);
  }

  private static boolean isIntentionalPrivateConstructor(@NotNull PsiMethod method, PsiClass containingClass) {
    return method.isConstructor() &&
           containingClass != null &&
           containingClass.getConstructors().length == 1;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy