Repository: SonarSource/sonarlint-core
Pull request #1296: SLCORE-1081 Batch automatic analyses
Author: login: damien-urruty-sonarsource
Created: 2025-03-24T15:54:51Z | Merged: 2025-03-25T13:05:10Z

[SLCORE-1081](https://sonarsource.atlassian.net/browse/SLCORE-1081)



[SLCORE-1081]: https://sonarsource.atlassian.net/browse/SLCORE-1081?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueue.java b/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueue.java
index e96dfcf6b9..0040e1bde6 100644
--- a/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueue.java
+++ b/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueue.java
@@ -27,11 +27,13 @@
 import java.util.Optional;
 import java.util.PriorityQueue;
 import java.util.function.Predicate;
+import java.util.stream.Stream;
 import org.sonarsource.sonarlint.core.analysis.command.AnalyzeCommand;
 import org.sonarsource.sonarlint.core.analysis.command.Command;
 import org.sonarsource.sonarlint.core.analysis.command.NotifyModuleEventCommand;
 import org.sonarsource.sonarlint.core.analysis.command.RegisterModuleCommand;
 import org.sonarsource.sonarlint.core.analysis.command.UnregisterModuleCommand;
+import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
 
 import static java.util.Map.entry;
 
@@ -58,8 +60,7 @@ public synchronized Command takeNextCommand() throws InterruptedException {
       var firstReadyCommand = pollNextReadyCommand();
       if (firstReadyCommand.isPresent()) {
         var command = firstReadyCommand.get();
-        tidyUp(command);
-        return command;
+        return tidyUp(command);
       }
       // wait for a new command to come in
       wait();
@@ -85,25 +86,48 @@ private Optional<Command> pollNextReadyCommand() {
     return Optional.empty();
   }
 
-  private void tidyUp(Command nextCommand) {
+  private Command tidyUp(Command nextCommand) {
     cleanUpOutdatedCommands(nextCommand);
+    return batchAutomaticAnalyses(nextCommand);
   }
 
   private void cleanUpOutdatedCommands(Command nextCommand) {
     if (nextCommand instanceof UnregisterModuleCommand unregisterCommand) {
-      removeAll(command -> command instanceof AnalyzeCommand analyzeCommand && analyzeCommand.getModuleKey().equals(unregisterCommand.getModuleKey())
+      removeAll(command -> (command instanceof AnalyzeCommand analyzeCommand && analyzeCommand.getModuleKey().equals(unregisterCommand.getModuleKey()))
         || command instanceof NotifyModuleEventCommand);
     }
   }
 
-  private void removeAll(Predicate<Command> predicate) {
+  private Command batchAutomaticAnalyses(Command nextCommand) {
+    if (nextCommand instanceof AnalyzeCommand analyzeCommand && analyzeCommand.getTriggerType().canBeBatchedWithSameTriggerType()) {
+      var removedCommands = (List<AnalyzeCommand>) removeAll(otherCommand -> canBeBatched(analyzeCommand, otherCommand));
+      removedCommands.forEach(AnalyzeCommand::cancel);
+      SonarLintLogger.get().debug("Merging analysis command with " + removedCommands.size() + " commands");
+      return Stream.concat(Stream.of(analyzeCommand), removedCommands.stream())
+        .sorted((c1, c2) -> (int) (c1.getSequenceNumber() - c2.getSequenceNumber()))
+        .reduce(AnalyzeCommand::mergeWith)
+        // this last clause should never occur
+        .orElse(analyzeCommand);
+    }
+    return nextCommand;
+  }
+
+  private static boolean canBeBatched(AnalyzeCommand analyzeCommand, Command otherCommand) {
+    return otherCommand instanceof AnalyzeCommand otherAnalyzeCommand && otherAnalyzeCommand.getModuleKey().equals(analyzeCommand.getModuleKey())
+      && otherAnalyzeCommand.getTriggerType().canBeBatchedWithSameTriggerType();
+  }
+
+  private List<? extends Command> removeAll(Predicate<Command> predicate) {
     var iterator = queue.iterator();
-    while (queue.iterator().hasNext()) {
+    var removedCommands = new ArrayList<Command>();
+    while (iterator.hasNext()) {
       var command = iterator.next();
       if (predicate.test(command)) {
         iterator.remove();
+        removedCommands.add(command);
       }
     }
+    return removedCommands;
   }
 
   private static class CommandComparator implements Comparator<Command> {
@@ -121,7 +145,7 @@ private static class CommandComparator implements Comparator<Command> {
     public int compare(Command command, Command otherCommand) {
       var commandRank = COMMAND_TYPES_ORDERED.get(command.getClass());
       var otherCommandRank = COMMAND_TYPES_ORDERED.get(otherCommand.getClass());
-      return !Objects.equals(commandRank, otherCommandRank) ? commandRank - otherCommandRank :
+      return !Objects.equals(commandRank, otherCommandRank) ? (commandRank - otherCommandRank) :
       // for same command types, respect insertion order
         (int) (command.getSequenceNumber() - otherCommand.getSequenceNumber());
     }
diff --git a/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java b/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java
index 57573f853f..bca8637bf8 100644
--- a/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java
+++ b/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java
@@ -20,5 +20,23 @@
 package org.sonarsource.sonarlint.core.analysis.api;
 
 public enum TriggerType {
-  AUTO, FORCED
+  AUTO(true, true),
+  FORCED(false, false),
+  FORCED_WITH_EXCLUSIONS(true, false);
+
+  private final boolean honorExclusions;
+  private final boolean canBeBatchedWithSameTriggerType;
+
+  TriggerType(boolean honorExclusions, boolean canBeBatchedWithSameTriggerType) {
+    this.honorExclusions = honorExclusions;
+    this.canBeBatchedWithSameTriggerType = canBeBatchedWithSameTriggerType;
+  }
+
+  public boolean shouldHonorExclusions() {
+    return honorExclusions;
+  }
+
+  public boolean canBeBatchedWithSameTriggerType() {
+    return canBeBatchedWithSameTriggerType;
+  }
 }
diff --git a/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/AnalyzeCommand.java b/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/AnalyzeCommand.java
index 7484db87f9..fc9da23f32 100644
--- a/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/AnalyzeCommand.java
+++ b/backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/command/AnalyzeCommand.java
@@ -19,17 +19,20 @@
  */
 package org.sonarsource.sonarlint.core.analysis.command;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.sonarsource.sonarlint.core.analysis.api.AnalysisConfiguration;
 import org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;
 import org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;
 import org.sonarsource.sonarlint.core.analysis.api.Issue;
+import org.sonarsource.sonarlint.core.analysis.api.TriggerType;
 import org.sonarsource.sonarlint.core.analysis.container.global.ModuleRegistry;
 import org.sonarsource.sonarlint.core.commons.api.SonarLanguage;
 import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
@@ -42,6 +45,7 @@ public class AnalyzeCommand extends Command {
   private static final SonarLintLogger LOG = SonarLintLogger.get();
 
   private final String moduleKey;
+  private final TriggerType triggerType;
   private final Supplier<AnalysisConfiguration> configurationSupplier;
   private final Consumer<Issue> issueListener;
   @Nullable
@@ -51,9 +55,10 @@ public class AnalyzeCommand extends Command {
   private final Consumer<List<ClientInputFile>> analysisStarted;
   private final Supplier<Boolean> isReadySupplier;
 
-  public AnalyzeCommand(@Nullable String moduleKey, Supplier<AnalysisConfiguration> configurationSupplier, Consumer<Issue> issueListener,
+  public AnalyzeCommand(@Nullable String moduleKey, TriggerType triggerType, Supplier<AnalysisConfiguration> configurationSupplier, Consumer<Issue> issueListener,
     @Nullable Trace trace, ProgressMonitor progressMonitor, Consumer<List<ClientInputFile>> analysisStarted, Supplier<Boolean> isReadySupplier) {
     this.moduleKey = moduleKey;
+    this.triggerType = triggerType;
     this.configurationSupplier = configurationSupplier;
     this.issueListener = issueListener;
     this.trace = trace;
@@ -71,6 +76,10 @@ public String getModuleKey() {
     return moduleKey;
   }
 
+  public TriggerType getTriggerType() {
+    return triggerType;
+  }
+
   public CompletableFuture<AnalysisResults> getResult() {
     return result;
   }
@@ -158,6 +167,26 @@ private AnalysisResults doRunAnalysis(ModuleRegistry moduleRegistry, ProgressMon
     }
   }
 
+  public AnalyzeCommand mergeWith(AnalyzeCommand otherNewerAnalyzeCommand) {
+    var analysisConfiguration = configurationSupplier.get();
+    var newerAnalysisConfiguration = otherNewerAnalyzeCommand.configurationSupplier.get();
+    var mergedInputFiles = new ArrayList<>(newerAnalysisConfiguration.inputFiles());
+    var newInputFileUris = newerAnalysisConfiguration.inputFiles().stream().map(ClientInputFile::uri).collect(Collectors.toSet());
+    for (ClientInputFile inputFile : analysisConfiguration.inputFiles()) {
+      if (!newInputFileUris.contains(inputFile.uri())) {
+        mergedInputFiles.add(inputFile);
+      }
+    }
+    var mergedAnalysisConfiguration = AnalysisConfiguration.builder()
+      .addActiveRules(newerAnalysisConfiguration.activeRules())
+      .setBaseDir(newerAnalysisConfiguration.baseDir())
+      .putAllExtraProperties(newerAnalysisConfiguration.extraProperties())
+      .addInputFiles(mergedInputFiles)
+      .build();
+    return new AnalyzeCommand(otherNewerAnalyzeCommand.moduleKey, otherNewerAnalyzeCommand.triggerType, () -> mergedAnalysisConfiguration, otherNewerAnalyzeCommand.issueListener,
+      otherNewerAnalyzeCommand.trace, otherNewerAnalyzeCommand.progressMonitor, otherNewerAnalyzeCommand.analysisStarted, otherNewerAnalyzeCommand.isReadySupplier);
+  }
+
   @Override
   public void cancel() {
     progressMonitor.cancel();
diff --git a/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueueTest.java b/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueueTest.java
index f3cb144f46..42f2acc7e7 100644
--- a/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueueTest.java
+++ b/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/AnalysisQueueTest.java
@@ -30,7 +30,7 @@ class AnalysisQueueTest {
   @Test
   void it_should_prioritize_register_module_commands_over_analyses() throws InterruptedException {
     var analysisQueue = new AnalysisQueue();
-    analysisQueue.post(new AnalyzeCommand(null, null, null, null, null, null, () -> true));
+    analysisQueue.post(new AnalyzeCommand(null, null, null, null, null, null, null, () -> true));
     var registerModuleCommand = new RegisterModuleCommand(null);
     analysisQueue.post(registerModuleCommand);
 
diff --git a/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/mediumtests/AnalysisSchedulerMediumTests.java b/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/mediumtests/AnalysisSchedulerMediumTests.java
index f24c4408cb..12a12ae846 100644
--- a/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/mediumtests/AnalysisSchedulerMediumTests.java
+++ b/backend/analysis-engine/src/test/java/org/sonarsource/sonarlint/core/analysis/mediumtests/AnalysisSchedulerMediumTests.java
@@ -51,6 +51,7 @@
 import org.sonarsource.sonarlint.core.analysis.api.ClientModuleFileSystem;
 import org.sonarsource.sonarlint.core.analysis.api.ClientModuleInfo;
 import org.sonarsource.sonarlint.core.analysis.api.Issue;
+import org.sonarsource.sonarlint.core.analysis.api.TriggerType;
 import org.sonarsource.sonarlint.core.analysis.command.AnalyzeCommand;
 import org.sonarsource.sonarlint.core.analysis.command.RegisterModuleCommand;
 import org.sonarsource.sonarlint.core.commons.api.SonarLanguage;
@@ -110,7 +111,7 @@ def foo():
       .setBaseDir(baseDir)
       .build();
     List<Issue> issues = new ArrayList<>();
-    var command = new AnalyzeCommand(null, () -> analysisConfig, issues::add, null, progressMonitor, NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER);
+    var command = new AnalyzeCommand(null, TriggerType.FORCED, () -> analysisConfig, issues::add, null, progressMonitor, NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER);
     analysisScheduler.post(command);
     command.getResult().get();
     assertThat(issues).hasSize(1);
@@ -135,7 +136,8 @@ def foo():
       .build();
     List<Issue> issues = new ArrayList<>();
     analysisScheduler.post(new RegisterModuleCommand(new ClientModuleInfo("moduleKey", aModuleFileSystem())));
-    var analyzeCommand = new AnalyzeCommand("moduleKey", () -> analysisConfig, issues::add, null, progressMonitor, NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER);
+    var analyzeCommand = new AnalyzeCommand("moduleKey", TriggerType.FORCED, () -> analysisConfig, issues::add, null, progressMonitor, NO_OP_ANALYSIS_STARTED_CONSUMER,
+      ANALYSIS_READY_SUPPLIER);
     analysisScheduler.post(analyzeCommand);
     analyzeCommand.getResult().get();
     assertThat(issues).hasSize(1);
@@ -147,7 +149,7 @@ def foo():
 
   @Test
   void should_fail_the_future_if_the_analyze_command_execution_fails() {
-    var command = new AnalyzeCommand("moduleKey", () -> {
+    var command = new AnalyzeCommand("moduleKey", TriggerType.FORCED, () -> {
       throw new RuntimeException("Kaboom");
     }, issue -> {
     }, null, progressMonitor, NO_OP_ANALYSIS_STARTED_CONSUMER, ANALYSIS_READY_SUPPLIER);
@@ -173,7 +175,8 @@ def foo():
       .addActiveRules(trailingCommentRule())
       .setBaseDir(baseDir)
       .build();
-    var analyzeCommand = new AnalyzeCommand("moduleKey", () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, inputFiles -> pause(300), ANALYSIS_READY_SUPPLIER);
+    var analyzeCommand = new AnalyzeCommand("moduleKey", TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, inputFiles -> pause(300),
+      ANALYSIS_READY_SUPPLIER);
     analysisScheduler.post(analyzeCommand);
 
     analysisScheduler.stop();
@@ -198,8 +201,10 @@ def foo():
       .addActiveRules(trailingCommentRule())
       .setBaseDir(baseDir)
       .build();
-    var analyzeCommand = new AnalyzeCommand("moduleKey", () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, inputFiles -> pause(300), ANALYSIS_READY_SUPPLIER);
-    var secondAnalyzeCommand = new AnalyzeCommand("moduleKey", () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, NO_OP_ANALYSIS_STARTED_CONSUMER,
+    var analyzeCommand = new AnalyzeCommand("moduleKey", TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, inputFiles -> pause(300),
+      ANALYSIS_READY_SUPPLIER);
+    var secondAnalyzeCommand = new AnalyzeCommand("moduleKey", TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor,
+      NO_OP_ANALYSIS_STARTED_CONSUMER,
       ANALYSIS_READY_SUPPLIER);
     analysisScheduler.post(analyzeCommand);
     analysisScheduler.post(secondAnalyzeCommand);
@@ -229,7 +234,7 @@ def foo():
       .setBaseDir(baseDir)
       .build();
     var threadTermination = new AtomicReference<String>();
-    var analyzeCommand = new AnalyzeCommand("moduleKey", () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, inputFiles -> {
+    var analyzeCommand = new AnalyzeCommand("moduleKey", TriggerType.FORCED, () -> analysisConfig, NO_OP_ISSUE_LISTENER, null, progressMonitor, inputFiles -> {
       try {
         Thread.sleep(3000);
       } catch (InterruptedException e) {
diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisService.java
index 6a552525db..9ba5e677e7 100644
--- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisService.java
+++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/analysis/AnalysisService.java
@@ -697,7 +697,7 @@ public CompletableFuture<AnalysisResult> scheduleAnalysis(String configurationSc
     var progressMonitor = new RpcProgressMonitor(client, cancelChecker, configurationScopeId, analysisId);
     var ruleDetailsCache = new ConcurrentHashMap<String, RuleDetailsForAnalysis>();
     var rawIssues = new ArrayList<RawIssue>();
-    var analysisTask = new AnalyzeCommand(configurationScopeId, () -> getAnalysisConfigForEngine(configurationScopeId, files, extraProperties, false, triggerType),
+    var analysisTask = new AnalyzeCommand(configurationScopeId, triggerType, () -> getAnalysisConfigForEngine(configurationScopeId, files, extraProperties, false, triggerType),
       issue -> streamIssue(configurationScopeId, analysisId, ruleDetailsCache, rawIssues, issue), monitoringService.newTrace("AnalysisService", "analyze"), progressMonitor,
       inputFiles -> analysisStarted(configurationScopeId, analysisId, inputFiles), () -> analysisReadinessByConfigScopeId.getOrDefault(configurationScopeId, false));
     return schedule(configurationScopeId, analysisTask, analysisId, rawIssues, startTime, shouldFetchServerIssues);
@@ -747,7 +747,7 @@ private AnalyzeCommand getAnalyzeCommand(String configurationScopeId, List<URI>
     UUID analysisId) {
     var progressMonitor = new RpcProgressMonitor(client, new SonarLintCancelMonitor(), configurationScopeId, analysisId);
     var ruleDetailsCache = new ConcurrentHashMap<String, RuleDetailsForAnalysis>();
-    return new AnalyzeCommand(configurationScopeId, () -> getAnalysisConfigForEngine(configurationScopeId, files, Map.of(), hotspotsOnly, triggerType),
+    return new AnalyzeCommand(configurationScopeId, triggerType, () -> getAnalysisConfigForEngine(configurationScopeId, files, Map.of(), hotspotsOnly, triggerType),
       issue -> streamIssue(configurationScopeId, analysisId, ruleDetailsCache, rawIssues, issue), monitoringService.newTrace("AnalysisService", "analyze"), progressMonitor,
       inputFiles -> analysisStarted(configurationScopeId, analysisId, inputFiles), () -> analysisReadinessByConfigScopeId.getOrDefault(configurationScopeId, false));
   }
diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileExclusionService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileExclusionService.java
index a32a943146..1401c6bf70 100644
--- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileExclusionService.java
+++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/fs/FileExclusionService.java
@@ -192,7 +192,7 @@ public boolean isExcluded(URI fileUri) {
   }
 
   public List<ClientFile> refineAnalysisScope(String configScopeId, List<URI> requestedFileUris, TriggerType triggerType, Path baseDir) {
-    if (triggerType.equals(TriggerType.FORCED)) {
+    if (!triggerType.shouldHonorExclusions()) {
       var filteredURIsNoFile = new ArrayList<URI>();
       var filesToAnalyze = requestedFileUris.stream().map(uri -> {
         var file = findFile(configScopeId, uri);
diff --git a/backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AnalysisRpcServiceDelegate.java b/backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AnalysisRpcServiceDelegate.java
index bddf9f6603..dcd678f2c6 100644
--- a/backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AnalysisRpcServiceDelegate.java
+++ b/backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/AnalysisRpcServiceDelegate.java
@@ -140,8 +140,7 @@ public CompletableFuture<AnalyzeFilesResponse> analyzeFilesAndTrack(AnalyzeFiles
     return requestAsync(cancelChecker -> {
       var analysisResults = getBean(AnalysisService.class)
         .scheduleAnalysis(params.getConfigurationScopeId(), params.getAnalysisId(), params.getFilesToAnalyze(), params.getExtraProperties(), params.getStartTime(),
-          // consider this method as an automatic analysis. This will take exclusions into account
-          params.isShouldFetchServerIssues(), TriggerType.AUTO, cancelChecker)
+          params.isShouldFetchServerIssues(), TriggerType.FORCED_WITH_EXCLUSIONS, cancelChecker)
         .join();
       return generateAnalyzeFilesResponse(analysisResults);
     }, configurationScopeId);


Reviews:
user: login: kirill-knize-sonarsource
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T10:06:37Z
user: login: kirill-knize-sonarsource
body: One comment, else LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T10:06:53Z
user: login: damien-urruty-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T12:50:04Z

Review comments:
diff_hunk: @@ -20,5 +20,5 @@
 package org.sonarsource.sonarlint.core.analysis.api;
 
 public enum TriggerType {
-  AUTO, FORCED
+  AUTO, FORCED, ANALYZE_FILES_AND_TRACK
path: backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java
user: login: kirill-knize-sonarsource
body: We can change the naming or document in code what is the rationale behind these 3 types.
created_at: 2025-03-25T10:06:37Z
author_association: MEMBER
diff_hunk: @@ -20,5 +20,5 @@
 package org.sonarsource.sonarlint.core.analysis.api;
 
 public enum TriggerType {
-  AUTO, FORCED
+  AUTO, FORCED, ANALYZE_FILES_AND_TRACK
path: backend/analysis-engine/src/main/java/org/sonarsource/sonarlint/core/analysis/api/TriggerType.java
user: login: damien-urruty-sonarsource
body: I change the names of the constants, and also tried to make things more explicit in the enum. Let me know what you think
created_at: 2025-03-25T12:50:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqube-next[bot]
created_at: 2025-03-25T12:47:41Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&resolved=false) [0 New issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&issueStatuses=ACCEPTED) [1 Accepted issue](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296) [0 Security Hotspots](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&metric=new_coverage&view=list) [87.7% Coverage on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.sonarlint.core%3Asonarlint-core-parent&pullRequest=1296)


author_association: NONE