All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonarsource.sonarlint.omnisharp.OmnisharpSensor Maven / Gradle / Ivy

There is a newer version: 1.26.0.100277
Show newest version
/*
 * SonarOmnisharp
 * Copyright (C) 2021-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonarsource.sonarlint.omnisharp;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.sonar.api.batch.fs.FilePredicate;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonarsource.analyzer.commons.ProgressReport;
import org.sonarsource.sonarlint.omnisharp.protocol.Diagnostic;
import org.sonarsource.sonarlint.omnisharp.protocol.DiagnosticLocation;
import org.sonarsource.sonarlint.omnisharp.protocol.Fix;
import org.sonarsource.sonarlint.omnisharp.protocol.OmnisharpEndpoints;
import org.sonarsource.sonarlint.omnisharp.protocol.QuickFix;
import org.sonarsource.sonarlint.omnisharp.protocol.QuickFixEdit;

public class OmnisharpSensor implements Sensor {

  private static final Logger LOG = Loggers.get(OmnisharpSensor.class);

  private final OmnisharpServerController server;
  private final OmnisharpEndpoints omnisharpEndpoints;

  public OmnisharpSensor(OmnisharpServerController server, OmnisharpEndpoints omnisharpEndpoints) {
    this.server = server;
    this.omnisharpEndpoints = omnisharpEndpoints;
  }

  @Override
  public void describe(SensorDescriptor descriptor) {
    descriptor
      .name("OmniSharp")
      .onlyOnLanguage(OmnisharpPlugin.LANGUAGE_KEY)
      .createIssuesForRuleRepositories(OmnisharpPlugin.REPOSITORY_KEY)
      .onlyWhenConfiguration(c -> c.hasKey(CSharpPropertyDefinitions.getOmnisharpMonoLocation())
        || c.hasKey(CSharpPropertyDefinitions.getOmnisharpWinLocation())
        || c.hasKey(CSharpPropertyDefinitions.getOmnisharpNet6Location()));
  }

  @Override
  public void execute(SensorContext context) {
    FilePredicate predicate = context.fileSystem().predicates().hasLanguage(OmnisharpPlugin.LANGUAGE_KEY);
    if (!context.fileSystem().hasFiles(predicate)) {
      return;
    }
    try {
      Path dotnetCliExePath = context.config().get(CSharpPropertyDefinitions.getDotnetCliExeLocation()).map(Paths::get).orElse(null);
      Path monoExePath = context.config().get(CSharpPropertyDefinitions.getMonoExeLocation()).map(Paths::get).orElse(null);
      Path msBuildPath = context.config().get(CSharpPropertyDefinitions.getMSBuildPath()).map(Paths::get).orElse(null);
      Path solutionPath = context.config().get(CSharpPropertyDefinitions.getSolutionPath()).map(Paths::get).orElse(null);
      boolean useFramework = context.config().getBoolean(CSharpPropertyDefinitions.getUseNet6()).orElse(false);
      boolean loadProjectsOnDemand = context.config().getBoolean(CSharpPropertyDefinitions.getLoadProjectsOnDemand()).orElse(false);
      int startupTimeOutSec = context.config().getInt(CSharpPropertyDefinitions.getStartupTimeout()).orElse(60);
      int loadProjectsTimeOutSec = context.config().getInt(CSharpPropertyDefinitions.getLoadProjectsTimeout()).orElse(60);
      server.lazyStart(context.fileSystem().baseDir().toPath(), useFramework, loadProjectsOnDemand, dotnetCliExePath, monoExePath, msBuildPath, solutionPath, startupTimeOutSec,
        loadProjectsTimeOutSec);
    } catch (InterruptedException e) {
      LOG.warn("Interrupted", e);
      Thread.currentThread().interrupt();
      return;
    } catch (Exception e) {
      throw new IllegalStateException("Unable to start OmniSharp", e);
    }

    try {
      server.whenReady().get();
      analyze(context, predicate);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    } catch (ExecutionException e) {
      if (e.getCause() instanceof TimeoutException) {
        LOG.error("Timeout waiting for the solution to be loaded." +
          " You can find help on https://docs.sonarsource.com/sonarlint/intellij/using-sonarlint/scan-my-project/#supported-features-in-rider" +
          " or https://docs.sonarsource.com/sonarlint/vs-code/getting-started/requirements/#c-analysis");
        return;
      }
      throw new IllegalStateException("Analysis failed: " + e.getMessage(), e.getCause());
    }
  }

  private void analyze(SensorContext context, FilePredicate predicate) {
    JsonObject config = buildRulesConfig(context);
    omnisharpEndpoints.config(config);

    ProgressReport progressReport = new ProgressReport("Report about progress of OmniSharp analyzer", TimeUnit.SECONDS.toMillis(10));
    progressReport.start(StreamSupport.stream(context.fileSystem().inputFiles(predicate).spliterator(), false).map(InputFile::toString).collect(Collectors.toList()));
    boolean successfullyCompleted = false;
    boolean cancelled = false;
    try {
      for (InputFile inputFile : context.fileSystem().inputFiles(predicate)) {
        if (context.isCancelled()) {
          cancelled = true;
          break;
        }
        scanFile(context, inputFile);
        progressReport.nextFile();
      }
      successfullyCompleted = !cancelled;
    } finally {
      if (successfullyCompleted) {
        progressReport.stop();
      } else {
        progressReport.cancel();
      }
    }
  }

  private static JsonObject buildRulesConfig(SensorContext context) {
    JsonObject config = new JsonObject();
    JsonArray rulesJson = new JsonArray();
    for (ActiveRule activeRule : context.activeRules().findByRepository(OmnisharpPlugin.REPOSITORY_KEY)) {
      JsonObject ruleJson = new JsonObject();
      ruleJson.addProperty("ruleId", activeRule.ruleKey().rule());
      if (!activeRule.params().isEmpty()) {
        JsonObject paramsJson = new JsonObject();
        for (Map.Entry param : activeRule.params().entrySet()) {
          paramsJson.addProperty(param.getKey(), param.getValue());
        }
        ruleJson.add("params", paramsJson);
      }
      rulesJson.add(ruleJson);
    }
    config.add("activeRules", rulesJson);
    return config;
  }

  private void scanFile(SensorContext context, InputFile f) {
    String buffer;
    try {
      buffer = f.contents();
    } catch (IOException e) {
      throw new IllegalStateException("Unable to read file buffer", e);
    }
    omnisharpEndpoints.updateBuffer(f.file(), buffer);
    omnisharpEndpoints.codeCheck(f.file(), diag -> handle(context, diag));
  }

  private static void handle(SensorContext context, Diagnostic diag) {
    var ruleKey = RuleKey.of(OmnisharpPlugin.REPOSITORY_KEY, diag.getId());
    if (context.activeRules().find(ruleKey) != null) {
      var diagFilePath = Paths.get(diag.getFilename());
      var diagInputFile = findInputFile(context, diagFilePath);
      if (diagInputFile != null) {
        var newIssue = context.newIssue();
        newIssue
          .forRule(ruleKey)
          .at(createLocation(newIssue, diag, diagInputFile));
        handleSecondaryLocations(context, diag, newIssue);
        handleQuickFixes(context, diag, newIssue);
        newIssue.save();
      }
    }
  }

  private static void handleQuickFixes(SensorContext context, Diagnostic diag, NewIssue newIssue) {
    var quickFixes = diag.getQuickFixes();
    if (quickFixes != null && quickFixes.length > 0) {
      newIssue.setQuickFixAvailable(true);
      for (var quickFix : quickFixes) {
        handleQuickFix(context, quickFix, newIssue);
      }
    }
  }

  static void handleQuickFix(SensorContext context, QuickFix quickFix, NewIssue newIssue) {
    var newQuickFix = newIssue.newQuickFix();
    newQuickFix.message(quickFix.getMessage());
    for (Fix fix : quickFix.getFixes()) {
      var fixInputFile = findInputFile(context, Paths.get(fix.getFilename()));
      if (fixInputFile != null) {
        var newInputFileEdit = newQuickFix.newInputFileEdit()
          .on(fixInputFile);
        for (QuickFixEdit edit : fix.getEdits()) {
          var newTextEdit = newInputFileEdit.newTextEdit()
            .at(fixInputFile.newRange(edit.getStartLine(), edit.getStartColumn() - 1, edit.getEndLine(), edit.getEndColumn() - 1))
            .withNewText(edit.getNewText());
          newInputFileEdit.addTextEdit(newTextEdit);
        }
        newQuickFix.addInputFileEdit(newInputFileEdit);
      }
    }
    newIssue.addQuickFix(newQuickFix);
  }

  private static void handleSecondaryLocations(SensorContext context, Diagnostic diag, NewIssue newIssue) {
    var additionalLocations = diag.getAdditionalLocations();
    if (additionalLocations != null) {
      for (var additionalLocation : additionalLocations) {
        var additionalFilePath = Paths.get(additionalLocation.getFilename());
        var additionalFilePathInputFile = findInputFile(context, additionalFilePath);
        if (additionalFilePathInputFile != null) {
          newIssue.addLocation(createLocation(newIssue, additionalLocation, additionalFilePathInputFile));
        }
      }
    }
  }

  private static InputFile findInputFile(SensorContext context, Path filePath) {
    return context.fileSystem().inputFile(context.fileSystem().predicates().is(filePath.toFile()));
  }

  private static NewIssueLocation createLocation(NewIssue newIssue, DiagnosticLocation location, InputFile inputFile) {
    return newIssue.newLocation()
      .on(inputFile)
      .at(inputFile.newRange(location.getLine(), location.getColumn() - 1, location.getEndLine(), location.getEndColumn() - 1))
      .message(location.getText());
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy