mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-19 01:00:52 +07:00
Since Kotlin is not used, have to introduce such method to avoid (false, true, false, true, guessWhatIDo)
2716 lines
108 KiB
Java
2716 lines
108 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
package com.intellij.codeInsight.daemon.impl;
|
|
|
|
import com.intellij.application.options.editor.CodeFoldingConfigurable;
|
|
import com.intellij.codeHighlighting.*;
|
|
import com.intellij.codeInsight.EditorInfo;
|
|
import com.intellij.codeInsight.completion.CompletionContributor;
|
|
import com.intellij.codeInsight.daemon.*;
|
|
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase;
|
|
import com.intellij.codeInsight.folding.CodeFoldingManager;
|
|
import com.intellij.codeInsight.folding.JavaCodeFoldingSettings;
|
|
import com.intellij.codeInsight.hint.EditorHintListener;
|
|
import com.intellij.codeInsight.intention.AbstractIntentionAction;
|
|
import com.intellij.codeInsight.intention.IntentionAction;
|
|
import com.intellij.codeInsight.intention.IntentionManager;
|
|
import com.intellij.codeInsight.intention.impl.IntentionHintComponent;
|
|
import com.intellij.codeInspection.*;
|
|
import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstance;
|
|
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
|
|
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
|
|
import com.intellij.codeInspection.ex.InspectionToolRegistrar;
|
|
import com.intellij.codeInspection.ex.InspectionToolWrapper;
|
|
import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
|
|
import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspectionBase;
|
|
import com.intellij.codeInspection.varScopeCanBeNarrowed.FieldCanBeLocalInspection;
|
|
import com.intellij.configurationStore.StorageUtilKt;
|
|
import com.intellij.diagnostic.PerformanceWatcher;
|
|
import com.intellij.diagnostic.ThreadDumper;
|
|
import com.intellij.execution.filters.TextConsoleBuilderFactory;
|
|
import com.intellij.execution.ui.ConsoleView;
|
|
import com.intellij.execution.ui.ConsoleViewContentType;
|
|
import com.intellij.ide.GeneralSettings;
|
|
import com.intellij.ide.SaveAndSyncHandlerImpl;
|
|
import com.intellij.ide.highlighter.JavaFileType;
|
|
import com.intellij.javaee.ExternalResourceManagerExImpl;
|
|
import com.intellij.lang.*;
|
|
import com.intellij.lang.annotation.AnnotationHolder;
|
|
import com.intellij.lang.annotation.Annotator;
|
|
import com.intellij.lang.annotation.ExternalAnnotator;
|
|
import com.intellij.lang.annotation.HighlightSeverity;
|
|
import com.intellij.lang.java.JavaLanguage;
|
|
import com.intellij.openapi.Disposable;
|
|
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
|
import com.intellij.openapi.actionSystem.DataContext;
|
|
import com.intellij.openapi.actionSystem.IdeActions;
|
|
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
|
|
import com.intellij.openapi.application.AccessToken;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.application.Result;
|
|
import com.intellij.openapi.application.WriteAction;
|
|
import com.intellij.openapi.application.ex.ApplicationEx;
|
|
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
|
import com.intellij.openapi.command.CommandProcessor;
|
|
import com.intellij.openapi.command.WriteCommandAction;
|
|
import com.intellij.openapi.command.undo.UndoManager;
|
|
import com.intellij.openapi.components.AbstractProjectComponent;
|
|
import com.intellij.openapi.editor.*;
|
|
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
|
|
import com.intellij.openapi.editor.actionSystem.TypedAction;
|
|
import com.intellij.openapi.editor.ex.EditorEx;
|
|
import com.intellij.openapi.editor.ex.MarkupModelEx;
|
|
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
|
|
import com.intellij.openapi.editor.impl.DocumentMarkupModel;
|
|
import com.intellij.openapi.editor.impl.EditorImpl;
|
|
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
|
|
import com.intellij.openapi.editor.markup.GutterIconRenderer;
|
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
|
|
import com.intellij.openapi.editor.markup.MarkupModel;
|
|
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
|
import com.intellij.openapi.fileEditor.FileEditorManager;
|
|
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
|
|
import com.intellij.openapi.fileEditor.TextEditor;
|
|
import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorProvider;
|
|
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
|
|
import com.intellij.openapi.fileTypes.PlainTextFileType;
|
|
import com.intellij.openapi.fileTypes.StdFileTypes;
|
|
import com.intellij.openapi.module.Module;
|
|
import com.intellij.openapi.progress.ProcessCanceledException;
|
|
import com.intellij.openapi.progress.ProgressIndicator;
|
|
import com.intellij.openapi.progress.impl.CoreProgressManager;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.project.ProjectManager;
|
|
import com.intellij.openapi.project.ex.ProjectManagerEx;
|
|
import com.intellij.openapi.project.impl.ProjectManagerImpl;
|
|
import com.intellij.openapi.util.Disposer;
|
|
import com.intellij.openapi.util.ProperTextRange;
|
|
import com.intellij.openapi.util.Segment;
|
|
import com.intellij.openapi.util.TextRange;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.impl.DebugUtil;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.refactoring.inline.InlineRefactoringActionHandler;
|
|
import com.intellij.refactoring.rename.RenameProcessor;
|
|
import com.intellij.testFramework.*;
|
|
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl;
|
|
import com.intellij.ui.HintListener;
|
|
import com.intellij.ui.LightweightHint;
|
|
import com.intellij.util.*;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.io.storage.HeavyProcessLatch;
|
|
import com.intellij.util.ui.UIUtil;
|
|
import com.intellij.xml.util.CheckDtdReferencesInspection;
|
|
import gnu.trove.THashSet;
|
|
import org.intellij.lang.annotations.Language;
|
|
import org.jetbrains.annotations.Nls;
|
|
import org.jetbrains.annotations.NonNls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.awt.*;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.*;
|
|
import java.util.List;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* @author cdr
|
|
*/
|
|
@SuppressWarnings("StringConcatenationInsideStringBufferAppend")
|
|
@SkipSlowTestLocally
|
|
public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
|
|
private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/typing/";
|
|
|
|
private DaemonCodeAnalyzerImpl myDaemonCodeAnalyzer;
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
enableInspectionTool(new UnusedDeclarationInspection());
|
|
myDaemonCodeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
UndoManager.getInstance(myProject);
|
|
myDaemonCodeAnalyzer.setUpdateByTimerEnabled(true);
|
|
DaemonProgressIndicator.setDebug(true);
|
|
}
|
|
|
|
@Override
|
|
protected void tearDown() throws Exception {
|
|
try {
|
|
Project project = getProject();
|
|
if (project != null) {
|
|
doPostponedFormatting(project);
|
|
((ProjectManagerImpl)ProjectManagerEx.getInstanceEx()).forceCloseProject(project, false);
|
|
}
|
|
}
|
|
finally {
|
|
myDaemonCodeAnalyzer = null;
|
|
super.tearDown();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean doTestLineMarkers() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected void setUpProject() throws Exception {
|
|
super.setUpProject();
|
|
ProjectManagerEx.getInstanceEx().openProject(getProject());
|
|
super.runStartupActivities();
|
|
UIUtil.dispatchAllInvocationEvents(); // startup activities
|
|
}
|
|
|
|
@Override
|
|
protected void runStartupActivities() {
|
|
|
|
}
|
|
|
|
private static void typeInAlienEditor(Editor alienEditor, char c) {
|
|
TypedAction action = EditorActionManager.getInstance().getTypedAction();
|
|
DataContext dataContext = ((EditorEx)alienEditor).getDataContext();
|
|
|
|
action.actionPerformed(alienEditor, c, dataContext);
|
|
}
|
|
|
|
|
|
public void testHighlightersUpdate() throws Exception {
|
|
configureByFile(BASE_PATH + "HighlightersUpdate.java");
|
|
Document document = getDocument(getFile());
|
|
highlightErrors();
|
|
List<HighlightInfo> errors = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.ERROR, getProject());
|
|
assertEquals(1, errors.size());
|
|
TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
|
|
assertNull(dirty);
|
|
|
|
type(' ');
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
|
|
assertNotNull(dirty);
|
|
}
|
|
|
|
|
|
public void testNoPsiEventsAltogether() throws Exception {
|
|
configureByFile(BASE_PATH + "HighlightersUpdate.java");
|
|
Document document = getDocument(getFile());
|
|
highlightErrors();
|
|
type(' ');
|
|
backspace();
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
|
|
TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
|
|
assertEquals(getFile().getTextRange(), dirty); // have to rehighlight whole file in case no PSI events have come
|
|
}
|
|
|
|
public void testRenameClass() throws Exception {
|
|
configureByFile(BASE_PATH + "AClass.java");
|
|
Document document = getDocument(getFile());
|
|
Collection<HighlightInfo> infos = highlightErrors();
|
|
assertEquals(0, infos.size());
|
|
final PsiClass psiClass = ((PsiJavaFile)getFile()).getClasses()[0];
|
|
new RenameProcessor(myProject, psiClass, "Class2", false, false).run();
|
|
TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
|
|
assertEquals(getFile().getTextRange(), dirty);
|
|
|
|
highlightErrors();
|
|
assertTrue(myDaemonCodeAnalyzer.isErrorAnalyzingFinished(getFile()));
|
|
}
|
|
|
|
|
|
public void testTypingSpace() throws Exception {
|
|
configureByFile(BASE_PATH + "AClass.java");
|
|
Document document = getDocument(getFile());
|
|
Collection<HighlightInfo> infos = highlightErrors();
|
|
assertEquals(0, infos.size());
|
|
|
|
type(" ");
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
PsiElement elementAtCaret = myFile.findElementAt(myEditor.getCaretModel().getOffset());
|
|
assertTrue(elementAtCaret instanceof PsiWhiteSpace);
|
|
|
|
TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
|
|
assertEquals(elementAtCaret.getTextRange(), dirty);
|
|
highlightErrors();
|
|
assertTrue(myDaemonCodeAnalyzer.isErrorAnalyzingFinished(getFile()));
|
|
}
|
|
|
|
|
|
public void testTypingSpaceInsideError() throws Exception {
|
|
configureByFile(BASE_PATH + "Error.java");
|
|
Collection<HighlightInfo> infos = highlightErrors();
|
|
assertEquals(1, infos.size());
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
type(" ");
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(1, errors.size());
|
|
}
|
|
}
|
|
|
|
|
|
public void testBackSpaceInsideError() throws Exception {
|
|
configureByFile(BASE_PATH + "BackError.java");
|
|
Collection<HighlightInfo> infos = highlightErrors();
|
|
assertEquals(1, infos.size());
|
|
|
|
backspace();
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(1, errors.size());
|
|
}
|
|
|
|
@Override
|
|
protected LocalInspectionTool[] configureLocalInspectionTools() {
|
|
if (isStressTest() && !getTestName(false).equals("TypingCurliesClearsEndOfFileErrorsInPhp_ItIsPerformanceTestBecauseItRunsTooLong")) {
|
|
// all possible inspections
|
|
List<InspectionToolWrapper> all = InspectionToolRegistrar.getInstance().createTools();
|
|
List<LocalInspectionTool> locals = new ArrayList<>();
|
|
all.stream().filter(tool -> tool instanceof LocalInspectionToolWrapper).forEach(tool -> {
|
|
LocalInspectionTool e = ((LocalInspectionToolWrapper)tool).getTool();
|
|
locals.add(e);
|
|
});
|
|
return locals.toArray(new LocalInspectionTool[locals.size()]);
|
|
}
|
|
return new LocalInspectionTool[]{
|
|
new FieldCanBeLocalInspection(),
|
|
new RequiredAttributesInspectionBase(),
|
|
new CheckDtdReferencesInspection(),
|
|
new AccessStaticViaInstance(),
|
|
};
|
|
}
|
|
|
|
|
|
public void testUnusedFieldUpdate() throws Exception {
|
|
configureByFile(BASE_PATH + "UnusedField.java");
|
|
Document document = getDocument(getFile());
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEquals(1, infos.size());
|
|
assertEquals("Private field 'ffff' is never used", infos.get(0).getDescription());
|
|
|
|
type(" foo(ffff++);");
|
|
highlightErrors();
|
|
|
|
List<HighlightInfo> errors = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.WARNING, getProject());
|
|
assertEquals(0, errors.size());
|
|
}
|
|
|
|
public void testUnusedMethodUpdate() throws Exception {
|
|
configureByText(JavaFileType.INSTANCE, "class X {\n" +
|
|
" static void ffff() {}\n" +
|
|
" public static void main(String[] args){\n" +
|
|
" for (int i=0; i<1000;i++) {\n" +
|
|
" System.out.println(i);\n" +
|
|
" <caret>ffff();\n" +
|
|
" }\n" +
|
|
" }\n}");
|
|
enableInspectionTool(new UnusedDeclarationInspection(true));
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
|
|
PlatformTestUtil.invokeNamedAction(IdeActions.ACTION_COMMENT_LINE);
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
|
|
assertEquals(1, infos.size());
|
|
assertEquals("Method 'ffff()' is never used", infos.get(0).getDescription());
|
|
}
|
|
|
|
|
|
public void testAssignedButUnreadFieldUpdate() throws Exception {
|
|
configureByFile(BASE_PATH + "AssignedButUnreadField.java");
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEquals(1, infos.size());
|
|
assertEquals("Private field 'text' is assigned but never accessed", infos.get(0).getDescription());
|
|
|
|
ctrlW();
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> EditorModificationUtil.deleteSelectedText(getEditor()));
|
|
type(" text");
|
|
|
|
List<HighlightInfo> errors = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(getFile().getText(), errors);
|
|
}
|
|
|
|
public void testDaemonIgnoresNonPhysicalEditor() throws Exception {
|
|
configureByFile(BASE_PATH + "AClass.java");
|
|
highlightErrors();
|
|
|
|
EditorFactory editorFactory = EditorFactory.getInstance();
|
|
final Document consoleDoc = editorFactory.createDocument("my console blah");
|
|
final Editor consoleEditor = editorFactory.createEditor(consoleDoc);
|
|
|
|
try {
|
|
checkDaemonReaction(false, () -> caretRight(consoleEditor));
|
|
checkDaemonReaction(true, () -> typeInAlienEditor(consoleEditor, 'x'));
|
|
checkDaemonReaction(true, () -> LightPlatformCodeInsightTestCase.backspace(consoleEditor, getProject()));
|
|
|
|
//real editor
|
|
checkDaemonReaction(true, this::caretRight);
|
|
}
|
|
finally {
|
|
editorFactory.releaseEditor(consoleEditor);
|
|
}
|
|
}
|
|
|
|
|
|
public void testDaemonIgnoresConsoleActivities() throws Exception {
|
|
configureByFile(BASE_PATH + "AClass.java");
|
|
doHighlighting(HighlightSeverity.WARNING);
|
|
|
|
final ConsoleView consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(getProject()).getConsole();
|
|
|
|
consoleView.getComponent(); //create editor
|
|
consoleView.print("haha", ConsoleViewContentType.NORMAL_OUTPUT);
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
|
|
try {
|
|
checkDaemonReaction(false, () -> {
|
|
consoleView.clear();
|
|
try {
|
|
Thread.sleep(300); // *&^ing alarm
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
UIUtil.dispatchAllInvocationEvents(); //flush
|
|
});
|
|
checkDaemonReaction(false, () -> {
|
|
consoleView.print("sss", ConsoleViewContentType.NORMAL_OUTPUT);
|
|
try {
|
|
Thread.sleep(300); // *&^ing alarm
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
UIUtil.dispatchAllInvocationEvents(); //flush
|
|
});
|
|
checkDaemonReaction(false, () -> {
|
|
consoleView.setOutputPaused(true);
|
|
try {
|
|
Thread.sleep(300); // *&^ing alarm
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
UIUtil.dispatchAllInvocationEvents(); //flush
|
|
});
|
|
}
|
|
finally {
|
|
Disposer.dispose(consoleView);
|
|
}
|
|
}
|
|
|
|
private void checkDaemonReaction(boolean mustCancelItself, @NotNull final Runnable action) {
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
highlightErrors();
|
|
myDaemonCodeAnalyzer.waitForTermination();
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
|
|
|
|
final AtomicBoolean run = new AtomicBoolean();
|
|
Disposable disposable = Disposer.newDisposable();
|
|
final AtomicReference<RuntimeException> stopDaemonReason = new AtomicReference<>();
|
|
StorageUtilKt.setDEBUG_LOG("");
|
|
getProject().getMessageBus().connect(disposable).subscribe(DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC,
|
|
new DaemonCodeAnalyzer.DaemonListenerAdapter() {
|
|
@Override
|
|
public void daemonCancelEventOccurred(@NotNull String reason) {
|
|
RuntimeException e = new RuntimeException("Some bastard's restarted daemon: " + reason +
|
|
"\nStorage write log: ----------\n" +
|
|
StorageUtilKt.getDEBUG_LOG() + "\n--------------");
|
|
stopDaemonReason.compareAndSet(null, e);
|
|
}
|
|
});
|
|
try {
|
|
while (true) {
|
|
try {
|
|
myDaemonCodeAnalyzer.runPasses(getFile(), getDocument(getFile()), textEditor, new int[0], true, () -> {
|
|
if (!run.getAndSet(true)) {
|
|
action.run();
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
catch (ProcessCanceledException ignored) { }
|
|
}
|
|
|
|
if (mustCancelItself) {
|
|
assertNotNull(stopDaemonReason.get());
|
|
}
|
|
else {
|
|
if (stopDaemonReason.get() != null) throw stopDaemonReason.get();
|
|
}
|
|
}
|
|
finally {
|
|
StorageUtilKt.setDEBUG_LOG(null);
|
|
Disposer.dispose(disposable);
|
|
}
|
|
}
|
|
|
|
|
|
public void testWholeFileInspection() throws Exception {
|
|
configureByFile(BASE_PATH + "FieldCanBeLocal.java");
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEquals(1, infos.size());
|
|
assertEquals("Field can be converted to a local variable", infos.get(0).getDescription());
|
|
|
|
ctrlW();
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> EditorModificationUtil.deleteSelectedText(getEditor()));
|
|
type("xxxx");
|
|
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
|
|
ctrlW();
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> EditorModificationUtil.deleteSelectedText(getEditor()));
|
|
type("0");
|
|
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEquals(1, infos.size());
|
|
assertEquals("Field can be converted to a local variable", infos.get(0).getDescription());
|
|
}
|
|
|
|
private static class MyWholeInspection extends LocalInspectionTool {
|
|
private final List<PsiElement> visited = Collections.synchronizedList(new ArrayList<>());
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getGroupDisplayName() {
|
|
return "fegna";
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getDisplayName() {
|
|
return getGroupDisplayName();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public String getShortName() {
|
|
return getGroupDisplayName();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
|
return new PsiElementVisitor() {
|
|
@Override
|
|
public void visitFile(PsiFile file) {
|
|
TimeoutUtil.sleep(1000); // make it run longer that LIP
|
|
super.visitFile(file);
|
|
}
|
|
|
|
@Override
|
|
public void visitElement(PsiElement element) {
|
|
visited.add(element);
|
|
super.visitElement(element);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public boolean runForWholeFile() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public void testWholeFileInspectionRestartedOnAllElements() throws Exception {
|
|
MyWholeInspection tool = new MyWholeInspection();
|
|
enableInspectionTool(tool);
|
|
disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
|
|
|
|
configureByText(JavaFileType.INSTANCE, "class X { void f() { <caret> } }");
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
int visitedCount = new HashSet<>(tool.visited).size();
|
|
tool.visited.clear();
|
|
|
|
type(" "); // white space modification
|
|
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
|
|
int countAfter = new HashSet<>(tool.visited).size();
|
|
assertTrue("visitedCount = "+visitedCount+"; countAfter="+countAfter, countAfter >= visitedCount);
|
|
}
|
|
|
|
public void testWholeFileInspectionRestartedEvenIfThereWasAModificationInsideCodeBlockInOtherFile() throws Exception {
|
|
MyWholeInspection tool = new MyWholeInspection();
|
|
|
|
enableInspectionTool(tool);
|
|
disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
|
|
|
|
PsiFile file = configureByText(JavaFileType.INSTANCE, "class X { void f() { <caret> } }");
|
|
PsiFile otherFile = createFile(myModule, file.getContainingDirectory().getVirtualFile(), "otherFile.txt", "xxx");
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
int visitedCount = tool.visited.size();
|
|
assertTrue(tool.visited.toString(), visitedCount > 0);
|
|
tool.visited.clear();
|
|
|
|
Document otherDocument = PsiDocumentManager.getInstance(getProject()).getDocument(otherFile);
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> otherDocument.setText("zzz"));
|
|
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
|
|
int countAfter = tool.visited.size();
|
|
assertTrue(tool.visited.toString(), countAfter > 0);
|
|
tool.visited.clear();
|
|
|
|
//ensure started on another file
|
|
configureByExistingFile(otherFile.getVirtualFile());
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEmpty(infos);
|
|
|
|
int countAfter2 = tool.visited.size();
|
|
assertTrue(tool.visited.toString(), countAfter2 > 0);
|
|
}
|
|
|
|
public void testOverriddenMethodMarkers() throws Exception {
|
|
configureByFile(BASE_PATH + getTestName(false) + ".java");
|
|
highlightErrors();
|
|
|
|
Document document = getEditor().getDocument();
|
|
List<LineMarkerInfo> markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
|
|
assertEquals(3, markers.size());
|
|
|
|
type("//xxxx");
|
|
|
|
highlightErrors();
|
|
markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
|
|
assertEquals(3, markers.size());
|
|
}
|
|
|
|
|
|
public void testOverriddenMethodMarkersDoNotClearedByChangingWhitespaceNearby() throws Exception {
|
|
configureByFile(BASE_PATH + "OverriddenMethodMarkers.java");
|
|
highlightErrors();
|
|
|
|
Document document = getEditor().getDocument();
|
|
List<LineMarkerInfo> markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
|
|
assertEquals(markers.toString(), 3, markers.size());
|
|
|
|
PsiElement element = ((PsiJavaFile)myFile).getClasses()[0].findMethodsByName("f", false)[0].getReturnTypeElement().getNextSibling();
|
|
assertEquals(" ", element.getText());
|
|
getEditor().getCaretModel().moveToOffset(element.getTextOffset() + 1);
|
|
type(" ");
|
|
|
|
highlightErrors();
|
|
markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
|
|
assertEquals(markers.toString(), 3, markers.size());
|
|
}
|
|
|
|
|
|
public void testChangeXmlIncludeLeadsToRehighlight() throws Exception {
|
|
LanguageFilter[] extensions = ((CompositeLanguage)StdLanguages.XML).getLanguageExtensions();
|
|
for (LanguageFilter extension : extensions) {
|
|
((CompositeLanguage)StdLanguages.XML).unregisterLanguageExtension(extension);
|
|
}
|
|
|
|
final String location = getTestName(false) + ".xsd";
|
|
final String url = "http://myschema/";
|
|
ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
|
|
|
|
configureByFiles(null, BASE_PATH + getTestName(false) + ".xml", BASE_PATH + getTestName(false) + ".xsd");
|
|
|
|
Collection<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(0, errors.size());
|
|
|
|
Editor[] allEditors = EditorFactory.getInstance().getAllEditors();
|
|
Editor schemaEditor = null;
|
|
for (Editor editor : allEditors) {
|
|
Document document = editor.getDocument();
|
|
PsiFile file = PsiDocumentManager.getInstance(getProject()).getPsiFile(document);
|
|
if (file == null) continue;
|
|
if (location.equals(file.getName())) {
|
|
schemaEditor = editor;
|
|
break;
|
|
}
|
|
}
|
|
delete(schemaEditor);
|
|
|
|
errors = highlightErrors();
|
|
assertFalse(errors.isEmpty());
|
|
|
|
for (LanguageFilter extension : extensions) {
|
|
((CompositeLanguage)StdLanguages.XML).registerLanguageExtension(extension);
|
|
}
|
|
}
|
|
|
|
|
|
public void testRehighlightInnerBlockAfterInline() throws Exception {
|
|
configureByFile(BASE_PATH + getTestName(false) + ".java");
|
|
|
|
Collection<HighlightInfo> errors = highlightErrors();
|
|
HighlightInfo error = assertOneElement(errors);
|
|
assertEquals("Variable 'e' is already defined in the scope", error.getDescription());
|
|
PsiElement element = getFile().findElementAt(getEditor().getCaretModel().getOffset()).getParent();
|
|
|
|
DataContext dataContext = SimpleDataContext.getSimpleContext(CommonDataKeys.PSI_ELEMENT.getName(), element, ((EditorEx)getEditor()).getDataContext());
|
|
new InlineRefactoringActionHandler().invoke(getProject(), getEditor(), getFile(), dataContext);
|
|
|
|
Collection<HighlightInfo> afterTyping = highlightErrors();
|
|
assertEmpty(afterTyping);
|
|
}
|
|
|
|
|
|
public void testRangeMarkersDoNotGetAddedOrRemovedWhenUserIsJustTypingInsideHighlightedRegionAndEspeciallyInsideInjectedFragmentsWhichAreColoredGreenAndUsersComplainEndlesslyThatEditorFlickersThere()
|
|
throws Throwable {
|
|
configureByText(JavaFileType.INSTANCE, "class S { int f() {\n" +
|
|
" return <caret>hashCode();\n" +
|
|
"}}");
|
|
|
|
Collection<HighlightInfo> infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
assertEquals(3, infos.size());
|
|
|
|
final int[] count = {0};
|
|
MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
|
|
modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
|
|
@Override
|
|
public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
|
|
count[0]++;
|
|
}
|
|
|
|
@Override
|
|
public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
|
|
count[0]++;
|
|
}
|
|
});
|
|
|
|
type(' ');
|
|
highlightErrors();
|
|
|
|
assertEquals(0, count[0]);
|
|
}
|
|
|
|
public void testLineMarkersReuse() throws Throwable {
|
|
configureByFile(BASE_PATH + "LineMarkerChange.java");
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
|
|
List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
assertSize(5, lineMarkers);
|
|
|
|
type('X');
|
|
|
|
final Collection<String> changed = new ArrayList<>();
|
|
MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
|
|
modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener() {
|
|
@Override
|
|
public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
|
|
changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("after added")));
|
|
}
|
|
|
|
@Override
|
|
public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
|
|
changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("before removed")));
|
|
}
|
|
|
|
@Override
|
|
public void attributesChanged(@NotNull RangeHighlighterEx highlighter, boolean renderersChanged, boolean fontStyleChanged) {
|
|
changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("changed")));
|
|
}
|
|
|
|
private void changed(@NotNull RangeHighlighterEx highlighter, String reason) {
|
|
if (highlighter.getTargetArea() != HighlighterTargetArea.LINES_IN_RANGE) return; // not line marker
|
|
List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
if (ContainerUtil.find(lineMarkers, lm -> lm.highlighter == highlighter) == null) return; // not line marker
|
|
|
|
changed.add(highlighter+": \n"+reason);
|
|
}
|
|
});
|
|
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
List<HighlightInfo> infosAfter = CodeInsightTestFixtureImpl.instantiateAndRun(myFile, myEditor, new int[]{/*Pass.UPDATE_ALL, Pass.LOCAL_INSPECTIONS*/}, false);
|
|
assertNotEmpty(filter(infosAfter, HighlightSeverity.ERROR));
|
|
|
|
assertEmpty(changed);
|
|
List<LineMarkerInfo> lineMarkersAfter = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
assertEquals(lineMarkersAfter.size(), lineMarkers.size());
|
|
}
|
|
|
|
public void testLineMarkersDoNotBlinkOnBackSpaceRightBeforeMethodIdentifier() throws Throwable {
|
|
configureByText(JavaFileType.INSTANCE, "package x; \n" +
|
|
"class <caret>ToRun{\n" +
|
|
" public static void main(String[] args) {\n"+
|
|
" }\n"+
|
|
"}");
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
|
|
List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
assertSize(2, lineMarkers);
|
|
|
|
backspace();
|
|
|
|
final Collection<String> changed = new ArrayList<>();
|
|
MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
|
|
modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener() {
|
|
@Override
|
|
public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
|
|
changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("after added")));
|
|
}
|
|
|
|
@Override
|
|
public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
|
|
changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("before removed")));
|
|
}
|
|
|
|
@Override
|
|
public void attributesChanged(@NotNull RangeHighlighterEx highlighter, boolean renderersChanged, boolean fontStyleChanged) {
|
|
changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("changed")));
|
|
}
|
|
|
|
private void changed(@NotNull RangeHighlighterEx highlighter, String reason) {
|
|
if (highlighter.getTargetArea() != HighlighterTargetArea.LINES_IN_RANGE) return; // not line marker
|
|
List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
if (ContainerUtil.find(lineMarkers, lm -> lm.highlighter == highlighter) == null) return; // not line marker
|
|
|
|
changed.add(highlighter+": \n"+reason);
|
|
}
|
|
});
|
|
|
|
assertEmpty(highlightErrors());
|
|
|
|
assertSize(2, DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject()));
|
|
|
|
assertEmpty(changed);
|
|
}
|
|
|
|
public void testTypeParametersMustNotBlinkWhenTypingInsideClass() throws Throwable {
|
|
configureByText(JavaFileType.INSTANCE, "package x; \n" +
|
|
"abstract class ToRun<TTTTTTTTTTTTTTT> implements Comparable<TTTTTTTTTTTTTTT> {\n" +
|
|
" private ToRun<TTTTTTTTTTTTTTT> delegate;\n"+
|
|
" <caret>\n"+
|
|
" \n"+
|
|
"}");
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
|
|
MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
|
|
modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
|
|
@Override
|
|
public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
|
|
if (TextRange.create(highlighter).substring(highlighter.getDocument().getText()).equals("TTTTTTTTTTTTTTT")) {
|
|
throw new RuntimeException("Must not remove type parameter highlighter");
|
|
}
|
|
}
|
|
});
|
|
|
|
assertEmpty(highlightErrors());
|
|
|
|
type("//xxx");
|
|
assertEmpty(highlightErrors());
|
|
backspace();
|
|
assertEmpty(highlightErrors());
|
|
backspace();
|
|
assertEmpty(highlightErrors());
|
|
backspace();
|
|
assertEmpty(highlightErrors());
|
|
backspace();
|
|
backspace();
|
|
assertEmpty(highlightErrors());
|
|
}
|
|
|
|
public void testLocallyUsedFieldHighlighting() {
|
|
configureByText(JavaFileType.INSTANCE, "class A {\n" +
|
|
" String cons;\n" +
|
|
" void foo() {\n" +
|
|
" String local = null;\n" +
|
|
" <selection>cons</selection>.substring(1);" +
|
|
" }\n" +
|
|
" public static void main(String[] args) {\n" +
|
|
" new A().foo();\n" +
|
|
" }" +
|
|
"}");
|
|
enableInspectionTool(new UnusedDeclarationInspection(true));
|
|
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertSize(1, infos);
|
|
assertEquals("Variable 'local' is never used", infos.get(0).getDescription());
|
|
|
|
type("local");
|
|
|
|
infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertSize(1, infos);
|
|
assertEquals("Field 'cons' is never used", infos.get(0).getDescription());
|
|
}
|
|
|
|
public void testOverrideMethodsHighlightingPersistWhenTypeInsideMethodBody() throws Throwable {
|
|
configureByText(JavaFileType.INSTANCE, "package x; \n" +
|
|
"class ClassA {\n" +
|
|
" static <T> void sayHello(Class<? extends T> msg) {}\n" +
|
|
"}\n" +
|
|
|
|
"class ClassB extends ClassA {\n" +
|
|
" static <T extends String> void sayHello(Class<? extends T> msg) {<caret>\n" +
|
|
" }\n" +
|
|
"}\n");
|
|
|
|
assertSize(1, highlightErrors());
|
|
type("//my comment inside method body, so class modifier won't be visited");
|
|
assertSize(1, highlightErrors());
|
|
}
|
|
|
|
public void testLineMarkersClearWhenTypingAtTheEndOfPsiComment() throws Throwable {
|
|
configureByText(JavaFileType.INSTANCE, "class S {\n//ddd<caret>\n}");
|
|
StringBuffer log = new StringBuffer();
|
|
final LineMarkerProvider provider = new LineMarkerProvider() {
|
|
@Nullable
|
|
@Override
|
|
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
|
|
String msg = "provider.getLineMarkerInfo(" + element + ") called\n";
|
|
LineMarkerInfo<PsiComment> info = null;
|
|
if (element instanceof PsiComment) {
|
|
info = new LineMarkerInfo<>((PsiComment)element, element.getTextRange(), null, Pass.LINE_MARKERS, null, null, GutterIconRenderer.Alignment.LEFT);
|
|
msg += " provider info: "+info + "\n";
|
|
}
|
|
log.append(msg);
|
|
return info;
|
|
}
|
|
|
|
@Override
|
|
public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
|
|
|
|
}
|
|
};
|
|
LineMarkerProviders.INSTANCE.addExplicitExtension(JavaLanguage.INSTANCE, provider);
|
|
Disposer.register(getTestRootDisposable(), () -> LineMarkerProviders.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, provider));
|
|
myDaemonCodeAnalyzer.restart();
|
|
try {
|
|
TextRange range = FileStatusMap.getDirtyTextRange(getEditor(), Pass.UPDATE_ALL);
|
|
log.append("FileStatusMap.getDirtyTextRange: " + range+"\n");
|
|
List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(getFile(), range.getStartOffset(), range.getEndOffset());
|
|
log.append("CollectHighlightsUtil.getElementsInRange" + range + ": " + elements.size() +" elements : "+ elements+"\n");
|
|
List<HighlightInfo> infos = doHighlighting();
|
|
log.append(" File text: '" + getFile().getText() + "'\n");
|
|
log.append("infos: " + infos + "\n");
|
|
assertEmpty(filter(infos,HighlightSeverity.ERROR));
|
|
|
|
List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
assertOneElement(lineMarkers);
|
|
|
|
type(' ');
|
|
infos = doHighlighting();
|
|
log.append("File text: '" + getFile().getText() + "'\n");
|
|
log.append("infos: " + infos + "\n");
|
|
assertEmpty(filter(infos,HighlightSeverity.ERROR));
|
|
|
|
lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
assertOneElement(lineMarkers);
|
|
|
|
backspace();
|
|
infos = doHighlighting();
|
|
log.append("File text: '" + getFile().getText() + "'\n");
|
|
log.append("infos: " + infos + "\n");
|
|
assertEmpty(filter(infos,HighlightSeverity.ERROR));
|
|
|
|
lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
|
|
assertOneElement(lineMarkers);
|
|
}
|
|
catch (AssertionError e) {
|
|
System.err.println("Log:\n"+log+"\n---");
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
public void testWhenTypingOverWrongReferenceItsColorChangesToBlackAndOnlyAfterHighlightingFinishedItReturnsToRed() throws Throwable {
|
|
configureByText(StdFileTypes.JAVA, "class S { int f() {\n" +
|
|
" return asfsdfsdfsd<caret>;\n" +
|
|
"}}");
|
|
|
|
Collection<HighlightInfo> errors = highlightErrors();
|
|
assertOneElement(errors);
|
|
assertSame(HighlightInfoType.WRONG_REF, errors.iterator().next().type);
|
|
|
|
Document document = getDocument(getFile());
|
|
|
|
type("xxx");
|
|
|
|
List<HighlightInfo> infos = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightInfoType.SYMBOL_TYPE_SEVERITY, getProject());
|
|
for (HighlightInfo info : infos) {
|
|
assertNotSame(HighlightInfoType.WRONG_REF, info.type);
|
|
}
|
|
|
|
errors = highlightErrors();
|
|
assertOneElement(errors);
|
|
assertSame(HighlightInfoType.WRONG_REF, errors.iterator().next().type);
|
|
}
|
|
|
|
|
|
public void testQuickFixRemainsAvailableAfterAnotherFixHasBeenAppliedInTheSameCodeBlockBefore() throws Exception {
|
|
configureByFile(BASE_PATH + "QuickFixes.java");
|
|
|
|
DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
|
|
boolean old = settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST;
|
|
settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST = true;
|
|
|
|
try {
|
|
Collection<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(3, errors.size());
|
|
new GotoNextErrorHandler(true).invoke(getProject(), getEditor(), getFile());
|
|
|
|
List<IntentionAction> fixes = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
|
|
IntentionAction fix = assertContainsOneOf(fixes, "Delete catch for 'java.io.IOException'");
|
|
|
|
final IntentionAction finalFix = fix;
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> finalFix.invoke(getProject(), getEditor(), getFile()));
|
|
|
|
errors = highlightErrors();
|
|
assertEquals(2, errors.size());
|
|
|
|
new GotoNextErrorHandler(true).invoke(getProject(), getEditor(), getFile());
|
|
fixes = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
|
|
fix = assertContainsOneOf(fixes, "Delete catch for 'java.io.IOException'");
|
|
|
|
final IntentionAction finalFix1 = fix;
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> finalFix1.invoke(getProject(), getEditor(), getFile()));
|
|
|
|
errors = highlightErrors();
|
|
assertOneElement(errors);
|
|
|
|
new GotoNextErrorHandler(true).invoke(getProject(), getEditor(), getFile());
|
|
fixes = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
|
|
fix = assertContainsOneOf(fixes, "Delete catch for 'java.io.IOException'");
|
|
|
|
final IntentionAction finalFix2 = fix;
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> finalFix2.invoke(getProject(), getEditor(), getFile()));
|
|
|
|
errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
}
|
|
finally {
|
|
settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST = old;
|
|
}
|
|
}
|
|
|
|
private static IntentionAction assertContainsOneOf(@NotNull Collection<IntentionAction> collection, @NotNull String text) {
|
|
IntentionAction result = null;
|
|
for (IntentionAction action : collection) {
|
|
if (text.equals(action.getText())) {
|
|
if (result != null) {
|
|
fail("multiple " + " objects present in collection " + collection);
|
|
}
|
|
else {
|
|
result = action;
|
|
}
|
|
}
|
|
}
|
|
assertNotNull(" object not found in collection " + collection, result);
|
|
return result;
|
|
}
|
|
|
|
|
|
public void testRangeHighlightersDoNotGetStuckForever() throws Throwable {
|
|
configureByText(StdFileTypes.JAVA, "class S { void ffffff() {fff<caret>fff();}}");
|
|
|
|
List<HighlightInfo> infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
MarkupModel markup = DocumentMarkupModel.forDocument(myEditor.getDocument(), getProject(), true);
|
|
final TextRange[] highlightersBefore = getHighlightersTextRange(markup);
|
|
|
|
type("%%%%");
|
|
highlightErrors();
|
|
backspace();
|
|
backspace();
|
|
backspace();
|
|
backspace();
|
|
infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
|
|
final TextRange[] highlightersAfter = getHighlightersTextRange(markup);
|
|
|
|
assertEquals(highlightersBefore.length, highlightersAfter.length);
|
|
for (int i = 0; i < highlightersBefore.length; i++) {
|
|
TextRange before = highlightersBefore[i];
|
|
TextRange after = highlightersAfter[i];
|
|
assertEquals(before.getStartOffset(), after.getStartOffset());
|
|
assertEquals(before.getEndOffset(), after.getEndOffset());
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
private static TextRange[] getHighlightersTextRange(@NotNull MarkupModel markup) {
|
|
final RangeHighlighter[] highlighters = markup.getAllHighlighters();
|
|
|
|
final TextRange[] result = new TextRange[highlighters.length];
|
|
for (int i = 0; i < highlighters.length; i++) {
|
|
result[i] = ProperTextRange.create(highlighters[i]);
|
|
}
|
|
return orderByHashCode(result); // markup.getAllHighlighters returns unordered array
|
|
}
|
|
|
|
@NotNull
|
|
private static <T extends Segment> T[] orderByHashCode(@NotNull T[] highlighters) {
|
|
Arrays.sort(highlighters, (o1, o2) -> o2.hashCode() - o1.hashCode());
|
|
return highlighters;
|
|
}
|
|
|
|
public void testFileStatusMapDirtyCachingWorks() throws Throwable {
|
|
myDaemonCodeAnalyzer.setUpdateByTimerEnabled(false); // to prevent auto-start highlighting
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
configureByText(StdFileTypes.JAVA, "class <caret>S { int ffffff = 0;}");
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
|
|
final int[] creation = {0};
|
|
class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
|
|
private Fac(Project project) {
|
|
super(project);
|
|
}
|
|
|
|
@Override
|
|
public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
|
|
TextRange textRange = FileStatusMap.getDirtyTextRange(editor, Pass.UPDATE_ALL);
|
|
if (textRange == null) return null;
|
|
return new MyPass(myProject);
|
|
}
|
|
|
|
class MyPass extends TextEditorHighlightingPass {
|
|
private MyPass(final Project project) {
|
|
super(project, getEditor().getDocument(), false);
|
|
creation[0]++;
|
|
}
|
|
|
|
@Override
|
|
public void doCollectInformation(@NotNull ProgressIndicator progress) {
|
|
}
|
|
|
|
@Override
|
|
public void doApplyInformationToEditor() {
|
|
}
|
|
}
|
|
}
|
|
TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
|
|
registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
|
|
highlightErrors();
|
|
assertEquals(1, creation[0]);
|
|
|
|
//cached
|
|
highlightErrors();
|
|
assertEquals(1, creation[0]);
|
|
highlightErrors();
|
|
assertEquals(1, creation[0]);
|
|
|
|
type(' ');
|
|
highlightErrors();
|
|
assertEquals(2, creation[0]);
|
|
highlightErrors();
|
|
assertEquals(2, creation[0]);
|
|
highlightErrors();
|
|
assertEquals(2, creation[0]);
|
|
}
|
|
|
|
|
|
public void testDefensivelyDirtyFlagDoesNotClearPrematurely() throws Throwable {
|
|
class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
|
|
private Fac(Project project) {
|
|
super(project);
|
|
}
|
|
|
|
@Override
|
|
public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
|
|
return null;
|
|
}
|
|
}
|
|
TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
|
|
registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
|
|
|
|
configureByText(StdFileTypes.JAVA, "@Deprecated<caret> class S { } ");
|
|
|
|
List<HighlightInfo> infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
assertEquals(2, infos.size());
|
|
|
|
assertEquals("@Deprecated", infos.get(0).getText());
|
|
assertEquals("S", infos.get(1).getText());
|
|
|
|
backspace();
|
|
type('d');
|
|
|
|
List<HighlightInfo> after = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
|
|
assertEquals("@Deprecated", after.get(0).getText());
|
|
assertEquals("S", after.get(1).getText());
|
|
|
|
backspace();
|
|
type('d');
|
|
|
|
getEditor().getCaretModel().moveToOffset(getEditor().getDocument().getTextLength());
|
|
type(" ");
|
|
|
|
after = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
assertEquals(2, after.size());
|
|
|
|
assertEquals("@Deprecated", after.get(0).getText());
|
|
assertEquals("S", after.get(1).getText());
|
|
}
|
|
|
|
|
|
public void testModificationInsideCodeblockDoesnotAffectErrorMarkersOutside() throws Exception {
|
|
configureByFile(BASE_PATH + "ErrorMark.java");
|
|
List<HighlightInfo> errs = highlightErrors();
|
|
assertEquals(1, errs.size());
|
|
assertEquals("'}' expected", errs.get(0).getDescription());
|
|
|
|
type("//comment");
|
|
errs = highlightErrors();
|
|
assertEquals(1, errs.size());
|
|
assertEquals("'}' expected", errs.get(0).getDescription());
|
|
}
|
|
|
|
public void testErrorMarkerAtTheEndOfTheFile() throws Exception {
|
|
CommandProcessor.getInstance().executeCommand(getProject(), () -> {
|
|
try {
|
|
configureByFile(BASE_PATH + "ErrorMarkAtEnd.java");
|
|
}
|
|
catch (Exception e) {
|
|
LOG.error(e);
|
|
}
|
|
}, "cc", this);
|
|
List<HighlightInfo> errs = highlightErrors();
|
|
assertEmpty(errs);
|
|
CommandProcessor.getInstance().executeCommand(getProject(), () -> {
|
|
Document document = getEditor().getDocument();
|
|
int offset = getEditor().getCaretModel().getOffset();
|
|
while (offset < document.getTextLength()) {
|
|
int i = StringUtil.indexOf(document.getText(), '}', offset, document.getTextLength());
|
|
if (i == -1) break;
|
|
getEditor().getCaretModel().moveToOffset(i);
|
|
delete(getEditor());
|
|
}
|
|
}, "my", this);
|
|
|
|
errs = highlightErrors();
|
|
assertEquals(2, errs.size());
|
|
assertEquals("'}' expected", errs.get(0).getDescription());
|
|
|
|
undo();
|
|
errs = highlightErrors();
|
|
assertEmpty(errs);
|
|
}
|
|
|
|
|
|
// disabled for now
|
|
public void _testSOEInEndlessAppendChainPerformance() throws Throwable {
|
|
StringBuilder text = new StringBuilder("class S { String ffffff = new StringBuilder()\n");
|
|
for (int i=0; i<2000; i++) {
|
|
text.append(".append(").append(i).append(")\n");
|
|
}
|
|
text.append(".toString();<caret>}");
|
|
configureByText(StdFileTypes.JAVA, text.toString());
|
|
|
|
PlatformTestUtil.startPerformanceTest("too many tree visitors", 30000, () -> {
|
|
List<HighlightInfo> infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
type("kjhgas");
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertFalse(errors.isEmpty());
|
|
backspace();
|
|
backspace();
|
|
backspace();
|
|
backspace();
|
|
backspace();
|
|
backspace();
|
|
infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
}).useLegacyScaling().assertTiming();
|
|
}
|
|
|
|
|
|
public void testBulbAppearsAfterType() throws Throwable {
|
|
String text = "class S { ArrayList<caret>XXX x;}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
|
|
|
|
final Set<LightweightHint> shown = ContainerUtil.newIdentityTroveSet();
|
|
getProject().getMessageBus().connect().subscribe(EditorHintListener.TOPIC, (project, hint, flags) -> {
|
|
shown.add(hint);
|
|
hint.addHintListener(event -> shown.remove(hint));
|
|
});
|
|
|
|
DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
highlightErrors();
|
|
|
|
IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint();
|
|
assertNotNull(hintComponent);
|
|
assertFalse(hintComponent.isDisposed());
|
|
assertNotNull(hintComponent.getComponentHint());
|
|
assertTrue(shown.contains(hintComponent.getComponentHint()));
|
|
|
|
type("x");
|
|
highlightErrors();
|
|
hintComponent = codeAnalyzer.getLastIntentionHint();
|
|
assertNotNull(hintComponent);
|
|
assertFalse(hintComponent.isDisposed());
|
|
assertNotNull(hintComponent.getComponentHint());
|
|
assertTrue(shown.contains(hintComponent.getComponentHint()));
|
|
}
|
|
|
|
@Override
|
|
protected void configureByExistingFile(@NotNull VirtualFile virtualFile) {
|
|
super.configureByExistingFile(virtualFile);
|
|
EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
|
|
editorTracker.setActiveEditors(Collections.singletonList(getEditor()));
|
|
}
|
|
|
|
@Override
|
|
protected VirtualFile configureByFiles(@Nullable File rawProjectRoot, @NotNull VirtualFile... vFiles) throws IOException {
|
|
VirtualFile file = super.configureByFiles(rawProjectRoot, vFiles);
|
|
EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
|
|
editorTracker.setActiveEditors(Collections.singletonList(getEditor()));
|
|
return file;
|
|
}
|
|
|
|
public void testDaemonIgnoresFrameDeactivation() throws Throwable {
|
|
// return default value to avoid unnecessary save
|
|
DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
|
|
|
|
String text = "class S { ArrayList<caret>XXX x;}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
highlightErrors();
|
|
|
|
GeneralSettings settings = GeneralSettings.getInstance();
|
|
ApplicationEx application = ApplicationManagerEx.getApplicationEx();
|
|
boolean frameSave = settings.isSaveOnFrameDeactivation();
|
|
boolean appSave = application.isDoNotSave();
|
|
|
|
settings.setSaveOnFrameDeactivation(true);
|
|
application.doNotSave(false);
|
|
try {
|
|
SaveAndSyncHandlerImpl.doSaveDocumentsAndProjectsAndApp();
|
|
|
|
checkDaemonReaction(false, SaveAndSyncHandlerImpl::doSaveDocumentsAndProjectsAndApp);
|
|
}
|
|
finally {
|
|
application.doNotSave(appSave);
|
|
settings.setSaveOnFrameDeactivation(frameSave);
|
|
}
|
|
}
|
|
|
|
public void testApplyLocalQuickFix() throws Throwable {
|
|
configureByText(StdFileTypes.JAVA, "class X { static int sss; public int f() { return this.<caret>sss; }}");
|
|
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
|
|
|
|
List<HighlightInfo> warns = doHighlighting(HighlightSeverity.WARNING);
|
|
assertOneElement(warns);
|
|
List<HighlightInfo.IntentionActionDescriptor> actions = ShowIntentionsPass.getAvailableFixes(getEditor(), getFile(), -1);
|
|
final HighlightInfo.IntentionActionDescriptor descriptor = assertOneElement(actions);
|
|
CodeInsightTestFixtureImpl.invokeIntention(descriptor.getAction(), getFile(), getEditor(), "");
|
|
|
|
highlightErrors();
|
|
assertEmpty(ShowIntentionsPass.getAvailableFixes(getEditor(), getFile(), -1));
|
|
}
|
|
|
|
|
|
public void testApplyErrorInTheMiddle() throws Throwable {
|
|
String text = "class <caret>X { ";
|
|
for (int i = 0; i < 100; i++) {
|
|
text += "\n {\n" +
|
|
"// String x = \"<zzzzzzzzzz/>\";\n" +
|
|
" }";
|
|
}
|
|
text += "\n}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
|
|
|
|
List<HighlightInfo> warns = highlightErrors();
|
|
assertEmpty(warns);
|
|
|
|
type("//");
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(2, errors.size());
|
|
|
|
backspace();
|
|
backspace();
|
|
|
|
errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
}
|
|
|
|
|
|
public void testErrorInTheEndOutsideVisibleArea() throws Throwable {
|
|
String text = "<xml> \n" + StringUtil.repeatSymbol('\n', 1000) + "</xml>\nxxxxx<caret>";
|
|
configureByText(StdFileTypes.XML, text);
|
|
|
|
ProperTextRange visibleRange = makeEditorWindowVisible(new Point(0, 1000), myEditor);
|
|
assertTrue(visibleRange.getStartOffset() > 0);
|
|
|
|
List<HighlightInfo> warns = highlightErrors();
|
|
HighlightInfo info = assertOneElement(warns);
|
|
assertEquals("Top level element is not completed", info.getDescription());
|
|
|
|
type("xxx");
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
info = assertOneElement(errors);
|
|
assertEquals("Top level element is not completed", info.getDescription());
|
|
}
|
|
|
|
public static ProperTextRange makeEditorWindowVisible(Point viewPosition, Editor editor) {
|
|
((EditorImpl)editor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
|
|
|
|
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
|
|
((EditorImpl)editor).getScrollPane().getViewport().setViewPosition(viewPosition);
|
|
((EditorImpl)editor).getScrollPane().getViewport().setExtentSize(new Dimension(100, ((EditorImpl)editor).getPreferredHeight() -
|
|
viewPosition.y));
|
|
return VisibleHighlightingPassFactory.calculateVisibleRange(editor);
|
|
}
|
|
|
|
|
|
public void testEnterInCodeBlock() throws Throwable {
|
|
String text = "class LQF {\n" +
|
|
" int wwwwwwwwwwww;\n" +
|
|
" public void main() {<caret>\n" +
|
|
" wwwwwwwwwwww = 1;\n" +
|
|
" }\n" +
|
|
"}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
|
|
List<HighlightInfo> infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
assertEquals(4, infos.size());
|
|
|
|
type('\n');
|
|
infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
assertEquals(4, infos.size());
|
|
|
|
deleteLine();
|
|
|
|
infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
|
|
assertEquals(4, infos.size());
|
|
}
|
|
|
|
|
|
public void testTypingNearEmptyErrorElement() throws Throwable {
|
|
String text = "class LQF {\n" +
|
|
" public void main() {\n" +
|
|
" int wwwwwwwwwwww = 1<caret>\n" +
|
|
" }\n" +
|
|
"}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
|
|
List<HighlightInfo> infos = highlightErrors();
|
|
assertEquals(1, infos.size());
|
|
|
|
type(';');
|
|
infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
}
|
|
|
|
|
|
public void testLIPGetAllParentsAfterCodeBlockModification() throws Throwable {
|
|
@Language("JAVA")
|
|
String text = "class LQF {\n" +
|
|
" int f;\n" +
|
|
" public void me() {\n" +
|
|
" //<caret>\n" +
|
|
" }\n" +
|
|
"}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
|
|
final List<PsiElement> visitedElements = Collections.synchronizedList(new ArrayList<>());
|
|
class MyVisitor extends PsiElementVisitor {
|
|
@Override
|
|
public void visitElement(PsiElement element) {
|
|
visitedElements.add(element);
|
|
}
|
|
}
|
|
final LocalInspectionTool tool = new LocalInspectionTool() {
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getGroupDisplayName() {
|
|
return "fegna";
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getDisplayName() {
|
|
return getGroupDisplayName();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public String getShortName() {
|
|
return getGroupDisplayName();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
|
return new MyVisitor();
|
|
}
|
|
};
|
|
disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
|
|
enableInspectionTool(tool);
|
|
|
|
List<HighlightInfo> infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
|
|
List<PsiElement> allPsi = CollectHighlightsUtil.getElementsInRange(myFile, 0, myFile.getTextLength());
|
|
assertEquals(new HashSet<>(allPsi), new HashSet<>(visitedElements));
|
|
|
|
// inside code block modification
|
|
visitedElements.clear();
|
|
backspace();
|
|
backspace();
|
|
|
|
infos = highlightErrors();
|
|
assertEmpty(infos);
|
|
|
|
PsiMethod method = ((PsiJavaFile)myFile).getClasses()[0].getMethods()[0];
|
|
List<PsiElement> methodAndParents =
|
|
CollectHighlightsUtil.getElementsInRange(myFile, method.getTextRange().getStartOffset(), method.getTextRange().getEndOffset(), true);
|
|
assertEquals(new HashSet<>(methodAndParents), new HashSet<>(visitedElements));
|
|
}
|
|
|
|
|
|
public void testCancelsItSelfOnTypingInAlienProject() throws Throwable {
|
|
String body = StringUtil.repeat("\"String field = null;\"\n", 1000);
|
|
configureByText(StdFileTypes.JAVA, "class X{ void f() {" + body + "<caret>\n} }");
|
|
|
|
File temp = createTempDirectory();
|
|
final Project alienProject = createProject(temp + "/alien.ipr", DebugUtil.currentStackTrace());
|
|
boolean succ2 = ProjectManagerEx.getInstanceEx().openProject(alienProject);
|
|
assertTrue(succ2);
|
|
DaemonProgressIndicator.setDebug(true);
|
|
|
|
try {
|
|
Module alienModule = doCreateRealModuleIn("x", alienProject, getModuleType());
|
|
final VirtualFile alienRoot = PsiTestUtil.createTestProjectStructure(alienProject, alienModule, myFilesToDelete);
|
|
OpenFileDescriptor alienDescriptor = new WriteAction<OpenFileDescriptor>() {
|
|
@Override
|
|
protected void run(@NotNull Result<OpenFileDescriptor> result) throws Throwable {
|
|
VirtualFile alienFile = alienRoot.createChildData(this, "X.java");
|
|
setFileText(alienFile, "class Alien { }");
|
|
OpenFileDescriptor alienDescriptor = new OpenFileDescriptor(alienProject, alienFile);
|
|
result.setResult(alienDescriptor);
|
|
}
|
|
}.execute().throwException().getResultObject();
|
|
|
|
FileEditorManager fe = FileEditorManager.getInstance(alienProject);
|
|
final Editor alienEditor = fe.openTextEditor(alienDescriptor, false);
|
|
((EditorImpl)alienEditor).setCaretActive();
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
PsiDocumentManager.getInstance(alienProject).commitAllDocuments();
|
|
|
|
// start daemon in main project. should check for its cancel when typing in alien
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
|
|
DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
final boolean[] checked = {false};
|
|
di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> {
|
|
if (checked[0]) return;
|
|
checked[0] = true;
|
|
typeInAlienEditor(alienEditor, 'x');
|
|
});
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
//DaemonProgressIndicator.setDebug(true);
|
|
//System.out.println("indicator = " + indicator[0]);
|
|
return;
|
|
}
|
|
finally {
|
|
ProjectManagerEx.getInstanceEx().closeAndDispose(alienProject);
|
|
}
|
|
fail("must throw PCE");
|
|
}
|
|
|
|
public void testPasteInAnonymousCodeBlock() throws Throwable {
|
|
configureByText(StdFileTypes.JAVA, "class X{ void f() {" +
|
|
" int x=0;\n" +
|
|
" Runnable r = new Runnable() { public void run() {\n" +
|
|
" <caret>\n" +
|
|
" }};\n" +
|
|
" <selection>int y = x;</selection>\n " +
|
|
"\n} }");
|
|
assertEmpty(highlightErrors());
|
|
PlatformTestUtil.invokeNamedAction(IdeActions.ACTION_EDITOR_COPY);
|
|
assertEquals("int y = x;", getEditor().getSelectionModel().getSelectedText());
|
|
getEditor().getSelectionModel().removeSelection();
|
|
PlatformTestUtil.invokeNamedAction(IdeActions.ACTION_EDITOR_PASTE);
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(1, errors.size());
|
|
}
|
|
|
|
public void testReactivityPerformance() throws Throwable {
|
|
@NonNls String filePath = "/psi/resolve/Thinlet.java";
|
|
configureByFile(filePath);
|
|
type(' ');
|
|
CompletionContributor.forLanguage(getFile().getLanguage());
|
|
highlightErrors();
|
|
|
|
final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, false));
|
|
System.out.println("N = " + N);
|
|
final long[] interruptTimes = new long[N];
|
|
for (int i = 0; i < N; i++) {
|
|
codeAnalyzer.restart();
|
|
final int finalI = i;
|
|
final long start = System.currentTimeMillis();
|
|
final AtomicLong typingStart = new AtomicLong();
|
|
final AtomicReference<RuntimeException> exception = new AtomicReference<>();
|
|
Thread watcher = new Thread("reactivity watcher") {
|
|
@Override
|
|
public void run() {
|
|
while (true) {
|
|
final long start1 = typingStart.get();
|
|
if (start1 == -1) break;
|
|
if (start1 == 0) {
|
|
try {
|
|
Thread.sleep(5);
|
|
}
|
|
catch (InterruptedException e1) {
|
|
throw new RuntimeException(e1);
|
|
}
|
|
continue;
|
|
}
|
|
long elapsed = System.currentTimeMillis() - start1;
|
|
if (elapsed > 500) {
|
|
// too long, see WTF
|
|
String message = "Too long interrupt: " + elapsed +
|
|
"; Progress: " + codeAnalyzer.getUpdateProgress() +
|
|
"\n----------------------------";
|
|
dumpThreadsToConsole();
|
|
exception.set(new RuntimeException(message));
|
|
throw exception.get();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
try {
|
|
PsiFile file = getFile();
|
|
Editor editor = getEditor();
|
|
Project project = file.getProject();
|
|
CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
watcher.start();
|
|
Runnable interrupt = () -> {
|
|
long now = System.currentTimeMillis();
|
|
if (now - start < 100) {
|
|
// wait to engage all highlighting threads
|
|
return;
|
|
}
|
|
typingStart.set(System.currentTimeMillis());
|
|
type(' ');
|
|
long end = System.currentTimeMillis();
|
|
long interruptTime = end - now;
|
|
interruptTimes[finalI] = interruptTime;
|
|
assertTrue(codeAnalyzer.getUpdateProgress().isCanceled());
|
|
System.out.println(interruptTime);
|
|
throw new ProcessCanceledException();
|
|
};
|
|
long hiStart = System.currentTimeMillis();
|
|
codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt);
|
|
long hiEnd = System.currentTimeMillis();
|
|
DaemonProgressIndicator progress = codeAnalyzer.getUpdateProgress();
|
|
String message = "Should have been interrupted: " + progress + "; Elapsed: " + (hiEnd - hiStart) + "ms";
|
|
dumpThreadsToConsole();
|
|
throw new RuntimeException(message);
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
}
|
|
finally {
|
|
typingStart.set(-1); // cancel watcher
|
|
watcher.join();
|
|
if (exception.get() != null) {
|
|
throw exception.get();
|
|
}
|
|
}
|
|
}
|
|
|
|
long ave = ArrayUtil.averageAmongMedians(interruptTimes, 3);
|
|
System.out.println("Average among the N/3 median times: " + ave + "ms");
|
|
assertTrue(ave < 300);
|
|
}
|
|
|
|
private static void dumpThreadsToConsole() {
|
|
System.err.println("----all threads---");
|
|
for (Thread thread : Thread.getAllStackTraces().keySet()) {
|
|
|
|
boolean canceled = CoreProgressManager.isCanceledThread(thread);
|
|
if (canceled) {
|
|
System.err.println("Thread " + thread + " indicator is canceled");
|
|
}
|
|
}
|
|
PerformanceWatcher.dumpThreadsToConsole("");
|
|
System.err.println("----///////---");
|
|
}
|
|
|
|
public void testTypingLatencyPerformance() throws Throwable {
|
|
@NonNls String filePath = "/psi/resolve/ThinletBig.java";
|
|
|
|
configureByFile(filePath);
|
|
|
|
type(' ');
|
|
CompletionContributor.forLanguage(getFile().getLanguage());
|
|
long s = System.currentTimeMillis();
|
|
highlightErrors();
|
|
long e = System.currentTimeMillis();
|
|
//System.out.println("Hi elapsed: "+(e-s));
|
|
|
|
//List<String> dumps = new ArrayList<>();
|
|
|
|
final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, false));
|
|
System.out.println("N = " + N);
|
|
final long[] interruptTimes = new long[N];
|
|
for (int i = 0; i < N; i++) {
|
|
codeAnalyzer.restart();
|
|
final int finalI = i;
|
|
final long start = System.currentTimeMillis();
|
|
Runnable interrupt = () -> {
|
|
long now = System.currentTimeMillis();
|
|
if (now - start < 100) {
|
|
// wait to engage all highlighting threads
|
|
return;
|
|
}
|
|
// uncomment to debug what's causing pauses
|
|
/*
|
|
AtomicBoolean finished = new AtomicBoolean();
|
|
AppExecutorUtil.getAppScheduledExecutorService().schedule(() -> {
|
|
if (!finished.get()) {
|
|
dumps.add(ThreadDumper.dumpThreadsToString());
|
|
}
|
|
}, 10, TimeUnit.MILLISECONDS);
|
|
*/
|
|
type(' ');
|
|
long end = System.currentTimeMillis();
|
|
//finished.set(true);
|
|
long interruptTime = end - now;
|
|
interruptTimes[finalI] = interruptTime;
|
|
assertTrue(codeAnalyzer.getUpdateProgress().isCanceled());
|
|
throw new ProcessCanceledException();
|
|
};
|
|
try {
|
|
PsiFile file = getFile();
|
|
Editor editor = getEditor();
|
|
Project project = file.getProject();
|
|
CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt);
|
|
|
|
throw new RuntimeException("should have been interrupted");
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
}
|
|
backspace();
|
|
//highlightErrors();
|
|
}
|
|
|
|
System.out.println("Interrupt times: " + Arrays.toString(interruptTimes));
|
|
|
|
/*
|
|
for (String dump : dumps) {
|
|
System.out.println("\n\n-----------------------------\n\n" + dump);
|
|
}
|
|
*/
|
|
|
|
long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3);
|
|
long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length;
|
|
long max = Arrays.stream(interruptTimes).max().getAsLong();
|
|
long min = Arrays.stream(interruptTimes).min().getAsLong();
|
|
System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg);
|
|
assertTrue(String.valueOf(mean), mean < 10);
|
|
}
|
|
|
|
@NotNull
|
|
private static Collection<LocalInspectionTool> getInspectionsFor(@NotNull com.intellij.lang.Language language) {
|
|
List<InspectionToolWrapper> allInspections = InspectionToolRegistrar.getInstance().createTools();
|
|
Collection<LocalInspectionTool> filtered = allInspections.stream().
|
|
map(wrapper -> wrapper instanceof LocalInspectionToolWrapper ? (LocalInspectionToolWrapper)wrapper :
|
|
//wrapper instanceof GlobalInspectionToolWrapper
|
|
//? ((GlobalInspectionToolWrapper)wrapper).getSharedLocalInspectionToolWrapper() :
|
|
null)
|
|
.filter(wrapper -> wrapper != null && wrapper.isApplicable(language))
|
|
.map(InspectionToolWrapper::getTool)
|
|
.collect(Collectors.toSet());
|
|
System.out.println("All inspections: " + allInspections.size() + "; filtered for " + language+": "+filtered.size());
|
|
return filtered;
|
|
}
|
|
|
|
/*
|
|
private void inspect(LocalInspectionToolWrapper wrapper) {
|
|
PsiFile file = getFile();
|
|
TextRange range = file.getTextRange();
|
|
|
|
List<Divider.DividedElements> allDivided = new ArrayList<>();
|
|
Divider.divideInsideAndOutsideAllRoots(file, range, range, Conditions.alwaysTrue(), new CommonProcessors.CollectProcessor<>(allDivided));
|
|
|
|
List<PsiElement> elements = ContainerUtil.concat(
|
|
(List<List<PsiElement>>)ContainerUtil.map(allDivided, d -> ContainerUtil.concat(d.inside, d.outside, d.parents)));
|
|
|
|
PsiErrorElement errorElement = ContainerUtil.findInstance(elements, PsiErrorElement.class);
|
|
assertNull(file.getText(), errorElement);
|
|
|
|
Set<String> elementDialectIds = InspectionEngine.calcElementDialectIds(elements);
|
|
|
|
//List<HighlightInfo> errors = DaemonAnalyzerTestCase.filter(doHighlighting(), HighlightSeverity.ERROR);// warmup
|
|
//assertEmpty(errors);
|
|
|
|
timeInspection(wrapper, elements, elementDialectIds); // warmup
|
|
}
|
|
|
|
private int timeInspection(@NotNull LocalInspectionToolWrapper wrapper, @NotNull List<PsiElement> elements, @NotNull Set<String> elementDialectIds) {
|
|
long start = System.currentTimeMillis();
|
|
InspectionEngine.inspectElements(Collections.singletonList(wrapper), getFile(), InspectionManager.getInstance(getProject()), true, true, new EmptyProgressIndicator(), elements,
|
|
elementDialectIds);
|
|
return (int)(System.currentTimeMillis() - start);
|
|
}
|
|
|
|
public void testTypingLatencyForAllInspectionsPerformance() throws Throwable {
|
|
@NonNls String filePath = "/psi/resolve/ThinletBig.java";
|
|
|
|
//Collection<LocalInspectionTool> tools = getInspectionsFor(JavaLanguage.INSTANCE);
|
|
//enableInspectionTools(tools.toArray(new InspectionProfileEntry[tools.size()]));
|
|
|
|
configureByFile(filePath);
|
|
|
|
//type(' ');
|
|
//CompletionContributor.forLanguage(getFile().getLanguage());
|
|
//long s = System.currentTimeMillis();
|
|
//highlightErrors();
|
|
//long e = System.currentTimeMillis();
|
|
//System.out.println("Hi elapsed: "+(e-s));
|
|
|
|
final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, false));
|
|
System.out.println("N = " + N);
|
|
final long[] interruptTimes = new long[N];
|
|
LossyEncodingInspection encodingInspection = new LossyEncodingInspection();
|
|
LocalInspectionToolWrapper wrapper = new LocalInspectionToolWrapper(encodingInspection);
|
|
codeAnalyzer.setUpdateByTimerEnabled(false);
|
|
((PsiDocumentManagerBase)PsiDocumentManager.getInstance(getProject())).disableBackgroundCommit(getTestRootDisposable());
|
|
|
|
// position caret away from line start to disable smart backspace heuristics which does commit document
|
|
int offset = getEditor().getDocument().getText().indexOf("{ ")+1;
|
|
getEditor().getCaretModel().moveToOffset(offset);
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
type(' ');
|
|
|
|
System.out.println("i = " + i);
|
|
//codeAnalyzer.restart();
|
|
//final int finalI = i;
|
|
//final long start = System.currentTimeMillis();
|
|
try {
|
|
//PsiFile file = getFile();
|
|
//Editor editor = getEditor();
|
|
//Project project = file.getProject();
|
|
//CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
|
|
//TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
|
//PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
|
|
inspect(wrapper);
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
}
|
|
backspace();
|
|
}
|
|
|
|
//long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3);
|
|
//long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length;
|
|
//long max = Arrays.stream(interruptTimes).max().getAsLong();
|
|
//long min = Arrays.stream(interruptTimes).min().getAsLong();
|
|
//System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg);
|
|
//assertTrue(mean < 10);
|
|
}
|
|
|
|
public void testResolveAllAfterTypingLatencyPerformance() throws Throwable {
|
|
@NonNls String filePath = "/psi/resolve/ThinletBig.java";
|
|
|
|
//Collection<LocalInspectionTool> tools = getInspectionsFor(JavaLanguage.INSTANCE);
|
|
//enableInspectionTools(tools.toArray(new InspectionProfileEntry[tools.size()]));
|
|
|
|
configureByFile(filePath);
|
|
|
|
//type(' ');
|
|
//CompletionContributor.forLanguage(getFile().getLanguage());
|
|
//long s = System.currentTimeMillis();
|
|
//highlightErrors();
|
|
//long e = System.currentTimeMillis();
|
|
//System.out.println("Hi elapsed: "+(e-s));
|
|
|
|
final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, false));
|
|
System.out.println("N = " + N);
|
|
final long[] interruptTimes = new long[N];
|
|
LossyEncodingInspection encodingInspection = new LossyEncodingInspection();
|
|
LocalInspectionToolWrapper wrapper = new LocalInspectionToolWrapper(encodingInspection);
|
|
codeAnalyzer.setUpdateByTimerEnabled(false);
|
|
((PsiDocumentManagerBase)PsiDocumentManager.getInstance(getProject())).disableBackgroundCommit(getTestRootDisposable());
|
|
|
|
// position caret away from line start to disable smart backspace heuristics which does commit document
|
|
int offset = getEditor().getDocument().getText().indexOf("{ ")+1;
|
|
getEditor().getCaretModel().moveToOffset(offset);
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
type(' ');
|
|
|
|
System.out.println("i = " + i);
|
|
//codeAnalyzer.restart();
|
|
//final int finalI = i;
|
|
//final long start = System.currentTimeMillis();
|
|
try {
|
|
//PsiFile file = getFile();
|
|
//Editor editor = getEditor();
|
|
//Project project = file.getProject();
|
|
//CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
|
|
//TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
|
//PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
//getFile().accept(new PsiRecursiveElementWalkingVisitor() {
|
|
// @Override
|
|
// public void visitElement(PsiElement element) {
|
|
// for (PsiReference reference : element.getReferences()) {
|
|
// reference.resolve();
|
|
// }
|
|
// super.visitElement(element);
|
|
// }
|
|
//});
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
}
|
|
backspace();
|
|
}
|
|
|
|
//long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3);
|
|
//long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length;
|
|
//long max = Arrays.stream(interruptTimes).max().getAsLong();
|
|
//long min = Arrays.stream(interruptTimes).min().getAsLong();
|
|
//System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg);
|
|
//assertTrue(mean < 10);
|
|
}
|
|
*/
|
|
|
|
private static void startCPUProfiling() {
|
|
try {
|
|
Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
|
|
Method method = ReflectionUtil.getDeclaredMethod(aClass, "startCPUProfiling");
|
|
method.invoke(null);
|
|
}
|
|
catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
private static void stopCPUProfiling() {
|
|
try {
|
|
Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
|
|
Method method = ReflectionUtil.getDeclaredMethod(aClass, "stopCPUProfiling");
|
|
method.invoke(null);
|
|
}
|
|
catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static String captureCPUSnapshot() {
|
|
try {
|
|
Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
|
|
Method method = ReflectionUtil.getDeclaredMethod(aClass, "captureCPUSnapshot");
|
|
return (String)method.invoke(null);
|
|
}
|
|
catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public void testPostHighlightingPassRunsOnEveryPsiModification() throws Exception {
|
|
PsiFile x = createFile("X.java", "public class X { public static void ffffffffffffff(){} }");
|
|
PsiFile use = createFile("Use.java", "public class Use { { <caret>X.ffffffffffffff(); } }");
|
|
configureByExistingFile(use.getVirtualFile());
|
|
|
|
InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
|
|
HighlightDisplayKey myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspectionBase.SHORT_NAME);
|
|
if (myDeadCodeKey == null) {
|
|
myDeadCodeKey = HighlightDisplayKey.register(UnusedDeclarationInspectionBase.SHORT_NAME, UnusedDeclarationInspectionBase.DISPLAY_NAME);
|
|
}
|
|
UnusedDeclarationInspectionBase myDeadCodeInspection = new UnusedDeclarationInspectionBase(true);
|
|
enableInspectionTool(myDeadCodeInspection);
|
|
assert profile.isToolEnabled(myDeadCodeKey, myFile);
|
|
|
|
Editor xEditor = createEditor(x.getVirtualFile());
|
|
List<HighlightInfo> xInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(x, xEditor, new int[0], false),
|
|
HighlightSeverity.WARNING);
|
|
HighlightInfo info = ContainerUtil.find(xInfos, xInfo -> xInfo.getDescription().equals("Method 'ffffffffffffff()' is never used"));
|
|
assertNull(xInfos.toString(), info);
|
|
|
|
Editor useEditor = myEditor;
|
|
List<HighlightInfo> useInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(use, useEditor, new int[0], false), HighlightSeverity.ERROR);
|
|
assertEmpty(useInfos);
|
|
|
|
type('/');
|
|
type('/');
|
|
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
xInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(x, xEditor, new int[0], false), HighlightSeverity.WARNING);
|
|
info = ContainerUtil.find(xInfos, xInfo -> xInfo.getDescription().equals("Method 'ffffffffffffff()' is never used"));
|
|
assertNotNull(xInfos.toString(), info);
|
|
}
|
|
|
|
|
|
public void testErrorDisappearsRightAfterTypingInsideVisibleAreaWhileDaemonContinuesToChugAlong() throws Throwable {
|
|
String text = "class X{\nint xxx;\n{\nint i = <selection>null</selection><caret>;\n" + StringUtil.repeat("{ this.hashCode(); }\n\n\n", 10000) + "}}";
|
|
configureByText(StdFileTypes.JAVA, text);
|
|
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(100, 100);
|
|
DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
|
|
|
|
myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setViewPosition(new Point(0, 0));
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setExtentSize(new Dimension(100, 100000));
|
|
ProperTextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(getEditor());
|
|
assertTrue(visibleRange.getLength() > 0);
|
|
final Document document = myEditor.getDocument();
|
|
assertTrue(visibleRange.getLength() < document.getTextLength());
|
|
|
|
List<HighlightInfo> err1 = highlightErrors();
|
|
HighlightInfo info = assertOneElement(err1);
|
|
final String errorDescription = "Incompatible types. Found: 'null', required: 'int'";
|
|
assertEquals(errorDescription, info.getDescription());
|
|
|
|
MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, false);
|
|
final boolean[] errorRemoved = {false};
|
|
|
|
model.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
|
|
@Override
|
|
public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
|
|
Object tt = highlighter.getErrorStripeTooltip();
|
|
if (!(tt instanceof HighlightInfo)) return;
|
|
String description = ((HighlightInfo)tt).getDescription();
|
|
if (errorDescription.equals(description)) {
|
|
errorRemoved[0] = true;
|
|
|
|
List<TextEditorHighlightingPass> passes = myDaemonCodeAnalyzer.getPassesToShowProgressFor(document);
|
|
GeneralHighlightingPass ghp = null;
|
|
for (TextEditorHighlightingPass pass : passes) {
|
|
if (pass instanceof GeneralHighlightingPass && pass.getId() == Pass.UPDATE_ALL) {
|
|
assert ghp == null : ghp;
|
|
ghp = (GeneralHighlightingPass)pass;
|
|
}
|
|
}
|
|
assertNotNull(ghp);
|
|
boolean finished = ghp.isFinished();
|
|
assertFalse(finished);
|
|
}
|
|
}
|
|
});
|
|
type("1");
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
assertTrue(errorRemoved[0]);
|
|
}
|
|
|
|
public void testDaemonWorksForDefaultProjectSinceItIsNeededInSettingsDialogForSomeReason() {
|
|
assertNotNull(DaemonCodeAnalyzer.getInstance(ProjectManager.getInstance().getDefaultProject()));
|
|
}
|
|
|
|
public void testChangeEventsAreNotAlwaysGeneric() throws Exception {
|
|
String body = "class X {\n" +
|
|
"<caret> @org.PPP\n" +
|
|
" void dtoArrayDouble() {\n" +
|
|
" }\n" +
|
|
"}";
|
|
configureByText(JavaFileType.INSTANCE, body);
|
|
makeEditorWindowVisible(new Point(), myEditor);
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertFalse(errors.isEmpty());
|
|
|
|
type("//");
|
|
errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
|
|
backspace();
|
|
backspace();
|
|
errors = highlightErrors();
|
|
assertFalse(errors.isEmpty());
|
|
}
|
|
|
|
public void testInterruptOnTyping() throws Throwable {
|
|
@NonNls String filePath = "/psi/resolve/Thinlet.java";
|
|
configureByFile(filePath);
|
|
highlightErrors();
|
|
|
|
final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
codeAnalyzer.restart();
|
|
try {
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
|
|
PsiFile file = getFile();
|
|
Editor editor = getEditor();
|
|
Project project = file.getProject();
|
|
CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
|
codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> type(' '));
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
return;
|
|
}
|
|
fail("PCE must have been thrown");
|
|
}
|
|
|
|
public void testAllPassesFinishAfterInterruptOnTyping_Performance() throws Throwable {
|
|
@NonNls String filePath = "/psi/resolve/Thinlet.java";
|
|
configureByFile(filePath);
|
|
highlightErrors();
|
|
|
|
final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
type(' ');
|
|
for (int i=0; i<100; i++) {
|
|
backspace();
|
|
codeAnalyzer.restart();
|
|
try {
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
|
|
PsiFile file = getFile();
|
|
Editor editor = getEditor();
|
|
Project project = file.getProject();
|
|
CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
|
|
codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> type(' '));
|
|
}
|
|
catch (ProcessCanceledException ignored) {
|
|
codeAnalyzer.waitForTermination();
|
|
continue;
|
|
}
|
|
fail("PCE must have been thrown");
|
|
}
|
|
}
|
|
|
|
public void testCodeFoldingInSplittedWindowAppliesToAllEditors() throws Exception {
|
|
final Set<Editor> applied = new THashSet<>();
|
|
final Set<Editor> collected = new THashSet<>();
|
|
registerFakePass(applied, collected);
|
|
|
|
configureByText(PlainTextFileType.INSTANCE, "");
|
|
Editor editor1 = getEditor();
|
|
final Editor editor2 = EditorFactory.getInstance().createEditor(editor1.getDocument(),getProject());
|
|
Disposer.register(getProject(), () -> EditorFactory.getInstance().releaseEditor(editor2));
|
|
TextEditor textEditor1 = new PsiAwareTextEditorProvider().getTextEditor(editor1);
|
|
TextEditor textEditor2 = new PsiAwareTextEditorProvider().getTextEditor(editor2);
|
|
|
|
List<HighlightInfo> errors = myDaemonCodeAnalyzer.runPasses(myFile, editor1.getDocument(), Arrays.asList(textEditor1,textEditor2), new int[0], false, null);
|
|
assertEmpty(errors);
|
|
|
|
assertEquals(collected, ContainerUtil.newHashSet(editor1, editor2));
|
|
assertEquals(applied, ContainerUtil.newHashSet(editor1, editor2));
|
|
}
|
|
|
|
private void registerFakePass(@NotNull final Set<Editor> applied, @NotNull final Set<Editor> collected) {
|
|
class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
|
|
private Fac(Project project) {
|
|
super(project);
|
|
}
|
|
|
|
@Override
|
|
public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) {
|
|
return new EditorBoundHighlightingPass(editor, file, false) {
|
|
@Override
|
|
public void doCollectInformation(@NotNull ProgressIndicator progress) {
|
|
collected.add(editor);
|
|
}
|
|
|
|
@Override
|
|
public void doApplyInformationToEditor() {
|
|
applied.add(editor);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
|
|
registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
|
|
}
|
|
|
|
private volatile boolean runHeavyProcessing;
|
|
public void testDaemonDisablesItselfDuringHeavyProcessing() throws Exception {
|
|
executeWithoutReparseDelay(() -> {
|
|
runHeavyProcessing = false;
|
|
try {
|
|
final Set<Editor> applied = Collections.synchronizedSet(new THashSet<>());
|
|
final Set<Editor> collected = Collections.synchronizedSet(new THashSet<>());
|
|
registerFakePass(applied, collected);
|
|
|
|
configureByText(PlainTextFileType.INSTANCE, "");
|
|
Editor editor = getEditor();
|
|
EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
|
|
editorTracker.setActiveEditors(Collections.singletonList(editor));
|
|
while (HeavyProcessLatch.INSTANCE.isRunning()) {
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
}
|
|
type("xxx"); // restart daemon
|
|
assertTrue(editorTracker.getActiveEditors().contains(editor));
|
|
assertSame(editor, FileEditorManager.getInstance(myProject).getSelectedTextEditor());
|
|
|
|
|
|
// wait for first pass to complete
|
|
long start = System.currentTimeMillis();
|
|
while (myDaemonCodeAnalyzer.isRunning() || !applied.contains(editor)) {
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
if (System.currentTimeMillis() - start > 10000) {
|
|
fail("Too long waiting for daemon");
|
|
}
|
|
}
|
|
|
|
runHeavyProcessing = true;
|
|
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
|
AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("my own heavy op");
|
|
try {
|
|
while (runHeavyProcessing) {
|
|
}
|
|
}
|
|
finally {
|
|
token.finish();
|
|
}
|
|
});
|
|
while (!HeavyProcessLatch.INSTANCE.isRunning()) {
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
}
|
|
applied.clear();
|
|
collected.clear();
|
|
|
|
type("xxx"); // try to restart daemon
|
|
|
|
start = System.currentTimeMillis();
|
|
while (System.currentTimeMillis() < start + 5000) {
|
|
assertEmpty(applied); // it should not restart
|
|
assertEmpty(collected);
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
}
|
|
}
|
|
finally {
|
|
runHeavyProcessing = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
public void testModificationInsideCodeBlockDoesNotRehighlightWholeFile() throws Exception {
|
|
configureByText(JavaFileType.INSTANCE, "class X { int f = \"error\"; int f() { int gg<caret> = 11; return 0;} }");
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEquals(1, errors.size());
|
|
assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
|
|
|
|
errors.get(0).getHighlighter().dispose();
|
|
|
|
errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
|
|
type("23");
|
|
errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
|
|
myEditor.getCaretModel().moveToOffset(0);
|
|
type("/* */");
|
|
errors = highlightErrors();
|
|
assertEquals(1, errors.size());
|
|
assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
|
|
}
|
|
|
|
public void _testCaretMovementDoesNotRestartHighlighting() throws Exception {
|
|
configureByText(JavaFileType.INSTANCE, "class X { int f = \"error\"; int f() { int gg<caret> = 11; return 0;} }");
|
|
|
|
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
|
|
final DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
|
|
final AtomicReference<ProgressIndicator> indicator = new AtomicReference<>();
|
|
final List<HighlightInfo> errors = filter(
|
|
di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, () -> {
|
|
if (indicator.get() == null) {
|
|
indicator.set(di.getUpdateProgress());
|
|
}
|
|
assertSame(indicator.get(), di.getUpdateProgress());
|
|
caretRight();
|
|
if (getEditor().getCaretModel().getOffset() == getEditor().getDocument().getTextLength()-1) {
|
|
getEditor().getCaretModel().moveToOffset(0);
|
|
}
|
|
}), HighlightSeverity.ERROR);
|
|
|
|
assertEquals(1, errors.size());
|
|
assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
|
|
}
|
|
|
|
|
|
public void testHighlightingDoesWaitForEmbarrassinglySlowExternalAnnotatorsToFinish() throws Exception {
|
|
configureByText(JavaFileType.INSTANCE, "class X { int f() { int gg<caret> = 11; return 0;} }");
|
|
final AtomicBoolean run = new AtomicBoolean();
|
|
final int SLEEP = 20000;
|
|
ExternalAnnotator<Integer, Integer> annotator = new ExternalAnnotator<Integer, Integer>() {
|
|
@Nullable
|
|
@Override
|
|
public Integer collectInformation(@NotNull PsiFile file) {
|
|
return 0;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Integer doAnnotate(final Integer collectedInfo) {
|
|
TimeoutUtil.sleep(SLEEP);
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void apply(@NotNull final PsiFile file, final Integer annotationResult, @NotNull final AnnotationHolder holder) {
|
|
run.set(true);
|
|
}
|
|
};
|
|
ExternalLanguageAnnotators.INSTANCE.addExplicitExtension(JavaLanguage.INSTANCE, annotator);
|
|
|
|
try {
|
|
long start = System.currentTimeMillis();
|
|
List<HighlightInfo> errors = filter(CodeInsightTestFixtureImpl.instantiateAndRun(getFile(), getEditor(), new int[0], false),
|
|
HighlightSeverity.ERROR);
|
|
long elapsed = System.currentTimeMillis() - start;
|
|
|
|
assertEquals(0, errors.size());
|
|
if (!run.get()) {
|
|
fail(ThreadDumper.dumpThreadsToString());
|
|
}
|
|
assertTrue("Elapsed: "+elapsed, elapsed >= SLEEP);
|
|
}
|
|
finally {
|
|
ExternalLanguageAnnotators.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, annotator);
|
|
}
|
|
}
|
|
|
|
public void testModificationInExcludedFileDoesNotCauseRehighlight() throws Exception {
|
|
final PsiFile excluded = configureByText(JavaFileType.INSTANCE, "class EEE { void f(){} }");
|
|
PsiTestUtil.addExcludedRoot(myModule, excluded.getVirtualFile().getParent());
|
|
|
|
configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
FileStatusMap me = DaemonCodeAnalyzerEx.getInstanceEx(getProject()).getFileStatusMap();
|
|
TextRange scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
|
|
assertNull(scope);
|
|
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> ((PsiJavaFile)excluded).getClasses()[0].getMethods()[0].delete());
|
|
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
|
|
assertNull(scope);
|
|
}
|
|
|
|
public void testModificationInWorkspaceXmlDoesNotCauseRehighlight() throws Exception {
|
|
configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
|
|
ApplicationEx application = ApplicationManagerEx.getApplicationEx();
|
|
boolean appSave = application.isDoNotSave();
|
|
application.doNotSave(false);
|
|
try {
|
|
application.saveAll();
|
|
final PsiFile excluded = PsiManager.getInstance(getProject()).findFile(getProject().getWorkspaceFile());
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
assertEmpty(errors);
|
|
FileStatusMap me = DaemonCodeAnalyzerEx.getInstanceEx(getProject()).getFileStatusMap();
|
|
TextRange scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
|
|
assertNull(scope);
|
|
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> {
|
|
Document document = PsiDocumentManager.getInstance(getProject()).getDocument(excluded);
|
|
document.insertString(0, "<!-- dsfsd -->");
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
});
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
|
|
assertNull(scope);
|
|
}
|
|
finally {
|
|
application.doNotSave(appSave);
|
|
}
|
|
}
|
|
|
|
public void testLightBulbDoesNotUpdateIntentionsInEDT() throws Exception {
|
|
final IntentionAction longLongUpdate = new AbstractIntentionAction() {
|
|
@Override
|
|
public void invoke(@NotNull Project project, Editor editor, PsiFile file) {
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getText() {
|
|
return "LongAction";
|
|
}
|
|
|
|
@Override
|
|
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) {
|
|
throw new RuntimeException("Must not update actions in EDT");
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
IntentionManager.getInstance().addAction(longLongUpdate);
|
|
Disposer.register(getTestRootDisposable(), () -> IntentionManager.getInstance().unregisterIntention(longLongUpdate));
|
|
configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
|
|
makeEditorWindowVisible(new Point(0, 0), myEditor);
|
|
doHighlighting();
|
|
myDaemonCodeAnalyzer.restart();
|
|
executeWithoutReparseDelay(() -> {
|
|
for (int i = 0; i < 1000; i++) {
|
|
caretRight();
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
caretLeft();
|
|
DaemonProgressIndicator updateProgress = myDaemonCodeAnalyzer.getUpdateProgress();
|
|
while (myDaemonCodeAnalyzer.getUpdateProgress() == updateProgress) { // wait until daemon started
|
|
UIUtil.dispatchAllInvocationEvents();
|
|
}
|
|
long start = System.currentTimeMillis();
|
|
while (myDaemonCodeAnalyzer.isRunning() && System.currentTimeMillis() < start + 500) {
|
|
UIUtil.dispatchAllInvocationEvents(); // wait for a bit more until ShowIntentionsPass.doApplyInformationToEditor() called
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private static void executeWithoutReparseDelay(@NotNull Runnable task) {
|
|
DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
|
|
int oldDelay = settings.AUTOREPARSE_DELAY;
|
|
settings.AUTOREPARSE_DELAY = 0;
|
|
try {
|
|
task.run();
|
|
}
|
|
finally {
|
|
settings.AUTOREPARSE_DELAY = oldDelay;
|
|
}
|
|
}
|
|
|
|
public void testLightBulbIsHiddenWhenFixRangeIsCollapsed() {
|
|
configureByText(StdFileTypes.JAVA, "class S { void foo() { boolean var; if (<selection>va<caret>r</selection>) {}} }");
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
|
|
final Set<LightweightHint> visibleHints = ContainerUtil.newIdentityTroveSet();
|
|
getProject().getMessageBus().connect(getTestRootDisposable()).subscribe(EditorHintListener.TOPIC, new EditorHintListener() {
|
|
@Override
|
|
public void hintShown(final Project project, final LightweightHint hint, final int flags) {
|
|
visibleHints.add(hint);
|
|
hint.addHintListener(new HintListener() {
|
|
@Override
|
|
public void hintHidden(EventObject event) {
|
|
visibleHints.remove(hint);
|
|
hint.removeHintListener(this);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
highlightErrors();
|
|
IntentionHintComponent lastHintBeforeDeletion = myDaemonCodeAnalyzer.getLastIntentionHint();
|
|
assertNotNull(lastHintBeforeDeletion);
|
|
|
|
delete(myEditor);
|
|
highlightErrors();
|
|
IntentionHintComponent lastHintAfterDeletion = myDaemonCodeAnalyzer.getLastIntentionHint();
|
|
assertSame(lastHintBeforeDeletion, lastHintAfterDeletion);
|
|
|
|
assertEmpty(visibleHints);
|
|
}
|
|
|
|
public void testCodeFoldingPassRestartsOnRegionUnfolding() throws Exception {
|
|
executeWithoutReparseDelay(() -> {
|
|
configureByText(StdFileTypes.JAVA, "class Foo {\n" +
|
|
" void m() {\n" +
|
|
"\n" +
|
|
" }\n" +
|
|
"}");
|
|
CodeFoldingManager.getInstance(getProject()).buildInitialFoldings(myEditor);
|
|
waitForDaemon();
|
|
EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_COLLAPSE_ALL_REGIONS);
|
|
waitForDaemon();
|
|
checkFoldingState("[FoldRegion +(25:33), placeholder='{}']");
|
|
|
|
new WriteCommandAction<Void>(myProject) {
|
|
@Override
|
|
protected void run(@NotNull Result<Void> result) throws Throwable {
|
|
myEditor.getDocument().insertString(0, "/*");
|
|
}
|
|
}.execute();
|
|
waitForDaemon();
|
|
checkFoldingState("[FoldRegion -(0:37), placeholder='/.../', FoldRegion +(27:35), placeholder='{}']");
|
|
|
|
EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_EXPAND_ALL_REGIONS);
|
|
waitForDaemon();
|
|
checkFoldingState("[FoldRegion -(0:37), placeholder='/.../']");
|
|
});
|
|
}
|
|
|
|
public void testChangingSettingsHasImmediateEffectOnOpenedEditor() throws Exception {
|
|
executeWithoutReparseDelay(() -> {
|
|
configureByText(StdFileTypes.JAVA, "class C { \n" +
|
|
" void m() {\n" +
|
|
" } \n" +
|
|
"}");
|
|
CodeFoldingManager.getInstance(getProject()).buildInitialFoldings(myEditor);
|
|
waitForDaemon();
|
|
checkFoldingState("[FoldRegion -(22:27), placeholder='{}']");
|
|
|
|
JavaCodeFoldingSettings settings = JavaCodeFoldingSettings.getInstance();
|
|
boolean savedValue = settings.isCollapseMethods();
|
|
try {
|
|
settings.setCollapseMethods(true);
|
|
CodeFoldingConfigurable.applyCodeFoldingSettingsChanges();
|
|
waitForDaemon();
|
|
checkFoldingState("[FoldRegion +(22:27), placeholder='{}']");
|
|
}
|
|
finally {
|
|
settings.setCollapseMethods(savedValue);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void checkFoldingState(String expected) {
|
|
assertEquals(expected, Arrays.toString(myEditor.getFoldingModel().getAllFoldRegions()));
|
|
}
|
|
|
|
private void waitForDaemon() {
|
|
long deadline = System.currentTimeMillis() + 60_000;
|
|
while (!daemonIsWorkingOrPending()) {
|
|
if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to start");
|
|
UIUtil.dispatchInvocationEvent();
|
|
}
|
|
while (daemonIsWorkingOrPending()) {
|
|
if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to finish");
|
|
UIUtil.dispatchInvocationEvent();
|
|
}
|
|
}
|
|
|
|
private boolean daemonIsWorkingOrPending() {
|
|
return PsiDocumentManager.getInstance(myProject).isUncommited(myEditor.getDocument()) || myDaemonCodeAnalyzer.isRunningOrPending();
|
|
}
|
|
|
|
public void testRehighlightInDebuggerExpressionFragment() throws Exception {
|
|
PsiExpressionCodeFragment fragment = JavaCodeFragmentFactory.getInstance(getProject()).createExpressionCodeFragment("+ <caret>\"a\"", null,
|
|
PsiType.getJavaLangObject(getPsiManager(), GlobalSearchScope.allScope(getProject())), true);
|
|
myFile = fragment;
|
|
Document document = PsiDocumentManager.getInstance(getProject()).getDocument(fragment);
|
|
myEditor = EditorFactory.getInstance().createEditor(document, getProject(), StdFileTypes.JAVA, false);
|
|
|
|
ProperTextRange visibleRange = makeEditorWindowVisible(new Point(0, 0), myEditor);
|
|
assertEquals(document.getTextLength(), visibleRange.getLength());
|
|
|
|
try {
|
|
final EditorInfo editorInfo = new EditorInfo(document.getText());
|
|
|
|
final String newFileText = editorInfo.getNewFileText();
|
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
|
if (!document.getText().equals(newFileText)) {
|
|
document.setText(newFileText);
|
|
}
|
|
|
|
editorInfo.applyToEditor(myEditor);
|
|
});
|
|
|
|
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
|
|
|
|
|
List<HighlightInfo> errors = highlightErrors();
|
|
HighlightInfo error = assertOneElement(errors);
|
|
assertEquals("Operator '+' cannot be applied to 'java.lang.String'", error.getDescription());
|
|
|
|
type(" ");
|
|
|
|
Collection<HighlightInfo> afterTyping = highlightErrors();
|
|
HighlightInfo after = assertOneElement(afterTyping);
|
|
assertEquals("Operator '+' cannot be applied to 'java.lang.String'", after.getDescription());
|
|
}
|
|
finally {
|
|
EditorFactory.getInstance().releaseEditor(myEditor);
|
|
}
|
|
}
|
|
|
|
public void testFileReload() throws Exception {
|
|
VirtualFile file = createFile("a.java", "").getVirtualFile();
|
|
Document document = getDocument(file);
|
|
assertNotNull(document);
|
|
|
|
FileStatusMap fileStatusMap = myDaemonCodeAnalyzer.getFileStatusMap();
|
|
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> {
|
|
PlatformTestUtil.tryGcSoftlyReachableObjects();
|
|
assertNull(PsiDocumentManager.getInstance(getProject()).getCachedPsiFile(document));
|
|
|
|
document.insertString(0, "class X { void foo() {}}");
|
|
assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
|
|
|
|
FileContentUtilCore.reparseFiles(file);
|
|
assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
|
|
|
|
findClass("X").getMethods()[0].delete();
|
|
assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
|
|
});
|
|
}
|
|
|
|
public void testAddRemoveHighlighterRaceInIncorrectAnnotatorsWhichUseFileRecursiveVisit() throws Exception {
|
|
Annotator annotator = new MyIncorrectlyRecursiveAnnotator();
|
|
com.intellij.lang.Language java = StdFileTypes.JAVA.getLanguage();
|
|
LanguageAnnotators.INSTANCE.addExplicitExtension(java, annotator);
|
|
try {
|
|
List<Annotator> list = LanguageAnnotators.INSTANCE.allForLanguage(java);
|
|
assertTrue(list.toString(), list.contains(annotator));
|
|
configureByText(StdFileTypes.JAVA, "class X {\n" +
|
|
" int foo(Object param) {\n" +
|
|
" if (param == this) return 1;\n" +
|
|
" return 0;\n" +
|
|
" }\n" +
|
|
"}\n");
|
|
((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
|
|
assertEquals(getFile().getTextRange(), VisibleHighlightingPassFactory.calculateVisibleRange(getEditor()));
|
|
|
|
assertEquals("XXX", assertOneElement(doHighlighting(HighlightSeverity.WARNING)).getDescription());
|
|
|
|
for (int i=0; i<100; i++) {
|
|
//System.out.println("i = " + i);
|
|
DaemonCodeAnalyzer.getInstance(getProject()).restart();
|
|
List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
|
|
assertEquals("XXX", assertOneElement(infos).getDescription());
|
|
}
|
|
}
|
|
finally {
|
|
LanguageAnnotators.INSTANCE.removeExplicitExtension(java, annotator);
|
|
}
|
|
|
|
List<Annotator> list = LanguageAnnotators.INSTANCE.allForLanguage(java);
|
|
assertFalse(list.toString(), list.contains(annotator));
|
|
}
|
|
|
|
public static class MyIncorrectlyRecursiveAnnotator implements Annotator {
|
|
Random random = new Random();
|
|
@Override
|
|
public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) {
|
|
if (psiElement instanceof PsiFile) {
|
|
psiElement.accept(new JavaRecursiveElementWalkingVisitor(){
|
|
@Override
|
|
public void visitKeyword(PsiKeyword keyword) {
|
|
if (Objects.equals(keyword.getText(), "this")) {
|
|
holder.createAnnotation(HighlightSeverity.WARNING, keyword.getTextRange(), "XXX");
|
|
TimeoutUtil.sleep(random.nextInt(100));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testDumbQuickFixIsNoLongerVisibleAfterApplied() {
|
|
MyInspection tool = new MyInspection();
|
|
enableInspectionTool(tool);
|
|
disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
|
|
|
|
@Language("JAVA")
|
|
String text = "class X { void f() { if (this == null) {} else return; } }";
|
|
configureByText(JavaFileType.INSTANCE, text);
|
|
WriteCommandAction.runWriteCommandAction(getProject(), () -> myEditor.getDocument().setText(text));
|
|
getEditor().getCaretModel().moveToOffset(getFile().getText().indexOf("if (") + 1);
|
|
assertEmpty(doHighlighting(HighlightSeverity.ERROR));
|
|
List<IntentionAction> fixes = findStupidFixes();
|
|
IntentionAction fix = assertOneElement(fixes);
|
|
fix.invoke(getProject(), getEditor(), getFile());
|
|
|
|
fixes = findStupidFixes();
|
|
assertEmpty(fixes);
|
|
|
|
assertEmpty(doHighlighting(HighlightSeverity.ERROR));
|
|
fixes = findStupidFixes();
|
|
assertEmpty(fixes);
|
|
}
|
|
|
|
private List<IntentionAction> findStupidFixes() {
|
|
return CodeInsightTestFixtureImpl.getAvailableIntentions(getEditor(), getFile())
|
|
.stream().filter(f->f.getFamilyName().equals(new MyInspection.StupidQuickFixWhichDoesntCheckItsOwnApplicability().getFamilyName()))
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
private static class MyInspection extends LocalInspectionTool {
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getGroupDisplayName() {
|
|
return "danuna";
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getDisplayName() {
|
|
return getGroupDisplayName();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public String getShortName() {
|
|
return getGroupDisplayName();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
|
return new JavaElementVisitor() {
|
|
@Override
|
|
public void visitIfStatement(PsiIfStatement statement) {
|
|
if (statement.getElseBranch() != null) {
|
|
PsiKeyword keyw = (PsiKeyword)statement.getChildren()[0];
|
|
holder.registerProblem(keyw, "dododo", new StupidQuickFixWhichDoesntCheckItsOwnApplicability());
|
|
}
|
|
}
|
|
};
|
|
}
|
|
private static class StupidQuickFixWhichDoesntCheckItsOwnApplicability implements LocalQuickFix {
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getName() {
|
|
return "danu";
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getFamilyName() {
|
|
return getName();
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
|
WriteCommandAction.runWriteCommandAction(project, () -> ((PsiIfStatement)descriptor.getPsiElement().getParent()).getElseBranch().delete());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|