Files
openide/platform/inspect/src/com/intellij/codeInspection/InspectionApplicationBase.java
Dima Golovinov 0d1406330c IJ-MR-115962 QD-4485 qodana scan: pr-mode doesn't honor source-directory
Process review suggestions

GitOrigin-RevId: 8191fcae33e71efbe18816f8005914df0a269f28
2023-10-21 21:03:29 +00:00

930 lines
38 KiB
Java

// 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;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInspection.ex.*;
import com.intellij.codeInspection.reference.RefElement;
import com.intellij.conversion.ConversionListener;
import com.intellij.conversion.ConversionService;
import com.intellij.diagnostic.ThreadDumper;
import com.intellij.diff.tools.util.text.LineOffsetsUtil;
import com.intellij.diff.util.Range;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommandLineInspectionProgressReporter;
import com.intellij.ide.CommandLineInspectionProjectConfigurator;
import com.intellij.ide.impl.PatchProjectUtil;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.progress.util.ProgressIndicatorWithDelayedPresentation;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.roots.AdditionalLibraryRootsListener;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.ex.RangesBuilder;
import com.intellij.openapi.vfs.*;
import com.intellij.profile.codeInspection.BaseInspectionProfileManager;
import com.intellij.profile.codeInspection.InspectionProfileManager;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScopesCore;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.scope.packageSet.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.messages.MessageBusConnection;
import kotlinx.coroutines.future.FutureKt;
import one.util.streamex.StreamEx;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.jetbrains.concurrency.AsyncPromise;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Predicate;
import static com.intellij.configurationStore.StoreUtilKt.forPoorJavaClientOnlySaveProjectIndEdtDoNotUseThisMethod;
public class InspectionApplicationBase implements CommandLineInspectionProgressReporter {
private static final Logger LOG = Logger.getInstance(InspectionApplicationBase.class);
public static final String PROJECT_STRUCTURE_DIR = "projectStructure";
public InspectionToolCmdlineOptionHelpProvider myHelpProvider;
public String myProjectPath;
public String myOutPath;
public String mySourceDirectory;
public String myStubProfile;
public String myProfileName;
public String myProfilePath;
public boolean myRunWithEditorSettings;
boolean myRunGlobalToolsOnly;
public boolean myAnalyzeChanges;
private boolean myPathProfiling;
private int myVerboseLevel;
private final Map<String, List<Range>> diffMap = new ConcurrentHashMap<>();
private final MultiMap<Pair<String, Integer>, String> originalWarnings = MultiMap.createConcurrent();
private final AsyncPromise<Void> isMappingLoaded = new AsyncPromise<>();
public String myOutputFormat;
public InspectionProfileImpl myInspectionProfile;
String myTargets;
public boolean myErrorCodeRequired = true;
String myScopePattern;
public void startup() {
if (myProjectPath == null) {
reportError("Project to inspect is not defined");
printHelpAndExit();
}
if (myProfileName == null && myProfilePath == null && myStubProfile == null) {
reportError("Profile to inspect with is not defined");
printHelpAndExit();
}
ApplicationManagerEx.getApplicationEx().setSaveAllowed(false);
try {
header();
execute();
}
catch (ProcessCanceledException e) {
reportError(e);
gracefulExit();
return;
}
catch (Throwable e) {
LOG.error(e);
reportError(e);
gracefulExit();
return;
}
if (myErrorCodeRequired) {
ApplicationManagerEx.getApplicationEx().exit(true, true);
}
}
@SuppressWarnings("unused")
public void enablePathProfiling() {
myPathProfiling = true;
}
public void header() { }
public void execute() throws Exception {
ApplicationInfo appInfo = ApplicationInfo.getInstance();
reportMessageNoLineBreak(1, InspectionsBundle.message("inspection.application.starting.up",
appInfo.getFullApplicationName() +
" (build " +
appInfo.getBuild().asString() +
")"));
reportMessage(1, InspectionsBundle.message("inspection.done"));
Disposable disposable = Disposer.newDisposable();
try {
run(Paths.get(FileUtil.toCanonicalPath(myProjectPath)), disposable);
}
finally {
Disposer.dispose(disposable);
}
}
private void printHelpAndExit() {
myHelpProvider.printHelpAndExit();
}
private @NotNull CommandLineInspectionProjectConfigurator.ConfiguratorContext configuratorContext(@NotNull Path projectPath,
@Nullable AnalysisScope scope) {
return new CommandLineInspectionProjectConfigurator.ConfiguratorContext() {
@Override
public @NotNull ProgressIndicator getProgressIndicator() {
return new ProgressIndicatorBase();
}
@Override
public @NotNull CommandLineInspectionProgressReporter getLogger() {
return InspectionApplicationBase.this;
}
@Override
public @NotNull Path getProjectPath() {
return projectPath;
}
@Override
public @NotNull Predicate<Path> getFilesFilter() {
return __ -> true;
}
@Override
public @NotNull Predicate<VirtualFile> getVirtualFilesFilter() {
return __ -> true;
}
};
}
protected void run(@NotNull Path projectPath, @NotNull Disposable parentDisposable)
throws IOException, InterruptedException, ExecutionException {
Project project = openProject(projectPath, parentDisposable);
if (project == null) return;
reportMessageNoLineBreak(1, InspectionsBundle.message("inspection.application.initializing.project"));
if (myInspectionProfile == null) {
myInspectionProfile = loadInspectionProfile(project);
}
AnalysisScope scope = getAnalysisScope(project);
if (scope == null) return;
LOG.info("Used scope: " + scope);
runAnalysisOnScope(projectPath, parentDisposable, project, myInspectionProfile, scope);
}
protected @Nullable Project openProject(@NotNull Path projectPath, @NotNull Disposable parentDisposable)
throws InterruptedException, ExecutionException {
VirtualFile vfsProject = LocalFileSystem.getInstance().refreshAndFindFileByPath(
FileUtil.toSystemIndependentName(projectPath.toString()));
if (vfsProject == null) {
reportError(InspectionsBundle.message("inspection.application.file.cannot.be.found", projectPath));
printHelpAndExit();
}
reportMessageNoLineBreak(1, InspectionsBundle.message("inspection.application.opening.project"));
ConversionService conversionService = ConversionService.getInstance();
StringBuilder convertErrorBuffer = new StringBuilder();
if (conversionService != null &&
conversionService.blockingConvertSilently(projectPath, createConversionListener(convertErrorBuffer)).openingIsCanceled()) {
onFailure(convertErrorBuffer.toString());
return null;
}
for (CommandLineInspectionProjectConfigurator configurator : CommandLineInspectionProjectConfigurator.EP_NAME.getExtensionList()) {
CommandLineInspectionProjectConfigurator.ConfiguratorContext context = configuratorContext(projectPath, null);
if (configurator.isApplicable(context)) {
configurator.configureEnvironment(context);
}
}
if (Boolean.getBoolean("log.project.structure.changes")) {
InspectionsReportConverter reportConverter = ReportConverterUtil.getReportConverter(myOutputFormat);
if (reportConverter != null) {
addRootChangesListener(parentDisposable, reportConverter);
}
}
AtomicReference<Project> projectRef = new AtomicReference<>();
ProgressManager.getInstance().runProcess(
() -> projectRef.set(ProjectUtil.openOrImport(projectPath)),
createProgressIndicator()
);
Project project = projectRef.get();
if (project == null) {
onFailure(InspectionsBundle.message("inspection.application.unable.open.project"));
return null;
}
waitAllStartupActivitiesPassed(project);
MessageBusConnection connection = project.getMessageBus().connect();
connection.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, () -> isMappingLoaded.setResult(null));
Disposer.register(parentDisposable, () -> closeProject(project));
ApplicationManager.getApplication().invokeAndWait(() -> VirtualFileManager.getInstance().refreshWithoutFileWatcher(false));
reportMessage(1, InspectionsBundle.message("inspection.done"));
return project;
}
@VisibleForTesting
public final @Nullable AnalysisScope getAnalysisScope(@NotNull Project project) throws ExecutionException, InterruptedException {
SearchScope scope = getSearchScope(project);
if (scope == null) return null;
return new AnalysisScope(scope, project);
}
protected @Nullable SearchScope getSearchScope(@NotNull Project project) throws ExecutionException, InterruptedException {
if (myScopePattern != null) {
try {
PackageSet packageSet = PackageSetFactory.getInstance().compile(myScopePattern);
NamedScope namedScope = new NamedScope("commandLineScope", AllIcons.Ide.LocalScope, packageSet);
return GlobalSearchScopesCore.filterScope(project, namedScope);
}
catch (ParsingException e) {
LOG.error("Error of scope parsing", e);
gracefulExit();
throw new IllegalStateException("unreachable");
}
}
if (mySourceDirectory != null) {
if (!new File(mySourceDirectory).isAbsolute()) {
mySourceDirectory = new File(myProjectPath, mySourceDirectory).getPath();
}
mySourceDirectory = mySourceDirectory.replace(File.separatorChar, '/');
VirtualFile vfsDir = LocalFileSystem.getInstance().findFileByPath(mySourceDirectory);
if (vfsDir == null) {
reportError(InspectionsBundle.message("inspection.application.directory.cannot.be.found", mySourceDirectory));
printHelpAndExit();
}
return GlobalSearchScopesCore.directoriesScope(project, true, Objects.requireNonNull(vfsDir));
}
String scopeName = System.getProperty("idea.analyze.scope");
NamedScope namedScope = scopeName != null ? NamedScopesHolder.getScope(project, scopeName) : null;
return namedScope != null ? GlobalSearchScopesCore.filterScope(project, namedScope) : GlobalSearchScope.projectScope(project);
}
private static void addRootChangesListener(Disposable parentDisposable, InspectionsReportConverter reportConverter) {
MessageBusConnection applicationBus = ApplicationManager.getApplication().getMessageBus().connect(parentDisposable);
applicationBus.subscribe(ProjectManager.TOPIC, new ProjectManagerListener() {
@Override
public void projectOpened(@NotNull Project project) {
subscribeToRootChanges(project, reportConverter);
}
});
}
private static void subscribeToRootChanges(Project project, InspectionsReportConverter reportConverter) {
Path rootLogDir = Paths.get(PathManager.getLogPath()).resolve("projectStructureChanges");
//noinspection ResultOfMethodCallIgnored
rootLogDir.toFile().mkdirs();
AtomicInteger counter = new AtomicInteger(0);
reportConverter.projectData(project, rootLogDir.resolve("state0"));
MessageBusConnection connection = project.getMessageBus().connect();
connection.subscribe(ModuleRootListener.TOPIC, new ModuleRootListener() {
@Override
public void rootsChanged(@NotNull ModuleRootEvent event) {
updateProjectStructure(counter, reportConverter, project, rootLogDir);
}
});
connection.subscribe(AdditionalLibraryRootsListener.TOPIC,
(__, __1, __2, __3) -> updateProjectStructure(counter, reportConverter, project, rootLogDir));
}
private static void updateProjectStructure(AtomicInteger counter,
InspectionsReportConverter reportConverter,
Project project,
Path rootLogDir) {
int i = counter.incrementAndGet();
reportConverter.projectData(project, rootLogDir.resolve("state" + i));
LOG.info("Project structure update written. Change number " + i);
}
public static List<VirtualFile> getChangedFiles(@NotNull Project project) throws ExecutionException, InterruptedException {
ChangeListManager changeListManager = ChangeListManager.getInstance(project);
CompletableFuture<List<VirtualFile>> future = new CompletableFuture<>();
changeListManager.invokeAfterUpdateWithModal(false, null, () -> {
try {
List<VirtualFile> files = changeListManager.getAffectedFiles();
future.complete(files);
}
catch (Throwable e) {
future.completeExceptionally(e);
}
});
return future.get();
}
private static void waitAllStartupActivitiesPassed(@NotNull Project project) throws InterruptedException, ExecutionException {
ApplicationManager.getApplication().assertIsNonDispatchThread();
LOG.info("Waiting for startup activities");
int timeout = Registry.intValue("batch.inspections.startup.activities.timeout", 180);
try {
FutureKt.asCompletableFuture(StartupManager.getInstance(project).getAllActivitiesPassedFuture()).get(timeout, TimeUnit.MINUTES);
waitForInvokeLaterActivities();
LOG.info("Startup activities finished");
}
catch (TimeoutException e) {
String threads = ThreadDumper.dumpThreadsToString();
throw new RuntimeException(String.format("Cannot process startup activities in %s minutes. ", timeout) +
"You can try to increase batch.inspections.startup.activities.timeout registry value. " +
"Thread dumps\n: " + threads, e);
}
}
private @NotNull GlobalInspectionContextEx createGlobalInspectionContext(Project project) {
InspectionManagerBase im = (InspectionManagerBase)InspectionManager.getInstance(project);
GlobalInspectionContextEx context = (GlobalInspectionContextEx)im.createNewGlobalContext();
context.setExternalProfile(myInspectionProfile);
if (myPathProfiling) {
context.startPathProfiling();
}
im.setProfile(myInspectionProfile.getName());
return context;
}
private void runAnalysisOnScope(Path projectPath,
@NotNull Disposable parentDisposable,
Project project,
InspectionProfileImpl inspectionProfile, AnalysisScope scope)
throws IOException {
reportMessage(1, InspectionsBundle.message("inspection.done"));
if (!myRunWithEditorSettings) {
reportMessage(1, InspectionsBundle.message("inspection.application.chosen.profile.log.message", inspectionProfile.getName()));
}
InspectionsReportConverter reportConverter = ReportConverterUtil.getReportConverter(myOutputFormat);
if (reportConverter == null && myOutputFormat != null && myOutputFormat.endsWith(".xsl")) {
// xslt converter
reportConverter = new XSLTReportConverter(myOutputFormat);
}
Path resultsDataPath;
try {
resultsDataPath = ReportConverterUtil.getResultsDataPath(parentDisposable, reportConverter, myOutPath);
}
catch (IOException e) {
LOG.error(e);
System.err.println("Cannot create tmp directory.");
System.exit(1);
return;
}
runAnalysis(project, projectPath, inspectionProfile, scope, reportConverter, resultsDataPath);
}
public void configureProject(@NotNull Path projectPath, @NotNull Project project, @NotNull AnalysisScope scope) {
for (CommandLineInspectionProjectConfigurator configurator : CommandLineInspectionProjectConfigurator.EP_NAME.getIterable()) {
CommandLineInspectionProjectConfigurator.ConfiguratorContext context = configuratorContext(projectPath, scope);
configurator.preConfigureProject(project, context);
}
for (CommandLineInspectionProjectConfigurator configurator : CommandLineInspectionProjectConfigurator.EP_NAME.getIterable()) {
CommandLineInspectionProjectConfigurator.ConfiguratorContext context = configuratorContext(projectPath, scope);
if (configurator.isApplicable(context)) {
configurator.configureProject(project, context);
}
}
ApplicationManager.getApplication().invokeAndWait(() -> PatchProjectUtil.patchProject(project));
waitForInvokeLaterActivities();
}
private static void waitForInvokeLaterActivities() {
for (int i = 0; i < 3; i++) {
ApplicationManager.getApplication().invokeAndWait(() -> { }, ModalityState.any());
}
}
private void runAnalysis(Project project,
Path projectPath,
InspectionProfileImpl inspectionProfile,
AnalysisScope scope,
InspectionsReportConverter reportConverter,
Path resultsDataPath) throws IOException {
GlobalInspectionContextEx context = createGlobalInspectionContext(project);
if (myAnalyzeChanges) {
AnalysisScope baseScope = scope;
GlobalInspectionContextEx baseContext = createGlobalInspectionContext(project);
scope = runAnalysisOnCodeWithoutChanges(
project,
baseContext,
() -> runUnderProgress(project, projectPath, baseContext, baseScope, resultsDataPath, new ArrayList<>())
);
setupSecondAnalysisHandler(project, context);
}
List<Path> inspectionsResults = new ArrayList<>();
runUnderProgress(project, projectPath, context, scope, resultsDataPath, inspectionsResults);
Path descriptionsFile = resultsDataPath.resolve(InspectionsResultUtil.DESCRIPTIONS + InspectionsResultUtil.XML_EXTENSION);
try {
InspectionsResultUtil.describeInspections(descriptionsFile,
myRunWithEditorSettings ? null : inspectionProfile.getName(),
inspectionProfile);
}
catch (XMLStreamException e) {
throw new IOException(e);
}
inspectionsResults.add(descriptionsFile);
// convert report
if (reportConverter != null) {
try {
List<File> results = ContainerUtil.map(inspectionsResults, Path::toFile);
reportConverter.convert(resultsDataPath.toString(), myOutPath, context.getTools(),
results);
InspectResultsConsumer.runConsumers(context.getTools(), results, project);
if (myOutPath != null) {
reportConverter.projectData(project, Paths.get(myOutPath).resolve(PROJECT_STRUCTURE_DIR));
}
}
catch (InspectionsReportConverter.ConversionException e) {
reportError("\n" + e.getMessage());
printHelpAndExit();
}
}
}
public @NotNull AnalysisScope runAnalysisOnCodeWithoutChanges(Project project,
GlobalInspectionContextEx context,
Runnable analysisRunner) {
VirtualFile[] changes = ChangesUtil.getFilesFromChanges(ChangeListManager.getInstance(project).getAllChanges());
setupFirstAnalysisHandler(context);
DumbService dumbService = DumbService.getInstance(project);
while (dumbService.isDumb()) {
LockSupport.parkNanos(50_000_000);
}
if (ProjectLevelVcsManager.getInstance(project).getAllVcsRoots().length == 0) {
try {
isMappingLoaded.blockingGet(60000);
}
catch (TimeoutException | ExecutionException e) {
onFailure(InspectionsBundle.message("inspection.application.cannot.initialize.vcs.mapping"));
}
}
runAnalysisAfterShelvingSync(
project,
ChangeListManager.getInstance(project).getAffectedFiles(),
createProgressIndicator(),
() -> {
syncProject(project, changes);
analysisRunner.run();
}
);
syncProject(project, changes);
// new added files becomes invalid after unshelving, so we need to update our scope
List<VirtualFile> files = ChangeListManager.getInstance(project).getAffectedFiles();
if (myVerboseLevel == 3) {
for (VirtualFile file : files) {
reportMessage(1, "modified after unshelving: " + file.getPath());
}
}
return new AnalysisScope(project, files);
}
private static void syncProject(Project project, VirtualFile[] changes) {
VfsUtil.markDirtyAndRefresh(false, false, false, changes);
WriteAction.runAndWait(() -> PsiDocumentManager.getInstance(project).commitAllDocuments());
}
private void setupFirstAnalysisHandler(GlobalInspectionContextEx context) {
reportMessage(1, "Running first analysis stage...");
context.setGlobalReportedProblemFilter(
(entity, description) -> {
if (!(entity instanceof RefElement)) return false;
Pair<VirtualFile, Integer> fileAndLine = findFileAndLineByRefElement((RefElement)entity);
if (fileAndLine == null) return false;
originalWarnings.putValue(Pair.create(fileAndLine.first.getPath(), fileAndLine.second), description);
return false;
}
);
context.setReportedProblemFilter(
(element, descriptors) -> {
List<ProblemDescriptorBase> problemDescriptors = ContainerUtil.filterIsInstance(descriptors, ProblemDescriptorBase.class);
if (!problemDescriptors.isEmpty()) {
ProblemDescriptorBase problemDescriptor = problemDescriptors.get(0);
VirtualFile file = problemDescriptor.getContainingFile();
if (file == null) return false;
int lineNumber = problemDescriptor.getLineNumber();
for (ProblemDescriptorBase it : problemDescriptors) {
originalWarnings.putValue(Pair.create(file.getPath(), lineNumber), it.toString());
}
}
return false;
}
);
}
public void setupSecondAnalysisHandler(Project project, GlobalInspectionContextEx context) {
reportMessage(1, "Running second analysis stage...");
printBeforeSecondStageProblems();
ChangeListManager changeListManager = ChangeListManager.getInstance(project);
context.setGlobalReportedProblemFilter(
(entity, description) -> {
if (!(entity instanceof RefElement)) return false;
Pair<VirtualFile, Integer> fileAndLine = findFileAndLineByRefElement((RefElement)entity);
if (fileAndLine == null) return false;
return secondAnalysisFilter(changeListManager, description, fileAndLine.first, fileAndLine.second);
}
);
context.setReportedProblemFilter(
(element, descriptors) -> {
List<ProblemDescriptorBase> any = ContainerUtil.filterIsInstance(descriptors, ProblemDescriptorBase.class);
if (!any.isEmpty()) {
ProblemDescriptorBase problemDescriptor = any.get(0);
String text = problemDescriptor.toString();
VirtualFile file = problemDescriptor.getContainingFile();
if (file == null) return true;
int line = problemDescriptor.getLineNumber();
return secondAnalysisFilter(changeListManager, text, file, line);
}
return true;
}
);
}
private static @Nullable Pair<VirtualFile, Integer> findFileAndLineByRefElement(RefElement refElement) {
PsiElement element = refElement.getPsiElement();
PsiFile psiFile = element.getContainingFile();
if (psiFile == null) return null;
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile == null) return null;
int line = ReadAction.compute(() -> {
Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
return (document == null) ? -1 : document.getLineNumber(element.getTextRange().getStartOffset());
});
return Pair.create(virtualFile, line);
}
private boolean secondAnalysisFilter(ChangeListManager changeListManager, String text, VirtualFile file, int line) {
List<Range> ranges = getOrComputeUnchangedRanges(file, changeListManager);
Range first = ContainerUtil.find(ranges, it -> it.start1 <= line && line < it.end1);
if (first == null) {
logNotFiltered(text, file, line, -1);
return true;
}
int position = first.start2 + line - first.start1;
Collection<String> problems = originalWarnings.get(Pair.create(file.getPath(), position));
if (problems.stream().anyMatch(it -> Objects.equals(it, text))) {
return false;
}
logNotFiltered(text, file, line, position);
return true;
}
private void logNotFiltered(String text, VirtualFile file, int line, int position) {
// unused asks shouldReport not only for warnings.
if (text.contains("unused")) return;
reportMessage(3, "Not filtered:");
reportMessage(3, file.getPath() + ":" + (line + 1) + " Original: " + (position + 1));
reportMessage(3, "\t\t" + text);
}
private void printBeforeSecondStageProblems() {
if (myVerboseLevel == 3) {
reportMessage(3, "Old warnings:");
ArrayList<Map.Entry<Pair<String, Integer>, Collection<String>>> entries = new ArrayList<>(originalWarnings.entrySet());
reportMessage(3, "total size: " + entries.size());
entries.sort(Comparator.comparing((Map.Entry<Pair<String, Integer>, Collection<String>> o) -> o.getKey().first)
.thenComparingInt(o -> o.getKey().second));
for (Map.Entry<Pair<String, Integer>, Collection<String>> entry : entries) {
reportMessage(3, entry.getKey().first + ":" + (entry.getKey().second + 1));
for (String value : entry.getValue()) {
reportMessage(3, "\t\t" + value);
}
}
}
}
private void runUnderProgress(@NotNull Project project,
@NotNull Path projectPath,
@NotNull GlobalInspectionContextEx context,
@NotNull AnalysisScope scope,
@NotNull Path resultsDataPath,
@NotNull List<? super Path> inspectionsResults) {
ProgressManager.getInstance().runProcess(() -> {
configureProject(projectPath, project, scope);
if (!GlobalInspectionContextUtil.canRunInspections(project, false, () -> {
})) {
onFailure(InspectionsBundle.message("inspection.application.cannot.configure.project.to.run.inspections"));
}
context.launchInspectionsOffline(scope, resultsDataPath, myRunGlobalToolsOnly, inspectionsResults);
reportMessage(1, "\n" + InspectionsBundle.message("inspection.capitalized.done") + "\n");
if (!myErrorCodeRequired) {
closeProject(project);
}
}, createProgressIndicator());
}
private @NotNull ProgressIndicatorBase createProgressIndicator() {
return new InspectionProgressIndicator();
}
private static void runAnalysisAfterShelvingSync(Project project, List<? extends VirtualFile> files,
ProgressIndicator progressIndicator, Runnable afterShelve) {
Set<VirtualFile> versionedRoots =
StreamEx.of(files).map(it -> ProjectLevelVcsManager.getInstance(project).getVcsRootFor(it)).nonNull().toSet();
String message = VcsBundle.message("searching.for.code.smells.freezing.process");
VcsPreservingExecutor.executeOperation(project, versionedRoots, message, progressIndicator, afterShelve);
}
public void gracefulExit() {
if (myErrorCodeRequired) {
System.exit(1);
}
else {
throw new RuntimeException("Failed to proceed");
}
}
private static void closeProject(@NotNull Project project) {
ApplicationManager.getApplication().invokeAndWait(() -> {
if (!project.isDisposed()) {
if (Boolean.getBoolean("inspect.save.project.settings")) {
forPoorJavaClientOnlySaveProjectIndEdtDoNotUseThisMethod(project, true);
}
ProjectManagerEx.getInstanceEx().forceCloseProject(project);
}
});
}
private @NotNull InspectionProfileImpl loadInspectionProfile(@NotNull Project project) {
var profileLoader = getInspectionProfileLoader(project);
InspectionProfileImpl profile = profileLoader.tryLoadProfileByNameOrPath(myProfileName, myProfilePath, "command line",
(msg) -> onFailure(msg));
if (profile != null) return profile;
if (myStubProfile != null) {
if (!myRunWithEditorSettings) {
profile = profileLoader.loadProfileByName(myStubProfile);
if (profile != null) return profile;
profile = profileLoader.loadProfileByPath(myStubProfile);
if (profile != null) return profile;
}
}
profile = InspectionProjectProfileManager.getInstance(project).getCurrentProfile();
reportError("Using default project profile");
return profile;
}
public InspectionProfileManager getProfileManager(@NotNull Project project) {
return InspectionProjectProfileManager.getInstance(project);
}
public @NotNull InspectionProfileLoader<? extends InspectionProfileImpl> getInspectionProfileLoader(@NotNull Project project) {
return new InspectionProfileLoaderBase<>(project) {
@Override
public @Nullable InspectionProfileImpl loadProfileByName(@NotNull String profileName) {
InspectionProfileManager.getInstance().getProfiles(); // force init provided profiles
InspectionProjectProfileManager profileManager = InspectionProjectProfileManager.getInstance(project);
InspectionProfileImpl inspectionProfile = profileManager.getProfile(profileName, false);
if (inspectionProfile == null) { // check if the IDE profile is used for the project
for (InspectionProfileImpl profile : profileManager.getProfiles()) {
if (Comparing.strEqual(profile.getName(), profileName)) {
inspectionProfile = profile;
break;
}
}
}
return inspectionProfile;
}
@Override
public @Nullable InspectionProfileImpl loadProfileByPath(@NotNull String profilePath) {
InspectionProfileImpl inspectionProfileFromYaml = tryLoadProfileFromYaml(profilePath,
InspectionToolRegistrar.getInstance(),
(BaseInspectionProfileManager)InspectionProjectProfileManager.getInstance(project));
if (inspectionProfileFromYaml != null) return inspectionProfileFromYaml;
try {
InspectionProfileImpl inspectionProfile = ApplicationInspectionProfileManagerBase.getInstanceBase().loadProfile(profilePath);
if (inspectionProfile != null) {
reportMessage(1, "Loaded the '" + inspectionProfile.getName() + "' profile from the file '" + profilePath + "'");
}
return inspectionProfile;
}
catch (IOException e) {
throw new InspectionApplicationException("Failed to read inspection profile file '" + profilePath + "': " + e);
}
catch (JDOMException e) {
throw new InspectionApplicationException("Invalid xml structure of inspection profile file '" + profilePath + "': " + e);
}
}
};
}
private ConversionListener createConversionListener(StringBuilder errorBuffer) {
return new ConversionListener() {
@Override
public void conversionNeeded() {
reportMessage(1, InspectionsBundle.message("inspection.application.project.has.older.format.and.will.be.converted"));
}
@Override
public void successfullyConverted(@NotNull Path backupDir) {
reportMessage(1, InspectionsBundle.message(
"inspection.application.project.was.successfully.converted.old.project.files.were.saved.to.0",
backupDir.toString()));
}
@Override
public void error(@NotNull String message) {
errorBuffer.append(InspectionsBundle.message("inspection.application.cannot.convert.project.0", message))
.append(System.lineSeparator());
}
@Override
public void cannotWriteToFiles(@NotNull List<? extends Path> readonlyFiles) {
StringBuilder files = new StringBuilder();
for (Path file : readonlyFiles) {
files.append(file.toString()).append("; ");
}
errorBuffer.append(InspectionsBundle
.message("inspection.application.cannot.convert.the.project.the.following.files.are.read.only.0",
files.toString()))
.append(System.lineSeparator());
}
};
}
public static @NotNull String getPrefix(@NotNull String text) {
int idx = text.indexOf(" in ");
if (idx == -1) {
idx = text.indexOf(" of ");
}
return idx == -1 ? text : text.substring(0, idx);
}
public void setVerboseLevel(int verboseLevel) {
myVerboseLevel = verboseLevel;
}
@SuppressWarnings("SameParameterValue")
protected void reportMessageNoLineBreak(int minVerboseLevel, String message) {
if (myVerboseLevel >= minVerboseLevel) {
System.out.print(message);
}
}
public void reportError(@NotNull Throwable e) {
reportError(e.getMessage());
}
public void onFailure(@NotNull String message) {
reportError(message);
gracefulExit();
}
@Override
public void reportError(String message) {
System.err.println(message);
}
@Override
public void reportMessage(int minVerboseLevel, String message) {
if (myVerboseLevel >= minVerboseLevel) {
System.out.println(message);
}
}
private List<Range> getOrComputeUnchangedRanges(@NotNull VirtualFile virtualFile,
@NotNull ChangeListManager changeListManager) {
return diffMap.computeIfAbsent(virtualFile.getPath(), key -> computeDiff(virtualFile, changeListManager));
}
private static List<Range> computeDiff(@NotNull VirtualFile virtualFile,
@NotNull ChangeListManager changeListManager) {
try {
Change change = changeListManager.getChange(virtualFile);
if (change == null) return Collections.emptyList();
ContentRevision revision = change.getBeforeRevision();
if (revision == null) return Collections.emptyList();
String oldContent = revision.getContent();
if (oldContent == null) return Collections.emptyList();
String newContent = VfsUtilCore.loadText(virtualFile);
return ContainerUtil.newArrayList(
RangesBuilder.compareLines(newContent, oldContent, LineOffsetsUtil.create(newContent), LineOffsetsUtil.create(oldContent))
.iterateUnchanged());
}
catch (VcsException | IOException e) {
LOG.error("Couldn't load content", e);
return Collections.emptyList();
}
}
private class InspectionProgressIndicator extends ProgressIndicatorBase implements ProgressIndicatorWithDelayedPresentation {
private String lastPrefix = "";
private int myLastPercent = -1;
private int nestingLevel;
private InspectionProgressIndicator() {
setText("");
}
@Override
public void pushState() {
super.pushState();
nestingLevel++;
}
@Override
public void popState() {
super.popState();
nestingLevel--;
}
@Override
public void setText(String text) {
if (Objects.equals(text, getText())) {
return;
}
super.setText(text);
if (text == null) return;
switch (myVerboseLevel) {
case 0 -> { }
case 1 -> {
String prefix = getPrefix(text);
if (prefix.equals(lastPrefix)) {
reportMessageNoLineBreak(1, ".");
}
else {
lastPrefix = prefix;
reportMessage(1, "");
reportMessage(1, prefix);
}
}
case 2 -> reportMessage(2, text);
case 3 -> {
int percent = (int)(getFraction() * 100);
if (!isIndeterminate() && getFraction() > 0 && myLastPercent != percent && nestingLevel == 0) {
// do not print duplicate "processing xx%"
// do not print nested excessively verbose "Searching for this symbol.... done"
myLastPercent = percent;
String msg = getPrefix(text) + " " + percent + "%";
reportMessage(2, msg);
}
}
}
}
@Override
public void setDelayInMillis(int delayInMillis) {
}
}
}