From 394e5c640a5027cf680df2b289984e7ac821ced8 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Mon, 12 Feb 2024 13:40:57 +0100 Subject: [PATCH] IDEA-321013 Outdated HighlightInfo's should be removed as soon as the relevant annotator or inspection finishes Rework annotators engine to make annotators run in parallel, each on all relevant PSI elements in their own order (makes fast annotators complete faster to allow them to remove outdated highlighters faster). For that, for each annotator (in parallel): - create its own AnnotationHolder - rearrange its PSI elements in "time to first diagnostic in previous run" order, to reduce latency. - run annotator on these PSI elements sequentially - as soon as annotator produces info/fails to produce the same info from the previous run, update the corresponding range highlighters Pleas note, there's no more contract "Do not call annotators for parent PSI if some (maybe completely unrelated) annotator/highlight visitor produced error for some PSI element". Fix highlighting tests, the majority of which relied on annotator order, or implicit contract above. Fix a bunch of annotators which tried to double-visit some PSI elements to fight the contract above. GitOrigin-RevId: 74f727fc6d3be3f500cdbb0f26e7d0daf1ffe7ff --- .../DaemonAnnotatorsRespondToChangesTest.java | 285 +++++++++++++++--- .../impl/DaemonRespondToChangesTest.java | 13 + .../LightAnnotatorHighlightingTest.java | 47 +-- .../daemon/impl/AnnotationHolderImpl.java | 9 +- .../daemon/impl/AnnotatorRunner.java | 262 ++++++++++++++++ .../BackgroundUpdateHighlightersUtil.java | 10 +- .../daemon/impl/DefaultHighlightVisitor.java | 86 +----- .../daemon/impl/GeneralHighlightingPass.java | 73 +++-- .../daemon/impl/HighlightInfo.java | 10 +- .../daemon/impl/HighlightInfoUpdater.java | 20 +- .../impl/HighlightVisitorBasedInspection.java | 3 +- .../com/intellij/concurrency/JobLauncher.java | 15 + .../com/intellij/core/CoreJobLauncher.java | 3 + .../editor/impl/RangeHighlighterImpl.java | 17 ++ .../daemon/impl/DaemonCodeAnalyzerImpl.java | 5 +- .../impl/DefaultHighlightInfoProcessor.java | 6 +- .../impl/InjectedGeneralHighlightingPass.java | 37 ++- .../daemon/impl/InspectionRunner.java | 16 +- .../intellij/concurrency/JobLauncherImpl.java | 18 +- .../editor/impl/DocumentMarkupModelTest.java | 5 +- .../groovy/annotator/GroovyAnnotator.java | 21 +- .../GroovyHighlightingTest.groovy | 4 +- .../AnonymousClassAbstractMethod.groovy | 2 +- ...izersAreNotAllowedInAbstractMethods.groovy | 2 +- .../highlighting/FieldModifiers.groovy | 2 +- .../highlighting/ScriptFieldModifiers.groovy | 2 +- ...riableDeclarationDuplicateModifiers.groovy | 2 +- .../highlighting/pre30/typeAnnotations.groovy | 8 +- .../highlighting/defaultTagInList.fxml | 2 +- .../highlighting/defaultTagProperties.fxml | 2 +- .../testData/highlighting/loginForm.fxml | 2 +- .../testData/highlighting/readOnly.fxml | 2 +- .../highlighting/rootTagProperties.fxml | 2 +- .../highlighting/staticProperties.fxml | 2 +- ...ClassWithDefaultImplementationComplex.java | 2 +- .../lang/xpath/validation/XPathAnnotator.java | 1 + .../yaml/highlighting/data/invalidIndent.yml | 4 +- .../highlighting/matchStatementBefore310.py | 2 +- .../typeAliasStatementBefore312.py | 2 +- 39 files changed, 761 insertions(+), 245 deletions(-) create mode 100644 platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonAnnotatorsRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonAnnotatorsRespondToChangesTest.java index 93e288336a01..5f570ae2d242 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonAnnotatorsRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonAnnotatorsRespondToChangesTest.java @@ -22,6 +22,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorMouseHoverPopupManager; import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.ex.MarkupModelEx; import com.intellij.openapi.editor.impl.DocumentMarkupModel; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.markup.MarkupModel; @@ -36,6 +37,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; @@ -80,6 +82,7 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase @Override protected void tearDown() throws Exception { + MyRecordingAnnotator.clearAll(); try { if (myEditor != null) { Document document = myEditor.getDocument(); @@ -169,7 +172,6 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase } public void testAddRemoveHighlighterRaceInIncorrectAnnotatorsWhichUseFileRecursiveVisit() { - //System.out.println("i = " + i); useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MyIncorrectlyRecursiveAnnotator()}, () -> { @Language("JAVA") String text1 = """ @@ -188,7 +190,6 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase assertEquals("XXX", assertOneElement(doHighlighting(HighlightSeverity.WARNING)).getDescription()); for (int i = 0; i < 100; i++) { - //System.out.println("i = " + i); myDaemonCodeAnalyzer.restart(); List infos = doHighlighting(HighlightSeverity.WARNING); assertEquals("XXX", assertOneElement(infos).getDescription()); @@ -295,6 +296,7 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase TimeoutUtil.sleep(100); } } + LOG.debug(getClass()+".annotate("+element+") = "+didIDoIt()); } } public static class MyFastAnnotator extends MyRecordingAnnotator { @@ -306,6 +308,7 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase holder.newAnnotation(HighlightSeverity.ERROR, SWEARING).range(element).create(); iDidIt(); } + LOG.debug(getClass()+".annotate("+element+") = "+didIDoIt()); } } @@ -316,6 +319,7 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase holder.newAnnotation(HighlightSeverity.INFORMATION, "comment").create(); iDidIt(); } + LOG.debug(getClass()+".annotate("+element+") = "+didIDoIt()); } } @@ -356,7 +360,9 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase MarkupModel markupModel = DocumentMarkupModel.forDocument(getEditor().getDocument(), getProject(), true); TestTimeOut n = TestTimeOut.setTimeout(100, TimeUnit.SECONDS); AtomicInteger called = new AtomicInteger(); + AtomicBoolean success = new AtomicBoolean(); Runnable checkHighlighted = () -> { + if (success.get()) return; called.incrementAndGet(); UIUtil.dispatchAllInvocationEvents(); long highlighted = Arrays.stream(markupModel.getAllHighlighters()) @@ -366,26 +372,25 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase .count(); if (highlighted != 0) { toSleepMs.set(0); - throw new DebugException(); // sorry for that, had to differentiate from failure + success.set(true); + //throw new DebugException(); // sorry for that, had to differentiate from failure } if (n.timedOut()) { toSleepMs.set(0); throw new RuntimeException(new TimeoutException(ThreadDumper.dumpThreadsToString())); } }; - try { - CodeInsightTestFixtureImpl.ensureIndexesUpToDate(getProject()); - TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor()); - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - long start = System.currentTimeMillis(); - myDaemonCodeAnalyzer.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtilRt.EMPTY_INT_ARRAY, false, checkHighlighted); + CodeInsightTestFixtureImpl.ensureIndexesUpToDate(getProject()); + TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor()); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + long start = System.currentTimeMillis(); + myDaemonCodeAnalyzer.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtilRt.EMPTY_INT_ARRAY, false, checkHighlighted); + if (!success.get()) { List errors = ContainerUtil.filter(markupModel.getAllHighlighters(), highlighter -> HighlightInfo.fromRangeHighlighter(highlighter) != null && HighlightInfo.fromRangeHighlighter(highlighter).getSeverity() == HighlightSeverity.ERROR); long elapsed = System.currentTimeMillis() - start; fail("should have been interrupted. toSleepMs: " + toSleepMs + "; highlights: " + errors + "; called: " + called+"; highlighted in "+elapsed+"ms"); } - catch (DebugException ignored) { - } } public static class MyNewBuilderAnnotator extends MyRecordingAnnotator { @@ -393,21 +398,21 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiComment && element.getText().equals("//XXX")) { holder.newAnnotation(HighlightSeverity.ERROR, MyFastAnnotator.SWEARING).create(); + iDidIt(); + } + else if (didIDoIt()) { // sleep after creating annotation to emulate a very big annotator which does a great amount of work after registering annotation - // use this contrived form to be able to bail out immediately by modifying toSleepMs in the other thread while (toSleepMs.addAndGet(-100) > 0) { TimeoutUtil.sleep(100); } - iDidIt(); } } } public void testAddAnnotationViaBuilderEntailsCreatingCorrespondingRangeHighlighterImmediately() { PlatformTestUtil.assumeEnoughParallelism(); - useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MyNewBuilderAnnotator()}, - this::checkSwearingHighlightIsVisibleImmediately); + useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyRecordingAnnotator[]{new MyNewBuilderAnnotator()}, this::checkSwearingHighlightIsVisibleImmediately); } private static final AtomicBoolean annotated = new AtomicBoolean(); @@ -436,7 +441,7 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase } } - public void test_SerializeCodeInsightPasses_SecretSettingDoesWork() { + public void _test_SerializeCodeInsightPasses_SecretSettingDoesWork() { PlatformTestUtil.assumeEnoughParallelism(); TextEditorHighlightingPassRegistrarImpl registrar = @@ -538,24 +543,17 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase } private void checkFirstAnnotation() { - AtomicReference reported = new AtomicReference<>(); + AtomicReference firstStatistics = new AtomicReference<>(); getProject().getMessageBus().connect(getTestRootDisposable()).subscribe(DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC, - new DaemonCodeAnalyzer.DaemonListener() { - @Override - public void daemonAnnotatorStatisticsGenerated(@NotNull AnnotationSession session, - @NotNull Collection statistics, - @NotNull PsiFile file) { - AnnotatorStatistics stat = assertOneElement(ContainerUtil.filter(statistics, stat1 -> stat1.annotator instanceof MyInfoAnnotator)); - Throwable old = reported.getAndSet(new Throwable()); - assertNull(old==null? null: ExceptionUtil.getMessage(old), old); - assertEquals("Annotation(message='comment', severity='INFORMATION', toolTip='comment')", stat.firstAnnotation.toString()); - assertSame(stat.firstAnnotation, stat.lastAnnotation); - assertTrue(stat.annotatorStartStamp > 0); - assertTrue(stat.firstAnnotationStamp >= stat.annotatorStartStamp); - assertTrue(stat.lastAnnotationStamp >= stat.firstAnnotationStamp); - assertTrue(stat.annotatorFinishStamp >= stat.lastAnnotationStamp); - } - }); + new DaemonCodeAnalyzer.DaemonListener() { + @Override + public void daemonAnnotatorStatisticsGenerated(@NotNull AnnotationSession session, + @NotNull Collection statistics, + @NotNull PsiFile file) { + AnnotatorStatistics stat = assertOneElement(ContainerUtil.filter(statistics, stat1 -> stat1.annotator instanceof MyInfoAnnotator)); + firstStatistics.compareAndExchange(null, stat); + } + }); @Language("JAVA") String text = """ @@ -566,7 +564,15 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase configureByText(JavaFileType.INSTANCE, text); doHighlighting(); - assertNotNull(reported.get()); + + DaemonCodeAnalyzer.DaemonListener.AnnotatorStatistics stat = firstStatistics.get(); + assertNotNull(stat); + assertEquals("Annotation(message='comment', severity='INFORMATION', toolTip='comment')", stat.firstAnnotation.toString()); + assertSame(stat.firstAnnotation, stat.lastAnnotation); + assertTrue(stat.annotatorStartStamp > 0); + assertTrue(stat.firstAnnotationStamp >= stat.annotatorStartStamp); + assertTrue(stat.lastAnnotationStamp >= stat.firstAnnotationStamp); + assertTrue(stat.annotatorFinishStamp >= stat.lastAnnotationStamp); } private static final String wordToAnnotate = "annotate_here"; @@ -642,4 +648,215 @@ public class DaemonAnnotatorsRespondToChangesTest extends DaemonAnalyzerTestCase expectedVisibleRange = new TextRange(0, editor.getDocument().getTextLength()); useAnnotatorsIn(PlainTextLanguage.INSTANCE, new MyRecordingAnnotator[]{new CheckVisibleRangeAnnotator()}, ()-> assertEmpty(doHighlighting())); } + + // highlight each field, stall every other element + static class MyFieldSlowAnnotator extends MyRecordingAnnotator { + static final AtomicReference fieldWarningText = new AtomicReference<>(); + static final AtomicInteger stallMs = new AtomicInteger(); + static final AtomicBoolean finished = new AtomicBoolean(); + @Override + public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + if (element instanceof PsiField) { + holder.newAnnotation(HighlightSeverity.WARNING, fieldWarningText.get()).range(element).create(); + iDidIt(); + } + else if (element instanceof PsiFile) { + finished.set(true); + } + else { + // stall every other element to exacerbate latency problems if the order is wrong + TimeoutUtil.sleep(stallMs.get()); + } + } + } + // highlights all "xxx" comments, but only when there are no comments after it + static class MyCommentFastAnnotator extends MyRecordingAnnotator { + static final AtomicBoolean finished = new AtomicBoolean(); + static final String fastToolText = "blah.MyCommentFastAnnotator"; + @Override + public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + if (element instanceof PsiComment) { + if (element.getText().contains("xxx") && !element.getContainingFile().getText().substring(element.getTextOffset()+2).contains("//")) { + holder.newAnnotation(HighlightSeverity.WARNING, fastToolText).range(element).create(); + iDidIt(); + } + } + else if (element instanceof PsiFile) { + finished.set(true); + iDidIt(); + } + } + } + + public void testAnnotatorMustRemoveItsObsoleteHighlightsImmediatelyAfterFinished() { + @Language("JAVA") + String text = """ + class LQF { + // xxx + int f; + }"""; + configureByText(JavaFileType.INSTANCE, text); + DaemonRespondToChangesTest.makeWholeEditorWindowVisible((EditorImpl)myEditor); // get "visible area first" optimization out of the way + UIUtil.markAsFocused(getEditor().getContentComponent(), true); // to make ShowIntentionPass call its collectInformation() + SeverityRegistrar.getSeverityRegistrar(getProject()); //preload inspection profile + MyFieldSlowAnnotator.fieldWarningText.set("1st run"); + MyFieldSlowAnnotator.finished.set(false); + MyFieldSlowAnnotator.stallMs.set(0); + + MyCommentFastAnnotator.finished.set(false); + + Map annotatorsByLanguage = new HashMap<>(); + annotatorsByLanguage.put(JavaLanguage.INSTANCE, new MyRecordingAnnotator[]{new MyFieldSlowAnnotator(), new MyCommentFastAnnotator()}); + MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(getEditor().getDocument(), getProject(), true); + + // both annos should produce their results + useAnnotatorsIn(annotatorsByLanguage, () -> { + List infos = doHighlighting(HighlightSeverity.WARNING); + assertTrue(infos.toString(), ContainerUtil.exists(infos, i -> i.getDescription().equals(MyFieldSlowAnnotator.fieldWarningText.get()))); + assertTrue(infos.toString(), ContainerUtil.exists(infos, i -> i.getDescription().equals(MyCommentFastAnnotator.fastToolText))); + RangeHighlighter[] markers = model.getAllHighlighters(); + assertTrue(Arrays.toString(markers), ContainerUtil.exists(markers, i -> HighlightInfo.fromRangeHighlighter(i) != null && MyFieldSlowAnnotator.fieldWarningText.get().equals(HighlightInfo.fromRangeHighlighter(i).getDescription()))); + assertTrue(Arrays.toString(markers), ContainerUtil.exists(markers, i -> HighlightInfo.fromRangeHighlighter(i) != null && MyCommentFastAnnotator.fastToolText.equals(HighlightInfo.fromRangeHighlighter(i).getDescription()))); + }); + + MyFieldSlowAnnotator.fieldWarningText.set("Aha, field, finally!"); + MyFieldSlowAnnotator.stallMs.set(100); + // type another comment which will cause the warning about the first comment (by MyCommentFastAnnotator) to disappear + // and check that as soon as MyCommentFastAnnotator is finished, it removed its own obsolete warnings, whereas MyFieldSlowAnnotator continues to run + type("// another comment"); + MyCommentFastAnnotator.finished.set(false); + MyFieldSlowAnnotator.finished.set(false); + DaemonRespondToChangesTest.makeWholeEditorWindowVisible((EditorImpl)myEditor); // get "visible area first" optimization out of the way + useAnnotatorsIn(annotatorsByLanguage, () -> { + // now when the highlighting is restarted, we should get back our inspection result very fast, despite very slow processing of every other element + long deadline = System.currentTimeMillis() + 10_000; + while (!DaemonRespondToChangesTest.daemonIsWorkingOrPending(myProject, myEditor.getDocument())) { + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(); + if (System.currentTimeMillis() > deadline) { + fail("Too long waiting for daemon to start"); + } + } + try { + boolean fastToolFinishedFaster = false; + while (DaemonRespondToChangesTest.daemonIsWorkingOrPending(myProject, myEditor.getDocument())) { + if (System.currentTimeMillis() > deadline) { + fail("Too long waiting for daemon to finish\n" + ThreadDumper.dumpThreadsToString()); + } + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(); + if (MyCommentFastAnnotator.finished.get() && !MyFieldSlowAnnotator.finished.get()) { + boolean fastToolWarningFound = !DaemonCodeAnalyzerEx.processHighlights(model, getProject(), HighlightSeverity.WARNING, 0, + myEditor.getDocument().getTextLength(), + info -> !MyCommentFastAnnotator.fastToolText.equals(info.getDescription())); + fastToolFinishedFaster = true; + if (fastToolWarningFound) { + fail("Annotator must have removed its own obsolete highlights as soon as it's finished, but got:" + + StringUtil.join(model.getAllHighlighters(), Object::toString, "\n ") + "; thread dump:\n" + ThreadDumper.dumpThreadsToString()); + } + } + } + assertTrue("Fast inspection must have finished faster than the slow one, but it didn't", fastToolFinishedFaster); + } + finally { + MyFieldSlowAnnotator.stallMs.set(0); + } + }); + } + + static class MyComment1Annotator extends MyRecordingAnnotator { + static final AtomicBoolean stall1 = new AtomicBoolean(); + static final String comment1Text = "comment1Text"; + public MyComment1Annotator() { + } + + @Override + public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + while (stall1.get()) { + Thread.yield(); + //ProgressManager.checkCanceled(); + } + if (element instanceof PsiComment) { + if (element.getText().contains("xxx")) { + holder.newAnnotation(HighlightSeverity.WARNING, comment1Text).range(element).create(); + iDidIt(); + //stall1.set(true); // stall right after producing annotation + } + } + } + } + static class MyComment2Annotator extends MyRecordingAnnotator { + static final AtomicBoolean stall2 = new AtomicBoolean(); + static final String comment2Text = "comment2Text"; + public MyComment2Annotator() { + } + + @Override + public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + while (stall2.get()) { + Thread.yield(); + //ProgressManager.checkCanceled(); + } + if (element instanceof PsiComment) { + if (element.getText().contains("xxx")) { + holder.newAnnotation(HighlightSeverity.WARNING, comment2Text).range(element).create(); + iDidIt(); + //stall2.set(true); // stall right after producing annotation + } + } + } + } + + public void testAnnotatorsMustNotWaitForEachOther() { + PlatformTestUtil.assumeEnoughParallelism(); + @Language("JAVA") + String text = """ + class LQF { + // xxx + int f; + }"""; + configureByText(JavaFileType.INSTANCE, text); + DaemonRespondToChangesTest.makeWholeEditorWindowVisible((EditorImpl)myEditor); // get "visible area first" optimization out of the way + UIUtil.markAsFocused(getEditor().getContentComponent(), true); // to make ShowIntentionPass call its collectInformation() + + Map annotatorsByLanguage = new HashMap<>(); + annotatorsByLanguage.put(JavaLanguage.INSTANCE, new MyRecordingAnnotator[]{new MyComment1Annotator(), new MyComment2Annotator()}); + MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(getEditor().getDocument(), getProject(), true); + + // both annos should produce their results + myDaemonCodeAnalyzer.restart(); + DaemonRespondToChangesTest.makeWholeEditorWindowVisible((EditorImpl)myEditor); // get "visible area first" optimization out of the way + useAnnotatorsIn(annotatorsByLanguage, () -> { + long deadline = System.currentTimeMillis() + 20_000; + while (!DaemonRespondToChangesTest.daemonIsWorkingOrPending(myProject, myEditor.getDocument())) { + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(); + if (System.currentTimeMillis() > deadline) { + fail("Too long waiting for daemon to start"); + } + } + boolean tool1AnnoFound = false; + boolean tool2AnnoFound = false; + while (!tool1AnnoFound || !tool2AnnoFound) { + if (System.currentTimeMillis() > deadline) { + fail("Too long waiting for daemon to finish\n" + ThreadDumper.dumpThreadsToString()); + } + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(); + tool1AnnoFound = !DaemonCodeAnalyzerEx.processHighlights(model, getProject(), HighlightSeverity.WARNING, 0, + myEditor.getDocument().getTextLength(), + info -> !MyComment1Annotator.comment1Text.equals(info.getDescription())); + tool2AnnoFound = !DaemonCodeAnalyzerEx.processHighlights(model, getProject(), HighlightSeverity.WARNING, 0, + myEditor.getDocument().getTextLength(), + info -> !MyComment2Annotator.comment2Text.equals(info.getDescription())); + } + MyComment1Annotator.stall1.set(false); + MyComment2Annotator.stall2.set(false); + while (DaemonRespondToChangesTest.daemonIsWorkingOrPending(myProject, myEditor.getDocument())) { + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(); + if (System.currentTimeMillis() > deadline+1_000) { + fail("Too long waiting for daemon to finish; stall1="+MyComment1Annotator.stall1+"; stall2="+MyComment2Annotator.stall2 + + "\n" + ThreadDumper.dumpThreadsToString()); + } + Thread.yield(); + } + }); + } + } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java index 048c35084003..891bba20e6ea 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java @@ -2632,4 +2632,17 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertEmpty(highlightErrors()); } + + public void testTypingErrorElementMustHighlightIt() { + ThreadingAssertions.assertEventDispatchThread(); + configureByText(JavaFileType.INSTANCE, "class X { void f() { } }"); + assertEmpty(highlightErrors()); + makeEditorWindowVisible(new Point(0, 1000), myEditor); + + type("/"); + waitForDaemon(myProject, myEditor.getDocument()); + List errors = DaemonCodeAnalyzerImpl.getHighlights(getEditor().getDocument(), HighlightSeverity.ERROR, getProject()); + assertNotEmpty(errors); + assertTrue(errors.toString().contains("'class' or 'interface' expected")); + } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java index 8ea1658cf888..945b40f5cf2b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java @@ -48,6 +48,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.util.Comparator; import java.util.List; import java.util.function.Function; @@ -70,15 +71,14 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase List fileLevel = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).getFileLevelHighlights(getProject(), getFile()); HighlightInfo info = assertOneElement(fileLevel); - assertEquals("top level", info.getDescription()); + assertTrue(MyFileLevelAnnotator.isMy(info)); type("\n\n"); assertEmpty(highlightErrors()); fileLevel = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).getFileLevelHighlights(getProject(), getFile()); info = assertOneElement(fileLevel); - assertEquals("top level", info.getDescription()); - + assertTrue(MyFileLevelAnnotator.isMy(info)); type("//xxx"); //disable top level annotation List warnings = doHighlighting(HighlightSeverity.WARNING); assertEmpty(warnings); @@ -121,6 +121,9 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase iDidIt(); } } + static boolean isMy(HighlightInfo info) { + return HighlightSeverity.WARNING.equals(info.getSeverity()) && "top level".equals(info.getDescription()); + } } public void testAnnotatorMustNotSpecifyCrazyRangeForCreatedAnnotation() { @@ -464,23 +467,25 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase DaemonAnnotatorsRespondToChangesTest.useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator[]{new MyStupidRepetitiveAnnotator()}, () -> runMyAnnotators()); } - public void testAnnotatorTryingToHighlightWarningAndErrorToTheSameElementMustFilterOutWarning() { + public void testDifferentAnnotatorsTryingToHighlightWarningAndErrorToTheSameElementMustNotInterfere() { DaemonAnnotatorsRespondToChangesTest.useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator[]{new MyErrorAnnotator(), new MyWarningAnnotator()}, () -> { configureFromFileText("My.java", "class My {}"); ((EditorEx)getEditor()).getScrollPane().getViewport().setSize(new Dimension(1000,1000)); // whole file fit onscreen - List infos = doHighlighting(HighlightSeverity.WARNING); - HighlightInfo info = assertOneElement(infos); - MyErrorAnnotator.assertMy(info); + List infos = ContainerUtil.sorted(doHighlighting(HighlightSeverity.WARNING), Comparator.comparing(HighlightInfo::getSeverity)); + assertEquals(2, infos.size()); + assertTrue(MyWarningAnnotator.isMy(infos.get(0))); + assertTrue(MyErrorAnnotator.isMy(infos.get(1))); }); } - public void testAnnotatorTryingToHighlightInformationAndErrorToTheSameElementMustFilterOutInformation() { + public void testDifferentAnnotatorsTryingToHighlightInformationAndErrorToTheSameElementMustNotInterfere() { DaemonAnnotatorsRespondToChangesTest.useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator[]{new MyErrorAnnotator(), new MyInfoAnnotator()}, () -> { configureFromFileText("My.java", "class My {}"); ((EditorEx)getEditor()).getScrollPane().getViewport().setSize(new Dimension(1000,1000)); // whole file fit onscreen - List infos = doHighlighting(HighlightSeverity.INFORMATION); - HighlightInfo info = assertOneElement(infos); - MyErrorAnnotator.assertMy(info); + List infos = ContainerUtil.sorted(doHighlighting(HighlightSeverity.INFORMATION), Comparator.comparing(HighlightInfo::getSeverity)); + assertEquals(2, infos.size()); + assertTrue(MyInfoAnnotator.isMy(infos.get(0))); + assertTrue(MyErrorAnnotator.isMy(infos.get(1))); }); } @@ -489,8 +494,8 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase configureFromFileText("My.java", "class My {}"); ((EditorEx)getEditor()).getScrollPane().getViewport().setSize(new Dimension(1000,1000)); // whole file fit onscreen List infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY); - assertTrue(infos.toString(), ContainerUtil.exists(infos, info -> info.getSeverity().equals(HighlightSeverity.ERROR) && info.getDescription().equals("error2"))); - assertTrue(infos.toString(), ContainerUtil.exists(infos, info -> info.getSeverity().equals(HighlightInfoType.SYMBOL_TYPE_SEVERITY) && info.getDescription().equals("symbol2"))); + assertTrue(infos.toString(), ContainerUtil.exists(infos, info -> info.getSeverity().equals(HighlightSeverity.ERROR) && MyErrorAnnotator.isMy(info))); + assertTrue(infos.toString(), ContainerUtil.exists(infos, info -> info.getSeverity().equals(HighlightInfoType.SYMBOL_TYPE_SEVERITY) && MySymbolAnnotator.isMy(info))); }); } @@ -502,9 +507,8 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase iDidIt(); } } - static void assertMy(HighlightInfo info) { - assertEquals(HighlightSeverity.ERROR, info.getSeverity()); - assertEquals("error2", info.getDescription()); + static boolean isMy(HighlightInfo info) { + return HighlightSeverity.ERROR.equals(info.getSeverity()) && "error2".equals(info.getDescription()); } } public static class MyWarningAnnotator extends DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator { @@ -515,6 +519,9 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase iDidIt(); } } + static boolean isMy(HighlightInfo info) { + return HighlightSeverity.WARNING.equals(info.getSeverity()) && "warn2".equals(info.getDescription()); + } } public static class MyInfoAnnotator extends DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator { @Override @@ -524,6 +531,9 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase iDidIt(); } } + static boolean isMy(HighlightInfo info) { + return HighlightSeverity.INFORMATION.equals(info.getSeverity()) && "info2".equals(info.getDescription()); + } } public static class MySymbolAnnotator extends DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator { @Override @@ -533,6 +543,9 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase iDidIt(); } } + static boolean isMy(HighlightInfo info) { + return HighlightInfoType.SYMBOL_TYPE_SEVERITY.equals(info.getSeverity()) && "symbol2".equals(info.getDescription()); + } } /** @@ -563,7 +576,7 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase private static volatile boolean FIX_ENABLED; @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { - if (element.getText().equals("hello")) { + if (element.getText().equals("hello") && !(element instanceof PsiFile)) { holder.newAnnotation(HighlightSeverity.ERROR, "i hate it") .newFix(new DeleteElementFix(element) { @Override diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotationHolderImpl.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotationHolderImpl.java index 6ba7335d88d6..ff713ea94448 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotationHolderImpl.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotationHolderImpl.java @@ -196,11 +196,16 @@ public class AnnotationHolderImpl extends SmartList implements Annot @Override public @NotNull AnnotationBuilder newAnnotation(@NotNull HighlightSeverity severity, @NotNull @Nls String message) { - return new B(this, severity, message, myCurrentElement, ObjectUtils.chooseNotNull(myCurrentAnnotator, myExternalAnnotator)); + return createBuilder(severity, message, myCurrentElement, ObjectUtils.chooseNotNull(myCurrentAnnotator, myExternalAnnotator)); } @Override public @NotNull AnnotationBuilder newSilentAnnotation(@NotNull HighlightSeverity severity) { - return new B(this, severity, null, myCurrentElement, ObjectUtils.chooseNotNull(myCurrentAnnotator, myExternalAnnotator)); + return createBuilder(severity, null, myCurrentElement, ObjectUtils.chooseNotNull(myCurrentAnnotator, myExternalAnnotator)); + } + + @NotNull + protected B createBuilder(@NotNull HighlightSeverity severity, @Nls String message, PsiElement currentElement, Object currentAnnotator) { + return new B(this, severity, message, currentElement, currentAnnotator); } PsiElement myCurrentElement; diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java new file mode 100644 index 000000000000..dbc2e6ee8f1f --- /dev/null +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/AnnotatorRunner.java @@ -0,0 +1,262 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight.daemon.impl; + +import com.intellij.codeInsight.daemon.AnnotatorStatisticsCollector; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder; +import com.intellij.codeInspection.ex.GlobalInspectionContextBase; +import com.intellij.concurrency.JobLauncher; +import com.intellij.diagnostic.PluginException; +import com.intellij.injected.editor.DocumentWindow; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageAnnotators; +import com.intellij.lang.annotation.Annotation; +import com.intellij.lang.annotation.Annotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ProperTextRange; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.PairProcessor; +import com.intellij.util.ReflectionUtil; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashingStrategy; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +final class AnnotatorRunner { + private static final Logger LOG = Logger.getInstance(AnnotatorRunner.class); + private static final Annotator TOMB_STONE = (__, __1) -> { }; + private final Project myProject; + private final PsiFile myPsiFile; + private final HighlightInfoHolder myHighlightInfoHolder; + private final HighlightingSession myHighlightingSession; + private final DumbService myDumbService; + private final boolean myBatchMode; + private boolean myDumb; + private final AnnotatorStatisticsCollector myAnnotatorStatisticsCollector = new AnnotatorStatisticsCollector(); + private final List results = Collections.synchronizedList(new ArrayList<>()); + + AnnotatorRunner(@NotNull PsiFile psiFile, boolean batchMode, @NotNull HighlightInfoHolder holder, @NotNull HighlightingSession highlightingSession) { + myProject = psiFile.getProject(); + myPsiFile = psiFile; + myHighlightInfoHolder = holder; + myHighlightingSession = highlightingSession; + myDumbService = DumbService.getInstance(myProject); + myBatchMode = batchMode; + } + + boolean runAnnotatorsAsync(@NotNull List inside, @NotNull List outside, @NotNull BooleanSupplier runnable) { + ApplicationManager.getApplication().assertIsNonDispatchThread(); + DaemonProgressIndicator indicator = GlobalInspectionContextBase.assertUnderDaemonProgress(); + + myDumb = myDumbService.isDumb(); + + // TODO move inside Divider to calc only once + List insideThenOutside = ContainerUtil.concat(inside, outside); + Map> supportedLanguages = calcSupportedLanguages(insideThenOutside); + PairProcessor> processor = (annotator, __) -> + ApplicationManagerEx.getApplicationEx().tryRunReadAction(() -> runAnnotator(annotator, insideThenOutside, supportedLanguages)); + boolean result = JobLauncher.getInstance().procInOrderAsync(indicator, supportedLanguages.size(), processor, addToQueue -> { + for (Annotator annotator : supportedLanguages.keySet()) { + addToQueue.enqueue(annotator); + } + addToQueue.finish(); + return runnable.getAsBoolean(); + }); + myAnnotatorStatisticsCollector.reportAnalysisFinished(myProject, myHighlightInfoHolder.getAnnotationSession(), myPsiFile); + return result; + } + + @NotNull + private Map> calcSupportedLanguages(@NotNull List elements) { + Map> map = CollectionFactory.createCustomHashingStrategyMap(new HashingStrategy<>() { + @Override + public int hashCode(Annotator object) { + return object.getClass().hashCode(); + } + + @Override + public boolean equals(Annotator o1, Annotator o2) { + return o1 == null || o2 == null ? o1==o2 : o1.getClass().equals(o2.getClass()); + } + }); + Set languages = new HashSet<>(); + for (PsiElement element : elements) { + Language language = element.getLanguage(); + addDialects(language, languages); + } + for (Language language : languages) { + List templates = LanguageAnnotators.INSTANCE.allForLanguageOrAny(language); + for (Annotator template : templates) { + Set supportedLanguages = map.get(template); + if (supportedLanguages == null) { + supportedLanguages = new HashSet<>(); + map.put(cloneTemplate(template), supportedLanguages); + } + supportedLanguages.add(language); + } + } + return map; + } + private static void addDialects(@NotNull Language language, @NotNull Set outProcessedLanguages) { + if (outProcessedLanguages.add(language)) { + Collection dialects = language.getTransitiveDialects(); + outProcessedLanguages.addAll(dialects); + } + } + + private void runAnnotator(@NotNull Annotator annotator, + @NotNull List insideThenOutside, + @NotNull Map> supportedLanguages) { + Set supported = supportedLanguages.get(annotator); + if (supported.isEmpty()) { + return; + } + AtomicReference currentElement = new AtomicReference<>(); + // create AnnotationHolderImpl for each Annotator to make it immutable thread-safe converter to the corresponding HighlightInfo + AnnotationHolderImpl annotationHolder = new AnnotationHolderImpl(myHighlightInfoHolder.getAnnotationSession(), myBatchMode) { + @Override + public boolean add(Annotation annotation) { + myAnnotatorStatisticsCollector.reportAnnotationProduced(annotator, annotation); + super.add(annotation); + return true; + } + + @Override + protected @NotNull B createBuilder(@NotNull HighlightSeverity severity, @Nls String message, PsiElement __, Object ___) { + return super.createBuilder(severity, message, currentElement.get(), annotator); + } + }; + HighlightersRecycler emptyElementRecycler = new HighlightersRecycler(); // no need to call incinerate/release because it's always empty + for (PsiElement element : insideThenOutside) { + if (!supported.contains(element.getLanguage())) { + continue; + } + if (myDumb && !DumbService.isDumbAware(annotator)) { + continue; + } + ProgressManager.checkCanceled(); + currentElement.set(element); + int sizeBefore = annotationHolder.size(); + annotator.annotate(element, annotationHolder); + int sizeAfter = annotationHolder.size(); + + List newInfos; + if (sizeBefore == sizeAfter) { + newInfos = List.of(); + } + else { + newInfos = new ArrayList<>(sizeAfter - sizeBefore); + for (int i = sizeBefore; i < sizeAfter; i++) { + Annotation annotation = annotationHolder.get(i); + HighlightInfo info = HighlightInfo.fromAnnotation(annotator.getClass(), annotation, myBatchMode); + info.setGroup(-1); // prevent DefaultHighlightProcessor from removing this info, we want to control it ourselves via `psiElementVisited` below + addConvertedToHostInfo(info, newInfos); + if (LOG.isDebugEnabled()) { + LOG.debug("runAnnotator annotation="+annotation+" -> "+newInfos); + } + } + results.addAll(newInfos); + } + Document hostDocument = myPsiFile.getFileDocument(); + if (hostDocument instanceof DocumentWindow w) hostDocument = w.getDelegate(); + HighlightInfoUpdater.getInstance(myProject).psiElementVisited(annotator.getClass(), element, newInfos, hostDocument, myPsiFile, myProject, + emptyElementRecycler, myHighlightingSession); + } + } + + private static void addPatchedInfos(@NotNull HighlightInfo info, + @NotNull PsiFile injectedPsi, + @NotNull DocumentWindow documentWindow, + @NotNull InjectedLanguageManager injectedLanguageManager, + @NotNull Consumer outInfos) { + ProperTextRange infoRange = new ProperTextRange(info.startOffset, info.endOffset); + List editables = injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, infoRange); + for (TextRange editable : editables) { + TextRange hostRange = documentWindow.injectedToHost(editable); + + boolean isAfterEndOfLine = info.isAfterEndOfLine(); + if (isAfterEndOfLine) { + // convert injected afterEndOfLine to either host's afterEndOfLine or not-afterEndOfLine highlight of the injected fragment boundary + int hostEndOffset = hostRange.getEndOffset(); + int lineNumber = documentWindow.getDelegate().getLineNumber(hostEndOffset); + int hostLineEndOffset = documentWindow.getDelegate().getLineEndOffset(lineNumber); + if (hostEndOffset < hostLineEndOffset) { + // convert to non-afterEndOfLine + isAfterEndOfLine = false; + hostRange = new ProperTextRange(hostRange.getStartOffset(), hostEndOffset+1); + } + } + + HighlightInfo patched = + new HighlightInfo(info.forcedTextAttributes, info.forcedTextAttributesKey, info.type, + hostRange.getStartOffset(), hostRange.getEndOffset(), + info.getDescription(), info.getToolTip(), info.getSeverity(), isAfterEndOfLine, null, + false, 0, info.getProblemGroup(), info.toolId, info.getGutterIconRenderer(), info.getGroup(), info.unresolvedReference); + patched.setHint(info.hasHint()); + + info.findRegisteredQuickFix((descriptor, quickfixTextRange) -> { + List editableQF = injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, quickfixTextRange); + for (TextRange editableRange : editableQF) { + TextRange hostEditableRange = documentWindow.injectedToHost(editableRange); + patched.registerFix(descriptor.getAction(), descriptor.myOptions, descriptor.getDisplayName(), hostEditableRange, descriptor.myKey); + } + return null; + }); + patched.markFromInjection(); + outInfos.accept(patched); + } + } + + private void addConvertedToHostInfo(@NotNull HighlightInfo info, @NotNull List newInfos) { + Document document = myPsiFile.getFileDocument(); + if (document instanceof DocumentWindow window) { + addPatchedInfos(info, myPsiFile, window, InjectedLanguageManager.getInstance(myProject), patched -> newInfos.add(patched)); + } + else { + newInfos.add(info); + } + } + + private @NotNull List cloneTemplates(@NotNull Collection templates) { + List result = new ArrayList<>(templates.size()); + for (Annotator template : templates) { + Annotator annotator = cloneTemplate(template); + if (annotator == null) continue; + result.add(annotator); + myAnnotatorStatisticsCollector.reportNewAnnotatorCreated(annotator); + } + return result; + } + + private static Annotator cloneTemplate(@NotNull Annotator template) { + Annotator annotator; + try { + annotator = ReflectionUtil.newInstance(template.getClass()); + } + catch (Exception e) { + LOG.error(PluginException.createByClass(e, template.getClass())); + return null; + } + return annotator; + } + + @NotNull + List getResults() { + return results; + } +} \ No newline at end of file diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/BackgroundUpdateHighlightersUtil.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/BackgroundUpdateHighlightersUtil.java index dd54b362b4af..fa5c6ee41077 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/BackgroundUpdateHighlightersUtil.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/BackgroundUpdateHighlightersUtil.java @@ -126,7 +126,7 @@ public final class BackgroundUpdateHighlightersUtil { Set infoSet = new HashSet<>(filteredInfos); Processor processor = info -> { - if (info.getGroup() == group) { + if (info.getGroup() == group && !info.isFromAnnotator()) { // ignore annotators, they are applied via HighlightInfoUpdater RangeHighlighterEx highlighter = info.getHighlighter(); int hiStart = highlighter.getStartOffset(); int hiEnd = highlighter.getEndOffset(); @@ -165,7 +165,7 @@ public final class BackgroundUpdateHighlightersUtil { if (UpdateHighlightersUtil.isWarningCoveredByError(info, severityRegistrar, overlappingIntervals)) { return true; } - if (info.getStartOffset() < priorityRange.getStartOffset() || info.getEndOffset() > priorityRange.getEndOffset()) { + if ((info.getStartOffset() < priorityRange.getStartOffset() || info.getEndOffset() > priorityRange.getEndOffset()) && !info.isFromAnnotator()) { // have to create RangeHighlighter later, to avoid exposing them to the markup model immediately, // thus messing the HighlightInfo.getStartOffset() leading to "sweep generator supplied infos in a wrong order" exception infosToCreateHighlightersFor.add(info); @@ -242,6 +242,10 @@ public final class BackgroundUpdateHighlightersUtil { List fileLevelHighlights = new ArrayList<>(); List infosToCreateHighlightersFor = new ArrayList<>(filteredInfos.size()); SweepProcessor.sweep(generator, (__, info, atStart, overlappingIntervals) -> { + //if (info.isFromAnnotator()) { + // // annotator infos are handled by HighlightInfoUpdater separately + // return true; + //} if (!atStart) { return true; } @@ -293,7 +297,7 @@ public final class BackgroundUpdateHighlightersUtil { infoStartOffset = Math.min(infoStartOffset, infoEndOffset); } if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine()) { - if (infoEndOffset == docLength) return; // empty highlighter beyond file boundaries + if (infoEndOffset == docLength) return; // empty highlighter beyond file boundaries infoEndOffset++; //show something in case of empty HighlightInfo } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightVisitor.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightVisitor.java index bbc0b267eba3..01502f45e630 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightVisitor.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightVisitor.java @@ -5,14 +5,7 @@ import com.intellij.codeInsight.daemon.AnnotatorStatisticsCollector; import com.intellij.codeInsight.daemon.impl.analysis.ErrorQuickFixProvider; import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder; import com.intellij.codeInsight.highlighting.HighlightErrorFilter; -import com.intellij.diagnostic.PluginException; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageAnnotators; import com.intellij.lang.LanguageUtil; -import com.intellij.lang.annotation.Annotation; -import com.intellij.lang.annotation.Annotator; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; @@ -22,41 +15,27 @@ import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.PsiFile; -import com.intellij.util.ReflectionUtil; -import com.intellij.util.containers.ConcurrentFactoryMap; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.Map; final class DefaultHighlightVisitor implements HighlightVisitor, DumbAware { - private static final Logger LOG = Logger.getInstance(DefaultHighlightVisitor.class); - private AnnotationHolderImpl myAnnotationHolder; - private final Map> myAnnotators = ConcurrentFactoryMap.createMap(language -> createAnnotators(language)); private final Project myProject; private final boolean myHighlightErrorElements; - private final boolean myRunAnnotators; - private final DumbService myDumbService; private HighlightInfoHolder myHolder; private final boolean myBatchMode; - private boolean myDumb; private final AnnotatorStatisticsCollector myAnnotatorStatisticsCollector = new AnnotatorStatisticsCollector(); @SuppressWarnings("UnusedDeclaration") DefaultHighlightVisitor(@NotNull Project project) { - this(project, true, true, false); + this(project, true, false); } DefaultHighlightVisitor(@NotNull Project project, boolean highlightErrorElements, - boolean runAnnotators, boolean batchMode) { myProject = project; myHighlightErrorElements = highlightErrorElements; - myRunAnnotators = runAnnotators; - myDumbService = DumbService.getInstance(project); myBatchMode = batchMode; } @@ -70,40 +49,19 @@ final class DefaultHighlightVisitor implements HighlightVisitor, DumbAware { boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action) { - myDumb = myDumbService.isDumb(); myHolder = holder; - myAnnotationHolder = new AnnotationHolderImpl(holder.getAnnotationSession(), myBatchMode) { - @Override - void queueToUpdateIncrementally() { - if (!isEmpty()) { - myAnnotatorStatisticsCollector.reportAnnotationProduced(myCurrentAnnotator, get(0)); - for (int i = 0; i < size(); i++) { - Annotation annotation = get(i); - holder.add(HighlightInfo.fromAnnotation(myCurrentAnnotator.getClass(), annotation, myBatchMode)); - } - clear(); - } - } - }; try { action.run(); - myAnnotationHolder.assertAllAnnotationsCreated(); } finally { - myAnnotators.clear(); myHolder = null; - myAnnotationHolder = null; - myAnnotatorStatisticsCollector.reportAnalysisFinished(myProject, holder.getAnnotationSession(), file); } return true; } @Override public void visit(@NotNull PsiElement element) { - if (myRunAnnotators) { - runAnnotators(element); - } if (element instanceof PsiErrorElement && myHighlightErrorElements) { visitErrorElement((PsiErrorElement)element); } @@ -112,26 +70,7 @@ final class DefaultHighlightVisitor implements HighlightVisitor, DumbAware { @SuppressWarnings("CloneDoesntCallSuperClone") @Override public @NotNull HighlightVisitor clone() { - return new DefaultHighlightVisitor(myProject, myHighlightErrorElements, myRunAnnotators, myBatchMode); - } - - private void runAnnotators(@NotNull PsiElement element) { - List annotators = myAnnotators.get(element.getLanguage()); - if (!annotators.isEmpty()) { - AnnotationHolderImpl holder = myAnnotationHolder; - holder.myCurrentElement = element; - for (Annotator annotator : annotators) { - if (!myDumb || DumbService.isDumbAware(annotator)) { - ProgressManager.checkCanceled(); - holder.myCurrentAnnotator = annotator; - annotator.annotate(element, holder); - // assume that annotator is done messing with just created annotations after its annotate() method completed, - // so we can start applying them incrementally at last - // (but not sooner, thanks to awfully racey Annotation.setXXX() API) - holder.queueToUpdateIncrementally(); - } - } - } + return new DefaultHighlightVisitor(myProject, myHighlightErrorElements, myBatchMode); } private void visitErrorElement(@NotNull PsiErrorElement element) { @@ -159,27 +98,6 @@ final class DefaultHighlightVisitor implements HighlightVisitor, DumbAware { return info; } - private @NotNull List cloneTemplates(@NotNull Collection templates) { - List result = new ArrayList<>(templates.size()); - for (Annotator template : templates) { - Annotator annotator; - try { - annotator = ReflectionUtil.newInstance(template.getClass()); - } - catch (Exception e) { - LOG.error(PluginException.createByClass(e, template.getClass())); - continue; - } - result.add(annotator); - myAnnotatorStatisticsCollector.reportNewAnnotatorCreated(annotator); - } - return result; - } - - private @NotNull List createAnnotators(@NotNull Language language) { - return cloneTemplates(LanguageAnnotators.INSTANCE.allForLanguageOrAny(language)); - } - private static @NotNull HighlightInfo.Builder createErrorElementInfoWithoutFixes(@NotNull PsiErrorElement element) { TextRange range = element.getTextRange(); String errorDescription = element.getErrorDescription(); diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java index 9793a9fdc394..82b990b9a8d5 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java @@ -56,6 +56,7 @@ import org.jetbrains.annotations.TestOnly; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; import java.util.function.Predicate; import java.util.function.Supplier; @@ -75,12 +76,14 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP final @NotNull ProperTextRange myPriorityRange; final List myHighlights = new ArrayList<>(); + final List myAnnotatorHighlights = Collections.synchronizedList(new ArrayList<>()); // have to store them separately to avoid double call setToEditor() protected volatile boolean myHasErrorElement; private volatile boolean myHasErrorSeverity; private volatile boolean myErrorFound; final EditorColorsScheme myGlobalScheme; private volatile @NotNull Supplier> myHighlightVisitorProducer = this::cloneHighlightVisitors; + private boolean myRunAnnotators = true; GeneralHighlightingPass(@NotNull PsiFile file, @NotNull Document document, @@ -286,35 +289,47 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP int chunkSize = Math.max(1, (elements1.size()+elements2.size()) / 100); // one percent precision is enough - boolean success = analyzeByVisitors(visitors, holder, 0, () -> { - LongStack nestedRange = new LongStack(); - Stack> nestedInfos = new Stack<>(); + BooleanSupplier runnable = () -> { + boolean success = analyzeByVisitors(visitors, holder, 0, () -> { + LongStack nestedRange = new LongStack(); + Stack> nestedInfos = new Stack<>(); - try (var ignored = HIGHLIGHTING_PERFORMANCE_ASSERT.runPass()) { - runVisitors(elements1, ranges1, chunkSize, skipParentsSet, holder, insideResult, outsideResult, forceHighlightParents, visitors, + try (var ignored = HIGHLIGHTING_PERFORMANCE_ASSERT.runPass()) { + runVisitors(elements1, ranges1, chunkSize, skipParentsSet, holder, insideResult, outsideResult, forceHighlightParents, visitors, + nestedRange, nestedInfos); + } + + boolean priorityIntersectionHasElements = myPriorityRange.intersectsStrict(myRestrictRange); + if ((!elements1.isEmpty() || !insideResult.isEmpty()) || priorityIntersectionHasElements) { // do not apply when there were no elements to highlight + myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced(myHighlightingSession, getEditor(), insideResult, myPriorityRange, myRestrictRange, getId()); + } + runVisitors(elements2, ranges2, chunkSize, skipParentsSet, holder, insideResult, outsideResult, forceHighlightParents, visitors, nestedRange, nestedInfos); + }); + // there can be extra highlights generated in PostHighlightVisitor + List postInfos; + synchronized (holder) { + postInfos = new ArrayList<>(holder.size()); + for (int j = 0; j < holder.size(); j++) { + HighlightInfo info = holder.get(j); + postInfos.add(info); + insideResult.add(info); + } } - - boolean priorityIntersectionHasElements = myPriorityRange.intersectsStrict(myRestrictRange); - if ((!elements1.isEmpty() || !insideResult.isEmpty()) || priorityIntersectionHasElements) { // do not apply when there were no elements to highlight - myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced(myHighlightingSession, getEditor(), insideResult, myPriorityRange, myRestrictRange, getId()); - } - runVisitors(elements2, ranges2, chunkSize, skipParentsSet, holder, insideResult, outsideResult, forceHighlightParents, visitors, - nestedRange, nestedInfos); - }); - // there can be extra highlights generated in PostHighlightVisitor - List postInfos; - synchronized (holder) { - postInfos = new ArrayList<>(holder.size()); - for (int j = 0; j < holder.size(); j++) { - HighlightInfo info = holder.get(j); - postInfos.add(info); - insideResult.add(info); - } + myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced(myHighlightingSession, getEditor(), + postInfos, getFile().getTextRange(), getFile().getTextRange(), POST_UPDATE_ALL); + return success; + }; + AnnotatorRunner annotatorRunner = myRunAnnotators ? new AnnotatorRunner(getFile(), false, holder, myHighlightingSession) : null; + boolean result; + if (annotatorRunner == null) { + result = runnable.getAsBoolean(); } - myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced(myHighlightingSession, getEditor(), - postInfos, getFile().getTextRange(), getFile().getTextRange(), POST_UPDATE_ALL); - return success; + else { + result = annotatorRunner.runAnnotatorsAsync(elements1, elements2, runnable); + outsideResult.addAll(annotatorRunner.getResults()); + } + return result; } private boolean analyzeByVisitors(HighlightVisitor @NotNull [] visitors, @@ -399,7 +414,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP // if this highlight info range is contained inside the current element range we are visiting // that means we can clear this highlight as soon as visitors won't produce any highlights during visiting the same range next time. // We also know that we can remove a syntax error element. - info.setVisitingTextRange(myFile, myDocument, elementRange); + info.setVisitingTextRange(getFile(), myDocument, elementRange); infosForThisRange.add(info); } holder.clear(); @@ -487,7 +502,8 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP synchronized (this) { added = super.add(info); } - if (info != null && added) { + // annotator infos are handled by HighlightInfoUpdater separately + if (info != null && added/* && !info.isFromAnnotator()*/) { queueInfoToUpdateIncrementally(info, info.getGroup() == 0 ? Pass.UPDATE_ALL : info.getGroup()); } return added; @@ -617,4 +633,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP private static @Nls String getPresentableNameText() { return AnalysisBundle.message("pass.syntax"); } + void setRunAnnotators(boolean run) { + myRunAnnotators = run; + } } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java index 42d69ef0e082..bcdf09838e55 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java @@ -13,10 +13,7 @@ import com.intellij.codeInspection.ex.InspectionToolWrapper; import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; import com.intellij.injected.editor.DocumentWindow; import com.intellij.lang.ASTNode; -import com.intellij.lang.annotation.Annotation; -import com.intellij.lang.annotation.ExternalAnnotator; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.lang.annotation.ProblemGroup; +import com.intellij.lang.annotation.*; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.modcommand.ModCommandAction; import com.intellij.openapi.diagnostic.Logger; @@ -322,7 +319,7 @@ public class HighlightInfo implements Segment { } public @Nullable @NonNls String getInspectionToolId() { - return toolId instanceof String ? (String)toolId : null; + return toolId instanceof String inspectionToolShortName ? inspectionToolShortName : null; } private boolean isFlagSet(@FlagConstant byte mask) { @@ -1074,4 +1071,7 @@ public class HighlightInfo implements Segment { void setUnresolvedReferenceQuickFixesComputed() { setFlag(UNRESOLVED_REFERENCE_QUICK_FIXES_COMPUTED_MASK, true); } + boolean isFromAnnotator() { + return toolId instanceof Class c && Annotator.class.isAssignableFrom(c); + } } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdater.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdater.java index 72a4129bcd8c..320446f8536e 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdater.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdater.java @@ -26,6 +26,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.injected.InjectedFileViewProvider; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; @@ -181,16 +182,15 @@ final class HighlightInfoUpdater { @NotNull HighlightersRecycler invalidElementRecycler, @NotNull HighlightingSession session) { List oldInfos = getInfosForVisitedPsi(psiFile, toolId, visitedPsiElement); - synchronized (oldInfos) { - if (LOG.isDebugEnabled()) { - LOG.debug("psiElementVisited: " + visitedPsiElement+ " in "+psiFile+" injected in "+InjectedLanguageManager.getInstance(project).injectedToHost(psiFile, psiFile.getTextRange())+ - "; tool:" + toolId + "; infos:" + newInfos+ "; oldInfos:" + oldInfos + "; document:" + hostDocument); - } - - if (!oldInfos.isEmpty() || !newInfos.isEmpty()) { - MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(hostDocument, project, true); - setHighlightersInRange(newInfos, oldInfos, markup, session, invalidElementRecycler); - } + if (LOG.isDebugEnabled()) { + LOG.debug("psiElementVisited: " + visitedPsiElement+ " in "+psiFile+ + (psiFile.getViewProvider() instanceof InjectedFileViewProvider ? + " injected in " + InjectedLanguageManager.getInstance(project).injectedToHost(psiFile, psiFile.getTextRange()) : "") + + "; tool:" + toolId + "; infos:" + newInfos + "; oldInfos:" + oldInfos + "; document:" + hostDocument); + } + if (!oldInfos.isEmpty() || !newInfos.isEmpty()) { + MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(hostDocument, project, true); + setHighlightersInRange(newInfos, oldInfos, markup, session, invalidElementRecycler); } // store back only after markup model changes are applied to avoid PCE thrown in the middle leaving corrupted data behind putInfosForVisitedPsi(psiFile, toolId, visitedPsiElement, newInfos); diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java index cdd3dc0cea9d..a6a1e8a37ed0 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightVisitorBasedInspection.java @@ -116,8 +116,9 @@ public final class HighlightVisitorBasedInspection extends GlobalSimpleInspectio for (GeneralHighlightingPass gpass : gpasses) { gpass.setHighlightVisitorProducer(() -> { gpass.incVisitorUsageCount(1); - return List.of(new DefaultHighlightVisitor(project, highlightErrorElements, runAnnotators, true)); + return List.of(new DefaultHighlightVisitor(project, highlightErrorElements, true)); }); + gpass.setRunAnnotators(runAnnotators); } } diff --git a/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java b/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java index 5220d45f7756..cb2cd906e9d9 100644 --- a/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java +++ b/platform/core-impl/src/com/intellij/concurrency/JobLauncher.java @@ -6,6 +6,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.PairProcessor; import com.intellij.util.Processor; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -89,4 +90,18 @@ public abstract class JobLauncher { * Use {@link com.intellij.openapi.application.Application#executeOnPooledThread(Runnable)} instead */ public abstract @NotNull Job submitToJobThread(final @NotNull Runnable action, @Nullable Consumer> onDoneCallback); + + @ApiStatus.Internal + public boolean procInOrderAsync(@NotNull ProgressIndicator progress, + int maxQueueSize, + @NotNull PairProcessor> thingProcessor, + @NotNull Processor> otherActions) throws ProcessCanceledException { + return false; + } + + public interface QueueController { + void enqueue(T element); + void dropEverythingAndPanic(); + void finish(); + } } diff --git a/platform/core-impl/src/com/intellij/core/CoreJobLauncher.java b/platform/core-impl/src/com/intellij/core/CoreJobLauncher.java index cf66a8ef23e1..1f407d1a9aa1 100644 --- a/platform/core-impl/src/com/intellij/core/CoreJobLauncher.java +++ b/platform/core-impl/src/com/intellij/core/CoreJobLauncher.java @@ -3,7 +3,9 @@ package com.intellij.core; import com.intellij.concurrency.Job; import com.intellij.concurrency.JobLauncher; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.PairProcessor; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -34,4 +36,5 @@ public class CoreJobLauncher extends JobLauncher { } return Job.nullJob(); } + } diff --git a/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java b/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java index a7aee38b5655..d45c14bd9e9d 100644 --- a/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java +++ b/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java @@ -3,6 +3,7 @@ package com.intellij.openapi.editor.impl; import com.intellij.codeInsight.daemon.GutterMark; import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; @@ -12,6 +13,7 @@ import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; import com.intellij.util.BitUtil; import com.intellij.util.Consumer; import org.intellij.lang.annotations.MagicConstant; @@ -79,6 +81,13 @@ sealed class RangeHighlighterImpl extends RangeMarkerImpl implements RangeHighli myModel = model; registerInTree(start, end, greedyToLeft, greedyToRight, layer); + if (LOG.isDebugEnabled()) { + LOG.debug("create: " + this); + if (getTextRange().equals(new TextRange(16, 22))) { + //new RuntimeException().printStackTrace(); + int i = 0; + } + } } private boolean isFlagSet(@Flag byte mask) { @@ -443,8 +452,16 @@ sealed class RangeHighlighterImpl extends RangeMarkerImpl implements RangeHighli getMarkupModel().removeHighlighter(this); } + private static final Logger LOG = Logger.getInstance(RangeHighlighterImpl.class); @Override public void dispose() { + if (LOG.isDebugEnabled()) { + if (getTextRange().equals(new TextRange(16, 22))) { + //new RuntimeException().printStackTrace(); + int i = 0; + } + LOG.debug("dispose: "+this); + } super.dispose(); GutterIconRenderer renderer = getGutterIconRenderer(); if (renderer instanceof Disposable disposableRenderer) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index 439a0cc7f3c7..f503e7e6c980 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -528,13 +528,16 @@ public final class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx } catch (Throwable e) { Throwable unwrapped = ExceptionUtilRt.unwrapException(e, ExecutionException.class); + LOG.debug("doRunPasses() thrown " + ExceptionUtil.getThrowableText(unwrapped)); if (progress.isCanceled() && progress.isRunning()) { unwrapped.addSuppressed(new RuntimeException("Daemon progress was canceled unexpectedly: " + progress)); } ExceptionUtil.rethrow(unwrapped); } finally { - progress.cancel(); + if (!progress.isCanceled()) { + ((DaemonProgressIndicator)progress).cancel("Cancel after highlighting. threads:\n"+ThreadDumper.dumpThreadsToString()); + } waitForTermination(); } return null; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightInfoProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightInfoProcessor.java index f1561d82860c..32bf4bcc7596 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightInfoProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultHighlightInfoProcessor.java @@ -44,11 +44,11 @@ public final class DefaultHighlightInfoProcessor extends HighlightInfoProcessor Document document = session.getDocument(); long modificationStamp = document.getModificationStamp(); TextRange priorityIntersection = priorityRange.intersection(restrictRange); - TextEditorHighlightingPass showAutoImportPass = editor == null ? null : getOrCreateShowAutoImportPass(editor, psiFile, session.getProgressIndicator()); - MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(document, project, true); if (priorityIntersection != null) { + MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(document, project, true); BackgroundUpdateHighlightersUtil.setHighlightersInRange(priorityIntersection, new ArrayList(infos), markupModel, groupId, session); } + TextEditorHighlightingPass showAutoImportPass = editor == null ? null : getOrCreateShowAutoImportPass(editor, psiFile, session.getProgressIndicator()); ApplicationManager.getApplication().invokeLater(() -> { if (editor != null && !editor.isDisposed() && modificationStamp == document.getModificationStamp()) { // usability: show auto import popup as soon as possible @@ -132,7 +132,7 @@ public final class DefaultHighlightInfoProcessor extends HighlightInfoProcessor @NotNull HighlightingSession session) { List toRemove = new ArrayList<>(); DaemonCodeAnalyzerEx.processHighlights(document, project, null, TextRangeScalarUtil.startOffset(range), TextRangeScalarUtil.endOffset(range), existing -> { - if (existing.getGroup() == Pass.UPDATE_ALL && TextRangeScalarUtil.startOffset(range) == existing.getVisitingTextRange().getStartOffset() && TextRangeScalarUtil.endOffset(range) == existing.getVisitingTextRange().getEndOffset()) { + if (existing.getGroup() == Pass.UPDATE_ALL && !existing.isFromAnnotator() && TextRangeScalarUtil.startOffset(range) == existing.getVisitingTextRange().getStartOffset() && TextRangeScalarUtil.endOffset(range) == existing.getVisitingTextRange().getEndOffset()) { if (infos != null) { for (HighlightInfo created : infos) { if (existing.equalsByActualOffset(created)) return true; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java index ce86bf604617..7193783d8e5d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InjectedGeneralHighlightingPass.java @@ -57,6 +57,13 @@ final class InjectedGeneralHighlightingPass extends GeneralHighlightingPass { myReducedRanges = reducedRanges; } + @Override + public @NotNull List getInfos() { + synchronized (myHighlights) { + return ContainerUtil.concat(myHighlights, myAnnotatorHighlights); + } + } + @Override protected @NotNull String getPresentableName() { return IdeBundle.message("highlighting.pass.injected.presentable.name"); @@ -92,10 +99,10 @@ final class InjectedGeneralHighlightingPass extends GeneralHighlightingPass { })); synchronized (myHighlights) { - // all infos for the "injected fragment for the host which is inside" are indeed inside + // all infos for the "injected fragment for the host which is inside" are inside indeed, // but some infos for the "injected fragment for the host which is outside" can be still inside if (resultOutside.isEmpty()) { - // apply only result (by default apply command) and only within inside + // apply only the result inside myHighlights.addAll(resultInside); myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced(myHighlightingSession, getEditor(), myHighlights, myRestrictRange, myRestrictRange, getId()); } @@ -230,16 +237,23 @@ final class InjectedGeneralHighlightingPass extends GeneralHighlightingPass { } HighlightInfoHolder holder = createInfoHolder(injectedPsi, documentWindow, injectedLanguageManager, outInfos); - runHighlightVisitorsForInjected(injectedPsi, holder); - highlightInjectedSyntax(injectedPsi, places, outInfos); + List elements = CollectHighlightsUtil.getElementsInRange(injectedPsi, 0, injectedPsi.getTextLength()); - if (!isDumbMode()) { - List todos = new ArrayList<>(); - highlightTodos(injectedPsi, injectedPsi.getText(), 0, injectedPsi.getTextLength(), myPriorityRange, todos, todos); - for (HighlightInfo info : todos) { - addPatchedInfos(info, injectedPsi, documentWindow, injectedLanguageManager, outInfos); + AnnotatorRunner annotatorRunner = new AnnotatorRunner(injectedPsi, false, holder, myHighlightingSession); + annotatorRunner.runAnnotatorsAsync(elements, List.of(), () -> { + runHighlightVisitorsForInjected(injectedPsi, holder, elements); + highlightInjectedSyntax(injectedPsi, places, outInfos); + + if (!isDumbMode()) { + List todos = new ArrayList<>(); + highlightTodos(injectedPsi, injectedPsi.getText(), 0, injectedPsi.getTextLength(), myPriorityRange, todos, todos); + for (HighlightInfo info : todos) { + addPatchedInfos(info, injectedPsi, documentWindow, injectedLanguageManager, outInfos); + } } - } + return true; + }); + myAnnotatorHighlights.addAll(annotatorRunner.getResults()); } private static void addPatchedInfos(@NotNull HighlightInfo info, @@ -285,10 +299,9 @@ final class InjectedGeneralHighlightingPass extends GeneralHighlightingPass { } } - private void runHighlightVisitorsForInjected(@NotNull PsiFile injectedPsi, @NotNull HighlightInfoHolder holder) { + private void runHighlightVisitorsForInjected(@NotNull PsiFile injectedPsi, @NotNull HighlightInfoHolder holder, List elements) { HighlightVisitor[] filtered = getHighlightVisitors(injectedPsi); try { - List elements = CollectHighlightsUtil.getElementsInRange(injectedPsi, 0, injectedPsi.getTextLength()); for (HighlightVisitor visitor : filtered) { visitor.analyze(injectedPsi, true, holder, () -> { for (PsiElement element : elements) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionRunner.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionRunner.java index ea4415e60843..0dac52e81ff2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionRunner.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionRunner.java @@ -179,7 +179,7 @@ class InspectionRunner { }); return true; }; - if (!((JobLauncherImpl)JobLauncher.getInstance()).procInOrderAsync(new SensitiveProgressWrapper(myProgress), initSize, contextProcessor, addToQueue -> { + if (!JobLauncher.getInstance().procInOrderAsync(new SensitiveProgressWrapper(myProgress), initSize, contextProcessor, addToQueue -> { // have to do all this even for empty elements, to perform correct cleanup/inspectionFinished if (init.isEmpty()) { addToQueue.finish(); @@ -189,15 +189,16 @@ class InspectionRunner { addToQueue.enqueue(context); } } + reportIdsOfInspectionsReportedAnyProblemToFUS(init); if (myInspectInjected && InjectionUtils.shouldInspectInjectedFiles(myPsiFile)) { // we don't run whole-file tools on injected fragments List localTools = ContainerUtil.filter(toolWrappers, t -> !t.runForWholeFile()); - inspectInjectedPsi(session, localTools, injectedContexts, applyIncrementallyCallback, - contextFinishedCallback, enabledToolsPredicate, addToInjectedQueue -> + return inspectInjectedPsi(session, localTools, injectedContexts, applyIncrementallyCallback, + contextFinishedCallback, enabledToolsPredicate, addToInjectedQueue -> getInjectedWithHosts(ContainerUtil.concat(restrictedInside, restrictedOutside), addToInjectedQueue)); } - reportIdsOfInspectionsReportedAnyProblemToFUS(init); + return true; })) { throw new ProcessCanceledException(); } @@ -418,7 +419,7 @@ class InspectionRunner { @NotNull ApplyIncrementallyCallback addDescriptorIncrementallyCallback, @NotNull Consumer contextFinishedCallback, @Nullable Condition enabledToolsPredicate, - @NotNull Consumer>> otherActions) { + @NotNull Processor>> otherActions) { PairProcessor, JobLauncherImpl.QueueController>> injectedProcessor = (pair,__) -> { executeInImpatientReadAction(() -> { PsiFile injectedPsi = pair.getFirst(); @@ -495,8 +496,8 @@ class InspectionRunner { } } - private void getInjectedWithHosts(@NotNull List elements, - @NotNull JobLauncherImpl.QueueController> addToQueue) { + private boolean getInjectedWithHosts(@NotNull List elements, + @NotNull JobLauncherImpl.QueueController> addToQueue) { Map injectedToHost = createInjectedFileMap(); Project project = myPsiFile.getProject(); for (PsiElement element : elements) { @@ -510,6 +511,7 @@ class InspectionRunner { }); } addToQueue.finish(); // no more injections + return true; } interface ApplyIncrementallyCallback { diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index 3a701df9ef1a..4aeea0350308 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -22,7 +22,10 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -381,12 +384,6 @@ public final class JobLauncherImpl extends JobLauncher { return (T)TOMBSTONE; } - public interface QueueController { - void enqueue(T element); - void dropEverythingAndPanic(); - void finish(); - } - /** * Method for producing some elements (to process via {@code thingProcessor}), and scheduling their processing along with ongoing calculations. * It has three parts: @@ -402,11 +399,12 @@ public final class JobLauncherImpl extends JobLauncher { * Guarantees all tasks are completed in the method end, or runtime exception is thrown, in which case some elements might be in-flight. * @return true if all processors returned true */ + @Override @ApiStatus.Internal public boolean procInOrderAsync(@NotNull ProgressIndicator progress, int maxQueueSize, @NotNull PairProcessor> thingProcessor, - @NotNull Consumer> otherActions) throws ProcessCanceledException { + @NotNull Processor> otherActions) throws ProcessCanceledException { progress.checkCanceled(); // do not start up expensive threads if there's no need to // optimization: if we know the max number of elements in the queue, use the cheaper ABQ BlockingQueue things = maxQueueSize < Integer.MAX_VALUE ? @@ -536,7 +534,9 @@ public final class JobLauncherImpl extends JobLauncher { try { // execute other actions while we are processing enqueued elements - otherActions.accept(addToQueue); + if (!otherActions.process(addToQueue)) { + futureResult.set(false); + } } catch (Exception e) { // in case of exception in normal flow, terminate background tasks diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/DocumentMarkupModelTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/DocumentMarkupModelTest.java index 77cf5a93ab13..1f1e1dc00846 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/DocumentMarkupModelTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/DocumentMarkupModelTest.java @@ -21,6 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.extensions.DefaultPluginDescriptor; import com.intellij.openapi.fileTypes.PlainTextFileType; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.testFramework.ExtensionTestUtil; import com.intellij.testFramework.fixtures.BasePlatformTestCase; import org.jetbrains.annotations.NotNull; @@ -117,7 +118,9 @@ public class DocumentMarkupModelTest extends BasePlatformTestCase { public static class TestAnnotator implements Annotator { @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { - holder.newSilentAnnotation(HighlightSeverity.INFORMATION).create(); + if (element instanceof PsiFile) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION).create(); + } } } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java index cd0f4c8a64f6..d7d22cc2b9a9 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java @@ -262,7 +262,6 @@ public final class GroovyAnnotator extends GroovyElementVisitor { @Override public void visitVariableDeclaration(@NotNull GrVariableDeclaration variableDeclaration) { - checkDuplicateModifiers(myHolder, variableDeclaration.getModifierList(), null); if (variableDeclaration.isTuple()) { final GrModifierList list = variableDeclaration.getModifierList(); @@ -385,7 +384,10 @@ public final class GroovyAnnotator extends GroovyElementVisitor { } checkTypeDefinition(myHolder, typeDefinition); - checkImplementedMethodsOfClass(myHolder, typeDefinition); + // enum constants are not handled here because their getTextOffset() is crazy - outside their own getTextRange() + if (!(typeDefinition instanceof PsiEnumConstantInitializer)) { + checkImplementedMethodsOfClass(myHolder, typeDefinition); + } checkConstructors(myHolder, typeDefinition); checkAnnotationCollector(myHolder, typeDefinition); @@ -993,6 +995,8 @@ public final class GroovyAnnotator extends GroovyElementVisitor { } else { checkVariableModifiers(myHolder, declaration); + GrVariable[] variables = declaration.getVariables(); + checkDuplicateModifiers(myHolder, modifierList, variables.length == 0 ? null : variables[0]); } } else if (parent instanceof GrClassInitializer) { @@ -1731,7 +1735,7 @@ public final class GroovyAnnotator extends GroovyElementVisitor { final TextRange range = GrHighlightUtil.getClassHeaderTextRange(typeDefinition); String message = GroovyBundle.message("method.is.not.implemented", notImplementedMethodName); - AnnotationBuilder builder = + AnnotationBuilder builder = holder.newAnnotation(HighlightSeverity.ERROR, message) .range(range); registerImplementsMethodsFix(typeDefinition, abstractMethod, builder, message, range).create(); @@ -1944,8 +1948,9 @@ public final class GroovyAnnotator extends GroovyElementVisitor { } } - private static void checkDuplicateModifiers(AnnotationHolder holder, @NotNull GrModifierList list, PsiMember member) { + private static void checkDuplicateModifiers(AnnotationHolder holder, @NotNull GrModifierList list, PsiElement member) { final PsiElement[] modifiers = list.getModifiers(); + if (modifiers.length <= 1) return; Set set = new HashSet<>(modifiers.length); for (PsiElement modifier : modifiers) { if (modifier instanceof GrAnnotation) continue; @@ -1953,10 +1958,12 @@ public final class GroovyAnnotator extends GroovyElementVisitor { if (set.contains(name)) { String message = GroovyBundle.message("duplicate.modifier", name); AnnotationBuilder builder = - holder.newAnnotation(HighlightSeverity.ERROR, message).range(list); + holder.newAnnotation(HighlightSeverity.ERROR, message).range(modifier); + GrModifierFix fix = member instanceof PsiMember ? new GrModifierFix((PsiMember)member, name, false, false, GrModifierFix.MODIFIER_LIST) : + member instanceof GrVariable ? new GrModifierFix((GrVariable)member, name, false, GrModifierFix.MODIFIER_LIST) : + null; if (member != null) { - builder = registerLocalFix(builder, new GrModifierFix(member, name, false, false, GrModifierFix.MODIFIER_LIST), list, message, - ProblemHighlightType.ERROR, list.getTextRange()); + builder = registerLocalFix(builder, fix, list, message, ProblemHighlightType.ERROR, list.getTextRange()); } builder.create(); } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GroovyHighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GroovyHighlightingTest.groovy index d76119580ed0..c8c47bf1da47 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GroovyHighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GroovyHighlightingTest.groovy @@ -1274,7 +1274,7 @@ class B extends A { void testUnresolvedQualifierHighlighting() { doTestHighlighting('''\ -Abc.Cde abc +Abc.Cde abc ''') } @@ -1422,7 +1422,7 @@ class X{} @A('ab') class Y{} -public public class Z {} +public public class Z {} ''') } diff --git a/plugins/groovy/testdata/highlighting/AnonymousClassAbstractMethod.groovy b/plugins/groovy/testdata/highlighting/AnonymousClassAbstractMethod.groovy index 3b47ccce558d..ad6e3a11294d 100644 --- a/plugins/groovy/testdata/highlighting/AnonymousClassAbstractMethod.groovy +++ b/plugins/groovy/testdata/highlighting/AnonymousClassAbstractMethod.groovy @@ -1,7 +1,7 @@ import java.awt.event.ActionEvent import java.awt.event.ActionListener -def x=new ActionListener() { +def x=new ActionListener() { def actionPerformed(ActionEvent e) { } diff --git a/plugins/groovy/testdata/highlighting/DefaultInitializersAreNotAllowedInAbstractMethods.groovy b/plugins/groovy/testdata/highlighting/DefaultInitializersAreNotAllowedInAbstractMethods.groovy index 0e7b0aafede6..567adb41e647 100644 --- a/plugins/groovy/testdata/highlighting/DefaultInitializersAreNotAllowedInAbstractMethods.groovy +++ b/plugins/groovy/testdata/highlighting/DefaultInitializersAreNotAllowedInAbstractMethods.groovy @@ -22,7 +22,7 @@ enum E { String bar(x = 2) } -def a = new Runnable() { +def a = new Runnable() { void run() {} abstract foo(x = 5); } diff --git a/plugins/groovy/testdata/highlighting/FieldModifiers.groovy b/plugins/groovy/testdata/highlighting/FieldModifiers.groovy index e68c9e3d8604..34aca1927fe7 100644 --- a/plugins/groovy/testdata/highlighting/FieldModifiers.groovy +++ b/plugins/groovy/testdata/highlighting/FieldModifiers.groovy @@ -20,7 +20,7 @@ class Combinations { } class Duplicates { - public public a + public public a } interface I { diff --git a/plugins/groovy/testdata/highlighting/ScriptFieldModifiers.groovy b/plugins/groovy/testdata/highlighting/ScriptFieldModifiers.groovy index 30acb221a456..f4e67f3c1720 100644 --- a/plugins/groovy/testdata/highlighting/ScriptFieldModifiers.groovy +++ b/plugins/groovy/testdata/highlighting/ScriptFieldModifiers.groovy @@ -17,4 +17,4 @@ import groovy.transform.Field @Field protected public c @Field volatile final g -@Field public public d +@Field public public d diff --git a/plugins/groovy/testdata/highlighting/VariableDeclarationDuplicateModifiers.groovy b/plugins/groovy/testdata/highlighting/VariableDeclarationDuplicateModifiers.groovy index 78b0a3e634cf..fddd077ca770 100644 --- a/plugins/groovy/testdata/highlighting/VariableDeclarationDuplicateModifiers.groovy +++ b/plugins/groovy/testdata/highlighting/VariableDeclarationDuplicateModifiers.groovy @@ -1 +1 @@ -def final def foo = 44 \ No newline at end of file +def final def foo = 44 \ No newline at end of file diff --git a/plugins/groovy/testdata/highlighting/pre30/typeAnnotations.groovy b/plugins/groovy/testdata/highlighting/pre30/typeAnnotations.groovy index d0e064600ef9..2ea0f9c85799 100644 --- a/plugins/groovy/testdata/highlighting/pre30/typeAnnotations.groovy +++ b/plugins/groovy/testdata/highlighting/pre30/typeAnnotations.groovy @@ -12,11 +12,11 @@ class JSR308BaseClass {} interface JSR308Interface1 {} interface JSR308Interface2@JSR308 CharSequence> {} -class JSR308Class extends @JSR308 JSR308BaseClass<@JSR308 List> implements @JSR308 JSR308Interface1<@JSR308 String>, @JSR308 JSR308Interface2<@JSR308 String> { +class JSR308Class extends @JSR308 JSR308BaseClass<@JSR308 List> implements @JSR308 JSR308Interface1<@JSR308 String>, @JSR308 JSR308Interface2<@JSR308 String> { @JSR308 private String name; @JSR308 List<@JSR308 String> test(@JSR308 List<@JSR308 ? extends @JSR308 Object> list) throws @JSR308 IOException, @JSR308 java.sql.SQLException { - @JSR308 List<@JSR308 String> localVar = new @JSR308 ArrayList<@JSR308 String>(); + @JSR308 List<@JSR308 String> localVar = new @JSR308 ArrayList<@JSR308 String>(); try { for (e in list) { @@ -27,9 +27,9 @@ class JSR308Class extends @JSR308 JSR308BaseClass<@JSR308 [] strs = new String @JSR308 [] { 'a' } - String @JSR308 [] @JSR308 [] strs2 = new String @JSR308 [] @JSR308 [] { new String[] {'a', 'b'} } + String @JSR308 [] @JSR308 [] strs2 = new String @JSR308 [] @JSR308 [] { new String[] {'a', 'b'} } String [][] @JSR308 [] strs3 = new String [1][2] @JSR308 [] - String [] @JSR308 [] @JSR308 [] @JSR308 [] strs4 = new String [1] @JSR308 [2] @JSR308 [] @JSR308 [] + String [] @JSR308 [] @JSR308 [] @JSR308 [] strs4 = new String [1] @JSR308 [2] @JSR308 [] @JSR308 [] localVar.add(strs[0]) localVar.add(strs2[0][1]) diff --git a/plugins/javaFX/testData/highlighting/defaultTagInList.fxml b/plugins/javaFX/testData/highlighting/defaultTagInList.fxml index b4bfe857567d..055450393fb8 100644 --- a/plugins/javaFX/testData/highlighting/defaultTagInList.fxml +++ b/plugins/javaFX/testData/highlighting/defaultTagInList.fxml @@ -9,6 +9,6 @@ id/> -