@NotNull List<? extends InspectionContext> inspect(@NotNull List<? extends LocalInspectionToolWrapper> toolWrappers,
                                                     @Nullable HighlightSeverity minimumSeverity,
                                                     boolean addRedundantSuppressions,
                                                     @NotNull ApplyIncrementallyCallback applyIncrementallyCallback,
                                                     @NotNull Consumer<? super InspectionContext> contextFinishedCallback,
                                                     @Nullable Condition<? super LocalInspectionToolWrapper> enabledToolsPredicate) {
    if (!shouldInspect(myPsiFile)) {
      return Collections.emptyList();
    }
    List<Divider.DividedElements> allDivided = new ArrayList<>();
    Divider.divideInsideAndOutsideAllRoots(myPsiFile, myRestrictRange, myPriorityRange,
                                           file -> shouldInspect(file), new CommonProcessors.CollectProcessor<>(allDivided));
    List<PsiElement> restrictedInside = ContainerUtil.concat(ContainerUtil.map(allDivided, d -> d.inside()));
    List<PsiElement> restrictedOutside = ContainerUtil.concat(ContainerUtil.map(allDivided, d -> ContainerUtil.concat(d.outside(), d.parents())));
    List<InspectionContext> injectedContexts = Collections.synchronizedList(new ArrayList<>());
    Project project = myPsiFile.getProject();

    boolean hasWholeFileTools = ContainerUtil.exists(toolWrappers, tool->tool.runForWholeFile());
    Set<String> dialectIdsInRestricted = InspectionEngine.calcElementDialectIds(restrictedInside, restrictedOutside);
    Set<String> dialectIdsInWhole;
    List<PsiElement> wholeInside;
    List<PsiElement> wholeOutside;
    if (hasWholeFileTools) {
      List<Divider.DividedElements> all = new ArrayList<>();
      Divider.divideInsideAndOutsideAllRoots(myPsiFile, myPsiFile.getTextRange(), myPriorityRange,
                                             file -> shouldInspect(file), new CommonProcessors.CollectProcessor<>(all));
      wholeInside = ContainerUtil.concat(ContainerUtil.map(all, d -> d.inside()));
      wholeOutside = ContainerUtil.concat(ContainerUtil.map(all, d -> ContainerUtil.concat(d.outside(), d.parents())));
      dialectIdsInWhole = InspectionEngine.calcElementDialectIds(wholeInside, wholeOutside);
    }
    else {
      wholeInside = List.of();
      wholeOutside = List.of();
      dialectIdsInWhole = Set.of();
    }

    List<LocalInspectionToolWrapper> applicableByLanguage =
      InspectionEngine.filterToolsApplicableByLanguage(toolWrappers, dialectIdsInRestricted, dialectIdsInWhole);

    List<InspectionContext> init = new ArrayList<>(applicableByLanguage.size());
    List<InspectionContext> redundantContexts = new ArrayList<>();
    HighlightInfoUpdaterImpl highlightInfoUpdater = (HighlightInfoUpdaterImpl)HighlightInfoUpdater.getInstance(project);
    // might be different from myPriorityRange because DividedElements can cache not exact but containing ranges
    TextRange finalPriorityRange = finalPriorityRange(myPriorityRange, allDivided);
    if (LOG.isTraceEnabled()) {
      LOG.trace("inspect: "+myPsiFile+"; host="+InjectedLanguageManager.getInstance(myPsiFile.getProject()).injectedToHost(myPsiFile, myPsiFile.getTextRange())+";" +
                                    "\n"+" inside:"+restrictedInside.size()+ ": "+restrictedInside+
                                    "\n"+"; outside:"+restrictedOutside.size()+": "+restrictedOutside);
    }
    InspectionEngine.withSession(myPsiFile, myRestrictRange, finalPriorityRange, minimumSeverity, myIsOnTheFly, session -> {
      for (LocalInspectionToolWrapper toolWrapper : applicableByLanguage) {
        if (enabledToolsPredicate == null || enabledToolsPredicate.value(toolWrapper)) {
          LocalInspectionTool tool = toolWrapper.getTool();
          AtomicInteger toolWasProcessed = new AtomicInteger();
          ToolStampInfo toolStamps = new ToolStampInfo();
          InspectionProblemHolder holder = new InspectionProblemHolder(myPsiFile, toolWrapper, myIsOnTheFly, myInspectionProfileWrapper,
                                                                       toolWasProcessed, toolStamps, applyIncrementallyCallback);
          PsiElementVisitor visitor = InspectionEngine.createVisitor(tool, holder, myIsOnTheFly, session);
          // if inspection returned the empty visitor, then it should be skipped
          if (visitor == PsiElementVisitor.EMPTY_VISITOR) {
            continue;
          }
          tool.inspectionStarted(session, myIsOnTheFly);

          List<? extends PsiElement> sortedInside = HighlightInfoUpdaterImpl.sortByPsiElementFertility(myPsiFile, toolWrapper, toolWrapper.runForWholeFile() ? wholeInside : restrictedInside);
          List<? extends PsiElement> outside = toolWrapper.runForWholeFile() ? wholeOutside : restrictedOutside;
          InspectionContext context = new InspectionContext(toolWrapper, holder, visitor, sortedInside, outside, true, InspectionVisitorOptimizer.getAcceptingPsiTypes(visitor), myPsiFile);
          init.add(context);
        }
      }
      //sort `init`, according to the priorities, saved earlier to run in order
      // but only for visible elements, because we don't care about the order in 'outside', and spending CPU on their rearrangement would be counterproductive
      InspectionProfilerDataHolder.sortByLatencies(myPsiFile, init, highlightInfoUpdater);

      int initSize = init.size();
      AtomicInteger addedInvisibles = new AtomicInteger();

      PairProcessor<? super InspectionContext, JobLauncherImpl.QueueController<? super InspectionContext>> contextProcessor = (context,addToQueue) -> {
        executeInImpatientReadAction(()-> {
          processContext(context);
          contextCompleted(context, session, addToQueue, initSize, addedInvisibles, contextFinishedCallback);
        });
        return true;
      };
      // start InspectionContexts in `init` parallel to discovering injected fragments and running inspection runner on them too.
      // note that the parallelism is restricted: first all InspectionContexts(visible=true) from `init` are run in parallel, then
      // all InspectionContexts(visible=false) from `init` are added to the queue and run in parallel too.
      // thus, we avoid running the same inspection tool visitor in a reentrant manner (on visible elements parallel to invisible elements),
      // because some of them are not ready for that.
      if (!JobLauncher.getInstance().procInOrderAsync(new SensitiveProgressWrapper(myProgress), initSize, contextProcessor, addToQueue -> {
        // have to do all this even for empty elements, to perform correct cleanup/inspectionFinished
        if (init.isEmpty()) {
          addToQueue.finish();
        }
        else {
          for (InspectionContext context : init) {
            addToQueue.enqueue(context);
          }
        }
        reportIdsOfInspectionsReportedAnyProblemToFUS(init);

        if (myInspectInjected && InjectionUtils.shouldInspectInjectedFiles(myPsiFile)) {
          // we don't run whole-file tools on injected fragments
          List<LocalInspectionToolWrapper> localTools = ContainerUtil.filter(toolWrappers, t -> !t.runForWholeFile());
          return inspectInjectedPsi(session, localTools, injectedContexts, applyIncrementallyCallback,
                                    contextFinishedCallback, enabledToolsPredicate, addToInjectedQueue ->
              getInjectedWithHosts(ContainerUtil.concat(restrictedInside, restrictedOutside), addToInjectedQueue));
        }
        return true;
      })) {
        throw new ProcessCanceledException();
      }

      boolean isWholeFileInspectionsPass = !init.isEmpty() && init.get(0).tool.runForWholeFile();
      if (myIsOnTheFly && !isWholeFileInspectionsPass) {
        // do not save stats for the batch process, there could be too many files
        InspectionProfilerDataHolder.saveStats(myPsiFile, init, highlightInfoUpdater);
      }
      if (myIsOnTheFly && addRedundantSuppressions) {
        addRedundantSuppressions(init, toolWrappers, redundantContexts, applyIncrementallyCallback, contextFinishedCallback);
      }
    });
    return ContainerUtil.concat(init, redundantContexts, injectedContexts);
  }
@NotNull List<? extends InspectionContext> inspect(@NotNull List<? extends LocalInspectionToolWrapper> toolWrappers,
                                                     @Nullable HighlightSeverity minimumSeverity,
                                                     boolean addRedundantSuppressions,
                                                     @NotNull ApplyIncrementallyCallback applyIncrementallyCallback,
                                                     @NotNull Consumer<? super InspectionContext> contextFinishedCallback,
                                                     @Nullable Condition<? super LocalInspectionToolWrapper> enabledToolsPredicate)